@seamapi/nextlove-sdk-generator 1.5.3 → 1.5.4

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.
Files changed (68) hide show
  1. package/lib/endpoints-returning-deprecated-action-attempt.d.ts +2 -0
  2. package/lib/endpoints-returning-deprecated-action-attempt.js +10 -0
  3. package/lib/endpoints-returning-deprecated-action-attempt.js.map +1 -0
  4. package/lib/generate-php-sdk/generate-php-sdk.js +4 -18
  5. package/lib/generate-php-sdk/generate-php-sdk.js.map +1 -1
  6. package/lib/generate-php-sdk/utils/php-client.d.ts +3 -3
  7. package/lib/generate-php-sdk/utils/php-client.js.map +1 -1
  8. package/lib/generate-python-sdk/class-file.d.ts +15 -2
  9. package/lib/generate-python-sdk/class-file.js +140 -21
  10. package/lib/generate-python-sdk/class-file.js.map +1 -1
  11. package/lib/generate-python-sdk/generate-python-sdk.js +65 -17
  12. package/lib/generate-python-sdk/generate-python-sdk.js.map +1 -1
  13. package/lib/generate-python-sdk/map-python-type.d.ts +1 -1
  14. package/lib/generate-python-sdk/map-python-type.js +6 -2
  15. package/lib/generate-python-sdk/map-python-type.js.map +1 -1
  16. package/lib/generate-python-sdk/templates/.gitignore.template.d.ts +2 -0
  17. package/lib/generate-python-sdk/templates/.gitignore.template.js +12 -0
  18. package/lib/generate-python-sdk/templates/.gitignore.template.js.map +1 -0
  19. package/lib/generate-python-sdk/templates/__init__.py.template.d.ts +2 -0
  20. package/lib/generate-python-sdk/templates/__init__.py.template.js +7 -0
  21. package/lib/generate-python-sdk/templates/__init__.py.template.js.map +1 -0
  22. package/lib/generate-python-sdk/templates/conftest.py.template.js +75 -6
  23. package/lib/generate-python-sdk/templates/conftest.py.template.js.map +1 -1
  24. package/lib/generate-python-sdk/templates/prebuild.py.template.d.ts +2 -0
  25. package/lib/generate-python-sdk/templates/prebuild.py.template.js +13 -0
  26. package/lib/generate-python-sdk/templates/prebuild.py.template.js.map +1 -0
  27. package/lib/generate-python-sdk/templates/pyproject.toml.template.js +6 -2
  28. package/lib/generate-python-sdk/templates/pyproject.toml.template.js.map +1 -1
  29. package/lib/generate-python-sdk/templates/seam.py.template.js +90 -14
  30. package/lib/generate-python-sdk/templates/seam.py.template.js.map +1 -1
  31. package/lib/generate-python-sdk/templates/snippets/abstract-seam.template.js +12 -5
  32. package/lib/generate-python-sdk/templates/snippets/abstract-seam.template.js.map +1 -1
  33. package/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.js +6 -1
  34. package/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.js.map +1 -1
  35. package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.d.ts +2 -0
  36. package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.js +17 -0
  37. package/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.js.map +1 -0
  38. package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.d.ts +2 -0
  39. package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.js +6 -0
  40. package/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.js.map +1 -0
  41. package/lib/generate-python-sdk/templates/utils/report_error.py.template.d.ts +2 -0
  42. package/lib/generate-python-sdk/templates/utils/report_error.py.template.js +17 -0
  43. package/lib/generate-python-sdk/templates/utils/report_error.py.template.js.map +1 -0
  44. package/lib/openapi/get-parameter-and-response-schema.js +9 -9
  45. package/lib/openapi/get-parameter-and-response-schema.js.map +1 -1
  46. package/lib/openapi/map-parent-to-children-resource.d.ts +3 -0
  47. package/lib/openapi/map-parent-to-children-resource.js +17 -0
  48. package/lib/openapi/map-parent-to-children-resource.js.map +1 -0
  49. package/package.json +1 -1
  50. package/src/lib/endpoints-returning-deprecated-action-attempt.ts +10 -0
  51. package/src/lib/generate-php-sdk/generate-php-sdk.ts +6 -30
  52. package/src/lib/generate-php-sdk/utils/php-client.ts +2 -2
  53. package/src/lib/generate-python-sdk/class-file.ts +232 -49
  54. package/src/lib/generate-python-sdk/generate-python-sdk.ts +96 -23
  55. package/src/lib/generate-python-sdk/map-python-type.ts +9 -3
  56. package/src/lib/generate-python-sdk/templates/.gitignore.template.ts +11 -0
  57. package/src/lib/generate-python-sdk/templates/__init__.py.template.ts +6 -0
  58. package/src/lib/generate-python-sdk/templates/conftest.py.template.ts +75 -6
  59. package/src/lib/generate-python-sdk/templates/prebuild.py.template.ts +12 -0
  60. package/src/lib/generate-python-sdk/templates/pyproject.toml.template.ts +6 -2
  61. package/src/lib/generate-python-sdk/templates/seam.py.template.ts +90 -14
  62. package/src/lib/generate-python-sdk/templates/snippets/abstract-seam.template.ts +12 -5
  63. package/src/lib/generate-python-sdk/templates/snippets/resource-dataclass.template.ts +9 -1
  64. package/src/lib/generate-python-sdk/templates/snippets/seam-api-exception-class.template.ts +16 -0
  65. package/src/lib/generate-python-sdk/templates/utils/get_sentry_dsn.py.template.ts +5 -0
  66. package/src/lib/generate-python-sdk/templates/utils/report_error.py.template.ts +16 -0
  67. package/src/lib/openapi/get-parameter-and-response-schema.ts +8 -11
  68. package/src/lib/openapi/map-parent-to-children-resource.ts +28 -0
@@ -1,24 +1,47 @@
1
- export type ClassFileMethod = {
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
- return_path: string[]
10
- return_resource: string
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(name: string, namespace?: string | undefined) {
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
- ...this.methods.map(({ method_name, parameters }) =>
33
- [
34
- "",
35
- "",
36
- `@abc.abstractmethod`,
37
- `def ${method_name}(self, ${parameters
38
- .sort((a, b) => (a.position ?? 9999) - (b.position ?? 9999))
39
- .map(({ name, type, position }) =>
40
- position !== undefined
41
- ? `${name}: ${type}`
42
- : `${name}: Optional[${type}] = None`
43
- )
44
- .join(", ")}):`,
45
- ` raise NotImplementedError()`,
46
- ]
47
- .map((s) => ` ${s}`)
48
- .join("\n")
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
- return [
55
- `from seamapi.types import (${[
56
- `Abstract${this.name}`,
57
- `AbstractSeam as Seam`,
58
- ...Array.from(
59
- new Set(
60
- this.methods.map((m) =>
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
- ].join(",")})`,
66
- `from typing import (Optional, Any)`,
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
- const is_return_resource_a_list = return_resource.startsWith("List[")
78
- const list_item_type = return_resource.slice(5, -1)
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((a, b) => (a.position ?? 9999) - (b.position ?? 9999))
84
- .map(({ name, type, position }) =>
85
- position !== undefined
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
- .join(", ")}):`,
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
- ` res = self.seam.make_request(`,
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
- is_return_resource_a_list
101
- ? ` return [${list_item_type}.from_dict(item) for item in res["${return_path.join(
102
- '"]["'
103
- )}"]]`
104
- : ` return ${return_resource}.from_dict(res["${return_path.join(
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(group_names.join("_"))
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
- class_map[class_name] = new ClassFile(class_name, namespace)
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
- param_val && typeof param_val === "object" && "type" in param_val
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(param_val.type),
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: response_obj_type
86
- ? pascalCase(response_obj_type)
87
- : `List[${pascalCase(response_arr_type)}]`,
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
- `class SeamAPIException(Exception):`,
123
- ` pass`,
161
+ SeamApiExceptionClassTemplate(),
124
162
  "",
125
163
  "",
126
- ...Object.entries(class_map).map(([_, cls]) =>
127
- cls.serializeToAbstractClassWithoutImports()
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["seamapi/__init__.py"] = `from seamapi.seam import Seam`
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
- fs["pyproject.toml"] = pyprojectTomlTemplate()
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
- return "List[Any]" // TODO, make more specific
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
 
@@ -0,0 +1,11 @@
1
+ export default () => `dist
2
+ seamapi/utils/get_sentry_dsn.py
3
+ .env
4
+ __pycache__
5
+ */__pycache__
6
+ node_modules
7
+
8
+ .deeper
9
+ .yalc
10
+ yalc.lock
11
+ `
@@ -0,0 +1,6 @@
1
+ export default () => `# flake8: noqa
2
+ # type: ignore
3
+
4
+ from seamapi.seam import Seam
5
+ from seamapi.seam import SeamApiException
6
+ `
@@ -1,11 +1,80 @@
1
- export default () => `import pytest
2
- import random
1
+ export default () => `import random
3
2
  import string
3
+ import pytest
4
4
  from seamapi import Seam
5
+ from dotenv import load_dotenv
6
+ from typing import Any
7
+ from dataclasses import dataclass
5
8
 
6
9
 
10
+ @pytest.fixture(autouse=True)
11
+ def dotenv_fixture():
12
+ load_dotenv()
13
+
14
+
15
+ @dataclass
16
+ class SeamBackend:
17
+ url: str
18
+ sandbox_api_key: str
19
+
20
+
21
+ # TODO this should use scope="session", but there's some issue, this would
22
+ # dramatically reduce test time to switch
7
23
  @pytest.fixture(scope="function")
8
- def seam():
9
- r = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
10
- seam = Seam(api_url=f"https://{r}.fakeseamconnect.seam.vc", api_key="seam_apikey1_token")
11
- yield seam`
24
+ def seam_backend():
25
+ random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
26
+ yield SeamBackend(
27
+ url=f"https://{random_string}.fakeseamconnect.seam.vc",
28
+ sandbox_api_key="seam_apikey1_token",
29
+ )
30
+
31
+
32
+ @pytest.fixture
33
+ def seam(seam_backend: Any):
34
+ seam = Seam(api_url=seam_backend.url, api_key=seam_backend.sandbox_api_key)
35
+ # seam.make_request("POST", "/workspaces/reset_sandbox")
36
+ yield seam
37
+
38
+
39
+ @pytest.fixture
40
+ def fake_sentry(monkeypatch):
41
+ sentry_dsn = "https://key@sentry.io/123"
42
+
43
+ monkeypatch.setenv("SENTRY_DSN", sentry_dsn)
44
+
45
+ sentry_init_args = {}
46
+ sentry_capture_exception_calls = []
47
+ sentry_add_breadcrumb_calls = []
48
+
49
+ class TestSentryClient(object):
50
+ def __init__(self, *args, **kwargs):
51
+ sentry_init_args.update(kwargs)
52
+
53
+ def set_context(self, *args, **kwargs):
54
+ pass
55
+
56
+ monkeypatch.setattr("sentry_sdk.Client", TestSentryClient)
57
+
58
+ class TestSentryScope(object):
59
+ def set_context(self, *args, **kwargs):
60
+ pass
61
+
62
+ class TestSentryHub(object):
63
+ def __init__(self, *args, **kwargs):
64
+ self.scope = TestSentryScope()
65
+
66
+ def capture_exception(self, *args, **kwargs):
67
+ sentry_capture_exception_calls.append((args, kwargs))
68
+
69
+ def add_breadcrumb(self, *args, **kwargs):
70
+ sentry_add_breadcrumb_calls.append((args, kwargs))
71
+
72
+ monkeypatch.setattr("sentry_sdk.Hub", TestSentryHub)
73
+
74
+ yield {
75
+ "sentry_init_args": sentry_init_args,
76
+ "sentry_capture_exception_calls": sentry_capture_exception_calls,
77
+ "sentry_add_breadcrumb_calls": sentry_add_breadcrumb_calls,
78
+ "sentry_dsn": sentry_dsn,
79
+ }
80
+ `
@@ -0,0 +1,12 @@
1
+ export default () => `import os
2
+
3
+ value = os.environ.get("SENTRY_DSN", "None")
4
+ if value != "None":
5
+ value = f'"{value}"'
6
+
7
+ f = open("seamapi/utils/get_sentry_dsn.py", "w")
8
+ f.write(f"""
9
+ def get_sentry_dsn():
10
+ return {value}
11
+ """)
12
+ `