Quick Start Guide
Get up and running with SPOC in minutes. This guide walks you through building your first SPOC application from scratch.
What You'll Build
By the end of this guide, you'll have a working SPOC application that:
- Organizes code into modular apps
- Manages dependencies between modules
- Registers and discovers components
- Handles lifecycle events (startup/shutdown)
Prerequisites
- Python 3.13 or higher
- Basic understanding of Python modules and classes
Installation
Install SPOC using pip:
Project Structure
SPOC applications follow a Django-like structure with an apps directory containing your modular applications.
graph TD
A{my_project/} --> B{apps/}
A --> C{config/}
A --> D[main.py]
B --> E[blog/]
B --> F[users/]
E --> E1[models.py]
E --> E2[views.py]
F --> F1[models.py]
F --> F2[views.py]
C --> C1[settings.py]
C --> C2[spoc.toml]
C --> C3[__init__.py]
Create your project structure:
my_project/
├── apps/
│ ├── blog/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── views.py
│ └── users/
│ ├── __init__.py
│ ├── models.py
│ └── views.py
├── config/
│ ├── __init__.py
│ ├── settings.py
│ └── spoc.toml
└── main.py
Step 1: Configure Your Project
Create Settings
Create config/settings.py:
"""Project settings configuration."""
from pathlib import Path
# Base directory of the project
BASE_DIR: Path = Path(__file__).resolve().parent.parent
# Apps to load (always installed, regardless of mode)
INSTALLED_APPS: list = [
"blog",
]
# Plugins for extending functionality
PLUGINS: dict = {
"middleware": [],
"hooks": [],
}
Create SPOC Configuration
Create config/spoc.toml:
# Application Configuration
[spoc]
mode = "development" # Options: development, staging, production
debug = true
# Apps by Environment Mode
[spoc.apps]
production = ["users"]
staging = []
development = []
# Additional Plugins
[spoc.plugins]
middleware = []
hooks = []
How it works:
- Apps in
INSTALLED_APPSare always loaded - Apps in
[spoc.apps.development]load only in development mode - Apps in
[spoc.apps.production]load in production mode - The mode determines which apps are active
Step 2: Define Components
Components are reusable units of functionality that SPOC can discover and manage.
Create Models
Create apps/blog/models.py:
"""Blog models."""
import dataclasses as dc
from spoc import Components
# Create a component registry
components = Components()
components.add_type("model")
@dc.dataclass
@components.register("model")
class Post:
"""Blog post model."""
id: int
title: str
content: str
author_id: int
@dc.dataclass
@components.register("model")
class Comment:
"""Comment model."""
id: int
post_id: int
text: str
author_id: int
Create apps/users/models.py:
"""User models."""
import dataclasses as dc
from spoc import Components
components = Components()
components.add_type("model")
@dc.dataclass
@components.register("model")
class User:
"""User model."""
id: int
username: str
email: str
Create Views
Create apps/blog/views.py:
"""Blog views."""
from spoc import Components
components = Components()
components.add_type("view")
@components.register("view")
def list_posts():
"""List all blog posts."""
return {"action": "list_posts", "status": "success"}
@components.register("view")
def create_post():
"""Create a new blog post."""
return {"action": "create_post", "status": "success"}
Step 3: Define Your Schema
The Schema defines which modules to load, their dependencies, and lifecycle hooks.
Create main.py:
"""Main application entry point."""
from pathlib import Path
from spoc import Framework, Schema, Hook
from config import settings
# Define the application schema
schema = Schema(
# Modules to load from each app
modules=["models", "views"],
# Module dependencies (views depend on models)
dependencies={
"views": ["models"],
},
# Lifecycle hooks
hooks={
"models": Hook(
startup=lambda m: print(f"✓ Loaded models: {m.__name__}"),
shutdown=lambda m: print(f"✗ Unloading models: {m.__name__}"),
),
"views": Hook(
startup=lambda m: print(f"✓ Loaded views: {m.__name__}"),
shutdown=lambda m: print(f"✗ Unloading views: {m.__name__}"),
),
},
)
# Create the framework instance
framework = Framework(
base_dir=settings.BASE_DIR,
schema=schema,
echo=False, # Set to True for debug output
mode="strict", # "strict" enforces all modules exist, "loose" allows missing
)
def main():
"""Run the application."""
print("\n=== SPOC Application Started ===\n")
# Access installed apps
print(f"Installed apps: {framework.installed_apps}")
# Get all models
print("\n--- Registered Models ---")
if hasattr(framework.components, 'models'):
for name, model in framework.components.models.items():
print(f" • {name}: {model}")
# Get all views
print("\n--- Registered Views ---")
if hasattr(framework.components, 'views'):
for name, view in framework.components.views.items():
print(f" • {name}: {view}")
# Get a specific component
print("\n--- Using Components ---")
list_posts = framework.get_component("views", "blog.list_posts")
if list_posts:
result = list_posts()
print(f"list_posts() returned: {result}")
print("\n=== Application Running ===\n")
# When done, shutdown gracefully
framework.shutdown()
print("\n=== Application Stopped ===\n")
if __name__ == "__main__":
main()
Step 4: Run Your Application
Execute your application:
Expected output:
apps_path C:\path\to\my_project\apps
✓ Loaded models: blog.models
✓ Loaded models: users.models
✓ Loaded views: blog.views
=== SPOC Application Started ===
Installed apps: ['blog', 'users']
--- Registered Models ---
• blog.Post: <class 'blog.models.Post'>
• blog.Comment: <class 'blog.models.Comment'>
• users.User: <class 'users.models.User'>
--- Registered Views ---
• blog.list_posts: <function list_posts at 0x...>
• blog.create_post: <function create_post at 0x...>
--- Using Components ---
list_posts() returned: {'action': 'list_posts', 'status': 'success'}
=== Application Running ===
✗ Unloading views: blog.views
✗ Unloading models: users.models
✗ Unloading models: blog.models
=== Application Stopped ===
Understanding Key Concepts
1. Schema
The Schema defines your application's structure:
schema = Schema(
modules=["models", "views", "services"],
dependencies={
"views": ["models"],
"services": ["models"],
},
hooks={
"models": Hook(startup=init_db, shutdown=close_db),
},
)
- modules: List of module names to load from each app
- dependencies: Dictionary mapping modules to their required dependencies
- hooks: Lifecycle callbacks for module startup/shutdown
2. Framework
The Framework orchestrates your application:
- base_dir: Root directory of your project
- schema: Schema defining modules and dependencies
- echo: Enable debug output (default: False)
- mode:
"strict"- All modules must exist in all apps"loose"- Apps can have missing modules
3. Components
Components are discoverable units with metadata:
from spoc import Components
components = Components()
components.add_type("service")
@components.register("service", config={"timeout": 30})
class EmailService:
pass
Access components via the framework:
# Get a specific component
service = framework.get_component("service", "notifications.EmailService")
# Get all components of a type
all_services = framework.components.service.values()
4. Lifecycle Hooks
Hooks execute during module loading/unloading:
def init_database(module):
"""Called when module loads."""
print(f"Initializing database for {module.__name__}")
# Setup database connections, create tables, etc.
def close_database(module):
"""Called when module unloads."""
print(f"Closing database for {module.__name__}")
# Close connections, cleanup resources
hooks = {
"models": Hook(
startup=init_database,
shutdown=close_database,
),
}
Application Lifecycle
The framework follows this lifecycle:
sequenceDiagram
participant User
participant Framework
participant Apps
participant Modules
User->>Framework: Create Framework(base_dir, schema)
Framework->>Framework: Load configuration
Framework->>Framework: Register apps
Framework->>Apps: Discover installed apps
User->>Framework: startup()
Framework->>Modules: Load modules by dependency order
Framework->>Modules: Execute startup hooks
Framework->>Framework: Register components
Note over User,Framework: Application runs...
User->>Framework: shutdown()
Framework->>Modules: Execute shutdown hooks (reverse order)
Framework->>Modules: Unload modules
Automatic Startup: The Framework calls startup() automatically during initialization.
Common Patterns
Pattern 1: Shared Component Registry
Create a shared registry for consistent component types across your app:
# framework/components.py
from spoc import Components
components = Components()
components.add_type("model")
components.add_type("view")
components.add_type("service")
# Export decorators
def model(obj):
return components.register("model", obj)
def view(obj):
return components.register("view", obj)
def service(obj):
return components.register("service", obj)
Use in your apps:
Pattern 2: Dependency Injection
Access framework components from anywhere:
class PostService:
def __init__(self, framework):
self.framework = framework
def create_post(self, title, content):
# Get the Post model
Post = self.framework.get_component("model", "blog.Post")
return Post(id=1, title=title, content=content)
Pattern 3: Environment-Based Apps
Control which apps load based on environment:
# spoc.toml
[spoc]
mode = "production"
[spoc.apps]
production = ["core", "api"]
development = ["core", "api", "debug", "docs"]
Next Steps
Now that you have a working SPOC application:
- Learn about Configuration: See Configuration Guide for advanced settings
- Explore Components: Read Components API for metadata and validation
- Study the Framework: Check Framework API for advanced features
- Review Examples: Browse the
examples/directory in the repository
Troubleshooting
Apps Not Loading
Problem: Apps aren't being discovered.
Solution: Ensure:
- Apps are in the apps/ directory
- Each app has an __init__.py file
- Apps are listed in INSTALLED_APPS or [spoc.apps.<mode>]
Module Not Found
Problem: ModuleNotFoundError when loading modules.
Solution: Check:
- Module name matches the file name (e.g., models.py for "models")
- Module is listed in schema.modules
- Dependencies are declared correctly
Circular Dependencies
Problem: CircularDependencyError during startup.
Solution:
- Review schema.dependencies for circular references
- Restructure dependencies to remove cycles
- Consider splitting modules differently
Strict Mode Errors
Problem: Framework errors about missing modules.
Solution:
- Set mode="loose" to allow missing modules
- Or ensure all apps have all required modules
- Or remove apps that don't match the schema
Summary
You've learned how to:
- Structure a SPOC project with apps
- Define schemas with modules, dependencies, and hooks
- Register and discover components
- Manage application lifecycle
- Access components from the framework
SPOC provides a solid foundation for building modular, maintainable Python applications. Continue exploring the documentation to unlock its full potential.