pydantic-graphql 1.0.0

Creator: bradpython12

Last updated:

Add to Cart

Description:

pydanticgraphql 1.0.0

ql (in development)
Graphql client library, wrapped around pydantic classes for type validation,
provide simple, safe and pythonic way to query data from a graphql api.
using pydantic for creating python objects from rest api is common, it is easy and
it has type validation, so why not make it easy also for graphql apis?
features:

python objects to valid graphql string
http send and recv information
scalar query responses

TOC

install
what can it do
how does it handle http
configure my pydantic model
querying

what if my class name is different from my query name?
what if my field name is different from my query name?


query operations

arguments
inline fragments


http
query examples

simple query
smart implements w nested query w inline fragment
query with http
query and scalar response


api

model

query_fields_nt


query



install
pip3 install pydantic-graphql

what can it do
at the time of writing, the ql library supports only querying data and scalarazing it to
the pydantic models, no extra code is required to make your pydantic model compatible
with the library.
to get a better image you can take a look at the #query examples
how does it handle http?
http can be different from implementation to implementation, most implementation of graphql
are very simple, a single POST request with basic authentication, there is no need for that
to be controlled by the library, the whole point of this library is to make it easy to work
with pydantic and graphql apis, for how configure http read #http
configure my pydantic model
it is simple, you can just configure your pydantic model like so
import ql
from pydantic import BaseModel

@ql.model
class MyModel(BaseModel):
...

querying
querying is the most common operation in any api, we read data more then
we mutate it, we will use this simple model for our example
import ql
from pydantic import BaseModel

@ql.model
class Point(BaseModel):
x: int
y: int

if we want to query this model from graphql, our request probably will look like this
query {
Point {
x,
y
}
}

with the ql library it will look like so
import ql

# define the `Point` model

query_str = ql.query(
(Point, (
ql._(Point).x,
ql._(Point).y
))
)


what the heck is the _ function? read here about the function

this python code will convert the python tuple to a valid graphql query that we
can use to send graphql, we can print it
print(query_str)
# query{Point{x,y,__typename}}

by default, all query functions will add the __typename field, we can prevent that
with passing the query function argument include_typename=False, but we won't do that for now.
the basic structure of a python query tuple is this
(<model>, (
<field_a>,
<field_b>,
...
))

now how do you deal with nested models?
import ql
from pydantic import BaseModel

@ql.model
class Owner(BaseModel):
name: str
age: int

@ql.model
class Shop(BaseModel):
owner: Owner
items_count: int

we want to query shop with the owner data, our graphql query will look like so
query {
Shop {
items_count,
owner {
name
age
}
}
}

our python query will look like so
query_str = ql.query(
(Shop, (
ql._(Shop).items_count,
(ql._(owner), (
ql._(Owner).name,
ql._(Owner).age
))
))
)

you see the pattern? for sub fields we use the same structured tuple,
but instead of a model, we give it a field, it this nesting can continue as
much as we want.
(<model>, (
<field_a>,
<field_b>,
(<field_c>, (
<c_model_field_a>,
<c_model_field_b>
...
))
))

what if my class name is different from my query name?
by default when you decorate your model with ql.model, the used
name in the query is the class name, but lets say we have this case
import ql
from pydantic import BaseModel

@ql.model
class Human(BaseModel):
name: str

but our query should look like this
query {
person { # we need the name `person` instead of human
name
}
}

for that ql.model can take the argument query_name which will be used
when we query that model
@ql.model(query_name="person")
class Human(BaseModel):
...

and we can query regularlly
query_str = ql.query(
(Human, (
ql._(Human).name
))
)
print(query_str)
# query{person{name, __typename}}

what if my field name is different from my query name?
in case we have this case:
import ql
from pydantic import BaseModel

@ql.model
class Human(BaseModel):
first_name: str
middle_name: str
last_name: str

but our query should look like so
query {
Human {
name, # first_name
middle, # middle_name
last # last_name
}
}

we can attach metadata to our field that tells ql, that this field
has different query name
...
from typing import Annotated

@ql.model
class Human(BaseModel):
first_name: Annotated[str, ql.metadata(query_name="first")]
middle_name: Annotated[str, ql.metadata(query_name="middle")]
last_name: Annotated[str, ql.metadata(query_name="last")]

we can query regularly and we get our expected results
query_str = ql.query(
(Human, (
ql._(Human).first_name,
ql._(Human).middle_name,
ql._(Human).last_name
))
)
print(query_str)
# query{Human{first,middle,last,__typename}}

query operations
in graphql we have couple of operations that we can use
when we query our data
raw query
send a simple query string and get response dict
response = ql.raw_query_response("""
query {
Person(name: "bob") {
name,
age
}
}
""")

scalar response
scalar given graphql response, note that the response
must contain the __typename field for any type, thats how the scalar
knows which model should be used
arguments
graphql supports arguments when querying, ql supports
it too
import ql
from pydantic import BaseModel

@ql.model
class Human(BaseModel):
name: str
age: int

query_str = ql.query(
(ql.arguments(Human, filter="age <= 50"), (
ql._(Human).name,
ql._(Human).age
))
)
print(query_str)
# query{Human(filter: "age <= 50"){name,age,__typename}}

it is simple as just wrapping our model with ql.arguments
inline fragments
graphql supports inline fragments, this happens when a type can return multiple
different types with different fields, ql supports that too
import ql
from pydantic import BaseModel

@ql.model
class Human(BaseModel):
name: str

@ql.model
class Male(Human):
working: bool

@ql.model
class Female(Human):
pregnant: bool

query_str = ql.query(
(Human, (
ql._(Human).name,
(ql.on(Male), (
ql._(Male).working,
)),
(ql.on(Female), (
ql._(Female).pregnant,
))
))
)
print(query_str)
# query{Human{name, ...on Male{working,__typename}, ...on Female{pregnant,__typename},__typename}}

http
todo
Query examples
simple query

import ql
from pydantic import BaseModel


@ql.model
class Point(BaseModel):
x: int
y: int


q = ql.query(
(Point, (
ql._(Point).x,
ql._(Point).y
))
)
print(q)


query{Point{x,y}}


smart implements w nested query w inline fragment

import ql
from pydantic import BaseModel

@ql.model
class Human(BaseModel):
first_name: str
last_name: str

@ql.model
class Female(Human):
pregnant: bool

@ql.model
class Male(Human):
pass

print(ql.implements(Human)) # what does `Human` implement
q = ql.query(
(Human, (
ql._(Human).first_name,
(ql.on(Female), (
ql._(Female).pregnant,
))
))
)
print(q)


frozenset({<class '__main__.Human'>})
query{Human{first_name,...on Female{pregnant,__typename},__typename}}


query with http

import ql
import requests
from pydantic import BaseModel

ql.http.set_request_func(lambda q: requests.get(...).json())

# define models ...

response = ql.query_response(
(Point, (
ql._(Point).x,
ql._(Point).y
))
)
print(response)


{"data": {"point": "x": 50, "y": -50}}


query and scalar response

import ql
import requests
from pydantic import BaseModel

ql.http.set_request_func(lambda q: requests.get(...).json())

@ql.model
class Point(BaseModel):
x: int
y: int

scalared = ql.query_response_scalar(
(Point, (
ql._(Point).x,
ql._(Point).y
))
)
print(scalared)


{"point": Point(x=50, y=-50)}


api
model
query_fields_nt
returns a namedtuple with the model queryable fields, which maps between
the model field name, to the defined field query_name.
import ql
from typing import Annotated
from pydantic import BaseModel

@ql.model
class Human(BaseMode):
first_name: Annotated[str, ql.metadata(query_name="name")]
last_name: Annotated[str, ql.metadata(query_name="family_name")]
age: int

print(ql.query_fields_nt(Human).first_name) # name
print(ql.query_fields_nt(Human).last_name) # family_name
print(ql.query_fields_nt(Human).age) # age


NOTE: because this function is common when querying, there is a function alias _
which just wraps the query_fields_nt, so calling _ is actually calling query_fields_nt

query
query
todo
query_response
todo
query_response_scalar

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.