Skip to content

Raise clear exception when sqlalchemy_loader is missing from context #245

@Ckk3

Description

@Ckk3

Currently, when the StrawberrySQLAlchemyLoader is not added to the GraphQL context, the code raises an AttributeError with the message 'NoneType' object has no attribute 'sqlalchemy_loader'. This error can be confusing for developers, as it does not clearly indicate the root cause of the issue.

To improve developer experience, I propose adding a custom exception (e.g., MissingSQLAlchemyLoaderError) that provides a clear and actionable error message when the loader is missing from the context. This will make it easier for developers to identify and fix the issue.

Proposed Solution

  • Introduce a custom exception class like MissingSQLAlchemyLoaderError

  • Add a utility function or check in the loader setup (e.g., get_loader_from_context) that validates the loader presence and type

  • Raise the exception with a helpful message, for example:

"The context is missing a required 'sqlalchemy_loader' of type StrawberrySQLAlchemyLoader. Please make sure to include it in your context configuration."

  • Add context injection examples in README using sync and async sessions:
    Useful in tests or advanced GraphQL setups where a custom view isn't used.

Sync:
If you're using a synchronous SQLAlchemy session (e.g., in tests with a regular sessionmaker), pass it via the bind parameter:

from sqlalchemy.orm import sessionmaker
from strawberry_sqlalchemy_mapper import StrawberrySQLAlchemyLoader
from your_project.schema import schema  # Replace with actual schema import

session = sessionmaker()  # This can be your session fixture or factory
result = schema.execute_sync(
    query,
    context_value={
        "sqlalchemy_loader": StrawberrySQLAlchemyLoader(bind=session)
    },
)

Async:
For async setups, pass an async_bind_factory instead:

from sqlalchemy.ext.asyncio import async_sessionmaker
from strawberry_sqlalchemy_mapper import StrawberrySQLAlchemyLoader
from your_project.schema import schema  # Replace with actual schema import

async_session = async_sessionmaker()  # This can be your async session fixture or factory
result = await schema.execute(
    query,
    context_value={
        "sqlalchemy_loader": StrawberrySQLAlchemyLoader(
            async_bind_factory=async_sessionmaker
        )
    },
)

Example:

We can do this kind of validation:

def get_sqlalchemy_loader_from_context(context):
    try:
        if isinstance(context, dict):
            loader = context["sqlalchemy_loader"]
        else:
            loader = context.sqlalchemy_loader
    except (KeyError, AttributeError):
        raise MissingSQLAlchemyLoaderError(
            "The context is missing a required 'sqlalchemy_loader' of type StrawberrySQLAlchemyLoader. "
            "Make sure to include it in your GraphQL context."
        )
    return loader

Also we can add a test like this one:

@pytest.mark.asyncio
async def test_should_raise_MissingSQLAlchemyLoaderError(base, async_engine, async_sessionmaker, user_and_group_tables):
    _, GroupModel = user_and_group_tables
    async with async_engine.begin() as conn:
        await conn.run_sync(base.metadata.create_all)
    mapper = StrawberrySQLAlchemyMapper()

    @mapper.type(GroupModel)
    class Group:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        async def group(self, id: strawberry.ID) -> Group:
            session = async_sessionmaker()
            return await session.get(GroupModel, int(id))

    mapper.finalize()
    schema = strawberry.Schema(query=Query)

    query = """
    query GetGroup($id: ID!) {
      group(id: $id) {
        users {
          edges {
            node {
              name
            }
          }
        }
      }
    }
    """

    group = GroupModel(name="Test Group")
    async with async_sessionmaker(expire_on_commit=False) as session:
        session.add(group)
        await session.commit()

    # This execute will raise the error:
    result = await schema.execute(
        query,
        variable_values={"id": group.id},
        # No context_value here to simulate the missing loader
    )

    assert result.errors is None
    # FAILED 😰  tests/relay/test_exception.py::test_should_raise_MissingSQLAlchemyLoaderError[postgresql] - assert [GraphQLError("'NoneType' object has no attribute 'sqlalchemy_loader'", locations=[SourceLocation(line=4, column=9)], path=['group', 'users'])] is None

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions