Skip to content

Commit f22e3fc

Browse files
authored
New graphql interface (anvilco#50)
* Basic temp usage * Add gql * Refactor to use gql client instead * Add graphql requests example * Update mutations to use gql * Fix weird i/o issue * Don't clobber existing content_types if found * Updates * Update `gql` dependency * Fix tests * Keep mypy happy * Fix doc style, lint * Few more tests * Add local schema * Update docs reqs * Update lock * Retry limit on gql * Only use `requests` as the transport for now * Update docs, add dynamic queries, bump version * 3.0 instead * Add more details to dynamic query building
1 parent 130f6fc commit f22e3fc

23 files changed

Lines changed: 2979 additions & 704 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 3.0.0 (2023-02-17)
2+
3+
- **[BREAKING CHANGE]** [`graphql-python/gql`](https://github.com/graphql-python/gql) is now the main GraphQL client
4+
implementation. All functions should still work the same as before. If there are any issues please let us know
5+
in `python-anvil` GitHub issues.
6+
- Updated examples to reflect new GraphQL implementation and added `examples/make_graphql_request.py` example.
7+
18
# 2.0.0 (2023-01-26)
29

310
- **[BREAKING CHANGE]** Minimum required Python version updated to `>=3.7.2`
File renamed without changes.

docs/advanced/dynamic_queries.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Dynamic query building with `gql`
2+
3+
This library makes use of [`graphql-python/gql`](https://github.com/graphql-python/gql) as its GraphQL client
4+
implementation. This allows us to have a simpler interface when interacting with Anvil's GraphQL API. This also gives us
5+
the ability to use gql's dynamic query
6+
builder. [More info on their documentation page](https://gql.readthedocs.io/en/latest/advanced/dsl_module.html).
7+
8+
We have a few helper functions to help generate your first dynamic query. These are shown in the example below.
9+
Keep in mind that your IDE will likely not be able to autocomplete any field lookups, so it may help to also
10+
have [Anvil's GraphQL reference page](https://www.useanvil.com/docs/api/graphql/reference/) open as you create your
11+
queries.
12+
13+
### Example usage
14+
15+
```python
16+
from gql.dsl import DSLQuery, dsl_gql
17+
18+
from python_anvil.api import Anvil
19+
from python_anvil.http import get_gql_ds
20+
21+
# These steps are similar to `gql's` docs on dynamic queries with Anvil helper functions.
22+
# https://gql.readthedocs.io/en/latest/advanced/dsl_module.html
23+
24+
anvil = Anvil(api_key=MY_API_KEY)
25+
26+
# Use `ds` to create your queries
27+
ds = get_gql_ds(anvil.gql_client)
28+
29+
# Create your query in one step
30+
query = ds.Query.currentUser.select(
31+
ds.User.name,
32+
ds.User.email,
33+
)
34+
35+
# Or, build your query with a chain or multiple steps until you're ready to use it.
36+
query = ds.Query.currentUser.select(ds.User.name)
37+
query.select(ds.User.email)
38+
query.select(ds.User.firstName)
39+
query.select(ds.User.lastName)
40+
41+
# Once your root query fields are defined, you can put them in an operation using DSLQuery, DSLMutation or DSLSubscription:
42+
final_query = dsl_gql(DSLQuery(query))
43+
44+
res = anvil.query(final_query)
45+
```

docs/api_usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ well.
160160
Creates an Anvil Etch E-sign packet.
161161

162162
This is one of the more complex processes due to the different types of data
163-
needed in the final payload. Please take a look at the [advanced section](advanced.md#create-etch-packet)
163+
needed in the final payload. Please take a look at the [advanced section](advanced/create_etch_packet.md)
164164
for more details the creation process.
165165

166166
* `payload` - Payload to use for the packet. Accepted types are `dict`,

docs/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
mkdocs==1.4.2; python_version >= "3.7"
2-
pygments==2.14.0; python_version >= "3.6"
1+
mkdocs==1.4.2 ; python_full_version >= "3.7.2" and python_version < "3.12"
2+
pygments==2.14.0 ; python_full_version >= "3.7.2" and python_version < "3.12"

examples/create_etch_upload_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def main():
138138
# Create your packet
139139
# If overriding/adding new fields, use the modified payload from
140140
# `packet.create_payload()`
141-
res = anvil.create_etch_packet(payload=packet, include_headers=True)
141+
res = anvil.create_etch_packet(payload=packet)
142142
print(res)
143143

144144

examples/create_etch_upload_file_multipart.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,20 @@ def main():
6363
# raise FileNotFoundError('File does not exist. Please check `file_path` '
6464
# 'and ensure it points to an existing file.')
6565

66+
# file data must be read in as _bytes_, not text.
67+
file = open(file_path, "rb") # pylint: disable=consider-using-with
68+
69+
# You can also provide a custom `content_type` if you needed.
70+
# The Anvil library will guess the file's content_type by its file
71+
# extension automatically, but this can be used to force a different
72+
# content_type.
73+
# f1.content_type = "application/pdf"
74+
6675
# Upload the file and define signer field locations.
6776
file1 = DocumentUpload(
6877
id="myNewFile",
6978
title="Please sign this important form",
70-
# You can send a callable that returns a file handle and the filename
71-
# you want to use in the Anvil app.
72-
# (IMPORTANT: The data must be read in as _bytes_, not text.)
73-
file=lambda: (
74-
open(file_path, "rb"), # pylint: disable=consider-using-with
75-
filename,
76-
),
77-
# or just the valid path itself
78-
# file=file_path,
79+
file=file,
7980
fields=[
8081
SignatureField(
8182
id="sign1",
@@ -140,8 +141,11 @@ def main():
140141
# Create your packet
141142
# If overriding/adding new fields, use the modified payload from
142143
# `packet.create_payload()`
143-
res = anvil.create_etch_packet(payload=packet, include_headers=True)
144-
print(res)
144+
try:
145+
res = anvil.create_etch_packet(payload=packet)
146+
print(res)
147+
finally:
148+
file.close()
145149

146150

147151
if __name__ == '__main__':

examples/create_workflow_submission.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from datetime import datetime
3+
from typing import Any, Dict
34

45
from python_anvil.api import Anvil
56
from python_anvil.api_resources.payload import ForgeSubmitPayload
@@ -23,9 +24,9 @@ def main():
2324
forge_eid=forge_eid, payload=dict(field1="Initial forgeSubmit")
2425
)
2526

26-
res = anvil.forge_submit(payload=payload)
27+
res: Dict[str, Any] = anvil.forge_submit(payload=payload)
2728

28-
data = res["data"]["forgeSubmit"]
29+
data = res.get("forgeSubmit", {})
2930

3031
print(data)
3132

@@ -49,7 +50,7 @@ def main():
4950

5051
res = anvil.forge_submit(payload=payload)
5152

52-
data = res["data"]["forgeSubmit"]
53+
data = res.get("forgeSubmit", {})
5354
print(data)
5455

5556

examples/make_graphql_request.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import os
2+
from gql.dsl import DSLQuery, dsl_gql
3+
4+
from python_anvil.api import Anvil
5+
from python_anvil.http import get_gql_ds
6+
7+
8+
API_KEY = os.environ.get("ANVIL_API_KEY")
9+
# or set your own key here
10+
# API_KEY = 'my-api-key'
11+
12+
13+
def call_current_user_query(anvil: Anvil) -> dict:
14+
"""Get the user data attached to the current API key.
15+
16+
:param anvil:
17+
:type anvil: Anvil
18+
:return:
19+
"""
20+
# See the reference docs for examples of all queries and mutations:
21+
# https://www.useanvil.com/docs/api/graphql/reference/
22+
# pylint: disable=unused-variable
23+
user_query = """
24+
query CurrentUser {
25+
currentUser {
26+
eid
27+
name
28+
organizations {
29+
eid
30+
slug
31+
name
32+
casts {
33+
eid
34+
name
35+
}
36+
welds {
37+
eid
38+
name
39+
}
40+
}
41+
}
42+
}
43+
"""
44+
45+
# You can also use `gql`'s query builder. Below is the equivalent of the
46+
# string above, but can potentially be a better interface if you're
47+
# building a query in multiple steps. See the official `gql` docs for more
48+
# details: https://gql.readthedocs.io/en/stable/advanced/dsl_module.html
49+
50+
# Use `ds` to create your queries
51+
ds = get_gql_ds(anvil.gql_client)
52+
ds_user_query_builder = ds.Query.currentUser.select(
53+
ds.User.eid,
54+
ds.User.name,
55+
ds.User.organizations.select(
56+
ds.Organization.eid,
57+
ds.Organization.slug,
58+
ds.Organization.name,
59+
ds.Organization.casts.select(
60+
ds.Cast.eid,
61+
ds.Cast.name,
62+
),
63+
ds.Organization.welds.select(
64+
ds.Weld.eid,
65+
ds.Weld.name,
66+
),
67+
),
68+
)
69+
70+
ds_query = dsl_gql(DSLQuery(ds_user_query_builder))
71+
72+
res = anvil.query(query=ds_query, variables=None)
73+
return res["currentUser"]
74+
75+
76+
def call_weld_query(anvil: Anvil, weld_eid: str):
77+
"""Call the weld query.
78+
79+
The weld() query is an example of a query that takes variables.
80+
:param anvil:
81+
:type anvil: Anvil
82+
:param weld_eid:
83+
:type weld_eid: str
84+
:return:
85+
"""
86+
87+
# pylint: disable=unused-variable
88+
weld_query = """
89+
query WeldQuery (
90+
$eid: String,
91+
) {
92+
weld (
93+
eid: $eid,
94+
) {
95+
eid
96+
name
97+
forges {
98+
eid
99+
slug
100+
name
101+
}
102+
}
103+
}
104+
"""
105+
variables = {"eid": weld_eid}
106+
107+
# You can also use `gql`'s query builder. Below is the equivalent of the
108+
# string above, but can potentially be a better interface if you're
109+
# building a query in multiple steps. See the official `gql` docs for more
110+
# details: https://gql.readthedocs.io/en/stable/advanced/dsl_module.html
111+
112+
# Use `ds` to create your queries
113+
ds = get_gql_ds(anvil.gql_client)
114+
ds_weld_query_builder = ds.Query.weld.args(eid=weld_eid).select(
115+
ds.Weld.eid,
116+
ds.Weld.name,
117+
ds.Weld.forges.select(
118+
ds.Forge.eid,
119+
ds.Forge.slug,
120+
ds.Forge.name,
121+
),
122+
)
123+
124+
ds_query = dsl_gql(DSLQuery(ds_weld_query_builder))
125+
126+
# You can call the query with the string literal and variables like usual
127+
# res = anvil.query(query=weld_query, variables=variables)
128+
129+
# Or, use only the `dsl_gql` query. `variables` not needed as it was
130+
# already used in `.args()`.
131+
res = anvil.query(query=ds_query)
132+
return res["weld"]
133+
134+
135+
def call_queries():
136+
anvil = Anvil(api_key=API_KEY)
137+
current_user = call_current_user_query(anvil)
138+
139+
first_weld = current_user["organizations"][0]["welds"][0]
140+
weld_data = call_weld_query(anvil, weld_eid=first_weld["eid"])
141+
142+
print("currentUser: ", current_user)
143+
print("First weld details: ", weld_data)
144+
145+
146+
if __name__ == "__main__":
147+
call_queries()

mkdocs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ nav:
1414
- Home: index.md
1515
- API Usage: api_usage.md
1616
- CLI Usage: cli_usage.md
17-
- Advanced: advanced.md
17+
- Advanced:
18+
- Create Etch Packet: advanced/create_etch_packet.md
19+
- Dynamic GraphQL queries: advanced/dynamic_queries.md
1820
- About:
1921
- Release Notes: about/changelog.md
2022
- Contributing: about/contributing.md

0 commit comments

Comments
 (0)