Added a new optional constructor parameter to always use lists instead of relay Connections for relationships. Defaults to False, maintaining current functionality. If set to True, all relationships will be handled as lists.
Example: mapper = StrawberrySQLAlchemyMapper(always_use_list=True)
Contributed by Gabor Torok via PR #257
Implement Relay-style cursor pagination for SQLAlchemy relationships by extending the connection resolver and DataLoader to accept pagination arguments, computing pageInfo metadata, and introducing cursor utilities. Add PaginatedLoader to scope DataLoader instances per pagination parameters and update tests to verify pagination behavior.
New Features:
- Support cursor-based pagination (first, after, last, before) on GraphQL relationship fields
- Introduce PaginatedLoader to manage DataLoader instances per pagination configuration
Enhancements:
- Extend connection resolvers to compute pageInfo fields (hasNextPage, hasPreviousPage, totalCount) and handle forward and backward pagination
- Add utilities for cursor encoding/decoding and relationship key extraction
Tests:
- Add comprehensive tests for forward and backward pagination scenarios in both synchronous and asynchronous execution contexts
Examples:
Get the first three books for a specific author:
query {
author(id: 1) {
id
name
books(first: 3) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Get all books after a specific book's cursor:
query($afterBook: String) {
author(id: 1) {
id
name
books(after: $afterBook) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Get the first three books for a specific author after a specific book's cursor:
query($afterBook: String) {
author(id: 1) {
id
name
books(first: 3, after: $afterBook) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Get the last three books for a specific author:
query {
author(id: 1) {
id
name
books(last: 3) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Get all books before a specific book's cursor:
query($beforeBook: String) {
author(id: 1) {
id
name
books(before: $beforeBook) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Get the last three books for a specific author before a specific book's cursor:
query($beforeBook: String) {
author(id: 1) {
id
name
books(last: 3, before: $beforeBook) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}Contributed by David Roeca via PR #255
This release improves how types inherit fields from other mapped types using @mapper.type(...).
You can now safely inherit from another mapped type, and the resulting GraphQL type will include all expected fields with predictable conflict resolution.
Some examples:
- Basic Inheritance:
@mapper.type(ModelA)
class ApiA:
pass
@mapper.type(ModelB)
class ApiB(ApiA):
# ApiB inherits all fields declared in ApiA
pass- The
__exclude__option continues working:
@mapper.type(ModelA)
class ApiA:
__exclude__ = ["relationshipB_id"]
@mapper.type(ModelB)
class ApiB(ApiA):
# ApiB will have all fields declared in ApiA, except "relationshipB_id"
pass- If two SQLAlchemy models define fields with the same name, the field from the model inside
.type(...)takes precedence:
class ModelA(base):
__tablename__ = "a"
id = Column(String, primary_key=True)
example_field = Column(String(50))
class ModelB(base):
__tablename__ = "b"
id = Column(String, primary_key=True)
example_field = Column(Integer, autoincrement=True)
@mapper.type(ModelA)
class ApiA:
# example_field will be a String
pass
@mapper.type(ModelB)
class ApiB(ApiA):
# example_field will be taken from ModelB and will be an Integer
pass- If a field is explicitly declared in the mapped type, it will override any inherited or model-based definition:
class ModelA(base):
__tablename__ = "a"
id = Column(String, primary_key=True)
example_field = Column(String(50))
class ModelB(base):
__tablename__ = "b"
id = Column(String, primary_key=True)
example_field = Column(Integer, autoincrement=True)
@mapper.type(ModelA)
class ApiA:
pass
@mapper.type(ModelB)
class ApiB(ApiA):
# example_field will be a Float
example_field: float = strawberry.field(name="exampleField")Contributed by Luis Gustavo via PR #253
This update helps verify compatibility with Python 3.13.
- Added new test environments in the CI pipeline to ensure compatibility with Python 3.13.
- No changes to runtime code or dependencies.
Contributed by Luis Gustavo via PR #254
This release does not introduce any new features or bug fixes. It focuses solely on internal code quality improvements.
Changes:
- Added Mypy configuration aligned with the main Strawberry project.
- Enforced type checking via CI to ensure consistency.
- Ran pre-commit across all files to standardize formatting and follow the project's linting architecture.
These changes aim to improve maintainability and ensure better development practices moving forward.
Contributed by Luis Gustavo via PR #250
Ensure association proxy resolvers return valid relay connections, including page_info and edge cursor details, even for empty results.
Thanks to https://github.com/tylernisonoff for the original PR.
Contributed by Luis Gustavo via PR #241
Added support for GraphQL directives in the SQLAlchemy type mapper, enabling better integration with GraphQL federation.
Example usage:
@mapper.type(Employee, directives=["@deprecated(reason: 'Use newEmployee instead')"])
class Employee:
passContributed by Cameron Sechrist via PR #204
Add an optional function to exclude relationships from relay pagination and use traditional strawberry lists. Default behavior preserves original behavior for backwords compatibilty.
Contributed by Juniper via PR #168
Updated imports to be compatible with strawberry 0.236.0 Increased the minimum required strawberry version to 0.236.0
Contributed by Hendrik via PR #187
Resolved an issue with the BigInt scalar definition, ensuring compatibility with Python 3.8 and 3.9. The missing name parameter was added to prevent runtime errors. Fixed failing CI tests by updating the GitHub Actions workflow to improve test stability.
Contributed by Luis Gustavo via PR #190
Fix an issue where auto generated connections were missing some expected attributes to be properly instantiated.
Contributed by Thiago Bellini Ribeiro via PR #137
This change implements a new custom scalar BigInt that is mapped to SQLAlchemy's BigInteger.
Contributed by IdoKendo via PR #101
Fix a regression from 0.4.0 which was raising an issue when trying to create a connection from the model's relationships.
Contributed by Thiago Bellini Ribeiro via PR #106
Initial relay connection/node support using Strawberry's relay integration.
Contributed by Thiago Bellini Ribeiro via PR #65
Make sure async session is still open when we call .all()
Contributed by mattalbr via PR #55
Adds support for async sessions. To use:
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from strawberry_sqlalchemy_mapper import StrawberrySQLAlchemyLoader
url = "postgresql://..."
engine = create_async_engine(url)
sessionmaker = async_sessionmaker(engine)
loader = StrawberrySQLAlchemyLoader(async_bind_factory=sessionmaker)Contributed by mattalbr via PR #53
Fix typo in pyproject.toml for poetry build.
Contributed by Dan Sully via PR #52
Native SQLAlchemy JSON Conversion Support. Added native support for SQLAlchemy JSON conversions. Now, you'll find that sqlalchemy.JSON is converted to strawberry.scalars.JSON for enhanced compatibility.
Contributed by Luis Gustavo via PR #40
Makes a series of minor changes to fix lint errors between MyPy and Ruff.
Contributed by mattalbr via PR #38
Fixes a bug where an Interface is not properly registered, resulting in an infinite-loop for mapping Interfaces to polymorphic Models.
Contributed by Layton Wedgeworth via PR #24
Dependency version changes needed for python 3.12 compatibility.