snakeboot
Spring Boot ergonomics for hexagonal Python applications built with Django, Django Ninja and wireup.
snakeboot imitates the basics of Spring Boot, not Spring itself. Each part is a handful of small classes you can read in an afternoon, and none of them knows anything about your domain.
| snakeboot | Spring Boot | Needs |
|---|---|---|
@transactional, after_commit |
@Transactional, @TransactionalEventListener(AFTER_COMMIT) |
nothing |
@application_root, ComponentScan |
@SpringBootApplication, @ComponentScan |
nothing |
ApplicationEventPublisher, @event_listener, @transactional_event_listener |
the same names | wireup |
@rest_controller, @route.get, @rest_controller_advice, @exception_handler |
@RestController, @GetMapping, @RestControllerAdvice, @ExceptionHandler |
snakeboot[ninja] |
DjangoAtomicTransactionManager |
PlatformTransactionManager for JDBC |
snakeboot[django] |
RecordingTransactionManager, declared_classes |
spring-test | nothing |
The transaction, component scan and testing packages import nothing but the standard library,
so the application layer of a hexagonal application can use @transactional without importing Django.
Install
uv add "snakeboot[django,ninja]"
A tour
The application root. The component scan starts in the package of the marked class, like
@SpringBootApplication. It skips Django's entry points (wsgi.py, asgi.py, migrations) and
adds the components snakeboot provides itself (the event publisher).
# myapp/myapp_application.py
@application_root
class MyApplication: ...
# settings.py: the setting wireup needs anyway
WIREUP = WireupSettings(injectables=[*ComponentScan.of(MyApplication).module_names()], auto_inject_views=False)
Transactions. A use case declares its boundary; composition chooses the technology.
@injectable
class CancelRegistrationUseCase:
@transactional
def execute(self, request: CancelRegistrationRequestDTO) -> None:
workshop = self._workshops.get(...)
workshop.cancel_registration(...)
self._workshops.save(workshop)
self._events.publish_all(workshop.pull_events())
# composition, at start-up
TransactionManagerRegistry.install(DjangoAtomicTransactionManager())
Application events. Listeners react to facts after the commit, so a rolled-back change never reaches anyone, and a failing listener cannot undo a committed one.
@application_event_listener
class ParticipantNotificationListener:
def __init__(self, notifications: NotificationPort) -> None:
self._notifications = notifications
@transactional_event_listener(WaitlistedParticipantPromoted)
def notify_promoted(self, event: WaitlistedParticipantPromoted) -> None: ...
REST controllers. Singleton classes with constructor injection, found by the component scan. Two endpoints for the same URL, or two answers for one error, stop the start-up.
@rest_controller("/workshops", tags=["workshops"])
class WorkshopRestController:
def __init__(self, enrollment: EnrollmentFacadePort) -> None:
self._enrollment = enrollment
@route.get("/{workshop_id}", response=WorkshopViewDTO)
def get_workshop(self, request: HttpRequest, workshop_id: str) -> WorkshopViewDTO: ...
@rest_controller_advice
class EnrollmentRestErrorAdvice:
@exception_handler(WorkshopNotFoundError, status=404)
def answer_not_found(self, error: Exception) -> dict[str, str]:
return error_answer(error) # {"code": "workshop_not_found", "detail": "..."}
# urls.py
mounted = RestControllerMounter().mount(api, scan=settings.WIREUP.injectables or ())
Design rules
- Imitate the basics, not all of Spring: no AOP, no profiles, no scheduling, no message brokers.
- Fail at start-up, not at the first request: a broken controller, listener or root stops the application.
- Each package knows as little as possible;
uv run lint-importsenforces it.
Develop
uv sync
uv run pre-commit install
uv run pytest # tests, including the import contracts
uv run ruff check # Clean Code rules
uv run mypy # strict
Metadata
Release files for snakeboot 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| snakeboot-0.1.2.tar.gz | 21.9 kB | Without provenance | Details for snakeboot-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| snakeboot-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for snakeboot-0.1.2-py3-none-any.whl |
Total release size: 53.6 kB
Release files / snakeboot-0.1.2.tar.gz
| Download URL | snakeboot-0.1.2.tar.gz |
|---|---|
| Size | 21.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ab60110cf8b18176dab9ceb44b453be10ef03362bd40c12cc4fd8cadb75da2d7
|
|
BLAKE2b-256 checksum How to use checksums |
be95fae1e6d578f82da87e1b1ef52007a7fe15619e794e579629cd63614e63e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / snakeboot-0.1.2-py3-none-any.whl
| Download URL | snakeboot-0.1.2-py3-none-any.whl |
|---|---|
| Size | 31.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2caec57f5ffd2db69f7114a4df2de3916808bef348f15d05033702f94679724f
|
|
BLAKE2b-256 checksum How to use checksums |
600844d12ff79f5649acc0a032f89542ef43a244743bda30b8446e6e004c98e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for snakeboot 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.