Skip to content

Latest commit

 

History

History
139 lines (104 loc) · 2.99 KB

pydantic_v1.md

File metadata and controls

139 lines (104 loc) · 2.99 KB

Pydantic V1 Plugin

This plugin generates simple pydantic data classes for your queries and fragments. Pydantic is capable of mapping nested dictionaries to nested types. I.e., no data mapping functions need to be generated.

This plugin expects exactly one gql definition per .gql file. Supported definitions are:

  • fragment
  • query
  • mutation (only response data structures - input data structures are still a TODO)

Opinionated Custom Scalars

The pydantic_v1 plugin has an opinionated approach towards some very common custom scalars defined in https://the-guild.dev/graphql/scalars/docs

Currently it maps the following:

  • JSON maps to pydantic.Json
  • DateTime maps to datetime.datetime

Any other custom scalar will be mapped to str.

Note, that you can also re-map these scalars, e.g.,

# qenerate: map_gql_scalar=JSON -> str
# qenerate: map_gql_scalar=DateTime -> str

See section about Custom Type Mapping in README.

Examples

Query with inline fragments

hero.gql:

# qenerate: plugin=pydantic_v1
query HeroForEpisode {
  hero {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}

hero.py:

class ConfiguredBaseModel(BaseModel):
  # This is set so pydantic can properly match the data to union, i.e., properly infer the correct type
  # https://pydantic-docs.helpmanual.io/usage/model_config/#smart-union
  # https://stackoverflow.com/a/69705356/4478420
  smart_union = True
  extra = Extra.forbid


class Hero(ConfiguredBaseModel):
  name: str = Field(..., alias="name")


class Droid(Hero):  # Note that Droid implements Hero
  primary_function: str = Field(..., alias="primaryFunction")


class Human(Hero):  # Note that Human implements Hero
  height: str = Field(..., alias="height")


class HeroForEpisodeData(ConfiguredBaseModel):
  hero: Optional[list[Union[Droid, Human, Hero]]] = Field(..., alias="hero")

Query with Fragments

hero.gql:

# qenerate: plugin=pydantic_v1
query HeroForEpisode {
  hero {
    ... HeroName
    ... HeroAge
    number
  }
}

hero_name_fragment.gql:

# qenerate: plugin=pydantic_v1
fragment HeroName on Hero {
  name
}

hero_age_fragment.gql:

# qenerate: plugin=pydantic_v1
fragment HeroAge on Hero {
  age
}

hero_name_fragment.py:

class HeroName(BaseModel):
    name: str = Field(..., alias="name")

hero_age_fragment.py:

class HeroAge(BaseModel):
    age: int = Field(..., alias="age")

hero.py:

from hero_age_fragment import HeroAge
from hero_name_fragment import HeroName


# Note, that Hero implements the fragments
class Hero(HeroAge, HeroName):
  number: int = Field(..., alias="number")


class HeroForEpisodeData(BaseModel):
  hero: Optional[list[Hero]] = Field(..., alias="hero")

Note, that the python import path is relative to the directory in which qenerate was executed.