proofit404 / stories

Service objects designed with OOP in mind.
https://proofit404.github.io/stories/
BSD 2-Clause "Simplified" License
297 stars 21 forks source link

Story should return its execution result. #723

Open proofit404 opened 1 year ago

proofit404 commented 1 year ago

Syntax

Once again we would change required syntax to work :sweat_smile:

from dataclasses import dataclass
from types import SimpleNamespace
from typing import Callable

from stories import story

from app.repositories import load_order, load_customer, create_payment

@story
def purchase(it, state):
    it.find_order(state)
    it.find_customer(state)
    it.check_balance(state)
    it.persist_payment(state)

@dataclass
class Steps:
    load_order: Callable
    load_customer: Callable
    create_payment: Callable

    def find_order(self, state):
        state.order = self.load_order(state.order_id)

    def find_customer(self, state):
        state.customer = self.load_customer(state.customer_id)

    def check_balance(self, state):
        if not state.order.affordable_for(state.customer):
            raise Exception

    def persist_payment(self, state):
        state.payment = self.create_payment(
            order_id=state.order_id, customer_id=state.customer_id
        )

steps = Steps(
    load_order=load_order,
    load_customer=load_customer,
    create_payment=create_payment,
)

state = SimpleNamespace(order_id=1, customer_id=1)

print(purchase.bind(steps).run(state))
purchase
  find_order
  find_customer
  check_balance
  persist_payment

order_id = 1                                                       # Story argument
customer_id = 1                                                    # Story argument
order = Order(product=Product(name='Books'), cost=Cost(amount=7))  # Set by purchase.Steps.find_order 
customer = Customer(balance=8)                                     # Set by purchase.Steps.find_customer
payment = Payment(due_date='tomorrow')                             # Set by purchase.Steps.find_subscription

Result object is...

a restored context representation from stories version 4.0.

Principles

2023 Aug 8 update

Typing support for conditional inner stories without when and unless.

from attrs import define
from stories import Story, I

@define
class Outer(Story):
    I.conditional

    def conditional(self, state):
        if state.condition:
            self.inner(state)

    inner: Story
M0dM commented 1 month ago

Stories is description first as we define steps before executing the code, it would be great to avoid using plain python for conditional to allow "diagram generation" (like a sequence diagram) from description (Steps and Conditional sub-stories) for example.

We would have almost everything like what xstate does in JS