We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 17 of 24
Self-types, and restricting a method to one parameterisation
typing.Self solves the fluent-builder problem: a chained method must return
the subclass, not the base, or .audit() disappears halfway down the
chain. It is the right default and it is one word long.
What Self cannot do is the subject here. It cannot make a method
conditionally available.
Annotating self to restrict a method
You are allowed to annotate self explicitly, and when you do, the method
only type-checks on receivers matching that annotation:
class QueryBuilder[T]:
def like[S: QueryBuilder[str]](self: S, pattern: str) -> S: ...
like now exists on QueryBuilder[str] and is a static error on
QueryBuilder[int] — the checker reports it as an invalid self argument.
This is how typeshed keeps str-only operations off bytes paths, and it
is the tool to reach for when “this method only makes sense for some
instantiations” is the actual invariant.
Note the extra method-scoped type parameter S. Writing self: QueryBuilder[str] alone would widen the receiver: chaining off it would
lose the subclass. Binding S to the receiver and returning S keeps both
properties — restricted and subclass-preserving.
Your task
Make the fluent API precise:
-
from_tableis a@classmethodreturningSelf -
whereandlimitreturnSelf -
likeis restricted toQueryBuilder[str]receivers, and preserves the receiver type -
build()renders"SELECT * FROM <table>"followed by the accumulated clauses, space-separated
Clause formats: where appends f"WHERE {clause}", limit appends
f"LIMIT {count}", like appends f"LIKE '{pattern}'".
def solve(table: str, pattern: str, limit: int) -> list[str]:
returns three strings:
-
QueryBuilder[str].from_table(table).where("a = 1").like(pattern).limit(limit).build() -
QueryBuilder[int].from_table(table).where("id = 2").limit(limit).build() -
AuditedQueryBuilder[str].from_table(table).where("b = 3").like(pattern).audit()
Where the type system does the work
Line 3 is the assertion. audit() exists only on AuditedQueryBuilder.
If where returns QueryBuilder[T] instead of Self, the chain degrades to
the base class at the first call and .audit() stops resolving — which is
exactly the bug the starter has, and exactly the bug fluent APIs ship with
when nobody annotates them.
Line 2 is the other half: int_q never touches like, because it cannot.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.