# pydantic-gql
A simple GraphQL query builder based on Pydantic models
## Installation
You can install this package with pip.
```sh
$ pip install pydantic-gql
```
## Links
[![Documentation](https://img.shields.io/badge/Documentation-C61C3E?style=for-the-badge&logo=Read+the+Docs&logoColor=%23FFFFFF)](https://abrahammurciano.github.io/pydantic-gql)
[![Source Code - GitHub](https://img.shields.io/badge/Source_Code-GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=%23FFFFFF)](https://github.com/abrahammurciano/pydantic-gql.git)
[![PyPI - pydantic-gql](https://img.shields.io/badge/PyPI-pydantic_gql-006DAD?style=for-the-badge&logo=PyPI&logoColor=%23FFD242)](https://pypi.org/project/pydantic-gql/)
## Usage
To use `pydantic-gql`, you need to define your Pydantic models and then use them to build GraphQL queries. The core classes you'll interact with is the `Query` class to create queries and the `Mutation` class to create mutations. (Both queries and mutations are types of "operations".)
### Queries
#### Defining Pydantic Models
First, define your Pydantic models that represent the structure of the data you want to query. Here's an example:
```python
from pydantic import BaseModel
class Group(BaseModel):
id: int
name: str
class User(BaseModel):
id: int
name: str
groups: list[Group]
```
#### Building a Query
Most GraphQL queries contain a single top-level field. Since this is the most common use case, this library provides `Query.from_model()` as a convenience method to create a query with one top-level field whose subfields are defined by the Pydantic model.
```python
from pydantic_gql import Query
query = Query.from_model(User)
```
This will create a query that looks like this:
```graphql
query User{
User {
id,
name,
groups {
id,
name,
},
},
}
```
This method also provides parameters to customise the query, such as the query name, field name, variables (see [Using Variables](#using-variables) for examples with variables), and arguments. Here's a more complex example:
```python
query = Query.from_model(
User,
query_name="GetUser",
field_name="users",
args={"id": 1},
)
```
This will create a query that looks like this:
```graphql
query GetUser{
users(id: 1) {
id,
name,
groups {
id,
name,
},
},
}
```
### Mutations
Since both queries and mutations are types of operations, the `Mutation` class works in the same way as the `Query` class. Here's an example of how to build a mutation that could create a new user and return their data.
```python
from pydantic_gql import Mutation
new_user = User(id=1, name="John Doe", groups=[])
mutation = Mutation.from_model(User, "create_user", args=dict(new_user))
```
This will create a mutation that looks like this:
```graphql
mutation CreateUser {
createUser(id: 1, name: "John Doe", groups: []) {
id,
name,
groups {
id,
name,
},
},
}
```
### Generating the GraphQL Operation String
To get the actual GraphQL query or mutation as a string that you can send to your server, simply convert the `Query` or `Mutation` object to a string.
```python
query_string = str(query)
```
You can control the indentation of the resulting string by using `format()` instead of `str()`. Valid values for the format specifier are:
- `indent` - The default. Indent the resulting string with two spaces.
- `noindent` - Do not indent the resulting string. The result will be a single line.
- A number - Indent the resulting string with the specified number of spaces.
- A whitespace string - Indent the resulting string with the specified string, e.g. `\t`.
```python
query_string = format(query, '\t')
```
### Using Variables
A GraphQL query can define variables at the top and then reference them throughout the rest of the operation. Then when the operation is sent to the server, the variables are passed in a separate dictionary.
To define variables for a GraphQL operation, first create a class that inherits from `BaseVars` and define the variables as attributes with `Var[T]` as the type annotation.
```python
from pydantic_gql import BaseVars, Var
class UserVars(BaseVars):
age: Var[int]
group: Var[str | None]
is_admin: Var[bool] = Var(default=False)
```
You can pass the class itself to the `.from_model()` method to include the variables in the query. You can also reference the class attributes in the operation's arguments directly.
```python
query = Query.from_model(
User,
variables=UserVars,
args={"age": UserVars.age, "group": UserVars.group, "isAdmin": UserVars.is_admin},
)
```
This will create a query that looks like this:
```graphql
query User($age: Int!, $group: String, $is_admin: Boolean = false){
User(age: $age, group: $group, isAdmin: $is_admin) {
id,
name,
groups {
id,
name,
},
},
}
```
When you want to send the query, you can instantiate the variables class, which itself is a `Mapping` of variable names to values, and pass it to your preferred HTTP client.
```python
variables = UserVars(age=18, group="admin", is_admin=True)
httpx.post(..., json={"query": str(query), "variables": dict(variables)})
```
### More Complex Operations
Sometimes you may need to build more complex operations than the ones we've seen so far. For example, you may need to include multiple top-level fields, or you may need to provide arguments to some deeply nested fields.
> In the following examples we'll be using queries, but the same principles apply to mutations.
In these cases, you can build the query manually with the `Query` constructor. The constructor takes the query name followed by any number of `GqlField` objects, then optionally `variables` as a keyword argument.
`GqlField`s themselves can also be constructed with their `from_model()` convenience method or manually with their constructor.
Here's an example of a more complex query:
```python
from pydantic import BaseModel, Field
from pydantic_gql import Query, GqlField, BaseVars
class Vars(BaseVars):
min_size: Var[int] = Var(default=0)
groups_per_user: Var[int | None]
class PageInfo(BaseModel):
has_next_page: bool = Field(alias="hasNextPage")
end_cursor: str | None = Field(alias="endCursor")
class GroupEdge(BaseModel):
node: Group
cursor: str
class GroupConnection(BaseModel):
edges: list[GroupEdge]
page_info: PageInfo = Field(alias="pageInfo")
query = Query(
"GetUsersAndGroups",
GqlField(
name="users",
args={"minAge": 18},
fields=(
GqlField("id"),
GqlField("name"),
GqlField.from_model(GroupConnection, "groups", args={"first": Vars.groups_per_user}),
),
)
GqlField.from_model(Group, "groups", args={"minSize": Vars.min_size}),
variables=Vars,
)
```
This will create a query that looks like this:
```graphql
query GetUsersAndGroups($min_size: Int = 0, $groups_per_user: Int){
users(minAge: 18) {
id,
name,
groups(first: $groups_per_user) {
edges {
node {
id,
name,
},
cursor,
},
pageInfo {
hasNextPage,
endCursor,
},
},
},
groups(minSize: $min_size) {
id,
name,
},
}
```
### Connections (Pagination)
The previous example demonstrates how to build a query that uses pagination. However, since pagination is a common pattern (see the [GraphQL Connections Specification](https://relay.dev/graphql/connections.htm)), this library provides a `Connection` class which is generic over the node type. You can use this class to easily build pagination queries.
Here's an example of how to use the `Connection` class:
```python
from pydantic_gql.connections import Connection
query = Query.from_model(
Connection[User],
"users",
args={"first": 10},
)
```
This will create a query that looks like this:
```graphql
query User{
users(first: 10) {
edges {
node {
id,
name,
groups {
id,
name,
},
},
cursor,
},
pageInfo {
hasNextPage,
endCursor,
},
},
}
```
Raw data
{
"_id": null,
"home_page": "https://github.com/abrahammurciano/pydantic-gql",
"name": "pydantic-gql",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": null,
"author": "Abraham Murciano",
"author_email": "abrahammurciano@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/e7/f0/a15fb66e72941b973b09315a96cb38c2555d284569e1527e140f03f650f2/pydantic_gql-1.2.2.tar.gz",
"platform": null,
"description": "# pydantic-gql\nA simple GraphQL query builder based on Pydantic models\n\n## Installation\n\nYou can install this package with pip.\n```sh\n$ pip install pydantic-gql\n```\n\n## Links\n\n[![Documentation](https://img.shields.io/badge/Documentation-C61C3E?style=for-the-badge&logo=Read+the+Docs&logoColor=%23FFFFFF)](https://abrahammurciano.github.io/pydantic-gql)\n\n[![Source Code - GitHub](https://img.shields.io/badge/Source_Code-GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=%23FFFFFF)](https://github.com/abrahammurciano/pydantic-gql.git)\n\n[![PyPI - pydantic-gql](https://img.shields.io/badge/PyPI-pydantic_gql-006DAD?style=for-the-badge&logo=PyPI&logoColor=%23FFD242)](https://pypi.org/project/pydantic-gql/)\n\n## Usage\n\nTo use `pydantic-gql`, you need to define your Pydantic models and then use them to build GraphQL queries. The core classes you'll interact with is the `Query` class to create queries and the `Mutation` class to create mutations. (Both queries and mutations are types of \"operations\".)\n\n### Queries\n\n#### Defining Pydantic Models\n\nFirst, define your Pydantic models that represent the structure of the data you want to query. Here's an example:\n\n```python\nfrom pydantic import BaseModel\n\nclass Group(BaseModel):\n id: int\n name: str\n\nclass User(BaseModel):\n id: int\n name: str\n groups: list[Group]\n```\n\n#### Building a Query\n\nMost GraphQL queries contain a single top-level field. Since this is the most common use case, this library provides `Query.from_model()` as a convenience method to create a query with one top-level field whose subfields are defined by the Pydantic model.\n\n```python\nfrom pydantic_gql import Query\n\nquery = Query.from_model(User)\n```\n\nThis will create a query that looks like this:\n\n```graphql\nquery User{\n User {\n id,\n name,\n groups {\n id,\n name,\n },\n },\n}\n```\n\nThis method also provides parameters to customise the query, such as the query name, field name, variables (see [Using Variables](#using-variables) for examples with variables), and arguments. Here's a more complex example:\n\n```python\nquery = Query.from_model(\n User,\n query_name=\"GetUser\",\n field_name=\"users\",\n args={\"id\": 1},\n)\n```\n\nThis will create a query that looks like this:\n\n```graphql\nquery GetUser{\n users(id: 1) {\n id,\n name,\n groups {\n id,\n name,\n },\n },\n}\n```\n\n### Mutations\n\nSince both queries and mutations are types of operations, the `Mutation` class works in the same way as the `Query` class. Here's an example of how to build a mutation that could create a new user and return their data.\n\n```python\nfrom pydantic_gql import Mutation\n\nnew_user = User(id=1, name=\"John Doe\", groups=[])\nmutation = Mutation.from_model(User, \"create_user\", args=dict(new_user))\n```\n\nThis will create a mutation that looks like this:\n\n```graphql\nmutation CreateUser {\n createUser(id: 1, name: \"John Doe\", groups: []) {\n\tid,\n\tname,\n\tgroups {\n\t id,\n\t name,\n\t},\n },\n}\n```\n\n### Generating the GraphQL Operation String\n\nTo get the actual GraphQL query or mutation as a string that you can send to your server, simply convert the `Query` or `Mutation` object to a string.\n\n```python\nquery_string = str(query)\n```\n\nYou can control the indentation of the resulting string by using `format()` instead of `str()`. Valid values for the format specifier are:\n\n- `indent` - The default. Indent the resulting string with two spaces.\n- `noindent` - Do not indent the resulting string. The result will be a single line.\n- A number - Indent the resulting string with the specified number of spaces.\n- A whitespace string - Indent the resulting string with the specified string, e.g. `\\t`.\n\n```python\nquery_string = format(query, '\\t')\n```\n\n### Using Variables\n\nA GraphQL query can define variables at the top and then reference them throughout the rest of the operation. Then when the operation is sent to the server, the variables are passed in a separate dictionary.\n\nTo define variables for a GraphQL operation, first create a class that inherits from `BaseVars` and define the variables as attributes with `Var[T]` as the type annotation.\n\n```python\nfrom pydantic_gql import BaseVars, Var\n\nclass UserVars(BaseVars):\n age: Var[int]\n group: Var[str | None]\n is_admin: Var[bool] = Var(default=False)\n```\n\nYou can pass the class itself to the `.from_model()` method to include the variables in the query. You can also reference the class attributes in the operation's arguments directly.\n\n```python\nquery = Query.from_model(\n User,\n variables=UserVars,\n args={\"age\": UserVars.age, \"group\": UserVars.group, \"isAdmin\": UserVars.is_admin},\n)\n```\n\nThis will create a query that looks like this:\n\n```graphql\nquery User($age: Int!, $group: String, $is_admin: Boolean = false){\n User(age: $age, group: $group, isAdmin: $is_admin) {\n id,\n name,\n groups {\n id,\n name,\n },\n },\n}\n```\n\nWhen you want to send the query, you can instantiate the variables class, which itself is a `Mapping` of variable names to values, and pass it to your preferred HTTP client.\n\n```python\nvariables = UserVars(age=18, group=\"admin\", is_admin=True)\nhttpx.post(..., json={\"query\": str(query), \"variables\": dict(variables)})\n```\n\n### More Complex Operations\n\nSometimes you may need to build more complex operations than the ones we've seen so far. For example, you may need to include multiple top-level fields, or you may need to provide arguments to some deeply nested fields.\n\n> In the following examples we'll be using queries, but the same principles apply to mutations.\n\nIn these cases, you can build the query manually with the `Query` constructor. The constructor takes the query name followed by any number of `GqlField` objects, then optionally `variables` as a keyword argument.\n\n`GqlField`s themselves can also be constructed with their `from_model()` convenience method or manually with their constructor.\n\nHere's an example of a more complex query:\n\n```python\nfrom pydantic import BaseModel, Field\nfrom pydantic_gql import Query, GqlField, BaseVars\n\nclass Vars(BaseVars):\n min_size: Var[int] = Var(default=0)\n groups_per_user: Var[int | None]\n\nclass PageInfo(BaseModel):\n has_next_page: bool = Field(alias=\"hasNextPage\")\n end_cursor: str | None = Field(alias=\"endCursor\")\n\nclass GroupEdge(BaseModel):\n node: Group\n cursor: str\n\nclass GroupConnection(BaseModel):\n edges: list[GroupEdge]\n page_info: PageInfo = Field(alias=\"pageInfo\")\n\nquery = Query(\n \"GetUsersAndGroups\",\n GqlField(\n name=\"users\",\n args={\"minAge\": 18},\n fields=(\n GqlField(\"id\"),\n GqlField(\"name\"),\n GqlField.from_model(GroupConnection, \"groups\", args={\"first\": Vars.groups_per_user}),\n ),\n )\n GqlField.from_model(Group, \"groups\", args={\"minSize\": Vars.min_size}),\n variables=Vars,\n)\n```\n\nThis will create a query that looks like this:\n\n```graphql\nquery GetUsersAndGroups($min_size: Int = 0, $groups_per_user: Int){\n users(minAge: 18) {\n id,\n name,\n groups(first: $groups_per_user) {\n edges {\n node {\n id,\n name,\n },\n cursor,\n },\n pageInfo {\n hasNextPage,\n endCursor,\n },\n },\n },\n groups(minSize: $min_size) {\n id,\n name,\n },\n}\n```\n\n### Connections (Pagination)\n\nThe previous example demonstrates how to build a query that uses pagination. However, since pagination is a common pattern (see the [GraphQL Connections Specification](https://relay.dev/graphql/connections.htm)), this library provides a `Connection` class which is generic over the node type. You can use this class to easily build pagination queries.\n\nHere's an example of how to use the `Connection` class:\n\n```python\nfrom pydantic_gql.connections import Connection\n\nquery = Query.from_model(\n Connection[User],\n \"users\",\n args={\"first\": 10},\n)\n```\n\nThis will create a query that looks like this:\n\n```graphql\nquery User{\n users(first: 10) {\n edges {\n node {\n id,\n name,\n groups {\n id,\n name,\n },\n },\n cursor,\n },\n pageInfo {\n hasNextPage,\n endCursor,\n },\n },\n}\n```\n",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "A simple GraphQL query builder based on Pydantic models",
"version": "1.2.2",
"project_urls": {
"Documentation": "https://abrahammurciano.github.io/pydantic-gql/pydantic-gql",
"Homepage": "https://github.com/abrahammurciano/pydantic-gql",
"Repository": "https://github.com/abrahammurciano/pydantic-gql"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2bc8993325e41a841238d333d7255ce4548a820ea827ed49790d03173614c36c",
"md5": "bbd33586d8944780cfa9438b6ad1bc7e",
"sha256": "009e63c259d5ca581fcad628e062ee8120a0bf8fff7b14ce526a9964a1ad6bfa"
},
"downloads": -1,
"filename": "pydantic_gql-1.2.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bbd33586d8944780cfa9438b6ad1bc7e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 32392,
"upload_time": "2024-10-28T13:10:32",
"upload_time_iso_8601": "2024-10-28T13:10:32.520417Z",
"url": "https://files.pythonhosted.org/packages/2b/c8/993325e41a841238d333d7255ce4548a820ea827ed49790d03173614c36c/pydantic_gql-1.2.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "e7f0a15fb66e72941b973b09315a96cb38c2555d284569e1527e140f03f650f2",
"md5": "034f25dee34a2e7953f76ed88b1adb7a",
"sha256": "f36a19f8fb5376e5a2dd3be20a9dd54bee2afa12d21b3fa61dec577cd81a4581"
},
"downloads": -1,
"filename": "pydantic_gql-1.2.2.tar.gz",
"has_sig": false,
"md5_digest": "034f25dee34a2e7953f76ed88b1adb7a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 27354,
"upload_time": "2024-10-28T13:10:34",
"upload_time_iso_8601": "2024-10-28T13:10:34.094829Z",
"url": "https://files.pythonhosted.org/packages/e7/f0/a15fb66e72941b973b09315a96cb38c2555d284569e1527e140f03f650f2/pydantic_gql-1.2.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-28 13:10:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "abrahammurciano",
"github_project": "pydantic-gql",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pydantic-gql"
}