Skip to main content

Greyhorse — dependency injection and lifecycle for Python services

Greyhorse assembles a service out of the objects you already wrote, keeps them alive for exactly as long as they should live, and takes them down in the mirror of the order they came up.

Your domain code stays plain Python. No base class to inherit, no decorator on your business logic, no container imported into a repository. Everything the library needs to know is stated in declarations that sit beside your code — so any class it builds can also be constructed by hand in a unit test, with no framework present at all.

What it gives you beyond wiring:

  • Lifetimes, not just construction. A resource is set up, checked while it runs, repaired when it breaks and torn down in reverse order. That is the library's centre of gravity, and it is what separates it from a container that only hands you instances.
  • Boundaries that hold. A component declares what it takes in and what it hands out; nothing else about it is reachable from outside, and nothing else from outside reaches in. The boundary is checked when the graph is planned, not when a request happens to hit it.
  • Failures reported at plan time. A missing dependency, an ambiguous one, a lifetime that cannot be honoured — all of it fails before anything is built, naming what is missing and where to declare it.
  • Sync and async in one graph. Both live side by side; the library bridges the two rather than making you pick.
  • Storage and transport packages on the same shape. greyhorse-sqla, greyhorse-redis, greyhorse-nats, greyhorse-clickhouse, greyhorse-elasticsearch and greyhorse-web plug into the same declarations described below.

Install

pip install greyhorse     # requires Python 3.14+

A first look

A module declares what it owns; the library builds it, wires it, and takes it down in the exact mirror of the order it came up:

from typing import ClassVar

from greyhorse.strand import Module, Resource, build


class Database:
    def __init__(self, dsn: str = 'sqlite://memory') -> None:
        self.dsn = dsn


class App(Module):
    name = 'minimal'
    resources: ClassVar = Resource(Database)


module = build(App)
module.setup()
try:
    ...  # the Database exists, wired and live
finally:
    module.teardown()

There is no factory to write: a class is its own factory when its constructor can be satisfied.

Boundaries that actually hold

A component states what it takes in and what it hands out. Nothing else about it is reachable from outside, and nothing else from outside reaches into it:

from greyhorse.rock import Fragment, factory, provider
from greyhorse.strand import Component, Module, Resource, Shared, Use


class CatalogFragment(Fragment):
    exports: ClassVar = {PriceList}

    db = provider(Shared[Database])  # a demand, filled from outside
    prices = factory(PriceList)  # no body needed


class CatalogComponent(Component):
    fragments: ClassVar = CatalogFragment
    imports: ClassVar = Shared[Database]
    exports: ClassVar = PriceList


class Shop(Module):
    name = 'shop'
    resources: ClassVar = Resource(Database)
    components: ClassVar = {'catalog': Use(CatalogComponent)}

Shared[X] lends a value read-only to any number of readers; Mut[X] lends it for mutation to exactly one at a time and refuses a competing borrower loudly rather than interleaving writes.

What else is in the box

  • Borders — an object that watches a resource and repairs it: phases for setup, check, repair and teardown, driven on a tick. Your resource never learns it is being watched.
  • Controllers and services over groupsControl(cls, over=(A, B)) puts one supervisor of your own over several resources in a fixed order; Serve(cls, over=(...)) gives one admission gate over several providers.
  • Handlers and gateways — mount an operation onto a transport (HTTP, a clock) without the operation knowing which transport it landed on.
  • A scheduler (greyhorse.river.sched) — one-shot and periodic jobs with reconciliation, retry policies and worker pools.
  • Result and MaybeResult[T, E] and Maybe[T] with a monadic API, plus an ADT-style Enum with Unit/Tuple/Struct variants that a mypy plugin checks for exhaustiveness.

Learn it by running it

Everything above is drawn from examples/, which is a set of runnable programs rather than snippets — each adds exactly one idea to the one before it:

python examples/01_minimal.py
Shows
01_minimal.py one resource, built and torn down
02_config.py configuration reaching a constructor without the class knowing where it came from
03_component.py a component as a boundary: what it takes in, what it hands out
04_http.py the full shape: resource, service, handler, HTTP dispatch
05_borders.py a border watching a resource and repairing it
06_submodules.py one module declaration mounted twice, configured differently each time
07_functional.py handing down a callable instead of a value, matched by signature
08_scheduler.py one-shot and periodic jobs, ticked by hand
09_controller_service.py your own controller and service over a group of resources

tests/test_examples.py runs every one of them and requires a zero exit code, so nothing here can quietly rot into documentation that lies.

Packages

greyhorse.strand lifecycle and ownership — Module, Component, Controller, Service, gateways, and the borrow windows Shared/Mut
greyhorse.rock declarative wiring — Fragment, @factory, @provider, the resolver
greyhorse.river operations, providers, slots, the scheduler
greyhorse.data data-layer abstractions — repositories, engines, serializers, cache
greyhorse.result / .maybe / .enum / .error core primitives

Every name a package lists in __all__ is guaranteed to resolve, carries no private type in its signatures, and is checked in CI — see tests/test_public_api.py.

Development

uv python pin 3.14
uv venv && uv sync
source .venv/bin/activate

ruff check --unsafe-fixes --fix && ruff format
pytest

License

MIT

Download files

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

Source Distribution

greyhorse-0.5.5.tar.gz (1.6 MB view details)

Uploaded Source

Built Distribution

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

greyhorse-0.5.5-py3-none-any.whl (865.4 kB view details)

Uploaded Python 3

File details

Details for the file greyhorse-0.5.5.tar.gz.

File metadata

  • Download URL: greyhorse-0.5.5.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.0

File hashes

Hashes for greyhorse-0.5.5.tar.gz
Algorithm Hash digest
SHA256 72b63578db19986157c08cf9539569abfbcd0f650de1264499a066a5ecab8f76
MD5 0340c3b0a251b15e4443e4214e38661c
BLAKE2b-256 92679ed8f3069809f39f69fba1e331410c50baf14c34af021f52886cf89a246d

See more details on using hashes here.

File details

Details for the file greyhorse-0.5.5-py3-none-any.whl.

File metadata

  • Download URL: greyhorse-0.5.5-py3-none-any.whl
  • Upload date:
  • Size: 865.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.0

File hashes

Hashes for greyhorse-0.5.5-py3-none-any.whl
Algorithm Hash digest
SHA256 2b13f61eaac7be27e7cbbf361689b46efc716c1e46532de9938ec16f56189ee4
MD5 e52b6b9ccfc8cd66b1fadf6567813789
BLAKE2b-256 8a1c717fea49cd53371237fcc66ca8b2afbc3c4242ed40beaa501c16ab32a278

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.5 This release

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.26

2 files

0.4.25

2 files

0.4.24

2 files

0.4.23

2 files

0.4.22

2 files

0.4.21

2 files

0.4.20

2 files

0.4.19.1

2 files

0.4.19

2 files

0.4.18

2 files

0.4.17

2 files

0.4.16

2 files

0.4.15

2 files

0.4.14

2 files

0.4.13

2 files

0.4.12

2 files

0.4.11

2 files

0.4.10

2 files

0.4.9

2 files

0.4.8

2 files

0.4.7

2 files

0.4.6

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page