Still smells like in such a case the developer avoids the complications of abstraction or OOP by making the user deal with it. That's bad API design due to putting ideology before practicality or ergonomics.
The query shape would be:
active_users = Query(User).filter(active=True)
That gives you an expression object which only encodes an intent. Then you have the option to make basic templates you can build from: def active_users_except(exclude):
return active_users.filter(User.id.not_in(exclude)
...where `exclude` is any set-valued expression.Then at execution time, the objects representing query expressions are rendered into queries and sent to the database:
exclude_criterion = rude_users() # A subquery expression
polite_active_users = load_records(
active_users_except(exclude_criterion)
)
With SQLAlchemy, I'll usually make simple dataclasses for the query shapes because "get_something" or "select_something" names are confusing when they're not really for actions. @dataclass
class ActiveUsers(QueryTemplate):
active_if: Expression = User.active == true()
@classmethod
excluding(cls, bad_set):
return cls(
and_(
User.active == true(),
User.id.not_in(bad_set)
)
)
@property
def query(self):
return Query(User).filter(self.active_if)
load_records(
ActiveUsers.excluding(select_alice | select_bob).query
) IEnumerable<User> getExpiredUsers(DbSet<User> users)
=> users.Where(u => u.ExpiresAt < DateTime.UtcNow);
Such simple logical expressions (called expression trees) get converted to SQL queriesI'm building a new project, Typegres, on this same philosophy for the modern web stack (TypeScript/PostgreSQL).
We can take your example a step further and blur the lines between database columns and computed business logic, building the "functional core" right in the model:
// This method compiles directly to a SQL expression
class User extends db.User {
isExpired() {
return this.expiresAt.lt(now());
}
}
const expired = await User.where((u) => u.isExpired());
Here's the playground if that looks interesting: https://typegres.com/play/