Metadata-Version: 2.4
Name: codeupipe
Version: 2.0.0b1
Summary: A Python library for writing a program as a pipeline of small steps.
Author: Joshua Wink
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/orchestrate-solutions/codeupipe
Project-URL: Repository, https://github.com/orchestrate-solutions/codeupipe
Project-URL: Issues, https://github.com/orchestrate-solutions/codeupipe/issues
Keywords: pipeline,filter,payload,composition
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Dynamic: license-file

# codeupipe

codeupipe is a Python library for writing a program as a pipeline of small
steps. It has no dependencies. Python 3.9 or later.

```bash
pip install codeupipe
```

## The eight primitives

| Primitive | What it does | Who writes it |
|---|---|---|
| **Payload** | Carries the data | The library |
| **Filter** | Changes the data | You |
| **Predicate** | Asks a yes-or-no question about the data | You |
| **Valve** | Joins a Predicate to a Filter: the Filter runs when the answer is yes | The library. You add one with `add_valve(predicate, filter)` |
| **Tap** | Reads the data as it goes by | You |
| **Hook** | Is called before and after each step | You |
| **State** | Records which steps ran | The library |
| **Pipeline** | Runs the steps in order | You |

## Example

Each class is in its own file. The pipeline file imports them and adds them.

```python
# discount_filter.py
class DiscountFilter:
    def __init__(self, rate):
        self.rate = rate

    def call(self, payload):
        return payload.insert("price", round(payload.get("price", 0) * (1 - self.rate), 2))
```

```python
# at_least_predicate.py
class AtLeastPredicate:
    def __init__(self, key, minimum):
        self.key = key
        self.minimum = minimum

    def test(self, payload):
        return payload.get(self.key, 0) >= self.minimum
```

```python
# pricing_pipeline.py
from codeupipe import Pipeline

from .at_least_predicate import AtLeastPredicate
from .audit_tap import AuditTap
from .discount_filter import DiscountFilter
from .mark_bulk_filter import MarkBulkFilter


class PricingPipeline(Pipeline):
    def __init__(self):
        super().__init__()
        self.add_filter(DiscountFilter(0.1))
        # MarkBulkFilter runs only when the predicate is true.
        self.add_valve(AtLeastPredicate("qty", 10), MarkBulkFilter())
        # A Tap only reads. If it fails, State records the error and the run goes on.
        self.add_tap(AuditTap())
```

```python
from codeupipe import Payload

PricingPipeline().run(Payload({"price": 100.0, "qty": 12})).to_dict()
# {'price': 90.0, 'qty': 12, 'bulk': True}
```

A step is a class. State and Hooks report each step by the name of its
class, for example `DiscountFilter`. A lambda is refused, because it does not
have the method of a step.

## A type of your own

`Payload` is the default: a map that any developer can read. Any object with
a `copy()` method can take its place, such as a dataclass:

```python
@dataclasses.dataclass
class Order:
    price: float

    def copy(self):
        return dataclasses.replace(self)


class DiscountFilter:
    def call(self, order):
        order.price = round(order.price * 0.9, 2)
        return order
```

The rules stay the same. Lineage is kept only if the object has
`_stamp(label)`; otherwise `observe(lineage=True)` does nothing for it. The
compiler reads Payload operations only: keep such a pipeline dynamic. See
"A type of your own" in [conformance/API.md](../../conformance/API.md).

## What is in the package

- `codeupipe`: the eight primitives.
- `python -m codeupipe.compile`: writes a pipeline as plain Python with no
  import of codeupipe.
- `codeupipe.config`: reads `cup.config.json`.

The linter is a separate package, `codeupipe-lint`. Its install command is
in the repository README.

## Documentation

- The contract: https://github.com/orchestrate-solutions/codeupipe/blob/main/conformance/API.md
- The compiler: https://github.com/orchestrate-solutions/codeupipe/blob/main/docs/compile.md
- The config file: https://github.com/orchestrate-solutions/codeupipe/blob/main/docs/config.md
- The repository: https://github.com/orchestrate-solutions/codeupipe

## License

Apache License 2.0.
