Skip to content

Commit 91e2180

Browse files
authored
Add forgeSubmit mutation as a client method (anvilco#38)
* Add forgeSubmit mutation as an API method * Lint fixes * Better forge_submit * Type fixes, lint things * Add forge list in welds, weld method * Remove prints * Add tests * Update docs, CHANGELOG * Bump version * Use resolvedPayload instead * Add status
1 parent e41a2da commit 91e2180

12 files changed

Lines changed: 399 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.5.0 (2022-08-05)
2+
3+
- Added support for `ForgeSubmit` mutation.
4+
15
# 1.4.1 (2022-05-11)
26

37
- Updated `mkdocs` dependency to fix issue with Read the Docs.

docs/api_usage.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ for more details the creation process.
150150
`CreateEtchPacket` and `CreateEtchPacketPayload`.
151151
* `json` - Raw JSON payload of the etch packet
152152

153+
### Anvil.forge_submit
154+
155+
Creates an Anvil submission
156+
object. [See documentation](https://www.useanvil.com/docs/api/graphql/reference/#operation-forgesubmit-Mutations) for
157+
more details.
158+
159+
* `payload` - Payload to use for the submission. Accepted types are `dict`,
160+
`ForgeSubmit` and `ForgeSubmitPayload`.
161+
* `json` - Raw JSON payload of the `forgeSubmit` mutation.
153162

154163
### Data Types
155164

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from datetime import datetime
2+
3+
from python_anvil.api import Anvil
4+
from python_anvil.api_resources.payload import ForgeSubmitPayload
5+
6+
7+
API_KEY = 'my-api-key'
8+
9+
10+
def main():
11+
# Use https://app.useanvil.com/org/YOUR_ORG_HERE/w/WORKFLOW_NAME/api
12+
# to get a detailed list and description of which fields, eids, etc.
13+
# are available to use.
14+
forge_eid = ""
15+
16+
anvil = Anvil(api_key=API_KEY)
17+
18+
# Create a payload with the payload model.
19+
payload = ForgeSubmitPayload(
20+
forge_eid=forge_eid, payload=dict(field1="Initial forgeSubmit")
21+
)
22+
23+
res = anvil.forge_submit(payload=payload)
24+
25+
data = res["data"]["forgeSubmit"]
26+
27+
print(data)
28+
29+
# Get submission and weld_data eids from the initial response
30+
submission_eid = data["eid"]
31+
weld_data_eid = data["weldData"]["eid"]
32+
33+
payload = ForgeSubmitPayload(
34+
forge_eid=forge_eid,
35+
# If submission and weld_data eids are provided, you will be _editing_
36+
# an existing submission.
37+
submission_eid=submission_eid,
38+
weld_data_eid=weld_data_eid,
39+
# NOTE: If using a development key, this will `is_test` will always
40+
# be `True` even if it's set as `False` here.
41+
is_test=False,
42+
payload=dict(
43+
field1=f"Edited this field {datetime.now()}",
44+
),
45+
)
46+
47+
res = anvil.forge_submit(payload=payload)
48+
49+
data = res["data"]["forgeSubmit"]
50+
print(data)
51+
52+
53+
if __name__ == "__main__":
54+
main()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22

33
name = "python_anvil"
4-
version = "1.4.1"
4+
version = "1.5.0"
55
description = "Anvil API"
66

77
license = "MIT"

python_anvil/api.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from logging import getLogger
2-
from typing import AnyStr, Callable, Dict, List, Optional, Tuple, Union
2+
from typing import Any, AnyStr, Callable, Dict, List, Optional, Text, Tuple, Union
33

44
from .api_resources.mutations import *
55
from .api_resources.payload import (
66
CreateEtchPacketPayload,
77
FillPDFPayload,
8+
ForgeSubmitPayload,
89
GeneratePDFPayload,
910
)
1011
from .api_resources.requests import GraphqlRequest, PlainRequest, RestRequest
@@ -37,7 +38,12 @@ class Anvil:
3738
def __init__(self, api_key=None, environment='dev'):
3839
self.client = HTTPClient(api_key=api_key, environment=environment)
3940

40-
def query(self, query: str, variables: Optional[str] = None, **kwargs):
41+
def query(
42+
self,
43+
query: str,
44+
variables: Union[Optional[Text], Dict[Text, Any]] = None,
45+
**kwargs,
46+
):
4147
gql = GraphqlRequest(client=self.client)
4248
return gql.post(query, variables=variables, **kwargs)
4349

@@ -187,6 +193,10 @@ def get_welds(self, **kwargs) -> Union[List, Tuple[List, Dict]]:
187193
eid
188194
slug
189195
title
196+
forges {
197+
eid
198+
name
199+
}
190200
}
191201
}
192202
}
@@ -200,6 +210,38 @@ def get_data(r):
200210

201211
return _get_return(res, get_data=get_data)
202212

213+
def get_weld(self, eid: Text, **kwargs):
214+
res = self.query(
215+
"""
216+
query WeldQuery(
217+
#$organizationSlug: String!,
218+
#$slug: String!
219+
$eid: String!
220+
) {
221+
weld(
222+
#organizationSlug: $organizationSlug,
223+
#slug: $slug
224+
eid: $eid
225+
) {
226+
eid
227+
slug
228+
name
229+
forges {
230+
eid
231+
name
232+
slug
233+
}
234+
}
235+
}""",
236+
variables=dict(eid=eid),
237+
**kwargs,
238+
)
239+
240+
def get_data(r):
241+
return r["data"]["weld"]
242+
243+
return _get_return(res, get_data=get_data)
244+
203245
def create_etch_packet(
204246
self,
205247
payload: Optional[
@@ -253,3 +295,33 @@ def download_documents(self, document_group_eid: str, **kwargs):
253295
"""Retrieve all completed documents in zip form."""
254296
api = PlainRequest(client=self.client)
255297
return api.get(f"document-group/{document_group_eid}.zip", **kwargs)
298+
299+
def forge_submit(
300+
self,
301+
payload: Optional[Union[Dict[Text, Any], ForgeSubmitPayload]] = None,
302+
json=None,
303+
**kwargs,
304+
):
305+
"""Create a Webform (forge) submission via a graphql mutation."""
306+
if not any([json, payload]):
307+
raise TypeError('One of arguments `json` or `payload` are required')
308+
309+
if json:
310+
payload = ForgeSubmitPayload.parse_raw(
311+
json, content_type="application/json"
312+
)
313+
314+
if isinstance(payload, dict):
315+
mutation = ForgeSubmit.create_from_dict(payload)
316+
elif isinstance(payload, ForgeSubmitPayload):
317+
mutation = ForgeSubmit(payload=payload)
318+
else:
319+
raise ValueError(
320+
"`payload` must be a valid ForgeSubmitPayload instance or dict"
321+
)
322+
323+
return self.mutate(
324+
mutation,
325+
variables=mutation.create_payload().dict(by_alias=True, exclude_none=True),
326+
**kwargs,
327+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .base import BaseQuery
22
from .create_etch_packet import CreateEtchPacket
3+
from .forge_submit import ForgeSubmit
34
from .generate_etch_signing_url import GenerateEtchSigningURL

python_anvil/api_resources/mutations/create_etch_packet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def __init__(
120120
self.merge_pdfs = merge_pdfs
121121

122122
@classmethod
123-
def create_from_dict(cls, payload: dict):
123+
def create_from_dict(cls, payload: Dict) -> 'CreateEtchPacket':
124124
"""Create a new instance of `CreateEtchPacket` from a dict payload."""
125125
try:
126126
mutation = cls(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from typing import Any, Dict, Optional, Text, Union
2+
3+
from python_anvil.api_resources.mutations.base import BaseQuery
4+
from python_anvil.api_resources.mutations.helpers import get_payload_attrs
5+
from python_anvil.api_resources.payload import ForgeSubmitPayload
6+
7+
8+
DEFAULT_RESPONSE_QUERY = """
9+
{
10+
id
11+
eid
12+
status
13+
resolvedPayload
14+
currentStep
15+
completedAt
16+
createdAt
17+
updatedAt
18+
signer {
19+
name
20+
email
21+
status
22+
routingOrder
23+
}
24+
weldData {
25+
id
26+
eid
27+
status
28+
isTest
29+
isComplete
30+
agents
31+
}
32+
}
33+
"""
34+
35+
# NOTE: Since the below will be used as a formatted string (this also applies
36+
# to f-strings) any literal curly braces need to be doubled, else they'll be
37+
# interpreted as string replacement tokens.
38+
FORGE_SUBMIT = """
39+
mutation ForgeSubmit(
40+
$forgeEid: String!,
41+
$weldDataEid: String,
42+
$submissionEid: String,
43+
$payload: JSON!,
44+
$currentStep: Int,
45+
$complete: Boolean,
46+
$isTest: Boolean,
47+
$timezone: String,
48+
$groupArrayId: String,
49+
$groupArrayIndex: Int,
50+
$errorType: String,
51+
) {{
52+
forgeSubmit (
53+
forgeEid: $forgeEid,
54+
weldDataEid: $weldDataEid,
55+
submissionEid: $submissionEid,
56+
payload: $payload,
57+
currentStep: $currentStep,
58+
complete: $complete,
59+
isTest: $isTest,
60+
timezone: $timezone,
61+
groupArrayId: $groupArrayId,
62+
groupArrayIndex: $groupArrayIndex,
63+
errorType: $errorType
64+
) {query}
65+
}}
66+
"""
67+
68+
69+
class ForgeSubmit(BaseQuery):
70+
mutation = FORGE_SUBMIT
71+
mutation_res_query = DEFAULT_RESPONSE_QUERY
72+
73+
def __init__(
74+
self,
75+
payload: Union[Dict[Text, Any], ForgeSubmitPayload],
76+
forge_eid: Optional[Text] = None,
77+
weld_data_eid: Optional[Text] = None,
78+
submission_eid: Optional[Text] = None,
79+
is_test: Optional[bool] = None,
80+
**kwargs,
81+
):
82+
"""
83+
:param forge_eid:
84+
:param payload:
85+
:param weld_data_eid:
86+
:param submission_eid:
87+
:param is_test:
88+
:param kwargs: kwargs may contain other fields defined in
89+
`ForgeSubmitPayload` if not explicitly in the `__init__` args.
90+
"""
91+
if not forge_eid and not isinstance(payload, ForgeSubmitPayload):
92+
raise ValueError(
93+
"`forge_eid` is required if `payload` is not a "
94+
"`ForgeSubmitPayload` instance"
95+
)
96+
97+
self.payload = payload
98+
self.forge_eid = forge_eid
99+
self.weld_data_eid = weld_data_eid
100+
self.submission_eid = submission_eid
101+
self.is_test = is_test
102+
103+
# Get other attrs from the model and set on the instance
104+
model_attrs = get_payload_attrs(ForgeSubmitPayload)
105+
for attr in model_attrs:
106+
if attr in kwargs:
107+
setattr(self, attr, kwargs[attr])
108+
109+
@classmethod
110+
def create_from_dict(cls, payload: Dict[Text, Any]):
111+
# Parse the data through the model class to validate and pass it back
112+
# as variables in this class.
113+
return cls(**payload)
114+
115+
def create_payload(self):
116+
# If provided a payload and no forge_eid, we'll assume that it's the
117+
# full thing. Return that instead.
118+
if not self.forge_eid and self.payload:
119+
return self.payload
120+
121+
model_attrs = get_payload_attrs(ForgeSubmitPayload)
122+
123+
for_payload = {}
124+
for attr in model_attrs:
125+
obj = getattr(self, attr, None)
126+
if obj is not None:
127+
for_payload[attr] = obj
128+
129+
return ForgeSubmitPayload(**for_payload)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from typing import List, Text, Type
2+
3+
from python_anvil.api_resources.base import BaseModel
4+
5+
6+
def get_payload_attrs(payload_model: Type[BaseModel]) -> List[Text]:
7+
return list(payload_model.__fields__.keys())

0 commit comments

Comments
 (0)