Skip to content

Commit c50ab2c

Browse files
committed
start on a Product model with an allocate fn [product_aggregate]
1 parent 7526014 commit c50ab2c

File tree

10 files changed

+73
-205
lines changed

10 files changed

+73
-205
lines changed

src/allocation/adapters/orm.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,19 @@
1515
Column("orderid", String(255)),
1616
)
1717

18+
products = Table(
19+
"products",
20+
metadata,
21+
Column("sku", String(255), primary_key=True),
22+
# Column('version_number', Integer, nullable=False, default=0),
23+
)
24+
1825
batches = Table(
1926
"batches",
2027
metadata,
2128
Column("id", Integer, primary_key=True, autoincrement=True),
2229
Column("reference", String(255)),
23-
Column("sku", String(255)),
30+
Column("sku", ForeignKey("products.sku")),
2431
Column("_purchased_quantity", Integer, nullable=False),
2532
Column("eta", Date, nullable=True),
2633
)
@@ -36,7 +43,7 @@
3643

3744
def start_mappers():
3845
lines_mapper = mapper(model.OrderLine, order_lines)
39-
mapper(
46+
batches_mapper = mapper(
4047
model.Batch,
4148
batches,
4249
properties={
@@ -47,3 +54,6 @@ def start_mappers():
4754
)
4855
},
4956
)
57+
mapper(
58+
model.Product, products, properties={"batches": relationship(batches_mapper)}
59+
)

src/allocation/adapters/repository.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,20 @@
44

55
class AbstractRepository(abc.ABC):
66
@abc.abstractmethod
7-
def add(self, batch: model.Batch):
7+
def add(self, product: model.Product):
88
raise NotImplementedError
99

1010
@abc.abstractmethod
11-
def get(self, reference) -> model.Batch:
11+
def get(self, sku) -> model.Product:
1212
raise NotImplementedError
1313

1414

1515
class SqlAlchemyRepository(AbstractRepository):
1616
def __init__(self, session):
1717
self.session = session
1818

19-
def add(self, batch):
20-
self.session.add(batch)
19+
def add(self, product):
20+
self.session.add(product)
2121

22-
def get(self, reference):
23-
return self.session.query(model.Batch).filter_by(reference=reference).one()
24-
25-
def list(self):
26-
return self.session.query(model.Batch).all()
22+
def get(self, sku):
23+
return self.session.query(model.Product).filter_by(sku=sku).first()

src/allocation/domain/model.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ class OutOfStock(Exception):
88
pass
99

1010

11-
def allocate(line: OrderLine, batches: List[Batch]) -> str:
12-
try:
13-
batch = next(b for b in sorted(batches) if b.can_allocate(line))
14-
batch.allocate(line)
15-
return batch.reference
16-
except StopIteration:
17-
raise OutOfStock(f"Out of stock for sku {line.sku}")
11+
class Product:
12+
def __init__(self, sku: str, batches: List[Batch]):
13+
self.sku = sku
14+
self.batches = batches
15+
16+
def allocate(self, line: OrderLine) -> str:
17+
try:
18+
batch = next(b for b in sorted(self.batches) if b.can_allocate(line))
19+
batch.allocate(line)
20+
return batch.reference
21+
except StopIteration:
22+
raise OutOfStock(f"Out of stock for sku {line.sku}")
1823

1924

2025
@dataclass(unsafe_hash=True)

src/allocation/service_layer/services.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ class InvalidSku(Exception):
1111
pass
1212

1313

14-
def is_valid_sku(sku, batches):
15-
return sku in {b.sku for b in batches}
16-
17-
1814
def add_batch(
1915
ref: str, sku: str, qty: int, eta: Optional[date],
2016
uow: unit_of_work.AbstractUnitOfWork,
2117
):
2218
with uow:
23-
uow.batches.add(model.Batch(ref, sku, qty, eta))
19+
product = uow.products.get(sku=sku)
20+
if product is None:
21+
product = model.Product(sku, batches=[])
22+
uow.products.add(product)
23+
product.batches.append(model.Batch(ref, sku, qty, eta))
2424
uow.commit()
2525

2626

@@ -30,9 +30,9 @@ def allocate(
3030
) -> str:
3131
line = OrderLine(orderid, sku, qty)
3232
with uow:
33-
batches = uow.batches.list()
34-
if not is_valid_sku(line.sku, batches):
33+
product = uow.products.get(sku=line.sku)
34+
if product is None:
3535
raise InvalidSku(f"Invalid sku {line.sku}")
36-
batchref = model.allocate(line, batches)
36+
batchref = product.allocate(line)
3737
uow.commit()
3838
return batchref

src/allocation/service_layer/unit_of_work.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
class AbstractUnitOfWork(abc.ABC):
13-
batches: repository.AbstractRepository
13+
products: repository.AbstractRepository
1414

1515
def __enter__(self) -> AbstractUnitOfWork:
1616
return self
@@ -40,7 +40,7 @@ def __init__(self, session_factory=DEFAULT_SESSION_FACTORY):
4040

4141
def __enter__(self):
4242
self.session = self.session_factory() # type: Session
43-
self.batches = repository.SqlAlchemyRepository(self.session)
43+
self.products = repository.SqlAlchemyRepository(self.session)
4444
return super().__enter__()
4545

4646
def __exit__(self, *args):

tests/integration/test_orm.py

Lines changed: 0 additions & 89 deletions
This file was deleted.

tests/integration/test_repository.py

Lines changed: 0 additions & 67 deletions
This file was deleted.

tests/integration/test_uow.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55

66
def insert_batch(session, ref, sku, qty, eta):
7+
session.execute(
8+
"INSERT INTO products (sku) VALUES (:sku)",
9+
dict(sku=sku),
10+
)
711
session.execute(
812
"INSERT INTO batches (reference, sku, _purchased_quantity, eta)"
913
" VALUES (:ref, :sku, :qty, :eta)",
@@ -31,9 +35,9 @@ def test_uow_can_retrieve_a_batch_and_allocate_to_it(session_factory):
3135

3236
uow = unit_of_work.SqlAlchemyUnitOfWork(session_factory)
3337
with uow:
34-
batch = uow.batches.get(reference="batch1")
38+
product = uow.products.get(sku="HIPSTER-WORKBENCH")
3539
line = model.OrderLine("o1", "HIPSTER-WORKBENCH", 10)
36-
batch.allocate(line)
40+
product.allocate(line)
3741
uow.commit()
3842

3943
batchref = get_allocated_batch_ref(session, "o1", "HIPSTER-WORKBENCH")
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
from datetime import date, timedelta
22
import pytest
3-
from allocation.domain.model import allocate, OrderLine, Batch, OutOfStock
3+
from allocation.domain.model import Product, OrderLine, Batch, OutOfStock
44

55
today = date.today()
66
tomorrow = today + timedelta(days=1)
77
later = tomorrow + timedelta(days=10)
88

99

10-
def test_prefers_current_stock_batches_to_shipments():
10+
def test_prefers_warehouse_batches_to_shipments():
1111
in_stock_batch = Batch("in-stock-batch", "RETRO-CLOCK", 100, eta=None)
1212
shipment_batch = Batch("shipment-batch", "RETRO-CLOCK", 100, eta=tomorrow)
13+
product = Product(sku="RETRO-CLOCK", batches=[in_stock_batch, shipment_batch])
1314
line = OrderLine("oref", "RETRO-CLOCK", 10)
1415

15-
allocate(line, [in_stock_batch, shipment_batch])
16+
product.allocate(line)
1617

1718
assert in_stock_batch.available_quantity == 90
1819
assert shipment_batch.available_quantity == 100
@@ -22,9 +23,10 @@ def test_prefers_earlier_batches():
2223
earliest = Batch("speedy-batch", "MINIMALIST-SPOON", 100, eta=today)
2324
medium = Batch("normal-batch", "MINIMALIST-SPOON", 100, eta=tomorrow)
2425
latest = Batch("slow-batch", "MINIMALIST-SPOON", 100, eta=later)
26+
product = Product(sku="MINIMALIST-SPOON", batches=[medium, earliest, latest])
2527
line = OrderLine("order1", "MINIMALIST-SPOON", 10)
2628

27-
allocate(line, [medium, earliest, latest])
29+
product.allocate(line)
2830

2931
assert earliest.available_quantity == 90
3032
assert medium.available_quantity == 100
@@ -35,13 +37,15 @@ def test_returns_allocated_batch_ref():
3537
in_stock_batch = Batch("in-stock-batch-ref", "HIGHBROW-POSTER", 100, eta=None)
3638
shipment_batch = Batch("shipment-batch-ref", "HIGHBROW-POSTER", 100, eta=tomorrow)
3739
line = OrderLine("oref", "HIGHBROW-POSTER", 10)
38-
allocation = allocate(line, [in_stock_batch, shipment_batch])
40+
product = Product(sku="HIGHBROW-POSTER", batches=[in_stock_batch, shipment_batch])
41+
allocation = product.allocate(line)
3942
assert allocation == in_stock_batch.reference
4043

4144

4245
def test_raises_out_of_stock_exception_if_cannot_allocate():
4346
batch = Batch("batch1", "SMALL-FORK", 10, eta=today)
44-
allocate(OrderLine("order1", "SMALL-FORK", 10), [batch])
47+
product = Product(sku="SMALL-FORK", batches=[batch])
48+
product.allocate(OrderLine("order1", "SMALL-FORK", 10))
4549

4650
with pytest.raises(OutOfStock, match="SMALL-FORK"):
47-
allocate(OrderLine("order2", "SMALL-FORK", 1), [batch])
51+
product.allocate(OrderLine("order2", "SMALL-FORK", 1))

0 commit comments

Comments
 (0)