Let's look at how SQLAlchemy implements its query builder using method chaining and the "builder pattern" in a particularly elegant way. This pattern allows for incredibly flexible and readable query construction.
# Here's a simplified version showing the core idea
class QueryBuilder:
def __init__(self):
self._filters = []
self._order_by = None
self._limit = None
def filter(self, condition):
self._filters.append(condition)
return self # This is the key - returning self enables chaining
def order_by(self, column):
self._order_by = column
return self
def limit(self, n):
self._limit = n
return self
def build(self):
query = "SELECT * FROM table"
if self._filters:
query += " WHERE " + " AND ".join(self._filters)
if self._order_by:
query += f" ORDER BY {self._order_by}"
if self._limit:
query += f" LIMIT {self._limit}"
return query
# Usage
query = (QueryBuilder()
.filter("age > 18")
.filter("country = 'US'")
.order_by("name")
.limit(10)
.build())
What's genius about this pattern is that it combines immutability with mutability in a clever way - while each method appears to modify the query immutably (like a functional programming style), it's actually modifying the same object. This gives us the best of both worlds: the readability of functional programming with the performance of imperative programming.
We could adapt this pattern for many other use cases. Here's an example of how we might use it for building complex data processing pipelines:
class DataPipeline:
def __init__(self, data):
self._data = data
self._operations = []
def filter(self, predicate):
self._operations.append(('filter', predicate))
return self
def transform(self, func):
self._operations.append(('transform', func))
return self
def group_by(self, key):
self._operations.append(('group_by', key))
return self
def execute(self):
result = self._data
for op_type, op in self._operations:
if op_type == 'filter':
result = [x for x in result if op(x)]
elif op_type == 'transform':
result = [op(x) for x in result]
elif op_type == 'group_by':
groups = {}
for item in result:
key = op(item)
groups.setdefault(key, []).append(item)
result = groups
return result
# Usage
data = [
{'name': 'Alice', 'age': 25, 'city': 'NY'},
{'name': 'Bob', 'age': 30, 'city': 'SF'},
{'name': 'Charlie', 'age': 35, 'city': 'NY'}
]
result = (DataPipeline(data)
.filter(lambda x: x['age'] > 25)
.transform(lambda x: {**x, 'age_group': x['age'] // 10 * 10})
.group_by(lambda x: x['city'])
.execute())
hope you enjoy it.