Metadata-Version: 2.5
Name: greyhorse
Version: 0.5.5
Summary: Greyhorse core library
Project-URL: Homepage, https://gitlab.com/max-plutonium/greyhorse
Project-URL: Repository, https://gitlab.com/max-plutonium/greyhorse
Author-email: Max Plutonium <plutonium.max@gmail.com>
Maintainer-email: Max Plutonium <plutonium.max@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: asyncio,dependency-injection,framework,greyhorse,microservices,scheduler
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.14
Requires-Dist: croniter~=6.0.0
Requires-Dist: greenback>=1.3
Requires-Dist: greenlet>=3.5
Requires-Dist: orjson>=3.11
Requires-Dist: pydantic>=2.13
Requires-Dist: timeparse-plus>=1.2
Requires-Dist: tomlkit>=0.15
Description-Content-Type: text/markdown

# 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

```bash
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:

```python
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:

```python
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 groups** — `Control(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 Maybe** — `Result[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/`](examples/), which is a set of
runnable programs rather than snippets — each adds exactly one idea to the one
before it:

```bash
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

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

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

## License

MIT
