@seamapi/nextlove-sdk-generator 1.5.3 → 1.5.5
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.
- package/lib/endpoints-returning-deprecated-action-attempt.d.ts +2 -0
- package/lib/endpoints-returning-deprecated-action-attempt.js +10 -0
- package/lib/endpoints-returning-deprecated-action-attempt.js.map +1 -0
- package/lib/generate-php-sdk/generate-php-sdk.js +4 -18
- package/lib/generate-php-sdk/generate-php-sdk.js.map +1 -1
- package/lib/generate-php-sdk/utils/generate-seam-client.js +7 -3
- package/lib/generate-php-sdk/utils/generate-seam-client.js.map +1 -1
- package/lib/generate-php-sdk/utils/php-client.d.ts +3 -3
- package/lib/generate-php-sdk/utils/php-client.js.map +1 -1
- package/lib/generate-python-sdk/class-file.d.ts +15 -2
- package/lib/generate-python-sdk/class-file.js +140 -21
- package/lib/generate-python-sdk/class-file.js.map +1 -1
- package/lib/generate-python-sdk/generate-python-sdk.js +65 -17
- package/lib/generate-python-sdk/generate-python-sdk.js.map +1 -1
- package/lib/generate-python-sdk/map-python-type.d.ts +1 -1
- package/lib/generate-python-sdk/map-python-type.js +6 -2
- package/lib/generate-python-sdk/map-python-type.js.map +1 -1
- package/lib/generate-python-sdk/templates/.gitignore.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/.gitignore.template.js +12 -0
- package/lib/generate-python-sdk/templates/.gitignore.template.js.map +1 -0
- package/lib/generate-python-sdk/templates/__init__.py.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/__init__.py.template.js +7 -0
- package/lib/generate-python-sdk/templates/__init__.py.template.js.map +1 -0
- package/lib/generate-python-sdk/templates/conftest.py.template.js +75 -6
- package/lib/generate-python-sdk/templates/conftest.py.template.js.map +1 -1
- package/lib/generate-python-sdk/templates/prebuild.py.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/prebuild.py.template.js +13 -0
- package/lib/generate-python-sdk/templates/prebuild.py.template.js.map +1 -0
- package/lib/generate-python-sdk/templates/pyproject.toml.template.js +6 -2
- package/lib/generate-python-sdk/templates/pyproject.toml.template.js.map +1 -1
- package/lib/generate-python-sdk/templates/seam.py.template.js +90 -14
- package/lib/generate-python-sdk/templates/seam.py.template.js.map +1 -1
- package/lib/generate-python-sdk/templates/snippets/abstract-seam.template.js +12 -5
- package/lib/generate-python-sdk/templates/snippets/abstract-seam.template.js.map +1 -1
- package/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.js +6 -1
- package/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.js.map +1 -1
- package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.js +17 -0
- package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.js.map +1 -0
- package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.js +6 -0
- package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.js.map +1 -0
- package/lib/generate-python-sdk/templates/utils/report_error.py.template.d.ts +2 -0
- package/lib/generate-python-sdk/templates/utils/report_error.py.template.js +17 -0
- package/lib/generate-python-sdk/templates/utils/report_error.py.template.js.map +1 -0
- package/lib/openapi/flatten-obj-schema.d.ts +2 -0
- package/lib/openapi/flatten-obj-schema.js +3 -0
- package/lib/openapi/flatten-obj-schema.js.map +1 -1
- package/lib/openapi/get-parameter-and-response-schema.js +9 -9
- package/lib/openapi/get-parameter-and-response-schema.js.map +1 -1
- package/lib/openapi/map-parent-to-children-resource.d.ts +3 -0
- package/lib/openapi/map-parent-to-children-resource.js +17 -0
- package/lib/openapi/map-parent-to-children-resource.js.map +1 -0
- package/package.json +1 -1
- package/src/lib/endpoints-returning-deprecated-action-attempt.ts +10 -0
- package/src/lib/generate-php-sdk/generate-php-sdk.ts +6 -30
- package/src/lib/generate-php-sdk/utils/generate-seam-client.ts +7 -3
- package/src/lib/generate-php-sdk/utils/php-client.ts +2 -2
- package/src/lib/generate-python-sdk/class-file.ts +232 -49
- package/src/lib/generate-python-sdk/generate-python-sdk.ts +96 -23
- package/src/lib/generate-python-sdk/map-python-type.ts +9 -3
- package/src/lib/generate-python-sdk/templates/.gitignore.template.ts +11 -0
- package/src/lib/generate-python-sdk/templates/__init__.py.template.ts +6 -0
- package/src/lib/generate-python-sdk/templates/conftest.py.template.ts +75 -6
- package/src/lib/generate-python-sdk/templates/prebuild.py.template.ts +12 -0
- package/src/lib/generate-python-sdk/templates/pyproject.toml.template.ts +6 -2
- package/src/lib/generate-python-sdk/templates/seam.py.template.ts +90 -14
- package/src/lib/generate-python-sdk/templates/snippets/abstract-seam.template.ts +12 -5
- package/src/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.ts +9 -1
- package/src/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.ts +16 -0
- package/src/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.ts +5 -0
- package/src/lib/generate-python-sdk/templates/utils/report_error.py.template.ts +16 -0
- package/src/lib/openapi/flatten-obj-schema.ts +8 -0
- package/src/lib/openapi/get-parameter-and-response-schema.ts +8 -11
- package/src/lib/openapi/map-parent-to-children-resource.ts +28 -0
|
@@ -4,7 +4,7 @@ import { getParameterAndResponseSchema } from "lib/index.js"
|
|
|
4
4
|
import { generateResourceObjectClass } from "./utils/generate-resource-object-class.js"
|
|
5
5
|
import { pascalCase } from "change-case"
|
|
6
6
|
import { getPhpType } from "./utils/get-php-type.js"
|
|
7
|
-
import { PhpClient, type
|
|
7
|
+
import { PhpClient, type PhpClientIdentifier } from "./utils/php-client.js"
|
|
8
8
|
import { generateSeamClient } from "./utils/generate-seam-client.js"
|
|
9
9
|
import { deepExtractResourceObjectSchemas } from "./utils/deep-extract-resource-object-schemas.js"
|
|
10
10
|
import readmeMdTemplate from "./templates/readme.md.template.js"
|
|
@@ -14,6 +14,7 @@ import gitignoreTemplate from "./templates/gitignore.template.js"
|
|
|
14
14
|
import envExampleTemplate from "./templates/env.example.template.js"
|
|
15
15
|
import testFixtureTemplate from "./templates/test-fixture.template.js"
|
|
16
16
|
import smokeTestTemplate from "./templates/smoke-test.template.js"
|
|
17
|
+
import mapParentToChildrenResources from "lib/openapi/map-parent-to-children-resource.js"
|
|
17
18
|
|
|
18
19
|
export const generatePhpSDK = async () => {
|
|
19
20
|
const openapi: OpenAPISchema = await axios
|
|
@@ -64,43 +65,18 @@ export const generatePhpSDK = async () => {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
const
|
|
68
|
-
(acc: Record<string, string[]>, route) => {
|
|
69
|
-
if (!route.post?.["x-fern-sdk-group-name"]) return acc
|
|
70
|
-
|
|
71
|
-
const [parent_resource_name, child_resource_name] =
|
|
72
|
-
route.post["x-fern-sdk-group-name"]
|
|
73
|
-
|
|
74
|
-
// Making TS happy
|
|
75
|
-
if (!parent_resource_name) return acc
|
|
76
|
-
|
|
77
|
-
if (!acc[parent_resource_name]) {
|
|
78
|
-
acc[parent_resource_name] = []
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
child_resource_name &&
|
|
83
|
-
!acc[parent_resource_name]!.includes(child_resource_name)
|
|
84
|
-
) {
|
|
85
|
-
acc[parent_resource_name]!.push(child_resource_name)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return acc
|
|
89
|
-
},
|
|
90
|
-
{}
|
|
91
|
-
)
|
|
92
|
-
|
|
68
|
+
const parent_to_children_resources_map = mapParentToChildrenResources(routes)
|
|
93
69
|
const clients: Record<string, PhpClient> = {}
|
|
94
70
|
|
|
95
71
|
const processClient = (resource_name: string) => {
|
|
96
|
-
const child_client_identifiers:
|
|
97
|
-
|
|
72
|
+
const child_client_identifiers: PhpClientIdentifier[] = (
|
|
73
|
+
parent_to_children_resources_map[resource_name] ?? []
|
|
98
74
|
).map((child_resource) => ({
|
|
99
75
|
client_name: pascalCase(`${resource_name} ${child_resource}`),
|
|
100
76
|
namespace: child_resource,
|
|
101
77
|
}))
|
|
102
78
|
const is_parent_client = Object.keys(
|
|
103
|
-
|
|
79
|
+
parent_to_children_resources_map
|
|
104
80
|
).includes(resource_name)
|
|
105
81
|
const pascal_resource_name = pascalCase(resource_name)
|
|
106
82
|
|
|
@@ -67,6 +67,7 @@ class SeamClient
|
|
|
67
67
|
// TODO handle request errors
|
|
68
68
|
$response = $this->client->request($method, $path, $options);
|
|
69
69
|
$status_code = $response->getStatusCode();
|
|
70
|
+
$request_id = $response->getHeaderLine('seam-request-id');
|
|
70
71
|
|
|
71
72
|
$res_json = null;
|
|
72
73
|
try {
|
|
@@ -83,7 +84,8 @@ class SeamClient
|
|
|
83
84
|
"\\" : " .
|
|
84
85
|
($res_json->error->type ?? "") .
|
|
85
86
|
": " .
|
|
86
|
-
$res_json->error->message
|
|
87
|
+
$res_json->error->message .
|
|
88
|
+
" [Request ID: " . $request_id . "]"
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -91,7 +93,8 @@ class SeamClient
|
|
|
91
93
|
$error_message = $response->getReasonPhrase();
|
|
92
94
|
|
|
93
95
|
throw new Exception(
|
|
94
|
-
"HTTP Error: " . $error_message . " [" . $status_code . "] " . $method . " " . $path
|
|
96
|
+
"HTTP Error: " . $error_message . " [" . $status_code . "] " . $method . " " . $path .
|
|
97
|
+
" [Request ID: " . $request_id . "]"
|
|
95
98
|
);
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -103,7 +106,8 @@ class SeamClient
|
|
|
103
106
|
'" for ' .
|
|
104
107
|
$method .
|
|
105
108
|
" " .
|
|
106
|
-
$path
|
|
109
|
+
$path .
|
|
110
|
+
" [Request ID: " . $request_id . "]"
|
|
107
111
|
);
|
|
108
112
|
}
|
|
109
113
|
return $res_json->$inner_object;
|
|
@@ -16,7 +16,7 @@ export type PhpClientMethod = {
|
|
|
16
16
|
|
|
17
17
|
type ClientName = string
|
|
18
18
|
type Namespace = string
|
|
19
|
-
export type
|
|
19
|
+
export type PhpClientIdentifier = {
|
|
20
20
|
client_name: ClientName
|
|
21
21
|
namespace: Namespace
|
|
22
22
|
}
|
|
@@ -28,7 +28,7 @@ export class PhpClient {
|
|
|
28
28
|
public client_name: ClientName,
|
|
29
29
|
public namespace: Namespace,
|
|
30
30
|
public is_parent_client: boolean,
|
|
31
|
-
public child_client_identifiers:
|
|
31
|
+
public child_client_identifiers: PhpClientIdentifier[]
|
|
32
32
|
) {
|
|
33
33
|
this.methods = []
|
|
34
34
|
}
|
|
@@ -1,24 +1,47 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ClassFileMethodBase = {
|
|
2
2
|
method_name: string
|
|
3
3
|
path: string
|
|
4
4
|
parameters: Array<{
|
|
5
5
|
name: string
|
|
6
6
|
type: string
|
|
7
7
|
position?: number | undefined
|
|
8
|
+
required?: boolean | undefined
|
|
9
|
+
default_value?: string
|
|
8
10
|
}>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ClassFileMethod = ClassFileMethodBase &
|
|
14
|
+
(
|
|
15
|
+
| {
|
|
16
|
+
return_path: string[]
|
|
17
|
+
return_resource: string
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
return_path: string[]
|
|
21
|
+
return_resource: "void"
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
type ChildClassIdentifier = {
|
|
26
|
+
class_name: string
|
|
27
|
+
namespace: string
|
|
11
28
|
}
|
|
12
29
|
|
|
13
30
|
export class ClassFile {
|
|
14
31
|
name: string
|
|
15
32
|
namespace: string | undefined
|
|
16
33
|
methods: ClassFileMethod[]
|
|
34
|
+
child_class_identifiers: ChildClassIdentifier[]
|
|
17
35
|
|
|
18
|
-
constructor(
|
|
36
|
+
constructor(
|
|
37
|
+
name: string,
|
|
38
|
+
namespace: string | undefined,
|
|
39
|
+
child_class_identifiers: ChildClassIdentifier[]
|
|
40
|
+
) {
|
|
19
41
|
this.name = name
|
|
20
42
|
this.namespace = namespace
|
|
21
43
|
this.methods = []
|
|
44
|
+
this.child_class_identifiers = child_class_identifiers
|
|
22
45
|
}
|
|
23
46
|
|
|
24
47
|
addMethod(method: ClassFileMethod) {
|
|
@@ -26,89 +49,249 @@ export class ClassFile {
|
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
serializeToAbstractClassWithoutImports(): string {
|
|
52
|
+
const has_child_classes = this.child_class_identifiers.length > 0
|
|
53
|
+
|
|
29
54
|
return [
|
|
30
55
|
`class Abstract${this.name}(abc.ABC):`,
|
|
31
56
|
this.methods.length === 0 ? ` pass` : "",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
57
|
+
`${
|
|
58
|
+
has_child_classes
|
|
59
|
+
? this.child_class_identifiers
|
|
60
|
+
.map((i) =>
|
|
61
|
+
[
|
|
62
|
+
` @property`,
|
|
63
|
+
` @abc.abstractmethod`,
|
|
64
|
+
` def ${i.namespace}(self) -> Abstract${i.class_name}:`,
|
|
65
|
+
` raise NotImplementedError()`,
|
|
66
|
+
].join("\n")
|
|
67
|
+
)
|
|
68
|
+
.join("\n\n")
|
|
69
|
+
: ""
|
|
70
|
+
}`,
|
|
71
|
+
...this.methods
|
|
72
|
+
.concat(
|
|
73
|
+
this.name === "ActionAttempts"
|
|
74
|
+
? [
|
|
75
|
+
{
|
|
76
|
+
method_name: "poll_until_ready",
|
|
77
|
+
parameters: [
|
|
78
|
+
{ name: "action_attempt_id", type: "str", required: true },
|
|
79
|
+
{ name: "timeout", type: "float", default_value: "5.0" },
|
|
80
|
+
{
|
|
81
|
+
name: "polling_interval",
|
|
82
|
+
type: "float",
|
|
83
|
+
default_value: "0.5",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
return_path: [],
|
|
87
|
+
return_resource: "ActionAttempt",
|
|
88
|
+
path: "",
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
: []
|
|
92
|
+
)
|
|
93
|
+
.map(({ method_name, parameters }) =>
|
|
94
|
+
[
|
|
95
|
+
"",
|
|
96
|
+
"",
|
|
97
|
+
`@abc.abstractmethod`,
|
|
98
|
+
`def ${method_name}(self, ${parameters
|
|
99
|
+
.sort(
|
|
100
|
+
(a, b) =>
|
|
101
|
+
(a.position ?? a.required ? 1000 : 9999) -
|
|
102
|
+
(b.position ?? b.required ? 1000 : 9999)
|
|
103
|
+
)
|
|
104
|
+
.map(({ name, type, required, default_value }) =>
|
|
105
|
+
required
|
|
106
|
+
? `${name}: ${type}`
|
|
107
|
+
: `${name}: Optional[${type}] = ${default_value ?? "None"}`
|
|
108
|
+
)
|
|
109
|
+
.join(", ")}):`,
|
|
110
|
+
` raise NotImplementedError()`,
|
|
111
|
+
]
|
|
112
|
+
.map((s) => ` ${s}`)
|
|
113
|
+
.join("\n")
|
|
114
|
+
),
|
|
50
115
|
].join("\n")
|
|
51
116
|
}
|
|
52
117
|
|
|
53
118
|
serializeToClass(): string {
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
m.return_resource.replace(/^List\[/, "").replace(/\]$/, "")
|
|
62
|
-
)
|
|
119
|
+
const validClasses = [
|
|
120
|
+
`Abstract${this.name}`,
|
|
121
|
+
`AbstractSeam as Seam`,
|
|
122
|
+
...Array.from(
|
|
123
|
+
new Set(
|
|
124
|
+
this.methods.map((m) =>
|
|
125
|
+
m.return_resource.replace(/^List\[/, "").replace(/\]$/, "")
|
|
63
126
|
)
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
127
|
+
)
|
|
128
|
+
),
|
|
129
|
+
].filter((classInstance) => classInstance !== "")
|
|
130
|
+
const has_child_classes = this.child_class_identifiers.length > 0
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
`from seamapi.types import (${validClasses
|
|
134
|
+
.filter((cls) => cls !== "None")
|
|
135
|
+
.join(",")})`,
|
|
136
|
+
`from typing import Optional, Any, List, Dict, Union`,
|
|
137
|
+
`${
|
|
138
|
+
has_child_classes
|
|
139
|
+
? this.child_class_identifiers
|
|
140
|
+
.map(
|
|
141
|
+
(i) =>
|
|
142
|
+
`from seamapi.${this.namespace}_${i.namespace} import ${i.class_name}`
|
|
143
|
+
)
|
|
144
|
+
.join("\n")
|
|
145
|
+
: ""
|
|
146
|
+
}`,
|
|
147
|
+
`${this.name === "ActionAttempts" ? "import time\n" : ""}`,
|
|
148
|
+
|
|
68
149
|
`class ${this.name}(Abstract${this.name}):`,
|
|
69
150
|
// TODO DOCSTRING
|
|
70
151
|
` seam: Seam`,
|
|
71
152
|
"",
|
|
72
153
|
` def __init__(self, seam: Seam):`,
|
|
73
154
|
` self.seam = seam`,
|
|
155
|
+
`${
|
|
156
|
+
has_child_classes
|
|
157
|
+
? this.child_class_identifiers
|
|
158
|
+
.map(
|
|
159
|
+
(i) => ` self._${i.namespace} = ${i.class_name}(seam=seam)`
|
|
160
|
+
)
|
|
161
|
+
.join("\n")
|
|
162
|
+
: ""
|
|
163
|
+
}`,
|
|
74
164
|
"",
|
|
165
|
+
`${
|
|
166
|
+
has_child_classes
|
|
167
|
+
? this.child_class_identifiers
|
|
168
|
+
.map((i) =>
|
|
169
|
+
[
|
|
170
|
+
` @property`,
|
|
171
|
+
` def ${i.namespace}(self) -> ${i.class_name}:`,
|
|
172
|
+
` return self._${i.namespace}`,
|
|
173
|
+
].join("\n")
|
|
174
|
+
)
|
|
175
|
+
.join("\n\n")
|
|
176
|
+
: ""
|
|
177
|
+
}`,
|
|
75
178
|
...this.methods.map(
|
|
76
179
|
({ method_name, path, return_resource, return_path, parameters }) => {
|
|
77
|
-
|
|
78
|
-
const
|
|
180
|
+
let return_resource_item = return_resource
|
|
181
|
+
const is_return_resource_list =
|
|
182
|
+
return_resource_item.startsWith("List[")
|
|
183
|
+
|
|
184
|
+
if (is_return_resource_list) {
|
|
185
|
+
return_resource_item = return_resource.slice(5, -1)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const does_method_use_action_attempt =
|
|
189
|
+
return_resource === "ActionAttempt" && !is_return_resource_list
|
|
190
|
+
const is_none_return_type = return_resource_item === "None"
|
|
191
|
+
|
|
79
192
|
return [
|
|
80
193
|
"",
|
|
81
194
|
"",
|
|
82
195
|
`def ${method_name}(self, ${parameters
|
|
83
|
-
.sort(
|
|
84
|
-
|
|
85
|
-
|
|
196
|
+
.sort(
|
|
197
|
+
(a, b) =>
|
|
198
|
+
(a.position ?? a.required ? 1000 : 9999) -
|
|
199
|
+
(b.position ?? b.required ? 1000 : 9999)
|
|
200
|
+
)
|
|
201
|
+
.map(({ name, type, required }) =>
|
|
202
|
+
required
|
|
86
203
|
? `${name}: ${type}`
|
|
87
204
|
: `${name}: Optional[${type}] = None`
|
|
88
205
|
)
|
|
89
|
-
.
|
|
206
|
+
.concat(
|
|
207
|
+
does_method_use_action_attempt
|
|
208
|
+
? [
|
|
209
|
+
"wait_for_action_attempt: Union[bool, Dict[str, float]] = True",
|
|
210
|
+
]
|
|
211
|
+
: []
|
|
212
|
+
)
|
|
213
|
+
.join(", ")}) -> ${return_resource}:`,
|
|
214
|
+
|
|
90
215
|
` json_payload = {}`,
|
|
216
|
+
"",
|
|
217
|
+
|
|
91
218
|
...parameters.map(
|
|
92
219
|
({ name }) =>
|
|
93
220
|
` if ${name} is not None:\n json_payload["${name}"] = ${name}`
|
|
94
221
|
),
|
|
95
|
-
|
|
222
|
+
"",
|
|
223
|
+
|
|
224
|
+
` ${is_none_return_type ? "" : "res = "}self.seam.make_request(`,
|
|
96
225
|
` "POST",`,
|
|
97
226
|
` "${path}",`,
|
|
98
227
|
` json=json_payload`,
|
|
99
228
|
` )`,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
229
|
+
"",
|
|
230
|
+
|
|
231
|
+
does_method_use_action_attempt
|
|
232
|
+
? [
|
|
233
|
+
" if isinstance(wait_for_action_attempt, dict):",
|
|
234
|
+
` updated_action_attempt = self.seam.action_attempts.poll_until_ready(`,
|
|
235
|
+
" res['action_attempt']['action_attempt_id'],",
|
|
236
|
+
" timeout=wait_for_action_attempt.get('timeout', None),",
|
|
237
|
+
" polling_interval=wait_for_action_attempt.get('polling_interval', None),",
|
|
238
|
+
" )",
|
|
239
|
+
" elif wait_for_action_attempt is True:",
|
|
240
|
+
` updated_action_attempt = self.seam.action_attempts.poll_until_ready(`,
|
|
241
|
+
" res['action_attempt']['action_attempt_id']",
|
|
242
|
+
" )",
|
|
243
|
+
" else:",
|
|
244
|
+
` return ${return_resource}.from_dict(res["${return_path.join(
|
|
245
|
+
'"]["'
|
|
246
|
+
)}"])`,
|
|
247
|
+
"",
|
|
248
|
+
" return updated_action_attempt",
|
|
249
|
+
].join("\n")
|
|
250
|
+
: "",
|
|
251
|
+
"",
|
|
252
|
+
|
|
253
|
+
!does_method_use_action_attempt
|
|
254
|
+
? is_none_return_type
|
|
255
|
+
? ` return None`
|
|
256
|
+
: is_return_resource_list
|
|
257
|
+
? ` return [${return_resource_item}.from_dict(item) for item in res["${return_path.join(
|
|
258
|
+
'"]["'
|
|
259
|
+
)}"]]`
|
|
260
|
+
: ` return ${return_resource}.from_dict(res["${return_path.join(
|
|
261
|
+
'"]["'
|
|
262
|
+
)}"])`
|
|
263
|
+
: "",
|
|
107
264
|
]
|
|
108
265
|
.map((s) => ` ${s}`)
|
|
109
266
|
.join("\n")
|
|
110
267
|
}
|
|
111
268
|
),
|
|
269
|
+
this.name === "ActionAttempts"
|
|
270
|
+
? [
|
|
271
|
+
"",
|
|
272
|
+
" def poll_until_ready(self, action_attempt_id: str, timeout: Optional[float] = 5.0, polling_interval: Optional[float] = 0.5) -> ActionAttempt:",
|
|
273
|
+
" seam = self.seam",
|
|
274
|
+
" time_waiting = 0.0",
|
|
275
|
+
"",
|
|
276
|
+
" action_attempt = seam.action_attempts.get(action_attempt_id, wait_for_action_attempt=False)",
|
|
277
|
+
"",
|
|
278
|
+
" while action_attempt.status == 'pending':",
|
|
279
|
+
" time.sleep(polling_interval)",
|
|
280
|
+
" time_waiting += polling_interval",
|
|
281
|
+
"",
|
|
282
|
+
" if time_waiting > timeout:",
|
|
283
|
+
" raise Exception('Timed out waiting for action attempt to be ready')",
|
|
284
|
+
"",
|
|
285
|
+
" action_attempt = seam.action_attempts.get(",
|
|
286
|
+
" action_attempt.action_attempt_id, wait_for_action_attempt=False",
|
|
287
|
+
" )",
|
|
288
|
+
"",
|
|
289
|
+
" if action_attempt.status == 'failed':",
|
|
290
|
+
" raise Exception(f'Action Attempt failed: {action_attempt.error.message}')",
|
|
291
|
+
"",
|
|
292
|
+
" return action_attempt",
|
|
293
|
+
].join("\n")
|
|
294
|
+
: "",
|
|
112
295
|
].join("\n")
|
|
113
296
|
}
|
|
114
297
|
}
|
|
@@ -14,7 +14,16 @@ import abstractRoutesTemplate from "./templates/snippets/abstract-routes.templat
|
|
|
14
14
|
import abstractSeamTemplate from "./templates/snippets/abstract-seam.template.js"
|
|
15
15
|
import resourceDataclassTemplate from "./templates/snippets/resource-dataclass.template.js"
|
|
16
16
|
import readmeMdTemplate from "./templates/readme.md.template.js"
|
|
17
|
+
import initTemplate from "./templates/__init__.py.template.js"
|
|
18
|
+
import gitignoreTemplate from "./templates/.gitignore.template.js"
|
|
19
|
+
import prebuildTemplate from "./templates/prebuild.py.template.js"
|
|
20
|
+
import reportErrorTemplate from "./templates/utils/report_error.py.template.js"
|
|
21
|
+
import getSentryDsnTemplate from "./templates/utils/get_sentry_dsn.py.template.js"
|
|
22
|
+
import SeamApiExceptionClassTemplate from "./templates/snippets/seam-api-exception-class.template.js"
|
|
17
23
|
import { getParameterAndResponseSchema } from "lib/openapi/get-parameter-and-response-schema.js"
|
|
24
|
+
import mapParentToChildrenResources from "lib/openapi/map-parent-to-children-resource.js"
|
|
25
|
+
import { deepFlattenOneOfAndAllOfSchema } from "lib/generate-php-sdk/utils/deep-flatten-one-of-and-all-of-schema.js"
|
|
26
|
+
import endpoints_returning_deprecated_action_attempt from "lib/endpoints-returning-deprecated-action-attempt.js"
|
|
18
27
|
|
|
19
28
|
export const generatePythonSDK = async () => {
|
|
20
29
|
const openapi: OpenAPISchema = await axios
|
|
@@ -26,24 +35,51 @@ export const generatePythonSDK = async () => {
|
|
|
26
35
|
}))
|
|
27
36
|
|
|
28
37
|
const fs: any = {}
|
|
29
|
-
|
|
30
|
-
fs["README.md"] = readmeMdTemplate()
|
|
31
|
-
|
|
32
38
|
const class_map: Record<string, ClassFile> = {}
|
|
33
|
-
|
|
34
39
|
const namespaces: string[][] = []
|
|
40
|
+
const parent_to_children_resources_map = mapParentToChildrenResources(routes)
|
|
41
|
+
|
|
42
|
+
const processClass = (resource_name: string) => {
|
|
43
|
+
const child_class_identifiers = (
|
|
44
|
+
parent_to_children_resources_map[resource_name] ?? []
|
|
45
|
+
).map((child_resource) => ({
|
|
46
|
+
class_name: pascalCase(`${resource_name} ${child_resource}`),
|
|
47
|
+
namespace: child_resource,
|
|
48
|
+
}))
|
|
49
|
+
const pascal_resource_name = pascalCase(resource_name)
|
|
50
|
+
|
|
51
|
+
class_map[pascal_resource_name] = new ClassFile(
|
|
52
|
+
pascal_resource_name,
|
|
53
|
+
resource_name,
|
|
54
|
+
child_class_identifiers
|
|
55
|
+
)
|
|
56
|
+
}
|
|
35
57
|
|
|
36
58
|
for (const route of routes) {
|
|
37
59
|
if (!route.post) continue
|
|
38
60
|
if (!route.post["x-fern-sdk-group-name"]) continue
|
|
39
61
|
const group_names = [...route.post["x-fern-sdk-group-name"]]
|
|
62
|
+
const [base_resource] = group_names
|
|
40
63
|
const namespace = group_names.join("_")
|
|
41
64
|
group_names.reverse()
|
|
42
|
-
const class_name = pascalCase(
|
|
65
|
+
const class_name = pascalCase(namespace)
|
|
66
|
+
|
|
43
67
|
if (!class_map[class_name]) {
|
|
44
68
|
namespaces.push(route.post["x-fern-sdk-group-name"])
|
|
45
|
-
|
|
69
|
+
|
|
70
|
+
processClass(namespace)
|
|
46
71
|
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
special case when we don't have routes for a base resource
|
|
75
|
+
and thus a respective x-fern-sdk-group-name for ex. /noise_sensors
|
|
76
|
+
*/
|
|
77
|
+
if (base_resource && !class_map[pascalCase(base_resource)]) {
|
|
78
|
+
namespaces.push([base_resource])
|
|
79
|
+
|
|
80
|
+
processClass(base_resource)
|
|
81
|
+
}
|
|
82
|
+
|
|
47
83
|
const cls = class_map[class_name]
|
|
48
84
|
|
|
49
85
|
if (!cls) {
|
|
@@ -54,11 +90,6 @@ export const generatePythonSDK = async () => {
|
|
|
54
90
|
const { parameter_schema, response_obj_type, response_arr_type } =
|
|
55
91
|
getParameterAndResponseSchema(route)
|
|
56
92
|
|
|
57
|
-
if (!response_obj_type && !response_arr_type) {
|
|
58
|
-
console.warn(`No response object/array ref for "${route.path}", skipping`)
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
|
|
62
93
|
if (!parameter_schema) {
|
|
63
94
|
console.warn(`No parameter schema for "${route.path}", skipping`)
|
|
64
95
|
continue
|
|
@@ -70,21 +101,29 @@ export const generatePythonSDK = async () => {
|
|
|
70
101
|
parameters: Object.entries(parameter_schema.properties)
|
|
71
102
|
.filter(
|
|
72
103
|
([_, param_val]) =>
|
|
73
|
-
|
|
104
|
+
"type" in param_val ||
|
|
105
|
+
("oneOf" in param_val && "type" in (param_val.oneOf[0] ?? {}))
|
|
74
106
|
)
|
|
75
107
|
.map(([param_name, param_val]: any) => ({
|
|
76
108
|
name: param_name,
|
|
77
|
-
type: mapPythonType(
|
|
109
|
+
type: mapPythonType(
|
|
110
|
+
"type" in param_val
|
|
111
|
+
? param_val
|
|
112
|
+
: deepFlattenOneOfAndAllOfSchema(param_val)
|
|
113
|
+
),
|
|
78
114
|
position:
|
|
79
115
|
route.post["x-fern-sdk-method-name"] === "get" &&
|
|
80
116
|
param_name === `${route.post["x-fern-sdk-return-value"]}_id`
|
|
81
117
|
? 0
|
|
82
118
|
: undefined,
|
|
119
|
+
required: parameter_schema.required?.includes(param_name),
|
|
83
120
|
})),
|
|
84
121
|
return_path: [route.post["x-fern-sdk-return-value"]],
|
|
85
|
-
return_resource:
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
return_resource: determineReturnResource({
|
|
123
|
+
route_path: route.path,
|
|
124
|
+
response_obj_type,
|
|
125
|
+
response_arr_type,
|
|
126
|
+
}),
|
|
88
127
|
})
|
|
89
128
|
}
|
|
90
129
|
|
|
@@ -119,13 +158,16 @@ export const generatePythonSDK = async () => {
|
|
|
119
158
|
),
|
|
120
159
|
"",
|
|
121
160
|
"",
|
|
122
|
-
|
|
123
|
-
` pass`,
|
|
161
|
+
SeamApiExceptionClassTemplate(),
|
|
124
162
|
"",
|
|
125
163
|
"",
|
|
126
|
-
...Object.entries(class_map)
|
|
127
|
-
|
|
128
|
-
|
|
164
|
+
...Object.entries(class_map)
|
|
165
|
+
.sort(
|
|
166
|
+
// define classes without children first for parent-child referencing
|
|
167
|
+
([, a], [, b]) =>
|
|
168
|
+
a.child_class_identifiers.length - b.child_class_identifiers.length
|
|
169
|
+
)
|
|
170
|
+
.map(([_, cls]) => cls.serializeToAbstractClassWithoutImports()),
|
|
129
171
|
"",
|
|
130
172
|
"",
|
|
131
173
|
abstractRoutesTemplate(top_level_namespaces),
|
|
@@ -133,11 +175,18 @@ export const generatePythonSDK = async () => {
|
|
|
133
175
|
abstractSeamTemplate(),
|
|
134
176
|
].join("\n")
|
|
135
177
|
|
|
136
|
-
fs["
|
|
178
|
+
fs["README.md"] = readmeMdTemplate()
|
|
179
|
+
fs["pyproject.toml"] = pyprojectTomlTemplate()
|
|
180
|
+
fs[".gitignore"] = gitignoreTemplate()
|
|
181
|
+
fs["scripts/prebuild.py"] = prebuildTemplate()
|
|
182
|
+
|
|
183
|
+
fs["seamapi/__init__.py"] = initTemplate()
|
|
137
184
|
fs["seamapi/routes.py"] = routesPyTemplate(top_level_namespaces)
|
|
138
185
|
fs["seamapi/seam.py"] = seamPyTemplate()
|
|
139
186
|
fs["seamapi/utils/deep_attr_dict.py"] = deep_attr_dictPyTemplate()
|
|
140
|
-
|
|
187
|
+
|
|
188
|
+
fs["seamapi/utils/report_error.py"] = reportErrorTemplate()
|
|
189
|
+
fs["seamapi/utils/get_sentry_dsn.py"] = getSentryDsnTemplate()
|
|
141
190
|
|
|
142
191
|
fs["tests/conftest.py"] = conftestPyTemplate()
|
|
143
192
|
fs["tests/test_smoke.py"] = test_smokePyTemplate()
|
|
@@ -145,3 +194,27 @@ export const generatePythonSDK = async () => {
|
|
|
145
194
|
|
|
146
195
|
return fs
|
|
147
196
|
}
|
|
197
|
+
|
|
198
|
+
function determineReturnResource({
|
|
199
|
+
route_path,
|
|
200
|
+
response_arr_type,
|
|
201
|
+
response_obj_type,
|
|
202
|
+
}: {
|
|
203
|
+
route_path: string
|
|
204
|
+
response_obj_type?: string
|
|
205
|
+
response_arr_type?: string
|
|
206
|
+
}) {
|
|
207
|
+
if (endpoints_returning_deprecated_action_attempt.includes(route_path)) {
|
|
208
|
+
return "None"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (response_obj_type) {
|
|
212
|
+
return pascalCase(response_obj_type)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (response_arr_type) {
|
|
216
|
+
return `List[${pascalCase(response_arr_type)}]`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return "None"
|
|
220
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PropertySchema } from "lib/types.js"
|
|
1
|
+
import type { PrimitiveSchema, PropertySchema } from "lib/types.js"
|
|
2
2
|
|
|
3
3
|
// TODO literals?
|
|
4
4
|
export const mapPythonType = (property_schema: PropertySchema) => {
|
|
@@ -20,8 +20,14 @@ export const mapPythonType = (property_schema: PropertySchema) => {
|
|
|
20
20
|
return "bool"
|
|
21
21
|
if ("type" in property_schema && property_schema.type === "number")
|
|
22
22
|
return "float"
|
|
23
|
-
if ("type" in property_schema && property_schema.type === "array")
|
|
24
|
-
|
|
23
|
+
if ("type" in property_schema && property_schema.type === "array") {
|
|
24
|
+
const array_item_type: string =
|
|
25
|
+
"type" in property_schema.items
|
|
26
|
+
? mapPythonType({ type: property_schema.items.type } as PrimitiveSchema)
|
|
27
|
+
: "Any"
|
|
28
|
+
|
|
29
|
+
return `List[${array_item_type}]`
|
|
30
|
+
} // TODO, make more specific
|
|
25
31
|
if ("type" in property_schema && property_schema.type === "object")
|
|
26
32
|
return "Dict[str, Any]" // TODO, make more specific
|
|
27
33
|
|