Core module for Spakky framework to support DI/IoC, AOP, Plugin system, and more.
Project description
Spakky
Core module for Spakky Framework - a Spring-inspired dependency injection framework for Python.
Installation
pip install spakky
Or install with plugins:
pip install spakky[fastapi]
pip install spakky[fastapi,kafka,security]
Features
- Dependency Injection: Powerful IoC container with constructor injection
- Aspect-Oriented Programming: Cross-cutting concerns with
@Aspect - Plugin System: Extensible architecture via entry points
- Stereotypes: Semantic annotations (
@Controller,@UseCase, etc.) - Scopes: Singleton, Prototype, and Context-scoped beans
- Type-Safe: Built with Python type hints
- Async First: Native async/await support
Quick Start
Define Pods
from spakky.core.pod.annotations.pod import Pod
@Pod()
class UserRepository:
def find_by_id(self, user_id: int) -> User | None:
# Database query logic
pass
@Pod()
class UserService:
def __init__(self, repository: UserRepository) -> None:
self.repository = repository
def get_user(self, user_id: int) -> User | None:
return self.repository.find_by_id(user_id)
Bootstrap Application
from spakky.core.application.application import SpakkyApplication
from spakky.core.application.application_context import ApplicationContext
import my_app
app = (
SpakkyApplication(ApplicationContext())
.load_plugins()
.scan(my_app) # or .scan() to auto-detect caller's package
.start()
)
# Get a service from the container
user_service = app.container.get(UserService)
📘 Auto-scan: When
scan()is called without arguments, it automatically detects the caller's package and scans it. This also works in Docker environments where the application root may not be insys.path- the framework automatically adds the necessary path.
Pod Scopes
from spakky.core.pod.annotations.pod import Pod
# Singleton (default) - one instance per container
@Pod(scope=Pod.Scope.SINGLETON)
class SingletonService:
pass
# Prototype - new instance on each request
@Pod(scope=Pod.Scope.PROTOTYPE)
class PrototypeService:
pass
# Context - scoped to request/context lifecycle
@Pod(scope=Pod.Scope.CONTEXT)
class ContextScopedService:
pass
Qualifiers
from spakky.core.pod.annotations.pod import Pod
from spakky.core.pod.annotations.primary import Primary
# Named qualifier
@Pod(name="mysql")
class MySQLRepository(IRepository):
pass
@Pod(name="postgres")
class PostgresRepository(IRepository):
pass
# Primary - preferred when multiple implementations exist
@Primary()
@Pod()
class DefaultRepository(IRepository):
pass
Stereotypes
from spakky.core.stereotype.controller import Controller
from spakky.core.stereotype.usecase import UseCase
@Controller()
class UserController:
"""Groups related handlers together."""
pass
@UseCase()
class CreateUserUseCase:
"""Encapsulates business logic."""
pass
Aspect-Oriented Programming
from dataclasses import dataclass
from spakky.core.aop.aspect import Aspect
from spakky.core.aop.interfaces.aspect import IAspect
from spakky.core.aop.pointcut import Before, After
from spakky.core.common.annotation import FunctionAnnotation
from spakky.core.pod.annotations.order import Order
@dataclass
class Traced(FunctionAnnotation): ...
# Create custom aspect
@Order(0)
@Aspect()
class TracingAspect(IAspect):
@Before(lambda m: Traced.exists(m))
def before(self, *args, **kwargs) -> None:
print("Before method execution")
@After(lambda m: Traced.exists(m))
def after(self, *args, **kwargs) -> None:
print("After method execution")
# Apply to methods
@Pod()
class MyService:
@Traced()
def my_method(self) -> str:
return "Hello"
Async Aspects
from spakky.core.aop.aspect import AsyncAspect
from spakky.core.aop.interfaces.aspect import IAsyncAspect
from spakky.core.aop.pointcut import Around
@Order(0)
@AsyncAspect()
class TimingAspect(IAsyncAspect):
@Around(lambda m: hasattr(m, "__timed__"))
async def around_async(self, joinpoint, *args, **kwargs):
start = time.time()
result = await joinpoint(*args, **kwargs)
elapsed = time.time() - start
print(f"Execution time: {elapsed:.2f}s")
return result
Context Management
ApplicationContext provides context-scoped value storage:
from spakky.core.application.application_context import ApplicationContext
context = ApplicationContext()
# Get unique context ID
context_id = context.get_context_id()
# Store and retrieve context values
context.set_context_value("user_id", 123)
user_id = context.get_context_value("user_id") # Returns 123
# Clear context (except system-managed keys)
context.clear_context()
⚠️ Note: System-managed keys like
"__spakky_context_id__"cannot be overridden viaset_context_value().
Tag Registry
ApplicationContext implements ITagRegistry for managing custom metadata tags. Tags are dataclass-based annotations that can be registered and queried at runtime.
Defining Custom Tags
from dataclasses import dataclass
from spakky.core.pod.annotations.tag import Tag
@dataclass(eq=False)
class MyCustomTag(Tag):
"""Custom tag for marking specific components."""
category: str = ""
Registering and Querying Tags
from spakky.core.application.application_context import ApplicationContext
context = ApplicationContext()
# Register tags
tag = MyCustomTag(category="database")
context.register_tag(tag)
# Check if tag exists
exists = context.contains_tag(tag) # True
# Get all tags
all_tags = context.tags # frozenset of all registered tags
# Filter tags with selector
db_tags = context.list_tags(lambda t: isinstance(t, MyCustomTag) and t.category == "database")
Tag Registry Aware Pods
Pods can receive the tag registry via ITagRegistryAware:
from spakky.core.pod.annotations.pod import Pod
from spakky.core.pod.interfaces.aware.tag_registry_aware import ITagRegistryAware
from spakky.core.pod.interfaces.tag_registry import ITagRegistry
@Pod()
class SchemaRegistry(ITagRegistryAware):
def __init__(self) -> None:
self._tag_registry: ITagRegistry | None = None
def set_tag_registry(self, tag_registry: ITagRegistry) -> None:
self._tag_registry = tag_registry
# Access registered tags
for tag in tag_registry.list_tags(MyCustomTag.exists):
# Process tags...
pass
Plugin System
Plugins extend framework functionality through entry points.
Creating a Plugin
- Create package with
uv init --lib spakky-<name>inplugins/directory - Register in root
pyproject.toml's[tool.uv.workspace]members - Define entry point in plugin's
pyproject.toml:
[project.entry-points."spakky.plugins"]
spakky-<name> = "spakky.plugins.<name>.main:initialize"
- Implement initialization function:
# In spakky.plugins.<name>/main.py
from spakky.core.application.application import SpakkyApplication
def initialize(app: SpakkyApplication) -> None:
# Register plugin components
pass
See Contributing Guide for detailed instructions.
Available Plugins
| Plugin | Description |
|---|---|
spakky-fastapi |
FastAPI integration |
spakky-typer |
Typer CLI integration |
spakky-sqlalchemy |
SQLAlchemy ORM integration |
spakky-kafka |
Apache Kafka event system |
spakky-rabbitmq |
RabbitMQ event system |
spakky-celery |
Celery task dispatch |
spakky-logging |
Structured logging with AOP |
spakky-opentelemetry |
OpenTelemetry SDK bridge |
spakky-security |
Security utilities |
Core Modules
| Module | Description |
|---|---|
spakky.core.pod |
Dependency injection container and annotations |
spakky.core.aop |
Aspect-oriented programming framework |
spakky.core.application |
Application context and lifecycle |
spakky.core.stereotype |
Semantic stereotype annotations |
spakky.core.service |
Service lifecycle interfaces |
spakky.core.common |
Core utilities (annotation, types, metadata) |
spakky.core.utils |
Utility functions |
Related Packages
| Package | Description |
|---|---|
spakky-domain |
DDD building blocks (Entity, AggregateRoot, ValueObject, Event) |
spakky-data |
Repository and transaction abstractions |
spakky-event |
Event handling (@EventHandler stereotype) |
spakky-task |
Task queue abstraction (@TaskHandler, @task, @schedule) |
spakky-tracing |
Distributed tracing abstraction (TraceContext, Propagator) |
spakky-outbox |
Transactional Outbox pattern (OutboxEventBus, Relay) |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spakky-6.3.1.tar.gz.
File metadata
- Download URL: spakky-6.3.1.tar.gz
- Upload date:
- Size: 36.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfd7aa619bff1f12c26fb58dba356a8a9938c7ae66e3f5d4d29dd99abfb55b2c
|
|
| MD5 |
2960b8b921bebd5496c2949c6fc3f736
|
|
| BLAKE2b-256 |
2551443509487efa290dc8080453772f6aabff37de9d663bc53bd267bbcc387b
|
Provenance
The following attestation bundles were made for spakky-6.3.1.tar.gz:
Publisher:
release.yml on E5presso/spakky-framework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spakky-6.3.1.tar.gz -
Subject digest:
cfd7aa619bff1f12c26fb58dba356a8a9938c7ae66e3f5d4d29dd99abfb55b2c - Sigstore transparency entry: 1236503986
- Sigstore integration time:
-
Permalink:
E5presso/spakky-framework@6d8469ad090c00a829421ab05aafc6d10178d93f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/E5presso
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6d8469ad090c00a829421ab05aafc6d10178d93f -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file spakky-6.3.1-py3-none-any.whl.
File metadata
- Download URL: spakky-6.3.1-py3-none-any.whl
- Upload date:
- Size: 59.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4f699092a469a209667ad9ba68848d5c97f29114db067d11e90d269cb214511
|
|
| MD5 |
fb59e83f9366e5ee0d8edf721c6b6356
|
|
| BLAKE2b-256 |
234be6cf8aa2baba687b5222c7914a2eae185aa76b1df95e85c2fe4557de156d
|
Provenance
The following attestation bundles were made for spakky-6.3.1-py3-none-any.whl:
Publisher:
release.yml on E5presso/spakky-framework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spakky-6.3.1-py3-none-any.whl -
Subject digest:
d4f699092a469a209667ad9ba68848d5c97f29114db067d11e90d269cb214511 - Sigstore transparency entry: 1236503990
- Sigstore integration time:
-
Permalink:
E5presso/spakky-framework@6d8469ad090c00a829421ab05aafc6d10178d93f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/E5presso
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6d8469ad090c00a829421ab05aafc6d10178d93f -
Trigger Event:
workflow_dispatch
-
Statement type: