Compare commits
53 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
006c0a494d | ||
|
|
36d9da10bc | ||
|
|
7e8b57793f | ||
|
|
2149fcf308 | ||
|
|
fee08f3609 | ||
|
|
2cc15bf250 | ||
|
|
3d132251fc | ||
|
|
420f7626fb | ||
|
|
1e82a457ac | ||
|
|
12c0f42f99 | ||
|
|
e61dd387d4 | ||
|
|
34b5a1272b | ||
|
|
af4974075c | ||
|
|
631c416723 | ||
|
|
ae0427ec75 | ||
|
|
cb7629b6fe | ||
|
|
ef8db0787e | ||
|
|
9d04fb8a2e | ||
|
|
002ec9b63f | ||
|
|
43a6a8b183 | ||
|
|
498087befb | ||
|
|
7b34426c82 | ||
|
|
a49fe64be8 | ||
|
|
c7cd08b7e0 | ||
|
|
7f83370873 | ||
|
|
e2286f1944 | ||
|
|
fe2a53860b | ||
|
|
346140d675 | ||
|
|
e065b51490 | ||
|
|
c879b22f43 | ||
|
|
6ca21a633c | ||
|
|
694dbf99be | ||
|
|
a392685506 | ||
|
|
9840c1706e | ||
|
|
8f9347381a | ||
|
|
dbc57b8ecb | ||
|
|
7f25bcbcb4 | ||
|
|
dc33a78700 | ||
|
|
4f5d3d550b | ||
|
|
521aa7c323 | ||
|
|
4f258ffc7a | ||
|
|
5affbf91b0 | ||
|
|
8cb03ff355 | ||
|
|
3a09db8e46 | ||
|
|
72d34799a1 | ||
|
|
e7e6491cc3 | ||
|
|
737f21bf06 | ||
|
|
d640f21107 | ||
|
|
52d1505839 | ||
|
|
d7b965d26d | ||
|
|
c1e841fea9 | ||
|
|
072a0d7c0a | ||
|
|
19da3c7d8e |
93 changed files with 1605 additions and 1640 deletions
2
.github/workflows/docs-publish.yml
vendored
2
.github/workflows/docs-publish.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.12"]
|
python-version: ["3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
2
.github/workflows/pylint.yml
vendored
2
.github/workflows/pylint.yml
vendored
|
|
@ -7,7 +7,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.12"]
|
python-version: ["3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
2
.github/workflows/pypi-publish.yml
vendored
2
.github/workflows/pypi-publish.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.12"]
|
python-version: ["3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -13,5 +13,6 @@ wheels/
|
||||||
.direnv
|
.direnv
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.python-version
|
.python-version
|
||||||
|
.pytest_cache
|
||||||
.vscode
|
.vscode
|
||||||
_autosummary
|
_autosummary
|
||||||
137
LICENSE
137
LICENSE
|
|
@ -1,121 +1,24 @@
|
||||||
Creative Commons Legal Code
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
CC0 1.0 Universal
|
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.
|
||||||
|
|
||||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
of this software dedicate any and all copyright interest in the
|
||||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
software to the public domain. We make this dedication for the benefit
|
||||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
of the public at large and to the detriment of our heirs and
|
||||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
successors. We intend this dedication to be an overt act of
|
||||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
software under copyright law.
|
||||||
HEREUNDER.
|
|
||||||
|
|
||||||
Statement of Purpose
|
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.
|
||||||
|
|
||||||
The laws of most jurisdictions throughout the world automatically confer
|
For more information, please refer to <https://unlicense.org>
|
||||||
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.
|
|
||||||
|
|
|
||||||
668
README.md
668
README.md
|
|
@ -1,29 +1,41 @@
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
# 🐍 Snakia Framework
|
# 🐍 Snakia Framework
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
[API Reference](https://ruject.github.io/snakia/)
|
||||||
|
•
|
||||||
|
[Telegram Chat](https://t.me/RuJect_Community)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
**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.
|
**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
|
## 📋 Table of Contents
|
||||||
|
|
||||||
- [🎯 Roadmap & TODO](#-roadmap--todo)
|
- [🐍 Snakia Framework](#-snakia-framework)
|
||||||
|
- [📋 Table of Contents](#-table-of-contents)
|
||||||
|
- [✨ Key Features](#-key-features)
|
||||||
- [🚀 Installation](#-installation)
|
- [🚀 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)
|
- [🚀 Quick Start](#-quick-start)
|
||||||
- [🏗️ Architecture](#️-architecture)
|
- [🏗️ 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)
|
- [📦 Examples](#-examples)
|
||||||
|
- [Health System](#health-system)
|
||||||
|
- [TUI Application](#tui-application)
|
||||||
- [🤝 Contributing](#-contributing)
|
- [🤝 Contributing](#-contributing)
|
||||||
- [🆘 Support](#-support)
|
- [How to Contribute](#how-to-contribute)
|
||||||
- [📄 License](#-license)
|
- [Development Guidelines](#development-guidelines)
|
||||||
|
|
||||||
### ✨ Key Features
|
## ✨ Key Features
|
||||||
|
|
||||||
- 🏗️ **ECS Architecture** - Flexible entity-component-system for scalable game/app logic
|
- 🏗️ **ECS Architecture** - Flexible entity-component-system for scalable game/app logic
|
||||||
- 📡 **Event System** - Asynchronous event handling with filters and priorities
|
- 📡 **Event System** - Asynchronous event handling with filters and priorities
|
||||||
|
|
@ -52,15 +64,9 @@ pip install snakia
|
||||||
### Install from Source
|
### Install from Source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
|
||||||
git clone https://github.com/RuJect/Snakia.git
|
git clone https://github.com/RuJect/Snakia.git
|
||||||
cd Snakia
|
cd Snakia
|
||||||
|
|
||||||
# Install with pip
|
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
# Or with uv (recommended)
|
|
||||||
uv sync
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🎯 Roadmap & TODO
|
## 🎯 Roadmap & TODO
|
||||||
|
|
@ -133,616 +139,6 @@ Snakia/
|
||||||
└── types/ # Special types
|
└── 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 `<function func at ...>`
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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
|
## 📦 Examples
|
||||||
|
|
||||||
### Health System
|
### Health System
|
||||||
|
|
@ -857,15 +253,3 @@ We welcome contributions to Snakia development! Whether you're fixing bugs, addi
|
||||||
- Write clear commit messages
|
- Write clear commit messages
|
||||||
- Update documentation for new features
|
- Update documentation for new features
|
||||||
- Test your changes thoroughly
|
- 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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
sys.path.insert(
|
sys.path.insert(0, str((Path(__file__).parent.parent.parent / "src").resolve()))
|
||||||
0, str((Path(__file__).parent.parent.parent / "src").resolve())
|
|
||||||
)
|
|
||||||
|
|
||||||
project = "Snakia"
|
project = "Snakia"
|
||||||
copyright = "2025, RuJect"
|
copyright = "2025, RuJect"
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
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()
|
|
||||||
|
|
@ -2,8 +2,12 @@ import sys
|
||||||
|
|
||||||
from snakia.core.tui import CanvasChar, RenderContext
|
from snakia.core.tui import CanvasChar, RenderContext
|
||||||
from snakia.core.tui.render import ANSIRenderer
|
from snakia.core.tui.render import ANSIRenderer
|
||||||
from snakia.core.tui.widgets import (BoxWidget, HorizontalSplitWidget,
|
from snakia.core.tui.widgets import (
|
||||||
TextWidget, VerticalSplitWidget)
|
BoxWidget,
|
||||||
|
HorizontalSplitWidget,
|
||||||
|
TextWidget,
|
||||||
|
VerticalSplitWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StdoutTarget:
|
class StdoutTarget:
|
||||||
|
|
|
||||||
13
flake.nix
13
flake.nix
|
|
@ -6,23 +6,20 @@
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs = {
|
||||||
{
|
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
flake-utils,
|
flake-utils,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
flake-utils.lib.eachDefaultSystem (
|
flake-utils.lib.eachDefaultSystem (
|
||||||
system:
|
system: let
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
};
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
python312
|
python310
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
|
|
@ -31,7 +28,7 @@
|
||||||
uv
|
uv
|
||||||
isort
|
isort
|
||||||
mypy
|
mypy
|
||||||
python312Packages.pylint
|
pylint
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
0
py.typed
Normal file
0
py.typed
Normal file
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "snakia"
|
name = "snakia"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
description = "Modern python framework"
|
description = "Modern python framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|
@ -10,17 +10,23 @@ keywords = ["python3", "event system", "ecs", "reactive programming"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
"Programming Language :: Python :: 3.14",
|
"Programming Language :: Python :: 3.14",
|
||||||
"Programming Language :: Python :: Free Threading",
|
"Programming Language :: Python :: Free Threading",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"annotated-types>=0.7.0",
|
||||||
|
"exceptiongroup>=1.3.1",
|
||||||
"networkx>=3.4.2",
|
"networkx>=3.4.2",
|
||||||
"pydantic>=2.12.3",
|
"pydantic>=2.11.10",
|
||||||
|
"types-networkx>=3.5.0.20251106",
|
||||||
|
"typing-extensions>=4.15.0",
|
||||||
]
|
]
|
||||||
license = "CC0-1.0"
|
license = "Unlicense"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|
@ -38,3 +44,4 @@ disable = ["C0114", "C0115", "C0116", "R0801"]
|
||||||
max-args = 8
|
max-args = 8
|
||||||
max-positional-arguments = 7
|
max-positional-arguments = 7
|
||||||
min-public-methods = 1
|
min-public-methods = 1
|
||||||
|
fail-on = "error"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile pyproject.toml -o requirements.txt
|
# uv pip compile pyproject.toml -o requirements.txt
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
# via pydantic
|
# via
|
||||||
networkx==3.5
|
# snakia (pyproject.toml)
|
||||||
|
# pydantic
|
||||||
|
exceptiongroup==1.3.1
|
||||||
# via snakia (pyproject.toml)
|
# via snakia (pyproject.toml)
|
||||||
|
networkx==3.4.2
|
||||||
|
# via snakia (pyproject.toml)
|
||||||
|
numpy==2.2.6
|
||||||
|
# via types-networkx
|
||||||
pydantic==2.12.3
|
pydantic==2.12.3
|
||||||
# via snakia (pyproject.toml)
|
# via snakia (pyproject.toml)
|
||||||
pydantic-core==2.41.4
|
pydantic-core==2.41.4
|
||||||
# via pydantic
|
# via pydantic
|
||||||
|
types-networkx==3.5.0.20251106
|
||||||
|
# via snakia (pyproject.toml)
|
||||||
typing-extensions==4.15.0
|
typing-extensions==4.15.0
|
||||||
# via
|
# via
|
||||||
|
# snakia (pyproject.toml)
|
||||||
|
# exceptiongroup
|
||||||
# pydantic
|
# pydantic
|
||||||
# pydantic-core
|
# pydantic-core
|
||||||
# typing-inspection
|
# typing-inspection
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,38 @@ from __future__ import annotations
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from typing import Any, cast, overload
|
from typing import Any, TypeVar, cast, overload
|
||||||
|
|
||||||
import networkx as nx # type: ignore
|
import networkx as nx
|
||||||
|
|
||||||
from snakia.utils import nolock
|
from snakia.utils import nolock
|
||||||
|
|
||||||
from .component import Component
|
from .component import Component
|
||||||
from .processor import Processor
|
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:
|
class System:
|
||||||
"""
|
"""
|
||||||
A system is a collection of entities and components that can be processed by processors.
|
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]
|
__processors: list[Processor]
|
||||||
__components: dict[type[Component], set[int]]
|
__components: dict[type[Component], set[int]]
|
||||||
__entitites: dict[int, dict[type[Component], Component]]
|
__entitites: dict[int, dict[type[Component], Component]]
|
||||||
|
|
@ -46,9 +63,7 @@ class System:
|
||||||
self.__entity_counter = count(start=1)
|
self.__entity_counter = count(start=1)
|
||||||
self.__dead_entities = set()
|
self.__dead_entities = set()
|
||||||
|
|
||||||
def get_processor[P: Processor](
|
def get_processor(self, processor_type: type[P], /) -> P | None:
|
||||||
self, processor_type: type[P], /
|
|
||||||
) -> P | None:
|
|
||||||
"""Returns the first processor of the given type."""
|
"""Returns the first processor of the given type."""
|
||||||
for processor in self.__processors:
|
for processor in self.__processors:
|
||||||
if isinstance(processor, processor_type):
|
if isinstance(processor, processor_type):
|
||||||
|
|
@ -67,39 +82,31 @@ class System:
|
||||||
self.__processors.remove(processor)
|
self.__processors.remove(processor)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components[A: Component](
|
def get_components(self, c1: type[A], /) -> Iterable[tuple[int, tuple[A]]]: ...
|
||||||
self, __c1: type[A], /
|
|
||||||
) -> Iterable[tuple[int, tuple[A]]]: ...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components[A: Component, B: Component](
|
def get_components(
|
||||||
self, __c1: type[A], __c2: type[B], /
|
self, c1: type[A], c2: type[B], /
|
||||||
) -> Iterable[tuple[int, tuple[A, B]]]: ...
|
) -> Iterable[tuple[int, tuple[A, B]]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components[A: Component, B: Component, C: Component](
|
def get_components(
|
||||||
self, __c1: type[A], __c2: type[B], __c3: type[C], /
|
self, c1: type[A], c2: type[B], c3: type[C], /
|
||||||
) -> Iterable[tuple[int, tuple[A, B, C]]]: ...
|
) -> Iterable[tuple[int, tuple[A, B, C]]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components[A: Component, B: Component, C: Component, D: Component](
|
def get_components(
|
||||||
self, __c1: type[A], __c2: type[B], __c3: type[C], __c4: type[D], /
|
self, c1: type[A], c2: type[B], c3: type[C], c4: type[D], /
|
||||||
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
|
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components[
|
def get_components(
|
||||||
A: Component,
|
|
||||||
B: Component,
|
|
||||||
C: Component,
|
|
||||||
D: Component,
|
|
||||||
E: Component,
|
|
||||||
](
|
|
||||||
self,
|
self,
|
||||||
__c1: type[A],
|
c1: type[A],
|
||||||
__c2: type[B],
|
c2: type[B],
|
||||||
__c3: type[C],
|
c3: type[C],
|
||||||
__c4: type[D],
|
c4: type[D],
|
||||||
__c5: type[E],
|
c5: type[E],
|
||||||
/,
|
/,
|
||||||
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
|
) -> Iterable[tuple[int, tuple[A, B, C, D]]]: ...
|
||||||
|
|
||||||
|
|
@ -108,10 +115,7 @@ class System:
|
||||||
) -> Iterable[tuple[int, tuple[Component, ...]]]:
|
) -> Iterable[tuple[int, tuple[Component, ...]]]:
|
||||||
"""Returns all entities with the given components."""
|
"""Returns all entities with the given components."""
|
||||||
entity_set = set.intersection(
|
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:
|
for entity in entity_set:
|
||||||
yield (
|
yield (
|
||||||
|
|
@ -123,51 +127,40 @@ class System:
|
||||||
)
|
)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components_of_entity[A: Component](
|
def get_components_of_entity(
|
||||||
self, entity: int, __c1: type[A], /
|
self, entity: int, c1: type[A], /
|
||||||
) -> tuple[A | None]: ...
|
) -> tuple[A | None]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components_of_entity[A: Component, B: Component](
|
def get_components_of_entity(
|
||||||
self, entity: int, __c1: type[A], __c2: type[B], /
|
self, entity: int, c1: type[A], c2: type[B], /
|
||||||
) -> tuple[A | None, B | None]: ...
|
) -> tuple[A | None, B | None]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components_of_entity[A: Component, B: Component, C: Component](
|
def get_components_of_entity(
|
||||||
self, entity: int, __c1: type[A], __c2: type[B], __c3: type[C], /
|
self, entity: int, c1: type[A], c2: type[B], c3: type[C], /
|
||||||
) -> tuple[A | None, B | None, C | None]: ...
|
) -> tuple[A | None, B | None, C | None]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components_of_entity[
|
def get_components_of_entity(
|
||||||
A: Component,
|
|
||||||
B: Component,
|
|
||||||
C: Component,
|
|
||||||
D: Component,
|
|
||||||
](
|
|
||||||
self,
|
self,
|
||||||
entity: int,
|
entity: int,
|
||||||
__c1: type[A],
|
c1: type[A],
|
||||||
__c2: type[B],
|
c2: type[B],
|
||||||
__c3: type[C],
|
c3: type[C],
|
||||||
__c4: type[D],
|
c4: type[D],
|
||||||
/,
|
/,
|
||||||
) -> tuple[A | None, B | None, C | None, D | None]: ...
|
) -> tuple[A | None, B | None, C | None, D | None]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_components_of_entity[
|
def get_components_of_entity(
|
||||||
A: Component,
|
|
||||||
B: Component,
|
|
||||||
C: Component,
|
|
||||||
D: Component,
|
|
||||||
E: Component,
|
|
||||||
](
|
|
||||||
self,
|
self,
|
||||||
entity: int,
|
entity: int,
|
||||||
__c1: type[A],
|
c1: type[A],
|
||||||
__c2: type[B],
|
c2: type[B],
|
||||||
__c3: type[C],
|
c3: type[C],
|
||||||
__c4: type[D],
|
c4: type[D],
|
||||||
__c5: type[E],
|
c5: type[E],
|
||||||
/,
|
/,
|
||||||
) -> tuple[A | None, B | None, C | None, D | None, E | None]: ...
|
) -> tuple[A | None, B | None, C | None, D | None, E | None]: ...
|
||||||
|
|
||||||
|
|
@ -183,20 +176,18 @@ class System:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_component[C: Component](
|
def get_component(self, component_type: type[C], /) -> Iterable[tuple[int, C]]:
|
||||||
self, component_type: type[C], /
|
|
||||||
) -> Iterable[tuple[int, C]]:
|
|
||||||
"""Returns all entities with the given component."""
|
"""Returns all entities with the given component."""
|
||||||
for entity in self.__components[component_type].copy():
|
for entity in self.__components[component_type].copy():
|
||||||
yield entity, cast(C, self.__entitites[entity][component_type])
|
yield entity, cast(C, self.__entitites[entity][component_type])
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_component_of_entity[C: Component](
|
def get_component_of_entity(
|
||||||
self, entity: int, component_type: type[C], /
|
self, entity: int, component_type: type[C], /
|
||||||
) -> C | None: ...
|
) -> C | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_component_of_entity[C: Component, D: Any](
|
def get_component_of_entity(
|
||||||
self, entity: int, component_type: type[C], /, default: D
|
self, entity: int, component_type: type[C], /, default: D
|
||||||
) -> C | D: ...
|
) -> C | D: ...
|
||||||
|
|
||||||
|
|
@ -216,24 +207,16 @@ class System:
|
||||||
self.__components[component_type].add(entity)
|
self.__components[component_type].add(entity)
|
||||||
self.__entitites[entity][component_type] = component
|
self.__entitites[entity][component_type] = component
|
||||||
|
|
||||||
def has_component(
|
def has_component(self, entity: int, component_type: type[Component]) -> bool:
|
||||||
self, entity: int, component_type: type[Component]
|
|
||||||
) -> bool:
|
|
||||||
"""Returns True if the entity has the given component."""
|
"""Returns True if the entity has the given component."""
|
||||||
return component_type in self.__entitites[entity]
|
return component_type in self.__entitites[entity]
|
||||||
|
|
||||||
def has_components(
|
def has_components(self, entity: int, *component_types: type[Component]) -> bool:
|
||||||
self, entity: int, *component_types: type[Component]
|
|
||||||
) -> bool:
|
|
||||||
"""Returns True if the entity has all the given components."""
|
"""Returns True if the entity has all the given components."""
|
||||||
components_dict = self.__entitites[entity]
|
components_dict = self.__entitites[entity]
|
||||||
return all(
|
return all(comp_type in components_dict for comp_type in component_types)
|
||||||
comp_type in components_dict for comp_type in component_types
|
|
||||||
)
|
|
||||||
|
|
||||||
def remove_component[C: Component](
|
def remove_component(self, entity: int, component_type: type[C]) -> C | None:
|
||||||
self, entity: int, component_type: type[C]
|
|
||||||
) -> C | None:
|
|
||||||
"""Removes a component from an entity."""
|
"""Removes a component from an entity."""
|
||||||
self.__components[component_type].discard(entity)
|
self.__components[component_type].discard(entity)
|
||||||
if not self.__components[component_type]:
|
if not self.__components[component_type]:
|
||||||
|
|
@ -265,9 +248,7 @@ class System:
|
||||||
|
|
||||||
def entity_exists(self, entity: int) -> bool:
|
def entity_exists(self, entity: int) -> bool:
|
||||||
"""Returns True if the entity exists."""
|
"""Returns True if the entity exists."""
|
||||||
return (
|
return entity in self.__entitites and entity not in self.__dead_entities
|
||||||
entity in self.__entitites and entity not in self.__dead_entities
|
|
||||||
)
|
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""Starts the system."""
|
"""Starts the system."""
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,19 @@ from typing import Final
|
||||||
|
|
||||||
from .ecs import System
|
from .ecs import System
|
||||||
from .es import Dispatcher
|
from .es import Dispatcher
|
||||||
from .loader.loader import Loader
|
|
||||||
|
|
||||||
|
|
||||||
class Engine:
|
class Engine:
|
||||||
|
__slots__ = (
|
||||||
|
"system",
|
||||||
|
"dispatcher",
|
||||||
|
"__system_thread",
|
||||||
|
"__dispatcher_thread",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.system: Final = System()
|
self.system: Final = System()
|
||||||
self.dispatcher: Final = Dispatcher()
|
self.dispatcher: Final = Dispatcher()
|
||||||
self.loader: Final = Loader(self)
|
|
||||||
self.__system_thread: threading.Thread | None = None
|
self.__system_thread: threading.Thread | None = None
|
||||||
self.__dispatcher_thread: threading.Thread | None = None
|
self.__dispatcher_thread: threading.Thread | None = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Self
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,27 +7,27 @@ class Action(BaseModel):
|
||||||
move: int = Field(default=1)
|
move: int = Field(default=1)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stop(cls) -> Self:
|
def stop(cls) -> Action:
|
||||||
"""Skip all handlers."""
|
"""Skip all handlers."""
|
||||||
return cls(move=2**8)
|
return cls(move=2**8)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def go_start(cls) -> Self:
|
def go_start(cls) -> Action:
|
||||||
"""Go to the first handler."""
|
"""Go to the first handler."""
|
||||||
return cls(move=-(2**8))
|
return cls(move=-(2**8))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def next(cls, count: int = 1) -> Self:
|
def next(cls, count: int = 1) -> Action:
|
||||||
"""Skip one handler."""
|
"""Skip one handler."""
|
||||||
return cls(move=count)
|
return cls(move=count)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prev(cls, count: int = 1) -> Self:
|
def prev(cls, count: int = 1) -> Action:
|
||||||
"""Go back one handler."""
|
"""Go back one handler."""
|
||||||
return cls(move=-count)
|
return cls(move=-count)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def skip(cls, count: int = 1) -> Self:
|
def skip(cls, count: int = 1) -> Action:
|
||||||
"""Skip n handlers.
|
"""Skip n handlers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import queue
|
import queue
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Callable, Final
|
from typing import Callable, Final, TypeVar
|
||||||
|
|
||||||
from snakia.utils import nolock
|
from snakia.utils import nolock
|
||||||
|
|
||||||
|
|
@ -11,35 +11,33 @@ from .filter import Filter
|
||||||
from .handler import Handler
|
from .handler import Handler
|
||||||
from .subscriber import Subscriber
|
from .subscriber import Subscriber
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Event)
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher:
|
class Dispatcher:
|
||||||
"""
|
"""
|
||||||
Event dispatcher
|
Event dispatcher
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__running: bool
|
__slots__ = ("__queue", "__subscribers", "__running")
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__queue: Final = queue.Queue[Event]()
|
self.__queue: Final = queue.Queue[Event]()
|
||||||
self.__subscribers: Final[
|
self.__subscribers: Final[dict[type[Event], list[Subscriber[Event]]]] = (
|
||||||
dict[type[Event], list[Subscriber[Event]]]
|
defaultdict(list)
|
||||||
] = defaultdict(list)
|
)
|
||||||
self.__running = False
|
self.__running: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
"""Returns True if the dispatcher is running."""
|
"""Returns True if the dispatcher is running."""
|
||||||
return self.__running
|
return self.__running
|
||||||
|
|
||||||
def subscribe[T: Event](
|
def subscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None:
|
||||||
self, event_type: type[T], subscriber: Subscriber[T]
|
|
||||||
) -> None:
|
|
||||||
"""Subscribe to an event type."""
|
"""Subscribe to an event type."""
|
||||||
self.__subscribers[event_type].append(subscriber) # type: ignore
|
self.__subscribers[event_type].append(subscriber) # type: ignore
|
||||||
|
|
||||||
def unsubscribe[T: Event](
|
def unsubscribe(self, event_type: type[T], subscriber: Subscriber[T]) -> None:
|
||||||
self, event_type: type[T], subscriber: Subscriber[T]
|
|
||||||
) -> None:
|
|
||||||
"""Unsubscribe from an event type."""
|
"""Unsubscribe from an event type."""
|
||||||
for sub in self.__subscribers[event_type].copy():
|
for sub in self.__subscribers[event_type].copy():
|
||||||
if sub.handler != subscriber.handler:
|
if sub.handler != subscriber.handler:
|
||||||
|
|
@ -48,7 +46,7 @@ class Dispatcher:
|
||||||
continue
|
continue
|
||||||
self.__subscribers[event_type].remove(sub)
|
self.__subscribers[event_type].remove(sub)
|
||||||
|
|
||||||
def on[T: Event](
|
def on(
|
||||||
self,
|
self,
|
||||||
event: type[T],
|
event: type[T],
|
||||||
filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622
|
filter: Filter[T] | None = None, # noqa: W0622 # pylint: disable=W0622
|
||||||
|
|
@ -95,9 +93,7 @@ class Dispatcher:
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(subscribers):
|
while i < len(subscribers):
|
||||||
subscriber = subscribers[i]
|
subscriber = subscribers[i]
|
||||||
if subscriber.filters is not None and not subscriber.filters(
|
if subscriber.filters is not None and not subscriber.filters(event):
|
||||||
event
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
action = subscriber.handler(event)
|
action = subscriber.handler(event)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Protocol
|
from typing import Generic, Protocol, TypeVar
|
||||||
|
|
||||||
from .event import Event
|
from .event import Event
|
||||||
|
|
||||||
|
T_contra = TypeVar("T_contra", bound=Event, contravariant=True)
|
||||||
|
|
||||||
class Filter[T: Event](Protocol):
|
|
||||||
|
class Filter(Protocol, Generic[T_contra]):
|
||||||
"""Filter for an event."""
|
"""Filter for an event."""
|
||||||
|
|
||||||
def __call__(self, event: T) -> bool: ...
|
def __call__(self, event: T_contra) -> bool: ...
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional, Protocol
|
from typing import Generic, Optional, Protocol, TypeVar
|
||||||
|
|
||||||
from .action import Action
|
from .action import Action
|
||||||
from .event import Event
|
from .event import Event
|
||||||
|
|
||||||
|
T_contra = TypeVar("T_contra", bound=Event, contravariant=True)
|
||||||
|
|
||||||
class Handler[T: Event](Protocol):
|
|
||||||
|
class Handler(Protocol, Generic[T_contra]):
|
||||||
"""Handler for an event."""
|
"""Handler for an event."""
|
||||||
|
|
||||||
def __call__(self, event: T) -> Optional[Action]: ...
|
def __call__(self, event: T_contra) -> Optional[Action]: ...
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import NamedTuple
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import NamedTuple
|
||||||
|
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .filter import Filter
|
from .filter import Filter
|
||||||
from .handler import Handler
|
from .handler import Handler
|
||||||
|
|
||||||
|
T_contra = TypeVar("T_contra", bound=Event, contravariant=True)
|
||||||
|
|
||||||
class Subscriber[T: Event](NamedTuple):
|
|
||||||
|
class Subscriber(NamedTuple, Generic[T_contra]):
|
||||||
"""
|
"""
|
||||||
Subscriber for an event."""
|
Subscriber for an event."""
|
||||||
|
|
||||||
handler: Handler[T]
|
handler: Handler[T_contra]
|
||||||
filters: Filter[T] | None
|
filters: Filter[T_contra] | None
|
||||||
priority: int
|
priority: int
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,33 @@
|
||||||
import sys
|
import sys
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any, Callable, Protocol, final
|
from typing import Any, Callable, Generic, Protocol, TypeVar, final
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=BaseException)
|
||||||
|
T_contra = TypeVar("T_contra", contravariant=True)
|
||||||
|
|
||||||
|
|
||||||
class ExceptionHook[T: BaseException](Protocol):
|
class ExceptionHook(Protocol, Generic[T_contra]):
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self, exception: T, frame: TracebackType | None, /
|
self, exception: T_contra, frame: TracebackType | None, /
|
||||||
) -> bool | None: ...
|
) -> bool | None: ...
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class _ExceptionManager:
|
class _ExceptionManager:
|
||||||
|
__slots__ = ("__hooks", "excepthook")
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = []
|
self.__hooks: list[tuple[type[BaseException], ExceptionHook[Any]]] = []
|
||||||
sys.excepthook = self._excepthook
|
sys.excepthook = self._excepthook
|
||||||
|
|
||||||
def hook_exception[T: BaseException](
|
def hook_exception(
|
||||||
self, exception_type: type[T], func: ExceptionHook[T]
|
self, exception_type: type[T], func: ExceptionHook[T]
|
||||||
) -> ExceptionHook[T]:
|
) -> ExceptionHook[T]:
|
||||||
self.__hooks.append((exception_type, func))
|
self.__hooks.append((exception_type, func))
|
||||||
return func
|
return func
|
||||||
|
|
||||||
def on_exception[T: BaseException](
|
def on_exception(
|
||||||
self, exception_type: type[T]
|
self, exception_type: type[T]
|
||||||
) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]:
|
) -> Callable[[ExceptionHook[T]], ExceptionHook[T]]:
|
||||||
def inner(func: ExceptionHook[T]) -> ExceptionHook[T]:
|
def inner(func: ExceptionHook[T]) -> ExceptionHook[T]:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
from .loadable import Loadable
|
|
||||||
from .meta import Meta
|
|
||||||
from .plugin import Plugin
|
|
||||||
from .plugin_processor import PluginProcessor
|
|
||||||
|
|
||||||
__all__ = ["Loadable", "Meta", "Plugin", "PluginProcessor"]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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: ...
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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()
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
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}"
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
from .async_bindable import AsyncBindable
|
from .async_bindable import AsyncBindable
|
||||||
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
||||||
from .bindable import Bindable
|
from .bindable import Bindable
|
||||||
from .chain import chain
|
from .chains import async_chain, chain
|
||||||
from .combine import combine
|
from .combines import async_combine, combine
|
||||||
from .concat import concat
|
from .concats import concat
|
||||||
from .const import const
|
from .conds import cond
|
||||||
from .filter import filter # noqa: W0622 # pylint: disable=W0622
|
from .consts import const
|
||||||
from .map import map # noqa: W0622 # pylint: disable=W0622
|
from .filters import filter # noqa: W0622 # pylint: disable=W0622
|
||||||
from .merge import async_merge, merge
|
from .maps import map # noqa: W0622 # pylint: disable=W0622
|
||||||
|
from .merges import async_merge, merge
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Bindable",
|
"Bindable",
|
||||||
|
|
@ -15,12 +16,15 @@ __all__ = [
|
||||||
"BaseBindable",
|
"BaseBindable",
|
||||||
"BindableSubscriber",
|
"BindableSubscriber",
|
||||||
"ValueChanged",
|
"ValueChanged",
|
||||||
|
"async_chain",
|
||||||
|
"async_combine",
|
||||||
|
"async_merge",
|
||||||
"chain",
|
"chain",
|
||||||
"combine",
|
"combine",
|
||||||
"concat",
|
"concat",
|
||||||
|
"cond",
|
||||||
"const",
|
"const",
|
||||||
"filter",
|
"filter",
|
||||||
"map",
|
"map",
|
||||||
"merge",
|
"merge",
|
||||||
"async_merge",
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from typing import Any, Awaitable, Callable, Literal, overload
|
from typing import Any, Awaitable, Callable, Generic, Literal, TypeVar, overload
|
||||||
|
|
||||||
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class AsyncBindable[T: Any](BaseBindable[T]):
|
|
||||||
|
class AsyncBindable(BaseBindable[T], Generic[T]):
|
||||||
"""
|
"""
|
||||||
An asynchronous bindable.
|
An asynchronous bindable.
|
||||||
"""
|
"""
|
||||||
|
|
@ -12,10 +14,6 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
super().__init__(default_value)
|
super().__init__(default_value)
|
||||||
self.__subscribers: list[BindableSubscriber[T, Awaitable[Any]]] = []
|
self.__subscribers: list[BindableSubscriber[T, Awaitable[Any]]] = []
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self) -> T:
|
|
||||||
return self.__value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subscribers(
|
def subscribers(
|
||||||
self,
|
self,
|
||||||
|
|
@ -25,8 +23,11 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
|
|
||||||
async def set(self, value: T) -> None:
|
async def set(self, value: T) -> None:
|
||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
e = ValueChanged(self.__value, value)
|
if not (self.has_default_value or self.has_value):
|
||||||
self.__value = value
|
self.set_silent(value)
|
||||||
|
return
|
||||||
|
e = ValueChanged(self.value, value)
|
||||||
|
self.set_silent(value)
|
||||||
for subscriber in self.__subscribers:
|
for subscriber in self.__subscribers:
|
||||||
await subscriber(e)
|
await subscriber(e)
|
||||||
|
|
||||||
|
|
@ -57,25 +58,19 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
if run_now:
|
if run_now:
|
||||||
|
|
||||||
async def _run() -> None:
|
async def _run() -> None:
|
||||||
await subscriber(
|
await subscriber(ValueChanged(self.__default_value, self.value))
|
||||||
ValueChanged(self.__default_value, self.__value)
|
|
||||||
)
|
|
||||||
|
|
||||||
return _run()
|
return _run()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def unsubscribe(
|
def unsubscribe(self, subscriber: BindableSubscriber[T, Awaitable[Any]]) -> None:
|
||||||
self, subscriber: BindableSubscriber[T, Awaitable[Any]]
|
|
||||||
) -> None:
|
|
||||||
"""Unsubscribe from an value."""
|
"""Unsubscribe from an value."""
|
||||||
self.__subscribers.remove(subscriber)
|
self.__subscribers.remove(subscriber)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def on(
|
def on(
|
||||||
self, run_now: Literal[True]
|
self, run_now: Literal[True]
|
||||||
) -> Callable[
|
) -> Callable[[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]]: ...
|
||||||
[BindableSubscriber[T, Awaitable[Any]]], Awaitable[None]
|
|
||||||
]: ...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def on(
|
def on(
|
||||||
|
|
@ -95,9 +90,7 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
if run_now:
|
if run_now:
|
||||||
|
|
||||||
async def _run() -> None:
|
async def _run() -> None:
|
||||||
await subscriber(
|
await subscriber(ValueChanged(self.__default_value, self.value))
|
||||||
ValueChanged(self.__default_value, self.__value)
|
|
||||||
)
|
|
||||||
|
|
||||||
return _run()
|
return _run()
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
from typing import Any, NamedTuple, Protocol
|
from typing import Generic, Protocol, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import NamedTuple
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
R_co = TypeVar("R_co", covariant=True)
|
||||||
|
|
||||||
|
|
||||||
class ValueChanged[T](NamedTuple):
|
class ValueChanged(NamedTuple, Generic[T]):
|
||||||
old_value: T
|
old_value: T
|
||||||
new_value: T
|
new_value: T
|
||||||
|
|
||||||
|
|
||||||
class BindableSubscriber[T: Any, R: Any](Protocol):
|
class BindableSubscriber(Protocol, Generic[T, R_co]):
|
||||||
def __call__(self, value: ValueChanged[T], /) -> R: ...
|
def __call__(self, value: ValueChanged[T], /) -> R_co: ...
|
||||||
|
|
||||||
|
|
||||||
class BaseBindable[T: Any]:
|
class BaseBindable(Generic[T]):
|
||||||
def __init__(self, default_value: T | None = None) -> None:
|
def __init__(self, default_value: T | None = None) -> None:
|
||||||
if default_value is not None:
|
if default_value is not None:
|
||||||
self.__default_value: T = default_value
|
self.__default_value: T = default_value
|
||||||
|
|
@ -22,7 +27,25 @@ class BaseBindable[T: Any]:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> T:
|
def value(self) -> T:
|
||||||
|
if self.has_value:
|
||||||
return self.__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
|
||||||
|
|
||||||
def set_silent(self, value: T) -> None:
|
def set_silent(self, value: T) -> None:
|
||||||
self.__value = value
|
self.__value = value
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, Generic, TypeVar
|
||||||
|
|
||||||
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
from .base_bindable import BaseBindable, BindableSubscriber, ValueChanged
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class Bindable[T: Any](BaseBindable[T]):
|
|
||||||
|
class Bindable(BaseBindable[T], Generic[T]):
|
||||||
"""
|
"""
|
||||||
A bindable value.
|
A bindable value.
|
||||||
"""
|
"""
|
||||||
|
|
@ -19,7 +21,10 @@ class Bindable[T: Any](BaseBindable[T]):
|
||||||
|
|
||||||
def set(self, value: T) -> None:
|
def set(self, value: T) -> None:
|
||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
e = ValueChanged(self.__value, 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)
|
self.set_silent(value)
|
||||||
for subscriber in self.__subscribers:
|
for subscriber in self.__subscribers:
|
||||||
subscriber(e)
|
subscriber(e)
|
||||||
|
|
@ -36,9 +41,7 @@ class Bindable[T: Any](BaseBindable[T]):
|
||||||
"""Unsubscribe from an value."""
|
"""Unsubscribe from an value."""
|
||||||
self.__subscribers.remove(subscriber)
|
self.__subscribers.remove(subscriber)
|
||||||
|
|
||||||
def on(
|
def on(self, run_now: bool = False) -> Callable[[BindableSubscriber[T, Any]], None]:
|
||||||
self, run_now: bool = False
|
|
||||||
) -> Callable[[BindableSubscriber[T, Any]], None]:
|
|
||||||
"""Decorator to subscribe to an value."""
|
"""Decorator to subscribe to an value."""
|
||||||
|
|
||||||
def wrapper(subscriber: BindableSubscriber[T, Any]) -> None:
|
def wrapper(subscriber: BindableSubscriber[T, Any]) -> None:
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
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
|
|
||||||
98
src/snakia/core/rx/chains.py
Normal file
98
src/snakia/core/rx/chains.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
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
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
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
|
|
||||||
193
src/snakia/core/rx/combines.py
Normal file
193
src/snakia/core/rx/combines.py
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
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
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, ParamSpec
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
|
|
||||||
def concat[**P](*funcs: Callable[P, Any]) -> Callable[P, None]:
|
def concat(*funcs: Callable[P, Any]) -> Callable[P, None]:
|
||||||
def inner(*args: P.args, **kwargs: P.kwargs) -> None:
|
def inner(*args: P.args, **kwargs: P.kwargs) -> None:
|
||||||
for f in funcs:
|
for f in funcs:
|
||||||
f(*args, **kwargs)
|
f(*args, **kwargs)
|
||||||
15
src/snakia/core/rx/conds.py
Normal file
15
src/snakia/core/rx/conds.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
|
|
||||||
def const[T](value: T) -> Callable[[], T]:
|
|
||||||
return lambda: value
|
|
||||||
7
src/snakia/core/rx/consts.py
Normal file
7
src/snakia/core/rx/consts.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from typing import Callable, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def const(value: T) -> Callable[[], T]:
|
||||||
|
return lambda: value
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import builtins
|
import builtins
|
||||||
from typing import Callable, Iterable, TypeGuard
|
from typing import Callable, Iterable, TypeGuard, TypeVar
|
||||||
|
|
||||||
|
S = TypeVar("S")
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
# noqa: W0622 # pylint: disable=W0622
|
# noqa: W0622 # pylint: disable=W0622
|
||||||
def filter[S, T](
|
def filter(
|
||||||
f: Callable[[S], TypeGuard[T]],
|
f: Callable[[S], TypeGuard[T]],
|
||||||
) -> Callable[[Iterable[S]], Iterable[T]]:
|
) -> Callable[[Iterable[S]], Iterable[T]]:
|
||||||
return lambda iterable: builtins.filter(f, iterable)
|
return lambda iterable: builtins.filter(f, iterable)
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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)
|
|
||||||
10
src/snakia/core/rx/maps.py
Normal file
10
src/snakia/core/rx/maps.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
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)
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from .async_bindable import AsyncBindable
|
from .async_bindable import AsyncBindable
|
||||||
from .bindable import Bindable
|
from .bindable import Bindable
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
def merge[T](
|
|
||||||
|
def merge(
|
||||||
*sources: Bindable[T],
|
*sources: Bindable[T],
|
||||||
) -> Bindable[T]:
|
) -> Bindable[T]:
|
||||||
merged = Bindable[T]()
|
merged = Bindable[T]()
|
||||||
|
|
@ -11,7 +15,7 @@ def merge[T](
|
||||||
return merged
|
return merged
|
||||||
|
|
||||||
|
|
||||||
async def async_merge[T](
|
async def async_merge(
|
||||||
*sources: AsyncBindable[T],
|
*sources: AsyncBindable[T],
|
||||||
) -> AsyncBindable[T]:
|
) -> AsyncBindable[T]:
|
||||||
merged = AsyncBindable[T]()
|
merged = AsyncBindable[T]()
|
||||||
|
|
@ -40,9 +40,7 @@ class Canvas:
|
||||||
|
|
||||||
def get_column(self, x: int, /) -> Iterable[CanvasChar]:
|
def get_column(self, x: int, /) -> Iterable[CanvasChar]:
|
||||||
"""Get the column at the given position."""
|
"""Get the column at the given position."""
|
||||||
return (
|
return (self.__buffer[self._get_index(x, y)] for y in range(self.height))
|
||||||
self.__buffer[self._get_index(x, y)] for y in range(self.height)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set(self, x: int, y: int, value: CanvasChar, /) -> None:
|
def set(self, x: int, y: int, value: CanvasChar, /) -> None:
|
||||||
"""Set the character at the given position."""
|
"""Set the character at the given position."""
|
||||||
|
|
@ -68,9 +66,7 @@ class Canvas:
|
||||||
value: CanvasChar,
|
value: CanvasChar,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set the area at the given position."""
|
"""Set the area at the given position."""
|
||||||
for i in range(
|
for i in range(self._get_index(x, y), self._get_index(x + width, y + height)):
|
||||||
self._get_index(x, y), self._get_index(x + width, y + height)
|
|
||||||
):
|
|
||||||
self.__buffer[i] = value
|
self.__buffer[i] = value
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Final, final
|
from typing import Final, TypeVar, final
|
||||||
|
|
||||||
from snakia.core.rx import AsyncBindable, Bindable
|
from snakia.core.rx import AsyncBindable, Bindable
|
||||||
from snakia.utils import to_async
|
from snakia.utils import to_async
|
||||||
|
|
||||||
from .canvas import Canvas
|
from .canvas import Canvas
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Widget(ABC):
|
class Widget(ABC):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -24,13 +26,13 @@ class Widget(ABC):
|
||||||
return self.__cache
|
return self.__cache
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def state[T](self, default_value: T) -> Bindable[T]:
|
def state(self, default_value: T) -> Bindable[T]:
|
||||||
field = Bindable(default_value)
|
field = Bindable(default_value)
|
||||||
field.subscribe(lambda _: self.dirty.set(True))
|
field.subscribe(lambda _: self.dirty.set(True))
|
||||||
return field
|
return field
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def async_state[T](self, default_value: T) -> AsyncBindable[T]:
|
def async_state(self, default_value: T) -> AsyncBindable[T]:
|
||||||
field = AsyncBindable(default_value)
|
field = AsyncBindable(default_value)
|
||||||
field.subscribe(to_async(lambda _: self.dirty.set(True)))
|
field.subscribe(to_async(lambda _: self.dirty.set(True)))
|
||||||
return field
|
return field
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ from .container import ContainerWidget
|
||||||
|
|
||||||
|
|
||||||
class HorizontalSplitWidget(ContainerWidget):
|
class HorizontalSplitWidget(ContainerWidget):
|
||||||
def __init__(
|
def __init__(self, children: Iterable[Widget], splitter_char: str = "|") -> None:
|
||||||
self, children: Iterable[Widget], splitter_char: str = "|"
|
|
||||||
) -> None:
|
|
||||||
super().__init__(children)
|
super().__init__(children)
|
||||||
self.splitter_char = splitter_char
|
self.splitter_char = splitter_char
|
||||||
|
|
||||||
|
|
@ -21,9 +19,7 @@ class HorizontalSplitWidget(ContainerWidget):
|
||||||
|
|
||||||
child_canvases = [child.render() for child in children_list]
|
child_canvases = [child.render() for child in children_list]
|
||||||
total_width = (
|
total_width = (
|
||||||
sum(canvas.width for canvas in child_canvases)
|
sum(canvas.width for canvas in child_canvases) + len(child_canvases) - 1
|
||||||
+ len(child_canvases)
|
|
||||||
- 1
|
|
||||||
)
|
)
|
||||||
max_height = max(canvas.height for canvas in child_canvases)
|
max_height = max(canvas.height for canvas in child_canvases)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ from .container import ContainerWidget
|
||||||
|
|
||||||
|
|
||||||
class VerticalSplitWidget(ContainerWidget):
|
class VerticalSplitWidget(ContainerWidget):
|
||||||
def __init__(
|
def __init__(self, children: Iterable[Widget], splitter_char: str = "-") -> None:
|
||||||
self, children: Iterable[Widget], splitter_char: str = "-"
|
|
||||||
) -> None:
|
|
||||||
super().__init__(children)
|
super().__init__(children)
|
||||||
self.splitter_char = splitter_char
|
self.splitter_char = splitter_char
|
||||||
|
|
||||||
|
|
@ -22,9 +20,7 @@ class VerticalSplitWidget(ContainerWidget):
|
||||||
child_canvases = [child.render() for child in children_list]
|
child_canvases = [child.render() for child in children_list]
|
||||||
max_width = max(canvas.width for canvas in child_canvases)
|
max_width = max(canvas.width for canvas in child_canvases)
|
||||||
total_height = (
|
total_height = (
|
||||||
sum(canvas.height for canvas in child_canvases)
|
sum(canvas.height for canvas in child_canvases) + len(child_canvases) - 1
|
||||||
+ len(child_canvases)
|
|
||||||
- 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = Canvas(max_width, total_height, CanvasChar())
|
result = Canvas(max_width, total_height, CanvasChar())
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@ from .inject_after import after_hook, inject_after
|
||||||
from .inject_before import before_hook, inject_before
|
from .inject_before import before_hook, inject_before
|
||||||
from .inject_const import inject_const
|
from .inject_const import inject_const
|
||||||
from .inject_replace import inject_replace, replace_hook
|
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 .pass_exceptions import pass_exceptions
|
||||||
from .singleton import singleton
|
from .singleton import singleton
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"inject_replace",
|
|
||||||
"replace_hook",
|
|
||||||
"inject_after",
|
|
||||||
"after_hook",
|
"after_hook",
|
||||||
"inject_before",
|
|
||||||
"before_hook",
|
"before_hook",
|
||||||
|
"inject_after",
|
||||||
|
"inject_before",
|
||||||
"inject_const",
|
"inject_const",
|
||||||
|
"inject_decorator",
|
||||||
|
"inject_replace",
|
||||||
|
"hook_decorator",
|
||||||
"pass_exceptions",
|
"pass_exceptions",
|
||||||
|
"replace_decorator",
|
||||||
|
"replace_hook",
|
||||||
"singleton",
|
"singleton",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
from typing import Callable
|
from typing import Callable, ParamSpec, TypeVar
|
||||||
|
|
||||||
from .inject_replace import inject_replace
|
from .inject_replace import inject_replace
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
T = TypeVar("T")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
def inject_after[T: object, **P, R](
|
|
||||||
obj: T, target: Callable[P, R], hook: Callable[[R], R]
|
def inject_after(obj: T, target: Callable[P, R], hook: Callable[[R], R]) -> T:
|
||||||
) -> T:
|
|
||||||
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
return hook(target(*args, **kwargs))
|
return hook(target(*args, **kwargs))
|
||||||
|
|
||||||
return inject_replace(obj, target, inner)
|
return inject_replace(obj, target, inner)
|
||||||
|
|
||||||
|
|
||||||
def after_hook[**P, R](
|
def after_hook(
|
||||||
obj: object, target: Callable[P, R]
|
obj: object, target: Callable[P, R]
|
||||||
) -> Callable[[Callable[[R], R]], Callable[[R], R]]:
|
) -> Callable[[Callable[[R], R]], Callable[[R], R]]:
|
||||||
def hook(new: Callable[[R], R]) -> Callable[[R], R]:
|
def hook(new: Callable[[R], R]) -> Callable[[R], R]:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, ParamSpec, TypeVar
|
||||||
|
|
||||||
from .inject_replace import inject_replace
|
from .inject_replace import inject_replace
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
T = TypeVar("T")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
def inject_before[T: object, **P, R](
|
|
||||||
obj: T, target: Callable[P, R], hook: Callable[P, Any]
|
def inject_before(obj: T, target: Callable[P, R], hook: Callable[P, Any]) -> T:
|
||||||
) -> T:
|
|
||||||
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
hook(*args, **kwargs)
|
hook(*args, **kwargs)
|
||||||
return target(*args, **kwargs)
|
return target(*args, **kwargs)
|
||||||
|
|
@ -13,7 +15,7 @@ def inject_before[T: object, **P, R](
|
||||||
return inject_replace(obj, target, inner)
|
return inject_replace(obj, target, inner)
|
||||||
|
|
||||||
|
|
||||||
def before_hook[**P, R](
|
def before_hook(
|
||||||
obj: object, target: Callable[P, R]
|
obj: object, target: Callable[P, R]
|
||||||
) -> Callable[[Callable[P, Any]], Callable[P, Any]]:
|
) -> Callable[[Callable[P, Any]], Callable[P, Any]]:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import sys
|
import sys
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
from typing import Any, Callable, cast
|
from typing import Any, Callable, TypeVar, cast
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Callable[..., Any])
|
||||||
|
|
||||||
if sys.version_info >= (3, 13):
|
if sys.version_info >= (3, 13):
|
||||||
|
|
||||||
def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]:
|
def inject_const(**consts: Any) -> Callable[[T], T]:
|
||||||
def inner(func: T) -> T:
|
def inner(func: T) -> T:
|
||||||
values = [*func.__code__.co_consts]
|
values = [*func.__code__.co_consts]
|
||||||
for i, name in enumerate(func.__code__.co_varnames):
|
for i, name in enumerate(func.__code__.co_varnames):
|
||||||
|
|
@ -26,7 +28,7 @@ if sys.version_info >= (3, 13):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def inject_const[T: Callable[..., Any]](**consts: Any) -> Callable[[T], T]:
|
def inject_const(**consts: Any) -> Callable[[T], T]:
|
||||||
def inner(func: T) -> T:
|
def inner(func: T) -> T:
|
||||||
values = [*func.__code__.co_consts]
|
values = [*func.__code__.co_consts]
|
||||||
for i, name in enumerate(func.__code__.co_varnames):
|
for i, name in enumerate(func.__code__.co_varnames):
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
from typing import Callable
|
from typing import Callable, ParamSpec, TypeVar
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
T = TypeVar("T")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
def inject_replace[T: object, **P, R](
|
def inject_replace(obj: T, old: Callable[P, R], new: Callable[P, R]) -> T:
|
||||||
obj: T, old: Callable[P, R], new: Callable[P, R]
|
|
||||||
) -> T:
|
|
||||||
for k, v in obj.__dict__.items():
|
for k, v in obj.__dict__.items():
|
||||||
if v is old:
|
if v is old:
|
||||||
setattr(obj, k, new)
|
setattr(obj, k, new)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def replace_hook[**P, R](
|
def replace_hook(
|
||||||
obj: object, old: Callable[P, R]
|
obj: object, old: Callable[P, R]
|
||||||
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
||||||
def hook(new: Callable[P, R]) -> Callable[P, R]:
|
def hook(new: Callable[P, R]) -> Callable[P, R]:
|
||||||
|
|
|
||||||
72
src/snakia/decorators/meta_decorators.py
Normal file
72
src/snakia/decorators/meta_decorators.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
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
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable, overload
|
from typing import Any, Callable, ParamSpec, TypeVar, overload
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def pass_exceptions[**P](
|
def pass_exceptions(
|
||||||
*errors: type[Exception],
|
*errors: type[Exception],
|
||||||
) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ...
|
) -> Callable[[Callable[P, Any | None]], Callable[P, Any | None]]: ...
|
||||||
@overload
|
@overload
|
||||||
def pass_exceptions[**P, R](
|
def pass_exceptions(
|
||||||
*errors: type[Exception],
|
*errors: type[Exception],
|
||||||
default: R,
|
default: R,
|
||||||
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,7 @@
|
||||||
def singleton[T](cls: type[T]) -> T:
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def singleton(cls: type[T]) -> T:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from .bool import BoolField
|
||||||
from .field import Field
|
from .field import Field
|
||||||
from .float import FloatField
|
from .float import FloatField
|
||||||
from .int import IntField
|
from .int import IntField
|
||||||
|
from .list import ListField
|
||||||
|
from .optional import OptionalField
|
||||||
from .str import StrField
|
from .str import StrField
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -11,5 +13,7 @@ __all__ = [
|
||||||
"BoolField",
|
"BoolField",
|
||||||
"FloatField",
|
"FloatField",
|
||||||
"IntField",
|
"IntField",
|
||||||
|
"ListField",
|
||||||
|
"OptionalField",
|
||||||
"StrField",
|
"StrField",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,52 @@
|
||||||
import pickle
|
import pickle
|
||||||
from typing import Final, override
|
from typing import Callable, Final, Generic, TypeVar, overload
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from snakia.types import Unset
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class AutoField[T](Field[T]):
|
|
||||||
|
class AutoField(Field[T], Generic[T]):
|
||||||
__slots__ = ("__target_type",)
|
__slots__ = ("__target_type",)
|
||||||
|
|
||||||
|
@overload
|
||||||
def __init__(
|
def __init__(
|
||||||
self, default_value: T, *, target_type: type[T] | None = None
|
self, default_value: T, *, target_type: type[T] | Unset = Unset()
|
||||||
) -> None:
|
) -> None: ...
|
||||||
super().__init__(default_value)
|
|
||||||
self.__target_type: Final = target_type
|
@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(),
|
||||||
|
) -> 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)
|
||||||
|
|
||||||
@override
|
|
||||||
def serialize(self, value: T, /) -> bytes:
|
def serialize(self, value: T, /) -> bytes:
|
||||||
return pickle.dumps(value)
|
return pickle.dumps(value)
|
||||||
|
|
||||||
@override
|
|
||||||
def deserialize(self, serialized: bytes, /) -> T:
|
def deserialize(self, serialized: bytes, /) -> T:
|
||||||
value = pickle.loads(serialized)
|
value = pickle.loads(serialized)
|
||||||
if not isinstance(value, self.__target_type or object):
|
|
||||||
return self.default_value
|
if not isinstance(value, self.__target_type):
|
||||||
|
return self._get_default()
|
||||||
return value # type: ignore
|
return value # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
from typing import override
|
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
|
||||||
class BoolField(Field[bool]):
|
class BoolField(Field[bool]):
|
||||||
@override
|
|
||||||
def serialize(self, value: bool, /) -> bytes:
|
def serialize(self, value: bool, /) -> bytes:
|
||||||
return b"\x01" if value else b"\x00"
|
return b"\x01" if value else b"\x00"
|
||||||
|
|
||||||
@override
|
|
||||||
def deserialize(self, serialized: bytes, /) -> bool:
|
def deserialize(self, serialized: bytes, /) -> bool:
|
||||||
return serialized == b"\x01"
|
return serialized == b"\x01"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Final, final
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Generic,
|
||||||
|
TypeVar,
|
||||||
|
final,
|
||||||
|
)
|
||||||
|
|
||||||
from snakia.property.priv_property import PrivProperty
|
from snakia.property.priv_property import PrivProperty
|
||||||
from snakia.utils import inherit
|
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
|
@abstractmethod
|
||||||
def serialize(self, value: T, /) -> bytes:
|
def serialize(self, value: T, /) -> bytes:
|
||||||
"""Serialize a value
|
"""Serialize a value
|
||||||
|
|
@ -34,16 +40,26 @@ class Field[T: Any](ABC, PrivProperty[T]):
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom[R](
|
def custom(
|
||||||
cls: type[Field[Any]],
|
cls: type[Field[Any]],
|
||||||
serialize: Callable[[R], str],
|
serialize: Callable[[Field[R], R], bytes],
|
||||||
deserialize: Callable[[str], R],
|
deserialize: Callable[[Field[R], bytes], R],
|
||||||
) -> type[Field[R]]:
|
) -> type[Field[R]]:
|
||||||
return inherit(
|
return inherit(
|
||||||
cls, {"serialize": serialize, "deserialize": deserialize}
|
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:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
@final
|
||||||
@classmethod
|
@classmethod
|
||||||
def type(cls) -> type[T]: ...
|
def type(cls) -> type[T]: ...
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import struct
|
import struct
|
||||||
from typing import override
|
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
|
||||||
class FloatField(Field[float]):
|
class FloatField(Field[float]):
|
||||||
@override
|
|
||||||
def serialize(self, value: float, /) -> bytes:
|
def serialize(self, value: float, /) -> bytes:
|
||||||
return struct.pack(">f", value)
|
return struct.pack(">f", value)
|
||||||
|
|
||||||
@override
|
|
||||||
def deserialize(self, serialized: bytes, /) -> float:
|
def deserialize(self, serialized: bytes, /) -> float:
|
||||||
return struct.unpack(">f", serialized)[0] # type: ignore
|
return struct.unpack(">f", serialized)[0] # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
from typing import override
|
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
|
||||||
class IntField(Field[int]):
|
class IntField(Field[int]):
|
||||||
@override
|
|
||||||
def serialize(self, value: int, /) -> bytes:
|
def serialize(self, value: int, /) -> bytes:
|
||||||
length = (value.bit_length() + 7) // 8
|
length = (value.bit_length() + 7) // 8
|
||||||
return value.to_bytes(length, "little")
|
return value.to_bytes(length, "little")
|
||||||
|
|
||||||
@override
|
|
||||||
def deserialize(self, serialized: bytes, /) -> int:
|
def deserialize(self, serialized: bytes, /) -> int:
|
||||||
return int.from_bytes(serialized, "little")
|
return int.from_bytes(serialized, "little")
|
||||||
|
|
|
||||||
38
src/snakia/field/list.py
Normal file
38
src/snakia/field/list.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
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
|
||||||
28
src/snakia/field/optional.py
Normal file
28
src/snakia/field/optional.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Final, override
|
from typing import Final
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
|
@ -8,10 +8,8 @@ class StrField(Field[str]):
|
||||||
super().__init__(default_value)
|
super().__init__(default_value)
|
||||||
self.encoding: Final = encoding
|
self.encoding: Final = encoding
|
||||||
|
|
||||||
@override
|
|
||||||
def serialize(self, value: str, /) -> bytes:
|
def serialize(self, value: str, /) -> bytes:
|
||||||
return value.encode(self.encoding)
|
return value.encode(self.encoding)
|
||||||
|
|
||||||
@override
|
|
||||||
def deserialize(self, serialized: bytes, /) -> str:
|
def deserialize(self, serialized: bytes, /) -> str:
|
||||||
return serialized.decode(self.encoding)
|
return serialized.decode(self.encoding)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from .bool import BoolField as bool
|
||||||
from .field import Field as field
|
from .field import Field as field
|
||||||
from .float import FloatField as float
|
from .float import FloatField as float
|
||||||
from .int import IntField as int
|
from .int import IntField as int
|
||||||
|
from .list import ListField as list
|
||||||
|
from .optional import OptionalField as optional
|
||||||
from .str import StrField as str
|
from .str import StrField as str
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -12,5 +14,7 @@ __all__ = [
|
||||||
"field",
|
"field",
|
||||||
"float",
|
"float",
|
||||||
"int",
|
"int",
|
||||||
|
"list",
|
||||||
|
"optional",
|
||||||
"str",
|
"str",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ctypes import CDLL, Array, c_char, c_char_p, create_string_buffer
|
from ctypes import CDLL, Array, c_char, c_char_p, create_string_buffer
|
||||||
from typing import Any, Final, Literal, cast, overload
|
from typing import Any, Final, Literal, TypeVar, cast, overload
|
||||||
|
|
||||||
from .layer import PlatformLayer
|
from .layer import PlatformLayer
|
||||||
from .os import PlatformOS
|
from .os import PlatformOS
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]):
|
class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]):
|
||||||
target = PlatformOS.ANDROID
|
target = PlatformOS.ANDROID
|
||||||
|
|
@ -16,7 +18,7 @@ class AndroidLayer(PlatformLayer[Literal[PlatformOS.ANDROID]]):
|
||||||
def get_prop(self, name: str) -> str | None: ...
|
def get_prop(self, name: str) -> str | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_prop[T](self, name: str, default: T) -> str | T: ...
|
def get_prop(self, name: str, default: T) -> str | T: ...
|
||||||
|
|
||||||
def get_prop(self, name: str, default: Any = None) -> Any:
|
def get_prop(self, name: str, default: Any = None) -> Any:
|
||||||
buffer = create_string_buffer(self.PROP_VALUE_MAX)
|
buffer = create_string_buffer(self.PROP_VALUE_MAX)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import ClassVar, Self, final, overload
|
from typing import ClassVar, Generic, TypeVar, final, overload
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from .os import PlatformOS
|
from .os import PlatformOS
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=PlatformOS)
|
||||||
|
|
||||||
class PlatformLayer[T: PlatformOS]:
|
|
||||||
|
class PlatformLayer(Generic[T]):
|
||||||
target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN
|
target: ClassVar[PlatformOS] = PlatformOS.UNKNOWN
|
||||||
|
|
||||||
@final
|
@final
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from .cell_property import CellProperty
|
from .cell_property import CellProperty
|
||||||
from .classproperty import ClassProperty
|
from .classproperty import ClassProperty, classproperty
|
||||||
from .hook_property import HookProperty
|
from .hook_property import HookProperty
|
||||||
from .initonly import Initonly, initonly
|
from .initonly import Initonly, initonly
|
||||||
from .priv_property import PrivProperty
|
from .priv_property import PrivProperty
|
||||||
|
|
@ -9,6 +9,7 @@ from .readonly import Readonly, readonly
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CellProperty",
|
"CellProperty",
|
||||||
"ClassProperty",
|
"ClassProperty",
|
||||||
|
"classproperty",
|
||||||
"HookProperty",
|
"HookProperty",
|
||||||
"Initonly",
|
"Initonly",
|
||||||
"initonly",
|
"initonly",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,29 @@
|
||||||
from typing import Any, Callable, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Generic, Protocol, TypeAlias, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from snakia.types import empty
|
from snakia.types import empty
|
||||||
|
|
||||||
type _Cell[T] = T | None
|
T = TypeVar("T")
|
||||||
type _Getter[T] = Callable[[Any, _Cell[T]], T]
|
|
||||||
type _Setter[T] = Callable[[Any, _Cell[T], T], _Cell[T]]
|
_Cell: TypeAlias = T | None
|
||||||
type _Deleter[T] = Callable[[Any, _Cell[T]], _Cell[T]]
|
|
||||||
|
|
||||||
|
|
||||||
class CellProperty[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]):
|
||||||
"""
|
"""
|
||||||
A property that uses a cell to store its value.
|
A property that uses a cell to store its value.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
from typing import Any, Callable, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable, Generic, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from snakia.types import empty
|
from snakia.types import empty
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class ClassProperty[T]:
|
|
||||||
|
class ClassProperty(Generic[T]):
|
||||||
"""
|
"""
|
||||||
Class property
|
Class property
|
||||||
"""
|
"""
|
||||||
|
|
@ -47,8 +53,8 @@ class ClassProperty[T]:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def classproperty[T](
|
def classproperty(
|
||||||
fget: Callable[[Any], T] = empty.func,
|
fget: Callable[[Any], T],
|
||||||
fset: Callable[[Any, T], None] = empty.func,
|
fset: Callable[[Any, T], None] = empty.func,
|
||||||
fdel: Callable[[Any], None] = empty.func,
|
fdel: Callable[[Any], None] = empty.func,
|
||||||
) -> ClassProperty[T]:
|
) -> ClassProperty[T]:
|
||||||
|
|
@ -59,6 +65,6 @@ def classproperty[T](
|
||||||
fset (Callable[[Any, T], None], optional): The setter function. Defaults to empty.func.
|
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.
|
fdel (Callable[[Any], None], optional): The deleter function. Defaults to empty.func.
|
||||||
Returns:
|
Returns:
|
||||||
ClassProperty[T]: The class property.
|
Self: The class property.
|
||||||
"""
|
"""
|
||||||
return ClassProperty(fget, fset, fdel)
|
return ClassProperty(fget, fset, fdel)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
from typing import Any, Callable, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable, Generic, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from snakia.types import empty
|
from snakia.types import empty
|
||||||
|
|
||||||
from .priv_property import PrivProperty
|
from .priv_property import PrivProperty
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class HookProperty[T](PrivProperty[T]):
|
|
||||||
|
class HookProperty(PrivProperty[T], Generic[T]):
|
||||||
"""
|
"""
|
||||||
A property that calls a function when the property is set, get, or deleted.
|
A property that calls a function when the property is set, get, or deleted.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from typing import Any
|
from typing import Any, Generic, TypeVar
|
||||||
|
|
||||||
from .priv_property import PrivProperty
|
from .priv_property import PrivProperty
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class Initonly[T](PrivProperty[T]):
|
|
||||||
|
class Initonly(PrivProperty[T], Generic[T]):
|
||||||
"""Property that can only be set once."""
|
"""Property that can only be set once."""
|
||||||
|
|
||||||
def __set__(self, instance: Any, value: T, /) -> None:
|
def __set__(self, instance: Any, value: T, /) -> None:
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,45 @@
|
||||||
from typing import Any
|
from typing import Any, Callable, Final, Generic, TypeVar, overload
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from snakia.types import Unset
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class PrivProperty[T]:
|
class PrivProperty(Generic[T]):
|
||||||
__slots__ = "__name", "__default_value"
|
__slots__ = "__name", "__default_value", "__default_factory"
|
||||||
|
|
||||||
__name: str
|
__name: str
|
||||||
|
|
||||||
def __init__(self, default_value: T | None = None) -> None:
|
@overload
|
||||||
self.__default_value: T | None = default_value
|
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 __set_name__(self, owner: type, name: str) -> None:
|
def __set_name__(self, owner: type, name: str) -> None:
|
||||||
self.__name = f"_{owner.__name__}__{name}"
|
self.__name = f"_{owner.__name__}__{name}"
|
||||||
|
|
||||||
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
||||||
if self.__default_value:
|
if not hasattr(instance, self.__name):
|
||||||
return getattr(instance, self.__name, self.__default_value)
|
setattr(instance, self.__name, self._get_default())
|
||||||
return getattr(instance, self.__name)
|
return getattr(instance, self.__name) # type: ignore
|
||||||
|
|
||||||
def __set__(self, instance: Any, value: T, /) -> None:
|
def __set__(self, instance: Any, value: T, /) -> None:
|
||||||
setattr(instance, self.__name, value)
|
setattr(instance, self.__name, value)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
from typing import Any, Callable, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable, Generic, TypeVar
|
||||||
|
|
||||||
from snakia.types import empty
|
from snakia.types import empty
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
class Property[T]:
|
|
||||||
|
class Property(Generic[T]):
|
||||||
"""
|
"""
|
||||||
A property that can be set, get, and deleted."""
|
A property that can be set, get, and deleted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = "__fget", "__fset", "__fdel", "__name"
|
||||||
|
|
||||||
|
__name: str
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -17,6 +26,9 @@ class Property[T]:
|
||||||
self.__fset = fset
|
self.__fset = fset
|
||||||
self.__fdel = fdel
|
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:
|
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
||||||
return self.__fget(instance)
|
return self.__fget(instance)
|
||||||
|
|
||||||
|
|
@ -26,17 +38,21 @@ class Property[T]:
|
||||||
def __delete__(self, instance: Any, /) -> None:
|
def __delete__(self, instance: Any, /) -> None:
|
||||||
return self.__fdel(instance)
|
return self.__fdel(instance)
|
||||||
|
|
||||||
def getter(self, fget: Callable[[Any], T], /) -> Self:
|
def getter(self, fget: Callable[[Any], T], /) -> Property[T]:
|
||||||
"""Descriptor getter."""
|
"""Descriptor getter."""
|
||||||
self.__fget = fget
|
self.__fget = fget
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def setter(self, fset: Callable[[Any, T], None], /) -> Self:
|
def setter(self, fset: Callable[[Any, T], None], /) -> Property[T]:
|
||||||
"""Descriptor setter."""
|
"""Descriptor setter."""
|
||||||
self.__fset = fset
|
self.__fset = fset
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def deleter(self, fdel: Callable[[Any], None], /) -> Self:
|
def deleter(self, fdel: Callable[[Any], None], /) -> Property[T]:
|
||||||
"""Descriptor deleter."""
|
"""Descriptor deleter."""
|
||||||
self.__fdel = fdel
|
self.__fdel = fdel
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self.__name
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,34 @@
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, Generic, TypeVar
|
||||||
|
|
||||||
|
from snakia.utils import throw
|
||||||
|
|
||||||
|
from .property import Property
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Readonly[T]:
|
class Readonly(Property[T], Generic[T]):
|
||||||
"""
|
"""
|
||||||
Readonly property.
|
Readonly property.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("__fget",)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
fget: Callable[[Any], T],
|
fget: Callable[[Any], T],
|
||||||
|
*,
|
||||||
|
strict: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.__fget = fget
|
super().__init__(
|
||||||
|
fget=fget,
|
||||||
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
fset=(
|
||||||
return self.__fget(instance)
|
(lambda *_: throw(TypeError("Cannot set readonly property")))
|
||||||
|
if strict
|
||||||
def __set__(self, instance: Any, value: T, /) -> None:
|
else lambda *_: None
|
||||||
pass
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def readonly[T](value: T) -> Readonly[T]:
|
def readonly(value: T, *, strict: bool = False) -> Readonly[T]:
|
||||||
"""Create a readonly property with the given value.
|
"""Create a readonly property with the given value.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -30,4 +37,4 @@ def readonly[T](value: T) -> Readonly[T]:
|
||||||
Returns:
|
Returns:
|
||||||
Readonly[T]: The readonly property.
|
Readonly[T]: The readonly property.
|
||||||
"""
|
"""
|
||||||
return Readonly(lambda _: value)
|
return Readonly(lambda _: value, strict=strict)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ class OSRandom(Random[None]):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def bits(self, k: int) -> int:
|
def bits(self, k: int) -> int:
|
||||||
return int.from_bytes(os.urandom((k + 7) // 8)) & ((1 << k) - 1)
|
v = os.urandom((k + 7) // 8)
|
||||||
|
return int.from_bytes(v, "little") & ((1 << k) - 1)
|
||||||
|
|
||||||
def get_state(self) -> None:
|
def get_state(self) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import random
|
import random
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
from .random import Random
|
from .random import Random
|
||||||
|
|
||||||
type _State = tuple[int, tuple[int, ...], int | float | None]
|
_State: TypeAlias = tuple[int, tuple[int, ...], int | float | None]
|
||||||
|
|
||||||
|
|
||||||
class PythonRandom(Random[_State]):
|
class PythonRandom(Random[_State]):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import builtins
|
import builtins
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, MutableSequence, Sequence, final
|
from typing import Any, Generic, MutableSequence, Sequence, TypeVar, final
|
||||||
|
|
||||||
|
S = TypeVar("S")
|
||||||
|
T = TypeVar("T")
|
||||||
|
M = TypeVar("M", bound=MutableSequence[Any])
|
||||||
|
|
||||||
|
|
||||||
class Random[S](ABC):
|
class Random(ABC, Generic[S]):
|
||||||
"""
|
"""
|
||||||
A random number generator.
|
A random number generator.
|
||||||
"""
|
"""
|
||||||
|
|
@ -45,12 +49,12 @@ class Random[S](ABC):
|
||||||
return self.bits(32) / (1 << 32)
|
return self.bits(32) / (1 << 32)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def choice[T](self, seq: Sequence[T]) -> T:
|
def choice(self, seq: Sequence[T]) -> T:
|
||||||
"""Return a random element from a non-empty sequence."""
|
"""Return a random element from a non-empty sequence."""
|
||||||
return seq[self.below(len(seq))]
|
return seq[self.below(len(seq))]
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def shuffle[T: MutableSequence[Any]](self, seq: T) -> T:
|
def shuffle(self, seq: M) -> M:
|
||||||
"""Shuffle a sequence in place."""
|
"""Shuffle a sequence in place."""
|
||||||
for i in range(len(seq) - 1, 0, -1):
|
for i in range(len(seq) - 1, 0, -1):
|
||||||
j = self.below(i + 1)
|
j = self.below(i + 1)
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,16 @@
|
||||||
|
|
||||||
from . import empty
|
from . import empty
|
||||||
from .color import Color
|
from .color import Color
|
||||||
|
from .marker import Marker, mark, marker
|
||||||
from .unique import Unique, UniqueType, unique
|
from .unique import Unique, UniqueType, unique
|
||||||
from .unset import Unset
|
from .unset import Unset
|
||||||
from .version import Version
|
from .version import Version
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Color",
|
"Color",
|
||||||
|
"Marker",
|
||||||
|
"mark",
|
||||||
|
"marker",
|
||||||
"Version",
|
"Version",
|
||||||
"UniqueType",
|
"UniqueType",
|
||||||
"Unique",
|
"Unique",
|
||||||
|
|
|
||||||
96
src/snakia/types/marker.py
Normal file
96
src/snakia/types/marker.py
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
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("<anonym marker>", (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
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
from typing import Any, final
|
from typing import Any, Callable, TypeGuard, TypeVar, final
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
V = TypeVar("V")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
|
|
@ -31,9 +35,49 @@ class UniqueType(type):
|
||||||
def __eq__(cls, other: Any) -> bool:
|
def __eq__(cls, other: Any) -> bool:
|
||||||
return cls is other
|
return cls is other
|
||||||
|
|
||||||
def __call__[T](cls: type[T]) -> T:
|
def __call__(cls: type[T]) -> T:
|
||||||
return cls.__new__(cls) # noqa: E1120 # pylint: disable=E1120
|
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
|
class Unique(metaclass=UniqueType): # noqa: R0903 # pylint: disable=R0903
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
from .frame import frame
|
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 .inherit import inherit
|
from .inherit import inherit
|
||||||
from .nolock import nolock
|
|
||||||
from .this import this
|
from .this import this
|
||||||
from .throw import throw
|
|
||||||
from .to_async import to_async
|
from .to_async import to_async
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"call",
|
||||||
|
"caller",
|
||||||
|
"get_or_set_attr",
|
||||||
|
"get_attrs",
|
||||||
|
"GIL_ENABLED",
|
||||||
"frame",
|
"frame",
|
||||||
"inherit",
|
"inherit",
|
||||||
"nolock",
|
"nolock",
|
||||||
|
"ret",
|
||||||
|
"side",
|
||||||
|
"side_func",
|
||||||
"this",
|
"this",
|
||||||
"throw",
|
"throw",
|
||||||
|
"catch",
|
||||||
"to_async",
|
"to_async",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
28
src/snakia/utils/attrs.py
Normal file
28
src/snakia/utils/attrs.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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)}
|
||||||
62
src/snakia/utils/exceptions.py
Normal file
62
src/snakia/utils/exceptions.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
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
|
||||||
24
src/snakia/utils/funcs.py
Normal file
24
src/snakia/utils/funcs.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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
|
||||||
|
|
@ -4,6 +4,9 @@ if TYPE_CHECKING:
|
||||||
GIL_ENABLED: Final[bool] = bool(...)
|
GIL_ENABLED: Final[bool] = bool(...)
|
||||||
"""
|
"""
|
||||||
Whether the GIL is enabled."""
|
Whether the GIL is enabled."""
|
||||||
|
|
||||||
|
def nolock() -> None: ...
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
@ -12,3 +15,14 @@ else:
|
||||||
GIL_ENABLED = sys._is_gil_enabled()
|
GIL_ENABLED = sys._is_gil_enabled()
|
||||||
else:
|
else:
|
||||||
GIL_ENABLED = True
|
GIL_ENABLED = True
|
||||||
|
|
||||||
|
if GIL_ENABLED:
|
||||||
|
import time
|
||||||
|
|
||||||
|
def nolock() -> None:
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def nolock() -> None:
|
||||||
|
pass
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from typing import Any
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=type)
|
||||||
|
|
||||||
|
|
||||||
def inherit[T: type](
|
def inherit(type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any) -> T:
|
||||||
type_: T, attrs: dict[str, Any] | None = None, /, **kwargs: Any
|
|
||||||
) -> T:
|
|
||||||
"""
|
"""
|
||||||
Create a new class that inherits from the given class.
|
Create a new class that inherits from the given class.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -2,7 +2,7 @@ import gc
|
||||||
from types import FunctionType, MethodType
|
from types import FunctionType, MethodType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .frame import frame
|
from .frames import frame
|
||||||
|
|
||||||
|
|
||||||
def this() -> Any:
|
def this() -> Any:
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
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_
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
from typing import Awaitable, Callable
|
from typing import Awaitable, Callable, ParamSpec, TypeVar
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
def to_async[**P, R](func: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
def to_async(func: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
||||||
"""Convert a sync function to an async function."""
|
"""Convert a sync function to an async function."""
|
||||||
|
|
||||||
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
|
|
|
||||||
360
uv.lock
generated
360
uv.lock
generated
|
|
@ -1,6 +1,10 @@
|
||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.10"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.11'",
|
||||||
|
"python_full_version < '3.11'",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
|
|
@ -12,17 +16,193 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "networkx"
|
name = "exceptiongroup"
|
||||||
version = "3.5"
|
version = "1.3.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
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" }
|
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 = [
|
wheels = [
|
||||||
{ 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" },
|
{ 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"
|
||||||
|
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" }
|
||||||
|
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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.12.3"
|
version = "2.11.10"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "annotated-types" },
|
{ name = "annotated-types" },
|
||||||
|
|
@ -30,91 +210,133 @@ dependencies = [
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
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" }
|
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" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ 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" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-core"
|
name = "pydantic-core"
|
||||||
version = "2.41.4"
|
version = "2.33.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
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" }
|
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "snakia"
|
name = "snakia"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "networkx" },
|
{ 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 = "pydantic" },
|
{ name = "pydantic" },
|
||||||
|
{ name = "types-networkx" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "annotated-types", specifier = ">=0.7.0" },
|
||||||
|
{ name = "exceptiongroup", specifier = ">=1.3.1" },
|
||||||
{ name = "networkx", specifier = ">=3.4.2" },
|
{ name = "networkx", specifier = ">=3.4.2" },
|
||||||
{ name = "pydantic", specifier = ">=2.12.3" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue