-
-
Notifications
You must be signed in to change notification settings - Fork 32
Description
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