strawberry
strawberry copied to clipboard
Full support for nested pydantic, sqlmodel and ormar models
This PR add the following features:
- Full support for deriving nested pydantic models (including when using
List
,Optional
,Union
andForwardRef
) - Deriving ormar models with relationships (
ForeignKey
,ManyToMany
, and reverse relations) - Deriving SQLModel models with
Relationship
fields - Strawberry types declarations don't have to follow model declaration order (eg: children can be defined before parents)
- Add a new
exclude
param to thestrawberry.experimental.pydantic.type
decorator, allowing to include all fields while excluding some
Description
Pydantic
Nested pydantic models should works in most situations, as long as it's GraphQL typed.
Example :
class User(pydantic.BaseModel):
name: str
hobby: Optional[List["Hobby"]]
class Hobby(pydantic.BaseModel):
name: str
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
The order in which strawberry types are defined doesn't matter (doesn't have to follow pydantic models declaration order):
class Hobby(pydantic.BaseModel):
name: str
class User(pydantic.BaseModel):
name: str
hobby: Hobby
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
Ormar
Omar is an orm that uses pydantic as its base: all ormar models are pydantic models.
ForeignKey
, ManyToMany
and reverse relations (related fields in Django) are supported.
When using the pydantic decorator to generate a strawberry type, ormar fields will be mapped as you would expect:
class Hobby(ormar.Model):
name: str
class User(ormar.Model):
name: str = ormar.String(max_length=255)
hobby: Hobby = ormar.ForeignKey(Hobby, nullable=False)
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
Will gives the following GraphQL schema:
type HobbyType {
name: String!
}
type UserType {
name: String!
hobby: HobbyType!
}
When using all_fields=True
it also includes the reverse relation users
that ormar automatically created in the Hobby
model:
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
type HobbyType {
name: String!
users: [UserType]
}
type UserType {
name: String!
hobby: HobbyType!
}
SQLModel
SQLModel is another pydantic-based orm, that uses SQLAlchemy to define models. All relations are defined using the Relationship
field:
class Hobby(SQLModel):
name: str
users: List["User"] = Relationship(back_populates="hobby")
class User(SQLModel):
name: str = Field()
hobby: Hobby = Relationship(back_populates="users")
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
Will translate in the following schema:
type HobbyType {
name: String!
users: [UserType!]!
}
type UserType {
name: String!
hobby: HobbyType!
}
Types of Changes
- [ ] Core
- [ ] Bugfix
- [ ] New feature
- [x] Enhancement/optimization
- [ ] Documentation
Issues Fixed or Closed by This PR
- #1183
Checklist
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the CONTRIBUTING document.
- [x] I have added tests to cover my changes.
- [x] I have tested the changes and verified that they work and don't break anything (as well as I can manage).
Thanks for adding the RELEASE.md
file!
Here's a preview of the changelog:
- Add Full support for deriving nested pydantic models (including when using
List
,Optional
,Union
andForwardRef
) - Support for deriving ormar models with relationships (
ForeignKey
,ManyToMany
, and reverse relations) - Support for deriving SQLModel models with
Relationship
fields - Strawberry type declarations don't have to follow model declarations order (eg: childs can be defined before parents)
- Add a new
exclude
param to thestrawberry.experimental.pydantic.type
decorator, allowing to include all fields while excluding some
Pydantic
GraphQL container types (List
, Optional
and Union
) and ForwardRef
are supported:
class User(pydantic.BaseModel):
name: str
hobby: Optional[List["Hobby"]]
class Hobby(pydantic.BaseModel):
name: str
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
Ormar
ForeignKey
, ManyToMany
and reverse relations are supported:
class Hobby(ormar.Model):
name: str
class User(ormar.Model):
name: str = ormar.String(max_length=255)
hobby: Hobby = ormar.ForeignKey(Hobby, nullable=False)
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
type HobbyType {
name: String!
users: [UserType]
}
type UserType {
name: String!
hobby: HobbyType!
}
SLQModel
SQLModel is another pydantic-based orm, that uses SQLAlchemy to define models. All relations are defined using the Relationship
field:
class Hobby(SQLModel, table=True):
name: str
users: List["User"] = Relationship(back_populates="hobby")
class User(SQLModel, table=True):
name: str = Field()
hobby: Hobby = Relationship(back_populates="users")
@strawberry.experimental.pydantic.type(Hobby, all_fields=True)
class HobbyType:
pass
@strawberry.experimental.pydantic.type(User, all_fields=True)
class UserType:
pass
type HobbyType {
name: String!
users: [UserType!]!
}
type UserType {
name: String!
hobby: HobbyType!
}
Here's the preview release card for twitter:
Here's the tweet text:
🆕 Release (next) is out! Thanks to @gazorby for this great new feature!
Strawberry types can now be generated from SQLModel / ormar models!
Get it here 👉 https://github.com/strawberry-graphql/strawberry/releases/tag/(next)
Hi @gazorby! Thanks for this PR, I'll try to take a look in the weekend <3
Codecov Report
Merging #1637 (6ac1045) into main (f2cc503) will decrease coverage by
0.02%
. The diff coverage is99.26%
.
:exclamation: Current head 6ac1045 differs from pull request most recent head 65da6f2. Consider uploading reports for the commit 65da6f2 to get more accurate results
Additional details and impacted files
@@ Coverage Diff @@
## main #1637 +/- ##
==========================================
- Coverage 98.14% 98.12% -0.02%
==========================================
Files 129 131 +2
Lines 4528 4649 +121
Branches 779 799 +20
==========================================
+ Hits 4444 4562 +118
- Misses 43 45 +2
- Partials 41 42 +1
Hi @patrick91! Will update the doc accordingly once API changes have been finalized
@gazorby thank you so much for doing this! is there any chance we can split this PR into multiple ones? it would make it easier to get things merged 😊
@gazorby @patrick91 I wonder how we could move this forward / split the PR if required. I've used @gazorby 's fork in a POC for a while (the Pydantic features, not ormar or SQLModel) and I feel features like the nested models delivered here are essential for most non-trivial Strawberry implementations using Pydantic.
With the (thankfully!) frequent releases of Strawberry though this is not sustainable for long as we want to uptake the latest features and releases, and some pydantic related code has also been moving again. Would be fantastic to be able to merge the functionality soon.
Hi @alexhafner, thanks for the message, that's great to know! I might need to do some pydantic+strawberry stuff for work so I should be able to take a look at this PR :)
Are you on discord by the way? I might want to ask you some questions regarding this PR 😊
@patrick91 sure thing, I'll message you there
Hi @alexhafner @patrick91
Sorry for being silent here, was busy on other projects. I think nested model generation is important for any advanced use-case making use of pydantic or any other model-like abstraction. I recently came through the great expedock/strawberry-sqlalchemy-mapper library that help generating strawberry types from sqlalchemy models, and forked it to add input types generation and full relay support (including pagination) to their already exisiting relay implementation.
I realized that doing the same in strawberry if this PR was merged would be quite cumbersome as it lacks some way of customizing what's happening during nested generation (eg: define a resolver that would accept a page input and return a connection). Of course we don't want to bloat more this PR by adding relay support, but maybe some bindings (decorator param?) to let strawberry users customize how nested types would be generated.
Hi @alexhafner @patrick91
Sorry for being silent here, was busy on other projects.
no worries at all!
I think nested model generation is important for any advanced use-case making use of pydantic or any other model-like abstraction.
Yup, I agree 😌
I recently came throught the great expedock/strawberry-sqlalchemy-mapper library that help generating strawberry types from sqlalchemy models, and forked it to add input types generation and full relay support (including pagination) to their already exisiting relay implementation.
Did you make a PR? would be interesting to see how you implemented it
I realized that doing the same in strawbrry if this PR was merged would be quite cumbersome as it lacks some way of customizing what's happening during nested generation (eg: define a resolver that would accept a page input and return a connection). Of course we don't to bloat more this PR by adding relay support, but maybe some bindings (decorator param?) to let strawberry users customize how nested types would be generated.
Would you be able to write a summary for this?
Also what do you think of splitting this PR in multiple pieces?
Hi! Is this feature development still in active development?
@gazorby a lot of great work here. Worth breaking down what can be merged straight away into a separate pr?
@gazorby These are fantastic changes! Any chance we can get them merged soon?
hi, I need exclude pls this PR looks great
Unfortunately I have no more interest in deriving strawberry types from sqlmodel/ormar models and pydantic first class support is tracked in #2181. For anyone willing to iterate from there, initial work is at https://github.com/gazorby/strawberry/tree/ref/simpler-orm-implementation