diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 7807037..fae2a4f 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index fce59c2..b7ad910 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index b6bcaaa..9e948a6 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index ab34020..b9c190e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,5 @@ wheels/ .direnv .mypy_cache .python-version -.pytest_cache .vscode _autosummary \ No newline at end of file diff --git a/LICENSE b/LICENSE index fdddb29..0e259d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,121 @@ -This is free and unencumbered software released into the public domain. +Creative Commons Legal Code -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. +CC0 1.0 Universal -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +Statement of Purpose -For more information, please refer to +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md index 234dea7..a2a7a91 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,29 @@ -
- # 🐍 Snakia Framework -![Code Quality](https://img.shields.io/codacy/grade/508e02449dd145e7a93c7ba08ac756a4?logo=codacy) -![Code Size](https://img.shields.io/github/languages/code-size/ruject/snakia) -![License](https://img.shields.io/github/license/ruject/snakia) -![Open Issues](https://img.shields.io/github/issues-raw/ruject/snakia) -![Commit Activity](https://img.shields.io/github/commit-activity/m/ruject/snakia) - -[API Reference](https://ruject.github.io/snakia/) - •  -[Telegram Chat](https://t.me/RuJect_Community) - -
- **Snakia** is a modern Python framework for creating applications with Entity-Component-System (ECS) architecture, event system, and reactive programming. Built with performance (maybe) and modularity in mind, Snakia provides a clean API for developing complex applications ranging from games to terminal user interfaces. ## 📋 Table of Contents -- [🐍 Snakia Framework](#-snakia-framework) - - [📋 Table of Contents](#-table-of-contents) - - [✨ Key Features](#-key-features) - - [🚀 Installation](#-installation) - - [Prerequisites](#prerequisites) - - [Install from PyPi (recommended)](#install-from-pypi-recommended) - - [Install from Source](#install-from-source) - - [🎯 Roadmap \& TODO](#-roadmap--todo) - - [🚀 Quick Start](#-quick-start) - - [🏗️ Architecture](#️-architecture) - - [📦 Examples](#-examples) - - [Health System](#health-system) - - [TUI Application](#tui-application) - - [🤝 Contributing](#-contributing) - - [How to Contribute](#how-to-contribute) - - [Development Guidelines](#development-guidelines) +- [🎯 Roadmap & TODO](#-roadmap--todo) +- [🚀 Installation](#-installation) +- [🚀 Quick Start](#-quick-start) +- [🏗️ Architecture](#️-architecture) +- [⚙️ Core](#️-core) +- [🎯 ECS System](#-ecs-system) +- [📡 Event System (ES)](#-event-system-es) +- [🔌 Plugin System](#-plugin-system) +- [🎨 TUI System](#-tui-system) +- [⚡ Reactive Programming (RX)](#-reactive-programming-rx) +- [🛠️ Utilities](#️-utilities) +- [🎭 Decorators](#-decorators) +- [🏷️ Properties](#-properties) +- [🌐 Platform Abstraction](#-platform-abstraction) +- [📦 Examples](#-examples) +- [🤝 Contributing](#-contributing) +- [🆘 Support](#-support) +- [📄 License](#-license) -## ✨ Key Features +### ✨ Key Features - 🏗️ **ECS Architecture** - Flexible entity-component-system for scalable game/app logic - 📡 **Event System** - Asynchronous event handling with filters and priorities @@ -64,9 +52,15 @@ pip install snakia ### Install from Source ```bash +# Clone the repository git clone https://github.com/RuJect/Snakia.git cd Snakia + +# Install with pip pip install -e . + +# Or with uv (recommended) +uv sync ``` ## 🎯 Roadmap & TODO @@ -139,6 +133,616 @@ Snakia/ └── types/ # Special types ``` +## ⚙️ Core + +### Engine + +The central component of the framework that coordinates all systems: + +```python +from snakia.core.engine import Engine + +engine = Engine() +# Systems: +# - engine.system - ECS system +# - engine.dispatcher - Event system +# - engine.loader - Plugin loader + +engine.start() # Start all systems +engine.stop() # Stop all systems +engine.update() # Update systems +``` + +## 🎯 ECS System + +Entity-Component-System architecture for creating flexible and performant applications. + +### Component + +Base class for all components: + +```python +from snakia.core.ecs import Component +from pydantic import Field + +class PositionComponent(Component): + x: float = Field(default=0.0) + y: float = Field(default=0.0) + +class VelocityComponent(Component): + vx: float = Field(default=0.0) + vy: float = Field(default=0.0) +``` + +### Processor + +Processors handle components in the system: + +```python +from snakia.core.ecs import Processor, System + +class MovementProcessor(Processor): + def process(self, system: System) -> None: + # Get all entities with Position and Velocity + for entity, (pos, vel) in system.get_components( + PositionComponent, VelocityComponent + ): + pos.x += vel.vx + pos.y += vel.vy +``` + +### System + +Entity and component management: + +```python +# Creating an entity with components +entity = system.create_entity( + PositionComponent(x=10, y=20), + VelocityComponent(vx=1, vy=0) +) + +# Adding a component to an existing entity +system.add_component(entity, HealthComponent(value=100)) + +# Getting entity components +pos, vel = system.get_components_of_entity( + entity, PositionComponent, VelocityComponent +) + +# Checking for components +if system.has_components(entity, PositionComponent, VelocityComponent): + print("Entity has position and velocity") + +# Removing a component +system.remove_component(entity, VelocityComponent) + +# Deleting an entity +system.delete_entity(entity) +``` + +## 📡 Event System (ES) + +Asynchronous event system with filter and priority support. + +### Event + +Base class for events: + +```python +from snakia.core.es import Event +from pydantic import Field + +class PlayerDiedEvent(Event): + player_id: int = Field() + cause: str = Field(default="unknown") + ttl: int = Field(default=10) # Event lifetime +``` + +### Handler + +Event handlers: + +```python +from snakia.core.es import Handler, Action + +def on_player_died(event: PlayerDiedEvent) -> Action | None: + print(f"Player {event.player_id} died from {event.cause}") + return Action.move(1) # Move to next handler +``` + +### Filter + +Event filters: + +```python +from snakia.core.es import Filter + +def only_important_deaths(event: PlayerDiedEvent) -> bool: + return event.cause in ["boss", "pvp"] + +# Using a filter +@dispatcher.on(PlayerDiedEvent, filter=only_important_deaths) +def handle_important_death(event: PlayerDiedEvent): + print("Important death occurred!") +``` + +### Dispatcher + +Central event dispatcher: + +```python +from snakia.core.es import Dispatcher, Subscriber + +dispatcher = Dispatcher() + +# Subscribing to an event +dispatcher.subscribe(PlayerDiedEvent, Subscriber( + handler=on_player_died, + filter=only_important_deaths, + priority=10 +)) + +# Decorator for subscription +@dispatcher.on(PlayerDiedEvent, priority=5) +def handle_death(event: PlayerDiedEvent): + print("Death handled!") + +# Publishing an event +dispatcher.publish(PlayerDiedEvent(player_id=123, cause="boss")) +``` + +## 🔌 Plugin System + +Modular system for loading and managing plugins. + +### Plugin + +Base class for plugins: + +```python +from snakia.core.loader import Meta, Plugin, PluginProcessor +from snakia.types import Version + +class MyProcessor(PluginProcessor): + def process(self, system): + # Processor logic + pass + +class MyPlugin(Plugin, meta=Meta( + name="my_plugin", + author="developer", + version=Version.from_args(1, 0, 0), + processors=(MyProcessor,), + subscribers=() +)): + def on_load(self): + print("Plugin loaded!") + + def on_unload(self): + print("Plugin unloaded!") +``` + +### Meta + +Plugin metadata: + +```python +from snakia.core.loader import Meta +from snakia.core.es import Subscriber + +meta = Meta( + name="plugin_name", + author="author_name", + version=Version.from_args(1, 0, 0), + processors=(Processor1, Processor2), + subscribers=( + (EventType, Subscriber(handler, filter, priority)), + ) +) +``` + +### Loader + +Plugin loader: + +```python +from snakia.core.loader import Loader + +loader = Loader(engine) + +# Registering a plugin +loader.register(MyPlugin) + +# Loading all plugins +loader.load_all() + +# Unloading all plugins +loader.unload_all() +``` + +## 🎨 TUI System + +System for creating text-based user interfaces. + +### Widget + +Base class for widgets: + +```python +from snakia.core.tui import Widget, Canvas, CanvasChar +from snakia.core.rx import Bindable + +class MyWidget(Widget): + def __init__(self): + super().__init__() + self.text = self.state("Hello World") + self.color = self.state(CanvasChar(fg_color="red")) + + def on_render(self) -> Canvas: + canvas = Canvas(20, 5) + canvas.draw_text(0, 0, self.text.value, self.color.value) + return canvas +``` + +### Canvas + +Drawing canvas: + +```python +from snakia.core.tui import Canvas, CanvasChar + +canvas = Canvas(80, 24) + +# Drawing text +canvas.draw_text(10, 5, "Hello", CanvasChar(fg_color="blue")) + +# Drawing rectangle +canvas.draw_rect(0, 0, 20, 10, CanvasChar("█", fg_color="green")) + +# Filling area +canvas.draw_filled_rect(5, 5, 10, 5, CanvasChar(" ", bg_color="red")) + +# Lines +canvas.draw_line_h(0, 0, 20, CanvasChar("-")) +canvas.draw_line_v(0, 0, 10, CanvasChar("|")) +``` + +### CanvasChar + +Character with attributes: + +```python +from snakia.core.tui import CanvasChar + +char = CanvasChar( + char="A", + fg_color="red", # Text color + bg_color="blue", # Background color + bold=True, # Bold + italic=False, # Italic + underline=True # Underline +) +``` + +### Renderer + +Screen rendering: + +```python +from snakia.core.tui import RenderContext +from snakia.core.tui.render import ANSIRenderer +import sys + +class StdoutTarget: + def write(self, text: str): sys.stdout.write(text) + def flush(self): sys.stdout.flush() + +renderer = ANSIRenderer(StdoutTarget()) + +with RenderContext(renderer) as ctx: + ctx.render(widget.render()) +``` + +### Ready-made Widgets + +```python +from snakia.core.tui.widgets import ( + TextWidget, BoxWidget, + HorizontalSplitWidget, VerticalSplitWidget +) + +# Text widget +text = TextWidget("Hello", CanvasChar(fg_color="red", bold=True)) + +# Box widget +box = BoxWidget(10, 5, CanvasChar("█", fg_color="yellow")) + +# Splitters +h_split = HorizontalSplitWidget([text1, text2], "|") +v_split = VerticalSplitWidget([h_split, box], "-") +``` + +## ⚡ Reactive Programming (RX) + +Reactive programming system for creating responsive interfaces. + +### Bindable + +Reactive variables: + +```python +from snakia.core.rx import Bindable, ValueChanged + +# Creating a reactive variable +counter = Bindable(0) + +# Subscribing to changes +def on_change(event: ValueChanged[int]): + print(f"Counter changed from {event.old_value} to {event.new_value}") + +counter.subscribe(on_change) + +# Changing value +counter.set(5) # Will call on_change +counter(10) # Alternative syntax +``` + +### AsyncBindable + +Asynchronous reactive variables: + +```python +from snakia.core.rx import AsyncBindable + +async_counter = AsyncBindable(0) + +async def async_handler(event: ValueChanged[int]): + print(f"Async counter: {event.new_value}") + +await async_counter.subscribe(async_handler, run_now=True) +await async_counter.set(42) +``` + +### Operators + +```python +from snakia.core.rx import map, filter, combine, merge + +# Transformation +doubled = map(counter, lambda x: x * 2) + +# Filtering +even_only = filter(counter, lambda x: x % 2 == 0) + +# Combining +combined = combine(counter, doubled, lambda a, b: a + b) + +# Merging streams +merged = merge(counter, async_counter) +``` + +## 🛠️ Utilities + +### to_async + +Converting synchronous functions to asynchronous: + +```python +from snakia.utils import to_async + +def sync_function(x): + return x * 2 + +async_function = to_async(sync_function) +result = await async_function(5) +``` + +### nolock + +Performance optimization: + +```python +from snakia.utils import nolock + +def busy_loop(): + while running: + # Work + nolock() # Release GIL +``` + +### inherit + +Simplified inheritance: + +```python +from snakia.utils import inherit + +class Base: + def method(self): pass + +class Derived(inherit(Base)): + def method(self): + super().method() + # Additional logic +``` + +### this + +Reference to current object: + +```python +from snakia.utils import this + +def func(): + return this() # Returns `` +``` + +### throw + +Throwing exceptions: + +```python +from snakia.utils import throw + +def validate(value): + if value < 0: + throw(ValueError("Value must be positive")) +``` + +### frame + +Working with frames: + +```python +from snakia.utils import frame + +def process_frame(): + current_frame = frame() + # Process frame +``` + +## 🎭 Decorators + +### inject_replace + +Method replacement: + +```python +from snakia.decorators import inject_replace + +class Original: + def method(self): return "original" + +@inject_replace(Original, "method") +def new_method(self): return "replaced" +``` + +### inject_before / inject_after + +Hooks before and after execution: + +```python +from snakia.decorators import inject_before, inject_after + +@inject_before(MyClass, "method") +def before_hook(self): print("Before method") + +@inject_after(MyClass, "method") +def after_hook(self): print("After method") +``` + +### singleton + +Singleton pattern: + +```python +from snakia.decorators import singleton + +@singleton +class Database: + def __init__(self): + self.connection = "connected" +``` + +### pass_exceptions + +Exception handling: + +```python +from snakia.decorators import pass_exceptions + +@pass_exceptions(ValueError, TypeError) +def risky_function(): + # Code that might throw exceptions + pass +``` + +## 🏷️ Properties + +### readonly + +Read-only property: + +```python +from snakia.property import readonly + + +class Currency: + @readonly + def rate(self) -> int: + return 100 + + +currency = Currency() +currency.rate = 200 +print(currency.rate) # Output: 100 +``` + +### initonly + +Initialization-only property: + +```python +from snakia.property import initonly + + +class Person: + name = initonly[str]("name") + + +bob = Person() +bob.name = "Bob" +print(bob.name) # Output: "Bob" +bob.name = "not bob" +print(bob.name) # Output: "Bob" +``` + +### 🏛️ classproperty + +Class property: + +```python +from snakia.property import classproperty + +class MyClass: + @classproperty + def class_value(cls): + return "class_value" +``` + +## 🌐 Platform Abstraction + +### 🖥️ PlatformOS + +Operating system abstraction: + +```python +from snakia.platform import PlatformOS, OS + +# Detecting current OS +current_os = OS.current() + +if current_os == PlatformOS.LINUX: + print("Running on Linux") +elif current_os == PlatformOS.ANDROID: + print("Running on Android") +``` + +### 🏗️ PlatformLayer + +Platform layers: + +```python +from snakia.platform import LinuxLayer, AndroidLayer + +# Linux layer +linux_layer = LinuxLayer() + +# Android layer +android_layer = AndroidLayer() +``` + ## 📦 Examples ### Health System @@ -252,4 +856,16 @@ We welcome contributions to Snakia development! Whether you're fixing bugs, addi - Add type hints to all new code - Write clear commit messages - Update documentation for new features -- Test your changes thoroughly \ No newline at end of file +- Test your changes thoroughly + +## 🆘 Support + +Need help? We're here to assist you! + +- 🐛 **Bug Reports** - [GitHub Issues](https://github.com/RuJect/Snakia/issues) +- 💬 **Community Chat** - [RuJect Community Telegram](https://t.me/RuJect_Community) +- 📧 **Direct Contact** - mailto:rus07tam.uwu@gmail.com + +## 📄 License + +See the `LICENSE` file for details. diff --git a/docs/source/conf.py b/docs/source/conf.py index 98653e6..314c382 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,9 @@ import sys from pathlib import Path -sys.path.insert(0, str((Path(__file__).parent.parent.parent / "src").resolve())) +sys.path.insert( + 0, str((Path(__file__).parent.parent.parent / "src").resolve()) +) project = "Snakia" copyright = "2025, RuJect" diff --git a/examples/health_plugin.py b/examples/health_plugin.py new file mode 100644 index 0000000..a795d33 --- /dev/null +++ b/examples/health_plugin.py @@ -0,0 +1,88 @@ +from typing import final + +from pydantic import Field + +from snakia.core.ecs import Component +from snakia.core.ecs.system import System +from snakia.core.engine import Engine +from snakia.core.es import Event +from snakia.core.loader import Meta, Plugin, PluginProcessor +from snakia.types import Version + + +class HealthComponent(Component): + max_value: int = Field(default=100, ge=0) + value: int = Field(default=100, ge=0) + + +class DamageComponent(Component): + damage: int = Field(ge=0) + ticks: int = Field(default=1, ge=0) + + +class HealComponent(Component): + heal: int = Field(ge=0) + ticks: int = Field(default=1, ge=0) + + +class DeathEvent(Event): + entity: int = Field() + + +class HealthProcessor(PluginProcessor): + def process(self, system: System) -> None: + for entity, (heal, health) in system.get_components( + HealComponent, HealthComponent + ): + health.value += heal.heal + heal.ticks -= 1 + if heal.ticks <= 0: + system.remove_component(entity, HealComponent) + for entity, (damage, health) in system.get_components( + DamageComponent, HealthComponent + ): + health.value -= damage.damage + damage.ticks -= 1 + if damage.ticks <= 0: + system.remove_component(entity, DamageComponent) + if health.value <= 0: + system.remove_component(entity, HealthComponent) + self.plugin.dispatcher.publish(DeathEvent(entity=entity)) + + +@final +class HealthPlugin( + Plugin, + meta=Meta( + name="health", + author="snakia", + version=Version.from_args(1, 0, 0), + subscribers=(), + processors=(HealthProcessor,), + ), +): + def on_load(self) -> None: + pass + + def on_unload(self) -> None: + pass + + +def main() -> None: + engine = Engine() + engine.loader.register(HealthPlugin) + engine.loader.load_all() + + @engine.dispatcher.on(DeathEvent) + def on_death(event: DeathEvent) -> None: + print(f"Entity: {event.entity} is death!") + + player = engine.system.create_entity() + engine.system.add_component(player, HealthComponent()) + engine.system.add_component(player, DamageComponent(damage=10, ticks=10)) + + engine.start() + + +if __name__ == "__main__": + main() diff --git a/examples/tui.py b/examples/tui.py index 213e1d8..7e64611 100644 --- a/examples/tui.py +++ b/examples/tui.py @@ -2,12 +2,8 @@ import sys from snakia.core.tui import CanvasChar, RenderContext from snakia.core.tui.render import ANSIRenderer -from snakia.core.tui.widgets import ( - BoxWidget, - HorizontalSplitWidget, - TextWidget, - VerticalSplitWidget, -) +from snakia.core.tui.widgets import (BoxWidget, HorizontalSplitWidget, + TextWidget, VerticalSplitWidget) class StdoutTarget: diff --git a/flake.nix b/flake.nix index 34c8081..541e3f5 100644 --- a/flake.nix +++ b/flake.nix @@ -6,20 +6,23 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { - nixpkgs, - flake-utils, - ... - }: + outputs = + { + nixpkgs, + flake-utils, + ... + }: flake-utils.lib.eachDefaultSystem ( - system: let + system: + let pkgs = import nixpkgs { inherit system; }; - in { + in + { devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ - python310 + python312 ]; buildInputs = with pkgs; [ @@ -28,7 +31,7 @@ uv isort mypy - pylint + python312Packages.pylint ]; }; } diff --git a/py.typed b/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index 9d48e55..13fdbbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "snakia" -version = "0.5.0" +version = "0.4.0" description = "Modern python framework" readme = "README.md" authors = [ @@ -10,23 +10,17 @@ keywords = ["python3", "event system", "ecs", "reactive programming"] classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Free Threading", ] -requires-python = ">=3.10" +requires-python = ">=3.12" dependencies = [ - "annotated-types>=0.7.0", - "exceptiongroup>=1.3.1", "networkx>=3.4.2", - "pydantic>=2.11.10", - "types-networkx>=3.5.0.20251106", - "typing-extensions>=4.15.0", + "pydantic>=2.12.3", ] -license = "Unlicense" +license = "CC0-1.0" license-files = ["LICENSE"] [project.urls] @@ -43,5 +37,4 @@ init-hook = "import sys; sys.path.append('.venv/lib/python3.12/site-packages')" disable = ["C0114", "C0115", "C0116", "R0801"] max-args = 8 max-positional-arguments = 7 -min-public-methods = 1 -fail-on = "error" +min-public-methods = 1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index eea532c..a56e901 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,15 @@ # This file was autogenerated by uv via the following command: # uv pip compile pyproject.toml -o requirements.txt annotated-types==0.7.0 - # via - # snakia (pyproject.toml) - # pydantic -exceptiongroup==1.3.1 + # via pydantic +networkx==3.5 # via snakia (pyproject.toml) -networkx==3.4.2 - # via snakia (pyproject.toml) -numpy==2.2.6 - # via types-networkx pydantic==2.12.3 # via snakia (pyproject.toml) pydantic-core==2.41.4 # via pydantic -types-networkx==3.5.0.20251106 - # via snakia (pyproject.toml) typing-extensions==4.15.0 # via - # snakia (pyproject.toml) - # exceptiongroup # pydantic # pydantic-core # typing-inspection diff --git a/src/snakia/core/ecs/system.py b/src/snakia/core/ecs/system.py index 569643b..e023c72 100644 --- a/src/snakia/core/ecs/system.py +++ b/src/snakia/core/ecs/system.py @@ -3,38 +3,21 @@ from __future__ import annotations from collections import defaultdict from collections.abc import Iterable from itertools import count -from typing import Any, TypeVar, cast, overload +from typing import Any, cast, overload -import networkx as nx +import networkx as nx # type: ignore from snakia.utils import nolock from .component import Component from .processor import Processor -A = TypeVar("A", bound=Component) -B = TypeVar("B", bound=Component) -C = TypeVar("C", bound=Component) -D = TypeVar("D", bound=Component) -E = TypeVar("E", bound=Component) - -P = TypeVar("P", bound=Processor) - class System: """ A system is a collection of entities and components that can be processed by processors. """ - __slots__ = ( - "__processors", - "__components", - "__entitites", - "__entity_counter", - "__dead_entities", - "__is_running", - ) - __processors: list[Processor] __components: dict[type[Component], set[int]] __entitites: dict[int, dict[type[Component], Component]] @@ -63,7 +46,9 @@ class System: self.__entity_counter = count(start=1) self.__dead_entities = set() - def get_processor(self, processor_type: type[P], /) -> P | None: + def get_processor[P: Processor]( + self, processor_type: type[P], / + ) -> P | None: """Returns the first processor of the given type.""" for processor in self.__processors: if isinstance(processor, processor_type): @@ -82,31 +67,39 @@ class System: self.__processors.remove(processor) @overload - def get_components(self, c1: type[A], /) -> Iterable[tuple[int, tuple[A]]]: ... + def get_components[A: Component]( + self, __c1: type[A], / + ) -> Iterable[tuple[int, tuple[A]]]: ... @overload - def get_components( - self, c1: type[A], c2: type[B], / + def get_components[A: Component, B: Component]( + self, __c1: type[A], __c2: type[B], / ) -> Iterable[tuple[int, tuple[A, B]]]: ... @overload - def get_components( - self, c1: type[A], c2: type[B], c3: type[C], / + def get_components[A: Component, B: Component, C: Component]( + self, __c1: type[A], __c2: type[B], __c3: type[C], / ) -> Iterable[tuple[int, tuple[A, B, C]]]: ... @overload - def get_components( - self, c1: type[A], c2: type[B], c3: type[C], c4: type[D], / + def get_components[A: Component, B: Component, C: Component, D: Component]( + self, __c1: type[A], __c2: type[B], __c3: type[C], __c4: type[D], / ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... @overload - def get_components( + def get_components[ + A: Component, + B: Component, + C: Component, + D: Component, + E: Component, + ]( self, - c1: type[A], - c2: type[B], - c3: type[C], - c4: type[D], - c5: type[E], + __c1: type[A], + __c2: type[B], + __c3: type[C], + __c4: type[D], + __c5: type[E], /, ) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ... @@ -115,7 +108,10 @@ class System: ) -> Iterable[tuple[int, tuple[Component, ...]]]: """Returns all entities with the given components.""" entity_set = set.intersection( - *(self.__components[component_type] for component_type in component_types) + *( + self.__components[component_type] + for component_type in component_types + ) ) for entity in entity_set: yield ( @@ -127,40 +123,51 @@ class System: ) @overload - def get_components_of_entity( - self, entity: int, c1: type[A], / + def get_components_of_entity[A: Component]( + self, entity: int, __c1: type[A], / ) -> tuple[A | None]: ... @overload - def get_components_of_entity( - self, entity: int, c1: type[A], c2: type[B], / + def get_components_of_entity[A: Component, B: Component]( + self, entity: int, __c1: type[A], __c2: type[B], / ) -> tuple[A | None, B | None]: ... @overload - def get_components_of_entity( - self, entity: int, c1: type[A], c2: type[B], c3: type[C], / + def get_components_of_entity[A: Component, B: Component, C: Component]( + self, entity: int, __c1: type[A], __c2: type[B], __c3: type[C], / ) -> tuple[A | None, B | None, C | None]: ... @overload - def get_components_of_entity( + def get_components_of_entity[ + A: Component, + B: Component, + C: Component, + D: Component, + ]( self, entity: int, - c1: type[A], - c2: type[B], - c3: type[C], - c4: type[D], + __c1: type[A], + __c2: type[B], + __c3: type[C], + __c4: type[D], /, ) -> tuple[A | None, B | None, C | None, D | None]: ... @overload - def get_components_of_entity( + def get_components_of_entity[ + A: Component, + B: Component, + C: Component, + D: Component, + E: Component, + ]( self, entity: int, - c1: type[A], - c2: type[B], - c3: type[C], - c4: type[D], - c5: type[E], + __c1: type[A], + __c2: type[B], + __c3: type[C], + __c4: type[D], + __c5: type[E], /, ) -> tuple[A | None, B | None, C | None, D | None, E | None]: ... @@ -176,18 +183,20 @@ class System: ), ) - def get_component(self, component_type: type[C], /) -> Iterable[tuple[int, C]]: + def get_component[C: Component]( + self, component_type: type[C], / + ) -> Iterable[tuple[int, C]]: """Returns all entities with the given component.""" for entity in self.__components[component_type].copy(): yield entity, cast(C, self.__entitites[entity][component_type]) @overload - def get_component_of_entity( + def get_component_of_entity[C: Component]( self, entity: int, component_type: type[C], / ) -> C | None: ... @overload - def get_component_of_entity( + def get_component_of_entity[C: Component, D: Any]( self, entity: int, component_type: type[C], /, default: D ) -> C | D: ... @@ -207,16 +216,24 @@ class System: self.__components[component_type].add(entity) self.__entitites[entity][component_type] = component - def has_component(self, entity: int, component_type: type[Component]) -> bool: + def has_component( + self, entity: int, component_type: type[Component] + ) -> bool: """Returns True if the entity has the given component.""" return component_type in self.__entitites[entity] - def has_components(self, entity: int, *component_types: type[Component]) -> bool: + def has_components( + self, entity: int, *component_types: type[Component] + ) -> bool: """Returns True if the entity has all the given components.""" components_dict = self.__entitites[entity] - return all(comp_type in components_dict for comp_type in component_types) + return all( + comp_type in components_dict for comp_type in component_types + ) - def remove_component(self, entity: int, component_type: type[C]) -> C | None: + def remove_component[C: Component]( + self, entity: int, component_type: type[C] + ) -> C | None: """Removes a component from an entity.""" self.__components[component_type].discard(entity) if not self.__components[component_type]: @@ -248,7 +265,9 @@ class System: def entity_exists(self, entity: int) -> bool: """Returns True if the entity exists.""" - return entity in self.__entitites and entity not in self.__dead_entities + return ( + entity in self.__entitites and entity not in self.__dead_entities + ) def start(self) -> None: """Starts the system.""" diff --git a/src/snakia/core/engine.py b/src/snakia/core/engine.py index a4f47b2..78c8120 100644 --- a/src/snakia/core/engine.py +++ b/src/snakia/core/engine.py @@ -3,19 +3,14 @@ from typing import Final from .ecs import System from .es import Dispatcher +from .loader.loader import Loader class Engine: - __slots__ = ( - "system", - "dispatcher", - "__system_thread", - "__dispatcher_thread", - ) - def __init__(self) -> None: self.system: Final = System() self.dispatcher: Final = Dispatcher() + self.loader: Final = Loader(self) self.__system_thread: threading.Thread | None = None self.__dispatcher_thread: threading.Thread | None = None diff --git a/src/snakia/core/es/action.py b/src/snakia/core/es/action.py index 742be4f..0f805a1 100644 --- a/src/snakia/core/es/action.py +++ b/src/snakia/core/es/action.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Self + from pydantic import BaseModel, Field @@ -7,27 +9,27 @@ class Action(BaseModel): move: int = Field(default=1) @classmethod - def stop(cls) -> Action: + def stop(cls) -> Self: """Skip all handlers.""" return cls(move=2**8) @classmethod - def go_start(cls) -> Action: + def go_start(cls) -> Self: """Go to the first handler.""" return cls(move=-(2**8)) @classmethod - def next(cls, count: int = 1) -> Action: + def next(cls, count: int = 1) -> Self: """Skip one handler.""" return cls(move=count) @classmethod - def prev(cls, count: int = 1) -> Action: + def prev(cls, count: int = 1) -> Self: """Go back one handler.""" return cls(move=-count) @classmethod - def skip(cls, count: int = 1) -> Action: + def skip(cls, count: int = 1) -> Self: """Skip n handlers. Args: diff --git a/src/snakia/core/es/dispatcher.py b/src/snakia/core/es/dispatcher.py index 73aeea4..9874f41 100644 --- a/src/snakia/core/es/dispatcher.py +++ b/src/snakia/core/es/dispatcher.py @@ -2,7 +2,7 @@ from __future__ import annotations import queue from collections import defaultdict -from typing import Callable, Final, TypeVar +from typing import Callable, Final from snakia.utils import nolock @@ -11,33 +11,35 @@ from .filter import Filter from .handler import Handler from .subscriber import Subscriber -T = TypeVar("T", bound=Event) - class Dispatcher: """ Event dispatcher """ - __slots__ = ("__queue", "__subscribers", "__running") + __running: bool def __init__(self) -> None: self.__queue: Final = queue.Queue[Event]() - self.__subscribers: Final[dict[type[Event], list[Subscriber[Event]]]] = ( - defaultdict(list) - ) - self.__running: bool = False + self.__subscribers: Final[ + dict[type[Event], list[Subscriber[Event]]] + ] = defaultdict(list) + self.__running = False @property def is_running(self) -> bool: """Returns True if the dispatcher is running.""" return self.__running - def subscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None: + def subscribe[T: Event]( + self, event_type: type[T], subscriber: Subscriber[T] + ) -> None: """Subscribe to an event type.""" self.__subscribers[event_type].append(subscriber) # type: ignore - def unsubscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None: + def unsubscribe[T: Event]( + self, event_type: type[T], subscriber: Subscriber[T] + ) -> None: """Unsubscribe from an event type.""" for sub in self.__subscribers[event_type].copy(): if sub.handler != subscriber.handler: @@ -46,7 +48,7 @@ class Dispatcher: continue self.__subscribers[event_type].remove(sub) - def on( + def on[T: Event]( self, event: type[T], filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622 @@ -93,7 +95,9 @@ class Dispatcher: i = 0 while i < len(subscribers): subscriber = subscribers[i] - if subscriber.filters is not None and not subscriber.filters(event): + if subscriber.filters is not None and not subscriber.filters( + event + ): continue action = subscriber.handler(event) diff --git a/src/snakia/core/es/filter.py b/src/snakia/core/es/filter.py index 32108cc..41ea6e7 100644 --- a/src/snakia/core/es/filter.py +++ b/src/snakia/core/es/filter.py @@ -1,13 +1,11 @@ from __future__ import annotations -from typing import Generic, Protocol, TypeVar +from typing import Protocol from .event import Event -T_contra = TypeVar("T_contra", bound=Event, contravariant=True) - -class Filter(Protocol, Generic[T_contra]): +class Filter[T: Event](Protocol): """Filter for an event.""" - def __call__(self, event: T_contra) -> bool: ... + def __call__(self, event: T) -> bool: ... diff --git a/src/snakia/core/es/handler.py b/src/snakia/core/es/handler.py index d968947..6cdb1ba 100644 --- a/src/snakia/core/es/handler.py +++ b/src/snakia/core/es/handler.py @@ -1,14 +1,12 @@ from __future__ import annotations -from typing import Generic, Optional, Protocol, TypeVar +from typing import Optional, Protocol from .action import Action from .event import Event -T_contra = TypeVar("T_contra", bound=Event, contravariant=True) - -class Handler(Protocol, Generic[T_contra]): +class Handler[T: Event](Protocol): """Handler for an event.""" - def __call__(self, event: T_contra) -> Optional[Action]: ... + def __call__(self, event: T) -> Optional[Action]: ... diff --git a/src/snakia/core/es/subscriber.py b/src/snakia/core/es/subscriber.py index 2044129..6bc9aad 100644 --- a/src/snakia/core/es/subscriber.py +++ b/src/snakia/core/es/subscriber.py @@ -1,20 +1,16 @@ from __future__ import annotations -from typing import Generic, TypeVar - -from typing_extensions import NamedTuple +from typing import NamedTuple from .event import Event from .filter import Filter from .handler import Handler -T_contra = TypeVar("T_contra", bound=Event, contravariant=True) - -class Subscriber(NamedTuple, Generic[T_contra]): +class Subscriber[T: Event](NamedTuple): """ Subscriber for an event.""" - handler: Handler[T_contra] - filters: Filter[T_contra] | None + handler: Handler[T] + filters: Filter[T] | None priority: int diff --git a/src/snakia/core/except_manager.py b/src/snakia/core/except_manager.py index 43693cc..a74d571 100644 --- a/src/snakia/core/except_manager.py +++ b/src/snakia/core/except_manager.py @@ -1,33 +1,27 @@ import sys from types import TracebackType -from typing import Any, Callable, Generic, Protocol, TypeVar, final - -T = TypeVar("T", bound=BaseException) -T_contra = TypeVar("T_contra", contravariant=True) +from typing import Any, Callable, Protocol, final -class ExceptionHook(Protocol, Generic[T_contra]): - +class ExceptionHook[T: BaseException](Protocol): def __call__( - self, exception: T_contra, frame: TracebackType | None, / + self, exception: T, frame: TracebackType | None, / ) -> bool | None: ... @final class _ExceptionManager: - __slots__ = ("__hooks", "excepthook") - def __init__(self) -> None: self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = [] sys.excepthook = self._excepthook - def hook_exception( + def hook_exception[T: BaseException]( self, exception_type: type[T], func: ExceptionHook[T] ) -> ExceptionHook[T]: self.__hooks.append((exception_type, func)) return func - def on_exception( + def on_exception[T: BaseException]( self, exception_type: type[T] ) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]: def inner(func: ExceptionHook[T]) -> ExceptionHook[T]: diff --git a/src/snakia/core/loader/__init__.py b/src/snakia/core/loader/__init__.py new file mode 100644 index 0000000..9e099c5 --- /dev/null +++ b/src/snakia/core/loader/__init__.py @@ -0,0 +1,6 @@ +from .loadable import Loadable +from .meta import Meta +from .plugin import Plugin +from .plugin_processor import PluginProcessor + +__all__ = ["Loadable", "Meta", "Plugin", "PluginProcessor"] diff --git a/src/snakia/core/loader/loadable.py b/src/snakia/core/loader/loadable.py new file mode 100644 index 0000000..51e75d0 --- /dev/null +++ b/src/snakia/core/loader/loadable.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from snakia.core.engine import Engine + + +class Loadable(ABC): + @abstractmethod + def __init__(self, engine: Engine) -> None: ... + + @abstractmethod + def load(self) -> None: ... + + @abstractmethod + def unload(self) -> None: ... diff --git a/src/snakia/core/loader/loader.py b/src/snakia/core/loader/loader.py new file mode 100644 index 0000000..280322d --- /dev/null +++ b/src/snakia/core/loader/loader.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Final + +if TYPE_CHECKING: + from snakia.core.engine import Engine + from snakia.core.loader import Loadable + + +class Loader: + def __init__(self, engine: Engine) -> None: + self.__engine: Final = engine + self.__loadables: Final[list[Loadable]] = [] + + def register(self, loadable: Callable[[Engine], Loadable]) -> None: + self.__loadables.append(loadable(self.__engine)) + + def load_all(self) -> None: + for loadable in self.__loadables: + loadable.load() + + def unload_all(self) -> None: + for loadable in reversed(self.__loadables): + loadable.unload() diff --git a/src/snakia/core/loader/meta.py b/src/snakia/core/loader/meta.py new file mode 100644 index 0000000..f0b66d3 --- /dev/null +++ b/src/snakia/core/loader/meta.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from snakia.core.es import Event, Subscriber +from snakia.types import Version + +from .plugin_processor import PluginProcessor + + +class Meta(BaseModel): + model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) + + name: str = Field( + default="unknown", + min_length=4, + max_length=32, + pattern="^[a-z0-9_]{4,32}$", + ) + author: str = Field( + default="unknown", + min_length=4, + max_length=32, + pattern="^[a-z0-9_]{4,32}$", + ) + version: Version = Field( + default_factory=lambda: Version(major=1, minor=0, patch=0) + ) + + subscribers: tuple[tuple[type[Event], Subscriber[Event]], ...] = Field( + default_factory=tuple + ) + processors: tuple[type[PluginProcessor], ...] = Field( + default_factory=tuple + ) + + @property + def id(self) -> str: + return f"{self.author}.{self.name}" diff --git a/src/snakia/core/loader/plugin.py b/src/snakia/core/loader/plugin.py new file mode 100644 index 0000000..f8abfa5 --- /dev/null +++ b/src/snakia/core/loader/plugin.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, ClassVar, Final, final + +from snakia.core.ecs import System +from snakia.core.es import Dispatcher + +from .loadable import Loadable +from .meta import Meta + +if TYPE_CHECKING: + from snakia.core.engine import Engine + + +class Plugin(Loadable): + __meta: ClassVar[Meta] + + @final + def __init__(self, engine: Engine) -> None: + self.__engine: Final = engine + + @final + @property + def meta(self) -> Meta: + """The plugin's metadata.""" + return self.__meta + + @final + @property + def dispatcher(self) -> Dispatcher: + return self.__engine.dispatcher + + @final + @property + def system(self) -> System: + return self.__engine.system + + @final + def load(self) -> None: + for processor in self.meta.processors: + self.__engine.system.add_processor(processor(self)) + for event_type, subscriber in self.meta.subscribers: + self.__engine.dispatcher.subscribe(event_type, subscriber) + self.on_load() + + @final + def unload(self) -> None: + for processor in self.meta.processors: + self.__engine.system.remove_processor(processor) + for event_type, subscriber in self.meta.subscribers: + self.__engine.dispatcher.unsubscribe(event_type, subscriber) + self.on_unload() + + @abstractmethod + def on_load(self) -> None: + pass + + @abstractmethod + def on_unload(self) -> None: + pass + + if TYPE_CHECKING: + + @final + def __init_subclass__(cls, meta: Meta) -> None: + pass + + else: + + def __init_subclass__(cls, meta: Meta) -> None: + cls.meta = meta diff --git a/src/snakia/core/loader/plugin_processor.py b/src/snakia/core/loader/plugin_processor.py new file mode 100644 index 0000000..f959327 --- /dev/null +++ b/src/snakia/core/loader/plugin_processor.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Final, final + +from snakia.core.ecs import Processor + +if TYPE_CHECKING: + from .plugin import Plugin + + +class PluginProcessor(Processor): + @final + def __init__(self, plugin: Plugin) -> None: + self.plugin: Final = plugin diff --git a/src/snakia/core/rx/__init__.py b/src/snakia/core/rx/__init__.py index 3c6bf2d..995ffa5 100644 --- a/src/snakia/core/rx/__init__.py +++ b/src/snakia/core/rx/__init__.py @@ -1,14 +1,13 @@ from .async_bindable import AsyncBindable from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged from .bindable import Bindable -from .chains import async_chain, chain -from .combines import async_combine, combine -from .concats import concat -from .conds import cond -from .consts import const -from .filters import filter # noqa: W0622 # pylint: disable=W0622 -from .maps import map # noqa: W0622 # pylint: disable=W0622 -from .merges import async_merge, merge +from .chain import chain +from .combine import combine +from .concat import concat +from .const import const +from .filter import filter # noqa: W0622 # pylint: disable=W0622 +from .map import map # noqa: W0622 # pylint: disable=W0622 +from .merge import async_merge, merge __all__ = [ "Bindable", @@ -16,15 +15,12 @@ __all__ = [ "BaseBindable", "BindableSubscriber", "ValueChanged", - "async_chain", - "async_combine", - "async_merge", "chain", "combine", "concat", - "cond", "const", "filter", "map", "merge", + "async_merge", ] diff --git a/src/snakia/core/rx/async_bindable.py b/src/snakia/core/rx/async_bindable.py index 04b153d..178ba18 100644 --- a/src/snakia/core/rx/async_bindable.py +++ b/src/snakia/core/rx/async_bindable.py @@ -1,11 +1,9 @@ -from typing import Any, Awaitable, Callable, Generic, Literal, TypeVar, overload +from typing import Any, Awaitable, Callable, Literal, overload from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged -T = TypeVar("T") - -class AsyncBindable(BaseBindable[T], Generic[T]): +class AsyncBindable[T: Any](BaseBindable[T]): """ An asynchronous bindable. """ @@ -14,6 +12,10 @@ class AsyncBindable(BaseBindable[T], Generic[T]): super().__init__(default_value) self.__subscribers: list[BindableSubscriber[T, Awaitable[Any]]] = [] + @property + def value(self) -> T: + return self.__value + @property def subscribers( self, @@ -23,11 +25,8 @@ class AsyncBindable(BaseBindable[T], Generic[T]): async def set(self, value: T) -> None: """Set the value.""" - if not (self.has_default_value or self.has_value): - self.set_silent(value) - return - e = ValueChanged(self.value, value) - self.set_silent(value) + e = ValueChanged(self.__value, value) + self.__value = value for subscriber in self.__subscribers: await subscriber(e) @@ -58,19 +57,25 @@ class AsyncBindable(BaseBindable[T], Generic[T]): if run_now: async def _run() -> None: - await subscriber(ValueChanged(self.__default_value, self.value)) + await subscriber( + ValueChanged(self.__default_value, self.__value) + ) return _run() return None - def unsubscribe(self, subscriber: BindableSubscriber[T, Awaitable[Any]]) -> None: + def unsubscribe( + self, subscriber: BindableSubscriber[T, Awaitable[Any]] + ) -> None: """Unsubscribe from an value.""" self.__subscribers.remove(subscriber) @overload def on( self, run_now: Literal[True] - ) -> Callable[[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]]: ... + ) -> Callable[ + [BindableSubscriber[T, Awaitable[Any]]], Awaitable[None] + ]: ... @overload def on( @@ -90,7 +95,9 @@ class AsyncBindable(BaseBindable[T], Generic[T]): if run_now: async def _run() -> None: - await subscriber(ValueChanged(self.__default_value, self.value)) + await subscriber( + ValueChanged(self.__default_value, self.__value) + ) return _run() return None diff --git a/src/snakia/core/rx/base_bindable.py b/src/snakia/core/rx/base_bindable.py index bd8c73b..58c557d 100644 --- a/src/snakia/core/rx/base_bindable.py +++ b/src/snakia/core/rx/base_bindable.py @@ -1,21 +1,16 @@ -from typing import Generic, Protocol, TypeVar - -from typing_extensions import NamedTuple - -T = TypeVar("T") -R_co = TypeVar("R_co", covariant=True) +from typing import Any, NamedTuple, Protocol -class ValueChanged(NamedTuple, Generic[T]): +class ValueChanged[T](NamedTuple): old_value: T new_value: T -class BindableSubscriber(Protocol, Generic[T, R_co]): - def __call__(self, value: ValueChanged[T], /) -> R_co: ... +class BindableSubscriber[T: Any, R: Any](Protocol): + def __call__(self, value: ValueChanged[T], /) -> R: ... -class BaseBindable(Generic[T]): +class BaseBindable[T: Any]: def __init__(self, default_value: T | None = None) -> None: if default_value is not None: self.__default_value: T = default_value @@ -27,25 +22,7 @@ class BaseBindable(Generic[T]): @property def value(self) -> T: - if self.has_value: - return self.__value - return self.default_value - - @property - def has_value(self) -> bool: - try: - _ = self.__value - return True - except AttributeError: - return False - - @property - def has_default_value(self) -> bool: - try: - _ = self.__default_value - return True - except AttributeError: - return False + return self.__value def set_silent(self, value: T) -> None: self.__value = value diff --git a/src/snakia/core/rx/bindable.py b/src/snakia/core/rx/bindable.py index 7896463..be66139 100644 --- a/src/snakia/core/rx/bindable.py +++ b/src/snakia/core/rx/bindable.py @@ -1,11 +1,9 @@ -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Callable from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged -T = TypeVar("T") - -class Bindable(BaseBindable[T], Generic[T]): +class Bindable[T: Any](BaseBindable[T]): """ A bindable value. """ @@ -21,10 +19,7 @@ class Bindable(BaseBindable[T], Generic[T]): def set(self, value: T) -> None: """Set the value.""" - if not (self.has_default_value or self.has_value): - self.set_silent(value) - return - e = ValueChanged(self.value, value) + e = ValueChanged(self.__value, value) self.set_silent(value) for subscriber in self.__subscribers: subscriber(e) @@ -41,7 +36,9 @@ class Bindable(BaseBindable[T], Generic[T]): """Unsubscribe from an value.""" self.__subscribers.remove(subscriber) - def on(self, run_now: bool = False) -> Callable[[BindableSubscriber[T, Any]], None]: + def on( + self, run_now: bool = False + ) -> Callable[[BindableSubscriber[T, Any]], None]: """Decorator to subscribe to an value.""" def wrapper(subscriber: BindableSubscriber[T, Any]) -> None: diff --git a/src/snakia/core/rx/chain.py b/src/snakia/core/rx/chain.py new file mode 100644 index 0000000..81ce12a --- /dev/null +++ b/src/snakia/core/rx/chain.py @@ -0,0 +1,53 @@ +from typing import Any, Callable, overload + + +@overload +def chain[**P, A](func1: Callable[P, A], /) -> Callable[P, A]: ... + + +@overload +def chain[**P, A, B]( + func1: Callable[P, A], func2: Callable[[A], B], / +) -> Callable[P, B]: ... +@overload +def chain[**P, A, B, C]( + func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], / +) -> Callable[P, C]: ... +@overload +def chain[**P, A, B, C, D]( + func1: Callable[P, A], + func2: Callable[[A], B], + func3: Callable[[B], C], + func4: Callable[[C], D], + /, +) -> Callable[P, D]: ... + + +@overload +def chain[**P, A, B, C, D, E]( + func1: Callable[P, A], + func2: Callable[[A], B], + func3: Callable[[B], C], + func4: Callable[[C], D], + func5: Callable[[D], E], + /, +) -> Callable[P, E]: ... + + +@overload +def chain[**P]( + func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] +) -> Callable[P, Any]: ... + + +def chain[**P]( + func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] +) -> Callable[P, Any]: + + def inner(*args: P.args, **kwargs: P.kwargs) -> Any: + v = func1(*args, **kwargs) + for f in funcs: + v = f(v) + return v + + return inner diff --git a/src/snakia/core/rx/chains.py b/src/snakia/core/rx/chains.py deleted file mode 100644 index 8b9ef71..0000000 --- a/src/snakia/core/rx/chains.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import Any, Awaitable, Callable, ParamSpec, TypeVar, overload - -P = ParamSpec("P") - -A = TypeVar("A") -B = TypeVar("B") -C = TypeVar("C") -D = TypeVar("D") -E = TypeVar("E") - - -@overload -def chain(func1: Callable[P, A], /) -> Callable[P, A]: ... -@overload -def chain(func1: Callable[P, A], func2: Callable[[A], B], /) -> Callable[P, B]: ... -@overload -def chain( - func1: Callable[P, A], func2: Callable[[A], B], func3: Callable[[B], C], / -) -> Callable[P, C]: ... -@overload -def chain( - func1: Callable[P, A], - func2: Callable[[A], B], - func3: Callable[[B], C], - func4: Callable[[C], D], - /, -) -> Callable[P, D]: ... -@overload -def chain( - func1: Callable[P, A], - func2: Callable[[A], B], - func3: Callable[[B], C], - func4: Callable[[C], D], - func5: Callable[[D], E], - /, -) -> Callable[P, E]: ... -@overload -def chain( - func1: Callable[P, Any], /, *funcs: Callable[[Any], Any] -) -> Callable[P, Any]: ... -def chain(func1: Callable[P, Any], /, *funcs: Callable[[Any], Any]) -> Callable[P, Any]: - - def inner(*args: P.args, **kwargs: P.kwargs) -> Any: - v = func1(*args, **kwargs) - for f in funcs: - v = f(v) - return v - - return inner - - -@overload -def async_chain(func1: Callable[P, Awaitable[A]], /) -> Callable[P, Awaitable[A]]: ... -@overload -def async_chain( - func1: Callable[P, Awaitable[A]], func2: Callable[[A], Awaitable[B]], / -) -> Callable[P, Awaitable[B]]: ... -@overload -def async_chain( - func1: Callable[P, Awaitable[A]], - func2: Callable[[A], Awaitable[B]], - func3: Callable[[B], Awaitable[C]], - /, -) -> Callable[P, Awaitable[C]]: ... -@overload -def async_chain( - func1: Callable[P, Awaitable[A]], - func2: Callable[[A], Awaitable[B]], - func3: Callable[[B], Awaitable[C]], - func4: Callable[[C], Awaitable[D]], - /, -) -> Callable[P, Awaitable[D]]: ... -@overload -def async_chain( - func1: Callable[P, Awaitable[A]], - func2: Callable[[A], Awaitable[B]], - func3: Callable[[B], Awaitable[C]], - func4: Callable[[C], Awaitable[D]], - func5: Callable[[D], Awaitable[E]], - /, -) -> Callable[P, Awaitable[E]]: ... -@overload -def async_chain( - func1: Callable[P, Any], /, *funcs: Callable[[Any], Awaitable[Any]] -) -> Callable[P, Awaitable[Any]]: ... -def async_chain( - func1: Callable[P, Awaitable[Any]], - /, - *funcs: Callable[[Any], Awaitable[Any]], -) -> Callable[P, Awaitable[Any]]: - - async def inner(*args: P.args, **kwargs: P.kwargs) -> Any: - v = await func1(*args, **kwargs) - for f in funcs: - v = await f(v) - return v - - return inner diff --git a/src/snakia/core/rx/combine.py b/src/snakia/core/rx/combine.py new file mode 100644 index 0000000..0cc5b1a --- /dev/null +++ b/src/snakia/core/rx/combine.py @@ -0,0 +1,94 @@ +import operator +from typing import Any, Callable, overload + +from snakia.utils import to_async + +from .async_bindable import AsyncBindable +from .base_bindable import ValueChanged +from .bindable import Bindable +from .concat import concat + + +@overload +def combine[A, R]( + source1: Bindable[A] | AsyncBindable[A], + /, + *, + combiner: Callable[[A], R], +) -> Bindable[R]: ... + + +@overload +def combine[A, B, R]( + source1: Bindable[A] | AsyncBindable[A], + source2: Bindable[B] | AsyncBindable[B], + /, + *, + combiner: Callable[[A, B], R], +) -> Bindable[R]: ... + + +@overload +def combine[A, B, C, R]( + source1: Bindable[A] | AsyncBindable[A], + source2: Bindable[B] | AsyncBindable[B], + source3: Bindable[C] | AsyncBindable[C], + /, + *, + combiner: Callable[[A, B, C], R], +) -> Bindable[R]: ... + + +@overload +def combine[A, B, C, D, R]( + source1: Bindable[A] | AsyncBindable[A], + source2: Bindable[B] | AsyncBindable[B], + source3: Bindable[C] | AsyncBindable[C], + source4: Bindable[D] | AsyncBindable[D], + /, + *, + combiner: Callable[[A, B, C, D], R], +) -> Bindable[R]: ... + + +@overload +def combine[A, B, C, D, R]( + source1: Bindable[A] | AsyncBindable[A], + source2: Bindable[B] | AsyncBindable[B], + source3: Bindable[C] | AsyncBindable[C], + source4: Bindable[D] | AsyncBindable[D], + /, + *, + combiner: Callable[[A, B, C, D], R], +) -> Bindable[R]: ... + + +@overload +def combine[R]( + *sources: Bindable[Any] | AsyncBindable[Any], + combiner: Callable[..., R], +) -> Bindable[R]: ... + + +def combine[R]( + *sources: Bindable[Any] | AsyncBindable[Any], + combiner: Callable[..., R], +) -> Bindable[R]: + combined = Bindable[R]() + values = [*map(lambda s: s.value, sources)] + + for i, source in enumerate(sources): + + def make_subscriber( + index: int, + ) -> Callable[[ValueChanged[Any]], None]: + return concat( + lambda v: operator.setitem(values, index, v.new_value), + lambda _: combiner(*values), + ) + + if isinstance(source, Bindable): + source.subscribe(make_subscriber(i)) + else: + source.subscribe(to_async(make_subscriber(i))) + return combined diff --git a/src/snakia/core/rx/combines.py b/src/snakia/core/rx/combines.py deleted file mode 100644 index 23c6675..0000000 --- a/src/snakia/core/rx/combines.py +++ /dev/null @@ -1,193 +0,0 @@ -from typing import Any, Awaitable, Callable, TypeVar, overload - -from snakia.types import Unset -from snakia.utils import caller, to_async - -from .async_bindable import AsyncBindable -from .base_bindable import ValueChanged -from .bindable import Bindable - -A = TypeVar("A") -B = TypeVar("B") -C = TypeVar("C") -D = TypeVar("D") -E = TypeVar("E") -R = TypeVar("R") - - -@overload -def combine( - source1: Bindable[A] | AsyncBindable[A], - /, - *, - combiner: Callable[[A], R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -@overload -def combine( - source1: Bindable[A] | AsyncBindable[A], - source2: Bindable[B] | AsyncBindable[B], - /, - *, - combiner: Callable[[A, B], R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -@overload -def combine( - source1: Bindable[A] | AsyncBindable[A], - source2: Bindable[B] | AsyncBindable[B], - source3: Bindable[C] | AsyncBindable[C], - /, - *, - combiner: Callable[[A, B, C], R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -@overload -def combine( - source1: Bindable[A] | AsyncBindable[A], - source2: Bindable[B] | AsyncBindable[B], - source3: Bindable[C] | AsyncBindable[C], - source4: Bindable[D] | AsyncBindable[D], - /, - *, - combiner: Callable[[A, B, C, D], R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -@overload -def combine( - source1: Bindable[A] | AsyncBindable[A], - source2: Bindable[B] | AsyncBindable[B], - source3: Bindable[C] | AsyncBindable[C], - source4: Bindable[D] | AsyncBindable[D], - /, - *, - combiner: Callable[[A, B, C, D], R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -@overload -def combine( - *sources: Bindable[Any] | AsyncBindable[Any], - combiner: Callable[..., R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: ... - - -def combine( - *sources: Bindable[Any] | AsyncBindable[Any], - combiner: Callable[..., R], - default_value: R | Unset = Unset(), -) -> Bindable[R]: - combined = Bindable[R]() - Unset.map( - default_value, - combined.set_silent, - caller(combined.set_silent, sources[0].default_value), - ) - - def subscriber(_: ValueChanged[Any]) -> None: - combined.set(combiner(*map(lambda s: s.value, sources))) - - for source in sources: - if isinstance(source, Bindable): - source.subscribe(subscriber) - else: - source.subscribe(to_async(subscriber)) - return combined - - -@overload -def async_combine( - source1: AsyncBindable[A], - /, - *, - combiner: Callable[[A], Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -@overload -def async_combine( - source1: AsyncBindable[A], - source2: AsyncBindable[B], - /, - *, - combiner: Callable[[A, B], Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -@overload -def async_combine( - source1: AsyncBindable[A], - source2: AsyncBindable[B], - source3: AsyncBindable[C], - /, - *, - combiner: Callable[[A, B, C], Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -@overload -def async_combine( - source1: AsyncBindable[A], - source2: AsyncBindable[B], - source3: AsyncBindable[C], - source4: AsyncBindable[D], - /, - *, - combiner: Callable[[A, B, C, D], Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -@overload -def async_combine( - source1: AsyncBindable[A], - source2: AsyncBindable[B], - source3: AsyncBindable[C], - source4: AsyncBindable[D], - /, - *, - combiner: Callable[[A, B, C, D], Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -@overload -def async_combine( - *sources: AsyncBindable[Any], - combiner: Callable[..., Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: ... - - -def async_combine( - *sources: AsyncBindable[Any], - combiner: Callable[..., Awaitable[R]], - default_value: R | Unset = Unset(), -) -> AsyncBindable[R]: - combined = AsyncBindable[R]() - Unset.map( - default_value, - combined.set_silent, - caller(combined.set_silent, sources[0].default_value), - ) - - async def subscriber(_: ValueChanged[Any]) -> None: - result = await combiner(*map(lambda s: s.value, sources)) - await combined.set(result) - - for source in sources: - source.subscribe(subscriber) - return combined diff --git a/src/snakia/core/rx/concats.py b/src/snakia/core/rx/concat.py similarity index 51% rename from src/snakia/core/rx/concats.py rename to src/snakia/core/rx/concat.py index 6a15ab3..d96cec1 100644 --- a/src/snakia/core/rx/concats.py +++ b/src/snakia/core/rx/concat.py @@ -1,9 +1,7 @@ -from typing import Any, Callable, ParamSpec - -P = ParamSpec("P") +from typing import Any, Callable -def concat(*funcs: Callable[P, Any]) -> Callable[P, None]: +def concat[**P](*funcs: Callable[P, Any]) -> Callable[P, None]: def inner(*args: P.args, **kwargs: P.kwargs) -> None: for f in funcs: f(*args, **kwargs) diff --git a/src/snakia/core/rx/conds.py b/src/snakia/core/rx/conds.py deleted file mode 100644 index 8429943..0000000 --- a/src/snakia/core/rx/conds.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Callable, ParamSpec, TypeVar - -P = ParamSpec("P") -T = TypeVar("T") -F = TypeVar("F") - - -def cond( - condition: Callable[P, bool], - if_true: Callable[P, T], - if_false: Callable[P, F], -) -> Callable[P, T | F]: - return lambda *args, **kw: ( - if_true(*args, **kw) if condition(*args, **kw) else if_false(*args, **kw) - ) diff --git a/src/snakia/core/rx/const.py b/src/snakia/core/rx/const.py new file mode 100644 index 0000000..f0781d9 --- /dev/null +++ b/src/snakia/core/rx/const.py @@ -0,0 +1,5 @@ +from typing import Callable + + +def const[T](value: T) -> Callable[[], T]: + return lambda: value diff --git a/src/snakia/core/rx/consts.py b/src/snakia/core/rx/consts.py deleted file mode 100644 index 7e0a4ea..0000000 --- a/src/snakia/core/rx/consts.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Callable, TypeVar - -T = TypeVar("T") - - -def const(value: T) -> Callable[[], T]: - return lambda: value diff --git a/src/snakia/core/rx/filters.py b/src/snakia/core/rx/filter.py similarity index 64% rename from src/snakia/core/rx/filters.py rename to src/snakia/core/rx/filter.py index 5bb761c..f7ab74c 100644 --- a/src/snakia/core/rx/filters.py +++ b/src/snakia/core/rx/filter.py @@ -1,12 +1,9 @@ import builtins -from typing import Callable, Iterable, TypeGuard, TypeVar - -S = TypeVar("S") -T = TypeVar("T") +from typing import Callable, Iterable, TypeGuard # noqa: W0622 # pylint: disable=W0622 -def filter( +def filter[S, T]( f: Callable[[S], TypeGuard[T]], ) -> Callable[[Iterable[S]], Iterable[T]]: return lambda iterable: builtins.filter(f, iterable) diff --git a/src/snakia/core/rx/map.py b/src/snakia/core/rx/map.py new file mode 100644 index 0000000..86d7bf1 --- /dev/null +++ b/src/snakia/core/rx/map.py @@ -0,0 +1,9 @@ +import builtins +from typing import Any, Callable, Iterable + + +# noqa: W0622 # pylint: disable=W0622 +def map[T: Any, U]( + func: Callable[[T], U], / +) -> Callable[[Iterable[T]], Iterable[U]]: + return lambda iterable: builtins.map(func, iterable) diff --git a/src/snakia/core/rx/maps.py b/src/snakia/core/rx/maps.py deleted file mode 100644 index c0a4fb9..0000000 --- a/src/snakia/core/rx/maps.py +++ /dev/null @@ -1,10 +0,0 @@ -import builtins -from typing import Callable, Iterable, TypeVar - -T = TypeVar("T") -U = TypeVar("U") - - -# noqa: W0622 # pylint: disable=W0622 -def map(func: Callable[[T], U], /) -> Callable[[Iterable[T]], Iterable[U]]: - return lambda iterable: builtins.map(func, iterable) diff --git a/src/snakia/core/rx/merges.py b/src/snakia/core/rx/merge.py similarity index 85% rename from src/snakia/core/rx/merges.py rename to src/snakia/core/rx/merge.py index 9bf32f5..fb5118e 100644 --- a/src/snakia/core/rx/merges.py +++ b/src/snakia/core/rx/merge.py @@ -1,12 +1,8 @@ -from typing import TypeVar - from .async_bindable import AsyncBindable from .bindable import Bindable -T = TypeVar("T") - -def merge( +def merge[T]( *sources: Bindable[T], ) -> Bindable[T]: merged = Bindable[T]() @@ -15,7 +11,7 @@ def merge( return merged -async def async_merge( +async def async_merge[T]( *sources: AsyncBindable[T], ) -> AsyncBindable[T]: merged = AsyncBindable[T]() diff --git a/src/snakia/core/tui/canvas.py b/src/snakia/core/tui/canvas.py index 11f6ff4..c44a99a 100644 --- a/src/snakia/core/tui/canvas.py +++ b/src/snakia/core/tui/canvas.py @@ -40,7 +40,9 @@ class Canvas: def get_column(self, x: int, /) -> Iterable[CanvasChar]: """Get the column at the given position.""" - return (self.__buffer[self._get_index(x, y)] for y in range(self.height)) + return ( + self.__buffer[self._get_index(x, y)] for y in range(self.height) + ) def set(self, x: int, y: int, value: CanvasChar, /) -> None: """Set the character at the given position.""" @@ -66,7 +68,9 @@ class Canvas: value: CanvasChar, ) -> None: """Set the area at the given position.""" - for i in range(self._get_index(x, y), self._get_index(x + width, y + height)): + for i in range( + self._get_index(x, y), self._get_index(x + width, y + height) + ): self.__buffer[i] = value def clear(self) -> None: diff --git a/src/snakia/core/tui/widget.py b/src/snakia/core/tui/widget.py index 3a86c1d..b64a263 100644 --- a/src/snakia/core/tui/widget.py +++ b/src/snakia/core/tui/widget.py @@ -1,13 +1,11 @@ from abc import ABC, abstractmethod -from typing import Final, TypeVar, final +from typing import Final, final from snakia.core.rx import AsyncBindable, Bindable from snakia.utils import to_async from .canvas import Canvas -T = TypeVar("T") - class Widget(ABC): def __init__(self) -> None: @@ -26,13 +24,13 @@ class Widget(ABC): return self.__cache @final - def state(self, default_value: T) -> Bindable[T]: + def state[T](self, default_value: T) -> Bindable[T]: field = Bindable(default_value) field.subscribe(lambda _: self.dirty.set(True)) return field @final - def async_state(self, default_value: T) -> AsyncBindable[T]: + def async_state[T](self, default_value: T) -> AsyncBindable[T]: field = AsyncBindable(default_value) field.subscribe(to_async(lambda _: self.dirty.set(True))) return field diff --git a/src/snakia/core/tui/widgets/horizontal_split.py b/src/snakia/core/tui/widgets/horizontal_split.py index 4a3c450..a267625 100644 --- a/src/snakia/core/tui/widgets/horizontal_split.py +++ b/src/snakia/core/tui/widgets/horizontal_split.py @@ -8,7 +8,9 @@ from .container import ContainerWidget class HorizontalSplitWidget(ContainerWidget): - def __init__(self, children: Iterable[Widget], splitter_char: str = "|") -> None: + def __init__( + self, children: Iterable[Widget], splitter_char: str = "|" + ) -> None: super().__init__(children) self.splitter_char = splitter_char @@ -19,7 +21,9 @@ class HorizontalSplitWidget(ContainerWidget): child_canvases = [child.render() for child in children_list] total_width = ( - sum(canvas.width for canvas in child_canvases) + len(child_canvases) - 1 + sum(canvas.width for canvas in child_canvases) + + len(child_canvases) + - 1 ) max_height = max(canvas.height for canvas in child_canvases) diff --git a/src/snakia/core/tui/widgets/vertical_split.py b/src/snakia/core/tui/widgets/vertical_split.py index 66e3686..5cdf08a 100644 --- a/src/snakia/core/tui/widgets/vertical_split.py +++ b/src/snakia/core/tui/widgets/vertical_split.py @@ -8,7 +8,9 @@ from .container import ContainerWidget class VerticalSplitWidget(ContainerWidget): - def __init__(self, children: Iterable[Widget], splitter_char: str = "-") -> None: + def __init__( + self, children: Iterable[Widget], splitter_char: str = "-" + ) -> None: super().__init__(children) self.splitter_char = splitter_char @@ -20,7 +22,9 @@ class VerticalSplitWidget(ContainerWidget): child_canvases = [child.render() for child in children_list] max_width = max(canvas.width for canvas in child_canvases) total_height = ( - sum(canvas.height for canvas in child_canvases) + len(child_canvases) - 1 + sum(canvas.height for canvas in child_canvases) + + len(child_canvases) + - 1 ) result = Canvas(max_width, total_height, CanvasChar()) diff --git a/src/snakia/decorators/__init__.py b/src/snakia/decorators/__init__.py index 5d84da4..afce779 100644 --- a/src/snakia/decorators/__init__.py +++ b/src/snakia/decorators/__init__.py @@ -2,21 +2,17 @@ from .inject_after import after_hook, inject_after from .inject_before import before_hook, inject_before from .inject_const import inject_const from .inject_replace import inject_replace, replace_hook -from .meta_decorators import hook_decorator, inject_decorator, replace_decorator from .pass_exceptions import pass_exceptions from .singleton import singleton __all__ = [ - "after_hook", - "before_hook", - "inject_after", - "inject_before", - "inject_const", - "inject_decorator", "inject_replace", - "hook_decorator", - "pass_exceptions", - "replace_decorator", "replace_hook", + "inject_after", + "after_hook", + "inject_before", + "before_hook", + "inject_const", + "pass_exceptions", "singleton", ] diff --git a/src/snakia/decorators/inject_after.py b/src/snakia/decorators/inject_after.py index d9e397e..c62974f 100644 --- a/src/snakia/decorators/inject_after.py +++ b/src/snakia/decorators/inject_after.py @@ -1,20 +1,18 @@ -from typing import Callable, ParamSpec, TypeVar +from typing import Callable from .inject_replace import inject_replace -P = ParamSpec("P") -T = TypeVar("T") -R = TypeVar("R") - -def inject_after(obj: T, target: Callable[P, R], hook: Callable[[R], R]) -> T: +def inject_after[T: object, **P, R]( + obj: T, target: Callable[P, R], hook: Callable[[R], R] +) -> T: def inner(*args: P.args, **kwargs: P.kwargs) -> R: return hook(target(*args, **kwargs)) return inject_replace(obj, target, inner) -def after_hook( +def after_hook[**P, R]( obj: object, target: Callable[P, R] ) -> Callable[[Callable[[R], R]], Callable[[R], R]]: def hook(new: Callable[[R], R]) -> Callable[[R], R]: diff --git a/src/snakia/decorators/inject_before.py b/src/snakia/decorators/inject_before.py index 769b12a..8f30dc1 100644 --- a/src/snakia/decorators/inject_before.py +++ b/src/snakia/decorators/inject_before.py @@ -1,13 +1,11 @@ -from typing import Any, Callable, ParamSpec, TypeVar +from typing import Any, Callable from .inject_replace import inject_replace -P = ParamSpec("P") -T = TypeVar("T") -R = TypeVar("R") - -def inject_before(obj: T, target: Callable[P, R], hook: Callable[P, Any]) -> T: +def inject_before[T: object, **P, R]( + obj: T, target: Callable[P, R], hook: Callable[P, Any] +) -> T: def inner(*args: P.args, **kwargs: P.kwargs) -> R: hook(*args, **kwargs) return target(*args, **kwargs) @@ -15,7 +13,7 @@ def inject_before(obj: T, target: Callable[P, R], hook: Callable[P, Any]) -> T: return inject_replace(obj, target, inner) -def before_hook( +def before_hook[**P, R]( obj: object, target: Callable[P, R] ) -> Callable[[Callable[P, Any]], Callable[P, Any]]: diff --git a/src/snakia/decorators/inject_const.py b/src/snakia/decorators/inject_const.py index e281360..d6610d8 100644 --- a/src/snakia/decorators/inject_const.py +++ b/src/snakia/decorators/inject_const.py @@ -1,12 +1,10 @@ import sys from types import FunctionType -from typing import Any, Callable, TypeVar, cast - -T = TypeVar("T", bound=Callable[..., Any]) +from typing import Any, Callable, cast if sys.version_info >= (3, 13): - def inject_const(**consts: Any) -> Callable[[T], T]: + def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]: def inner(func: T) -> T: values = [*func.__code__.co_consts] for i, name in enumerate(func.__code__.co_varnames): @@ -28,7 +26,7 @@ if sys.version_info >= (3, 13): else: - def inject_const(**consts: Any) -> Callable[[T], T]: + def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]: def inner(func: T) -> T: values = [*func.__code__.co_consts] for i, name in enumerate(func.__code__.co_varnames): diff --git a/src/snakia/decorators/inject_replace.py b/src/snakia/decorators/inject_replace.py index 4d459c5..d85e445 100644 --- a/src/snakia/decorators/inject_replace.py +++ b/src/snakia/decorators/inject_replace.py @@ -1,18 +1,16 @@ -from typing import Callable, ParamSpec, TypeVar - -P = ParamSpec("P") -T = TypeVar("T") -R = TypeVar("R") +from typing import Callable -def inject_replace(obj: T, old: Callable[P, R], new: Callable[P, R]) -> T: +def inject_replace[T: object, **P, R]( + obj: T, old: Callable[P, R], new: Callable[P, R] +) -> T: for k, v in obj.__dict__.items(): if v is old: setattr(obj, k, new) return obj -def replace_hook( +def replace_hook[**P, R]( obj: object, old: Callable[P, R] ) -> Callable[[Callable[P, R]], Callable[P, R]]: def hook(new: Callable[P, R]) -> Callable[P, R]: diff --git a/src/snakia/decorators/meta_decorators.py b/src/snakia/decorators/meta_decorators.py deleted file mode 100644 index e684d71..0000000 --- a/src/snakia/decorators/meta_decorators.py +++ /dev/null @@ -1,72 +0,0 @@ -import functools -from typing import Callable, Concatenate, ParamSpec, TypeVar - -T = TypeVar("T") -R = TypeVar("R") -D = ParamSpec("D") -P = ParamSpec("P") - - -def inject_decorator( - decorator: Callable[Concatenate[Callable[P, T], D], None], -) -> Callable[D, Callable[[Callable[P, T]], Callable[P, T]]]: - - @functools.wraps(decorator) - def wrapper( - *d_args: D.args, **d_kwargs: D.kwargs - ) -> Callable[[Callable[P, T]], Callable[P, T]]: - def inner(obj: Callable[P, T]) -> Callable[P, T]: - @functools.wraps(obj) - def func(*args: P.args, **kwargs: P.kwargs) -> T: - decorator(obj, *d_args, **d_kwargs) - return obj(*args, **kwargs) - - return func - - return inner - - return wrapper - - -def hook_decorator( - decorator: Callable[Concatenate[Callable[P, T], T, D], T], -) -> Callable[D, Callable[[Callable[P, T]], Callable[P, T]]]: - - @functools.wraps(decorator) - def wrapper( - *d_args: D.args, **d_kwargs: D.kwargs - ) -> Callable[[Callable[P, T]], Callable[P, T]]: - def inner(obj: Callable[P, T]) -> Callable[P, T]: - @functools.wraps(obj) - def func(*args: P.args, **kwargs: P.kwargs) -> T: - val = obj(*args, **kwargs) - return decorator(obj, val, *d_args, **d_kwargs) - - return func - - return inner - - return wrapper - - -def replace_decorator( - decorator: Callable[Concatenate[T, D], T], -) -> Callable[D, Callable[[T], T]]: - @functools.wraps(decorator) - def wrapper(*d_args: D.args, **d_kwargs: D.kwargs) -> Callable[[T], T]: - def inner(obj: T) -> T: - result = decorator(obj, *d_args, **d_kwargs) - if not callable(obj): - return result - for attr in functools.WRAPPER_ASSIGNMENTS: - try: - value = getattr(obj, attr) - except AttributeError: - pass - else: - setattr(result, attr, value) - return result - - return inner - - return wrapper diff --git a/src/snakia/decorators/pass_exceptions.py b/src/snakia/decorators/pass_exceptions.py index 73c9c25..cd31a89 100644 --- a/src/snakia/decorators/pass_exceptions.py +++ b/src/snakia/decorators/pass_exceptions.py @@ -1,17 +1,14 @@ from __future__ import annotations -from typing import Any, Callable, ParamSpec, TypeVar, overload - -P = ParamSpec("P") -R = TypeVar("R") +from typing import Any, Callable, overload @overload -def pass_exceptions( +def pass_exceptions[**P]( *errors: type[Exception], ) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ... @overload -def pass_exceptions( +def pass_exceptions[**P, R]( *errors: type[Exception], default: R, ) -> Callable[[Callable[P, R]], Callable[P, R]]: ... diff --git a/src/snakia/decorators/singleton.py b/src/snakia/decorators/singleton.py index d8b644c..5a1b21a 100644 --- a/src/snakia/decorators/singleton.py +++ b/src/snakia/decorators/singleton.py @@ -1,7 +1,2 @@ -from typing import TypeVar - -T = TypeVar("T") - - -def singleton(cls: type[T]) -> T: +def singleton[T](cls: type[T]) -> T: return cls() diff --git a/src/snakia/field/__init__.py b/src/snakia/field/__init__.py index e95ce46..654fdd9 100644 --- a/src/snakia/field/__init__.py +++ b/src/snakia/field/__init__.py @@ -3,8 +3,6 @@ from .bool import BoolField from .field import Field from .float import FloatField from .int import IntField -from .list import ListField -from .optional import OptionalField from .str import StrField __all__ = [ @@ -13,7 +11,5 @@ __all__ = [ "BoolField", "FloatField", "IntField", - "ListField", - "OptionalField", "StrField", ] diff --git a/src/snakia/field/auto.py b/src/snakia/field/auto.py index f456a1d..2ed3945 100644 --- a/src/snakia/field/auto.py +++ b/src/snakia/field/auto.py @@ -1,52 +1,25 @@ import pickle -from typing import Callable, Final, Generic, TypeVar, overload - -from typing_extensions import Self - -from snakia.types import Unset +from typing import Final, override from .field import Field -T = TypeVar("T") - -class AutoField(Field[T], Generic[T]): +class AutoField[T](Field[T]): __slots__ = ("__target_type",) - @overload def __init__( - self, default_value: T, *, target_type: type[T] | Unset = Unset() - ) -> None: ... - - @overload - def __init__( - self, - *, - default_factory: Callable[[Self], T], - target_type: type[T] | Unset = Unset(), - ) -> None: ... - - def __init__( - self, - default_value: T | Unset = Unset(), - *, - default_factory: Callable[[Self], T] | Unset = Unset(), - target_type: type[T] | Unset = Unset(), + self, default_value: T, *, target_type: type[T] | None = None ) -> None: - if not Unset.itis(default_factory): - super().__init__(default_factory=Unset.unwrap(default_factory)) - elif not Unset.itis(default_value): - super().__init__(Unset.unwrap(default_value)) - else: - super().__init__() - self.__target_type: Final[type] = Unset.unwrap_or(target_type, object) + super().__init__(default_value) + self.__target_type: Final = target_type + @override def serialize(self, value: T, /) -> bytes: return pickle.dumps(value) + @override def deserialize(self, serialized: bytes, /) -> T: value = pickle.loads(serialized) - - if not isinstance(value, self.__target_type): - return self._get_default() + if not isinstance(value, self.__target_type or object): + return self.default_value return value # type: ignore diff --git a/src/snakia/field/bool.py b/src/snakia/field/bool.py index f6637f7..1cad3f8 100644 --- a/src/snakia/field/bool.py +++ b/src/snakia/field/bool.py @@ -1,9 +1,13 @@ +from typing import override + from .field import Field class BoolField(Field[bool]): + @override def serialize(self, value: bool, /) -> bytes: return b"\x01" if value else b"\x00" + @override def deserialize(self, serialized: bytes, /) -> bool: return serialized == b"\x01" diff --git a/src/snakia/field/field.py b/src/snakia/field/field.py index 57e9de6..c59c8ae 100644 --- a/src/snakia/field/field.py +++ b/src/snakia/field/field.py @@ -1,23 +1,17 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Generic, - TypeVar, - final, -) +from typing import TYPE_CHECKING, Any, Callable, Final, final from snakia.property.priv_property import PrivProperty from snakia.utils import inherit -T = TypeVar("T") -R = TypeVar("R") +class Field[T: Any](ABC, PrivProperty[T]): + def __init__(self, default_value: T) -> None: + self.default_value: Final[T] = default_value + super().__init__(default_value) -class Field(ABC, PrivProperty[T], Generic[T]): @abstractmethod def serialize(self, value: T, /) -> bytes: """Serialize a value @@ -40,26 +34,16 @@ class Field(ABC, PrivProperty[T], Generic[T]): @final @classmethod - def custom( + def custom[R]( cls: type[Field[Any]], - serialize: Callable[[Field[R], R], bytes], - deserialize: Callable[[Field[R], bytes], R], + serialize: Callable[[R], str], + deserialize: Callable[[str], R], ) -> type[Field[R]]: return inherit( cls, {"serialize": serialize, "deserialize": deserialize} ) - @final - @staticmethod - def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]: - if not isinstance(class_, type): - class_ = class_.__class__ - return { - k: v for k, v in class_.__dict__.items() if isinstance(v, Field) - } - if TYPE_CHECKING: - @final @classmethod def type(cls) -> type[T]: ... diff --git a/src/snakia/field/float.py b/src/snakia/field/float.py index e25a2ff..10f852a 100644 --- a/src/snakia/field/float.py +++ b/src/snakia/field/float.py @@ -1,11 +1,14 @@ import struct +from typing import override from .field import Field class FloatField(Field[float]): + @override def serialize(self, value: float, /) -> bytes: return struct.pack(">f", value) + @override def deserialize(self, serialized: bytes, /) -> float: return struct.unpack(">f", serialized)[0] # type: ignore diff --git a/src/snakia/field/int.py b/src/snakia/field/int.py index 890f522..ca268aa 100644 --- a/src/snakia/field/int.py +++ b/src/snakia/field/int.py @@ -1,10 +1,14 @@ +from typing import override + from .field import Field class IntField(Field[int]): + @override def serialize(self, value: int, /) -> bytes: length = (value.bit_length() + 7) // 8 return value.to_bytes(length, "little") + @override def deserialize(self, serialized: bytes, /) -> int: return int.from_bytes(serialized, "little") diff --git a/src/snakia/field/list.py b/src/snakia/field/list.py deleted file mode 100644 index f2671da..0000000 --- a/src/snakia/field/list.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Callable, Final, Iterable, TypeVar - -from typing_extensions import Self - -from .field import Field - -T = TypeVar("T") - - -class ListField(Field[list[T]]): - def __init__( - self, - field: Field[T], - *, - length_size: int = 1, - default_factory: Callable[[Self], Iterable[T]] = lambda _: (), - ) -> None: - self.length_size: Final[int] = length_size - self.field: Final = field - super().__init__(default_factory=lambda s: [*default_factory(s)]) - - def serialize(self, items: list[T], /) -> bytes: - result = b"" - for item in items: - value = self.field.serialize(item) - length_prefix = len(value).to_bytes(self.length_size, "big") - result += length_prefix + value - return result - - def deserialize(self, serialized: bytes, /) -> list[T]: - result = [] - while serialized: - length = int.from_bytes(serialized[: self.length_size], "big") - serialized = serialized[self.length_size :] - item = self.field.deserialize(serialized[:length]) - serialized = serialized[length:] - result.append(item) - return result diff --git a/src/snakia/field/optional.py b/src/snakia/field/optional.py deleted file mode 100644 index eb437c0..0000000 --- a/src/snakia/field/optional.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Final, TypeVar - -from .field import Field - -T = TypeVar("T") - - -class OptionalField(Field[T | None]): - - def __init__( - self, - field: Field[T], - *, - none_value: bytes = b"", - ) -> None: - super().__init__(None) - self.none_value: Final = none_value - self.field: Final = field - - def serialize(self, value: T | None, /) -> bytes: - if value is None: - return self.none_value - return self.field.serialize(value) - - def deserialize(self, serialized: bytes, /) -> T | None: - if serialized == self.none_value: - return None - return self.field.deserialize(serialized) diff --git a/src/snakia/field/str.py b/src/snakia/field/str.py index 152a938..1bdacfb 100644 --- a/src/snakia/field/str.py +++ b/src/snakia/field/str.py @@ -1,4 +1,4 @@ -from typing import Final +from typing import Final, override from .field import Field @@ -8,8 +8,10 @@ class StrField(Field[str]): super().__init__(default_value) self.encoding: Final = encoding + @override def serialize(self, value: str, /) -> bytes: return value.encode(self.encoding) + @override def deserialize(self, serialized: bytes, /) -> str: return serialized.decode(self.encoding) diff --git a/src/snakia/field/t.py b/src/snakia/field/t.py index abb404b..f535738 100644 --- a/src/snakia/field/t.py +++ b/src/snakia/field/t.py @@ -4,8 +4,6 @@ from .bool import BoolField as bool from .field import Field as field from .float import FloatField as float from .int import IntField as int -from .list import ListField as list -from .optional import OptionalField as optional from .str import StrField as str __all__ = [ @@ -14,7 +12,5 @@ __all__ = [ "field", "float", "int", - "list", - "optional", "str", ] diff --git a/src/snakia/platform/android.py b/src/snakia/platform/android.py index ab60652..ea9621f 100644 --- a/src/snakia/platform/android.py +++ b/src/snakia/platform/android.py @@ -1,13 +1,11 @@ from __future__ import annotations from ctypes import CDLL, Array, c_char, c_char_p, create_string_buffer -from typing import Any, Final, Literal, TypeVar, cast, overload +from typing import Any, Final, Literal, cast, overload from .layer import PlatformLayer from .os import PlatformOS -T = TypeVar("T") - class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]): target = PlatformOS.ANDROID @@ -18,7 +16,7 @@ class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]): def get_prop(self, name: str) -> str | None: ... @overload - def get_prop(self, name: str, default: T) -> str | T: ... + def get_prop[T](self, name: str, default: T) -> str | T: ... def get_prop(self, name: str, default: Any = None) -> Any: buffer = create_string_buffer(self.PROP_VALUE_MAX) diff --git a/src/snakia/platform/layer.py b/src/snakia/platform/layer.py index 31521e3..8420b43 100644 --- a/src/snakia/platform/layer.py +++ b/src/snakia/platform/layer.py @@ -1,15 +1,11 @@ from __future__ import annotations -from typing import ClassVar, Generic, TypeVar, final, overload - -from typing_extensions import Self +from typing import ClassVar, Self, final, overload from .os import PlatformOS -T = TypeVar("T", bound=PlatformOS) - -class PlatformLayer(Generic[T]): +class PlatformLayer[T: PlatformOS]: target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN @final diff --git a/src/snakia/property/__init__.py b/src/snakia/property/__init__.py index 6af10c9..91e4ec1 100644 --- a/src/snakia/property/__init__.py +++ b/src/snakia/property/__init__.py @@ -1,5 +1,5 @@ from .cell_property import CellProperty -from .classproperty import ClassProperty, classproperty +from .classproperty import ClassProperty from .hook_property import HookProperty from .initonly import Initonly, initonly from .priv_property import PrivProperty @@ -9,7 +9,6 @@ from .readonly import Readonly, readonly __all__ = [ "CellProperty", "ClassProperty", - "classproperty", "HookProperty", "Initonly", "initonly", diff --git a/src/snakia/property/cell_property.py b/src/snakia/property/cell_property.py index 8eacea6..84a0924 100644 --- a/src/snakia/property/cell_property.py +++ b/src/snakia/property/cell_property.py @@ -1,29 +1,14 @@ -from __future__ import annotations - -from typing import Any, Generic, Protocol, TypeAlias, TypeVar - -from typing_extensions import Self +from typing import Any, Callable, Self from snakia.types import empty -T = TypeVar("T") - -_Cell: TypeAlias = T | None +type _Cell[T] = T | None +type _Getter[T] = Callable[[Any, _Cell[T]], T] +type _Setter[T] = Callable[[Any, _Cell[T], T], _Cell[T]] +type _Deleter[T] = Callable[[Any, _Cell[T]], _Cell[T]] -class _Getter(Protocol, Generic[T]): - def __call__(self, instance: Any, cell: _Cell[T], /) -> T: ... - - -class _Setter(Protocol, Generic[T]): - def __call__(self, instance: Any, cell: _Cell[T], value: T, /) -> _Cell[T]: ... - - -class _Deleter(Protocol, Generic[T]): - def __call__(self, instance: Any, cell: _Cell[T], /) -> _Cell[T]: ... - - -class CellProperty(Generic[T]): +class CellProperty[T]: """ A property that uses a cell to store its value. """ diff --git a/src/snakia/property/classproperty.py b/src/snakia/property/classproperty.py index afba619..48691ed 100644 --- a/src/snakia/property/classproperty.py +++ b/src/snakia/property/classproperty.py @@ -1,15 +1,9 @@ -from __future__ import annotations - -from typing import Any, Callable, Generic, TypeVar - -from typing_extensions import Self +from typing import Any, Callable, Self from snakia.types import empty -T = TypeVar("T") - -class ClassProperty(Generic[T]): +class ClassProperty[T]: """ Class property """ @@ -53,8 +47,8 @@ class ClassProperty(Generic[T]): return self -def classproperty( - fget: Callable[[Any], T], +def classproperty[T]( + fget: Callable[[Any], T] = empty.func, fset: Callable[[Any, T], None] = empty.func, fdel: Callable[[Any], None] = empty.func, ) -> ClassProperty[T]: @@ -65,6 +59,6 @@ def classproperty( fset (Callable[[Any, T], None], optional): The setter function. Defaults to empty.func. fdel (Callable[[Any], None], optional): The deleter function. Defaults to empty.func. Returns: - Self: The class property. + ClassProperty[T]: The class property. """ return ClassProperty(fget, fset, fdel) diff --git a/src/snakia/property/hook_property.py b/src/snakia/property/hook_property.py index 568ee01..c6fa676 100644 --- a/src/snakia/property/hook_property.py +++ b/src/snakia/property/hook_property.py @@ -1,17 +1,11 @@ -from __future__ import annotations - -from typing import Any, Callable, Generic, TypeVar - -from typing_extensions import Self +from typing import Any, Callable, Self from snakia.types import empty from .priv_property import PrivProperty -T = TypeVar("T") - -class HookProperty(PrivProperty[T], Generic[T]): +class HookProperty[T](PrivProperty[T]): """ A property that calls a function when the property is set, get, or deleted. """ diff --git a/src/snakia/property/initonly.py b/src/snakia/property/initonly.py index e5638c5..2193b4f 100644 --- a/src/snakia/property/initonly.py +++ b/src/snakia/property/initonly.py @@ -1,11 +1,9 @@ -from typing import Any, Generic, TypeVar +from typing import Any from .priv_property import PrivProperty -T = TypeVar("T") - -class Initonly(PrivProperty[T], Generic[T]): +class Initonly[T](PrivProperty[T]): """Property that can only be set once.""" def __set__(self, instance: Any, value: T, /) -> None: diff --git a/src/snakia/property/priv_property.py b/src/snakia/property/priv_property.py index fba63da..4203503 100644 --- a/src/snakia/property/priv_property.py +++ b/src/snakia/property/priv_property.py @@ -1,45 +1,21 @@ -from typing import Any, Callable, Final, Generic, TypeVar, overload - -from typing_extensions import Self - -from snakia.types import Unset - -T = TypeVar("T") +from typing import Any -class PrivProperty(Generic[T]): - __slots__ = "__name", "__default_value", "__default_factory" +class PrivProperty[T]: + __slots__ = "__name", "__default_value" __name: str - @overload - def __init__(self) -> None: ... - @overload - def __init__(self, default_value: T) -> None: ... - @overload - def __init__(self, *, default_factory: Callable[[Self], T]) -> None: ... - def __init__( - self, - default_value: T | Unset = Unset(), - default_factory: Callable[[Self], T] | Unset = Unset(), - ) -> None: - self.__default_value: Final[T | Unset] = default_value - self.__default_factory: Final[Callable[[Self], T] | Unset] = default_factory - - def _get_default(self: Self) -> T: - return Unset.map( - self.__default_factory, - lambda f: f(self), - lambda _: Unset.unwrap(self.__default_value), - ) + def __init__(self, default_value: T | None = None) -> None: + self.__default_value: T | None = default_value def __set_name__(self, owner: type, name: str) -> None: self.__name = f"_{owner.__name__}__{name}" def __get__(self, instance: Any, owner: type | None = None, /) -> T: - if not hasattr(instance, self.__name): - setattr(instance, self.__name, self._get_default()) - return getattr(instance, self.__name) # type: ignore + if self.__default_value: + return getattr(instance, self.__name, self.__default_value) + return getattr(instance, self.__name) def __set__(self, instance: Any, value: T, /) -> None: setattr(instance, self.__name, value) diff --git a/src/snakia/property/property.py b/src/snakia/property/property.py index 969cc04..97e8b29 100644 --- a/src/snakia/property/property.py +++ b/src/snakia/property/property.py @@ -1,20 +1,11 @@ -from __future__ import annotations - -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Callable, Self from snakia.types import empty -T = TypeVar("T") - -class Property(Generic[T]): +class Property[T]: """ - A property that can be set, get, and deleted. - """ - - __slots__ = "__fget", "__fset", "__fdel", "__name" - - __name: str + A property that can be set, get, and deleted.""" def __init__( self, @@ -26,9 +17,6 @@ class Property(Generic[T]): self.__fset = fset self.__fdel = fdel - def __set_name__(self, owner: type, name: str) -> None: - self.__name = name - def __get__(self, instance: Any, owner: type | None = None, /) -> T: return self.__fget(instance) @@ -38,21 +26,17 @@ class Property(Generic[T]): def __delete__(self, instance: Any, /) -> None: return self.__fdel(instance) - def getter(self, fget: Callable[[Any], T], /) -> Property[T]: + def getter(self, fget: Callable[[Any], T], /) -> Self: """Descriptor getter.""" self.__fget = fget return self - def setter(self, fset: Callable[[Any, T], None], /) -> Property[T]: + def setter(self, fset: Callable[[Any, T], None], /) -> Self: """Descriptor setter.""" self.__fset = fset return self - def deleter(self, fdel: Callable[[Any], None], /) -> Property[T]: + def deleter(self, fdel: Callable[[Any], None], /) -> Self: """Descriptor deleter.""" self.__fdel = fdel return self - - @property - def name(self) -> str: - return self.__name diff --git a/src/snakia/property/readonly.py b/src/snakia/property/readonly.py index 7084fe7..0f6f2cf 100644 --- a/src/snakia/property/readonly.py +++ b/src/snakia/property/readonly.py @@ -1,34 +1,27 @@ -from typing import Any, Callable, Generic, TypeVar - -from snakia.utils import throw - -from .property import Property - -T = TypeVar("T") +from typing import Any, Callable -class Readonly(Property[T], Generic[T]): +class Readonly[T]: """ Readonly property. """ + __slots__ = ("__fget",) + def __init__( self, fget: Callable[[Any], T], - *, - strict: bool = False, ) -> None: - super().__init__( - fget=fget, - fset=( - (lambda *_: throw(TypeError("Cannot set readonly property"))) - if strict - else lambda *_: None - ), - ) + self.__fget = fget + + def __get__(self, instance: Any, owner: type | None = None, /) -> T: + return self.__fget(instance) + + def __set__(self, instance: Any, value: T, /) -> None: + pass -def readonly(value: T, *, strict: bool = False) -> Readonly[T]: +def readonly[T](value: T) -> Readonly[T]: """Create a readonly property with the given value. Args: @@ -37,4 +30,4 @@ def readonly(value: T, *, strict: bool = False) -> Readonly[T]: Returns: Readonly[T]: The readonly property. """ - return Readonly(lambda _: value, strict=strict) + return Readonly(lambda _: value) diff --git a/src/snakia/random/os.py b/src/snakia/random/os.py index 6f3b20a..558afd2 100644 --- a/src/snakia/random/os.py +++ b/src/snakia/random/os.py @@ -9,8 +9,7 @@ class OSRandom(Random[None]): """ def bits(self, k: int) -> int: - v = os.urandom((k + 7) // 8) - return int.from_bytes(v, "little") & ((1 << k) - 1) + return int.from_bytes(os.urandom((k + 7) // 8)) & ((1 << k) - 1) def get_state(self) -> None: return None diff --git a/src/snakia/random/python.py b/src/snakia/random/python.py index 61698af..0a680d2 100644 --- a/src/snakia/random/python.py +++ b/src/snakia/random/python.py @@ -1,9 +1,8 @@ import random -from typing import TypeAlias from .random import Random -_State: TypeAlias = tuple[int, tuple[int, ...], int | float | None] +type _State = tuple[int, tuple[int, ...], int | float | None] class PythonRandom(Random[_State]): diff --git a/src/snakia/random/random.py b/src/snakia/random/random.py index 3c32d04..5fcbec1 100644 --- a/src/snakia/random/random.py +++ b/src/snakia/random/random.py @@ -1,13 +1,9 @@ import builtins from abc import ABC, abstractmethod -from typing import Any, Generic, MutableSequence, Sequence, TypeVar, final - -S = TypeVar("S") -T = TypeVar("T") -M = TypeVar("M", bound=MutableSequence[Any]) +from typing import Any, MutableSequence, Sequence, final -class Random(ABC, Generic[S]): +class Random[S](ABC): """ A random number generator. """ @@ -49,12 +45,12 @@ class Random(ABC, Generic[S]): return self.bits(32) / (1 << 32) @final - def choice(self, seq: Sequence[T]) -> T: + def choice[T](self, seq: Sequence[T]) -> T: """Return a random element from a non-empty sequence.""" return seq[self.below(len(seq))] @final - def shuffle(self, seq: M) -> M: + def shuffle[T: MutableSequence[Any]](self, seq: T) -> T: """Shuffle a sequence in place.""" for i in range(len(seq) - 1, 0, -1): j = self.below(i + 1) diff --git a/src/snakia/types/__init__.py b/src/snakia/types/__init__.py index 320dc15..e93cfd2 100644 --- a/src/snakia/types/__init__.py +++ b/src/snakia/types/__init__.py @@ -2,16 +2,12 @@ from . import empty from .color import Color -from .marker import Marker, mark, marker from .unique import Unique, UniqueType, unique from .unset import Unset from .version import Version __all__ = [ "Color", - "Marker", - "mark", - "marker", "Version", "UniqueType", "Unique", diff --git a/src/snakia/types/marker.py b/src/snakia/types/marker.py deleted file mode 100644 index 3024fb9..0000000 --- a/src/snakia/types/marker.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Any, Callable, Literal, ParamSpec, TypeVar, final, overload - -from typing_extensions import Self - -from snakia.utils import get_attrs, get_or_set_attr - -T = TypeVar("T") -M = TypeVar("M", bound="Marker") -P = ParamSpec("P") - -MARKERS_ATTR = "__snakia_markers__" - - -def _get_all_markers(obj: Any) -> dict[type["Marker"], "Marker"]: - return get_or_set_attr(obj, MARKERS_ATTR, dict()) - - -class Marker: - @overload - @classmethod - def get(cls, obj: Any, default: None = None) -> Self: ... - @overload - @classmethod - def get(cls, obj: Any, default: T) -> Self | T: ... - @final - @classmethod - def get(cls, obj: Any, default: Any = None) -> Any: - markers = _get_all_markers(obj) - return markers.get(cls, default) - - @final - @classmethod - def has(cls, obj: Any) -> bool: - if not hasattr(obj, MARKERS_ATTR): - return False - _marker = obj.__dict__[MARKERS_ATTR].get(cls, None) - return isinstance(_marker, cls) - - @final - def set_mark(self, obj: T) -> T: - markers = _get_all_markers(obj) - markers[self.__class__] = self - return obj - - @final - @classmethod - def mark(cls, *a: Any, **kw: Any) -> Callable[[T], T]: - def inner(obj: T) -> T: - return cls(*a, **kw).set_mark(obj) - - return inner - - @final - @classmethod - def unmark(cls, obj: T) -> T: - markers = _get_all_markers(obj) - if cls in markers: - del markers[cls] - return obj - - @overload - @classmethod - def get_marks( - cls, container: Any, *, only_values: Literal[False] = False - ) -> dict[str, tuple[Any, Self]]: ... - @overload - @classmethod - def get_marks( - cls, container: Any, *, only_values: Literal[True] - ) -> dict[str, Self]: ... - @final - @classmethod - def get_marks( - cls, container: Any, *, only_values: bool = False - ) -> dict[str, tuple[Any, Self]] | dict[str, Self]: - markers = {} - for k, v in get_attrs(container).items(): - if not cls.has(v): - continue - _marker = cls.get(v) - if _marker is not None: - markers[k] = _marker if only_values else (v, _marker) - return markers # type: ignore - - -def marker() -> type[Marker]: - return type("", (Marker,), {}) - - -def mark( - func: Callable[P, Marker], /, *args: P.args, **kwargs: P.kwargs -) -> Callable[[T], T]: - def inner(obj: T) -> T: - return func(*args, **kwargs).set_mark(obj) - - return inner diff --git a/src/snakia/types/unique.py b/src/snakia/types/unique.py index 2f66e0f..52aaf6f 100644 --- a/src/snakia/types/unique.py +++ b/src/snakia/types/unique.py @@ -1,8 +1,4 @@ -from typing import Any, Callable, TypeGuard, TypeVar, final - -T = TypeVar("T") -V = TypeVar("V") -R = TypeVar("R") +from typing import Any, final @final @@ -35,49 +31,9 @@ class UniqueType(type): def __eq__(cls, other: Any) -> bool: return cls is other - def __call__(cls: type[T]) -> T: + def __call__[T](cls: type[T]) -> T: return cls.__new__(cls) # noqa: E1120 # pylint: disable=E1120 - def __hash__(cls) -> int: - return id(cls) - - def itis(cls: type[T], value: Any) -> TypeGuard[T]: - return value is cls or isinstance(value, cls) - - def unwrap(cls: type[T], value: V | type[T] | T, /) -> V: - if value is cls or isinstance(value, cls): - raise TypeError(f"{cls} not unwrapped") - return value # type: ignore - - def unwrap_or(cls: type[T], value: V | type[T] | T, default: R, /) -> V | R: - if value is cls or isinstance(value, cls): - return default - return value # type: ignore - - def map( - cls: type[T], - value: V | type[T] | T, - or_else: Callable[[V], R], - and_then: Callable[[type[T]], R], - ) -> R: - if value is cls or isinstance(value, cls): - return and_then(cls) - return or_else(value) # type: ignore - - def or_else( - cls: type[T], value: V | type[T] | T, func: Callable[[V], R] - ) -> type[T] | R: - if value is cls or isinstance(value, cls): - return cls - return func(value) # type: ignore - - def and_then( - cls: type[T], value: V | type[T] | T, func: Callable[[type[T]], R] - ) -> R | V: - if value is cls or isinstance(value, cls): - return func(cls) - return value # type: ignore - class Unique(metaclass=UniqueType): # noqa: R0903 # pylint: disable=R0903 """ diff --git a/src/snakia/utils/__init__.py b/src/snakia/utils/__init__.py index ad6100d..db30b42 100644 --- a/src/snakia/utils/__init__.py +++ b/src/snakia/utils/__init__.py @@ -1,26 +1,15 @@ -from .attrs import get_attrs, get_or_set_attr -from .exceptions import catch, throw -from .frames import frame -from .funcs import call, caller, ret, side, side_func -from .gil import GIL_ENABLED, nolock +from .frame import frame from .inherit import inherit +from .nolock import nolock from .this import this +from .throw import throw from .to_async import to_async __all__ = [ - "call", - "caller", - "get_or_set_attr", - "get_attrs", - "GIL_ENABLED", "frame", "inherit", "nolock", - "ret", - "side", - "side_func", "this", "throw", - "catch", "to_async", ] diff --git a/src/snakia/utils/attrs.py b/src/snakia/utils/attrs.py deleted file mode 100644 index 643181c..0000000 --- a/src/snakia/utils/attrs.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any, TypeVar - -T = TypeVar("T") - - -def get_or_set_attr(obj: Any, name: str, default: T) -> T: - if not hasattr(obj, name): - setattr(obj, name, default) - attr = getattr(obj, name) - if not isinstance(attr, type(default)): - setattr(obj, name, default) - return default - return attr - - -def get_attrs( - obj: Any, *, use_dir: bool = False, of_class: bool = False -) -> dict[str, Any]: - if of_class and not isinstance(obj, type): - obj = obj.__class__ - if not use_dir: - if hasattr(obj, "__dict__"): - return obj.__dict__ # type: ignore - if hasattr(obj, "__slots__"): - return {k: getattr(obj, k) for k in obj.__slots__} - raise NotImplementedError("Unknown layout") - else: - return {k: getattr(obj, k) for k in dir(obj)} diff --git a/src/snakia/utils/exceptions.py b/src/snakia/utils/exceptions.py deleted file mode 100644 index 5c1599e..0000000 --- a/src/snakia/utils/exceptions.py +++ /dev/null @@ -1,62 +0,0 @@ -import contextlib -from typing import Any, Callable, NoReturn, TypeVar, overload - -from exceptiongroup import ExceptionGroup - -from snakia.types.unset import Unset - -E = TypeVar("E", bound=Exception) -T = TypeVar("T") -D = TypeVar("D") - - -@overload -def throw( - *exceptions: E, # pyright: ignore[reportInvalidTypeVarUse] - from_: Unset | BaseException = Unset(), -) -> NoReturn: ... - - -@overload -def throw( - exception: BaseException, from_: Unset | BaseException = Unset(), / -) -> NoReturn: ... - - -def throw(*exceptions: Any, from_: Unset | BaseException = Unset()) -> NoReturn: - """Throw an exception.""" - if isinstance(from_, Unset): - if len(exceptions) == 1: - raise exceptions[0] - raise ExceptionGroup("", exceptions) - if len(exceptions) == 1: - raise exceptions[0] from from_ - raise ExceptionGroup("", exceptions) from from_ - - -contextlib.suppress() - - -@overload -def catch( - func: Callable[[], T], - *exceptions: type[Exception] | type[BaseException], - default: None = None, -) -> T | None: ... -@overload -def catch( - func: Callable[[], T], - *exceptions: type[Exception] | type[BaseException], - default: D, -) -> T | D: ... -def catch( - func: Callable[[], T], - *exceptions: type[Exception] | type[BaseException], - default: Any = None, -) -> T | Any: - try: - return func() - except BaseException as e: - if any(isinstance(e, exc) for exc in exceptions): - return default - raise diff --git a/src/snakia/utils/frames.py b/src/snakia/utils/frame.py similarity index 100% rename from src/snakia/utils/frames.py rename to src/snakia/utils/frame.py diff --git a/src/snakia/utils/funcs.py b/src/snakia/utils/funcs.py deleted file mode 100644 index 94cdad4..0000000 --- a/src/snakia/utils/funcs.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Any, Callable, ParamSpec, TypeVar - -P = ParamSpec("P") -T = TypeVar("T") - - -def call(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: - return f(*args, **kwargs) - - -def caller(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[..., T]: - return lambda *_, **__: f(*args, **kwargs) - - -def side(value: T, *_: Any, **__: Any) -> T: - return value - - -def side_func(value: T, *_: Any, **__: Any) -> Callable[..., T]: - return lambda *_, **__: value - - -def ret() -> Callable[[T], T]: - return lambda x: x diff --git a/src/snakia/utils/gil.py b/src/snakia/utils/gil_enabled.py similarity index 63% rename from src/snakia/utils/gil.py rename to src/snakia/utils/gil_enabled.py index 7cf6719..3ba393b 100644 --- a/src/snakia/utils/gil.py +++ b/src/snakia/utils/gil_enabled.py @@ -4,9 +4,6 @@ if TYPE_CHECKING: GIL_ENABLED: Final[bool] = bool(...) """ Whether the GIL is enabled.""" - - def nolock() -> None: ... - else: import sys @@ -15,14 +12,3 @@ else: GIL_ENABLED = sys._is_gil_enabled() else: GIL_ENABLED = True - - if GIL_ENABLED: - import time - - def nolock() -> None: - time.sleep(0.001) - - else: - - def nolock() -> None: - pass diff --git a/src/snakia/utils/inherit.py b/src/snakia/utils/inherit.py index a396524..a971f62 100644 --- a/src/snakia/utils/inherit.py +++ b/src/snakia/utils/inherit.py @@ -1,9 +1,9 @@ -from typing import Any, TypeVar - -T = TypeVar("T", bound=type) +from typing import Any -def inherit(type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> T: +def inherit[T: type]( + type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any +) -> T: """ Create a new class that inherits from the given class. diff --git a/src/snakia/utils/nolock.py b/src/snakia/utils/nolock.py new file mode 100644 index 0000000..39f001c --- /dev/null +++ b/src/snakia/utils/nolock.py @@ -0,0 +1,20 @@ +from typing import TYPE_CHECKING + +from .gil_enabled import GIL_ENABLED + +if TYPE_CHECKING: + + def nolock() -> None: ... + +else: + + if GIL_ENABLED: + import time + + def nolock() -> None: + time.sleep(0.001) + + else: + + def nolock() -> None: + pass diff --git a/src/snakia/utils/this.py b/src/snakia/utils/this.py index 35339e3..d5dd37b 100644 --- a/src/snakia/utils/this.py +++ b/src/snakia/utils/this.py @@ -2,7 +2,7 @@ import gc from types import FunctionType, MethodType from typing import Any -from .frames import frame +from .frame import frame def this() -> Any: diff --git a/src/snakia/utils/throw.py b/src/snakia/utils/throw.py new file mode 100644 index 0000000..b93d8ac --- /dev/null +++ b/src/snakia/utils/throw.py @@ -0,0 +1,29 @@ +from typing import Any, NoReturn, overload + +from snakia.types.unset import Unset + + +@overload +def throw[T: Exception]( + *exceptions: T, # pyright: ignore[reportInvalidTypeVarUse] + from_: Unset | BaseException = Unset(), +) -> NoReturn: ... + + +@overload +def throw( + exception: BaseException, from_: Unset | BaseException = Unset(), / +) -> NoReturn: ... + + +def throw( + *exceptions: Any, from_: Unset | BaseException = Unset() +) -> NoReturn: + """Throw an exception.""" + if isinstance(from_, Unset): + if len(exceptions) == 1: + raise exceptions[0] + raise ExceptionGroup("", exceptions) + if len(exceptions) == 1: + raise exceptions[0] from from_ + raise ExceptionGroup("", exceptions) from from_ diff --git a/src/snakia/utils/to_async.py b/src/snakia/utils/to_async.py index e701a22..1866a01 100644 --- a/src/snakia/utils/to_async.py +++ b/src/snakia/utils/to_async.py @@ -1,10 +1,7 @@ -from typing import Awaitable, Callable, ParamSpec, TypeVar - -P = ParamSpec("P") -R = TypeVar("R") +from typing import Awaitable, Callable -def to_async(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: +def to_async[**P, R](func: Callable[P, R]) -> Callable[P, Awaitable[R]]: """Convert a sync function to an async function.""" async def inner(*args: P.args, **kwargs: P.kwargs) -> R: diff --git a/uv.lock b/uv.lock index 70c0964..d894a9f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version < '3.11'", -] +requires-python = ">=3.12" [[package]] name = "annotated-types" @@ -15,194 +11,18 @@ 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 = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - [[package]] name = "networkx" -version = "3.4.2" +version = "3.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, -] - -[[package]] -name = "networkx" -version = "3.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/e8/fc/7b6fd4d22c8c4dc5704430140d8b3f520531d4fe7328b8f8d03f5a7950e8/networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad", size = 2511464, upload-time = "2025-11-24T03:03:47.158Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c7/d64168da60332c17d24c0d2f08bdf3987e8d1ae9d84b5bbd0eec2eb26a55/networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f", size = 2063713, upload-time = "2025-11-24T03:03:45.21Z" }, -] - -[[package]] -name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, -] - -[[package]] -name = "numpy" -version = "2.3.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, ] [[package]] name = "pydantic" -version = "2.11.10" +version = "2.12.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -210,133 +30,91 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/54/ecab642b3bed45f7d5f59b38443dcb36ef50f85af192e6ece103dbfe9587/pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423", size = 788494, upload-time = "2025-10-04T10:40:41.338Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/1f/73c53fcbfb0b5a78f91176df41945ca466e71e9d9d836e5c522abda39ee7/pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a", size = 444823, upload-time = "2025-10-04T10:40:39.055Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, ] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.4" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, ] [[package]] name = "snakia" -version = "0.5.0" +version = "0.4.0" source = { editable = "." } dependencies = [ - { name = "annotated-types" }, - { name = "exceptiongroup" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "networkx" }, { name = "pydantic" }, - { name = "types-networkx" }, - { name = "typing-extensions" }, ] [package.metadata] requires-dist = [ - { name = "annotated-types", specifier = ">=0.7.0" }, - { name = "exceptiongroup", specifier = ">=1.3.1" }, { name = "networkx", specifier = ">=3.4.2" }, - { name = "pydantic", specifier = ">=2.11.10" }, - { name = "types-networkx", specifier = ">=3.5.0.20251106" }, - { name = "typing-extensions", specifier = ">=4.15.0" }, -] - -[[package]] -name = "types-networkx" -version = "3.5.0.20251106" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/0d/0657390fb7e4fffb9e0a20d51b9b63451771c238ad7ca2eb31de12827a87/types_networkx-3.5.0.20251106.tar.gz", hash = "sha256:2f17a53a35cf6dbbb031039c19bf0f127ec1b37f3530c57a517a0dd67a42e343", size = 72307, upload-time = "2025-11-06T03:06:52.263Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/01/f768e7d3662841a5163f261510148a89564ffc4320568dfb98b79f8f6e68/types_networkx-3.5.0.20251106-py3-none-any.whl", hash = "sha256:674c53111d5ca6cf96eb6ccd3152d0d52c2720b8ddea8082c7b0116a21347a30", size = 160776, upload-time = "2025-11-06T03:06:50.89Z" }, + { name = "pydantic", specifier = ">=2.12.3" }, ] [[package]]