snapline-api-adapters 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snapline_api_adapters-0.1.10/.gitignore +5 -0
- snapline_api_adapters-0.1.10/PKG-INFO +11 -0
- snapline_api_adapters-0.1.10/pyproject.toml +22 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/__init__.py +29 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/api_factory.py +22 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/execute_api.py +28 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/graphql/execute_graphql.py +104 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/resolve_url.py +11 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/rest/execute_rest.py +93 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/soap/execute_soap.py +87 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/soap/xml_utils.py +37 -0
- snapline_api_adapters-0.1.10/snapline/api_adapters/types.py +86 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snapline-api-adapters
|
|
3
|
+
Version: 0.1.10
|
|
4
|
+
Summary: REST, GraphQL, and SOAP API adapters for Snapline
|
|
5
|
+
Project-URL: Homepage, https://github.com/vaagatech/snapline-python
|
|
6
|
+
Project-URL: Repository, https://github.com/vaagatech/snapline-python
|
|
7
|
+
Author-email: VaagaTech <info@vaagatech.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: snapline-engine==0.1.10
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "snapline-api-adapters"
|
|
7
|
+
version = "0.1.10"
|
|
8
|
+
description = "REST, GraphQL, and SOAP API adapters for Snapline"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "VaagaTech", email = "info@vaagatech.com" }]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"snapline-engine==0.1.10",
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/vaagatech/snapline-python"
|
|
19
|
+
Repository = "https://github.com/vaagatech/snapline-python"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["snapline"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .api_factory import api
|
|
2
|
+
from .execute_api import execute_api
|
|
3
|
+
from .graphql.execute_graphql import execute_graphql
|
|
4
|
+
from .resolve_url import resolve_url
|
|
5
|
+
from .rest.execute_rest import execute_rest
|
|
6
|
+
from .soap.execute_soap import execute_soap
|
|
7
|
+
from .soap.xml_utils import build_soap_envelope, parse_soap_body
|
|
8
|
+
from .types import (
|
|
9
|
+
ApiExecuteContext,
|
|
10
|
+
ApiExecuteResult,
|
|
11
|
+
ApiRequestConfig,
|
|
12
|
+
is_graphql_config,
|
|
13
|
+
is_rest_config,
|
|
14
|
+
is_soap_config,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"api",
|
|
19
|
+
"build_soap_envelope",
|
|
20
|
+
"execute_api",
|
|
21
|
+
"execute_graphql",
|
|
22
|
+
"execute_rest",
|
|
23
|
+
"execute_soap",
|
|
24
|
+
"is_graphql_config",
|
|
25
|
+
"is_rest_config",
|
|
26
|
+
"is_soap_config",
|
|
27
|
+
"parse_soap_body",
|
|
28
|
+
"resolve_url",
|
|
29
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .types import GraphqlApiConfig, RestApiConfig, SoapApiConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApiFactory:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def rest(config: dict[str, Any]) -> RestApiConfig:
|
|
11
|
+
return {"protocol": "rest", **config}
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def soap(config: dict[str, Any]) -> SoapApiConfig:
|
|
15
|
+
return {"protocol": "soap", **config}
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def graphql(config: dict[str, Any]) -> GraphqlApiConfig:
|
|
19
|
+
return {"protocol": "graphql", **config}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
api = ApiFactory()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .graphql.execute_graphql import execute_graphql
|
|
6
|
+
from .rest.execute_rest import execute_rest
|
|
7
|
+
from .soap.execute_soap import execute_soap
|
|
8
|
+
from .types import (
|
|
9
|
+
ApiExecuteContext,
|
|
10
|
+
ApiExecuteResult,
|
|
11
|
+
ApiRequestConfig,
|
|
12
|
+
is_graphql_config,
|
|
13
|
+
is_rest_config,
|
|
14
|
+
is_soap_config,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def execute_api(
|
|
19
|
+
config: ApiRequestConfig,
|
|
20
|
+
context: ApiExecuteContext | dict[str, Any] | None = None,
|
|
21
|
+
) -> ApiExecuteResult:
|
|
22
|
+
if is_rest_config(config):
|
|
23
|
+
return execute_rest(config, context)
|
|
24
|
+
if is_soap_config(config):
|
|
25
|
+
return execute_soap(config, context)
|
|
26
|
+
if is_graphql_config(config):
|
|
27
|
+
return execute_graphql(config, context)
|
|
28
|
+
raise ValueError("Unsupported API protocol")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from snapline.engine import load_json_file
|
|
10
|
+
|
|
11
|
+
from ..resolve_url import resolve_url
|
|
12
|
+
from ..types import ApiExecuteContext, ApiExecuteResult, GraphqlApiConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _default_fetch(
|
|
16
|
+
url: str,
|
|
17
|
+
*,
|
|
18
|
+
method: str = "GET",
|
|
19
|
+
headers: dict[str, str] | None = None,
|
|
20
|
+
content: str | bytes | None = None,
|
|
21
|
+
data: str | bytes | None = None,
|
|
22
|
+
) -> httpx.Response:
|
|
23
|
+
body = content if content is not None else data
|
|
24
|
+
return httpx.request(method, url, headers=headers, content=body)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_query(config: GraphqlApiConfig) -> str:
|
|
28
|
+
if config.get("query"):
|
|
29
|
+
return config["query"]
|
|
30
|
+
|
|
31
|
+
query_file = config.get("queryFile")
|
|
32
|
+
if not query_file:
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
raw = Path(query_file).read_text(encoding="utf-8").strip()
|
|
36
|
+
if raw.startswith("{"):
|
|
37
|
+
parsed = json.loads(raw)
|
|
38
|
+
return parsed.get("query", raw)
|
|
39
|
+
return raw
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_by_path(obj: Any, path: str | None) -> Any:
|
|
43
|
+
if not path:
|
|
44
|
+
return obj
|
|
45
|
+
|
|
46
|
+
cursor = obj
|
|
47
|
+
for key in path.split("."):
|
|
48
|
+
if isinstance(cursor, dict) and key in cursor:
|
|
49
|
+
cursor = cursor[key]
|
|
50
|
+
else:
|
|
51
|
+
return None
|
|
52
|
+
return cursor
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def execute_graphql(
|
|
56
|
+
config: GraphqlApiConfig,
|
|
57
|
+
context: ApiExecuteContext | dict[str, Any] | None = None,
|
|
58
|
+
) -> ApiExecuteResult:
|
|
59
|
+
ctx = context or {}
|
|
60
|
+
base_url = ctx.get("baseUrl")
|
|
61
|
+
auth_headers = ctx.get("authHeaders", {})
|
|
62
|
+
fetch_impl = ctx.get("fetchImpl") or _default_fetch
|
|
63
|
+
input_from_row = ctx.get("inputFromRow")
|
|
64
|
+
|
|
65
|
+
query = _load_query(config)
|
|
66
|
+
variables: dict[str, Any] = dict(config.get("variables") or {})
|
|
67
|
+
|
|
68
|
+
if config.get("variablesFile"):
|
|
69
|
+
variables = load_json_file(config["variablesFile"])
|
|
70
|
+
if config.get("inputFile"):
|
|
71
|
+
variables = load_json_file(config["inputFile"])
|
|
72
|
+
if input_from_row:
|
|
73
|
+
variables = {**variables, **input_from_row}
|
|
74
|
+
|
|
75
|
+
url = resolve_url(config["endpoint"], base_url)
|
|
76
|
+
response = fetch_impl(
|
|
77
|
+
url,
|
|
78
|
+
method="POST",
|
|
79
|
+
headers={
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"Accept": "application/json",
|
|
82
|
+
**auth_headers,
|
|
83
|
+
**(config.get("headers") or {}),
|
|
84
|
+
},
|
|
85
|
+
content=json.dumps({"query": query, "variables": variables}),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
text = response.text
|
|
89
|
+
response_headers = dict(response.headers)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
parsed = json.loads(text) if text else None
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
parsed = text
|
|
95
|
+
|
|
96
|
+
gql_data = parsed.get("data") if isinstance(parsed, dict) and "data" in parsed else parsed
|
|
97
|
+
data = _get_by_path(gql_data, config.get("dataPath"))
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
"status": response.status_code,
|
|
101
|
+
"data": data,
|
|
102
|
+
"headers": response_headers,
|
|
103
|
+
"raw": text,
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urljoin, urlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def resolve_url(endpoint: str, base_url: str | None = None) -> str:
|
|
7
|
+
if urlparse(endpoint).scheme:
|
|
8
|
+
return endpoint
|
|
9
|
+
if not base_url:
|
|
10
|
+
return endpoint
|
|
11
|
+
return urljoin(base_url.rstrip("/") + "/", endpoint.lstrip("/"))
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from snapline.engine import load_json_file
|
|
10
|
+
|
|
11
|
+
from ..resolve_url import resolve_url
|
|
12
|
+
from ..types import ApiExecuteContext, ApiExecuteResult, RestApiConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _default_fetch(
|
|
16
|
+
url: str,
|
|
17
|
+
*,
|
|
18
|
+
method: str = "GET",
|
|
19
|
+
headers: dict[str, str] | None = None,
|
|
20
|
+
content: str | bytes | None = None,
|
|
21
|
+
data: str | bytes | None = None,
|
|
22
|
+
) -> httpx.Response:
|
|
23
|
+
body = content if content is not None else data
|
|
24
|
+
return httpx.request(method, url, headers=headers, content=body)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def execute_rest(
|
|
28
|
+
config: RestApiConfig,
|
|
29
|
+
context: ApiExecuteContext | dict[str, Any] | None = None,
|
|
30
|
+
) -> ApiExecuteResult:
|
|
31
|
+
ctx = context or {}
|
|
32
|
+
base_url = ctx.get("baseUrl")
|
|
33
|
+
auth_headers = ctx.get("authHeaders", {})
|
|
34
|
+
fetch_impl = ctx.get("fetchImpl") or _default_fetch
|
|
35
|
+
input_from_row = ctx.get("inputFromRow")
|
|
36
|
+
|
|
37
|
+
endpoint = config["endpoint"]
|
|
38
|
+
method = config.get("method") or "GET"
|
|
39
|
+
input_file = config.get("inputFile")
|
|
40
|
+
body = config.get("body")
|
|
41
|
+
headers = config.get("headers") or {}
|
|
42
|
+
|
|
43
|
+
url = resolve_url(endpoint, base_url)
|
|
44
|
+
payload: Any = body
|
|
45
|
+
|
|
46
|
+
if input_file:
|
|
47
|
+
payload = load_json_file(input_file)
|
|
48
|
+
|
|
49
|
+
if input_from_row:
|
|
50
|
+
if isinstance(payload, dict) and not isinstance(payload, list):
|
|
51
|
+
payload = {**payload, **input_from_row}
|
|
52
|
+
else:
|
|
53
|
+
payload = dict(input_from_row)
|
|
54
|
+
|
|
55
|
+
http_method = method.upper()
|
|
56
|
+
if input_from_row and http_method in {"GET", "HEAD"}:
|
|
57
|
+
parsed = urlparse(url)
|
|
58
|
+
query = dict(parse_qsl(parsed.query))
|
|
59
|
+
query.update({key: str(value) for key, value in input_from_row.items()})
|
|
60
|
+
url = urlunparse(parsed._replace(query=urlencode(query)))
|
|
61
|
+
|
|
62
|
+
request_headers = {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
**auth_headers,
|
|
66
|
+
**headers,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
request_body: str | None = None
|
|
70
|
+
if payload is not None and http_method not in {"GET", "HEAD"}:
|
|
71
|
+
request_body = json.dumps(payload)
|
|
72
|
+
|
|
73
|
+
response = fetch_impl(
|
|
74
|
+
url,
|
|
75
|
+
method=http_method,
|
|
76
|
+
headers=request_headers,
|
|
77
|
+
content=request_body,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
text = response.text
|
|
81
|
+
response_headers = dict(response.headers)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
data = json.loads(text) if text else None
|
|
85
|
+
except json.JSONDecodeError:
|
|
86
|
+
data = text
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"status": response.status_code,
|
|
90
|
+
"data": data,
|
|
91
|
+
"headers": response_headers,
|
|
92
|
+
"raw": text,
|
|
93
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from ..resolve_url import resolve_url
|
|
9
|
+
from ..soap.xml_utils import build_soap_envelope, parse_soap_body
|
|
10
|
+
from ..types import ApiExecuteContext, ApiExecuteResult, SoapApiConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _default_fetch(
|
|
14
|
+
url: str,
|
|
15
|
+
*,
|
|
16
|
+
method: str = "GET",
|
|
17
|
+
headers: dict[str, str] | None = None,
|
|
18
|
+
content: str | bytes | None = None,
|
|
19
|
+
data: str | bytes | None = None,
|
|
20
|
+
) -> httpx.Response:
|
|
21
|
+
body = content if content is not None else data
|
|
22
|
+
return httpx.request(method, url, headers=headers, content=body)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_envelope(config: SoapApiConfig) -> str:
|
|
26
|
+
if config.get("envelope"):
|
|
27
|
+
return config["envelope"]
|
|
28
|
+
|
|
29
|
+
input_file = config.get("inputFile")
|
|
30
|
+
if input_file:
|
|
31
|
+
return Path(input_file).read_text(encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
return build_soap_envelope(
|
|
34
|
+
"<GetUserRequest><email>unknown@example.com</email></GetUserRequest>"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def execute_soap(
|
|
39
|
+
config: SoapApiConfig,
|
|
40
|
+
context: ApiExecuteContext | dict[str, Any] | None = None,
|
|
41
|
+
) -> ApiExecuteResult:
|
|
42
|
+
ctx = context or {}
|
|
43
|
+
base_url = ctx.get("baseUrl")
|
|
44
|
+
auth_headers = ctx.get("authHeaders", {})
|
|
45
|
+
fetch_impl = ctx.get("fetchImpl") or _default_fetch
|
|
46
|
+
input_from_row = ctx.get("inputFromRow")
|
|
47
|
+
|
|
48
|
+
envelope = _load_envelope(config)
|
|
49
|
+
|
|
50
|
+
if input_from_row and input_from_row.get("email"):
|
|
51
|
+
import re
|
|
52
|
+
|
|
53
|
+
envelope = re.sub(
|
|
54
|
+
r"<email>[^<]*</email>",
|
|
55
|
+
f"<email>{input_from_row['email']}</email>",
|
|
56
|
+
envelope,
|
|
57
|
+
flags=re.IGNORECASE,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
url = resolve_url(config["endpoint"], base_url)
|
|
61
|
+
headers: dict[str, str] = {
|
|
62
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
63
|
+
"Accept": "text/xml",
|
|
64
|
+
**auth_headers,
|
|
65
|
+
**(config.get("headers") or {}),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if config.get("soapAction"):
|
|
69
|
+
headers["SOAPAction"] = config["soapAction"]
|
|
70
|
+
|
|
71
|
+
response = fetch_impl(
|
|
72
|
+
url,
|
|
73
|
+
method="POST",
|
|
74
|
+
headers=headers,
|
|
75
|
+
content=envelope,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
text = response.text
|
|
79
|
+
response_headers = dict(response.headers)
|
|
80
|
+
data = parse_soap_body(text)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
"status": response.status_code,
|
|
84
|
+
"data": data,
|
|
85
|
+
"headers": response_headers,
|
|
86
|
+
"raw": text,
|
|
87
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_soap_body(xml: str) -> dict[str, Any]:
|
|
8
|
+
body_match = re.search(
|
|
9
|
+
r"<(?:[\w]+:)?Body[^>]*>([\s\S]*?)</(?:[\w]+:)?Body>",
|
|
10
|
+
xml,
|
|
11
|
+
flags=re.IGNORECASE,
|
|
12
|
+
)
|
|
13
|
+
inner = body_match.group(1) if body_match else xml
|
|
14
|
+
result: dict[str, Any] = {}
|
|
15
|
+
|
|
16
|
+
tag_pattern = re.compile(
|
|
17
|
+
r"<(?:[\w]+:)?(\w+)[^>]*>([^<]*)</(?:[\w]+:)?\1>",
|
|
18
|
+
flags=re.IGNORECASE,
|
|
19
|
+
)
|
|
20
|
+
for match in tag_pattern.finditer(inner):
|
|
21
|
+
key = match.group(1)
|
|
22
|
+
value = (match.group(2) or "").strip()
|
|
23
|
+
if key and value is not None:
|
|
24
|
+
result[key] = value
|
|
25
|
+
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_soap_envelope(body_inner_xml: str) -> str:
|
|
30
|
+
return (
|
|
31
|
+
'<?xml version="1.0" encoding="UTF-8"?>'
|
|
32
|
+
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
|
|
33
|
+
"<soap:Body>"
|
|
34
|
+
f"{body_inner_xml}"
|
|
35
|
+
"</soap:Body>"
|
|
36
|
+
"</soap:Envelope>"
|
|
37
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any, Literal, Protocol, TypeAlias
|
|
5
|
+
|
|
6
|
+
HttpMethod: TypeAlias = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]
|
|
7
|
+
ApiProtocol: TypeAlias = Literal["rest", "soap", "graphql"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FetchResponse(Protocol):
|
|
11
|
+
status_code: int
|
|
12
|
+
text: str
|
|
13
|
+
headers: dict[str, str]
|
|
14
|
+
|
|
15
|
+
def json(self) -> Any: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FetchImpl(Protocol):
|
|
19
|
+
def __call__(
|
|
20
|
+
self,
|
|
21
|
+
url: str,
|
|
22
|
+
*,
|
|
23
|
+
method: str = "GET",
|
|
24
|
+
headers: dict[str, str] | None = None,
|
|
25
|
+
content: str | bytes | None = None,
|
|
26
|
+
data: str | bytes | None = None,
|
|
27
|
+
) -> FetchResponse: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ApiExecuteContext(dict):
|
|
31
|
+
baseUrl: str | None
|
|
32
|
+
authHeaders: dict[str, str]
|
|
33
|
+
fetchImpl: FetchImpl | None
|
|
34
|
+
inputFromRow: dict[str, Any] | None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ApiExecuteResult(dict):
|
|
38
|
+
status: int
|
|
39
|
+
data: Any
|
|
40
|
+
headers: dict[str, str]
|
|
41
|
+
raw: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RestApiConfig(dict):
|
|
45
|
+
protocol: Literal["rest"]
|
|
46
|
+
endpoint: str
|
|
47
|
+
method: HttpMethod | None
|
|
48
|
+
inputFile: str | None
|
|
49
|
+
body: Any
|
|
50
|
+
headers: dict[str, str] | None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SoapApiConfig(dict):
|
|
54
|
+
protocol: Literal["soap"]
|
|
55
|
+
endpoint: str
|
|
56
|
+
soapAction: str | None
|
|
57
|
+
envelope: str | None
|
|
58
|
+
inputFile: str | None
|
|
59
|
+
headers: dict[str, str] | None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GraphqlApiConfig(dict):
|
|
63
|
+
protocol: Literal["graphql"]
|
|
64
|
+
endpoint: str
|
|
65
|
+
query: str | None
|
|
66
|
+
queryFile: str | None
|
|
67
|
+
variables: dict[str, Any] | None
|
|
68
|
+
variablesFile: str | None
|
|
69
|
+
inputFile: str | None
|
|
70
|
+
dataPath: str | None
|
|
71
|
+
headers: dict[str, str] | None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ApiRequestConfig = RestApiConfig | SoapApiConfig | GraphqlApiConfig
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_rest_config(config: ApiRequestConfig) -> bool:
|
|
78
|
+
return config.get("protocol") == "rest"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_soap_config(config: ApiRequestConfig) -> bool:
|
|
82
|
+
return config.get("protocol") == "soap"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def is_graphql_config(config: ApiRequestConfig) -> bool:
|
|
86
|
+
return config.get("protocol") == "graphql"
|