diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..a5ff9bf --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "deny": [ + "Read(**/obj/**)", + "Read(**/bin/**)", + "Read(**/.fable/**)", + "Read(**/__pycache__/**)", + "Read(**/*.pyc)" + ] + } +} diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index af6390c..805b3c8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,7 +10,7 @@ "rollForward": false }, "fable": { - "version": "5.0.0-alpha.17", + "version": "5.0.0-alpha.20", "commands": [ "fable" ], diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d61327a..4eb301d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,9 +20,9 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: | - 6.x 8.x 9.x + 10.x - name: Install just uses: extractions/setup-just@v2 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 8add6af..9252951 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -34,7 +34,6 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: | - 6.x 8.x 9.x 10.x diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ef88c22 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# Fable.Python + +F# to Python compiler extension for Fable. + +## Project Structure + +- `src/stdlib/` - Python standard library bindings (Builtins, Json, Os, etc.) +- `src/flask/` - Flask web framework bindings +- `src/fastapi/` - FastAPI web framework bindings +- `src/pydantic/` - Pydantic model bindings +- `test/` - Test files +- `examples/` - Example applications (flask, fastapi, django, timeflies) +- `build/` - Generated Python output (gitignored) + +## Build Commands + +```bash +just clean # Clean all build artifacts (build/, obj/, bin/, .fable/) +just build # Build the project +just test-python # Run Python tests +just restore # Restore .NET and paket dependencies +just example-flask # Build and run Flask example +just example-fastapi # Build and run FastAPI example +just dev-fastapi # Run FastAPI with hot-reload +``` + +## Build Output + +Generated Python code goes to `build/` directories (gitignored): +- `build/` - Main library output +- `build/tests/` - Test output +- `examples/*/build/` - Example outputs + +## Key Concepts + +### Fable Type Serialization + +F# types compile to non-native Python types: + +- `int` → `Int32` (not Python's `int`) +- `int64` → `Int64` +- F# array → `FSharpArray` (not Python's `list`) +- `ResizeArray` → Python `list` +- `nativeint` → Python `int` + +Use `Fable.Python.Json.dumps` with `fableDefault` for JSON serialization of Fable types. +Use `ResizeArray` for collections in web API responses. +Use Pydantic `BaseModel` for FastAPI request/response types (handles `Int32` correctly). + +See `JSON.md` for detailed serialization documentation. + +### Decorator Attributes + +Route decorators use `Py.DecorateTemplate`: + +```fsharp +[] +type GetAttribute(path: string) = inherit Attribute() +``` + +Class attributes use `Py.ClassAttributesTemplate` for Pydantic-style classes. diff --git a/JSON.md b/JSON.md new file mode 100644 index 0000000..8de8336 --- /dev/null +++ b/JSON.md @@ -0,0 +1,186 @@ +# JSON Serialization with Fable.Python + +This document explains how to properly serialize F# types to JSON when using Fable.Python. + +## The Problem + +When Fable compiles F# to Python, certain types are not native Python types: + +| F# Type | Fable Python Type | Native Python? | +| ---------------- | -------------------------- | -------------- | +| `int` | `Int32` | No | +| `int64` | `Int64` | No | +| `float32` | `Float32` | No | +| F# record | Class with `__slots__` | No | +| F# union | Class with `tag`, `fields` | No | +| F# array | `FSharpArray` | No | +| `ResizeArray` | `list` | Yes | +| `nativeint` | `int` | Yes | +| `string` | `str` | Yes | + +Python's standard `json.dumps()` and web framework serializers (Flask's `jsonify`, FastAPI's `jsonable_encoder`) don't know how to serialize these Fable-specific types. + +### Why Can't Fable's Int32 Just Inherit from Python's int? + +Fable.Python uses [PyO3](https://pyo3.rs/) for its runtime. Due to [PyO3 limitations](https://github.com/PyO3/pyo3/issues/991), it's not possible to create a Rust type that subclasses Python's immutable `int` type. This means `Int32` is a separate type that needs special handling during serialization. + +## The Solution: fableDefault + +The `Fable.Python.Json` module provides a `fableDefault` function that handles Fable types: + +```fsharp +open Fable.Python.Json + +// Use the convenience function (recommended) +let jsonStr = dumps myObject + +// Or use json.dumps with fableDefault explicitly +let jsonStr = json.dumps(myObject, ``default`` = fableDefault) + +// With indentation +let prettyJson = dumpsIndented myObject 2 +``` + +### What fableDefault Handles + +| Type | Serialization | +| ------------------------------------- | ------------------------------------------- | +| `Int8`, `Int16`, `Int32`, `Int64` | → Python `int` | +| `UInt8`, `UInt16`, `UInt32`, `UInt64` | → Python `int` | +| `Float32`, `Float64` | → Python `float` | +| F# Records (with `__slots__`) | → Python `dict` | +| F# Unions (with `tag`, `fields`) | → `["CaseName", ...fields]` or `"CaseName"` | + +## Usage Examples + +### Basic Serialization + +```fsharp +open Fable.Python.Json + +// Anonymous record with F# int (compiles to Int32) +let data = {| id = 42; name = "Alice" |} +let json = dumps data +// Output: {"id": 42, "name": "Alice"} + +// F# record +type User = { Id: int; Name: string } +let user = { Id = 1; Name = "Bob" } +let json = dumps user +// Output: {"Id": 1, "Name": "Bob"} + +// F# discriminated union +type Status = Active | Inactive | Pending of string +let status = Pending "review" +let json = dumps status +// Output: ["Pending", "review"] +``` + +### With Web Frameworks + +#### Flask + +Flask's `jsonify` does **not** handle Fable types. Use `dumps` from `Fable.Python.Json`: + +```fsharp +open Fable.Python.Flask +open Fable.Python.Json + +[] +type Routes() = + [")>] + static member get_user(user_id: int) : string = + // Use dumps for Fable type support + dumps {| id = user_id; name = "Alice" |} + + [] + static member simple() : obj = + // jsonify works ONLY with native Python types + jsonify {| message = "Hello"; count = 42n |} // 'n' suffix = native int +``` + +#### FastAPI + +FastAPI's `jsonable_encoder` does **not** handle Fable types in anonymous records. You have two options: + +**Option 1: Use Pydantic models** (recommended for FastAPI) + +```fsharp +open Fable.Python.FastAPI +open Fable.Python.Pydantic + +[] +type UserResponse(Id: int, Name: string) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + +[] +type API() = + [] + static member get_user(user_id: int) : UserResponse = + UserResponse(Id = user_id, Name = "Alice") // Works! Pydantic handles Int32 +``` + +**Option 2: Use nativeint for anonymous records** + +```fsharp +[] +static member delete_item(item_id: int) : obj = + {| status = "deleted"; id = nativeint item_id |} // Convert to native int +``` + +### Collections + +Use `ResizeArray` instead of F# arrays for web API responses: + +```fsharp +// Good - ResizeArray compiles to Python list +let users = ResizeArray() +users.Add(User(Id = 1, Name = "Alice")) +let json = dumps users + +// Avoid - F# array compiles to FSharpArray +let users = [| User(Id = 1, Name = "Alice") |] // May not serialize correctly +``` + +## Quick Reference + +| Scenario | Solution | +|----------|----------| +| JSON API with Fable types | Use `Fable.Python.Json.dumps` | +| Flask endpoint | Use `dumps` instead of `jsonify` | +| FastAPI endpoint | Use Pydantic models or `nativeint` | +| Int literals in anonymous records | Use `42n` suffix for native int | +| Collections in API responses | Use `ResizeArray` | +| F# array needed | Convert with `ResizeArray(myArray)` | + +## API Reference + +```fsharp +module Fable.Python.Json + +/// Default serializer for Fable types +val fableDefault: obj -> obj + +/// Serialize to JSON with Fable type support +val dumps: obj -> string + +/// Serialize to JSON with indentation +val dumpsIndented: obj -> int -> string + +/// Serialize to file with Fable type support +val dump: obj -> TextIOWrapper -> unit + +/// Serialize to file with indentation +val dumpIndented: obj -> TextIOWrapper -> int -> unit + +/// Raw Python json module (use with fableDefault for Fable types) +val json: IExports +``` + +## Further Reading + +- [PyO3 Issue #991](https://github.com/PyO3/pyo3/issues/991) - Why Int32 can't subclass Python's int +- [Python json module](https://docs.python.org/3/library/json.html) - Standard library documentation +- [Pydantic](https://docs.pydantic.dev/) - Data validation for Python (works with Fable's Int32) diff --git a/examples/.paket/Paket.Restore.targets b/examples/.paket/Paket.Restore.targets deleted file mode 100644 index 712cd77..0000000 --- a/examples/.paket/Paket.Restore.targets +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - $(MSBuildVersion) - 15.0.0 - false - true - - true - $(MSBuildThisFileDirectory) - $(MSBuildThisFileDirectory)..\ - $(PaketRootPath)paket-files\paket.restore.cached - $(PaketRootPath)paket.lock - classic - proj - assembly - native - /Library/Frameworks/Mono.framework/Commands/mono - mono - - - $(PaketRootPath)paket.bootstrapper.exe - $(PaketToolsPath)paket.bootstrapper.exe - $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ - - "$(PaketBootStrapperExePath)" - $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" - - - - - true - true - - - True - - - False - - $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) - - - - - - - - - $(PaketRootPath)paket - $(PaketToolsPath)paket - - - - - - $(PaketRootPath)paket.exe - $(PaketToolsPath)paket.exe - - - - - - <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) - <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) - <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false - - - - - - - - - - - <_PaketCommand>dotnet paket - - - - - - $(PaketToolsPath)paket - $(PaketBootStrapperExeDir)paket - - - paket - - - - - <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" - - - - - - - - - - - - - - - - - - - - - true - $(NoWarn);NU1603;NU1604;NU1605;NU1608 - false - true - - - - - - - - - $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) - - - - - - - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) - - - - - %(PaketRestoreCachedKeyValue.Value) - %(PaketRestoreCachedKeyValue.Value) - - - - - true - false - true - - - - - true - - - - - - - - - - - - - - - - - - - $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached - - $(MSBuildProjectFullPath).paket.references - - $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references - - $(MSBuildProjectDirectory)\paket.references - - false - true - true - references-file-or-cache-not-found - - - - - $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) - $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) - references-file - false - - - - - false - - - - - true - target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) - - - - - - - - - - - false - true - - - - - - - - - - - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[2]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) - - - %(PaketReferencesFileLinesInfo.PackageVersion) - All - runtime - $(ExcludeAssets);contentFiles - $(ExcludeAssets);build;buildMultitargeting;buildTransitive - %(PaketReferencesFileLinesInfo.Aliases) - true - true - - - - - %(PaketReferencesFileLinesInfo.PackageVersion) - - - - - $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools - - - - - - - - - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) - - - %(PaketCliToolFileLinesInfo.PackageVersion) - - - - - - - - - - false - - - - - - <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> - - - - - - $(MSBuildProjectDirectory)/$(MSBuildProjectFile) - true - false - true - false - true - false - true - false - true - false - true - $(PaketIntermediateOutputPath)\$(Configuration) - $(PaketIntermediateOutputPath) - - - - <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/django-minimal/DjangoMinimal.fsproj b/examples/django-minimal/DjangoMinimal.fsproj index c3f22c9..0bae7d3 100644 --- a/examples/django-minimal/DjangoMinimal.fsproj +++ b/examples/django-minimal/DjangoMinimal.fsproj @@ -1,15 +1,15 @@ - - Exe - net5.0 - 3390;$(WarnOn) - - - - - - - - + + Exe + net9.0 + 3390;$(WarnOn) + + + + + + + + \ No newline at end of file diff --git a/examples/django-minimal/paket.references b/examples/django-minimal/paket.references new file mode 100644 index 0000000..bf9c02c --- /dev/null +++ b/examples/django-minimal/paket.references @@ -0,0 +1 @@ +group Examples diff --git a/examples/django-minimal/requirements.txt b/examples/django-minimal/requirements.txt deleted file mode 100644 index 699ce5c..0000000 --- a/examples/django-minimal/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Django>=3.2.8,<4.0.0 \ No newline at end of file diff --git a/examples/django/Django.fsproj b/examples/django/Django.fsproj index 179dca5..2192839 100644 --- a/examples/django/Django.fsproj +++ b/examples/django/Django.fsproj @@ -1,19 +1,19 @@ - - Exe - net5.0 - 3390;$(WarnOn) - - - - - - - - - - - - + + Exe + net9.0 + 3390;$(WarnOn) + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/django/paket.references b/examples/django/paket.references new file mode 100644 index 0000000..bf9c02c --- /dev/null +++ b/examples/django/paket.references @@ -0,0 +1 @@ +group Examples diff --git a/examples/django/requirements.txt b/examples/django/requirements.txt deleted file mode 100644 index 699ce5c..0000000 --- a/examples/django/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Django>=3.2.8,<4.0.0 \ No newline at end of file diff --git a/examples/fastapi/.gitignore b/examples/fastapi/.gitignore new file mode 100644 index 0000000..c152436 --- /dev/null +++ b/examples/fastapi/.gitignore @@ -0,0 +1,19 @@ +# Generated Python code +build/ + +# Python +__pycache__/ +*.pyc +.venv/ + +# .NET +bin/ +obj/ + +# Fable +.fable/ +fable_modules/ + +# Generated Fable code +src +*.py \ No newline at end of file diff --git a/examples/fastapi/App.fs b/examples/fastapi/App.fs new file mode 100644 index 0000000..a47b9ec --- /dev/null +++ b/examples/fastapi/App.fs @@ -0,0 +1,107 @@ +/// FastAPI Example Application +/// Demonstrates how to use Fable.Python.FastAPI with decorator attributes +module App + +open Fable.Core +open Fable.Python.FastAPI +open Fable.Python.Pydantic +open Models + +// Create the FastAPI application instance +let app = FastAPI(title = "Fable.Python FastAPI Example", version = "1.0.0") + +// ============================================================================ +// Sample Data Store (in-memory for demonstration) +// ============================================================================ + +let users = ResizeArray() +let items = ResizeArray() + +// Initialize with sample data +let user1 = User(Id = 1, Name = "Alice", Email = "alice@example.com") +let user2 = User(Id = 2, Name = "Bob", Email = "bob@example.com") +users.Add(user1) +users.Add(user2) + +let item1 = Item(Id = 1, Name = "Laptop", Price = 999.99, InStock = true) +let item2 = Item(Id = 2, Name = "Mouse", Price = 29.99, InStock = true) +items.Add(item1) +items.Add(item2) + +// ============================================================================ +// API Endpoints using Class-based Pattern with Decorators +// ============================================================================ + +[] +type API() = + /// Root endpoint - welcome message + [] + static member root() : obj = + {| message = "Welcome to Fable.Python + FastAPI!" + version = "1.0.0" |} + + /// Health check endpoint + [] + static member health() : obj = + {| status = "healthy" + users_count = users.Count + items_count = items.Count |} + + /// Get all users + [] + static member get_users() : ResizeArray = + users + + /// Get a user by ID + [] + static member get_user(user_id: int) : obj = + match users |> Seq.tryFind (fun u -> u.Id = user_id) with + | Some user -> user :> obj + | None -> {| error = "User not found" |} + + /// Create a new user + [] + static member create_user(request: CreateUserRequest) : obj = + let newId = if users.Count = 0 then 1 else (users |> Seq.map (fun u -> u.Id) |> Seq.max) + 1 + let newUser = User(Id = newId, Name = request.Name, Email = request.Email) + users.Add(newUser) + {| status = "created"; user = newUser |} + + /// Get all items + [] + static member get_items() : ResizeArray = + items + + /// Get an item by ID + [] + static member get_item(item_id: int) : obj = + match items |> Seq.tryFind (fun i -> i.Id = item_id) with + | Some item -> item :> obj + | None -> {| error = "Item not found" |} + + /// Create a new item + [] + static member create_item(request: CreateItemRequest) : obj = + let newId = if items.Count = 0 then 1 else (items |> Seq.map (fun i -> i.Id) |> Seq.max) + 1 + let newItem = Item(Id = newId, Name = request.Name, Price = request.Price, InStock = request.InStock) + items.Add(newItem) + {| status = "created"; item = newItem |} + + /// Update an item + [] + static member update_item(item_id: int, request: CreateItemRequest) : obj = + match items |> Seq.tryFindIndex (fun i -> i.Id = item_id) with + | Some index -> + let updatedItem = Item(Id = item_id, Name = request.Name, Price = request.Price, InStock = request.InStock) + items.[index] <- updatedItem + {| status = "updated"; item = updatedItem |} + | None -> {| error = "Item not found" |} + + /// Delete an item + [] + static member delete_item(item_id: int) : obj = + match items |> Seq.tryFindIndex (fun i -> i.Id = item_id) with + | Some index -> + items.RemoveAt(index) + {| status = "deleted"; deleted_id = nativeint item_id |} + | None -> {| error = "Item not found" |} diff --git a/examples/fastapi/FastApi.fsproj b/examples/fastapi/FastApi.fsproj new file mode 100644 index 0000000..f5a4e6b --- /dev/null +++ b/examples/fastapi/FastApi.fsproj @@ -0,0 +1,15 @@ + + + + net9.0 + Exe + + + + + + + + + + \ No newline at end of file diff --git a/examples/fastapi/Models.fs b/examples/fastapi/Models.fs new file mode 100644 index 0000000..ddb6a0b --- /dev/null +++ b/examples/fastapi/Models.fs @@ -0,0 +1,46 @@ +/// Pydantic Models for the FastAPI Example +/// Demonstrates how to create Pydantic models using F# classes +module Models + +open Fable.Core +open Fable.Python.Pydantic + +// ============================================================================ +// User Models +// ============================================================================ + +/// User model - a Pydantic BaseModel +[] +type User(Id: int, Name: string, Email: string) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + member val Email: string = Email with get, set + +/// Request model for creating a new user +[] +type CreateUserRequest(Name: string, Email: string) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Email: string = Email with get, set + +// ============================================================================ +// Item Models +// ============================================================================ + +/// Item model - a Pydantic BaseModel +[] +type Item(Id: int, Name: string, Price: float, InStock: bool) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + member val Price: float = Price with get, set + member val InStock: bool = InStock with get, set + +/// Request model for creating/updating an item +[] +type CreateItemRequest(Name: string, Price: float, InStock: bool) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Price: float = Price with get, set + member val InStock: bool = InStock with get, set diff --git a/examples/fastapi/README.md b/examples/fastapi/README.md new file mode 100644 index 0000000..35a937b --- /dev/null +++ b/examples/fastapi/README.md @@ -0,0 +1,130 @@ +# FastAPI Example + +This example demonstrates how to build a FastAPI application using **Fable.Python** with the decorator-based API pattern. + +## Features + +- Class-based API endpoints using `[]` attribute +- HTTP method decorators: `[]`, `[]`, `[]`, `[]` +- Pydantic models for request/response validation +- Automatic OpenAPI documentation generation + +## Project Structure + +``` +examples/fastapi/ +├── App.fs # Main application with API endpoints +├── Models.fs # Pydantic model definitions +├── PydanticExample.fsproj +├── build/ # Generated Python code (git-ignored) +└── README.md +``` + +## Building + +From the repository root: + +```bash +# Using justfile +just example-fastapi + +# Or manually +cd examples/fastapi +dotnet fable --lang python --outDir build +``` + +## Running + +```bash +cd examples/fastapi/build +uv run --group fastapi uvicorn app:app --reload +``` + +Then visit: +- http://localhost:8000 - API root +- http://localhost:8000/docs - Interactive Swagger UI documentation +- http://localhost:8000/redoc - ReDoc documentation + +## API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/` | Welcome message | +| GET | `/health` | Health check | +| GET | `/users` | List all users | +| GET | `/users/{user_id}` | Get user by ID | +| POST | `/users` | Create a new user | +| GET | `/items` | List all items | +| GET | `/items/{item_id}` | Get item by ID | +| POST | `/items` | Create a new item | +| PUT | `/items/{item_id}` | Update an item | +| DELETE | `/items/{item_id}` | Delete an item | + +## Example Usage + +```bash +# Get all users +curl http://localhost:8000/users + +# Create a new user +curl -X POST http://localhost:8000/users \ + -H "Content-Type: application/json" \ + -d '{"Name": "Charlie", "Email": "charlie@example.com"}' + +# Get all items +curl http://localhost:8000/items + +# Create a new item +curl -X POST http://localhost:8000/items \ + -H "Content-Type: application/json" \ + -d '{"Name": "Keyboard", "Price": 79.99, "InStock": true}' +``` + +## How It Works + +### Pydantic Models + +F# classes that inherit from `BaseModel` with the `[]` attribute compile to Pydantic models: + +```fsharp +[] +type User(Id: int, Name: string, Email: string) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + member val Email: string = Email with get, set +``` + +### API Endpoints + +The `[]` attribute marks a class for FastAPI routing, and method decorators define the HTTP methods: + +```fsharp +[] +type API() = + [] + static member get_users() : ResizeArray = + users + + [] + static member create_user(request: CreateUserRequest) : obj = + // ... create user logic +``` + +## Development Mode + +For hot-reloading during development: + +```bash +# Using justfile +just dev-fastapi + +# Or manually in two terminals: +# Terminal 1: Watch F# files +cd examples/fastapi +dotnet fable --lang python --outDir build --watch + +# Terminal 2: Run uvicorn with reload +cd examples/fastapi/build +uv run --group fastapi uvicorn app:app --reload +``` diff --git a/examples/fastapi/paket.references b/examples/fastapi/paket.references new file mode 100644 index 0000000..bf9c02c --- /dev/null +++ b/examples/fastapi/paket.references @@ -0,0 +1 @@ +group Examples diff --git a/examples/flask/.gitignore b/examples/flask/.gitignore index 8e5bbf0..abf7d96 100644 --- a/examples/flask/.gitignore +++ b/examples/flask/.gitignore @@ -1 +1,15 @@ -*.py \ No newline at end of file +# Generated Python code +build/ + +# Python +__pycache__/ +*.pyc +.venv/ + +# .NET +bin/ +obj/ + +# Fable +.fable/ +fable_modules/ diff --git a/examples/flask/.paket/Paket.Restore.targets b/examples/flask/.paket/Paket.Restore.targets index e230bb2..712cd77 100644 --- a/examples/flask/.paket/Paket.Restore.targets +++ b/examples/flask/.paket/Paket.Restore.targets @@ -1,322 +1,330 @@ - - - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - $(MSBuildVersion) - 15.0.0 - false - true - - true - $(MSBuildThisFileDirectory) - $(MSBuildThisFileDirectory)..\ - $(PaketRootPath)paket-files\paket.restore.cached - $(PaketRootPath)paket.lock - classic - proj - assembly - native - /Library/Frameworks/Mono.framework/Commands/mono - mono - - - $(PaketRootPath)paket.bootstrapper.exe - $(PaketToolsPath)paket.bootstrapper.exe - $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ - - "$(PaketBootStrapperExePath)" - $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" - - - - - true - true - - - True - - - False - - $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) - - - - - - - - - $(PaketRootPath)paket - $(PaketToolsPath)paket - - - - - - $(PaketRootPath)paket.exe - $(PaketToolsPath)paket.exe - - - - - - <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) - <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) - <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false - - - - - - - - - - - <_PaketCommand>dotnet paket - - - - - - $(PaketToolsPath)paket - $(PaketBootStrapperExeDir)paket - - - paket - - - - - <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" - - - - - - - - - - - - - - - - - - - - - true - $(NoWarn);NU1603;NU1604;NU1605;NU1608 - false - true - - - - - - - - - $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) - - - - - - - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) - - - - - %(PaketRestoreCachedKeyValue.Value) - %(PaketRestoreCachedKeyValue.Value) - - - - - true - false - true - - - + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + $(MSBuildVersion) + 15.0.0 + false + true + + true + $(MSBuildThisFileDirectory) + $(MSBuildThisFileDirectory)..\ + $(PaketRootPath)paket-files\paket.restore.cached + $(PaketRootPath)paket.lock + classic + proj + assembly + native + /Library/Frameworks/Mono.framework/Commands/mono + mono + + + $(PaketRootPath)paket.bootstrapper.exe + $(PaketToolsPath)paket.bootstrapper.exe + $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ + + "$(PaketBootStrapperExePath)" + $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" + + + + + true + true + + + True + + + False + + $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) + + + + + + + + + $(PaketRootPath)paket + $(PaketToolsPath)paket + + + + + + $(PaketRootPath)paket.exe + $(PaketToolsPath)paket.exe + + + + + + <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) + <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) + <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false + + + + + + + + + + + <_PaketCommand>dotnet paket + + + + + + $(PaketToolsPath)paket + $(PaketBootStrapperExeDir)paket + + + paket + + + + + <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) + <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" + <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" + <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" + + + + + + + + + + + + + + + + + + + + + true + $(NoWarn);NU1603;NU1604;NU1605;NU1608 + false + true + + + + + + + + + $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) + + + + + + + $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) + $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) + + + + + %(PaketRestoreCachedKeyValue.Value) + %(PaketRestoreCachedKeyValue.Value) + + + + + true + false + true + + + - - true - - - - - - - - - - - - - - - - - - - $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached - - $(MSBuildProjectFullPath).paket.references - - $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references - - $(MSBuildProjectDirectory)\paket.references - - false - true - true - references-file-or-cache-not-found - - - - - $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) - $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) - references-file - false - - - - - false - - - - - true - target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) - - - - - - - - - - - false - true - - - - - - - - - - - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) - - - %(PaketReferencesFileLinesInfo.PackageVersion) - All - runtime - $(ExcludeAssets);contentFiles - $(ExcludeAssets);build;buildMultitargeting;buildTransitive - true - true - - - - - $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools - - - - - - - - - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) - - - %(PaketCliToolFileLinesInfo.PackageVersion) - - - - + + + + + + + + + + + + + + + + $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached + + $(MSBuildProjectFullPath).paket.references + + $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references + + $(MSBuildProjectDirectory)\paket.references + + false + true + true + references-file-or-cache-not-found + + + + + $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) + $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) + references-file + false + + + + + false + + + + + true + target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) + + + + + + + + + + + false + true + + + + + + + + + + + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[2]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) + + + %(PaketReferencesFileLinesInfo.PackageVersion) + All + runtime + $(ExcludeAssets);contentFiles + $(ExcludeAssets);build;buildMultitargeting;buildTransitive + %(PaketReferencesFileLinesInfo.Aliases) + true + true + + + + + %(PaketReferencesFileLinesInfo.PackageVersion) + + + + + $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools + + + + + + + + + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) + + + %(PaketCliToolFileLinesInfo.PackageVersion) + + + + - - - - - false - - - - - - <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> - - - - - - $(MSBuildProjectDirectory)/$(MSBuildProjectFile) - true - false - true - false - true - false - true - false - true - false - true - $(PaketIntermediateOutputPath)\$(Configuration) - $(PaketIntermediateOutputPath) - - - - <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> - - - - - - - - - + + + + + false + + + + + + <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> + + + + + + $(MSBuildProjectDirectory)/$(MSBuildProjectFile) + true + false + true + false + true + false + true + false + true + false + true + $(PaketIntermediateOutputPath)\$(Configuration) + $(PaketIntermediateOutputPath) + + + + <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> + + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/examples/flask/README.md b/examples/flask/README.md index 9be3132..289c843 100644 --- a/examples/flask/README.md +++ b/examples/flask/README.md @@ -1,24 +1,111 @@ -# Fable Python on Flask +# Flask Example -## Install Dependencies +This example demonstrates how to build a Flask web application using **Fable.Python** with the decorator-based API pattern and Feliz.ViewEngine for HTML rendering. -```sh -> dotnet tool restore -> dotnet restore +## Features -> pip install flask -> rehash # so you can run flask below +- Class-based routes using `[]` and `[]` decorators +- Server-side HTML rendering with Feliz.ViewEngine +- Bulma CSS framework for styling +- JSON API endpoints with `jsonify` + +## Project Structure + +``` +examples/flask/ +├── src/ +│ ├── App.fs # Main application with routes +│ ├── Model.fs # Data model and CSS classes +│ ├── Head.fs # HTML head component +│ ├── NavBar.fs # Navigation component +│ ├── Hero.fs # Hero section component +│ └── Flask.fsproj # F# project file +├── build/ # Generated Python code (git-ignored) +└── README.md +``` + +## Building + +From the repository root: + +```bash +# Using justfile +just example-flask + +# Or manually +cd examples/flask/src +dotnet fable --lang python --outDir ../build +``` + +## Running + +```bash +cd examples/flask/build +uv run --group flask flask --app app run --reload ``` -## Build +Then visit: +- http://localhost:5000 - Home page +- http://localhost:5000/about - About page +- http://localhost:5000/api/info - JSON API endpoint +- http://localhost:5000/health - Health check + +## Routes +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/` | Home page with Feliz.ViewEngine | +| GET | `/about` | About page | +| GET | `/api/info` | JSON API endpoint | +| GET | `/health` | Health check | + +## How It Works + +### Decorator-based Routes + +Routes are defined using class attributes: + +```fsharp +[] +type Routes() = + [] + static member index() : string = + "Hello World!" + + [] + static member get_data() : obj = + jsonify {| message = "Hello from F#!" |} ``` -> dotnet fable-py src + +### HTML Rendering with Feliz.ViewEngine + +The example uses Feliz.ViewEngine for type-safe HTML generation: + +```fsharp +open Feliz.ViewEngine + +let page = + Html.html [ + Html.head [ + Html.title "My Page" + ] + Html.body [ + Html.h1 [ prop.text "Hello World!" ] + ] + ] + |> Render.htmlDocument ``` -## Run +## Development Mode -```sh -> cd src -> flask run -``` \ No newline at end of file +For hot-reloading during development: + +```bash +# Terminal 1: Watch F# files +cd examples/flask/src +dotnet fable --lang python --outDir ../build --watch + +# Terminal 2: Run Flask with reload +cd examples/flask/build +uv run --group flask flask --app app run --reload +``` diff --git a/examples/flask/build.sh b/examples/flask/build.sh deleted file mode 100755 index 025d3ba..0000000 --- a/examples/flask/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -dotnet fable-py src/Flask.fsproj --outDir ./src diff --git a/examples/flask/paket.dependencies b/examples/flask/paket.dependencies deleted file mode 100644 index 01e5496..0000000 --- a/examples/flask/paket.dependencies +++ /dev/null @@ -1,8 +0,0 @@ -source https://api.nuget.org/v3/index.json -framework: net6.0 -storage: none - -nuget Fable.Core.Experimental >= 4.0.0-alpha-022 -nuget Fable.Python -nuget Feliz.ViewEngine -nuget Zanaptak.TypedCssClasses diff --git a/examples/flask/paket.lock b/examples/flask/paket.lock deleted file mode 100644 index 8a6cd28..0000000 --- a/examples/flask/paket.lock +++ /dev/null @@ -1,13 +0,0 @@ -STORAGE: NONE -RESTRICTION: == net6.0 -NUGET - remote: https://api.nuget.org/v3/index.json - Fable.Core.Experimental (4.0.0-alpha-021) - Fable.Python (0.16) - Fable.Core.Experimental (>= 4.0.0-alpha-006) - FSharp.Core (>= 4.7.2) - Feliz.ViewEngine (0.24) - FSharp.Core (>= 4.7) - FSharp.Core (6.0.1) - Zanaptak.TypedCssClasses (1.0) - FSharp.Core (>= 4.3.4) diff --git a/examples/flask/poetry.lock b/examples/flask/poetry.lock deleted file mode 100644 index 7fae5d7..0000000 --- a/examples/flask/poetry.lock +++ /dev/null @@ -1,213 +0,0 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. - -[[package]] -name = "click" -version = "8.0.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] - -[[package]] -name = "flask" -version = "2.2.5" -description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "Flask-2.2.5-py3-none-any.whl", hash = "sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf"}, - {file = "Flask-2.2.5.tar.gz", hash = "sha256:edee9b0a7ff26621bd5a8c10ff484ae28737a2410d99b0bb9a6850c7fb977aa0"}, -] - -[package.dependencies] -click = ">=8.0" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.2.2" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "itsdangerous" -version = "2.0.1" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, - {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "werkzeug" -version = "3.0.3" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-ruff"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.8" -content-hash = "1b6816f00718673d7b549f37c01068828087fe0a736a8552767642174d6085cf" diff --git a/examples/flask/pyproject.toml b/examples/flask/pyproject.toml deleted file mode 100644 index 02828fc..0000000 --- a/examples/flask/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ - -[tool.poetry] -name = "flask-example" -version = "0.1.0" -description = "Flask Example" -authors = ["Dag Brattli "] -license = "MIT" - -[tool.poetry.dependencies] -python = "^3.8" -Flask = "^2.2.5" - -[tool.poetry.dev-dependencies] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/examples/flask/src/App.fs b/examples/flask/src/App.fs index b5831c3..230054a 100644 --- a/examples/flask/src/App.fs +++ b/examples/flask/src/App.fs @@ -4,40 +4,74 @@ open Fable.Python.Builtins open Fable.Python.Flask open Feliz.ViewEngine -let app = Flask.Create(__name__, "/public") +// Create Flask app +let app = Flask(__name__, static_url_path = "/public") +// Helper to render HTML let htmlPage page = page |> Render.htmlDocument - +// Page model let model : Model = { - Title="Fable Python |> F# ♥️ Python" - Description="Demo Website, Fable Python running on Flask!" - Banner="https://unsplash.it/1200/900?random" - PermaLink="https://fable.io" - Author="dag@brattli.net" - Brand="public/favicon.png" + Title = "Fable Python |> F# -> Python" + Description = "Demo Website, Fable Python running on Flask!" + Banner = "https://unsplash.it/1200/900?random" + PermaLink = "https://fable.io" + Author = "dag@brattli.net" + Brand = "public/favicon.png" } +// Helper components let title (str: string) = Html.p [ prop.classes [ Bulma.Title ]; prop.text str ] let subTitle (str: string) = Html.p [ prop.classes [ Bulma.Subtitle ]; prop.text str ] -let helloWorld () = - let body = Html.div [ - title model.Title - subTitle model.Description - ] +// Routes using class-based decorator pattern +[] +type Routes() = + /// Home page + [] + static member index() : string = + let body = Html.div [ + title model.Title + subTitle model.Description + ] + + Html.html [ + Head.head model + Navbar.navbar model + Hero.hero model body + ] + |> htmlPage - Html.html [ - Head.head model + /// About page + [] + static member about() : string = + let body = Html.div [ + title "About" + subTitle "This is a Flask app built with F# and Fable!" + Html.p [ + prop.text "Fable.Python compiles F# code to Python, allowing you to use F#'s type safety and functional programming features while targeting Python frameworks like Flask." + ] + ] - Navbar.navbar model - Hero.hero model body - ] - |> htmlPage + Html.html [ + Head.head model + Navbar.navbar model + Hero.hero { model with Title = "About"; Description = "Learn more about Fable.Python" } body + ] + |> htmlPage -// Setup the routes. See if we can use attributes instead -app.route("/")(helloWorld) |> ignore + /// JSON API endpoint + [] + static member api_info() : obj = + jsonify {| + title = model.Title + description = model.Description + framework = "Flask" + language = "F# (via Fable)" + |} -[] -let main args = 1 + /// Health check + [] + static member health() : obj = + jsonify {| status = "healthy" |} diff --git a/examples/flask/src/Flask.fsproj b/examples/flask/src/Flask.fsproj index 7faebc4..55eb8d8 100644 --- a/examples/flask/src/Flask.fsproj +++ b/examples/flask/src/Flask.fsproj @@ -1,7 +1,7 @@ - + - net6.0 + net9.0 false Dag Brattli Dag Brattli @@ -17,5 +17,5 @@ - - \ No newline at end of file + + diff --git a/examples/flask/src/paket.references b/examples/flask/src/paket.references index df19221..5a94ca7 100644 --- a/examples/flask/src/paket.references +++ b/examples/flask/src/paket.references @@ -1,3 +1,3 @@ -#Fable.Core.Experimental +group Examples Feliz.ViewEngine -Zanaptak.TypedCssClasses \ No newline at end of file +Zanaptak.TypedCssClasses diff --git a/examples/microbit/Build.fsproj b/examples/microbit/Build.fsproj deleted file mode 100644 index 23ab60c..0000000 --- a/examples/microbit/Build.fsproj +++ /dev/null @@ -1,11 +0,0 @@ - - - Exe - net5.0 - - - - - - - \ No newline at end of file diff --git a/examples/paket.dependencies b/examples/paket.dependencies deleted file mode 100644 index 8cb5d38..0000000 --- a/examples/paket.dependencies +++ /dev/null @@ -1 +0,0 @@ -# Just to keep paket out of the examples folder \ No newline at end of file diff --git a/examples/paket.lock b/examples/paket.lock deleted file mode 100644 index 8b13789..0000000 --- a/examples/paket.lock +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/pydantic-fastapi/uv.lock b/examples/pydantic-fastapi/uv.lock deleted file mode 100644 index 6f49637..0000000 --- a/examples/pydantic-fastapi/uv.lock +++ /dev/null @@ -1,336 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, -] - -[[package]] -name = "click" -version = "8.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "fastapi" -version = "0.116.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httptools" -version = "0.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, -] - -[[package]] -name = "pydantic" -version = "2.11.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, -] - -[[package]] -name = "pydantic-fastapi-example" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "fastapi" }, - { name = "pydantic" }, - { name = "uvicorn", extra = ["standard"] }, -] - -[package.metadata] -requires-dist = [ - { name = "fastapi", specifier = ">=0.100.0" }, - { name = "pydantic", specifier = ">=2.0.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.23.0" }, -] - -[package.metadata.requires-dev] -dev = [] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "starlette" -version = "0.47.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, -] - -[[package]] -name = "uvicorn" -version = "0.35.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, -] - -[[package]] -name = "watchfiles" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, -] - -[[package]] -name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, -] diff --git a/examples/pydantic/.gitignore b/examples/pydantic/.gitignore new file mode 100644 index 0000000..c152436 --- /dev/null +++ b/examples/pydantic/.gitignore @@ -0,0 +1,19 @@ +# Generated Python code +build/ + +# Python +__pycache__/ +*.pyc +.venv/ + +# .NET +bin/ +obj/ + +# Fable +.fable/ +fable_modules/ + +# Generated Fable code +src +*.py \ No newline at end of file diff --git a/examples/pydantic/App.fs b/examples/pydantic/App.fs new file mode 100644 index 0000000..d8e6308 --- /dev/null +++ b/examples/pydantic/App.fs @@ -0,0 +1,52 @@ +/// Example showing how to use Pydantic models imported from Python. +/// +/// This demonstrates: +/// 1. Importing Pydantic models defined in Python +/// 2. Creating instances of those models +/// 3. Accessing model properties +/// 4. Using Pydantic's validation and serialization +module App + +open Models + +// ============================================================================ +// Using imported Pydantic models +// ============================================================================ + +// Create a user using the helper function +let user1 = User.create 1 "Alice" (Some "alice@example.com") (Some 30) +let user2 = User.create 2 "Bob" None None + +// Create a product +let product1 = + Product.create + 1 + "Laptop" + "High-performance laptop" + 1299.99 + true + [| "electronics"; "computers" |] + +// Access properties (type-safe!) +printfn "User 1: %s (id=%d)" user1.name user1.id +printfn "User 1 email: %A" user1.email +printfn "User 1 age: %A" user1.age + +printfn "" +printfn "User 2: %s (id=%d)" user2.name user2.id +printfn "User 2 email: %A" user2.email + +printfn "" +printfn "Product: %s - $%.2f" product1.name product1.price +printfn "In stock: %b" product1.in_stock +printfn "Tags: %A" product1.tags + +// Modify properties +user1.email <- Some "alice.updated@example.com" +printfn "" +printfn "Updated email: %A" user1.email + +// Create a request model +let createRequest = CreateUserRequest.create "Charlie" (Some "charlie@example.com") (Some 25) +printfn "" +printfn "Create request for: %s" createRequest.name diff --git a/examples/pydantic/Models.fs b/examples/pydantic/Models.fs new file mode 100644 index 0000000..2221289 --- /dev/null +++ b/examples/pydantic/Models.fs @@ -0,0 +1,85 @@ +/// F# bindings for Pydantic models defined in Python. +/// +/// This module demonstrates how to import and use Pydantic models +/// that are defined in Python code (models.py). +/// +/// This pattern is useful when you want to: +/// - Use models generated from OpenAPI specs or other tools +/// - Use models maintained by a Python team +/// - Integrate with an existing Python codebase +/// - Share models between Python and F# code +module Models + +open Fable.Core + +// ============================================================================ +// Import Pydantic models from Python +// ============================================================================ + +/// User model - imported from models.py +/// The interface defines the shape of the Python Pydantic model +[] +type User = + abstract id: int with get, set + abstract name: string with get, set + abstract email: string option with get, set + abstract age: int option with get, set + +/// Product model - imported from models.py +[] +type Product = + abstract id: int with get, set + abstract name: string with get, set + abstract description: string with get, set + abstract price: float with get, set + abstract in_stock: bool with get, set + abstract tags: string array with get, set + +/// Request model for creating users +[] +type CreateUserRequest = + abstract name: string with get, set + abstract email: string option with get, set + abstract age: int option with get, set + +/// Request model for creating products +[] +type CreateProductRequest = + abstract name: string with get, set + abstract description: string with get, set + abstract price: float with get, set + abstract in_stock: bool with get, set + abstract tags: string array with get, set + +// ============================================================================ +// Constructors for creating instances +// ============================================================================ + +/// Helper module for creating model instances +[] +module User = + /// Create a new User instance + [] + [] + let create (id: int) (name: string) (email: string option) (age: int option) : User = nativeOnly + +[] +module Product = + /// Create a new Product instance + [] + [] + let create (id: int) (name: string) (description: string) (price: float) (inStock: bool) (tags: string array) : Product = nativeOnly + +[] +module CreateUserRequest = + /// Create a new CreateUserRequest instance + [] + [] + let create (name: string) (email: string option) (age: int option) : CreateUserRequest = nativeOnly + +[] +module CreateProductRequest = + /// Create a new CreateProductRequest instance + [] + [] + let create (name: string) (description: string) (price: float) (inStock: bool) (tags: string array) : CreateProductRequest = nativeOnly diff --git a/examples/pydantic/PydanticExample.fsproj b/examples/pydantic/PydanticExample.fsproj new file mode 100644 index 0000000..2dc31ee --- /dev/null +++ b/examples/pydantic/PydanticExample.fsproj @@ -0,0 +1,15 @@ + + + + net9.0 + false + + + + + + + + + + \ No newline at end of file diff --git a/examples/pydantic/paket.references b/examples/pydantic/paket.references new file mode 100644 index 0000000..ef4900b --- /dev/null +++ b/examples/pydantic/paket.references @@ -0,0 +1 @@ +Fable.Core diff --git a/examples/timeflies/TimeFlies.fsproj b/examples/timeflies/TimeFlies.fsproj index 7f63ad7..0bae7d3 100644 --- a/examples/timeflies/TimeFlies.fsproj +++ b/examples/timeflies/TimeFlies.fsproj @@ -1,19 +1,15 @@ - + Exe - net10.0 + net9.0 3390;$(WarnOn) - - - - - - + + \ No newline at end of file diff --git a/examples/timeflies/paket.references b/examples/timeflies/paket.references new file mode 100644 index 0000000..3dc4ba3 --- /dev/null +++ b/examples/timeflies/paket.references @@ -0,0 +1,2 @@ +group Examples +FSharp.Control.AsyncRx diff --git a/examples/timeflies/pyproject.toml b/examples/timeflies/pyproject.toml deleted file mode 100644 index 42119c9..0000000 --- a/examples/timeflies/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ - -[project] -name = "timeflies" -version = "0.1.0" -description = "Fable Timeflies Demo" -authors = [{ name = "Dag Brattli", email = "dag@brattli.net" }] -license = "MIT" -requires-python = ">=3.11" -dependencies = [ - "fable-library==5.0.0a17", -] - -[dependency-groups] -dev = [ - "black>=24.3.0", - #"fable-python", - #"fsharp-control-async-rx", -] diff --git a/examples/timeflies/uv.lock b/examples/timeflies/uv.lock deleted file mode 100644 index 544e8c8..0000000 --- a/examples/timeflies/uv.lock +++ /dev/null @@ -1,193 +0,0 @@ -version = 1 -requires-python = ">=3.11" - -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, -] - -[[package]] -name = "click" -version = "8.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "fable-library" -version = "5.0.0a17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/39/46eddc670f19e44d8d27e5fba2f127293ab253d88781cd8bf9f30dbf9c27/fable_library-5.0.0a17.tar.gz", hash = "sha256:76d1d0ade9de86cb4d7e33fe097e6d046d2f3bcacbce64478894b6c7c32366e2", size = 181946 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/43/04ce725d78fd4a0ed20070459c4ac6e328543dc586bf1515023b38c3a050/fable_library-5.0.0a17-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cdad5353b5fd7d29012896f753186297986a23eceb090657b06910f2c4e44a82", size = 1685689 }, - { url = "https://files.pythonhosted.org/packages/51/26/d523ab6ef3c552a977494931510a0086f88ff6d48c3fd7bdfa2d4f563d5a/fable_library-5.0.0a17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd069a979766daab0243bb799c5c3883584176f50b386650ac334380a1d7e7ac", size = 1603459 }, - { url = "https://files.pythonhosted.org/packages/62/46/2a6b73684fd86b8b3aaccea761258dc76b1278815a1e962e6c52f78d4ee2/fable_library-5.0.0a17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baaab9ce4598301e39f1f28254ad1db61cde32355241a41d3a3637cbe90773d4", size = 1676316 }, - { url = "https://files.pythonhosted.org/packages/c4/ad/1875e491809d3e291951a2962306a7955a4e44ce23669468f17cf8e34f34/fable_library-5.0.0a17-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c007aaa3d5c18ff94925030e3a991ee9c87a5a351476ec6001be1d7266f219b", size = 1634997 }, - { url = "https://files.pythonhosted.org/packages/0e/87/908ecb9f7bff8e589eb5917c6e523625292a812b3cb5a40f6098bcd78e7d/fable_library-5.0.0a17-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7834ba195cb7ae0dbc6b895e8cd88a89c791a78122c757f0f25cf3dfdb3bc340", size = 1866023 }, - { url = "https://files.pythonhosted.org/packages/19/d5/c912ac46751584ee8829b0ec4141053381aef6c06af7a2c16b4319758272/fable_library-5.0.0a17-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc707e293799690652b195bac98c212d66e37d767c003852fbdcb9337d23d811", size = 1778020 }, - { url = "https://files.pythonhosted.org/packages/8b/68/b5789190bc1e5a7c394804c55ce930253ba867ab547be6f6f9c2e0468acc/fable_library-5.0.0a17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0056f968626908766f99b16a88dca7b2262eb49e4f178bdd4fe063b3656f10e", size = 1680013 }, - { url = "https://files.pythonhosted.org/packages/3b/75/7e137be400d053c793c21e3da10b35152687486655311d205fb2e6dd5138/fable_library-5.0.0a17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dfa33d0e8669589606379318e1e1a88238413d306d7feecc32a0251934a8b2e1", size = 1792170 }, - { url = "https://files.pythonhosted.org/packages/96/2b/b50b4dd262dedd7a2f57b1d7918632d54f948633142a42590fb3c44458dc/fable_library-5.0.0a17-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9c2ecc84a09d61de38fe4743e470f9422c400e033119ae0e469b5d6d554b484b", size = 1860008 }, - { url = "https://files.pythonhosted.org/packages/d3/43/59139a4391be3957adddf6743783982ab15761386cb671c1ccda0b8217d7/fable_library-5.0.0a17-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e6904658a7caeb4e52406d0b2c86e9eff264bee78f5d42424f4570fe6ae9398b", size = 1900561 }, - { url = "https://files.pythonhosted.org/packages/54/fc/46a595b6c5f7418394b7824c605e54fbaf91ad7f853339fb3b39df061f46/fable_library-5.0.0a17-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:aa2173746e3c9f379823b05dea5f5736b49a7e4dca074a4a66658c99cda2e69b", size = 1903914 }, - { url = "https://files.pythonhosted.org/packages/78/1b/f8105f6249bca178748c4627e4c307eacfa8c1096acde8cdf9a2bff7abeb/fable_library-5.0.0a17-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bd825cd0b5c97f441d4957b2e796f875cbcdb6124a462fbce8f78548954ed2a", size = 1902122 }, - { url = "https://files.pythonhosted.org/packages/c0/ad/5668899e9d98fd9c8acb4c35047d6d1ae0639fafb59b409420781dce5373/fable_library-5.0.0a17-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd70842b7246086d3b40681b212d5a495540d97b1d1b54dc26d915526337da", size = 1389570 }, - { url = "https://files.pythonhosted.org/packages/f9/e2/bc0aedc309da577791e24a5b0401417ee4a31fbb540918b67857bdecd063/fable_library-5.0.0a17-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:33c5cd3f3d3b22387d3e8a6f8b2671b5116dd790d7e3722ede98c7643e68c54e", size = 1691410 }, - { url = "https://files.pythonhosted.org/packages/57/6b/60cb12b84e318b30749b485f64c0ab281b25610eb513dad527a4b2efd5ef/fable_library-5.0.0a17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac51f1d89aa385eda124edb5c223820590e41a4c614bc2ff8bca7f8a975f2165", size = 1599332 }, - { url = "https://files.pythonhosted.org/packages/7f/53/4bcb316ef823c746ca1381f4f18170a3cb3fd0c88eabec6aec9339cc2173/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5cbbca0c6e0123e0ea31846616427121fe9879497876571f44390dda475e3ea", size = 1678480 }, - { url = "https://files.pythonhosted.org/packages/3e/ca/8cbdc5a48d5b149d12b8f983b156f1de121f063767215eaec5d0a7ed4062/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af423d94cf131afa2639943f1fb54c9648abe42928a8e613ad2a38e51e056a17", size = 1646911 }, - { url = "https://files.pythonhosted.org/packages/5d/2b/07b2ac66f22552d0a9fbe30dbe488ad9c2eebb446625d9426a09aa50f817/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c46dc9a073002dec5b90c7e90fc973f3a24e4307e6a8978b586fa985621351", size = 1856429 }, - { url = "https://files.pythonhosted.org/packages/66/62/98cda972d52303d2714df935b3fd037733ee291c4acec386a94a6f95fd36/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1a78ee26cd2c37f09c9662e8ad2e2c871ec8bdb023383ff85b5dfaa3fbc7e80", size = 1788103 }, - { url = "https://files.pythonhosted.org/packages/78/85/7d9a1c1cfb369b85cf2cde6ee2b4d2f1670126d7cbe247d1473d0c03b6e9/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1fe000c1a0d60a7c4a4652d20d47d9f3dd1ecb3ea531e209da27899b66659e", size = 1694562 }, - { url = "https://files.pythonhosted.org/packages/2a/74/cf686a1f7b2476f063a58ef42adbd042f348e5c1b21aad349bdcaf1debc5/fable_library-5.0.0a17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffa31557d42526c18dfbc15b05d9f9c8badd79c8a74ffb14dc0dc8379ef0fbac", size = 1796335 }, - { url = "https://files.pythonhosted.org/packages/3e/ee/fc5657b46f5ef7955a263bde92a86ef24dc8c15ced124ce558476cbd2217/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74becfd5a81da386afe2b9481ac74862835f1f73c97db07a713ac630862bd8b9", size = 1860582 }, - { url = "https://files.pythonhosted.org/packages/e8/74/693c1a42ec3d56da807f2b3f746a8a690af5df9b5417e72f28b9c6cb1a75/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7ac2ac71a7d0387f997d4d8a1d50d1ba5158eef08e112b29133487bdaa3c1b78", size = 1913156 }, - { url = "https://files.pythonhosted.org/packages/a3/91/1b85e295f44b0413126cc87d34317e08b7d3065c30227f142ca57698d91b/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a261de6a9aeb1e8324c9afb74924e8227e8119846b5e60fbb420db1540816a03", size = 1910444 }, - { url = "https://files.pythonhosted.org/packages/a0/fa/c05cda9cbf3fb88a7484127aa047053907d8a2136b84daad4419f4f07421/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:138c7f3dd36d939c6d7866e609df1bac866e644608303932d6d4d335efd3b681", size = 1913755 }, - { url = "https://files.pythonhosted.org/packages/b0/7d/1598177d869691edb7b336016e4479e8ca7e75105376d48a7ba1770d6c66/fable_library-5.0.0a17-cp312-cp312-win_amd64.whl", hash = "sha256:ad78d9f9aa4357f19405830ccaa7c9ff10efccd4cc1dde4e7fc2fdaf9a2eb004", size = 1411069 }, - { url = "https://files.pythonhosted.org/packages/a2/ad/2cb0d8d6c90223c9f76715da3b4dc57a4dadea67b9fe3ac235b583f1e493/fable_library-5.0.0a17-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b0b6798853abe29c82ee5b7e9b6aa1f20fa8f0e4aaae7c3f82a27a6c8ce3e78b", size = 1691370 }, - { url = "https://files.pythonhosted.org/packages/ff/dc/4129a3b87cfad1db199a9ef438f6c5d640ad18a9898d97299d4dab5dcb05/fable_library-5.0.0a17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:459d0161a2c79e4f205e8118dc9987c55e6441475fb95793374791814b442777", size = 1599346 }, - { url = "https://files.pythonhosted.org/packages/72/e2/fe87a1cd65183c9ea05066dbb0233f2b6fa8075591f09fa8db8186c8069b/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61d882817d0b52e088389790b88051f52a966f141d4df1a298d9ac8295f9ea7f", size = 1677602 }, - { url = "https://files.pythonhosted.org/packages/92/89/c8357d26dce3e2d2961a89eee005b5783fd695c648eb8fb23645d884102e/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03c95b0f1b8b477f544de0602704d51dc908428d8566509b9bf76d67f9a16492", size = 1646019 }, - { url = "https://files.pythonhosted.org/packages/24/c9/d1bd05d8570549e81a685f32ab4c872dfc3e81ac358d842607b5cfe33a30/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af3d9ebc9f967f07abc588c697f41b086044792327e2a136b69c1dc6b9185a5", size = 1853942 }, - { url = "https://files.pythonhosted.org/packages/aa/fb/03bf0abd5fd1b97cb2f2ee3d3f9c2e97151ee42daa8d51edb19fcd7f953c/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00086ff93ec4a043b9c8d337296561511af73979e89d41711494a7db96093d29", size = 1788275 }, - { url = "https://files.pythonhosted.org/packages/d4/5b/4cf6a163e5a5647d5da60a51921ce1129396d3206d85ffdef6755a275cc2/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eabf12e4d4148ec9796822dcbb2813fe4389bf5432e29a9e5e2f607bb20db16", size = 1693592 }, - { url = "https://files.pythonhosted.org/packages/98/2d/16c6eba1ac5230cc454e22a3cef514ac19e6a9baeb40836e0164a52d8673/fable_library-5.0.0a17-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:626c0e953db7f80640cc421b6f82c98a85595130ae21c5b16a1cb2551e8ca1df", size = 1796282 }, - { url = "https://files.pythonhosted.org/packages/e0/71/773b0970132d5d9d318071d152d5add4bd76f7c4c019cd2532c1d26e24a5/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d4928051dd56a9266640ec31700cbb83d9ff392cf0367d0d72f70f9cef397333", size = 1859700 }, - { url = "https://files.pythonhosted.org/packages/93/a6/b7f6bf1f4980dc005629c86eec43ee8f278b028c52f468a033efac460432/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:62e354cc2086495b728ee9fc3beff55b976b33f91afcbac790acc2c8bf8e97bd", size = 1912588 }, - { url = "https://files.pythonhosted.org/packages/d9/d8/b0f93b3c270de5a884ed5db856b84cff4d86826c1e123dc2954c80ef5374/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f5d8c33052083accc51beb3af14cf3f1d9f23a2a8dab700694af4fc548dc804c", size = 1910298 }, - { url = "https://files.pythonhosted.org/packages/e7/97/77fd12e3d8556def4bb800e0cbf89f0310f53f232b63b1abeec32cfb8275/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:143301c014e8f3730ccf34c3352c1b43a7a661ea9dbdb2a469132124e8ac56ed", size = 1913052 }, - { url = "https://files.pythonhosted.org/packages/34/28/ce21303cb0406bccc8ca8dde318eebb88cb22755caba7529117786bf0c77/fable_library-5.0.0a17-cp313-cp313-win_amd64.whl", hash = "sha256:b471070e3eab0cd10626bba99c164e5f9a468b720d8b7f53a09b1e85c19365c0", size = 1411150 }, - { url = "https://files.pythonhosted.org/packages/80/a9/6d1adccf6534017e5ccbb0d89889a631384794bd03936a938f839f896142/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a0902ea0b507cc6d4260ee649ee0cba8905054f5d2bdb1d0c1d7b7f4d20497", size = 1703312 }, - { url = "https://files.pythonhosted.org/packages/6a/74/dbc90755901d56d4dbf1aff1094a4fa47b124acac9e59de79751be260977/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f725a5f5e2e85a3cf3086bf3cd4630637c53a34aeea69e883e46716993119a9", size = 1682667 }, - { url = "https://files.pythonhosted.org/packages/eb/0d/9f3e681fd530ed965483d588c39eabca6de7ad8a64103849a6fae24d6a1c/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2eff10a9e42d24fe0cf989469c09977dede0243326257f9f4da752ff568ee5e", size = 1893420 }, - { url = "https://files.pythonhosted.org/packages/27/dc/158b0ebfe2025ba76862f5ecb32f33eda0004baab04402193e1171fcce15/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:feb07936a48b994f752c96d160c8d11e8dcd7f011e00df4b62df9f0ceb5cab7a", size = 1826167 }, - { url = "https://files.pythonhosted.org/packages/78/6b/ac592731db1bbc8c6ff4ce88336c924e9a1c67bfb07809d638f8cba6cc2f/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:efa973c02f5bcfb5a3e982797738769f6a29a3b3dbbdc9486aae2f7507892af5", size = 1885647 }, - { url = "https://files.pythonhosted.org/packages/16/5c/f564ed669540077b932acf7f9cd63b5a39ab840983ede9084421e7d445a5/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:1674f84763177d2d8f23ff9130fae28b2268a99394a53c49dcafa545d47080bf", size = 1949577 }, - { url = "https://files.pythonhosted.org/packages/65/ae/df14e0f55cfd10d91eeffbd537e43737745001b5cf103ba15245a14fa637/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b7cea435911c9eb52e3fd0b1b959eff4715887baeeae9efa1c84c207c0e3c35c", size = 1948006 }, - { url = "https://files.pythonhosted.org/packages/d9/ab/51580c1ee9d2851df182f0cf7741608137777b16311bdc3ced615cdccf9a/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:05e54e2f6d52f8afb618baf4d2e9de8fae9a35d976b37b60fc3023bb10a681c1", size = 1949741 }, - { url = "https://files.pythonhosted.org/packages/97/e8/a59d2c175cbb120357328b1a959d829f766288afb088caff17875843484d/fable_library-5.0.0a17-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e05e2cca491107f59dae1b7d95152f7f45bc244f3e2fecde6b4e7afd5b772adf", size = 1678581 }, - { url = "https://files.pythonhosted.org/packages/4b/f6/c1ec2d7bda962bca80ff3248aa9502f8e22c8e853516603c1bba8981ae35/fable_library-5.0.0a17-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c207d03941b33871c1b1be9e9a3ff80ab130e9db22259413a3f6a017f99a5cc1", size = 1590836 }, - { url = "https://files.pythonhosted.org/packages/c4/49/8cb15ccc800fdd5ee22a35bfe7a74bfe6c5eaf2a0135d9e6ed7f1c75c769/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c6a2b3da8965f41727747d92d4bc88badea1863cb85308966aff2c393ab89ef", size = 1671523 }, - { url = "https://files.pythonhosted.org/packages/b2/bb/7fa06bcacd026f74dcf2ded700ef2f1ca7f69ce5f6618553e66b776ddeb9/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a3237cc83ec36a69b7a6b21e2de128f570eef4012776b58f35bbf63f2f3de41", size = 1632185 }, - { url = "https://files.pythonhosted.org/packages/75/13/dacd0fbd6bfcc9a442806176a180469c5264516863fce099c6c98a81818d/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba00aa9959067245cb59721a4c315294e1e0882f1a2664848e5bf653c055239e", size = 1854112 }, - { url = "https://files.pythonhosted.org/packages/40/99/85b732612f70fd6338110526f39f5b9441e3e943c48e4df61e98811093ee/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fb20c05ef9d278bc36081c123accb9c9a793ec797f51f134a285a8a8da53ec6", size = 1780844 }, - { url = "https://files.pythonhosted.org/packages/d5/0a/7883946553157fdff7d5ed0ce773e2537f6e6e51b25b493a705ff634533a/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db327cf997d54bc3a61aaf0a386d5f84d11dff525b7f4dcaabeeb58cc99d50e0", size = 1679723 }, - { url = "https://files.pythonhosted.org/packages/7d/13/707a9f4b65530c20a966864812f30576d9249d290fe89b89391c7159439e/fable_library-5.0.0a17-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:882221037b8ed4f539b4499556269c5caaba608bc966f2c60f16235122ee35f1", size = 1787845 }, - { url = "https://files.pythonhosted.org/packages/ac/d5/d58f2fe7acfb01735eda44542bc7781c7a972f4f87043281f4bd44cc2f85/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b0e49c159b696436df098326b2b95af27d1d66ef4c8b811bada708b2bf31c7db", size = 1854562 }, - { url = "https://files.pythonhosted.org/packages/a5/99/4862333a479686149f0dc7bd3785c2efa97a60184dab8f7cd90f4c14fdf7/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5334156ab14a91e9dfc721e8523feb88a3d3a22be2cc5a13640019d7ea29b2a7", size = 1900038 }, - { url = "https://files.pythonhosted.org/packages/1e/e5/c8867c226d82cb88abd471a7f0456a9979aa58def69489946ee1ed5ad9fe/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fa1cc2d9d02ec87aeba5f06d4335cbbaea6be777b92dc41c544944fe18c0dcb1", size = 1903699 }, - { url = "https://files.pythonhosted.org/packages/df/8a/579cf0e340eee6ecb0174e1de24bbb469e495bc0cc00bccff0d9724bafba/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b4b233fafdc1bdff8ab6709717106be9091f667c377dbb68f02e90939d63d9f", size = 1901808 }, - { url = "https://files.pythonhosted.org/packages/38/4e/d45bd132faba3d2a93e71c6a7ba3e60d6e6e7b021abbd5d7b68e8170c9a2/fable_library-5.0.0a17-cp314-cp314-win32.whl", hash = "sha256:429f1706db3f19e2c2c8501e58f0285ff2ef659ff735b059964baa2b40121a86", size = 1276767 }, - { url = "https://files.pythonhosted.org/packages/3a/12/07d4a9b9bd7de9edd50534bc9406e3ed3f32ce52d516ac8913b5ffbf8792/fable_library-5.0.0a17-cp314-cp314-win_amd64.whl", hash = "sha256:76c38f95a07f20746a75e174d12cab72d608506b65929d45e70b06e7d81c4a83", size = 1399548 }, - { url = "https://files.pythonhosted.org/packages/26/55/6987d4bc5f2c300866994dfb5a5b9d517e26e20b57032f50d9f22f00fe73/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a561562e5490571bb5185a76e802fcce9ac0523d84e9cfa7c7e14e00188a5289", size = 1702465 }, - { url = "https://files.pythonhosted.org/packages/6b/9d/35260a8db15edcfa61cc3e65209c451fe05a396acd59d229667713fd91a4/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0839ee5e3a769c7f70673da3f3298aa17497b2db2cfd84c1a2a1e8998362591f", size = 1683471 }, - { url = "https://files.pythonhosted.org/packages/93/7d/d5c7fb9064dab4afc9dff3809944978be10e0839d606887a406b3aa794b1/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3618911c2e831478b36ba58b10c314c76f7237e299e568355a5504a5f6d43ee", size = 1893602 }, - { url = "https://files.pythonhosted.org/packages/b0/65/c9230732d2c90ec77a6d2199aed1f0dd803df558aa585e7ded2f5cdc62f2/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2093ea940619c4421e2804332e175b7a54ed39043969323bde056fe28eb07631", size = 1824144 }, - { url = "https://files.pythonhosted.org/packages/ac/a3/9c145f8b27d4888055a4dec97f781b9b02b9ebff4656b4e629b3870be164/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6a2eef5c100199587824d2a7c2182415a173001ae4ef9db5161470c2586dfd8", size = 1885247 }, - { url = "https://files.pythonhosted.org/packages/bf/07/511002b542c69c2d83112d38e0fa1af48f49a0e3863381d7254694c9313a/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3516ebf9ecf30d50e0f4f6834fec9dddf85ab95beb45bea7aa0500f4896e0a14", size = 1950279 }, - { url = "https://files.pythonhosted.org/packages/2f/c9/b576fafbcbc8d9ff2e0853ddd8e9bb0cba7c632f96e5f4e46c270a4fab1c/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:12b9f8cf16f7031f0c47a48f9ffd17bde2dca1db80af1577b42b3ee1ffd94cb2", size = 1949585 }, - { url = "https://files.pythonhosted.org/packages/62/c5/0e8974a4d64ae5fc4025528d15d398703e462a08b817571f0b1859a4bc16/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8b68546c5dda788d5d117c51cc1ecf1e074641428330ee010fdc328bdba77ef9", size = 1950401 }, - { url = "https://files.pythonhosted.org/packages/16/d4/38e36faddb723319a2e0f082317d4076af1c1b6ee28a7f51c41f2ab943d7/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc6916ff0e6598caa2e0961fc6f21d58a8be32486e27060514cee921d358d58a", size = 1680898 }, - { url = "https://files.pythonhosted.org/packages/e2/41/10664354503a2a81a8af4961b418240d6354d0b858d274ed956c0cd8bd38/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:172ab59b58bff6c9c935e0932d100c93370d2dc5862e825f01de188d880f1e1d", size = 1637455 }, - { url = "https://files.pythonhosted.org/packages/8b/b8/a785f977edbed922108f975fb5362d2412eebd5cd200a17e28017dcac90d/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c31b486732c41c540e9f838d8a604cf892eff502a89031e3df16488bb0fa4252", size = 1868853 }, - { url = "https://files.pythonhosted.org/packages/57/fc/b0456281bbe14e5f1423457614468c08e416566980d0501ac416c66c5444/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8472d76a6eff299aea8fab9850ae76585a5d6b1659ae26da0809e263e44153f7", size = 1780116 }, - { url = "https://files.pythonhosted.org/packages/70/92/1d4cd3a24b0d377e267ffe73f592d625ce07225472f4ad69ad88f755601f/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81fa3cfbcf02d02359391fd3b48fdea9ffa9e099afba5ccb2711a0a582ce99b5", size = 1686519 }, - { url = "https://files.pythonhosted.org/packages/3e/68/d0190aa3fe053b89ce41d09ec0ed25772b2b0ffbc172328bc488967610ef/fable_library-5.0.0a17-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4cf7678d38a806e0d52266dfc6f89c2b3556094902cf5d03a4164a78ea8ba534", size = 1794803 }, - { url = "https://files.pythonhosted.org/packages/94/ba/a2f1dc3ff9ec57a148b7b592b4dc79911f0fdd255dc43d4913b78f1f4ccd/fable_library-5.0.0a17-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1fab0292a45f998599dc6b8924fe3eac4518e4c78865707020bedd5fb56ac64b", size = 1863646 }, - { url = "https://files.pythonhosted.org/packages/cc/eb/8a69a6a0301a7b6786758c8fcf89a8327c844a02a294d3de0c42fa08a8c8/fable_library-5.0.0a17-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:adf6a428a1735f40143f0bbedc330d16527ca13d77e616921241cf423490e523", size = 1901927 }, - { url = "https://files.pythonhosted.org/packages/47/84/6a7db0520d9dda0ee4ba5268a48659becdd98430483ccefe2b3a0975cef8/fable_library-5.0.0a17-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:33025f243233a58c22a2345022cbf2e7a7a93efb67374c6d9c1bff14cb88aa5a", size = 1906779 }, - { url = "https://files.pythonhosted.org/packages/67/fb/3a3078baf1f6b55116e8cae5027b97498a4eb94943b74fa138b581fb67dd/fable_library-5.0.0a17-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:7c8b54a69b22f64835924fc9713485d418a96b50a61d59398e140a7f2d2bfc02", size = 1907641 }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, -] - -[[package]] -name = "timeflies" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "fable-library" }, -] - -[package.dev-dependencies] -dev = [ - { name = "black" }, -] - -[package.metadata] -requires-dist = [{ name = "fable-library", specifier = "==5.0.0a17" }] - -[package.metadata.requires-dev] -dev = [{ name = "black", specifier = ">=24.3.0" }] diff --git a/justfile b/justfile index c85ceab..c335b9a 100644 --- a/justfile +++ b/justfile @@ -7,20 +7,35 @@ build_path := "build" src_path := "src" test_path := "test" +# Development mode: use local Fable repo instead of dotnet tool +# Usage: just dev=true test-python +dev := "false" +fable_repo := justfile_directory() / "../Fable" +fable := if dev == "true" { "dotnet run --project " + fable_repo / "src/Fable.Cli" + " --" } else { "dotnet fable" } + # Default recipe - show available commands default: @just --list -# Clean build artifacts +# Clean Fable build output (preserves dotnet obj/bin directories) clean: rm -rf {{build_path}} + rm -rf {{src_path}}/obj {{src_path}}/bin + rm -rf {{test_path}}/obj {{test_path}}/bin + rm -rf examples/*/build examples/*/obj examples/*/bin + rm -rf examples/*/src/obj examples/*/src/bin + rm -rf examples/*/.fable examples/*/src/.fable + rm -rf .fable + +# Deep clean - removes everything including dotnet obj/bin directories +clean-all: clean rm -rf {{src_path}}/obj {{test_path}}/obj rm -rf {{src_path}}/bin {{test_path}}/bin # Build F# source to Python using Fable build: clean mkdir -p {{build_path}} - dotnet fable {{src_path}} --lang Python --outDir {{build_path}} + {{fable}} {{src_path}} --lang Python --outDir {{build_path}} # Compile F# source using dotnet (without Fable transpilation) run: clean @@ -32,7 +47,7 @@ test: build @echo "Running native .NET tests..." dotnet run --project {{test_path}} @echo "Compiling and running Python tests..." - dotnet fable {{test_path}} --lang Python --outDir {{build_path}}/tests + {{fable}} {{test_path}} --lang Python --outDir {{build_path}}/tests uv run pytest {{build_path}}/tests # Run only native .NET tests @@ -42,7 +57,7 @@ test-native: # Run only Python tests (requires build first) test-python: build - dotnet fable {{test_path}} --lang Python --outDir {{build_path}}/tests + {{fable}} {{test_path}} --lang Python --outDir {{build_path}}/tests uv run pytest {{build_path}}/tests # Create NuGet package @@ -66,6 +81,7 @@ format-check: # Restore all dependencies restore: dotnet tool restore + dotnet paket install dotnet restore {{src_path}} dotnet restore {{test_path}} @@ -78,4 +94,40 @@ setup: restore install-python # Watch for changes and rebuild (useful during development) watch: - dotnet fable watch {{src_path}} --lang Python --outDir {{build_path}} + {{fable}} watch {{src_path}} --lang Python --outDir {{build_path}} + +# --- Examples --- + +# Run Flask example +example-flask: + cd examples/flask/src && {{fable}} . --lang Python --outDir ../build + cd examples/flask/build && uv run --group flask flask --app app run + +# Run TimeFlies example +example-timeflies: + cd examples/timeflies && {{fable}} . --lang Python --outDir build + cd examples/timeflies/build && uv run --group examples python program.py + +# Run Django example +example-django: + cd examples/django && {{fable}} . --lang Python --outDir build + cd examples/django/build && uv run --group django python manage.py runserver + +# Run Django minimal example +example-django-minimal: + cd examples/django-minimal && {{fable}} . --lang Python --outDir build + cd examples/django-minimal/build && uv run --group django python program.py + +# Run FastAPI example +example-fastapi: + cd examples/fastapi && {{fable}} . --lang Python --outDir build + cd examples/fastapi/build && uv run --group fastapi uvicorn app:app --reload + +# Run FastAPI example in dev mode (watch for F# changes and auto-reload) +dev-fastapi: + cd examples/fastapi && {{fable}} . --lang Python --outDir build --watch & cd examples/fastapi/build && sleep 3 && uv run --group fastapi uvicorn app:app --reload + +# Run Pydantic example (importing Python Pydantic models from F#) +example-pydantic: + cd examples/pydantic && {{fable}} . --lang Python --outDir build + cd examples/pydantic/build && uv run python app.py diff --git a/paket.dependencies b/paket.dependencies index 38aed66..911d584 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -4,8 +4,7 @@ source https://api.nuget.org/v3/index.json storage: none framework: netstandard2.0, netstandard2.1, net6.0, net8.0, net9.0, net10.0 -nuget FSharp.Core >= 4.7.2 lowest_matching: true -nuget Fable.Core 5.0.0-beta.2 +nuget Fable.Core 5.0.0-beta.4 group Test source https://api.nuget.org/v3/index.json @@ -13,9 +12,20 @@ group Test framework: net9.0 nuget FSharp.Core - nuget Fable.Core 5.0.0-beta.2 + nuget Fable.Core 5.0.0-beta.4 nuget Microsoft.NET.Test.Sdk ~> 16 nuget xunit ~> 2 - nuget xunit.runner.visualstudio ~> 2 + nuget xunit.runner.visualstudio ~> 2 + +group Examples + source https://api.nuget.org/v3/index.json + storage: none + framework: net9.0 + + nuget FSharp.Core + nuget Fable.Core 5.0.0-beta.4 + nuget Feliz.ViewEngine + nuget Zanaptak.TypedCssClasses + nuget FSharp.Control.AsyncRx diff --git a/paket.lock b/paket.lock index 72bfb6e..aaba6e7 100644 --- a/paket.lock +++ b/paket.lock @@ -2,16 +2,31 @@ STORAGE: NONE RESTRICTION: || (== net10.0) (== net6.0) (== net8.0) (== net9.0) (== netstandard2.0) (== netstandard2.1) NUGET remote: https://api.nuget.org/v3/index.json - Fable.Core (5.0.0-beta.2) + Fable.Core (5.0.0-beta.4) FSharp.Core (>= 4.7.2) FSharp.Core (4.7.2) +GROUP Examples +STORAGE: NONE +RESTRICTION: == net9.0 +NUGET + remote: https://api.nuget.org/v3/index.json + Fable.Core (5.0.0-beta.4) + FSharp.Core (>= 4.7.2) + Feliz.ViewEngine (1.0.3) + FSharp.Core (>= 4.7) + FSharp.Control.AsyncRx (1.6.7) + FSharp.Core (>= 6.0.6) + FSharp.Core (10.0.100) + Zanaptak.TypedCssClasses (1.0) + FSharp.Core (>= 4.3.4) + GROUP Test STORAGE: NONE RESTRICTION: == net9.0 NUGET remote: https://api.nuget.org/v3/index.json - Fable.Core (5.0.0-beta.2) + Fable.Core (5.0.0-beta.4) FSharp.Core (>= 4.7.2) FSharp.Core (10.0.100) Microsoft.CodeCoverage (18.0.1) diff --git a/pyproject.toml b/pyproject.toml index 90ed620..b5fabfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "Fable.Python" -version = "5.0.0-alpha.11" +version = "5.0.0-alpha.20" description = "Fable" authors = [{ name = "Dag Brattli", email = "dag@brattli.net" }] requires-python = ">= 3.12, < 4" readme = "README.md" license = "MIT" dependencies = [ - "fable-library==5.0.0a17", + "fable-library==5.0.0a20", ] [project.urls] @@ -17,6 +17,29 @@ Homepage = "https://fable.io" dev = [ "pytest>=6.2.4,<10", "ruff>=0.14.0", + "pydantic>=2.0.0", + "httpx>=0.28.1", + "fastapi>=0.115.0", +] + +# Example dependency groups +flask = [ + "Flask>=3.1.2,<4", +] +django = [ + "Django>=4.2,<5", +] +fastapi = [ + "fastapi>=0.100.0", + "pydantic>=2.0.0", + "uvicorn[standard]>=0.23.0", +] + +# Meta-group that includes all example dependencies +examples = [ + { include-group = "flask" }, + { include-group = "django" }, + { include-group = "fastapi" }, ] [tool.ruff] diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index d5b72a7..b446621 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -31,6 +31,9 @@ + + + diff --git a/src/fastapi/FastAPI.fs b/src/fastapi/FastAPI.fs new file mode 100644 index 0000000..f3060f5 --- /dev/null +++ b/src/fastapi/FastAPI.fs @@ -0,0 +1,701 @@ +/// FastAPI bindings for F# to Python web framework +/// +/// FastAPI is a modern, fast web framework for building APIs with Python. +/// These bindings support the class-based approach using method decorators. +/// +/// Usage: Create API endpoints using classes with decorated methods: +/// +/// ```fsharp +/// open Fable.Python.FastAPI +/// +/// let app = FastAPI() +/// +/// [] +/// type API() = +/// [] +/// static member root() = +/// {| message = "Hello World" |} +/// +/// [] +/// static member get_item(item_id: int) = +/// {| item_id = item_id |} +/// +/// [] +/// static member create_item(item: Item) = +/// {| status = "created"; item = item |} +/// ``` +/// +/// For routers, use the RouterGet, RouterPost, etc. attributes: +/// +/// ```fsharp +/// let router = APIRouter(prefix = "/users") +/// +/// [] +/// type UsersAPI() = +/// [] +/// static member list_users() = {| users = [] |} +/// ``` +module Fable.Python.FastAPI + +open System +open Fable.Core + +// fsharplint:disable MemberNames,InterfaceNames + +// ============================================================================ +// API Class Attribute +// ============================================================================ + +/// Marks a class as a FastAPI endpoint class with class-level attributes. +/// This is equivalent to [] +[] +type APIClassAttribute() = + inherit Attribute() + +// ============================================================================ +// Route Decorator Attributes (using Py.DecorateTemplate) +// ============================================================================ + +/// GET route decorator for app +[] +type GetAttribute(path: string) = + inherit Attribute() + +/// POST route decorator for app +[] +type PostAttribute(path: string) = + inherit Attribute() + +/// PUT route decorator for app +[] +type PutAttribute(path: string) = + inherit Attribute() + +/// DELETE route decorator for app +[] +type DeleteAttribute(path: string) = + inherit Attribute() + +/// PATCH route decorator for app +[] +type PatchAttribute(path: string) = + inherit Attribute() + +/// OPTIONS route decorator for app +[] +type OptionsAttribute(path: string) = + inherit Attribute() + +/// HEAD route decorator for app +[] +type HeadAttribute(path: string) = + inherit Attribute() + +/// WebSocket route decorator for app +[] +type WebSocketAttribute(path: string) = + inherit Attribute() + +// ============================================================================ +// Router Decorator Attributes +// ============================================================================ + +/// GET route decorator for router +[] +type RouterGetAttribute(path: string) = + inherit Attribute() + +/// POST route decorator for router +[] +type RouterPostAttribute(path: string) = + inherit Attribute() + +/// PUT route decorator for router +[] +type RouterPutAttribute(path: string) = + inherit Attribute() + +/// DELETE route decorator for router +[] +type RouterDeleteAttribute(path: string) = + inherit Attribute() + +/// PATCH route decorator for router +[] +type RouterPatchAttribute(path: string) = + inherit Attribute() + +/// WebSocket route decorator for router +[] +type RouterWebSocketAttribute(path: string) = + inherit Attribute() + +// ============================================================================ +// Response Classes +// ============================================================================ + +/// JSON response class +[] +type JSONResponse(content: obj, ?status_code: int) = + class end + +/// HTML response class +[] +type HTMLResponse(content: string, ?status_code: int) = + class end + +/// Plain text response class +[] +type PlainTextResponse(content: string, ?status_code: int) = + class end + +/// Redirect response class +[] +type RedirectResponse(url: string, ?status_code: int) = + class end + +/// Streaming response class +[] +type StreamingResponse(content: obj, ?media_type: string) = + class end + +/// File response class +[] +type FileResponse(path: string, ?filename: string, ?media_type: string) = + class end + +// ============================================================================ +// Request and WebSocket +// ============================================================================ + +/// FastAPI Request object +[] +type Request() = + /// The request method (GET, POST, etc.) + [] + member _.method: string = nativeOnly + + /// The request URL + [] + member _.url: obj = nativeOnly + + /// The request headers + [] + member _.headers: obj = nativeOnly + + /// The query parameters + [] + member _.query_params: obj = nativeOnly + + /// The path parameters + [] + member _.path_params: obj = nativeOnly + + /// The cookies + [] + member _.cookies: obj = nativeOnly + + /// The client information + [] + member _.client: obj = nativeOnly + + /// Get the request body as JSON + [] + member _.json() : Async = nativeOnly + + /// Get the request body as bytes + [] + member _.body() : Async = nativeOnly + + /// Get form data + [] + member _.form() : Async = nativeOnly + +/// WebSocket connection +[] +type WebSocket() = + /// Accept the WebSocket connection + [] + member _.accept() : Async = nativeOnly + + /// Close the WebSocket connection + [] + member _.close() : Async = nativeOnly + + /// Close with a specific code + [] + member _.close_with_code(_code: int) : Async = nativeOnly + + /// Send text data + [] + member _.send_text(_data: string) : Async = nativeOnly + + /// Send bytes data + [] + member _.send_bytes(_data: byte array) : Async = nativeOnly + + /// Send JSON data + [] + member _.send_json(_data: obj) : Async = nativeOnly + + /// Receive text data + [] + member _.receive_text() : Async = nativeOnly + + /// Receive bytes data + [] + member _.receive_bytes() : Async = nativeOnly + + /// Receive JSON data + [] + member _.receive_json() : Async = nativeOnly + +// ============================================================================ +// HTTP Exceptions +// ============================================================================ + +/// HTTP exception for returning error responses +[] +type HTTPException(status_code: int, ?detail: string) = + class end + +// ============================================================================ +// Dependency Injection +// ============================================================================ + +/// Dependency injection marker +[] +[] +let Depends (_dependency: unit -> 'T) : 'T = nativeOnly + +/// Dependency injection with callable +[] +[] +let DependsOn (_dependency: obj) : obj = nativeOnly + +// ============================================================================ +// Path, Query, Body, Header, Cookie parameters +// ============================================================================ + +/// Path parameter with metadata +[] +type PathParam = + /// Create a path parameter with default value + [] + static member Default(_value: 'T) : 'T = nativeOnly + + /// Create a path parameter with title + [] + static member Title(_title: string) : 'T = nativeOnly + + /// Create a path parameter with description + [] + static member Description(_description: string) : 'T = nativeOnly + + /// Create a path parameter with ge constraint + [] + static member Ge(_value: float) : 'T = nativeOnly + + /// Create a path parameter with le constraint + [] + static member Le(_value: float) : 'T = nativeOnly + + /// Create a path parameter with gt constraint + [] + static member Gt(_value: float) : 'T = nativeOnly + + /// Create a path parameter with lt constraint + [] + static member Lt(_value: float) : 'T = nativeOnly + +/// Query parameter with metadata +[] +type QueryParam = + /// Create a query parameter with default value + [] + static member Default(_value: 'T) : 'T = nativeOnly + + /// Create an optional query parameter (default None) + [] + static member Optional() : 'T option = nativeOnly + + /// Create a query parameter with title + [] + static member Title(_title: string) : 'T = nativeOnly + + /// Create a query parameter with description + [] + static member Description(_description: string) : 'T = nativeOnly + + /// Create a query parameter with alias + [] + static member Alias(_alias: string) : 'T = nativeOnly + + /// Create a query parameter with min_length constraint + [] + static member MinLength(_length: int) : 'T = nativeOnly + + /// Create a query parameter with max_length constraint + [] + static member MaxLength(_length: int) : 'T = nativeOnly + + /// Create a query parameter with regex pattern + [] + static member Pattern(_pattern: string) : 'T = nativeOnly + +/// Body parameter with metadata +[] +type BodyParam = + /// Create a body parameter with embed flag + [] + static member Embed(_embed: bool) : 'T = nativeOnly + + /// Create a body parameter with title + [] + static member Title(_title: string) : 'T = nativeOnly + + /// Create a body parameter with description + [] + static member Description(_description: string) : 'T = nativeOnly + +/// Header parameter with metadata +[] +type HeaderParam = + /// Create a header parameter with default value + [] + static member Default(_value: 'T) : 'T = nativeOnly + + /// Create an optional header parameter + [] + static member Optional() : 'T option = nativeOnly + + /// Create a header parameter with alias + [] + static member Alias(_alias: string) : 'T = nativeOnly + +/// Cookie parameter with metadata +[] +type CookieParam = + /// Create a cookie parameter with default value + [] + static member Default(_value: 'T) : 'T = nativeOnly + + /// Create an optional cookie parameter + [] + static member Optional() : 'T option = nativeOnly + +// ============================================================================ +// File Upload +// ============================================================================ + +/// Uploaded file type +[] +type UploadFile() = + /// The filename + [] + member _.filename: string = nativeOnly + + /// The content type + [] + member _.content_type: string = nativeOnly + + /// The file size + [] + member _.size: int = nativeOnly + + /// Read the file content + [] + member _.read() : Async = nativeOnly + + /// Write content to the file + [] + member _.write(_data: byte array) : Async = nativeOnly + + /// Seek to a position + [] + member _.seek(_pos: int) : Async = nativeOnly + + /// Close the file + [] + member _.close() : Async = nativeOnly + +/// File parameter marker +[] +[] +let File() : UploadFile = nativeOnly + +/// Form parameter marker +[] +type FormParam = + /// Create a form parameter with default value + [] + static member Default(_value: 'T) : 'T = nativeOnly + + /// Create a form parameter + [] + static member Create() : 'T = nativeOnly + +// ============================================================================ +// APIRouter +// ============================================================================ + +/// API Router for modular route organization +[] +type APIRouter(?prefix: string, ?tags: ResizeArray) = + /// Include another router + [] + member _.include_router(_router: APIRouter) : unit = nativeOnly + + /// Include another router with prefix + [] + member _.include_router_with_prefix(_router: APIRouter, _prefix: string) : unit = nativeOnly + + /// Include another router with prefix and tags + [] + member _.include_router_with_prefix_and_tags(_router: APIRouter, _prefix: string, _tags: ResizeArray) : unit = nativeOnly + +// ============================================================================ +// FastAPI Application +// ============================================================================ + +/// FastAPI application instance +[] +type FastAPI(?title: string, ?description: string, ?version: string) = + /// Include a router + [] + member _.include_router(_router: APIRouter) : unit = nativeOnly + + /// Include a router with prefix + [] + member _.include_router_with_prefix(_router: APIRouter, _prefix: string) : unit = nativeOnly + + /// Include a router with prefix and tags + [] + member _.include_router_with_prefix_and_tags(_router: APIRouter, _prefix: string, _tags: ResizeArray) : unit = nativeOnly + + /// Add middleware + [] + member _.add_middleware(_middleware: obj) : unit = nativeOnly + + /// Add exception handler + [] + member _.add_exception_handler(_exc_class: obj, _handler: obj) : unit = nativeOnly + + /// Get the OpenAPI schema + [] + member _.openapi() : obj = nativeOnly + + /// The OpenAPI schema (cached) + [] + member _.openapi_schema: obj = nativeOnly + + /// The app title + [] + member _.title: string = nativeOnly + + /// The app description + [] + member _.description: string = nativeOnly + + /// The app version + [] + member _.version: string = nativeOnly + +// ============================================================================ +// Status Codes (commonly used) +// ============================================================================ + +/// HTTP status codes module +module status = + let HTTP_200_OK = 200 + let HTTP_201_CREATED = 201 + let HTTP_204_NO_CONTENT = 204 + let HTTP_301_MOVED_PERMANENTLY = 301 + let HTTP_302_FOUND = 302 + let HTTP_304_NOT_MODIFIED = 304 + let HTTP_307_TEMPORARY_REDIRECT = 307 + let HTTP_308_PERMANENT_REDIRECT = 308 + let HTTP_400_BAD_REQUEST = 400 + let HTTP_401_UNAUTHORIZED = 401 + let HTTP_403_FORBIDDEN = 403 + let HTTP_404_NOT_FOUND = 404 + let HTTP_405_METHOD_NOT_ALLOWED = 405 + let HTTP_409_CONFLICT = 409 + let HTTP_422_UNPROCESSABLE_ENTITY = 422 + let HTTP_429_TOO_MANY_REQUESTS = 429 + let HTTP_500_INTERNAL_SERVER_ERROR = 500 + let HTTP_502_BAD_GATEWAY = 502 + let HTTP_503_SERVICE_UNAVAILABLE = 503 + let HTTP_504_GATEWAY_TIMEOUT = 504 + +// ============================================================================ +// Background Tasks +// ============================================================================ + +/// Background tasks for running tasks after response +[] +type BackgroundTasks() = + /// Add a task to run in the background + [] + member _.add_task(_func: unit -> unit) : unit = nativeOnly + + /// Add a task with arguments + [] + member _.add_task_with_args(_func: obj, [] _args: obj array) : unit = nativeOnly + +// ============================================================================ +// Security +// ============================================================================ + +/// OAuth2 password bearer scheme +[] +type OAuth2PasswordBearer = + [] + [] + static member Create(_tokenUrl: string) : OAuth2PasswordBearer = nativeOnly + +/// OAuth2 password request form +[] +type OAuth2PasswordRequestForm() = + /// The username + [] + member _.username: string = nativeOnly + + /// The password + [] + member _.password: string = nativeOnly + + /// The scope + [] + member _.scope: string = nativeOnly + +/// HTTP Basic authentication +[] +type HTTPBasic() = + class end + +/// HTTP Basic credentials +[] +type HTTPBasicCredentials() = + /// The username + [] + member _.username: string = nativeOnly + + /// The password + [] + member _.password: string = nativeOnly + +/// HTTP Bearer authentication +[] +type HTTPBearer() = + class end + +/// HTTP Bearer credentials (token in Authorization header) +[] +type HTTPAuthorizationCredentials() = + /// The scheme (e.g., "Bearer") + [] + member _.scheme: string = nativeOnly + + /// The credentials (the token) + [] + member _.credentials: string = nativeOnly + +/// API Key in header +[] +type APIKeyHeader(name: string) = + class end + +/// API Key in query parameter +[] +type APIKeyQuery(name: string) = + class end + +/// API Key in cookie +[] +type APIKeyCookie(name: string) = + class end + +// ============================================================================ +// CORS Middleware +// ============================================================================ + +/// CORS middleware for handling Cross-Origin Resource Sharing +[] +type CORSMiddleware = + class end + +/// CORS middleware configuration helper +module CORSMiddleware = + /// Create CORS middleware configuration + [] + [] + let middleware: obj = nativeOnly + + /// Add CORS middleware to app with all origins allowed + [] + let addToApp (_app: FastAPI) (_middleware: obj) (_origins: string array) (_credentials: bool) (_methods: string array) (_headers: string array) : unit = nativeOnly + +// ============================================================================ +// Encoders +// ============================================================================ + +/// JSON-compatible encoder for converting objects +[] +[] +let jsonable_encoder (_obj: 'T) : obj = nativeOnly + +/// JSON-compatible encoder with options +[] +[] +let jsonable_encoder_exclude_unset (_obj: 'T) (_exclude_unset: bool) : obj = nativeOnly + +// ============================================================================ +// Testclient +// ============================================================================ + +/// Test client for testing FastAPI applications +[] +type TestClient(app: FastAPI) = + /// Make a GET request + [] + member _.get(_url: string) : obj = nativeOnly + + /// Make a POST request + [] + member _.post(_url: string) : obj = nativeOnly + + /// Make a POST request with JSON body + [] + member _.post_json(_url: string, _json: obj) : obj = nativeOnly + + /// Make a PUT request + [] + member _.put(_url: string) : obj = nativeOnly + + /// Make a PUT request with JSON body + [] + member _.put_json(_url: string, _json: obj) : obj = nativeOnly + + /// Make a DELETE request + [] + member _.delete(_url: string) : obj = nativeOnly + + /// Make a PATCH request + [] + member _.patch(_url: string) : obj = nativeOnly + + /// Make a PATCH request with JSON body + [] + member _.patch_json(_url: string, _json: obj) : obj = nativeOnly + +// ============================================================================ +// Response type for test client +// ============================================================================ + +/// Response from test client +type TestResponse = + abstract status_code: int + abstract json: unit -> obj + abstract text: string + abstract headers: obj diff --git a/src/fastapi/README.md b/src/fastapi/README.md new file mode 100644 index 0000000..bb6bf9b --- /dev/null +++ b/src/fastapi/README.md @@ -0,0 +1,190 @@ +# Fable.Python.FastAPI + +F# bindings for [FastAPI](https://fastapi.tiangolo.com/), a modern, fast web framework for building APIs with Python. + +## Features + +- Class-based API endpoints using decorator attributes +- HTTP method decorators: `Get`, `Post`, `Put`, `Delete`, `Patch`, `Options`, `Head` +- Router-based API organization with `RouterGet`, `RouterPost`, etc. +- Pydantic model integration for request/response validation +- Full FastAPI feature support: dependency injection, security, responses, etc. + +## Basic Usage + +```fsharp +open Fable.Python.FastAPI +open Fable.Python.Pydantic + +// Create FastAPI app +let app = FastAPI(title = "My API", version = "1.0.0") + +// Define a Pydantic model +[] +type User(Id: int, Name: string, Email: string) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + member val Email: string = Email with get, set + +// Define API endpoints +[] +type API() = + [] + static member root() : obj = + {| message = "Hello World" |} + + [] + static member get_user(user_id: int) : User = + User(Id = user_id, Name = "Alice", Email = "alice@example.com") + + [] + static member create_user(user: User) : obj = + {| status = "created"; user = user |} +``` + +## Router-based APIs + +```fsharp +let router = APIRouter(prefix = "/users", tags = ResizeArray ["users"]) + +[] +type UsersAPI() = + [] + static member list_users() : obj = + {| users = [] |} + + [] + static member get_user(user_id: int) : obj = + {| user_id = user_id |} +``` + +## Important: Serialization with Anonymous Records + +When returning anonymous records (`{| ... |}`) from API endpoints, be aware that F#'s `int` type compiles to Fable's `int32`, which is not a native Python `int`. This can cause serialization errors with FastAPI's `jsonable_encoder`. + +### The Problem + +```fsharp +// This may fail with: "Unable to serialize unknown type: Int32" +[] +static member delete_item(item_id: int) : obj = + {| status = "deleted"; item_id = item_id |} // item_id is int32, not native int +``` + +### Why It Happens + +- Pydantic models work fine because `int32` has `__get_pydantic_core_schema__` +- Anonymous records compile to plain Python dicts +- FastAPI's `jsonable_encoder` doesn't use Pydantic schemas for plain dicts +- `int32` is not a true Python `int` subclass due to [PyO3 limitations](https://github.com/PyO3/pyo3/issues/991) + +### Solutions + +**Option 1: Use `nativeint` for values in anonymous records** + +```fsharp +[] +static member delete_item(item_id: int) : obj = + {| status = "deleted"; item_id = nativeint item_id |} +``` + +**Option 2: Define response models as Pydantic BaseModels** + +```fsharp +[] +type DeleteResponse(Status: string, ItemId: int) = + inherit BaseModel() + member val Status: string = Status with get, set + member val ItemId: int = ItemId with get, set + +[] +static member delete_item(item_id: int) : DeleteResponse = + DeleteResponse(Status = "deleted", ItemId = item_id) +``` + +**Option 3: Return Pydantic models directly** + +Pydantic models serialize correctly because FastAPI uses the model's schema: + +```fsharp +[] +static member get_users() : ResizeArray = + users // ResizeArray works, User is a BaseModel +``` + +### Summary + +| Return Type | int32 Serialization | +|-------------|---------------------| +| Pydantic BaseModel | Works (uses `__get_pydantic_core_schema__`) | +| Anonymous record `{| ... |}` | Fails (use `nativeint` or response models) | +| `ResizeArray` where T is BaseModel | Works | + +## Collections + +Use `ResizeArray` instead of F# arrays or lists for API responses: + +```fsharp +let users = ResizeArray() + +[] +static member get_users() : ResizeArray = + users // Compiles to Python list, serializes correctly +``` + +F# arrays (`[| ... |]`) compile to Fable's `FSharpArray` which may not serialize correctly with FastAPI. + +## Available Attributes + +### Route Decorators (for `app`) + +- `[]` - HTTP GET +- `[]` - HTTP POST +- `[]` - HTTP PUT +- `[]` - HTTP DELETE +- `[]` - HTTP PATCH +- `[]` - HTTP OPTIONS +- `[]` - HTTP HEAD +- `[]` - WebSocket endpoint + +### Router Decorators (for `router`) + +- `[]` +- `[]` +- `[]` +- `[]` +- `[]` +- `[]` + +### Class Attribute + +- `[]` - Marks a class for FastAPI routing (equivalent to `[]`) + +## Running the Server + +```bash +# Build F# to Python +dotnet fable --lang python + +# Run with uvicorn +uvicorn app:app --reload +``` + +## Development Mode + +For hot-reloading during development: + +```bash +# Terminal 1: Watch F# files +dotnet fable --lang python --watch + +# Terminal 2: Run uvicorn with reload +uvicorn app:app --reload +``` + +Or use the justfile: + +```bash +just dev-fastapi +``` diff --git a/src/flask/Flask.fs b/src/flask/Flask.fs index 23241b3..f07348f 100644 --- a/src/flask/Flask.fs +++ b/src/flask/Flask.fs @@ -1,53 +1,381 @@ -/// Type bindings for Flask web framework: https://flask.palletsprojects.com/ +/// Flask bindings for F# to Python web framework +/// +/// Flask is a lightweight WSGI web application framework for Python. +/// These bindings support the class-based approach using method decorators. +/// +/// Usage: Create routes using classes with decorated methods: +/// +/// ```fsharp +/// open Fable.Python.Flask +/// +/// let app = Flask.Create(__name__) +/// +/// [] +/// type Routes() = +/// [] +/// static member index() = +/// "Hello World!" +/// +/// [")>] +/// static member get_user(user_id: int) = +/// {| user_id = user_id |} +/// +/// [] +/// static member create_user() = +/// {| status = "created" |} +/// ``` module Fable.Python.Flask +open System open Fable.Core -// fsharplint:disable MemberNames +// fsharplint:disable MemberNames,InterfaceNames -/// Base interface for Flask request objects -type RequestBase = - /// The URL of the current request - abstract url: string +// ============================================================================ +// API Class Attribute +// ============================================================================ + +/// Marks a class as a Flask endpoint class with class-level attributes. +[] +type APIClassAttribute() = + inherit Attribute() + +// ============================================================================ +// Route Decorator Attributes +// ============================================================================ + +/// Route decorator for Flask app +[] +type RouteAttribute(path: string) = + inherit Attribute() + +/// Route decorator with HTTP methods +[] +type RouteMethodsAttribute(path: string, methods: string array) = + inherit Attribute() + +/// GET route decorator +[] +type GetAttribute(path: string) = + inherit Attribute() + +/// POST route decorator +[] +type PostAttribute(path: string) = + inherit Attribute() + +/// PUT route decorator +[] +type PutAttribute(path: string) = + inherit Attribute() + +/// DELETE route decorator +[] +type DeleteAttribute(path: string) = + inherit Attribute() + +/// PATCH route decorator +[] +type PatchAttribute(path: string) = + inherit Attribute() + +// ============================================================================ +// Blueprint Route Decorators +// ============================================================================ + +/// Blueprint route decorator +[] +type BpRouteAttribute(path: string) = + inherit Attribute() + +/// Blueprint GET route decorator +[] +type BpGetAttribute(path: string) = + inherit Attribute() + +/// Blueprint POST route decorator +[] +type BpPostAttribute(path: string) = + inherit Attribute() + +/// Blueprint PUT route decorator +[] +type BpPutAttribute(path: string) = + inherit Attribute() + +/// Blueprint DELETE route decorator +[] +type BpDeleteAttribute(path: string) = + inherit Attribute() + +// ============================================================================ +// Request Object +// ============================================================================ /// Flask request object containing information about the current HTTP request -type Request = - inherit RequestBase - -/// Flask application object -type Flask = - /// Decorator to register a view function for a given URL rule - abstract route: rule: string -> ((unit -> string) -> Flask) - /// Decorator to register a view function for a given URL rule with specific HTTP methods - abstract route: rule: string * methods: string array -> ((unit -> string) -> Flask) - -/// Static constructor interface for Flask application -type FlaskStatic = - /// Create a Flask application with a static URL path - [] - abstract Create: name: string * static_url_path: string -> Flask +[] +type Request() = + /// The request method (GET, POST, etc.) + [] + member _.method: string = nativeOnly + + /// The full URL of the request + [] + member _.url: string = nativeOnly + + /// The URL path + [] + member _.path: string = nativeOnly + + /// Query string arguments as a dict + [] + member _.args: obj = nativeOnly + + /// Form data from POST/PUT requests + [] + member _.form: obj = nativeOnly + + /// Combined args and form data + [] + member _.values: obj = nativeOnly + + /// Request headers + [] + member _.headers: obj = nativeOnly + + /// Cookies + [] + member _.cookies: obj = nativeOnly + + /// The raw request data as bytes + [] + member _.data: byte array = nativeOnly + + /// Get JSON data from request body + [] + member _.get_json() : obj = nativeOnly + + /// Get JSON data with options + [] + member _.get_json_with_options(_force: bool, _silent: bool) : obj = nativeOnly + + /// Check if request is JSON + [] + member _.is_json: bool = nativeOnly + + /// Content type of request + [] + member _.content_type: string = nativeOnly + +/// The current request object (context local) +[] +let request: Request = nativeOnly + +// ============================================================================ +// Response Helpers +// ============================================================================ + +/// Create a JSON response +[] +[] +let jsonify (_data: obj) : obj = nativeOnly + +/// Create a redirect response +[] +[] +let redirect (_location: string) : obj = nativeOnly + +/// Create a redirect response with status code +[] +[] +let redirect_with_code (_location: string) (_code: int) : obj = nativeOnly + +/// Generate a URL for the given endpoint +[] +[] +let url_for (_endpoint: string) : string = nativeOnly + +/// Generate a URL for the given endpoint with filename +[] +[] +let url_for_static (_endpoint: string) (_filename: string) : string = nativeOnly + +/// Render a template +[] +[] +let render_template (_template_name: string) : string = nativeOnly + +/// Render a template with context +[] +[] +let render_template_with_context (_template_name: string) (_context: obj) : string = nativeOnly -/// Flask application constructor +/// Abort request with HTTP status code +[] +[] +let abort (_code: int) : unit = nativeOnly + +/// Abort request with status code and description +[] +[] +let abort_with_message (_code: int) (_description: string) : unit = nativeOnly + +// ============================================================================ +// Response Class +// ============================================================================ + +/// Flask Response object +[] +type Response(?response: string, ?status: int, ?mimetype: string) = + /// Set a header + [] + member _.set_header(_name: string, _value: string) : unit = nativeOnly + + /// Set a cookie + [] + member _.set_cookie(_key: string, _value: string) : unit = nativeOnly + + /// Set a cookie with options + [] + member _.set_cookie_with_options(_key: string, _value: string, _max_age: int, _httponly: bool) : unit = nativeOnly + + /// Delete a cookie + [] + member _.delete_cookie(_key: string) : unit = nativeOnly + +/// Create a response with make_response +[] +[] +let make_response (_body: obj) : Response = nativeOnly + +/// Create a response with status code +[] +[] +let make_response_with_status (_body: obj) (_status: int) : Response = nativeOnly + +// ============================================================================ +// Blueprint +// ============================================================================ + +/// Flask Blueprint for modular applications +[] +type Blueprint(name: string, import_name: string, ?url_prefix: string) = + class end + +// ============================================================================ +// Flask Application +// ============================================================================ + +/// Flask application instance [] -let Flask: FlaskStatic = nativeOnly - -[] -type IExports = - /// Render a template by name - /// See https://flask.palletsprojects.com/api/#flask.render_template - abstract render_template: template_name_or_list: string -> string - /// Render a template from a sequence of names (tries each until one succeeds) - /// See https://flask.palletsprojects.com/api/#flask.render_template - abstract render_template: template_name_or_list: string seq -> string - /// The current request object - /// See https://flask.palletsprojects.com/api/#flask.request - abstract request: Request - - /// Generate a URL for the given endpoint with the given filename - /// See https://flask.palletsprojects.com/api/#flask.url_for - [] - abstract url_for: endpoint: string * filename: string -> string - -/// Flask web framework module -[] -let flask: IExports = nativeOnly +type Flask(import_name: string, ?static_url_path: string, ?static_folder: string, ?template_folder: string) = + /// Register a blueprint + [] + member _.register_blueprint(_blueprint: Blueprint) : unit = nativeOnly + + /// Register a blueprint with URL prefix + [] + member _.register_blueprint_with_prefix(_blueprint: Blueprint, _url_prefix: string) : unit = nativeOnly + + /// Run the development server + [] + member _.run() : unit = nativeOnly + + /// Run the development server with options + [] + member _.run_with_options(_host: string, _port: int, _debug: bool) : unit = nativeOnly + + /// Application configuration + [] + member _.config: obj = nativeOnly + + /// Add a URL rule + [] + member _.add_url_rule(_rule: string, _endpoint: string, _view_func: obj) : unit = nativeOnly + + /// Register an error handler + [] + member _.errorhandler(_code: int) : (('T -> obj) -> unit) = nativeOnly + + /// Before request decorator + [] + member _.before_request: ((unit -> unit) -> unit) = nativeOnly + + /// After request decorator + [] + member _.after_request: ((Response -> Response) -> unit) = nativeOnly + +/// Static constructor for Flask (alternative syntax) +module Flask = + /// Create a Flask application + [] + [] + let Create (_import_name: string) : Flask = nativeOnly + + /// Create a Flask application with static URL path + [] + [] + let CreateWithStaticPath (_import_name: string) (_static_url_path: string) : Flask = nativeOnly + +// ============================================================================ +// Session +// ============================================================================ + +/// Flask session object (requires secret key) +[] +type Session() = + /// Get a session value + [] + member _.get(_key: string) : obj = nativeOnly + + /// Set a session value + [] + member _.set(_key: string, _value: obj) : unit = nativeOnly + + /// Remove a session value + [] + member _.pop(_key: string) : obj = nativeOnly + + /// Clear the session + [] + member _.clear() : unit = nativeOnly + +/// The current session object +[] +let session: Session = nativeOnly + +// ============================================================================ +// Flash Messages +// ============================================================================ + +/// Flash a message to the user +[] +[] +let flash (_message: string) : unit = nativeOnly + +/// Flash a message with a category +[] +[] +let flash_with_category (_message: string) (_category: string) : unit = nativeOnly + +/// Get flashed messages +[] +[] +let get_flashed_messages () : string array = nativeOnly + +/// Get flashed messages with categories +[] +[] +let get_flashed_messages_with_categories () : (string * string) array = nativeOnly + +// ============================================================================ +// Application Context +// ============================================================================ + +/// Current application context +[] +let current_app: Flask = nativeOnly + +/// Application context globals +[] +let g: obj = nativeOnly diff --git a/src/flask/README.md b/src/flask/README.md new file mode 100644 index 0000000..e14eeaf --- /dev/null +++ b/src/flask/README.md @@ -0,0 +1,279 @@ +# Fable.Python.Flask + +F# bindings for [Flask](https://flask.palletsprojects.com/), a lightweight WSGI web application framework for Python. + +## Features + +- Class-based routes using decorator attributes +- HTTP method decorators: `Route`, `Get`, `Post`, `Put`, `Delete`, `Patch` +- Blueprint support for modular applications +- Full Flask feature support: request, response, session, flash messages, etc. + +## Basic Usage + +```fsharp +open Fable.Python.Builtins +open Fable.Python.Flask + +// Create Flask app +let app = Flask(__name__) + +// Define routes using class-based pattern +[] +type Routes() = + [] + static member index() : string = + "Hello World!" + + [")>] + static member get_user(user_id: int) : obj = + jsonify {| user_id = user_id; name = "Alice" |} + + [] + static member create_user() : obj = + let data = request.get_json() + jsonify {| status = "created"; data = data |} +``` + +## Route Decorators + +### Basic Route + +```fsharp +[] +static member handler() = "response" + +// With methods +[] +static member handler() = "response" +``` + +### HTTP Method Shortcuts + +```fsharp +[] +static member list_users() = ... + +[] +static member create_user() = ... + +[")>] +static member update_user(id: int) = ... + +[")>] +static member delete_user(id: int) = ... + +[")>] +static member patch_user(id: int) = ... +``` + +## Blueprint Routes + +For modular applications using Blueprints: + +```fsharp +let bp = Blueprint("users", __name__, url_prefix = "/users") + +[] +type UserRoutes() = + [] + static member list_users() = ... + + [] + static member create_user() = ... + + [")>] + static member get_user(user_id: int) = ... + +// Register blueprint +app.register_blueprint(bp) +``` + +## Request Object + +Access the current request: + +```fsharp +open Fable.Python.Flask + +[] +static member handle_data() = + // Get JSON body + let data = request.get_json() + + // Access request properties + let method = request.method // "POST" + let path = request.path // "/data" + let isJson = request.is_json // true + + // Query parameters + let args = request.args + + // Form data + let form = request.form + + jsonify {| received = data |} +``` + +## Response Helpers + +```fsharp +// JSON response +let response = jsonify {| message = "Hello" |} + +// Redirect +let redir = redirect "/other-page" +let redir2 = redirect_with_code "/other" 301 + +// Abort with error +abort 404 +abort_with_message 404 "User not found" + +// Custom response +let resp = make_response "Hello" +let resp2 = make_response_with_status "Created" 201 +``` + +## Templates + +```fsharp +// Render a template +let html = render_template "index.html" + +// With context variables +let html = render_template_with_context "user.html" {| name = "Alice"; age = 30n |} +``` + +## Session + +```fsharp +// Set secret key first +app.config?SECRET_KEY <- "your-secret-key" + +[] +static member login() = + session.set("user_id", 123) + jsonify {| status = "logged in" |} + +[] +static member profile() = + let userId = session.get("user_id") + jsonify {| user_id = userId |} + +[] +static member logout() = + session.clear() + redirect "/" +``` + +## Flash Messages + +```fsharp +[] +static member do_action() = + flash "Action completed successfully!" + flash_with_category "Please verify your email" "warning" + redirect "/" + +// In template or route +let messages = get_flashed_messages() +let messagesWithCategories = get_flashed_messages_with_categories() +``` + +## Error Handlers + +```fsharp +// Register error handler +app.errorhandler 404 (fun error -> + jsonify {| error = "Not found" |} +) + +app.errorhandler 500 (fun error -> + jsonify {| error = "Internal server error" |} +) +``` + +## Middleware + +```fsharp +// Before each request +app.before_request (fun () -> + printfn "Request: %s %s" request.method request.path +) + +// After each request +app.after_request (fun response -> + response.set_header("X-Custom-Header", "value") + response +) +``` + +## Running the Application + +```fsharp +// Development server +app.run() + +// With options +app.run_with_options("0.0.0.0", 5000, true) +``` + +Or use the Flask CLI: + +```bash +# Build F# to Python +dotnet fable --lang python + +# Run with Flask +flask --app app run --reload +``` + +## URL Building + +```fsharp +// Generate URL for endpoint +let url = url_for "index" + +// For static files +let staticUrl = url_for_static "static" "style.css" +``` + +## Important Notes + +### Serialization + +Flask's `jsonify` does not handle Fable types like `int32`. Use the Fable.Python `json` module which includes a `fableDefault` handler: + +```fsharp +open Fable.Python.Json + +// Use json.dumps with fableDefault for Fable type support +[] +static member get_data() = + json.dumps({| name = "Alice"; age = 30 |}, ``default`` = fableDefault) + +// Or use the convenience function +[] +static member get_data() = + Json.dumps {| name = "Alice"; age = 30 |} +``` + +For simple cases where you're only using native Python types (strings, native ints), Flask's `jsonify` works: + +```fsharp +[] +static member simple() = + jsonify {| message = "Hello"; count = 42n |} // Use 'n' suffix for native int +``` + +## Development Mode + +For hot-reloading during development: + +```bash +# Terminal 1: Watch F# files +dotnet fable --lang python --watch + +# Terminal 2: Run Flask with reload +flask --app app run --reload +``` diff --git a/src/pydantic/Pydantic.fs b/src/pydantic/Pydantic.fs new file mode 100644 index 0000000..8b95cda --- /dev/null +++ b/src/pydantic/Pydantic.fs @@ -0,0 +1,461 @@ +/// Pydantic bindings for F# to Python data validation and serialization +/// +/// Usage: Create Pydantic models by inheriting from BaseModel and using +/// the ClassAttributes attribute: +/// +/// ```fsharp +/// [] +/// type User(name: string, age: int, email: string option) = +/// inherit BaseModel() +/// member val Name: string = name with get, set +/// member val Age: int = age with get, set +/// member val Email: string option = email with get, set +/// ``` +module Fable.Python.Pydantic + +open Fable.Core + +// fsharplint:disable MemberNames,InterfaceNames + +// ============================================================================ +// Field Configuration +// ============================================================================ + +/// Erased type for Pydantic Field values +[] +type Field<'T> = 'T + +/// Field helper functions for creating Pydantic field configurations +module Field = + /// Create a field marked as frozen (immutable) + [] + [] + let Frozen (_frozen: bool) : Field<'T> = nativeOnly + + /// Create a field with a default value + [] + [] + let Default (_value: 'T) : Field<'T> = nativeOnly + + /// Create a field with a default factory function + [] + [] + let DefaultFactory (_factory: unit -> 'T) : Field<'T> = nativeOnly + + /// Create a field with an alias + [] + [] + let Alias (_alias: string) : Field<'T> = nativeOnly + + /// Create a field with a title + [] + [] + let Title (_title: string) : Field<'T> = nativeOnly + + /// Create a field with a description + [] + [] + let Description (_description: string) : Field<'T> = nativeOnly + + /// Create a field with gt (greater than) constraint + [] + [] + let Gt (_value: float) : Field<'T> = nativeOnly + + /// Create a field with ge (greater than or equal) constraint + [] + [] + let Ge (_value: float) : Field<'T> = nativeOnly + + /// Create a field with lt (less than) constraint + [] + [] + let Lt (_value: float) : Field<'T> = nativeOnly + + /// Create a field with le (less than or equal) constraint + [] + [] + let Le (_value: float) : Field<'T> = nativeOnly + + /// Create a field with min_length constraint + [] + [] + let MinLength (_length: int) : Field<'T> = nativeOnly + + /// Create a field with max_length constraint + [] + [] + let MaxLength (_length: int) : Field<'T> = nativeOnly + + /// Create a field with pattern (regex) constraint + [] + [] + let Pattern (_pattern: string) : Field<'T> = nativeOnly + + /// Create a field with multiple constraints + [] + [] + let WithRange (_defaultValue: 'T) (_ge: float) (_le: float) : Field<'T> = nativeOnly + + /// Create a field with default and description + [] + [] + let WithDescription (_defaultValue: 'T) (_description: string) : Field<'T> = nativeOnly + +// ============================================================================ +// BaseModel Class +// ============================================================================ + +/// Pydantic BaseModel base class - inherit from this for Pydantic models +[] +type BaseModel() = + /// Convert model to dictionary + [] + member _.model_dump() : obj = nativeOnly + + /// Convert model to JSON string + [] + member _.model_dump_json() : string = nativeOnly + + /// Convert model to JSON string with indentation + [] + member _.model_dump_json_indented(_indent: int) : string = nativeOnly + + /// Create a copy of the model + [] + member _.model_copy() : BaseModel = nativeOnly + + /// Create a copy of the model with updated values + [] + member _.model_copy_with(_update: obj) : BaseModel = nativeOnly + + /// Get model fields info (class property) + [] + member _.model_fields: obj = nativeOnly + + /// Generate JSON schema + [] + member _.model_json_schema() : obj = nativeOnly + + /// Get model configuration + [] + member _.model_config: obj = nativeOnly + + /// Validate data and create a model instance (class method called via instance) + [] + member _.model_validate(_data: obj) : BaseModel = nativeOnly + + /// Validate JSON string and create a model instance (class method called via instance) + [] + member _.model_validate_json(_json: string) : BaseModel = nativeOnly + + /// Create a model instance without validation (for performance) + [] + member _.model_construct(_data: obj) : BaseModel = nativeOnly + +// ============================================================================ +// ConfigDict - Model Configuration +// ============================================================================ + +/// Options for extra field handling +[] +type ExtraFieldsMode = + | [] Forbid + | [] Allow + | [] Ignore + +/// ConfigDict helper type for model configuration +type ConfigDictBuilder = + /// Create a ConfigDict with extra fields mode + [] + [] + static member WithExtra(_extra: string) : obj = nativeOnly + + /// Create a ConfigDict with strict mode + [] + [] + static member WithStrict(_strict: bool) : obj = nativeOnly + + /// Create a ConfigDict with frozen mode + [] + [] + static member WithFrozen(_frozen: bool) : obj = nativeOnly + + /// Create a ConfigDict with validate_assignment + [] + [] + static member WithValidateAssignment(_validate: bool) : obj = nativeOnly + + /// Create a ConfigDict with arbitrary_types_allowed + [] + [] + static member WithArbitraryTypesAllowed(_allowed: bool) : obj = nativeOnly + + /// Create a ConfigDict with from_attributes + [] + [] + static member WithFromAttributes(_fromAttrs: bool) : obj = nativeOnly + +// ============================================================================ +// Validation Error Handling +// ============================================================================ + +/// Pydantic ValidationError +[] +type ValidationError() = + /// Get error count + [] + member _.error_count() : int = nativeOnly + + /// Get errors as list + [] + member _.errors() : obj array = nativeOnly + + /// Get errors as JSON string + [] + member _.json() : string = nativeOnly + +// ============================================================================ +// Functional Validators (for use with Annotated types) +// ============================================================================ + +/// Field validator decorator - use this to generate the import for @field_validator +/// This is a placeholder to ensure the import is generated. +/// Use with: [] +[] +let pydantic_field_validator: obj = nativeOnly + +/// BeforeValidator - runs before Pydantic's own validation +/// Usage with Python's Annotated: Annotated[str, BeforeValidator(my_func)] +[] +[] +let BeforeValidator (_validator: 'T -> 'T) : obj = nativeOnly + +/// AfterValidator - runs after Pydantic's own validation +/// Usage with Python's Annotated: Annotated[str, AfterValidator(my_func)] +[] +[] +let AfterValidator (_validator: 'T -> 'T) : obj = nativeOnly + +/// PlainValidator - replaces Pydantic's validation entirely +[] +[] +let PlainValidator (_validator: obj -> 'T) : obj = nativeOnly + +/// WrapValidator - wraps Pydantic's validation with custom logic +[] +[] +let WrapValidator (_validator: obj -> (obj -> 'T) -> 'T) : obj = nativeOnly + +// ============================================================================ +// TypeAdapter - for validating non-model types +// ============================================================================ + +/// TypeAdapter for validating arbitrary types without a full model +/// Can be used with Annotated types and validators +[] +type TypeAdapter<'T>(_type: obj) = + /// Validate data and return typed result + [] + member _.validate_python(_data: obj) : 'T = nativeOnly + + /// Validate JSON string and return typed result + [] + member _.validate_json(_json: string) : 'T = nativeOnly + + /// Dump to Python object + [] + member _.dump_python(_value: 'T) : obj = nativeOnly + + /// Dump to JSON string + [] + member _.dump_json(_value: 'T) : string = nativeOnly + + /// Get JSON schema + [] + member _.json_schema() : obj = nativeOnly + +// ============================================================================ +// Field Information Types +// ============================================================================ + +/// Field information type +type FieldInfo = + abstract annotation: obj + abstract ``default``: obj option + abstract is_required: unit -> bool + +// ============================================================================ +// Pydantic V2 Types +// ============================================================================ + +// --- String Types --- + +/// Email string type with validation +[] +type EmailStr = string + +/// Name email string (e.g., "John Doe ") +[] +type NameEmail = string + +// --- URL Types --- + +/// HTTP URL type with validation +[] +type HttpUrl = string + +/// Any URL type with validation +[] +type AnyUrl = string + +/// Any HTTP URL type +[] +type AnyHttpUrl = string + +/// File URL type (file://) +[] +type FileUrl = string + +/// FTP URL type +[] +type FtpUrl = string + +/// WebSocket URL type +[] +type WebsocketUrl = string + +// --- Numeric Types --- + +/// Positive integer type (> 0) +[] +type PositiveInt = int + +/// Negative integer type (< 0) +[] +type NegativeInt = int + +/// Non-negative integer type (>= 0) +[] +type NonNegativeInt = int + +/// Non-positive integer type (<= 0) +[] +type NonPositiveInt = int + +/// Positive float type (> 0) +[] +type PositiveFloat = float + +/// Negative float type (< 0) +[] +type NegativeFloat = float + +/// Non-negative float type (>= 0) +[] +type NonNegativeFloat = float + +/// Non-positive float type (<= 0) +[] +type NonPositiveFloat = float + +/// Finite float type (not inf or -inf) +[] +type FiniteFloat = float + +// --- Strict Types (no coercion) --- + +/// Strict integer - only accepts int, not bool or float +[] +type StrictInt = int + +/// Strict float - only accepts float, not int +[] +type StrictFloat = float + +/// Strict string - only accepts str +[] +type StrictStr = string + +/// Strict bool - only accepts bool, not int +[] +type StrictBool = bool + +/// Strict bytes - only accepts bytes +[] +type StrictBytes = byte array + +// --- Secret Types --- + +/// Secret string - hidden in logs/repr, use get_secret_value() to access +[] +type SecretStr() = + [] + member _.get_secret_value() : string = nativeOnly + +/// Secret bytes - hidden in logs/repr, use get_secret_value() to access +[] +type SecretBytes() = + [] + member _.get_secret_value() : byte array = nativeOnly + +// --- UUID Types --- + +/// UUID version 1 +[] +type UUID1 = string + +/// UUID version 3 +[] +type UUID3 = string + +/// UUID version 4 (random) +[] +type UUID4 = string + +/// UUID version 5 +[] +type UUID5 = string + +// --- Path Types --- + +/// File path that must exist +[] +type FilePath = string + +/// Directory path that must exist +[] +type DirectoryPath = string + +/// Path that must not exist (for creating new files) +[] +type NewPath = string + +// --- Network Types --- + +/// IP address (v4 or v6) +[] +type IPvAnyAddress = string + +/// IP interface (v4 or v6) +[] +type IPvAnyInterface = string + +/// IP network (v4 or v6) +[] +type IPvAnyNetwork = string + +// --- Other Types --- + +/// Base64 encoded bytes +[] +type Base64Bytes = byte array + +/// Base64 encoded string +[] +type Base64Str = string + +/// JSON type - validates JSON string and parses it +[] +type JsonValue = obj + diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index bdd274b..88f4860 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -19,6 +19,8 @@ + + diff --git a/test/TestAsyncIO.fs b/test/TestAsyncIO.fs index 4e1173d..843a8a1 100644 --- a/test/TestAsyncIO.fs +++ b/test/TestAsyncIO.fs @@ -5,33 +5,29 @@ open Fable.Python.AsyncIO [] let ``test builder run zero works`` () = - let tsk = task { - () - } - let result = asyncio.run(tsk) + let tsk = task { () } + let result = asyncio.run (tsk) result |> equal () [] let ``test builder run int works`` () = - let tsk = task { - return 42 - } - let result = asyncio.run(tsk) + let tsk = task { return 42 } + let result = asyncio.run (tsk) result |> equal 42 [] let ``test sleep works`` () = - let tsk = task { - do! asyncio.create_task(asyncio.sleep(0.1)) - return 42 - } - let result = asyncio.run(tsk) + let tsk = + task { + do! asyncio.create_task (asyncio.sleep (0.1)) + return 42 + } + + let result = asyncio.run (tsk) result |> equal 42 [] let ``test sleep with value works`` () = - let tsk = task { - return! asyncio.create_task(asyncio.sleep(0.1, 42)) - } - let result = asyncio.run(tsk) + let tsk = task { return! asyncio.create_task (asyncio.sleep (0.1, 42)) } + let result = asyncio.run (tsk) result |> equal 42 diff --git a/test/TestBuiltins.fs b/test/TestBuiltins.fs index 8b489cc..19ea2df 100644 --- a/test/TestBuiltins.fs +++ b/test/TestBuiltins.fs @@ -14,7 +14,7 @@ let ``test __name__ works`` () = __name__ |> equal "test_builtins" [] let ``test write works`` () = let tempFile = os.path.join (os.path.expanduser "~", ".fable_test_temp.txt") - let result = builtins.``open``(tempFile, OpenTextMode.Write) + let result = builtins.``open`` (tempFile, OpenTextMode.Write) result.write "ABC" |> equal 3 result.Dispose() os.remove tempFile @@ -31,8 +31,8 @@ let ``test max with three arguments works`` () = [] let ``test max with iterable works`` () = - builtins.max [1; 2; 3; 4; 5] |> equal 5 - builtins.max [10; -5; 3] |> equal 10 + builtins.max [ 1; 2; 3; 4; 5 ] |> equal 5 + builtins.max [ 10; -5; 3 ] |> equal 10 [] let ``test min with two arguments works`` () = @@ -46,22 +46,22 @@ let ``test min with three arguments works`` () = [] let ``test min with iterable works`` () = - builtins.min [1; 2; 3; 4; 5] |> equal 1 - builtins.min [10; -5; 3] |> equal -5 + builtins.min [ 1; 2; 3; 4; 5 ] |> equal 1 + builtins.min [ 10; -5; 3 ] |> equal -5 [] let ``test sum works`` () = - builtins.sum [1; 2; 3; 4; 5] |> equal 15 - builtins.sum [10; -5; 3] |> equal 8 + builtins.sum [ 1; 2; 3; 4; 5 ] |> equal 15 + builtins.sum [ 10; -5; 3 ] |> equal 8 [] let ``test all works`` () = - builtins.all [true; true; true] |> equal true - builtins.all [true; false; true] |> equal false + builtins.all [ true; true; true ] |> equal true + builtins.all [ true; false; true ] |> equal false builtins.all [] |> equal true [] let ``test any works`` () = - builtins.any [false; false; true] |> equal true - builtins.any [false; false; false] |> equal false + builtins.any [ false; false; true ] |> equal true + builtins.any [ false; false; false ] |> equal false builtins.any [] |> equal false diff --git a/test/TestFastAPI.fs b/test/TestFastAPI.fs new file mode 100644 index 0000000..73818d9 --- /dev/null +++ b/test/TestFastAPI.fs @@ -0,0 +1,308 @@ +module Fable.Python.Tests.TestFastAPI + +open Fable.Python.Tests.Util.Testing + +#if FABLE_COMPILER +open Fable.Core +open Fable.Core.PyInterop +open Fable.Python.FastAPI +open Fable.Python.Pydantic + +// Helper to check if object exists (not null in Python) +let inline notNull (x: obj) : bool = not (isNull x) + +// ============================================================================ +// FastAPI Application Tests +// ============================================================================ + +[] +let ``test FastAPI app can be created`` () = + let app = FastAPI() + notNull app |> equal true + +[] +let ``test FastAPI app can be created with title`` () = + let app = FastAPI(title = "My API") + app.title |> equal "My API" + +[] +let ``test FastAPI app can be created with title and version`` () = + let app = FastAPI(title = "My API", version = "1.0.0") + app.title |> equal "My API" + app.version |> equal "1.0.0" + +[] +let ``test FastAPI app can be created with description`` () = + let app = FastAPI(title = "My API", description = "A test API") + app.description |> equal "A test API" + +// ============================================================================ +// APIRouter Tests +// ============================================================================ + +[] +let ``test APIRouter can be created`` () = + let router = APIRouter() + notNull router |> equal true + +[] +let ``test APIRouter can be created with prefix`` () = + let router = APIRouter(prefix = "/api") + notNull router |> equal true + +[] +let ``test APIRouter can be created with tags`` () = + let router = APIRouter(tags = ResizeArray ["users"; "admin"]) + notNull router |> equal true + +[] +let ``test FastAPI app can include router`` () = + let app = FastAPI() + let router = APIRouter(prefix = "/api") + app.include_router(router) + // If we get here without error, the test passes + true |> equal true + +// ============================================================================ +// HTTPException Tests +// ============================================================================ + +[] +let ``test HTTPException can be created with status code`` () = + let exc = HTTPException(404) + notNull exc |> equal true + +[] +let ``test HTTPException can be created with status code and detail`` () = + let exc = HTTPException(404, detail = "Not found") + notNull exc |> equal true + +// ============================================================================ +// Response Classes Tests +// ============================================================================ + +[] +let ``test JSONResponse can be created`` () = + let response = JSONResponse({| message = "Hello" |}) + notNull response |> equal true + +[] +let ``test JSONResponse can be created with status code`` () = + let response = JSONResponse({| message = "Created" |}, status_code = 201) + notNull response |> equal true + +[] +let ``test HTMLResponse can be created`` () = + let response = HTMLResponse("

Hello

") + notNull response |> equal true + +[] +let ``test PlainTextResponse can be created`` () = + let response = PlainTextResponse("Hello, World!") + notNull response |> equal true + +[] +let ``test RedirectResponse can be created`` () = + let response = RedirectResponse("/new-url") + notNull response |> equal true + +// ============================================================================ +// Status Codes Tests +// ============================================================================ + +[] +let ``test status codes are correct`` () = + status.HTTP_200_OK |> equal 200 + status.HTTP_201_CREATED |> equal 201 + status.HTTP_204_NO_CONTENT |> equal 204 + status.HTTP_400_BAD_REQUEST |> equal 400 + status.HTTP_401_UNAUTHORIZED |> equal 401 + status.HTTP_403_FORBIDDEN |> equal 403 + status.HTTP_404_NOT_FOUND |> equal 404 + status.HTTP_500_INTERNAL_SERVER_ERROR |> equal 500 + +// ============================================================================ +// BackgroundTasks Tests +// ============================================================================ + +[] +let ``test BackgroundTasks can be created`` () = + let tasks = BackgroundTasks() + notNull tasks |> equal true + +// ============================================================================ +// Security Tests +// ============================================================================ + +[] +let ``test OAuth2PasswordBearer can be created`` () = + let oauth2_scheme = OAuth2PasswordBearer.Create("token") + notNull oauth2_scheme |> equal true + +[] +let ``test HTTPBasic can be created`` () = + let basic = HTTPBasic() + notNull basic |> equal true + +[] +let ``test HTTPBearer can be created`` () = + let bearer = HTTPBearer() + notNull bearer |> equal true + +[] +let ``test APIKeyHeader can be created`` () = + let api_key = APIKeyHeader(name = "X-API-Key") + notNull api_key |> equal true + +[] +let ``test APIKeyQuery can be created`` () = + let api_key = APIKeyQuery(name = "api_key") + notNull api_key |> equal true + +[] +let ``test APIKeyCookie can be created`` () = + let api_key = APIKeyCookie(name = "api_key") + notNull api_key |> equal true + +// ============================================================================ +// Pydantic Model with FastAPI (integration) +// ============================================================================ + +[] +type Item(Name: string, Price: float, InStock: bool) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Price: float = Price with get, set + member val InStock: bool = InStock with get, set + +[] +let ``test Pydantic model works with FastAPI patterns`` () = + // This test verifies that Pydantic models can be used in FastAPI endpoints + let item = Item(Name = "Widget", Price = 9.99, InStock = true) + item.Name |> equal "Widget" + item.Price |> equal 9.99 + item.InStock |> equal true + +[] +let ``test Pydantic model serialization for FastAPI`` () = + let item = Item(Name = "Gadget", Price = 19.99, InStock = false) + let json = item.model_dump_json() + json.Contains("Gadget") |> equal true + json.Contains("19.99") |> equal true + +// ============================================================================ +// Class-based API with decorators (the main pattern) +// ============================================================================ + +// Note: These tests verify the decorator pattern compiles correctly. +// Full endpoint testing requires running the server. + +let app = FastAPI(title = "Test API", version = "1.0.0") + +[] +type API() = + [] + static member root() : obj = + {| message = "Hello World" |} + + [] + static member get_item(item_id: int) : obj = + {| item_id = item_id; name = "Test Item" |} + + [] + static member create_item(item: Item) : obj = + {| status = "created"; item = item |} + + [] + static member update_item(item_id: int, item: Item) : obj = + {| item_id = item_id; item = item |} + + [] + static member delete_item(item_id: int) : obj = + {| deleted = item_id |} + +[] +let ``test class-based API methods can be called`` () = + let result = API.root() + notNull result |> equal true + +[] +let ``test class-based API with path parameter`` () = + let result = API.get_item(42) + notNull result |> equal true + +[] +let ``test class-based API POST method`` () = + let item = Item(Name = "Test", Price = 10.0, InStock = true) + let result = API.create_item(item) + notNull result |> equal true + +[] +let ``test class-based API PUT method`` () = + let item = Item(Name = "Updated", Price = 20.0, InStock = false) + let result = API.update_item(1, item) + notNull result |> equal true + +[] +let ``test class-based API DELETE method`` () = + let result = API.delete_item(1) + notNull result |> equal true + +// ============================================================================ +// TestClient Tests +// ============================================================================ + +[] +let ``test TestClient can be created`` () = + let testApp = FastAPI() + let client = TestClient(testApp) + notNull client |> equal true + +// ============================================================================ +// Router-based API pattern +// ============================================================================ + +let router = APIRouter(prefix = "/users", tags = ResizeArray ["users"]) + +[] +type UsersAPI() = + [] + static member list_users() : obj = + {| users = [| "Alice"; "Bob" |] |} + + [] + static member get_user(user_id: int) : obj = + {| user_id = user_id |} + + [] + static member create_user(name: string) : obj = + {| name = name; id = 1 |} + + [] + static member update_user(user_id: int, name: string) : obj = + {| user_id = user_id; name = name |} + + [] + static member delete_user(user_id: int) : obj = + {| deleted = user_id |} + +[] +let ``test router-based API methods work`` () = + let users = UsersAPI.list_users() + notNull users |> equal true + +[] +let ``test router can be included in app`` () = + let mainApp = FastAPI() + let usersRouter = APIRouter(prefix = "/api/v1") + mainApp.include_router(usersRouter) + true |> equal true + +[] +let ``test router can be included with prefix and tags`` () = + let mainApp = FastAPI() + let usersRouter = APIRouter(prefix = "/users", tags = ResizeArray ["users"]) + mainApp.include_router_with_prefix_and_tags(usersRouter, "/api/v1", ResizeArray ["api"]) + true |> equal true + +#endif diff --git a/test/TestJson.fs b/test/TestJson.fs index 7e4fb3b..eaffe3f 100644 --- a/test/TestJson.fs +++ b/test/TestJson.fs @@ -29,13 +29,16 @@ let ``test json.dumps with nativeint works`` () = [] let ``test json.dumps with ResizeArray of nativeint works`` () = - let values = ResizeArray([1n; 2n; 3n]) + let values = ResizeArray([ 1n; 2n; 3n ]) let result = json.dumps values result |> equal "[1, 2, 3]" [] let ``test json.dumps with nested object works`` () = - let obj = {| Name = "test"; Values = ResizeArray([1n; 2n; 3n]) |} + let obj = + {| Name = "test" + Values = ResizeArray([ 1n; 2n; 3n ]) |} + let result = dumps obj result |> equal """{"Name": "test", "Values": [1, 2, 3]}""" diff --git a/test/TestOs.fs b/test/TestOs.fs index 8197f6d..f01ab88 100644 --- a/test/TestOs.fs +++ b/test/TestOs.fs @@ -9,8 +9,7 @@ let ``test os.path.exists works`` () = os.path.exists "nonexistent_path_xyz" |> equal false [] -let ``test os.path.isdir works`` () = - os.path.isdir "." |> equal true +let ``test os.path.isdir works`` () = os.path.isdir "." |> equal true [] let ``test os.path.join works`` () = diff --git a/test/TestPydantic.fs b/test/TestPydantic.fs new file mode 100644 index 0000000..a3f9ed0 --- /dev/null +++ b/test/TestPydantic.fs @@ -0,0 +1,354 @@ +module Fable.Python.Tests.TestPydantic + +open Fable.Python.Tests.Util.Testing + +#if FABLE_COMPILER +open Fable.Core +open Fable.Core.PyInterop +open Fable.Python.Pydantic + +// Note: Constructor parameter names must be PascalCase to match Pydantic field names. +// Fable converts camelCase to snake_case, but Pydantic expects exact field name match. + +// ============================================================================ +// Basic Pydantic Model - Simple types +// ============================================================================ + +[] +type SimpleUser(Name: string, Age: int) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Age: int = Age with get, set + +[] +let ``test Simple Pydantic model can be created`` () = + let user = SimpleUser(Name = "Alice", Age = 30) + user.Name |> equal "Alice" + user.Age |> equal 30 + +[] +let ``test Simple Pydantic model can be modified`` () = + let user = SimpleUser(Name = "Alice", Age = 30) + user.Name <- "Bob" + user.Age <- 25 + user.Name |> equal "Bob" + user.Age |> equal 25 + +// ============================================================================ +// Pydantic Model with optional fields +// ============================================================================ + +[] +type UserWithOptional(Name: string, Email: string option) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Email: string option = Email with get, set + +[] +let ``test Pydantic model with Some optional field`` () = + let user = UserWithOptional(Name = "Alice", Email = Some "alice@example.com") + user.Name |> equal "Alice" + user.Email |> equal (Some "alice@example.com") + +[] +let ``test Pydantic model with None optional field`` () = + let user = UserWithOptional(Name = "Bob", Email = None) + user.Name |> equal "Bob" + user.Email |> equal None + +// ============================================================================ +// Pydantic Model with Field defaults +// ============================================================================ + +[] +type UserWithDefaults(Name: string, Active: bool) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Active: bool = Field.Default(true) with get, set + +[] +let ``test Pydantic model with Field.Default`` () = + let user = UserWithDefaults(Name = "Charlie", Active = false) + user.Name |> equal "Charlie" + +// ============================================================================ +// Pydantic Model with frozen field +// ============================================================================ + +[] +type UserWithFrozenField(Id: string, Name: string) = + inherit BaseModel() + member val Id: string = Field.Frozen(true) with get, set + member val Name: string = Name with get, set + +[] +let ``test Pydantic model with frozen field can be created`` () = + let user = UserWithFrozenField(Id = "user-123", Name = "Dave") + user.Name |> equal "Dave" + +// ============================================================================ +// Pydantic Model with numeric types (now with Pydantic schema support) +// ============================================================================ + +[] +type NumericModel(IntVal: int, FloatVal: float, Int64Val: int64) = + inherit BaseModel() + member val IntVal: int = IntVal with get, set + member val FloatVal: float = FloatVal with get, set + member val Int64Val: int64 = Int64Val with get, set + +[] +let ``test Pydantic model with int type`` () = + let model = NumericModel(IntVal = 42, FloatVal = 3.14, Int64Val = 9999999999L) + model.IntVal |> equal 42 + +[] +let ``test Pydantic model with float type`` () = + let model = NumericModel(IntVal = 42, FloatVal = 3.14, Int64Val = 9999999999L) + model.FloatVal |> equal 3.14 + +[] +let ``test Pydantic model with int64 type`` () = + let model = NumericModel(IntVal = 42, FloatVal = 3.14, Int64Val = 9999999999L) + model.Int64Val |> equal 9999999999L + +// ============================================================================ +// Pydantic Model with more numeric types +// ============================================================================ + +[] +type MoreNumericModel(ByteVal: byte, Int16Val: int16, DecimalVal: decimal) = + inherit BaseModel() + member val ByteVal: byte = ByteVal with get, set + member val Int16Val: int16 = Int16Val with get, set + member val DecimalVal: decimal = DecimalVal with get, set + +[] +let ``test Pydantic model with byte type`` () = + let model = MoreNumericModel(ByteVal = 255uy, Int16Val = 1000s, DecimalVal = 99.99M) + model.ByteVal |> equal 255uy + +[] +let ``test Pydantic model with int16 type`` () = + let model = MoreNumericModel(ByteVal = 255uy, Int16Val = 1000s, DecimalVal = 99.99M) + model.Int16Val |> equal 1000s + +[] +let ``test Pydantic model with decimal type`` () = + let model = MoreNumericModel(ByteVal = 255uy, Int16Val = 1000s, DecimalVal = 99.99M) + model.DecimalVal |> equal 99.99M + +// ============================================================================ +// Pydantic Model with list fields +// ============================================================================ + +[] +type ModelWithList(Name: string, Tags: string list) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Tags: string list = Tags with get, set + +[] +let ``test Pydantic model with list field`` () = + let model = ModelWithList(Name = "Test", Tags = [ "tag1"; "tag2"; "tag3" ]) + model.Name |> equal "Test" + model.Tags |> List.length |> equal 3 + model.Tags |> List.head |> equal "tag1" + +[] +let ``test Pydantic model with empty list field`` () = + let model = ModelWithList(Name = "Test", Tags = []) + model.Tags |> List.length |> equal 0 + +// ============================================================================ +// Pydantic Model with nested models +// ============================================================================ + +[] +type Address(Street: string, City: string) = + inherit BaseModel() + member val Street: string = Street with get, set + member val City: string = City with get, set + +[] +type Person(Name: string, Address: Address) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Address: Address = Address with get, set + +[] +let ``test Pydantic model with nested model`` () = + let address = Address(Street = "123 Main St", City = "Seattle") + let person = Person(Name = "Eve", Address = address) + person.Name |> equal "Eve" + person.Address.Street |> equal "123 Main St" + person.Address.City |> equal "Seattle" + +// ============================================================================ +// Pydantic Model serialization (model_dump_json) +// ============================================================================ + +[] +let ``test Pydantic model_dump_json returns valid JSON`` () = + let user = SimpleUser(Name = "Frank", Age = 40) + let json = user.model_dump_json () + // Check that it contains expected fields + json.Contains("Frank") |> equal true + json.Contains("40") |> equal true + +[] +let ``test Pydantic model_dump returns object`` () = + let user = SimpleUser(Name = "Grace", Age = 35) + let dict = user.model_dump () + // The dump should be an object (Python dict) + dict |> isNull |> equal false + +// ============================================================================ +// Pydantic Model validation (model_validate_json) +// ============================================================================ + +[] +let ``test Pydantic model_validate_json parses JSON`` () = + let template = SimpleUser(Name = "", Age = 0) + + let parsed = + template.model_validate_json ("""{"Name": "Henry", "Age": 45}""") :?> SimpleUser + + parsed.Name |> equal "Henry" + parsed.Age |> equal 45 + +[] +let ``test Pydantic roundtrip dump and validate`` () = + let original = SimpleUser(Name = "Ivy", Age = 28) + let json = original.model_dump_json () + let restored = original.model_validate_json (json) :?> SimpleUser + restored.Name |> equal "Ivy" + restored.Age |> equal 28 + +// ============================================================================ +// Pydantic Model with bool fields +// ============================================================================ + +[] +type SettingsModel(Enabled: bool, Visible: bool, Archived: bool) = + inherit BaseModel() + member val Enabled: bool = Enabled with get, set + member val Visible: bool = Visible with get, set + member val Archived: bool = Archived with get, set + +[] +let ``test Pydantic model with bool fields`` () = + let settings = SettingsModel(Enabled = true, Visible = false, Archived = true) + settings.Enabled |> equal true + settings.Visible |> equal false + settings.Archived |> equal true + +// ============================================================================ +// Pydantic Model inheriting from another Pydantic model +// ============================================================================ + +[] +type BaseEntity(Id: int) = + inherit BaseModel() + member val Id: int = Id with get, set + +[] +type ExtendedEntity(Id: int, Name: string) = + inherit BaseEntity(Id) + member val Name: string = Name with get, set + +[] +let ``test Pydantic model inheritance`` () = + let entity = ExtendedEntity(Id = 1, Name = "Entity1") + entity.Id |> equal 1 + entity.Name |> equal "Entity1" + +// ============================================================================ +// Pydantic Model - Complex scenario with multiple field types +// ============================================================================ + +[] +type ComplexModel(Id: int, Name: string, Score: float, Email: string option, Tags: string list, Active: bool) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + member val Score: float = Score with get, set + member val Email: string option = Email with get, set + member val Tags: string list = Tags with get, set + member val Active: bool = Active with get, set + +[] +let ``test Complex Pydantic model with multiple field types`` () = + let model = + ComplexModel( + Id = 42, + Name = "Complex", + Score = 95.5, + Email = Some "complex@example.com", + Tags = [ "a"; "b"; "c" ], + Active = true + ) + + model.Id |> equal 42 + model.Name |> equal "Complex" + model.Score |> equal 95.5 + model.Email |> equal (Some "complex@example.com") + model.Tags |> List.length |> equal 3 + model.Active |> equal true + +// ============================================================================ +// Test model_fields property +// ============================================================================ + +[] +let ``test Pydantic model_fields property exists`` () = + let user = SimpleUser(Name = "Test", Age = 25) + let fields = user.model_fields + fields |> isNull |> equal false + +// ============================================================================ +// Test model_json_schema method +// ============================================================================ + +[] +let ``test Pydantic model_json_schema returns schema`` () = + let user = SimpleUser(Name = "Test", Age = 25) + let schema = user.model_json_schema () + schema |> isNull |> equal false + +// ============================================================================ +// Test model_config property +// ============================================================================ + +[] +let ``test Pydantic model_config property exists`` () = + let user = SimpleUser(Name = "Test", Age = 25) + let config = user.model_config + // Config should exist (may be empty dict by default) + config |> isNull |> equal false + +// ============================================================================ +// Pydantic Model with field validator using Py.Decorate +// ============================================================================ + +[] +type UserWithValidator(Name: string, Age: int) = + inherit BaseModel() + member val Name: string = Name with get, set + member val Age: int = Age with get, set + + [] + [] + static member validate_name(_cls: obj, v: string) : string = v.ToUpper() + +// Reference to ensure field_validator gets imported +let private _fieldValidatorImport = pydantic_field_validator + +[] +let ``test Pydantic model with field validator`` () = + let user = UserWithValidator(Name = "alice", Age = 30) + // The validator should have upper-cased the name + user.Name |> equal "ALICE" + user.Age |> equal 30 + +#endif diff --git a/test/TestString.fs b/test/TestString.fs index 32a8299..c92b7c8 100644 --- a/test/TestString.fs +++ b/test/TestString.fs @@ -5,10 +5,10 @@ open Fable.Python.String [] let ``test string format works`` () = - let result = "The sum of 1 + 2 is {0}".format(1+2) + let result = "The sum of 1 + 2 is {0}".format (1 + 2) result |> equal "The sum of 1 + 2 is 3" [] let ``test string format 2 works`` () = - let result = "The sum of {0} + 2 is {1}".format(1, 1+2) + let result = "The sum of {0} + 2 is {1}".format (1, 1 + 2) result |> equal "The sum of 1 + 2 is 3" diff --git a/test/Util.fs b/test/Util.fs index be4537a..d765e47 100644 --- a/test/Util.fs +++ b/test/Util.fs @@ -15,8 +15,8 @@ module Testing = let equal expected actual : unit = Assert.AreEqual(actual, expected) let notEqual expected actual : unit = Assert.NotEqual(actual, expected) - type Fact () = - inherit System.Attribute () + type Fact() = + inherit System.Attribute() #else open Xunit type FactAttribute = Xunit.FactAttribute @@ -28,6 +28,4 @@ module Testing = match n with | 0 -> 0. | 1 -> Seq.head zs - | _ -> - (Seq.head zs) - + sumFirstSeq (Seq.skip 1 zs) (n - 1) + | _ -> (Seq.head zs) + sumFirstSeq (Seq.skip 1 zs) (n - 1) diff --git a/uv.lock b/uv.lock index 3a86ae0..dfa132e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,76 @@ version = 1 revision = 3 requires-python = ">=3.12, <4" +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, +] + +[[package]] +name = "asgiref" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -11,73 +81,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "django" +version = "4.2.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/ff/6aa5a94b85837af893ca82227301ac6ddf4798afda86151fb2066d26ca0a/django-4.2.27.tar.gz", hash = "sha256:b865fbe0f4a3d1ee36594c5efa42b20db3c8bbb10dff0736face1c6e4bda5b92", size = 10432781, upload-time = "2025-12-02T14:01:49.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/f5/1a2319cc090870bfe8c62ef5ad881a6b73b5f4ce7330c5cf2cb4f9536b12/django-4.2.27-py3-none-any.whl", hash = "sha256:f393a394053713e7d213984555c5b7d3caeee78b2ccb729888a0774dff6c11a8", size = 7995090, upload-time = "2025-12-02T14:01:44.234Z" }, +] + [[package]] name = "fable-library" -version = "5.0.0a17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/39/46eddc670f19e44d8d27e5fba2f127293ab253d88781cd8bf9f30dbf9c27/fable_library-5.0.0a17.tar.gz", hash = "sha256:76d1d0ade9de86cb4d7e33fe097e6d046d2f3bcacbce64478894b6c7c32366e2", size = 181946, upload-time = "2025-11-27T14:39:51.758Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/e2/bc0aedc309da577791e24a5b0401417ee4a31fbb540918b67857bdecd063/fable_library-5.0.0a17-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:33c5cd3f3d3b22387d3e8a6f8b2671b5116dd790d7e3722ede98c7643e68c54e", size = 1691410, upload-time = "2025-11-27T14:39:02.769Z" }, - { url = "https://files.pythonhosted.org/packages/57/6b/60cb12b84e318b30749b485f64c0ab281b25610eb513dad527a4b2efd5ef/fable_library-5.0.0a17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac51f1d89aa385eda124edb5c223820590e41a4c614bc2ff8bca7f8a975f2165", size = 1599332, upload-time = "2025-11-27T14:38:57.346Z" }, - { url = "https://files.pythonhosted.org/packages/7f/53/4bcb316ef823c746ca1381f4f18170a3cb3fd0c88eabec6aec9339cc2173/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5cbbca0c6e0123e0ea31846616427121fe9879497876571f44390dda475e3ea", size = 1678480, upload-time = "2025-11-27T14:37:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/8cbdc5a48d5b149d12b8f983b156f1de121f063767215eaec5d0a7ed4062/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af423d94cf131afa2639943f1fb54c9648abe42928a8e613ad2a38e51e056a17", size = 1646911, upload-time = "2025-11-27T14:38:08.685Z" }, - { url = "https://files.pythonhosted.org/packages/5d/2b/07b2ac66f22552d0a9fbe30dbe488ad9c2eebb446625d9426a09aa50f817/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c46dc9a073002dec5b90c7e90fc973f3a24e4307e6a8978b586fa985621351", size = 1856429, upload-time = "2025-11-27T14:38:21.169Z" }, - { url = "https://files.pythonhosted.org/packages/66/62/98cda972d52303d2714df935b3fd037733ee291c4acec386a94a6f95fd36/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1a78ee26cd2c37f09c9662e8ad2e2c871ec8bdb023383ff85b5dfaa3fbc7e80", size = 1788103, upload-time = "2025-11-27T14:38:32.023Z" }, - { url = "https://files.pythonhosted.org/packages/78/85/7d9a1c1cfb369b85cf2cde6ee2b4d2f1670126d7cbe247d1473d0c03b6e9/fable_library-5.0.0a17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1fe000c1a0d60a7c4a4652d20d47d9f3dd1ecb3ea531e209da27899b66659e", size = 1694562, upload-time = "2025-11-27T14:38:50.147Z" }, - { url = "https://files.pythonhosted.org/packages/2a/74/cf686a1f7b2476f063a58ef42adbd042f348e5c1b21aad349bdcaf1debc5/fable_library-5.0.0a17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffa31557d42526c18dfbc15b05d9f9c8badd79c8a74ffb14dc0dc8379ef0fbac", size = 1796335, upload-time = "2025-11-27T14:38:42.273Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ee/fc5657b46f5ef7955a263bde92a86ef24dc8c15ced124ce558476cbd2217/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74becfd5a81da386afe2b9481ac74862835f1f73c97db07a713ac630862bd8b9", size = 1860582, upload-time = "2025-11-27T14:39:09.05Z" }, - { url = "https://files.pythonhosted.org/packages/e8/74/693c1a42ec3d56da807f2b3f746a8a690af5df9b5417e72f28b9c6cb1a75/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7ac2ac71a7d0387f997d4d8a1d50d1ba5158eef08e112b29133487bdaa3c1b78", size = 1913156, upload-time = "2025-11-27T14:39:21.763Z" }, - { url = "https://files.pythonhosted.org/packages/a3/91/1b85e295f44b0413126cc87d34317e08b7d3065c30227f142ca57698d91b/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a261de6a9aeb1e8324c9afb74924e8227e8119846b5e60fbb420db1540816a03", size = 1910444, upload-time = "2025-11-27T14:39:33.541Z" }, - { url = "https://files.pythonhosted.org/packages/a0/fa/c05cda9cbf3fb88a7484127aa047053907d8a2136b84daad4419f4f07421/fable_library-5.0.0a17-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:138c7f3dd36d939c6d7866e609df1bac866e644608303932d6d4d335efd3b681", size = 1913755, upload-time = "2025-11-27T14:39:44.162Z" }, - { url = "https://files.pythonhosted.org/packages/b0/7d/1598177d869691edb7b336016e4479e8ca7e75105376d48a7ba1770d6c66/fable_library-5.0.0a17-cp312-cp312-win_amd64.whl", hash = "sha256:ad78d9f9aa4357f19405830ccaa7c9ff10efccd4cc1dde4e7fc2fdaf9a2eb004", size = 1411069, upload-time = "2025-11-27T14:39:55.418Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ad/2cb0d8d6c90223c9f76715da3b4dc57a4dadea67b9fe3ac235b583f1e493/fable_library-5.0.0a17-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b0b6798853abe29c82ee5b7e9b6aa1f20fa8f0e4aaae7c3f82a27a6c8ce3e78b", size = 1691370, upload-time = "2025-11-27T14:39:04.036Z" }, - { url = "https://files.pythonhosted.org/packages/ff/dc/4129a3b87cfad1db199a9ef438f6c5d640ad18a9898d97299d4dab5dcb05/fable_library-5.0.0a17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:459d0161a2c79e4f205e8118dc9987c55e6441475fb95793374791814b442777", size = 1599346, upload-time = "2025-11-27T14:38:58.675Z" }, - { url = "https://files.pythonhosted.org/packages/72/e2/fe87a1cd65183c9ea05066dbb0233f2b6fa8075591f09fa8db8186c8069b/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61d882817d0b52e088389790b88051f52a966f141d4df1a298d9ac8295f9ea7f", size = 1677602, upload-time = "2025-11-27T14:37:58.93Z" }, - { url = "https://files.pythonhosted.org/packages/92/89/c8357d26dce3e2d2961a89eee005b5783fd695c648eb8fb23645d884102e/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03c95b0f1b8b477f544de0602704d51dc908428d8566509b9bf76d67f9a16492", size = 1646019, upload-time = "2025-11-27T14:38:09.932Z" }, - { url = "https://files.pythonhosted.org/packages/24/c9/d1bd05d8570549e81a685f32ab4c872dfc3e81ac358d842607b5cfe33a30/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af3d9ebc9f967f07abc588c697f41b086044792327e2a136b69c1dc6b9185a5", size = 1853942, upload-time = "2025-11-27T14:38:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fb/03bf0abd5fd1b97cb2f2ee3d3f9c2e97151ee42daa8d51edb19fcd7f953c/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00086ff93ec4a043b9c8d337296561511af73979e89d41711494a7db96093d29", size = 1788275, upload-time = "2025-11-27T14:38:33.241Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5b/4cf6a163e5a5647d5da60a51921ce1129396d3206d85ffdef6755a275cc2/fable_library-5.0.0a17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eabf12e4d4148ec9796822dcbb2813fe4389bf5432e29a9e5e2f607bb20db16", size = 1693592, upload-time = "2025-11-27T14:38:51.652Z" }, - { url = "https://files.pythonhosted.org/packages/98/2d/16c6eba1ac5230cc454e22a3cef514ac19e6a9baeb40836e0164a52d8673/fable_library-5.0.0a17-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:626c0e953db7f80640cc421b6f82c98a85595130ae21c5b16a1cb2551e8ca1df", size = 1796282, upload-time = "2025-11-27T14:38:43.579Z" }, - { url = "https://files.pythonhosted.org/packages/e0/71/773b0970132d5d9d318071d152d5add4bd76f7c4c019cd2532c1d26e24a5/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d4928051dd56a9266640ec31700cbb83d9ff392cf0367d0d72f70f9cef397333", size = 1859700, upload-time = "2025-11-27T14:39:10.589Z" }, - { url = "https://files.pythonhosted.org/packages/93/a6/b7f6bf1f4980dc005629c86eec43ee8f278b028c52f468a033efac460432/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:62e354cc2086495b728ee9fc3beff55b976b33f91afcbac790acc2c8bf8e97bd", size = 1912588, upload-time = "2025-11-27T14:39:22.988Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d8/b0f93b3c270de5a884ed5db856b84cff4d86826c1e123dc2954c80ef5374/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f5d8c33052083accc51beb3af14cf3f1d9f23a2a8dab700694af4fc548dc804c", size = 1910298, upload-time = "2025-11-27T14:39:34.708Z" }, - { url = "https://files.pythonhosted.org/packages/e7/97/77fd12e3d8556def4bb800e0cbf89f0310f53f232b63b1abeec32cfb8275/fable_library-5.0.0a17-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:143301c014e8f3730ccf34c3352c1b43a7a661ea9dbdb2a469132124e8ac56ed", size = 1913052, upload-time = "2025-11-27T14:39:45.444Z" }, - { url = "https://files.pythonhosted.org/packages/34/28/ce21303cb0406bccc8ca8dde318eebb88cb22755caba7529117786bf0c77/fable_library-5.0.0a17-cp313-cp313-win_amd64.whl", hash = "sha256:b471070e3eab0cd10626bba99c164e5f9a468b720d8b7f53a09b1e85c19365c0", size = 1411150, upload-time = "2025-11-27T14:39:56.884Z" }, - { url = "https://files.pythonhosted.org/packages/80/a9/6d1adccf6534017e5ccbb0d89889a631384794bd03936a938f839f896142/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a0902ea0b507cc6d4260ee649ee0cba8905054f5d2bdb1d0c1d7b7f4d20497", size = 1703312, upload-time = "2025-11-27T14:38:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/6a/74/dbc90755901d56d4dbf1aff1094a4fa47b124acac9e59de79751be260977/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f725a5f5e2e85a3cf3086bf3cd4630637c53a34aeea69e883e46716993119a9", size = 1682667, upload-time = "2025-11-27T14:38:12.075Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0d/9f3e681fd530ed965483d588c39eabca6de7ad8a64103849a6fae24d6a1c/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2eff10a9e42d24fe0cf989469c09977dede0243326257f9f4da752ff568ee5e", size = 1893420, upload-time = "2025-11-27T14:38:23.616Z" }, - { url = "https://files.pythonhosted.org/packages/27/dc/158b0ebfe2025ba76862f5ecb32f33eda0004baab04402193e1171fcce15/fable_library-5.0.0a17-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:feb07936a48b994f752c96d160c8d11e8dcd7f011e00df4b62df9f0ceb5cab7a", size = 1826167, upload-time = "2025-11-27T14:38:34.465Z" }, - { url = "https://files.pythonhosted.org/packages/78/6b/ac592731db1bbc8c6ff4ce88336c924e9a1c67bfb07809d638f8cba6cc2f/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:efa973c02f5bcfb5a3e982797738769f6a29a3b3dbbdc9486aae2f7507892af5", size = 1885647, upload-time = "2025-11-27T14:39:11.991Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/f564ed669540077b932acf7f9cd63b5a39ab840983ede9084421e7d445a5/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:1674f84763177d2d8f23ff9130fae28b2268a99394a53c49dcafa545d47080bf", size = 1949577, upload-time = "2025-11-27T14:39:24.646Z" }, - { url = "https://files.pythonhosted.org/packages/65/ae/df14e0f55cfd10d91eeffbd537e43737745001b5cf103ba15245a14fa637/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b7cea435911c9eb52e3fd0b1b959eff4715887baeeae9efa1c84c207c0e3c35c", size = 1948006, upload-time = "2025-11-27T14:39:35.93Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ab/51580c1ee9d2851df182f0cf7741608137777b16311bdc3ced615cdccf9a/fable_library-5.0.0a17-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:05e54e2f6d52f8afb618baf4d2e9de8fae9a35d976b37b60fc3023bb10a681c1", size = 1949741, upload-time = "2025-11-27T14:39:46.665Z" }, - { url = "https://files.pythonhosted.org/packages/97/e8/a59d2c175cbb120357328b1a959d829f766288afb088caff17875843484d/fable_library-5.0.0a17-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e05e2cca491107f59dae1b7d95152f7f45bc244f3e2fecde6b4e7afd5b772adf", size = 1678581, upload-time = "2025-11-27T14:39:05.248Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f6/c1ec2d7bda962bca80ff3248aa9502f8e22c8e853516603c1bba8981ae35/fable_library-5.0.0a17-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c207d03941b33871c1b1be9e9a3ff80ab130e9db22259413a3f6a017f99a5cc1", size = 1590836, upload-time = "2025-11-27T14:38:59.889Z" }, - { url = "https://files.pythonhosted.org/packages/c4/49/8cb15ccc800fdd5ee22a35bfe7a74bfe6c5eaf2a0135d9e6ed7f1c75c769/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c6a2b3da8965f41727747d92d4bc88badea1863cb85308966aff2c393ab89ef", size = 1671523, upload-time = "2025-11-27T14:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/b2/bb/7fa06bcacd026f74dcf2ded700ef2f1ca7f69ce5f6618553e66b776ddeb9/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a3237cc83ec36a69b7a6b21e2de128f570eef4012776b58f35bbf63f2f3de41", size = 1632185, upload-time = "2025-11-27T14:38:13.236Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/dacd0fbd6bfcc9a442806176a180469c5264516863fce099c6c98a81818d/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba00aa9959067245cb59721a4c315294e1e0882f1a2664848e5bf653c055239e", size = 1854112, upload-time = "2025-11-27T14:38:25.178Z" }, - { url = "https://files.pythonhosted.org/packages/40/99/85b732612f70fd6338110526f39f5b9441e3e943c48e4df61e98811093ee/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fb20c05ef9d278bc36081c123accb9c9a793ec797f51f134a285a8a8da53ec6", size = 1780844, upload-time = "2025-11-27T14:38:35.736Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0a/7883946553157fdff7d5ed0ce773e2537f6e6e51b25b493a705ff634533a/fable_library-5.0.0a17-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db327cf997d54bc3a61aaf0a386d5f84d11dff525b7f4dcaabeeb58cc99d50e0", size = 1679723, upload-time = "2025-11-27T14:38:52.834Z" }, - { url = "https://files.pythonhosted.org/packages/7d/13/707a9f4b65530c20a966864812f30576d9249d290fe89b89391c7159439e/fable_library-5.0.0a17-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:882221037b8ed4f539b4499556269c5caaba608bc966f2c60f16235122ee35f1", size = 1787845, upload-time = "2025-11-27T14:38:44.761Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d5/d58f2fe7acfb01735eda44542bc7781c7a972f4f87043281f4bd44cc2f85/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b0e49c159b696436df098326b2b95af27d1d66ef4c8b811bada708b2bf31c7db", size = 1854562, upload-time = "2025-11-27T14:39:13.271Z" }, - { url = "https://files.pythonhosted.org/packages/a5/99/4862333a479686149f0dc7bd3785c2efa97a60184dab8f7cd90f4c14fdf7/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5334156ab14a91e9dfc721e8523feb88a3d3a22be2cc5a13640019d7ea29b2a7", size = 1900038, upload-time = "2025-11-27T14:39:26.236Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e5/c8867c226d82cb88abd471a7f0456a9979aa58def69489946ee1ed5ad9fe/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fa1cc2d9d02ec87aeba5f06d4335cbbaea6be777b92dc41c544944fe18c0dcb1", size = 1903699, upload-time = "2025-11-27T14:39:37.151Z" }, - { url = "https://files.pythonhosted.org/packages/df/8a/579cf0e340eee6ecb0174e1de24bbb469e495bc0cc00bccff0d9724bafba/fable_library-5.0.0a17-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b4b233fafdc1bdff8ab6709717106be9091f667c377dbb68f02e90939d63d9f", size = 1901808, upload-time = "2025-11-27T14:39:47.974Z" }, - { url = "https://files.pythonhosted.org/packages/38/4e/d45bd132faba3d2a93e71c6a7ba3e60d6e6e7b021abbd5d7b68e8170c9a2/fable_library-5.0.0a17-cp314-cp314-win32.whl", hash = "sha256:429f1706db3f19e2c2c8501e58f0285ff2ef659ff735b059964baa2b40121a86", size = 1276767, upload-time = "2025-11-27T14:40:00.072Z" }, - { url = "https://files.pythonhosted.org/packages/3a/12/07d4a9b9bd7de9edd50534bc9406e3ed3f32ce52d516ac8913b5ffbf8792/fable_library-5.0.0a17-cp314-cp314-win_amd64.whl", hash = "sha256:76c38f95a07f20746a75e174d12cab72d608506b65929d45e70b06e7d81c4a83", size = 1399548, upload-time = "2025-11-27T14:39:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/26/55/6987d4bc5f2c300866994dfb5a5b9d517e26e20b57032f50d9f22f00fe73/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a561562e5490571bb5185a76e802fcce9ac0523d84e9cfa7c7e14e00188a5289", size = 1702465, upload-time = "2025-11-27T14:38:03.879Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9d/35260a8db15edcfa61cc3e65209c451fe05a396acd59d229667713fd91a4/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0839ee5e3a769c7f70673da3f3298aa17497b2db2cfd84c1a2a1e8998362591f", size = 1683471, upload-time = "2025-11-27T14:38:14.509Z" }, - { url = "https://files.pythonhosted.org/packages/93/7d/d5c7fb9064dab4afc9dff3809944978be10e0839d606887a406b3aa794b1/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3618911c2e831478b36ba58b10c314c76f7237e299e568355a5504a5f6d43ee", size = 1893602, upload-time = "2025-11-27T14:38:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/b0/65/c9230732d2c90ec77a6d2199aed1f0dd803df558aa585e7ded2f5cdc62f2/fable_library-5.0.0a17-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2093ea940619c4421e2804332e175b7a54ed39043969323bde056fe28eb07631", size = 1824144, upload-time = "2025-11-27T14:38:36.961Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a3/9c145f8b27d4888055a4dec97f781b9b02b9ebff4656b4e629b3870be164/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6a2eef5c100199587824d2a7c2182415a173001ae4ef9db5161470c2586dfd8", size = 1885247, upload-time = "2025-11-27T14:39:14.788Z" }, - { url = "https://files.pythonhosted.org/packages/bf/07/511002b542c69c2d83112d38e0fa1af48f49a0e3863381d7254694c9313a/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3516ebf9ecf30d50e0f4f6834fec9dddf85ab95beb45bea7aa0500f4896e0a14", size = 1950279, upload-time = "2025-11-27T14:39:27.699Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c9/b576fafbcbc8d9ff2e0853ddd8e9bb0cba7c632f96e5f4e46c270a4fab1c/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:12b9f8cf16f7031f0c47a48f9ffd17bde2dca1db80af1577b42b3ee1ffd94cb2", size = 1949585, upload-time = "2025-11-27T14:39:38.363Z" }, - { url = "https://files.pythonhosted.org/packages/62/c5/0e8974a4d64ae5fc4025528d15d398703e462a08b817571f0b1859a4bc16/fable_library-5.0.0a17-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8b68546c5dda788d5d117c51cc1ecf1e074641428330ee010fdc328bdba77ef9", size = 1950401, upload-time = "2025-11-27T14:39:49.231Z" }, +version = "5.0.0a20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/71/d3c1e17e0fea982c3ef8b9ceb7aa5b6749db9124d3b555f73ab511c21284/fable_library-5.0.0a20.tar.gz", hash = "sha256:3bba05805c33b1ba4929ba069db4fb3522f1d2614230eeb838b28020107d49d1", size = 200300, upload-time = "2025-12-08T14:18:05.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/bf/4a66121fe2289870d0c537ca06fdc1200138e268857cdaffbd6f88b14c3e/fable_library-5.0.0a20-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:317cb9e1cf19af4850bfd549a4e4dd1318b4f8edff67edbc9bf7dc44293d1752", size = 1677386, upload-time = "2025-12-08T14:17:17.585Z" }, + { url = "https://files.pythonhosted.org/packages/e5/27/59cd313a3c5cc0b66d8ee335d5a73dd86354d8658093e852603b6c9129f0/fable_library-5.0.0a20-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:773932351766f4181a4b8389fd10cb7c3d3beb310b482eb4cfd1cfb47ab281ea", size = 1595217, upload-time = "2025-12-08T14:17:13.002Z" }, + { url = "https://files.pythonhosted.org/packages/8e/73/22bb6794b2f154aa63bb4273d3725ab388d30de6cdb66335cb5e0824d71e/fable_library-5.0.0a20-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8861a20ac8a08996dc904e022ed5405d3a64d8bed0f5706170d2a4fd71f7abb3", size = 1712084, upload-time = "2025-12-08T14:16:10.728Z" }, + { url = "https://files.pythonhosted.org/packages/97/ee/ebccb63b8a7d0a50656748433aad9c6876df9fa777aad829ba8b5268d38f/fable_library-5.0.0a20-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:795b889fde722badfa5e258f58a3b615b2a659b456c8c4a5235f41082b46dbe9", size = 1673111, upload-time = "2025-12-08T14:16:21.859Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/43d7de5990cd8c98b1db5f924c9970de321c7716a536a097f4ab8b246cb4/fable_library-5.0.0a20-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:614a5e3d2b86be680f1806da964d1b724d428a59dc6f34e7192d07ecae825e52", size = 1887006, upload-time = "2025-12-08T14:16:32.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/873267e136ebc96e1e54dcc02019c5f926822c157828c15d69010104017d/fable_library-5.0.0a20-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a4507fbf7bc9fe1ca1f7f357cb77f77019f243c1e485d5754e654073b79c819", size = 1809038, upload-time = "2025-12-08T14:16:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9a/d5de1a70c236c143b8fd437d60c8ec680f5f4cb043acd0de1024950e77f7/fable_library-5.0.0a20-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04d8eb693b8599e688ac7d34c39939f5721aa7552401ef25d51322efe518857e", size = 1724428, upload-time = "2025-12-08T14:17:05.7Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ee/0a0b6e63c1b7e4f98dae2b1eddb4dd3c5e72951f41580100c2eeee897050/fable_library-5.0.0a20-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c43061df7b866433854253ed5f1365888edb7db45d7c80163f7ccff010ad0a29", size = 1818458, upload-time = "2025-12-08T14:16:57.676Z" }, + { url = "https://files.pythonhosted.org/packages/33/c2/9e6aa0143257649a9b111ab79d8eca436c01d8d931dfb251d9d800da3a61/fable_library-5.0.0a20-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d0ac46820ee472acf143ed206bef4812cfbecf1129e70102b0d4d6755633a55a", size = 1893296, upload-time = "2025-12-08T14:17:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bd/61acd361e50ce5f7b84c6a2a9e71416f58ba2a60ddfb842572fc74fd39ea/fable_library-5.0.0a20-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc33ecf874ff306e444e854d9e90cb5dc43399c3ff27298e2133886749292c63", size = 1937677, upload-time = "2025-12-08T14:17:34.064Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9e/73860ac35ff750bd8e26e1332c5bdc5b053abf8fbcc7f7016dbb1744468b/fable_library-5.0.0a20-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ae43aa046addc1c8d53f4d23164abbbffc9654ba0f6c892b619ac6273058c926", size = 1929893, upload-time = "2025-12-08T14:17:45.995Z" }, + { url = "https://files.pythonhosted.org/packages/27/93/a1789440603de315f6c1cbd0fc588cdeb5081d8aaec9df6ffad24a0c3171/fable_library-5.0.0a20-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5556b6333db2362d62e0a8f74a2dfeafba35ce452d6d630cd3518fd57b64ec3c", size = 1943482, upload-time = "2025-12-08T14:17:56.835Z" }, + { url = "https://files.pythonhosted.org/packages/94/a2/17384cdd6a94394be53ae80072a0d6a48b919b183b12d8efa58fbf7af26f/fable_library-5.0.0a20-cp312-cp312-win_amd64.whl", hash = "sha256:4955a8dbea5d49ccf45c375d094975a9b08a1da35b0539ebb14e3b71e3238d58", size = 1442787, upload-time = "2025-12-08T14:18:08.999Z" }, + { url = "https://files.pythonhosted.org/packages/ae/46/b1079a48a7bb7490c18aebf6231bd1e0ec14da4191d395de85666304e166/fable_library-5.0.0a20-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:36ae0df00867ac0c717657a5a66a3ea9dd443847f90bdd18da0a01b04e75aa64", size = 1677535, upload-time = "2025-12-08T14:17:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/14/42/0d4be2261873b927282688e9fae24b03ff81e4ea3715d87eb711d2ac82dd/fable_library-5.0.0a20-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:834986ff156ceea94da2cfabe1e61dc0e290f0947c0f66f88bd76e66875bee75", size = 1595009, upload-time = "2025-12-08T14:17:14.144Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a8/ca23cf445a71a537729b3d38ca3552e2abfd4240167f4fae78970cdbcb32/fable_library-5.0.0a20-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff8893b1d64f4d4d82d43d72d996b296e0e29818544f1cdb0662cc7ce1f89120", size = 1711478, upload-time = "2025-12-08T14:16:11.792Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a0/c88aaa85903fa0715d4df924bfb391989bff045f2d695da492c47be40106/fable_library-5.0.0a20-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9308ad690ddf7320ae069f788bdb8bd970865a04c07d1d34c905ef2eaabd539", size = 1672618, upload-time = "2025-12-08T14:16:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/a5/62/4ae073d23e000fc4579a6a95fceae2e722a9f93363c2564e9ceb63083c15/fable_library-5.0.0a20-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e5700b673090c8aa4482e979fe3cbdb6e82ca01a178972f87db6c5c7f6498d8", size = 1885077, upload-time = "2025-12-08T14:16:33.789Z" }, + { url = "https://files.pythonhosted.org/packages/6e/73/475d999c04ba67b4dce77e721605d99487bf0b50135805418c5c516e672d/fable_library-5.0.0a20-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc73e1921ccc76b77e43b4d2b44540e7d1690882b902a83451b3c2465fbb959e", size = 1809792, upload-time = "2025-12-08T14:16:45.693Z" }, + { url = "https://files.pythonhosted.org/packages/ca/34/bb8d82e532d9e917153171cf1ea69f22a54f3ba1fc9758fbfeb95683d2d0/fable_library-5.0.0a20-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a4b398735819a18a062200f131faa43c6e8614fda6029cc503d17564e6217d1", size = 1723476, upload-time = "2025-12-08T14:17:07.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4a/77ae0d80c68014efe453f7e80177b9c32023b05430567dd43099c374ebb8/fable_library-5.0.0a20-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:30ad506b671fff2350676dee01877500b98be4eb6c41f6b79e5f160ec51615ef", size = 1818391, upload-time = "2025-12-08T14:16:58.967Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ca/52f1122d6f9f2de8e16d2875884c3583e9937342c343fb737810d19e4d63/fable_library-5.0.0a20-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2d25bf0a1410b736de1dc86dc79e8e3f2a72d811c50b8f9f87f4635ff582752d", size = 1892284, upload-time = "2025-12-08T14:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/90/58/c6a17f892c10e4dbc53f5556a38ec08178ec2675e44033dd7dddcfb60dc2/fable_library-5.0.0a20-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9ef61908d167361848909af2bcb817705c88051059303aab1c5d485d5e5af7be", size = 1937425, upload-time = "2025-12-08T14:17:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/75/d4/6f5435d04060fa7804eef148fdfc908ec51714837ed175624b274610bef7/fable_library-5.0.0a20-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c713ceca5d565ad6d391c28f03492ee72f1dc7a6ebebf70d3324b980af29a24e", size = 1929446, upload-time = "2025-12-08T14:17:47.187Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a0/65b167a2e7b8739696e20ba1cfedd143ad393630ee0f1b2bd8d4d555717a/fable_library-5.0.0a20-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9b5e4b9a14cbafae16c4b88c5ed5c65e86ddcb430041ac3b59a7378e15bf3ad", size = 1942992, upload-time = "2025-12-08T14:17:58.024Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6b/ef6e23223a76cfe127699e52edac17e3a5934c81803def4f42cb4e425089/fable_library-5.0.0a20-cp313-cp313-win_amd64.whl", hash = "sha256:f04c8eed205143c677e5beebea8d302aede692c4a815ea9cffdd44a6093a75ef", size = 1442837, upload-time = "2025-12-08T14:18:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/af/a5/5bc8b1611d2a7589f02d92be7839c022d6c47c61d4d01b1f481762c2603c/fable_library-5.0.0a20-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12bab89825b61bedbf7d2a0d80a9ba5a5bab73e5c46d40b3affa32cffe522ebf", size = 1711567, upload-time = "2025-12-08T14:16:13.242Z" }, + { url = "https://files.pythonhosted.org/packages/25/8d/48b51e8033d0f71d0f25db4fc7ac31d853aec9e5aee9bb1369ae4194a17e/fable_library-5.0.0a20-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f5377c6250f303463b2f2762243bc54209e9d4de24c12ed66caf6ba36315733", size = 1678665, upload-time = "2025-12-08T14:16:24.605Z" }, + { url = "https://files.pythonhosted.org/packages/7b/08/3ff3e4b6946e9e7e34a29058e7a175bb83182fb263c5f980449401ef71c3/fable_library-5.0.0a20-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2535035fded13ced47d3eed58ad5a18ea15267b12b3609eee4d158865cfaeb96", size = 1898217, upload-time = "2025-12-08T14:16:35.775Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/62661e3152ee4d067a4ba7a9e88c2fe578fdbbce46012cfebdce93524a70/fable_library-5.0.0a20-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8bf87de72a6ea961891fac9e369dde56e49a622670dee427081aa30c6953bb6", size = 1807154, upload-time = "2025-12-08T14:16:47.513Z" }, + { url = "https://files.pythonhosted.org/packages/ce/64/b5aa1e0fd5372a70ad85f0e657ef744f2e619c48f7cb233057f91a84b16c/fable_library-5.0.0a20-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:17f9acdcd857c18b7502bb04188996364d61ada3b833796579fc42ef494c3196", size = 1891441, upload-time = "2025-12-08T14:17:26.496Z" }, + { url = "https://files.pythonhosted.org/packages/07/81/34e5b90b1d8bd3ca4dfe050bf8c2ae10c030fd79129da6bd79dee4f9ecce/fable_library-5.0.0a20-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:282ab309c19e0c5bc38a5ccf47c4f175792c89158b46e73d7bdb61f064b6c933", size = 1942045, upload-time = "2025-12-08T14:17:36.922Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1d/56cc9c1fc8a426165ce16fd1a0c8269ae0c010b49e232137ecf26ed926a5/fable_library-5.0.0a20-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:caed2a670b4cac21d8056c066325963e0cc943af9c1506d8b025a963d7716349", size = 1935372, upload-time = "2025-12-08T14:17:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/02/e2/1161b1ed6bb74712b1c5369f1b3093c89d9ed059109127ff0482196bbe92/fable_library-5.0.0a20-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:78026935b8126f051c98f74effb04c8b3d25317b0ea97b0b6eaee09d816e4cb4", size = 1947627, upload-time = "2025-12-08T14:17:59.374Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/5799ccb0be448ce473223dd686ff6a68e092b0de5bab30736af103f8d761/fable_library-5.0.0a20-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0c6522d0465192276e8b8de1b1cf9419744535fc7237bd228907167106c62b3f", size = 1661135, upload-time = "2025-12-08T14:17:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/67/d2/7c0c9adc74cca250ec1583fb34cd2da1a3871a54bd277a315052285271cb/fable_library-5.0.0a20-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:98c8665f6702d3550479d473fa43b96e283b5b226248a4a7154fca52eabd7b9a", size = 1588822, upload-time = "2025-12-08T14:17:15.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d1/08a41f3044609e7bbea22d2c394b7630e7a20109c38f81e7068423e13eea/fable_library-5.0.0a20-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a721a2ece60069cfbcf814ce7356b3986c4ead23c76789263f7abc159b4c4107", size = 1706597, upload-time = "2025-12-08T14:16:14.637Z" }, + { url = "https://files.pythonhosted.org/packages/e3/42/f2c91c66cb83e19219a603f445b6482d7d8c38ce3c793f443d3425146e87/fable_library-5.0.0a20-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70aa4f7f84e28c1e4247f1552a4ae66afbd1397bb3d301f6ad4914451f4cdca6", size = 1661074, upload-time = "2025-12-08T14:16:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d4/ee641c8fa490c8ee363db0210d17727ce83b64cad6661bb3b1b6ae800d30/fable_library-5.0.0a20-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:490c332ca5b7075aa08de4efdac1807766713b392d431af2dad82242046bc193", size = 1887218, upload-time = "2025-12-08T14:16:38.352Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ca/7139e4c11554d9e7b72515c140340adf6f6ebe65deaf5071a8272909e638/fable_library-5.0.0a20-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a909bc7fa5f8f0dccbdeb5a71d2fb1fd941905b6e18a6439c3a9198ff5485ad3", size = 1802009, upload-time = "2025-12-08T14:16:49.119Z" }, + { url = "https://files.pythonhosted.org/packages/08/d0/02cd919390bc16924e341f2182745da509a0355df061a66f3b9cd9e3cfd6/fable_library-5.0.0a20-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ae7ca54f20ccc8f8ccfef6938830c6ccdaa19262f55713d1244550e3175e4a0", size = 1708040, upload-time = "2025-12-08T14:17:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/2c3425a07a577cfb85e3f7dba23efd4023bb797ff1119b67d38c8b6915d3/fable_library-5.0.0a20-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:90ee6c863d5e8fff8a659bbae8715d33a3eb387950926e04c89ec4650d1fb716", size = 1814233, upload-time = "2025-12-08T14:17:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/1f/cb/5cce1721473c5fcaaefc70258cedc500243230cfa7d3ccefa2abfa3dc007/fable_library-5.0.0a20-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb04fd4a86d7b846f5577879d388156fd64495c8f2bc3c23f8ff1fa66d1416c3", size = 1886419, upload-time = "2025-12-08T14:17:27.698Z" }, + { url = "https://files.pythonhosted.org/packages/09/66/ce8c0ea5282eae7478ea53fe4581899c0da79aabc885ced640f896fac284/fable_library-5.0.0a20-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:af04e6581136ba5d1d87828d84a65a7f1f0b7d0f463e148a9a83af9fbc75b32b", size = 1928128, upload-time = "2025-12-08T14:17:38.183Z" }, + { url = "https://files.pythonhosted.org/packages/66/45/b2725a1179fc70ed961b1741f61851417ffdfc97002d8edbf0fa35a5d767/fable_library-5.0.0a20-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b4fb050e549ae347b3198e204a52dc8533b64598f56fcc418aa43b8e86f2d6ee", size = 1925428, upload-time = "2025-12-08T14:17:49.853Z" }, + { url = "https://files.pythonhosted.org/packages/62/26/302ff5b84aa6ae1e2c2b3a4a5a5c04ebd8b97be145bfb357d7971311de91/fable_library-5.0.0a20-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c272da657429e0afc343e7045a596717f9a0e1c5d331e83793e45d61101f2ae1", size = 1930167, upload-time = "2025-12-08T14:18:00.538Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/fb0238c7b5707b4f51b1702a5840625c85d36a23f33b239edaded66d2b54/fable_library-5.0.0a20-cp314-cp314-win32.whl", hash = "sha256:f55c02d8bf2965936b308fe5760d39c77a71f03db8b89c8be31fc1cde79b9aa0", size = 1296393, upload-time = "2025-12-08T14:18:12.915Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/12e33d3121e324eb6b009192c0b60200bf81064815e0d6d797e628f6d124/fable_library-5.0.0a20-cp314-cp314-win_amd64.whl", hash = "sha256:d03ec6bf28c858c2ad3b33df552b0248b9143c2d798ab88cb21076efc5d41f74", size = 1429532, upload-time = "2025-12-08T14:18:11.754Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/14aa99d1831dd03006daf7b3d534ca0065ee17d420419a659d3227a7076f/fable_library-5.0.0a20-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf970ea08a40470ad69e6481ff02806d55695ac37aca80cec415e37cc0b4dff8", size = 1714205, upload-time = "2025-12-08T14:16:16.019Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/6cfc6d504dc56ff9c02e22b072ff5f9b4bdb1a528905e33e5da365887762/fable_library-5.0.0a20-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4a92ba7a3c69f00d31ab68e8144629c122f02d8c6a2a1d89c0498fc42ed68e4", size = 1679306, upload-time = "2025-12-08T14:16:27.281Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b5/a443520cdfa9c3c66c957715239b519c6430e91d91ede4b8809843a35af1/fable_library-5.0.0a20-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9159b1450974f49cb4a53b6ac2b70c0fd5feacfb10daa1bb37fc3852bbef52a9", size = 1898090, upload-time = "2025-12-08T14:16:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/17/a136966a847cdcfdfacf1d3abd385bf6ca008bae00a09505e5161e2f8efa/fable_library-5.0.0a20-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a0b9db1a02cc9ac29949a35bd6420f41b9fe655d7738a413f862e68b211dc04", size = 1807667, upload-time = "2025-12-08T14:16:50.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/43bc41b370ebf17a2f732267858685f751b05f1c7771549e3457da13e9ac/fable_library-5.0.0a20-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6f72cb71a084a5b17a43491be56b75a794cc2f38c99d6d30f2d563e8c230b068", size = 1894424, upload-time = "2025-12-08T14:17:28.886Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/4ee0911f6ac6edb99b2db1faf2fcba59ce1a97314a7ccd2722623fca4ffb/fable_library-5.0.0a20-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a59776aaf1f656a11ae2b96444198a816bc7080a222cb9f9e8dd4cf96b70796", size = 1942243, upload-time = "2025-12-08T14:17:39.519Z" }, + { url = "https://files.pythonhosted.org/packages/b0/30/543414b7ea9a5ffcd88e694269a00cb4d3d387d04dc4a3afb065ef28d64c/fable_library-5.0.0a20-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ced671ccdbbd4b4863334bf207dfc40a4fd44ae9c73e4ffcf92ec2e81a13f04f", size = 1936812, upload-time = "2025-12-08T14:17:51.26Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c4/31cee9d7c8f72ffebe793d5aa6fd317152fbdc841666e5035bcfb2a6e03c/fable_library-5.0.0a20-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:987b6f9aeb729390979714c943fb56d2bbbbf19271d839c080c34ac9b6e9af8b", size = 1944870, upload-time = "2025-12-08T14:18:02.547Z" }, ] [[package]] name = "fable-python" -version = "5.0.0a11" +version = "5.0.0a20" source = { virtual = "." } dependencies = [ { name = "fable-library" }, @@ -85,18 +169,163 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "fastapi" }, + { name = "httpx" }, + { name = "pydantic" }, { name = "pytest" }, { name = "ruff" }, ] +django = [ + { name = "django" }, +] +examples = [ + { name = "django" }, + { name = "fastapi" }, + { name = "flask" }, + { name = "pydantic" }, + { name = "uvicorn", extra = ["standard"] }, +] +fastapi = [ + { name = "fastapi" }, + { name = "pydantic" }, + { name = "uvicorn", extra = ["standard"] }, +] +flask = [ + { name = "flask" }, +] [package.metadata] -requires-dist = [{ name = "fable-library", specifier = "==5.0.0a17" }] +requires-dist = [{ name = "fable-library", specifier = "==5.0.0a20" }] [package.metadata.requires-dev] dev = [ + { name = "fastapi", specifier = ">=0.115.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "pydantic", specifier = ">=2.0.0" }, { name = "pytest", specifier = ">=6.2.4,<10" }, { name = "ruff", specifier = ">=0.14.0" }, ] +django = [{ name = "django", specifier = ">=4.2,<5" }] +examples = [ + { name = "django", specifier = ">=4.2,<5" }, + { name = "fastapi", specifier = ">=0.100.0" }, + { name = "flask", specifier = ">=3.1.2,<4" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.23.0" }, +] +fastapi = [ + { name = "fastapi", specifier = ">=0.100.0" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.23.0" }, +] +flask = [{ name = "flask", specifier = ">=3.1.2,<4" }] + +[[package]] +name = "fastapi" +version = "0.124.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/9c/11969bd3e3bc4aa3a711f83dd3720239d3565a934929c74fc32f6c9f3638/fastapi-0.124.0.tar.gz", hash = "sha256:260cd178ad75e6d259991f2fd9b0fee924b224850079df576a3ba604ce58f4e6", size = 357623, upload-time = "2025-12-06T13:11:35.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/29/9e1e82e16e9a1763d3b55bfbe9b2fa39d7175a1fd97685c482fa402e111d/fastapi-0.124.0-py3-none-any.whl", hash = "sha256:91596bdc6dde303c318f06e8d2bc75eafb341fc793a0c9c92c0bc1db1ac52480", size = 112505, upload-time = "2025-12-06T13:11:34.392Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] [[package]] name = "iniconfig" @@ -107,6 +336,90 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -125,6 +438,92 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -150,28 +549,304 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + [[package]] name = "ruff" -version = "0.14.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/5b/dd7406afa6c95e3d8fa9d652b6d6dd17dd4a6bf63cb477014e8ccd3dcd46/ruff-0.14.7.tar.gz", hash = "sha256:3417deb75d23bd14a722b57b0a1435561db65f0ad97435b4cf9f85ffcef34ae5", size = 5727324, upload-time = "2025-11-28T20:55:10.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/b1/7ea5647aaf90106f6d102230e5df874613da43d1089864da1553b899ba5e/ruff-0.14.7-py3-none-linux_armv6l.whl", hash = "sha256:b9d5cb5a176c7236892ad7224bc1e63902e4842c460a0b5210701b13e3de4fca", size = 13414475, upload-time = "2025-11-28T20:54:54.569Z" }, - { url = "https://files.pythonhosted.org/packages/af/19/fddb4cd532299db9cdaf0efdc20f5c573ce9952a11cb532d3b859d6d9871/ruff-0.14.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3f64fe375aefaf36ca7d7250292141e39b4cea8250427482ae779a2aa5d90015", size = 13634613, upload-time = "2025-11-28T20:55:17.54Z" }, - { url = "https://files.pythonhosted.org/packages/40/2b/469a66e821d4f3de0440676ed3e04b8e2a1dc7575cf6fa3ba6d55e3c8557/ruff-0.14.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93e83bd3a9e1a3bda64cb771c0d47cda0e0d148165013ae2d3554d718632d554", size = 12765458, upload-time = "2025-11-28T20:55:26.128Z" }, - { url = "https://files.pythonhosted.org/packages/f1/05/0b001f734fe550bcfde4ce845948ac620ff908ab7241a39a1b39bb3c5f49/ruff-0.14.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3838948e3facc59a6070795de2ae16e5786861850f78d5914a03f12659e88f94", size = 13236412, upload-time = "2025-11-28T20:55:28.602Z" }, - { url = "https://files.pythonhosted.org/packages/11/36/8ed15d243f011b4e5da75cd56d6131c6766f55334d14ba31cce5461f28aa/ruff-0.14.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24c8487194d38b6d71cd0fd17a5b6715cda29f59baca1defe1e3a03240f851d1", size = 13182949, upload-time = "2025-11-28T20:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3b/cf/fcb0b5a195455729834f2a6eadfe2e4519d8ca08c74f6d2b564a4f18f553/ruff-0.14.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79c73db6833f058a4be8ffe4a0913b6d4ad41f6324745179bd2aa09275b01d0b", size = 13816470, upload-time = "2025-11-28T20:55:08.203Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5d/34a4748577ff7a5ed2f2471456740f02e86d1568a18c9faccfc73bd9ca3f/ruff-0.14.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:12eb7014fccff10fc62d15c79d8a6be4d0c2d60fe3f8e4d169a0d2def75f5dad", size = 15289621, upload-time = "2025-11-28T20:55:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/53/53/0a9385f047a858ba133d96f3f8e3c9c66a31cc7c4b445368ef88ebeac209/ruff-0.14.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c623bbdc902de7ff715a93fa3bb377a4e42dd696937bf95669118773dbf0c50", size = 14975817, upload-time = "2025-11-28T20:55:24.107Z" }, - { url = "https://files.pythonhosted.org/packages/a8/d7/2f1c32af54c3b46e7fadbf8006d8b9bcfbea535c316b0bd8813d6fb25e5d/ruff-0.14.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f53accc02ed2d200fa621593cdb3c1ae06aa9b2c3cae70bc96f72f0000ae97a9", size = 14284549, upload-time = "2025-11-28T20:55:06.08Z" }, - { url = "https://files.pythonhosted.org/packages/92/05/434ddd86becd64629c25fb6b4ce7637dd52a45cc4a4415a3008fe61c27b9/ruff-0.14.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:281f0e61a23fcdcffca210591f0f53aafaa15f9025b5b3f9706879aaa8683bc4", size = 14071389, upload-time = "2025-11-28T20:55:35.617Z" }, - { url = "https://files.pythonhosted.org/packages/ff/50/fdf89d4d80f7f9d4f420d26089a79b3bb1538fe44586b148451bc2ba8d9c/ruff-0.14.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dbbaa5e14148965b91cb090236931182ee522a5fac9bc5575bafc5c07b9f9682", size = 14202679, upload-time = "2025-11-28T20:55:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/77/54/87b34988984555425ce967f08a36df0ebd339bb5d9d0e92a47e41151eafc/ruff-0.14.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1464b6e54880c0fe2f2d6eaefb6db15373331414eddf89d6b903767ae2458143", size = 13147677, upload-time = "2025-11-28T20:55:19.933Z" }, - { url = "https://files.pythonhosted.org/packages/67/29/f55e4d44edfe053918a16a3299e758e1c18eef216b7a7092550d7a9ec51c/ruff-0.14.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f217ed871e4621ea6128460df57b19ce0580606c23aeab50f5de425d05226784", size = 13151392, upload-time = "2025-11-28T20:55:21.967Z" }, - { url = "https://files.pythonhosted.org/packages/36/69/47aae6dbd4f1d9b4f7085f4d9dcc84e04561ee7ad067bf52e0f9b02e3209/ruff-0.14.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6be02e849440ed3602d2eb478ff7ff07d53e3758f7948a2a598829660988619e", size = 13412230, upload-time = "2025-11-28T20:55:12.749Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4b/6e96cb6ba297f2ba502a231cd732ed7c3de98b1a896671b932a5eefa3804/ruff-0.14.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19a0f116ee5e2b468dfe80c41c84e2bbd6b74f7b719bee86c2ecde0a34563bcc", size = 14195397, upload-time = "2025-11-28T20:54:56.896Z" }, - { url = "https://files.pythonhosted.org/packages/69/82/251d5f1aa4dcad30aed491b4657cecd9fb4274214da6960ffec144c260f7/ruff-0.14.7-py3-none-win32.whl", hash = "sha256:e33052c9199b347c8937937163b9b149ef6ab2e4bb37b042e593da2e6f6cccfa", size = 13126751, upload-time = "2025-11-28T20:55:03.47Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b5/d0b7d145963136b564806f6584647af45ab98946660d399ec4da79cae036/ruff-0.14.7-py3-none-win_amd64.whl", hash = "sha256:e17a20ad0d3fad47a326d773a042b924d3ac31c6ca6deb6c72e9e6b5f661a7c6", size = 14531726, upload-time = "2025-11-28T20:54:59.121Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d2/1637f4360ada6a368d3265bf39f2cf737a0aaab15ab520fc005903e883f8/ruff-0.14.7-py3-none-win_arm64.whl", hash = "sha256:be4d653d3bea1b19742fcc6502354e32f65cd61ff2fbdb365803ef2c2aec6228", size = 13609215, upload-time = "2025-11-28T20:55:15.375Z" }, +version = "0.14.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, + { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, + { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, + { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, + { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, + { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, ]