Modern python framework https://ruject.github.io/snakia/
Find a file
2025-10-26 16:26:46 +00:00
.github/workflows initial commit 2025-10-26 16:26:46 +00:00
docs initial commit 2025-10-26 16:26:46 +00:00
examples initial commit 2025-10-26 16:26:46 +00:00
src/snakia initial commit 2025-10-26 16:26:46 +00:00
.envrc initial commit 2025-10-26 16:26:46 +00:00
.gitignore initial commit 2025-10-26 16:26:46 +00:00
flake.lock initial commit 2025-10-26 16:26:46 +00:00
flake.nix initial commit 2025-10-26 16:26:46 +00:00
LICENSE initial commit 2025-10-26 16:26:46 +00:00
pyproject.toml initial commit 2025-10-26 16:26:46 +00:00
README.md initial commit 2025-10-26 16:26:46 +00:00
requirements.txt initial commit 2025-10-26 16:26:46 +00:00
uv.lock initial commit 2025-10-26 16:26:46 +00:00

🐍 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

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
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

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests if applicable
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. 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!

📄 License

See the LICENSE file for details.