Skip to main content

Fractal Commands

Fractal Commands is a minimal command bus for building SOLID logic in your Python applications.

PyPI Version Build Status

Installation

pip install fractal-commands

Background

A command is a plain data object describing an intent: add this road, approve this member. A handler carries it out. The bus is the thin thing in between — it knows which handler answers which command, and nothing else.

Keeping the two apart is what makes the pattern useful. The caller states what it wants and stays ignorant of how it happens; the handler owns the how and never has to know who asked. Commands cross that seam as data, so they are easy to log, queue, replay, or map onto from events.

Usage

from dataclasses import dataclass

from fractal_commands import Command, CommandBus, CommandHandler


@dataclass
class Greet(Command):
    name: str


class GreetHandler(CommandHandler[Greet]):
    command = Greet

    def handle(self, command: Greet):
        return f"hello {command.name}"


bus = CommandBus()
bus.add_handler(GreetHandler())

bus.handle(Greet("world"))
# {'GreetHandler': 'hello world'}

Every handler registered for a command runs. handle collects the return values of the ones that produced something, keyed by handler class name; handlers that return None — most write handlers — simply do not appear.

await bus.handle_async(command) is the same thing for async handlers.

A command with no handler is an error

Dispatching a command nobody handles raises NoCommandHandlerError:

bus = CommandBus()
bus.handle(Greet("world"))
# NoCommandHandlerError: no handler is registered for Greet, so the command was
# dropped without being executed

This is deliberate, and it is worth explaining, because the obvious alternative — shrug and return an empty result — is what this library was extracted to stop doing.

Handlers usually register themselves through a decorator at import time. That makes registration a side effect of the import graph, and import graphs lose edges: a module stops being imported, and every command of that type silently stops being handled. Nothing raises. No event is published, no row is written, and the call returns normally. Tests that assert on the service layer see a perfectly healthy no-op, so the failure surfaces days later somewhere else entirely, as missing data.

Raising turns that into a one-line stack trace at the first dispatch.

When silence is legitimate

A bus fed by fan-out is the honest exception. An event projector that maps events onto commands will produce commands this particular deployment has no handler for, and that is normal rather than broken. Pass strict=False to log the miss at ERROR and carry on:

bus = CommandBus(strict=False)
bus.handle(Greet("world"))
# ERROR:fractal_commands.command_bus:no handler is registered for Greet, so the
# command was dropped without being executed
# {}

It is also the migration path if you are adopting this library in an application that has been relying on the old silence: start with strict=False, fix what the logs show, then turn it on.

Entity commands

The three commands generic CRUD needs are included, built on fractal-specifications and fractal-repositories:

from fractal_commands import AddEntityCommand, UpdateEntityCommand, DeleteEntityCommand

Each carries the specification the handler checks the entity against, so the business rule travels with the intent instead of living in the handler.

Development

make dev-install
make test
make lint
make format

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fractal_commands-1.0.0.tar.gz (54.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fractal_commands-1.0.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file fractal_commands-1.0.0.tar.gz.

File metadata

  • Download URL: fractal_commands-1.0.0.tar.gz
  • Upload date:
  • Size: 54.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.34.2

File hashes

Hashes for fractal_commands-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c930b90043747f80d82e2f6e3cc5740feb90a4ad7ff6bd7fdefcc575626c64c0
MD5 6363bc7a8a4311080f807b35d4d03736
BLAKE2b-256 07c0ab703f39bac6393cca52f6208ef5598f5c8b44c721a637018e6c2f28ec1e

See more details on using hashes here.

File details

Details for the file fractal_commands-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fractal_commands-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5e3e4ad27b92ee12638d5f2a18f568c74888a86e2f9bd16090ace74d00e8484f
MD5 a1ce5a0938d2718e1fd183b549a14962
BLAKE2b-256 7cecd7d31d5d7bd704eafb2d449f0777ae5a751593517525186bd84b90879013

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page