Skip to content

Defer relationship DTO resolution until schema finalization #175

Description

@adiberk

Problem

When a relationship field is generated for a related SQLAlchemy model that also has an explicit GraphQL DTO name, Strawchemy can bind the relationship to the synthesized default DTO type instead of the explicitly registered DTO.

That means custom fields declared on the explicit DTO are not present on the relationship field type.

This is not module discovery: if the related DTO module is never imported/decorated, Strawchemy cannot know about it. The issue is the case where the explicit DTO exists, but relationship generation has already selected or synthesized the default related DTO name.

Minimal example

from sqlalchemy import ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from strawberry import Schema
from strawchemy import Strawchemy, StrawchemyConfig

class Base(DeclarativeBase):
    pass

class Account(Base):
    __tablename__ = "accounts"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String)

class Template(Base):
    __tablename__ = "templates"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    account_id: Mapped[int] = mapped_column(ForeignKey("accounts.id"))
    account: Mapped[Account] = relationship()

sc = Strawchemy(StrawchemyConfig(dialect="postgresql"))

@sc.type(Template, name="TemplateNode", include=["id", "account"])
class TemplateNode:
    pass

@sc.type(Account, name="AccountNode", include=["id", "name"])
class AccountNode:
    @strawberry.field
    def custom_label(self) -> str:
        return "custom"

@strawberry.type
class Query:
    template: TemplateNode | None = None

schema = Schema(query=Query)
print(schema.as_str())

Expected relationship field:

account: AccountNode!

Observed with default DTO naming:

account: AccountType!

The AccountNode.customLabel field is therefore not available on TemplateNode.account.

Why deferred/schema-time resolution would help

If relationship fields stored a deferred reference keyed by the related model and DTO purpose/scope instead of immediately binding to the currently synthesized class/name, schema construction/finalization could resolve that reference to the explicit registered DTO when one exists.

That would avoid import-order workarounds such as registering otherwise-unused related-node imports purely so relationship fields bind to the desired type.

Possible direction

  • Keep a registry keyed by (model, purpose/mode, scope) that records explicit DTO registrations.
  • Let relationship field generation emit a model-keyed deferred reference rather than an eagerly chosen default DTO class/name.
  • At schema build or finalization time, resolve pending relationship references to the explicit registered DTO if present; otherwise fall back to generated defaults.
  • If this is expected to be handled by the future scope behavior mentioned in Walker auto-registers stub types that collide with subsequent @sc.type declarations (partial-port footgun) #159, document whether scope will also cover relationship-target resolution for explicitly named DTOs.

Workaround

In our app we can work around this by customizing DTO naming so synthesized relationship DTO names match our explicit *Node convention, but that is project-specific and feels like it should not be required for relationship fields to point at explicit registered DTOs.

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