Python Builder Design Pattern

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.