19 KiB
🐍 Snakia Framework
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
- 🎯 Roadmap & TODO
- 🚀 Installation
- 🚀 Quick Start
- 🏗️ Architecture
- ⚙️ Core
- 🎯 ECS System
- 📡 Event System (ES)
- 🔌 Plugin System
- 🎨 TUI System
- ⚡ Reactive Programming (RX)
- 🛠️ Utilities
- 🎭 Decorators
- 🏷️ Properties
- 🌐 Platform Abstraction
- 📦 Examples
- 🤝 Contributing
- 🆘 Support
- 📄 License
✨ Key Features
- 🏗️ ECS Architecture - Flexible entity-component-system for scalable game/app logic
- 📡 Event System - Asynchronous event handling with filters and priorities
- 🔌 Plugin System - Modular plugin architecture for extensibility
- 🎨 TUI Framework - Rich terminal user interface with reactive widgets
- ⚡ Reactive Programming - Observable data streams and reactive bindings
- 🛠️ Rich Utilities - Decorators, properties, platform abstraction, and more
- 🎯 Type Safety - Full type hints and Pydantic integration
⚠️ Experimental Framework
This framework is currently in beta/experimental stage. Not all features are fully implemented, there might be bugs, and the API is subject to change. Use at your own risk! 🚧
🚀 Installation
Prerequisites
- Python >= 3.12
- pip or uv (recommended) package manager
Install from PyPi (recommended)
pip install snakia
Install from Source
# Clone the repository
git clone https://github.com/RuJect/Snakia.git
cd Snakia
# Install with pip
pip install -e .
# Or with uv (recommended)
uv sync
🎯 Roadmap & TODO
Here's what we're working on to make Snakia even better:
- Plugin Isolation: restrict plugin access to only events and components statically defined in manifest
- Async & Multithreading: implement proper async/await support and multithreading capabilities
- Platform Support: expand platform abstraction to support more operating systems
- Random Implementations: add various random generations implementations
- TUI Widgets: create more ready-to-use TUI widgets and components
- Code Documentation: add comprehensive docstrings and inline comments
- Documentation: create detailed API documentation and tutorials
🚀 Quick Start
from snakia.core.engine import Engine
from snakia.core.loader import Meta, Plugin, PluginProcessor
from snakia.core.ecs import Component
from snakia.types import Version
# Creating a component
class HealthComponent(Component):
value: int = 100
max_value: int = 100
# Creating a processor
class HealthProcessor(PluginProcessor):
def process(self, system):
for entity, (health,) in system.get_components(HealthComponent):
if health.value <= 0:
print(f"Entity {entity} died!")
# Creating a plugin
class HealthPlugin(Plugin, meta=Meta(
name="health",
version=Version.from_args(1, 0, 0),
processors=(HealthProcessor,)
)):
def on_load(self): pass
def on_unload(self): pass
# Starting the engine
engine = Engine()
engine.loader.register(HealthPlugin)
engine.loader.load_all()
engine.start()
🏗️ Architecture
Snakia is built on a modular architecture with clear separation of concerns:
Snakia/
├── core/ # Framework core
│ ├── engine.py # Main engine
│ ├── ecs/ # Entity-Component-System
│ ├── es/ # Event System
│ ├── loader/ # Plugin loading system
│ ├── rx/ # Reactive programming
│ └── tui/ # Terminal User Interface
├── decorators/ # Decorators
├── property/ # Property system
├── platform/ # Platform abstraction
├── utils/ # Utilities
├── random/ # Random number generation
├── field/ # Typed fields
└── types/ # Special types
⚙️ Core
Engine
The central component of the framework that coordinates all systems:
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:
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:
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:
# 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
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:
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:
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
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:
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:
from snakia.utils import nolock
def busy_loop():
while running:
# Work
nolock() # Release GIL
inherit
Simplified inheritance:
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:
from snakia.utils import this
def func():
return this() # Returns `<function func at ...>`
throw
Throwing exceptions:
from snakia.utils import throw
def validate(value):
if value < 0:
throw(ValueError("Value must be positive"))
frame
Working with frames:
from snakia.utils import frame
def process_frame():
current_frame = frame()
# Process frame
🎭 Decorators
inject_replace
Method replacement:
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:
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:
from snakia.decorators import singleton
@singleton
class Database:
def __init__(self):
self.connection = "connected"
pass_exceptions
Exception handling:
from snakia.decorators import pass_exceptions
@pass_exceptions(ValueError, TypeError)
def risky_function():
# Code that might throw exceptions
pass
🏷️ Properties
readonly
Read-only property:
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:
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:
from snakia.property import classproperty
class MyClass:
@classproperty
def class_value(cls):
return "class_value"
🌐 Platform Abstraction
🖥️ PlatformOS
Operating system abstraction:
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:
from snakia.platform import LinuxLayer, AndroidLayer
# Linux layer
linux_layer = LinuxLayer()
# Android layer
android_layer = AndroidLayer()
📦 Examples
Health System
from snakia.core.engine import Engine
from snakia.core.ecs import Component
from snakia.core.es import Event
from snakia.core.loader import Meta, Plugin, PluginProcessor
from snakia.types import Version
from pydantic import Field
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 DeathEvent(Event):
entity: int = Field()
class HealthProcessor(PluginProcessor):
def process(self, system):
# Processing damage
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))
class HealthPlugin(Plugin, meta=Meta(
name="health",
version=Version.from_args(1, 0, 0),
processors=(HealthProcessor,)
)):
def on_load(self): pass
def on_unload(self): pass
# Usage
engine = Engine()
engine.loader.register(HealthPlugin)
engine.loader.load_all()
# Creating a player
player = engine.system.create_entity(
HealthComponent(value=100, max_value=100)
)
# Dealing damage
engine.system.add_component(player, DamageComponent(damage=25, ticks=1))
engine.start()
TUI Application
from snakia.core.tui import CanvasChar, RenderContext
from snakia.core.tui.render import ANSIRenderer
from snakia.core.tui.widgets import TextWidget, BoxWidget, VerticalSplitWidget
import sys
class StdoutTarget:
def write(self, text: str): sys.stdout.write(text)
def flush(self): sys.stdout.flush()
def main():
# Creating widgets
title = TextWidget("Snakia TUI", CanvasChar(fg_color="cyan", bold=True))
content = TextWidget("Welcome to Snakia!", CanvasChar(fg_color="white"))
box = BoxWidget(20, 5, CanvasChar("█", fg_color="green"))
# Layout
layout = VerticalSplitWidget([title, content, box], "-")
# Rendering
renderer = ANSIRenderer(StdoutTarget())
with RenderContext(renderer) as ctx:
ctx.render(layout.render())
if __name__ == "__main__":
main()
🤝 Contributing
We welcome contributions to Snakia development! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
How to Contribute
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests if applicable
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Add type hints to all new code
- Write clear commit messages
- Update documentation for new features
- Test your changes thoroughly
🆘 Support
Need help? We're here to assist you!
- 🐛 Bug Reports - GitHub Issues
- 💬 Community Chat - RuJect Community Telegram
- 📧 Direct Contact - mailto:rus07tam.uwu@gmail.com
📄 License
See the LICENSE file for details.