A Spring Boot-inspired Python web framework built on Starlette, combining the elegance of Spring's dependency injection with Python's simplicity
Project description
StarSpring
A Spring Boot-inspired Python web framework built on Starlette, combining the elegance of Spring's dependency injection with Python's simplicity.
Table of Contents
- What is StarSpring?
- Key Features
- Framework Comparison
- Core Concepts
- Installation
- Quick Start
- Database & ORM
- Building REST APIs
- Working with Templates
- Middleware
- Security
- Advanced Features
- Examples
- Contributing
- License
What is StarSpring?
StarSpring is a modern Python web framework that brings Spring Boot's proven architectural patterns to the Python ecosystem. It provides:
- Dependency Injection: Automatic component scanning and dependency resolution
- Declarative Programming: Annotations-based routing, services, and repositories
- Built-in ORM: SQLAlchemy integration with Spring Data-style repositories
- Convention over Configuration: Auto-configuration with sensible defaults
- Production-Ready: Built on Starlette for high performance and ASGI support
StarSpring is ideal for developers who appreciate Spring Boot's structure but prefer Python's ecosystem, or Python developers looking for a more opinionated, enterprise-ready framework.
What's New in v0.2.2 🎉
starspring-security Integration
Password hashing is now built into StarSpring via the companion starspring-security package — automatically installed with StarSpring.
from starspring_security import BCryptPasswordEncoder
encoder = BCryptPasswordEncoder()
hashed = encoder.encode("my_password") # BCrypt hash
encoder.matches("my_password", hashed) # True
Session-Based Authentication
First-class support for session-based auth using Starlette's SessionMiddleware.
from starlette.middleware.sessions import SessionMiddleware
app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
What's New in v0.2.0
Automatic Form Data Parsing
HTML forms are now automatically parsed into Pydantic models - no more manual await request.form()!
@PostMapping("/users")
async def create_user(self, form: UserCreateForm): # Automatic parsing!
user = await self.user_service.create(form.name, form.email, form.age)
return ModelAndView("success.html", {"user": user}, status_code=201)
Custom HTTP Status Codes
Set custom status codes for template responses:
return ModelAndView("users/created.html", {"user": user}, status_code=201)
return ModelAndView("errors/404.html", {"message": "Not found"}, status_code=404)
HTTP Method Override
Use PUT, PATCH, and DELETE methods in HTML forms with the _method field:
<form action="/users/{{ user.id }}" method="POST">
<input type="hidden" name="_method" value="PUT">
<!-- form fields -->
</form>
@PutMapping("/users/{id}") # Works with HTML forms!
async def update_user(self, id: int, form: UserForm):
# Handles both API requests and form submissions
Key Features
Dependency Injection & IoC Container
Automatic component scanning, registration, and dependency resolution with constructor injection.
Declarative Routing
Define REST endpoints using familiar decorators like @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping.
Spring Data-Style Repositories
Auto-implemented repository methods based on naming conventions:
async def find_by_username(self, username: str) -> User | None:
pass # Automatically implemented!
Built-in ORM
SQLAlchemy integration with automatic table creation, transaction management, and entity mapping.
Auto-Configuration
Automatically loads application.yaml and scans common module names (controller, service, repository, entity).
Template Support
Jinja2 integration for server-side rendering with the @TemplateMapping decorator.
Middleware Stack
Built-in exception handling, logging, CORS support, and custom middleware capabilities.
Type Safety
Full type hint support with automatic parameter injection and validation.
Framework Comparison
| Feature | StarSpring | FastAPI | Flask | Django |
|---|---|---|---|---|
| Layered Architecture | Yes | No | No | Yes |
| Dependency Injection | Built-in IoC | Function params | Manual/Extensions | Manual |
| Built-in ORM | Yes (SQLAlchemy) | No | Extension | Yes (Django ORM) |
| Auto-Configuration | Yes | No | No | Partial |
| Auto-Repositories | Yes | No | No | No |
| Async Support | Native (ASGI) | Native (ASGI) | Limited | Partial |
| Template Engine | Jinja2 | Manual | Jinja2 | Django Templates |
| Admin Interface | No | No | Extensions | Yes |
When to choose StarSpring:
- You're familiar with Spring Boot and want similar patterns in Python
- Building API-first or microservice architectures
- Need strong separation of concerns (Controller/Service/Repository)
- Want automatic dependency injection and component scanning
- Prefer convention over configuration
Core Concepts
Application Context
The IoC (Inversion of Control) container that manages component lifecycle and dependencies.
Components
Classes decorated with @Controller, @Service, or @Repository are automatically discovered and registered.
Dependency Injection
Components are injected via constructor parameters:
@Controller("/api/users")
class UserController:
def __init__(self, user_service: UserService):
self.user_service = user_service
Entities
Domain models decorated with @Entity that map to database tables.
Repositories
Data access layer with auto-implemented query methods based on method names.
Services
Business logic layer, typically transactional, that orchestrates repositories.
Controllers
HTTP request handlers that define REST endpoints and return responses.
Installation
Requirements
- Python 3.10 or higher
- pip or uv package manager
Install from PyPI
pip install starspring
Install for Development
git clone https://github.com/DasunNethsara-04/starspring.git
cd starspring
pip install -e .
Optional Dependencies
For PostgreSQL support:
pip install psycopg2-binary
# or for async
pip install asyncpg
For MySQL support:
pip install pymysql
Quick Start
1. Create Project Structure
my_app/
├── entity.py
├── repository.py
├── service.py
├── controller.py
├── main.py
└── application.yaml
2. Configure Database (application.yaml)
database:
url: "sqlite:///app.db"
ddl-auto: "create-if-not-exists"
server:
port: 8000
host: "0.0.0.0"
3. Define Entity (entity.py)
from starspring import BaseEntity, Column, Entity
@Entity(table_name="users")
class User(BaseEntity):
# BaseEntity provides: id, created_at, updated_at
username = Column(type=str, unique=True, nullable=False)
email = Column(type=str, unique=True, nullable=False)
is_active = Column(type=bool, default=True)
role = Column(type=str, default="USER")
4. Create Repository (repository.py)
from starspring import Repository, StarRepository
from entity import User
@Repository
class UserRepository(StarRepository[User]):
# Auto-implemented based on method name!
async def find_by_username(self, username: str) -> User | None:
pass
async def find_by_email(self, email: str) -> User | None:
pass
5. Implement Service (service.py)
from starspring import Service, Transactional
from entity import User
from repository import UserRepository
@Service
class UserService:
def __init__(self, user_repo: UserRepository):
self.user_repo = user_repo
@Transactional
async def create_user(self, username: str, email: str) -> User:
# Check if user exists
existing = await self.user_repo.find_by_email(email)
if existing:
raise ValueError(f"User with email {email} already exists")
# Create and save
user = User(username=username, email=email)
return await self.user_repo.save(user)
async def get_user(self, username: str) -> User | None:
return await self.user_repo.find_by_username(username)
6. Build Controller (controller.py)
from starspring import Controller, GetMapping, PostMapping, ResponseEntity
from service import UserService
@Controller("/api/users")
class UserController:
def __init__(self, user_service: UserService):
self.user_service = user_service
@PostMapping("/register")
async def register(self, username: str, email: str):
try:
user = await self.user_service.create_user(username, email)
return ResponseEntity.created(user)
except ValueError as e:
return ResponseEntity.bad_request(str(e))
@GetMapping("/{username}")
async def get_user(self, username: str):
user = await self.user_service.get_user(username)
if user:
return ResponseEntity.ok(user)
return ResponseEntity.not_found()
@GetMapping("")
async def list_users(self):
users = await self.user_service.user_repo.find_all()
return ResponseEntity.ok(users)
7. Bootstrap Application (main.py)
from starspring import StarSpringApplication
app = StarSpringApplication(title="My User API")
if __name__ == "__main__":
app.run()
8. Run the Application
python main.py
Your API is now running at http://localhost:8000!
Test it:
# Register a user
curl -X POST http://localhost:8000/api/users/register \
-H "Content-Type: application/json" \
-d '{"username": "john", "email": "john@example.com"}'
# Get user
curl http://localhost:8000/api/users/john
# List all users
curl http://localhost:8000/api/users
Database & ORM
Configuration
StarSpring uses SQLAlchemy under the hood. Configure your database in application.yaml:
database:
url: "postgresql://user:password@localhost/mydb"
ddl-auto: "create-if-not-exists" # Options: create, create-if-not-exists, update, none
pool-size: 10
max-overflow: 20
Supported databases:
- SQLite:
sqlite:///app.db - PostgreSQL:
postgresql://user:pass@host/db - MySQL:
mysql://user:pass@host/db
Defining Entities
Entities are Python classes decorated with @Entity:
from datetime import datetime
from starspring import BaseEntity, Column, Entity
@Entity(table_name="posts")
class Post(BaseEntity):
title = Column(type=str, nullable=False, length=200)
content = Column(type=str, nullable=False)
published = Column(type=bool, default=False)
author_id = Column(type=int, nullable=False)
published_at = Column(type=datetime, nullable=True)
BaseEntity provides:
id: Auto-incrementing primary keycreated_at: Timestamp of creationupdated_at: Timestamp of last update
Column Options
Column(
type=str, # Python type (str, int, bool, datetime)
nullable=True, # Allow NULL values
unique=False, # Unique constraint
default=None, # Default value
length=None # Max length for strings
)
Repository Patterns
StarSpring provides Spring Data-style repositories with auto-implemented methods:
from starspring.data.query_builder import QueryOperation
@Repository
class PostRepository(StarRepository[Post]):
# Find by single field
async def find_by_title(self, title: str) -> Post | None:
pass
# Find by multiple fields
async def find_by_author_id_and_published(
self, author_id: int, published: bool
) -> list[Post]:
pass
# Custom queries (manual implementation)
async def find_recent_posts(self, limit: int = 10) -> list[Post]:
# Implement custom logic here
return await self._gateway.execute_query(
"SELECT * FROM posts ORDER BY created_at DESC LIMIT :limit",
{"limit": limit},
Post,
QueryOperation.FIND
)
Auto-implemented method patterns:
find_by_<field>- Find by single fieldfind_by_<field>_and_<field>- Find by multiple fieldscount_by_<field>- Count matching recordsdelete_by_<field>- Delete matching recordsexists_by_<field>- Check if exists
Built-in methods:
save(entity)- Insert or updatefind_by_id(id)- Find by primary keyfind_all()- Get all recordsdelete(entity)- Delete entitycount()- Count all records
Transactions
Use the @Transactional decorator for automatic transaction management:
from starspring import Service, Transactional
@Service
class PostService:
def __init__(self, post_repo: PostRepository):
self.post_repo = post_repo
@Transactional
async def publish_post(self, post_id: int) -> Post:
post = await self.post_repo.find_by_id(post_id)
if not post:
raise ValueError("Post not found")
post.published = True
post.published_at = datetime.now()
return await self.post_repo.save(post)
Building REST APIs
Request Mapping Decorators
from starspring import (
Controller,
GetMapping,
PostMapping,
PutMapping,
DeleteMapping,
PatchMapping
)
@Controller("/api/posts")
class PostController:
@GetMapping("")
async def list_posts(self):
# GET /api/posts
pass
@GetMapping("/{id}")
async def get_post(self, id: int):
# GET /api/posts/123
pass
@PostMapping("")
async def create_post(self, title: str, content: str):
# POST /api/posts
pass
@PutMapping("/{id}")
async def update_post(self, id: int, title: str, content: str):
# PUT /api/posts/123
pass
@DeleteMapping("/{id}")
async def delete_post(self, id: int):
# DELETE /api/posts/123
pass
Parameter Injection
StarSpring automatically injects parameters from:
- Path variables:
/{id}→id: int - Query parameters:
?page=1→page: int - Request body: JSON payload → function parameters
@GetMapping("/search")
async def search_posts(
self,
query: str, # From ?query=...
page: int = 1, # From ?page=... (default: 1)
limit: int = 10 # From ?limit=... (default: 10)
):
# Implementation
pass
Response Handling
Return entity directly:
@GetMapping("/{id}")
async def get_post(self, id: int):
post = await self.post_service.get_post(id)
return post # Auto-serialized to JSON
Use ResponseEntity for control:
from starspring import ResponseEntity
@GetMapping("/{id}")
async def get_post(self, id: int):
post = await self.post_service.get_post(id)
if post:
return ResponseEntity.ok(post)
return ResponseEntity.not_found()
ResponseEntity methods:
ResponseEntity.ok(body)- 200 OKResponseEntity.created(body)- 201 CreatedResponseEntity.accepted(body)- 202 AcceptedResponseEntity.no_content()- 204 No ContentResponseEntity.bad_request(body)- 400 Bad RequestResponseEntity.unauthorized(body)- 401 UnauthorizedResponseEntity.forbidden(body)- 403 ForbiddenResponseEntity.not_found(body)- 404 Not FoundResponseEntity.status(code, body)- Custom status
Request Body Validation
from pydantic import BaseModel
class CreatePostRequest(BaseModel):
title: str
content: str
published: bool = False
@PostMapping("")
async def create_post(self, request: CreatePostRequest):
# Pydantic validates automatically
post = await self.post_service.create_post(
title=request.title,
content=request.content,
published=request.published
)
return ResponseEntity.created(post)
Working with Templates
StarSpring integrates Jinja2 for server-side rendering.
Setup Templates Directory
my_app/
├── templates/
│ ├── index.html
│ ├── post.html
│ └── layout.html
├── static/
│ ├── css/
│ └── js/
└── main.py
Configure Template Path
from starspring import StarSpringApplication
app = StarSpringApplication(title="My Blog")
app.add_template_directory("templates")
app.add_static_files("/static", "static")
Template Controller
from starspring import TemplateController, GetMapping, ModelAndView
@TemplateController("")
class WebController:
def __init__(self, post_service: PostService):
self.post_service = post_service
@GetMapping("/")
async def index(self) -> ModelAndView:
posts = await self.post_service.get_recent_posts()
return ModelAndView("index.html", {
"posts": posts,
"title": "My Blog"
})
@GetMapping("/post/{id}")
async def view_post(self, id: int) -> ModelAndView:
post = await self.post_service.get_post(id)
return ModelAndView("post.html", {"post": post})
Template Files
templates/layout.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Blog{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav>
<a href="/">Home</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
templates/index.html:
{% extends "layout.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h1>Recent Posts</h1>
{% for post in posts %}
<article>
<h2><a href="/post/{{ post.id }}">{{ post.title }}</a></h2>
<p>{{ post.content[:200] }}...</p>
<small>{{ post.created_at }}</small>
</article>
{% endfor %}
{% endblock %}
Middleware
Built-in Middleware
StarSpring includes:
- ExceptionHandlerMiddleware: Catches and formats exceptions
- LoggingMiddleware: Logs requests and responses
- CORSMiddleware: Handles Cross-Origin Resource Sharing
Enable CORS
from starspring import StarSpringApplication
from starspring.middleware.cors import CORSConfig
app = StarSpringApplication(title="My API")
cors_config = CORSConfig(
allow_origins=["http://localhost:3000"],
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
allow_credentials=True
)
app.add_cors(cors_config)
Custom Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
class CustomMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Before request
print(f"Request: {request.method} {request.url}")
# Process request
response = await call_next(request)
# After request
print(f"Response: {response.status_code}")
return response
# Add to application
from starlette.middleware import Middleware
app = StarSpringApplication(title="My API")
app._middleware.append(Middleware(CustomMiddleware))
Security
Password Hashing with starspring-security
StarSpring ships with starspring-security — a standalone password hashing library that works with any Python framework.
BCrypt (Default — Recommended)
from starspring_security import BCryptPasswordEncoder
encoder = BCryptPasswordEncoder() # Default: rounds=12
encoder = BCryptPasswordEncoder(rounds=14) # Stronger (slower)
hashed = encoder.encode("my_password")
encoder.matches("my_password", hashed) # True
encoder.matches("wrong", hashed) # False
Argon2 (Modern, Memory-Hard)
pip install starspring-security[argon2]
from starspring_security import Argon2PasswordEncoder
encoder = Argon2PasswordEncoder()
hashed = encoder.encode("my_password")
encoder.matches("my_password", hashed) # True
SHA-256 (Deprecated ⚠️)
from starspring_security import Sha256PasswordEncoder
encoder = Sha256PasswordEncoder() # Raises DeprecationWarning
SHA-256 is too fast for passwords and vulnerable to brute-force attacks. Use BCrypt or Argon2 instead.
Session-Based Authentication
StarSpring supports session-based authentication via Starlette's SessionMiddleware. Sessions are signed using itsdangerous — tamper-proof out of the box.
Setup
# main.py
from starspring import StarSpringApplication
from starlette.middleware.sessions import SessionMiddleware
app = StarSpringApplication(title="My App")
app.add_middleware(SessionMiddleware, secret_key="your-strong-secret-key")
app.run()
Using Sessions in Controllers
Inject Request into any controller method to read or write session data:
from starlette.requests import Request
from starlette.responses import RedirectResponse
from starspring import TemplateController, GetMapping, PostMapping, ModelAndView
from starspring_security import BCryptPasswordEncoder
encoder = BCryptPasswordEncoder()
@TemplateController("")
class AuthController:
def __init__(self, user_service: UserService):
self.user_service = user_service
@PostMapping("/login")
async def login(self, form: LoginForm, request: Request):
user = await self.user_service.find_by_username(form.username)
if not user or not encoder.matches(form.password, user.password):
return ModelAndView("login.html", {"error": "Invalid credentials"}, status_code=401)
# Store user info in session
request.session["username"] = user.username
request.session["role"] = user.role
return RedirectResponse(url="/dashboard", status_code=302)
@GetMapping("/logout")
async def logout(self, request: Request):
request.session.clear()
return RedirectResponse(url="/login", status_code=302)
@GetMapping("/dashboard")
async def dashboard(self, request: Request):
if not request.session.get("username"):
return RedirectResponse(url="/login", status_code=302)
return ModelAndView("dashboard.html", {
"username": request.session["username"],
"role": request.session["role"],
})
Role-Based Authorization
@GetMapping("/admin/users")
async def admin_users(self, request: Request):
if not request.session.get("username"):
return RedirectResponse(url="/login", status_code=302)
if request.session.get("role") != "ADMIN":
return RedirectResponse(url="/dashboard", status_code=302) # Forbidden
# ... admin logic
application.yaml Configuration
server:
host: "0.0.0.0"
port: 8000
database:
url: "sqlite:///app.db"
ddl-auto: "create-if-not-exists"
Note: Keep your
secret_keysecret and never commit it to version control. Use environment variables in production.
Advanced Features
Lifecycle Hooks
app = StarSpringApplication(title="My API")
@app.on_startup
async def startup():
print("Application starting...")
# Initialize resources
@app.on_shutdown
async def shutdown():
print("Application shutting down...")
# Cleanup resources
Environment-Specific Configuration
# application.yaml
spring:
profiles:
active: ${ENVIRONMENT:development}
---
# Development profile
spring:
profiles: development
database:
url: "sqlite:///dev.db"
---
# Production profile
spring:
profiles: production
database:
url: "postgresql://user:pass@prod-db/myapp"
Manual Component Registration
from starspring.core.context import get_application_context
# Register bean manually
context = get_application_context()
context.register_bean("my_service", MyService())
# Get bean
service = context.get_bean(MyService)
Custom Repository Methods
from starspring.data.query_builder import QueryOperation
@Repository
class PostRepository(StarRepository[Post]):
async def find_published_posts(self) -> list[Post]:
# Custom SQL query
return await self._gateway.execute_query(
"SELECT * FROM posts WHERE published = :published",
{"published": True},
Post,
QueryOperation.FIND
)
Examples
Complete examples are available in the examples/ directory:
- examples/example1: Basic CRUD API with users
- examples/example2: Blog application with templates and form handling
- examples/example3: Session-based authentication with role-based authorization and Bootstrap 5 UI
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
StarSpring is released under the MIT License. See LICENSE file for details.
Support
- Documentation: https://github.com/DasunNethsara-04/starspring
- Issues: https://github.com/DasunNethsara-04/starspring/issues
- Discussions: https://github.com/DasunNethsara-04/starspring/discussions
Built with love for the Python community by developers who appreciate Spring Boot's elegance.
Project details
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 starspring-0.2.2.tar.gz.
File metadata
- Download URL: starspring-0.2.2.tar.gz
- Upload date:
- Size: 51.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.14.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72d5966e05dbe262203d531784407c28dc4d148890ab27f8a5df67c28b899dc7
|
|
| MD5 |
e54726c05249233a232d38e557f5320e
|
|
| BLAKE2b-256 |
94216ef0f3e45391fb42a7f0c1abe3c17502ee9d3ac2a2b0e6e82bd5d0face8f
|
File details
Details for the file starspring-0.2.2-py3-none-any.whl.
File metadata
- Download URL: starspring-0.2.2-py3-none-any.whl
- Upload date:
- Size: 54.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.14.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d96a31ddbda32bf4d6071c0a56a62c709bd76e67a884d71c3faed80b51c6f697
|
|
| MD5 |
c1e2d2ee86de475774b76a44d761f8de
|
|
| BLAKE2b-256 |
54a91de22f3a4501ac02a43159f841b631c54a2b1b97e03821a88d5bc4dfa09e
|