Skip to content

Source of Truth for Nullable Fields in READ DTOs #151

@Ckk3

Description

@Ckk3

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 nullable argument on mapped_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.nullable be 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions