strawberry-django-dataloaders


Namestrawberry-django-dataloaders JSON
Version 0.2.0 PyPI version JSON
download
home_pagehttps://github.com/VojtechPetru/strawberry-django-dataloaders
SummaryA set of tools for using dataloaders with Django and Strawberry GraphQL.
upload_time2023-11-04 17:38:28
maintainer
docs_urlNone
authorvojtech
requires_python>=3.10,<4.0
licenseLICENSE.md
keywords strawberry django graphql dataloader
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 3.34848s