-
Notifications
You must be signed in to change notification settings - Fork 1
Description
While working on nullable field handling in strawchemy, I noticed a potential ambiguity regarding how GraphQL nullability for READ DTOs should be determined.
Currently, strawchemy derives nullability from the Python type hint:
Mapped[str]→ non-null (String!)Mapped[str | None]→ nullable (String)
However, SQLAlchemy itself supports deriving nullability from both:
- the mapped annotation
- and the explicit
nullableargument onmapped_column
According to SQLAlchemy 2.0 documentation, when nullable is explicitly set, it overrides the type-hint-based inference:
https://docs.sqlalchemy.org/en/20/orm/declarative_tables.html#mapped-column-derives-the-datatype-and-nullability-from-the-mapped-annotation
This raises the question to me:
For READ DTOs and GraphQL output types, should strawchemy trust the Python type hint or the SQLAlchemy column nullable attribute?
Why I think this is a problem
In some codebases, it is possible (and probably intentional) to have mismatches between:
- the Python type hint
- the database nullability
For example:
- a column is nullable in the DB, but exposed as
Mapped[str] - or a column is non-nullable in the DB, but temporarily annotated as
str | None
At the moment, strawchemy always trusts the type hint, even when the developer explicitly sets nullable=True or nullable=False.
What I would like to discuss
- Is the current behavior intentional?
- Should SQLAlchemy's
column.nullablebe the source of truth for READ DTO nullability? (IMO: Yes) - Should explicit
nullable=override the type hint for GraphQL schema generation? (IMO: Yes)
I have prepared a draft PR with tests that document the current behavior and highlight the mismatch cases, but I wanted to align on the intended design before proposing a concrete fix.
Additional Note: User-Level Overrides
One important point is that even if strawchemy changes the default behavior to trust the SQLAlchemy nullable attribute, users who disagree with the inferred GraphQL nullability are not locked in.
Example:
@strawchemy.type(NullableTestModel, include="all")
class NullableTestModelType:
# Override: even if DB says nullable=True, expose as non-null in GraphQL
non_optional_nullable_true: str
# Or make a non-nullable DB field nullable in GraphQL
non_optional_nullable_false: str | None