# Dataloaders for Django and Strawberry
A set of tools for using dataloaders with [Django](https://github.com/django/django)
and [Strawberry](https://github.com/strawberry-graphql/strawberry) without unnecessary boilerplate.
## Installation
```bash
pip install strawberry-django-dataloaders
```
# Usage & examples
This package provides 3 levels of generating dataloaders, each offering higher level of abstraction
than the previous one.
### View
In the `graphql/` endpoint where you wish to use dataloaders, use (or subclass) `DataloaderAsyncGraphQLView`.
This is necessary because created dataloaders are stored to the request context. This ensures that:
- fresh dataloader instances are created for each request
- a dataloader persists for the duration of the request
which are both important properties for loaded values caching.
```python
from django.urls import path
from strawberry_django_dataloaders.views import DataloaderAsyncGraphQLView
urlpatterns = [
path('graphql/', DataloaderAsyncGraphQLView.as_view(schema=...)),
]
```
### Models definition
Definition of models used in the examples.
```python
from django.db import models
class Fruit(models.Model):
plant = models.OneToOneField("FruitPlant", ...)
color = models.ForeignKey("Color", ...)
varieties = models.ManyToManyField("FruitVariety", ..., related_name="fruits")
class FruitEater(models.Model):
favourite_fruit = models.ForeignKey("Fruit", ..., related_name="eaters")
```
### Level 1: Simple dataloader
On the first level, we're defining and using different dataloader for each relationship.
#### One-to-one and Many-to-one relationship
1. Define the dataloaders
```python
from strawberry_django_dataloaders import dataloaders
class ColorPKDataLoader(dataloaders.BasicPKDataLoader):
model = models.Color
class FruitPlantPKDataLoader(dataloaders.BasicPKDataLoader):
model = models.FruitPlant
```
2. Use them when defining the Strawberry type
```python
@strawberry_django.type(models.Fruit)
class FruitType:
id: strawberry.auto
### ↓ HERE ↓ ###
@strawberry.field
async def color(self: "models.Fruit", info: "Info") -> ColorType | None:
return await dataloaders.ColorPKDataLoader(context=info.context).load(self.color_id)
@strawberry.field
async def plant(self: "models.Fruit", info: "Info") -> FruitPlantType | None:
return await dataloaders.FruitPlantPKDataLoader(context=info.context).load(self.plant_id)
```
#### One-to-many relationship
1. Define the dataloader
```python
from strawberry_django_dataloaders import dataloaders
class FruitEatersReverseFKDataLoader(dataloaders.BasicReverseFKDataLoader):
model = models.FruitEater
reverse_path = "favourite_fruit_id" # <-- is the "reverse" FK field from FruitEater to Fruit model
```
2. Use it when defining the Strawberry type
```python
@strawberry_django.type(models.Fruit)
class FruitType:
id: strawberry.auto
### ↓ HERE ↓ ###
@strawberry.field
async def eaters(self: "models.Fruit", info: "Info") -> list[FruitEaterType]:
return await dataloaders.FruitEatersReverseFKDataLoader(context=info.context).load(self.pk)
```
### Level 2: Dataloader factories
When using the dataloader factories, we no longer need to define a dataloader for each relation.
```python
from strawberry_django_dataloaders import factories
@strawberry_django.type(models.Fruit)
class FruitTypeDataLoaderFactories:
id: strawberry.auto
### ↓ ONE-TO-ONE AND MANY-TO-ONE DATALOADERS ↓ ###
@strawberry.field
async def color(self: "models.Fruit", info: "Info") -> ColorType | None:
loader = factories.PKDataLoaderFactory.get_loader_class("tests.Color")
return await loader(context=info.context).load(self.color_id)
@strawberry.field
async def plant(self: "models.Fruit", info: "Info") -> FruitPlantType | None:
loader = factories.PKDataLoaderFactory.get_loader_class("tests.FruitPlant")
return await loader(context=info.context).load(self.plant_id)
### ↓ ONE-TO-MANY DATALOADER ↓ ###
@strawberry.field
async def eaters(self: "models.Fruit", info: "Info") -> list[FruitEaterType]:
loader = factories.ReverseFKDataLoaderFactory.get_loader_class(
"tests.FruitEater",
reverse_path="favourite_fruit_id",
)
return await loader(context=info.context).load(self.color_id)
```
### Level 3: Auto dataloader field
A field used in a similar fashion as native Django strawberry field, but it has auto-defined correct dataloader handler
based on the field relationship.
```python
from strawberry_django_dataloaders import fields
@strawberry_django.type(models.Fruit)
class FruitTypeAutoDataLoaderFields:
id: strawberry.auto
color: ColorType = fields.auto_dataloader_field()
plant: FruitPlantType = fields.auto_dataloader_field()
varieties: list[FruitVarietyType] = fields.auto_dataloader_field()
eaters: list[FruitEaterType] = fields.auto_dataloader_field()
```
## Contributing
Pull requests for any improvements are welcome.
[Poetry](https://github.com/sdispater/poetry) is used to manage dependencies.
To get started follow these steps:
```shell
git clone https://github.com/VojtechPetru/strawberry-django-dataloaders
cd strawberry-django-dataloaders
poetry install
poetry run pytest
```
### Pre commit
We have a configuration for
[pre-commit](https://github.com/pre-commit/pre-commit), to add the hook run the
following command:
```shell
pre-commit install
```
## Links
- Inspired and builds on top of a great article at: https://alexcleduc.com/posts/graphql-dataloader-composition/
- Repository: https://github.com/VojtechPetru/strawberry-django-dataloaders
- Issue tracker: https://github.com/VojtechPetru/strawberry-django-dataloaders/issues.
In case of sensitive bugs (e.g. security vulnerabilities) please contact me at _petru.vojtech@gmail.com_ directly.
## Known issues/shortcomings
- `Many-to-many` relation is currently not supported.
Raw data
{
"_id": null,
"home_page": "https://github.com/VojtechPetru/strawberry-django-dataloaders",
"name": "strawberry-django-dataloaders",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.10,<4.0",
"maintainer_email": "",
"keywords": "strawberry,django,graphql,dataloader",
"author": "vojtech",
"author_email": "petru.vojtech@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/47/59/0e87b6838b165a15b3246ebb49581ae08f323f5f2a308501febd0d4597ad/strawberry_django_dataloaders-0.2.0.tar.gz",
"platform": null,
"description": "# Dataloaders for Django and Strawberry\nA set of tools for using dataloaders with [Django](https://github.com/django/django) \nand [Strawberry](https://github.com/strawberry-graphql/strawberry) without unnecessary boilerplate.\n\n## Installation\n\n```bash\npip install strawberry-django-dataloaders\n```\n\n\n# Usage & examples\nThis package provides 3 levels of generating dataloaders, each offering higher level of abstraction\nthan the previous one.\n\n### View\nIn the `graphql/` endpoint where you wish to use dataloaders, use (or subclass) `DataloaderAsyncGraphQLView`.\nThis is necessary because created dataloaders are stored to the request context. This ensures that:\n\n- fresh dataloader instances are created for each request\n- a dataloader persists for the duration of the request\n\nwhich are both important properties for loaded values caching.\n\n```python\nfrom django.urls import path\n\nfrom strawberry_django_dataloaders.views import DataloaderAsyncGraphQLView\n\nurlpatterns = [\n path('graphql/', DataloaderAsyncGraphQLView.as_view(schema=...)),\n]\n```\n\n### Models definition\nDefinition of models used in the examples.\n```python\nfrom django.db import models\n\nclass Fruit(models.Model):\n plant = models.OneToOneField(\"FruitPlant\", ...)\n color = models.ForeignKey(\"Color\", ...)\n varieties = models.ManyToManyField(\"FruitVariety\", ..., related_name=\"fruits\")\n\nclass FruitEater(models.Model):\n favourite_fruit = models.ForeignKey(\"Fruit\", ..., related_name=\"eaters\")\n```\n### Level 1: Simple dataloader\nOn the first level, we're defining and using different dataloader for each relationship.\n#### One-to-one and Many-to-one relationship\n1. Define the dataloaders\n```python\nfrom strawberry_django_dataloaders import dataloaders\n\nclass ColorPKDataLoader(dataloaders.BasicPKDataLoader):\n model = models.Color\n\n\nclass FruitPlantPKDataLoader(dataloaders.BasicPKDataLoader):\n model = models.FruitPlant\n```\n2. Use them when defining the Strawberry type\n```python\n@strawberry_django.type(models.Fruit)\nclass FruitType:\n id: strawberry.auto\n \n ### \u2193 HERE \u2193 ###\n @strawberry.field\n async def color(self: \"models.Fruit\", info: \"Info\") -> ColorType | None:\n return await dataloaders.ColorPKDataLoader(context=info.context).load(self.color_id)\n\n @strawberry.field\n async def plant(self: \"models.Fruit\", info: \"Info\") -> FruitPlantType | None:\n return await dataloaders.FruitPlantPKDataLoader(context=info.context).load(self.plant_id)\n```\n\n#### One-to-many relationship\n1. Define the dataloader\n```python\nfrom strawberry_django_dataloaders import dataloaders\n\nclass FruitEatersReverseFKDataLoader(dataloaders.BasicReverseFKDataLoader):\n model = models.FruitEater\n reverse_path = \"favourite_fruit_id\" # <-- is the \"reverse\" FK field from FruitEater to Fruit model\n```\n2. Use it when defining the Strawberry type\n```python\n@strawberry_django.type(models.Fruit)\nclass FruitType:\n id: strawberry.auto\n \n ### \u2193 HERE \u2193 ###\n @strawberry.field\n async def eaters(self: \"models.Fruit\", info: \"Info\") -> list[FruitEaterType]:\n return await dataloaders.FruitEatersReverseFKDataLoader(context=info.context).load(self.pk)\n```\n\n### Level 2: Dataloader factories\nWhen using the dataloader factories, we no longer need to define a dataloader for each relation.\n```python\nfrom strawberry_django_dataloaders import factories\n\n\n@strawberry_django.type(models.Fruit)\nclass FruitTypeDataLoaderFactories:\n id: strawberry.auto\n \n ### \u2193 ONE-TO-ONE AND MANY-TO-ONE DATALOADERS \u2193 ###\n @strawberry.field\n async def color(self: \"models.Fruit\", info: \"Info\") -> ColorType | None:\n loader = factories.PKDataLoaderFactory.get_loader_class(\"tests.Color\")\n return await loader(context=info.context).load(self.color_id)\n\n @strawberry.field\n async def plant(self: \"models.Fruit\", info: \"Info\") -> FruitPlantType | None:\n loader = factories.PKDataLoaderFactory.get_loader_class(\"tests.FruitPlant\")\n return await loader(context=info.context).load(self.plant_id)\n \n ### \u2193 ONE-TO-MANY DATALOADER \u2193 ###\n @strawberry.field\n async def eaters(self: \"models.Fruit\", info: \"Info\") -> list[FruitEaterType]:\n loader = factories.ReverseFKDataLoaderFactory.get_loader_class(\n \"tests.FruitEater\",\n reverse_path=\"favourite_fruit_id\",\n )\n return await loader(context=info.context).load(self.color_id)\n```\n\n### Level 3: Auto dataloader field\nA field used in a similar fashion as native Django strawberry field, but it has auto-defined correct dataloader handler\nbased on the field relationship.\n```python\nfrom strawberry_django_dataloaders import fields \n\n@strawberry_django.type(models.Fruit)\nclass FruitTypeAutoDataLoaderFields:\n id: strawberry.auto\n color: ColorType = fields.auto_dataloader_field()\n plant: FruitPlantType = fields.auto_dataloader_field()\n varieties: list[FruitVarietyType] = fields.auto_dataloader_field()\n eaters: list[FruitEaterType] = fields.auto_dataloader_field()\n```\n\n## Contributing\nPull requests for any improvements are welcome.\n\n[Poetry](https://github.com/sdispater/poetry) is used to manage dependencies.\nTo get started follow these steps:\n\n```shell\ngit clone https://github.com/VojtechPetru/strawberry-django-dataloaders\ncd strawberry-django-dataloaders\npoetry install\npoetry run pytest\n```\n\n### Pre commit\n\nWe have a configuration for\n[pre-commit](https://github.com/pre-commit/pre-commit), to add the hook run the\nfollowing command:\n\n```shell\npre-commit install\n```\n\n## Links\n- Inspired and builds on top of a great article at: https://alexcleduc.com/posts/graphql-dataloader-composition/\n- Repository: https://github.com/VojtechPetru/strawberry-django-dataloaders\n- Issue tracker: https://github.com/VojtechPetru/strawberry-django-dataloaders/issues. \nIn case of sensitive bugs (e.g. security vulnerabilities) please contact me at _petru.vojtech@gmail.com_ directly.\n\n## Known issues/shortcomings\n- `Many-to-many` relation is currently not supported.\n",
"bugtrack_url": null,
"license": "LICENSE.md",
"summary": "A set of tools for using dataloaders with Django and Strawberry GraphQL.",
"version": "0.2.0",
"project_urls": {
"Homepage": "https://github.com/VojtechPetru/strawberry-django-dataloaders",
"Repository": "https://github.com/VojtechPetru/strawberry-django-dataloaders"
},
"split_keywords": [
"strawberry",
"django",
"graphql",
"dataloader"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "478cdf5c21509b839d41fe555fb378f682d907b4e5b058639e01d235b3eeff5e",
"md5": "2a8704d6b9a69e8b9d3ab53314c99341",
"sha256": "0a47f61115a3596120e70d5a79cf936b55b20c5472c6b5bad59e0dc7456bc379"
},
"downloads": -1,
"filename": "strawberry_django_dataloaders-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2a8704d6b9a69e8b9d3ab53314c99341",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10,<4.0",
"size": 10340,
"upload_time": "2023-11-04T17:38:27",
"upload_time_iso_8601": "2023-11-04T17:38:27.415404Z",
"url": "https://files.pythonhosted.org/packages/47/8c/df5c21509b839d41fe555fb378f682d907b4e5b058639e01d235b3eeff5e/strawberry_django_dataloaders-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "47590e87b6838b165a15b3246ebb49581ae08f323f5f2a308501febd0d4597ad",
"md5": "6d617731fcb4b182c87691ec44dd10e5",
"sha256": "f76c47c58065fe449b88d0913332ae91e919f60ef9d57ebca2f8ebcb62d7c666"
},
"downloads": -1,
"filename": "strawberry_django_dataloaders-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "6d617731fcb4b182c87691ec44dd10e5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10,<4.0",
"size": 8360,
"upload_time": "2023-11-04T17:38:28",
"upload_time_iso_8601": "2023-11-04T17:38:28.765317Z",
"url": "https://files.pythonhosted.org/packages/47/59/0e87b6838b165a15b3246ebb49581ae08f323f5f2a308501febd0d4597ad/strawberry_django_dataloaders-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-04 17:38:28",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "VojtechPetru",
"github_project": "strawberry-django-dataloaders",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "strawberry-django-dataloaders"
}