๐Ÿ—ป James Van Dyne

โœˆ๏ธTrips ๐Ÿ—บ๏ธMaps โœ๏ธ๏ธBlog ๐Ÿ”—๏ธ๏ธLinks ๐ŸƒRuns ๐Ÿ‘‰Now
  • ๐ŸกHome
  • โœˆ๏ธTrips
  • ๐Ÿ—บ๏ธMaps
  • โœ๏ธBlog
  • ๐Ÿ”—๏ธLinks
  • ๐ŸƒRuns
  • ๐Ÿ‘‰Now
  • โœ๏ธArticles
  • ๐Ÿ“ค๏ธReplies
  • ๐Ÿ’ฌStatus
  • ๐Ÿ”–๏ธ๏ธBookmarks
  • ๐Ÿ—บCheckins
  • ๐Ÿ“…The Week
  • ๐Ÿ–ฅTech
  • ๐ŸŒฒSustainability
  • ๐ŸƒRunning
  • ๐Ÿง Thoughts
  • ๐Ÿ‡ฏ๐Ÿ‡ตJapan
  • ๐Ÿ’กTIL
  • โ›ฐTanzawa
  • ๐ŸกHome
  • โœ๏ธArticles
  • ๐Ÿ“ค๏ธReplies
  • ๐Ÿ’ฌStatus
  • ๐Ÿ”–๏ธ๏ธBookmarks
  • ๐Ÿ—บCheckins
  • ๐Ÿ“…The Week
  • ๐Ÿ–ฅTech
  • ๐ŸŒฒSustainability
  • ๐ŸƒRunning
  • ๐Ÿง Thoughts
  • ๐Ÿ‡ฏ๐Ÿ‡ตJapan
  • ๐Ÿ’กTIL
  • โ›ฐTanzawa
  • How I Architect My Graphene-Django Projects

    May 19, 2020
    by James

    Recently at work Iโ€™ve been working quite a bit with Django and GraphQL. There doesnโ€™t seem to be much written about best practices for organizing your Graphene-Django projects, so Iโ€™ve decided to document whatโ€™s working for me. In this example I have 3 django apps: common, foo, and hoge.

    Thereโ€™s two main goals for this architecture:


    1. Minimize importing from โ€œoutsideโ€ apps.

    2. Keep testing simple.


    Queries and Mutations Package

    Anything beyond simple queries (i.e. a query that just returns all records of a given model) are implemented in their own file in the queries or mutations sub-package. Each file is as self-contained as possible and contains any type definitions specific to that query, forms for validation, and an object that can be imported by the app's schema.py.

    Input Validation

    All input validation is performed by a classic Django form instance. For ease of use django form input does not necessarily match the GraphQL input. Consider a mutation that sends a list of dictionaries with an object id.

    {
    "foos": [
    {
    "id": 1,
    "name": "Bumble"
    },
    {
    "id": 2,
    "name": "Bee"
    ]
    }

    Before processing the request, you want to validate that the ids passed actually exist and or reference-able by the user making the request. Writing a django form field to handle input would be time consuming and potentially error prone. Instead each form has a class method called convert_graphql_input_to_form_input which takes the mutation input object and returns a dictionary that can be passed the form to clean and validate it.

    from django import forms
    from foo import models

    class UpdateFooForm(forms.Form):
    foos = forms.ModelMultipleChoiceField(queryset=models.Foo.objects)

    @classmethod
    def convert_graphql_input_to_form_input(cls, graphql_input: UpdateFooInput):
    return { "foos": [foo["id"] for foo in graphql_input.foos]] }

    Extra Processing

    Extra processing before save is handled by the form in a prepare_data method. The role this method plays is to prepare any data prior to / without saving. Usually I'd prepare model instances, set values on existing instances and so forth. This allows the save() method to use bulk_create() and bulk_update() easily to keeps save doing just that - saving.

    Objects/List of objects that are going to be saved / bulk_created / updated in save are stored on the form. The list is defined / set in init with full typehints. Example:

    from typing import List, Optional

    class UpdateFooForm(forms.Form):
    foos = forms.ModelMultipleChoiceField(queryset=models.Foo.objects)

    def __init__(*args, **kwargs)
    super().__init__(*args, **kwargs)
    self.foo_bars: List[FooBar] = []
    self.bar: Optional[Bar] = None


    Type Definition Graduation

    Types are defined in each query / mutation where possible. As schema grows and multiple queries/mutations or other app's queries/mutations reference the same type, the location where the type is defined changes. This is partially for a cleaner architecture, but also to avoid import errors.

    โ””โ”€โ”€ apps
    โ”œโ”€โ”€ common
    โ”‚ โ”œโ”€โ”€ schema.py
    โ”‚ โ””โ”€โ”€ types.py # global types used by multiple apps are defined here
    โ””โ”€โ”€ hoge
    โ”œโ”€โ”€ mutations
    โ”‚ โ”œโ”€โ”€ create_hoge.py # types only used by create_hoge are in here
    โ”‚ โ””โ”€โ”€ update_hoge.py
    โ”œโ”€โ”€ queries
    โ”‚ โ””โ”€โ”€ complex_query.py
    โ”œโ”€โ”€ schema.py
    โ””โ”€โ”€ types.py # types used by either create/update_hoge and or complex_query are defined here

    Example Mutation

    The logic kept inside a query/mutation is as minimal as possible. This is as it's difficult to test logic inside the mutation without writing a full-blown end-to-end test.

    from graphene_django.types import ErrorType

    class UpdateHogeReturnType(graphene.Union):
    class Meta:
    types = (HogeType, ErrorType)

    class UpdateHogeMutationType(graphene.Mutation):

    class Meta:
    output = graphene.NonNull(UpdateHogeReturnType)

    class Arguments:
    update_hoge_input = UpdateHogeInputType()

    @staticmethod
    def mutate(root, info, update_hoge_input: UpdateHogeInputType) -> str:
    data = UpdateHogeForm.convert_mutation_input_to_form_input(update_hoge_)
    form = MutationValidationForm(data=data)
    if form.is_valid():
    form.prepare_data()
    return form.save()
    errors = ErrorType.from_errors(form)
    return ErrorType(errors=errors)

    Adding Queries/Mutations to your Schema

    This architecture tries to consistently follow the graphene standard for defining schema. i.e. when defining your schema you create a class Query and class Mutation, then pass those to your schema schema = Schema(query=Query, mutation=Mutation)

    Each app should build its Query and Mutation objects. These will then be imported in the schema.py, combined into a new Query class, and passed to schema.

    # hoge/mutations/update_hoge.py

    class UpdateHogeMutation:

    update_hoge = UpdateHogeMutationType.Field()

    # hoge/mutations/schema.py

    from .mutations import update_hoge, create_hoge

    class Mutation(update_hoge.Mutation,
    create_hoge.Mutation):
    pass

    # common/schema.py

    import graphene

    import foo.schema
    import hoge.schema

    class Query(hoge.schema.Query, foo.schema.Query, graphene.GrapheneObjectType):
    pass

    class Mutation(hoge.schema.Mutation, foo.schema.Mutation, graphene.GrapheneObjectType):
    pass

    schema = graphene.Schema(query=Query, mutation=Mutation)


    Directory Tree Overview


    โ””โ”€โ”€ apps
    โ”œโ”€โ”€ common
    โ”‚ โ”œโ”€โ”€ schema.py
    โ”‚ โ””โ”€โ”€ types.py
    โ”œโ”€โ”€ foo
    โ”‚ โ”œโ”€โ”€ mutations
    โ”‚ โ”‚ โ””โ”€โ”€ create_or_update_foo.py
    โ”‚ โ”œโ”€โ”€ queries
    โ”‚ โ”‚ โ””โ”€โ”€ complex_foo_query.py
    โ”‚ โ””โ”€โ”€ schema.py
    โ””โ”€โ”€ hoge
    โ”œโ”€โ”€ mutations
    โ”‚ โ”œโ”€โ”€ common.py
    โ”‚ โ”œโ”€โ”€ create_hoge.py
    โ”‚ โ””โ”€โ”€ update_hoge.py
    โ”œโ”€โ”€ queries
    โ”‚ โ””โ”€โ”€ complex_query.py
    โ”œโ”€โ”€ schema.py
    โ””โ”€โ”€ types.py

    ๐Ÿ”—permalink
  • May 02, 2020
    by James

    Went for a drive today (at Leoโ€™s insistence) down to Chigasaki-Shi. Felt a little bad with Yokohama plates in Shonan plate territory.

    Didnโ€™t get out of the car, but man itโ€™s so green and nice out there. Saw some huge koinobori too.

    ๐Ÿ”—permalink
  • A Glimpse of the Future

    Apr 20, 2020
    by James

    One of the common memes to come from covid19 is to post a before-after photo of a famous city or landmark. The before covid19 photo is the city as weโ€™ve become accustomed to it: brown air full of smog. The after covid19 at the same location, but with naturally blue skies and clear air.

    With everyone social distancing and automobile/truck traffic near zero we have been given a rare opportunity.ย We no longer have to imagine what our air and cities could be like if we didnโ€™t drive pollution emitting vehicles everywhere, we can see, taste, and smell it with our own eyes.

    Air pollution from cars and trucks have been suffocating our cities slowly, like one boilโ€™s a frog, so we acclimate and brown air becomes โ€œnormalโ€ and the way things have always been. With the burner temporary malfunctioning we can see just what a precarious position weโ€™ve put ourselves in.

    When this is all done and our lungs have acclimated to clean air weโ€™ll have a choice: do we go back to the way things were and forget what weโ€™ve experienced, or do we the courage to demand a change.

    https://twitter.com/sistercelluloid/status/1249027255797460993

    ๐Ÿ”—permalink
  • UNIX: Making Computers Easier To Use

    Apr 12, 2020
    by James

    Watching videos like this one about UNIX system from 1982 is a great reminder that no matter what you're building today, we all stand on the shoulders of giants. Highly worth 20 minutes of your time.

    https://youtu.be/XvDZLjaCJuw

    ๐Ÿ”—permalink
  • Checkin to ๆณ‰ใฎๆฃฎ

    ๆณ‰ใฎๆฃฎ 35.42063680251615 139.5134731287625
    Apr 11, 2020
    by James
    in Japan

    Social distancing in the local forest. Such a great little place to let the little one run about and have a snack surrounded by trees.



    ๐Ÿ”—permalink
  • Checkin to ๅผฅ็”Ÿๅฐ้ง…ๅ‰ๅ…ฌๅœ’

    ๅผฅ็”Ÿๅฐ้ง…ๅ‰ๅ…ฌๅœ’ 35.43038512118084 139.5062232133423
    Mar 22, 2020
    by James
    in Yokohama, Kanagawa, Japan

    Lovely park weather and the blossoms are starting to blossom.



    ๐Ÿ”—permalink
  • Checkin to Tully's Coffee

    Tully's Coffee 35.31103396326208 139.4872309830384
    Mar 20, 2020
    by James
    in Fujisawa, Kanagawa, Japan

    The best spot to drink a coffee and watch some trains in front of Enoshima station.



    ๐Ÿ”—permalink
  • Checkin to Enoshima Beach (ๆฑŸใƒŽๅณถใƒ“ใƒผใƒ)

    Enoshima Beach (ๆฑŸใƒŽๅณถใƒ“ใƒผใƒ) 35.30816905752166 139.4810187792224
    Mar 20, 2020
    by James
    in Fujisawa, Kanagawa, Japan

    ๆตทใƒฉใƒ–



    ๐Ÿ”—permalink
  • Checkin to Starbucks

    Starbucks 35.42861 139.506997
    Mar 01, 2020
    by James
    in Kanagawa, Japan

    Sakura donuts and an ice coffee while Leo sleeps.

    ๐Ÿ”—permalink
  • Checkin to ๆˆธๅกš็จŽๅ‹™็ฝฒ

    ๆˆธๅกš็จŽๅ‹™็ฝฒ 35.39927871436361 139.5408103580196
    Feb 17, 2020
    by James
    in Yokohama, Kanagawa, Japan

    Tax office is a zoo.

    ๐Ÿ”—permalink
Previous 186 of 360 Next
Reply by email
Powered by
๐Ÿ”Tanzawa

โ† An IndieWeb Webring ๐Ÿ•ธ๐Ÿ’โ†’
Photo of James Van Dyne James Van Dyne Japan

Web developer living in Japan.