non intrusive python graphql client library wrapped around pydantic
Project description
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
- query operations
- http
- query examples
- api
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 thequery_fields_nt, so calling_is actually callingquery_fields_nt
query
query
todo
query_response
todo
query_response_scalar
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydantic_graphql-1.0.0.tar.gz.
File metadata
- Download URL: pydantic_graphql-1.0.0.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.4 Linux/6.10.4-arch2-1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc9c6fec2b999719fbdd528c77292e9f763061075ed9be7deb5f011c3ef53976
|
|
| MD5 |
2cb20fa798ed65a987d3455dd8b1c1df
|
|
| BLAKE2b-256 |
3e17b043aec36fc602b65bdb81c8b605c96dee71d2a219b4fd4600d3d4a50881
|
File details
Details for the file pydantic_graphql-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pydantic_graphql-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.4 Linux/6.10.4-arch2-1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44249ce94b463ac359609e3e062de0940324d3cf365375b46e518131e70252db
|
|
| MD5 |
1d299002802b69bf3ac341417e29ce14
|
|
| BLAKE2b-256 |
2c201f71e01a7f7e611d7310c384b57be9435edc9c9d3150c3e5e0bd6ea143bd
|