1+ """
2+ Example: Specification Pattern Applied to an Inventory Management System
3+ =========================================================================
4+ This example shows how the Specification pattern can be used to create
5+ flexible, composable filters for inventory queries.
6+
7+ This directly relates to an Inventory Management System project,
8+ demonstrating how design patterns integrate into real-world applications.
9+ """
10+
11+ from __future__ import annotations
12+ from abc import abstractmethod
13+ from typing import List
14+
15+
16+ # --- Specification Base ---
17+
18+
19+ class Specification :
20+ @abstractmethod
21+ def is_satisfied_by (self , candidate ) -> bool :
22+ pass
23+
24+ def and_ (self , other : Specification ) -> AndSpec :
25+ return AndSpec (self , other )
26+
27+ def or_ (self , other : Specification ) -> OrSpec :
28+ return OrSpec (self , other )
29+
30+ def not_ (self ) -> NotSpec :
31+ return NotSpec (self )
32+
33+
34+ class AndSpec (Specification ):
35+ def __init__ (self , one : Specification , other : Specification ) -> None :
36+ self ._one = one
37+ self ._other = other
38+
39+ def is_satisfied_by (self , candidate ) -> bool :
40+ return self ._one .is_satisfied_by (candidate ) and self ._other .is_satisfied_by (candidate )
41+
42+
43+ class OrSpec (Specification ):
44+ def __init__ (self , one : Specification , other : Specification ) -> None :
45+ self ._one = one
46+ self ._other = other
47+
48+ def is_satisfied_by (self , candidate ) -> bool :
49+ return self ._one .is_satisfied_by (candidate ) or self ._other .is_satisfied_by (candidate )
50+
51+
52+ class NotSpec (Specification ):
53+ def __init__ (self , wrapped : Specification ) -> None :
54+ self ._wrapped = wrapped
55+
56+ def is_satisfied_by (self , candidate ) -> bool :
57+ return not self ._wrapped .is_satisfied_by (candidate )
58+
59+
60+ # --- Inventory Domain ---
61+
62+
63+ class InventoryItem :
64+ """A product in the inventory system."""
65+
66+ def __init__ (
67+ self , name : str , category : str , quantity : int , price : float , supplier : str
68+ ) -> None :
69+ self .name = name
70+ self .category = category
71+ self .quantity = quantity
72+ self .price = price
73+ self .supplier = supplier
74+
75+ def __repr__ (self ) -> str :
76+ return f"{ self .name } (qty: { self .quantity } )"
77+
78+
79+ # --- Inventory Specifications ---
80+
81+
82+ class LowStockSpec (Specification ):
83+ """Items with quantity below a minimum threshold."""
84+
85+ def __init__ (self , minimum : int = 10 ) -> None :
86+ self ._minimum = minimum
87+
88+ def is_satisfied_by (self , candidate : InventoryItem ) -> bool :
89+ return candidate .quantity < self ._minimum
90+
91+
92+ class InCategorySpec (Specification ):
93+ """Items belonging to a specific category."""
94+
95+ def __init__ (self , category : str ) -> None :
96+ self ._category = category
97+
98+ def is_satisfied_by (self , candidate : InventoryItem ) -> bool :
99+ return candidate .category == self ._category
100+
101+
102+ class PriceAboveSpec (Specification ):
103+ """Items priced above a certain amount."""
104+
105+ def __init__ (self , amount : float ) -> None :
106+ self ._amount = amount
107+
108+ def is_satisfied_by (self , candidate : InventoryItem ) -> bool :
109+ return candidate .price > self ._amount
110+
111+
112+ class FromSupplierSpec (Specification ):
113+ """Items from a specific supplier."""
114+
115+ def __init__ (self , supplier : str ) -> None :
116+ self ._supplier = supplier
117+
118+ def is_satisfied_by (self , candidate : InventoryItem ) -> bool :
119+ return candidate .supplier == self ._supplier
120+
121+
122+ class OutOfStockSpec (Specification ):
123+ """Items with zero quantity."""
124+
125+ def is_satisfied_by (self , candidate : InventoryItem ) -> bool :
126+ return candidate .quantity == 0
127+
128+
129+ def main ():
130+ """
131+ >>> inventory = [
132+ ... InventoryItem("Laptop", "electronics", 25, 1200.0, "TechCorp"),
133+ ... InventoryItem("Mouse", "electronics", 3, 25.0, "TechCorp"),
134+ ... InventoryItem("Desk", "furniture", 8, 350.0, "OfficePlus"),
135+ ... InventoryItem("Chair", "furniture", 0, 200.0, "OfficePlus"),
136+ ... InventoryItem("Notebook", "stationery", 150, 3.0, "PaperWorld"),
137+ ... InventoryItem("Pen", "stationery", 5, 1.5, "PaperWorld"),
138+ ... ]
139+
140+ # Find low stock items (below 10 units)
141+ >>> low_stock = LowStockSpec(10)
142+ >>> [i for i in inventory if low_stock.is_satisfied_by(i)]
143+ [Mouse (qty: 3), Desk (qty: 8), Chair (qty: 0), Pen (qty: 5)]
144+
145+ # Find out-of-stock items
146+ >>> out_of_stock = OutOfStockSpec()
147+ >>> [i for i in inventory if out_of_stock.is_satisfied_by(i)]
148+ [Chair (qty: 0)]
149+
150+ # Find low stock electronics (combining two rules)
151+ >>> low_stock_electronics = low_stock.and_(InCategorySpec("electronics"))
152+ >>> [i for i in inventory if low_stock_electronics.is_satisfied_by(i)]
153+ [Mouse (qty: 3)]
154+
155+ # Find expensive items from TechCorp
156+ >>> expensive_techcorp = PriceAboveSpec(100).and_(FromSupplierSpec("TechCorp"))
157+ >>> [i for i in inventory if expensive_techcorp.is_satisfied_by(i)]
158+ [Laptop (qty: 25)]
159+
160+ # Find items that need reordering: low stock OR out of stock
161+ >>> needs_reorder = low_stock.or_(out_of_stock)
162+ >>> [i for i in inventory if needs_reorder.is_satisfied_by(i)]
163+ [Mouse (qty: 3), Desk (qty: 8), Chair (qty: 0), Pen (qty: 5)]
164+
165+ # Find furniture that is NOT out of stock
166+ >>> available_furniture = InCategorySpec("furniture").and_(out_of_stock.not_())
167+ >>> [i for i in inventory if available_furniture.is_satisfied_by(i)]
168+ [Desk (qty: 8)]
169+ """
170+
171+
172+ if __name__ == "__main__" :
173+ import doctest
174+
175+ doctest .testmod ()
0 commit comments