Release/v0.4.1 #1
14 changed files with 103 additions and 678 deletions
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
|
||||||
680
README.md
680
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)
|
||||||
- [🚀 Installation](#-installation)
|
- [📋 Table of Contents](#-table-of-contents)
|
||||||
- [🚀 Quick Start](#-quick-start)
|
- [✨ Key Features](#-key-features)
|
||||||
- [🏗️ Architecture](#️-architecture)
|
- [🚀 Installation](#-installation)
|
||||||
- [⚙️ Core](#️-core)
|
- [Prerequisites](#prerequisites)
|
||||||
- [🎯 ECS System](#-ecs-system)
|
- [Install from PyPi (recommended)](#install-from-pypi-recommended)
|
||||||
- [📡 Event System (ES)](#-event-system-es)
|
- [Install from Source](#install-from-source)
|
||||||
- [🔌 Plugin System](#-plugin-system)
|
- [🎯 Roadmap \& TODO](#-roadmap--todo)
|
||||||
- [🎨 TUI System](#-tui-system)
|
- [🚀 Quick Start](#-quick-start)
|
||||||
- [⚡ Reactive Programming (RX)](#-reactive-programming-rx)
|
- [🏗️ Architecture](#️-architecture)
|
||||||
- [🛠️ Utilities](#️-utilities)
|
- [📦 Examples](#-examples)
|
||||||
- [🎭 Decorators](#-decorators)
|
- [Health System](#health-system)
|
||||||
- [🏷️ Properties](#-properties)
|
- [TUI Application](#tui-application)
|
||||||
- [🌐 Platform Abstraction](#-platform-abstraction)
|
- [🤝 Contributing](#-contributing)
|
||||||
- [📦 Examples](#-examples)
|
- [How to Contribute](#how-to-contribute)
|
||||||
- [🤝 Contributing](#-contributing)
|
- [Development Guidelines](#development-guidelines)
|
||||||
- [🆘 Support](#-support)
|
|
||||||
- [📄 License](#-license)
|
|
||||||
|
|
||||||
### ✨ 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
|
||||||
|
|
@ -856,16 +252,4 @@ We welcome contributions to Snakia development! Whether you're fixing bugs, addi
|
||||||
- Add type hints to all new code
|
- Add type hints to all new code
|
||||||
- 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,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "snakia"
|
name = "snakia"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
description = "Modern python framework"
|
description = "Modern python framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|
@ -37,4 +37,5 @@ init-hook = "import sys; sys.path.append('.venv/lib/python3.12/site-packages')"
|
||||||
disable = ["C0114", "C0115", "C0116", "R0801"]
|
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"
|
||||||
|
|
@ -4,6 +4,7 @@ from .bindable import Bindable
|
||||||
from .chain import chain
|
from .chain import chain
|
||||||
from .combine import combine
|
from .combine import combine
|
||||||
from .concat import concat
|
from .concat import concat
|
||||||
|
from .cond import cond
|
||||||
from .const import const
|
from .const import const
|
||||||
from .filter import filter # noqa: W0622 # pylint: disable=W0622
|
from .filter import filter # noqa: W0622 # pylint: disable=W0622
|
||||||
from .map import map # noqa: W0622 # pylint: disable=W0622
|
from .map import map # noqa: W0622 # pylint: disable=W0622
|
||||||
|
|
@ -18,6 +19,7 @@ __all__ = [
|
||||||
"chain",
|
"chain",
|
||||||
"combine",
|
"combine",
|
||||||
"concat",
|
"concat",
|
||||||
|
"cond",
|
||||||
"const",
|
"const",
|
||||||
"filter",
|
"filter",
|
||||||
"map",
|
"map",
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,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 +21,8 @@ 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)
|
e = ValueChanged(self.value, value)
|
||||||
self.__value = value
|
self.set_silent(value)
|
||||||
for subscriber in self.__subscribers:
|
for subscriber in self.__subscribers:
|
||||||
await subscriber(e)
|
await subscriber(e)
|
||||||
|
|
||||||
|
|
@ -58,7 +54,7 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
|
|
||||||
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()
|
||||||
|
|
@ -96,7 +92,7 @@ class AsyncBindable[T: Any](BaseBindable[T]):
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ 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)
|
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)
|
||||||
|
|
|
||||||
13
src/snakia/core/rx/cond.py
Normal file
13
src/snakia/core/rx/cond.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
def cond[**P, T, F](
|
||||||
|
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
|
@ -36,14 +36,24 @@ class Field[T: Any](ABC, PrivProperty[T]):
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom[R](
|
def custom[R](
|
||||||
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,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",
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class ClassProperty[T]:
|
||||||
|
|
||||||
|
|
||||||
def classproperty[T](
|
def classproperty[T](
|
||||||
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]:
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class PrivProperty[T]:
|
||||||
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 self.__default_value:
|
||||||
return getattr(instance, self.__name, self.__default_value)
|
return getattr(instance, self.__name, self.__default_value)
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ from snakia.types import empty
|
||||||
|
|
||||||
class Property[T]:
|
class Property[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 +22,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)
|
||||||
|
|
||||||
|
|
@ -40,3 +48,7 @@ class 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,32 @@
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
from snakia.utils import throw
|
||||||
|
|
||||||
class Readonly[T]:
|
from .property import Property
|
||||||
|
|
||||||
|
|
||||||
|
class Readonly[T](Property[T]):
|
||||||
"""
|
"""
|
||||||
|
🚫 Codacy found a high ErrorProne issue: invalid syntax (F999) The issue arises from the use of generics in the class definition. The syntax Here's the code suggestion to fix the issue: This comment was generated by an experimental AI tool. :no_entry_sign: **Codacy** found a **high ErrorProne** issue: [invalid syntax (F999)](https://app.codacy.com/gh/RuJect/snakia/pull-requests/1)
The issue arises from the use of generics in the class definition. The syntax `Readonly[T](Property[T])` is incorrect in Python, as the use of generics requires the `Generic` base class from the `typing` module. To fix this, you need to inherit from `Generic[T]` in addition to `Property[T]`.
Here's the code suggestion to fix the issue:
```suggestion
class Readonly(Property[T], Generic[T]):
```
---
*This comment was generated by an experimental AI tool.*
|
|||||||
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[T](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 +35,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)
|
||||||
|
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -104,7 +104,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "snakia"
|
name = "snakia"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "networkx" },
|
{ name = "networkx" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
🚫 Codacy found a high ErrorProne issue: expected '(' (F999)
The issue reported by the Prospector linter is due to the incorrect syntax used for the type parameters in the function definition. In Python's type hinting, you cannot use square brackets
[]for defining type variables; instead, you should use parentheses()with theTypeVarfunction from thetypingmodule.To fix the issue, you need to change the definition of the
condfunction to useTypeVarfor the type parameters. Here's the single line change needed:This comment was generated by an experimental AI tool.