UncountablePythonSDK 0.0.21__py3-none-any.whl → 0.0.23__py3-none-any.whl

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.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

@@ -5,18 +5,22 @@ WORK-IN-PROGRESS, DON'T USE!
5
5
  """
6
6
 
7
7
  import dataclasses
8
+ import json
8
9
  import re
9
- from typing import cast
10
+ from typing import Collection, cast
10
11
 
11
12
  import yaml
12
13
 
13
14
  from . import builder, util
15
+ from .builder import EndpointGuideKey, RootGuideKey
14
16
  from .config import OpenAPIConfig
15
17
  from .emit_open_api_util import (
16
18
  MODIFY_NOTICE,
17
19
  EmitOpenAPIContext,
18
20
  EmitOpenAPIEndpoint,
21
+ EmitOpenAPIEndpointExample,
19
22
  EmitOpenAPIGlobalContext,
23
+ EmitOpenAPIGuide,
20
24
  EmitOpenAPIPath,
21
25
  EmitOpenAPIServer,
22
26
  EmitOpenAPITag,
@@ -74,12 +78,23 @@ def _rewrite_with_notice(
74
78
  return util.rewrite_file(file_path, f"{notice}\n{modified_file_content}")
75
79
 
76
80
 
77
- def _open_api_info(config: OpenAPIConfig) -> GlobalContextInfo:
78
- description = config.description
81
+ def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
82
+ return f"""
83
+ <details>
84
+ <summary>{guide.title}</summary>
85
+ {guide.html_content}
86
+ </details>"""
87
+
88
+
89
+ def _open_api_info(
90
+ config: OpenAPIConfig, guides: list[EmitOpenAPIGuide]
91
+ ) -> GlobalContextInfo:
92
+ full_guides = "<br/>".join([_write_guide_as_html(guide) for guide in guides])
93
+ full_description = f"{config.description}<br/>{full_guides}"
79
94
  info: GlobalContextInfo = dict()
80
95
  info["version"] = "1.0.0"
81
96
  info["title"] = "Uncountable API Documentation"
82
- info["description"] = description
97
+ info["description"] = full_description
83
98
  info["x-logo"] = {"url": "../static/images/logo_blue.png", "altText": "Logo"}
84
99
  return info
85
100
 
@@ -90,9 +105,14 @@ def _open_api_servers(config: OpenAPIConfig) -> list[EmitOpenAPIServer]:
90
105
 
91
106
 
92
107
  def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> None:
108
+ root_guides = builder.guides.get(RootGuideKey(), [])
109
+ openapi_guides = [
110
+ EmitOpenAPIGuide(title=guide.title, html_content=guide.html_content)
111
+ for guide in root_guides
112
+ ]
93
113
  gctx = EmitOpenAPIGlobalContext(
94
114
  version="3.0.0",
95
- info=_open_api_info(config),
115
+ info=_open_api_info(config, openapi_guides),
96
116
  servers=_open_api_servers(config),
97
117
  )
98
118
 
@@ -112,6 +132,8 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
112
132
  ctx,
113
133
  namespace=namespace,
114
134
  config=config,
135
+ examples=builder.examples,
136
+ guides=builder.guides,
115
137
  )
116
138
 
117
139
  _rewrite_with_notice(
@@ -144,52 +166,183 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
144
166
  return yaml.dump(oa_root, sort_keys=False)
145
167
 
146
168
 
147
- def _emit_endpoint_parameters(typ: OpenAPIType | None) -> dict[str, list[dict[str, str]]]:
169
+ def _is_empty_object_type(typ: OpenAPIType) -> bool:
148
170
  if not isinstance(typ, OpenAPIObjectType):
171
+ return False
172
+ return len(typ.properties) == 0
173
+
174
+
175
+ _QUERY_PARM_METHODS = ("get", "head", "options")
176
+ _REQUEST_BODY_METHODS = ("put", "post", "patch", "delete")
177
+
178
+ ApiSchema = dict[str, "ApiSchema"] | Collection["ApiSchema"] | str | bool
179
+ DictApiSchema = dict[str, ApiSchema]
180
+
181
+
182
+ def _emit_endpoint_argument_examples(
183
+ examples: list[EmitOpenAPIEndpointExample],
184
+ ) -> DictApiSchema:
185
+ if len(examples) == 0:
186
+ return {}
187
+
188
+ response_examples = {}
189
+ for example in examples:
190
+ response_examples[example.ref_name] = {
191
+ "summary": example.summary,
192
+ "description": example.description,
193
+ "value": example.arguments,
194
+ }
195
+ return {"examples": response_examples}
196
+
197
+
198
+ def _emit_endpoint_parameter_examples(
199
+ examples: list[EmitOpenAPIEndpointExample],
200
+ ) -> DictApiSchema:
201
+ if len(examples) == 0:
202
+ return {}
203
+
204
+ paramater_examples = []
205
+ comment_new_line = "\n// "
206
+ new_line = "\n"
207
+ for example in examples:
208
+ javascript_description = (
209
+ f"// {comment_new_line.join(example.description.split(new_line))}"
210
+ )
211
+ javascript_json_payload = f"{json.dumps(example.arguments, indent=2)}"
212
+ paramater_examples.append({
213
+ "lang": "JavaScript",
214
+ "label": f"Payload - {example.summary}",
215
+ "source": f"{javascript_description}\n{javascript_json_payload}",
216
+ })
217
+ return {"x-codeSamples": paramater_examples}
218
+
219
+
220
+ def _emit_endpoint_parameters(
221
+ endpoint: EmitOpenAPIEndpoint,
222
+ argument_type: OpenAPIType | None,
223
+ examples: list[EmitOpenAPIEndpointExample],
224
+ ) -> DictApiSchema:
225
+ if (
226
+ endpoint.method.lower() not in _QUERY_PARM_METHODS
227
+ or argument_type is None
228
+ or _is_empty_object_type(argument_type)
229
+ ):
149
230
  return {}
150
231
 
151
232
  return {
152
233
  "parameters": [
153
- {"$ref": f"#/components/schema/Arguments/{prop_name}"}
154
- for prop_name in typ.properties
234
+ {
235
+ "name": "data",
236
+ "required": True,
237
+ "in": "query",
238
+ "content": {
239
+ "application/json": {
240
+ "schema": {"$ref": "#/components/schema/Arguments"}
241
+ }
242
+ },
243
+ }
155
244
  ]
156
- }
245
+ } | _emit_endpoint_parameter_examples(examples)
157
246
 
158
247
 
159
- def _emit_is_beta(is_beta: bool) -> dict[str, bool]:
248
+ def _emit_is_beta(is_beta: bool) -> DictApiSchema:
160
249
  if is_beta:
161
250
  return {"x-beta": True}
162
251
  return {}
163
252
 
164
253
 
254
+ def _emit_endpoint_request_body(
255
+ endpoint: EmitOpenAPIEndpoint,
256
+ arguments_type: OpenAPIType | None,
257
+ examples: list[EmitOpenAPIEndpointExample],
258
+ ) -> DictApiSchema:
259
+ if (
260
+ endpoint.method.lower() not in _REQUEST_BODY_METHODS
261
+ or arguments_type is None
262
+ or _is_empty_object_type(arguments_type)
263
+ ):
264
+ return {}
265
+
266
+ return {
267
+ "requestBody": {
268
+ "content": {
269
+ "application/json": {
270
+ "schema": {
271
+ "type": "object",
272
+ "title": "Body",
273
+ "required": ["data"],
274
+ "properties": {"data": {"$ref": "#/components/schema/Arguments"}},
275
+ }
276
+ }
277
+ | _emit_endpoint_argument_examples(examples)
278
+ },
279
+ }
280
+ }
281
+
282
+
283
+ def _emit_endpoint_response_examples(
284
+ examples: list[EmitOpenAPIEndpointExample],
285
+ ) -> dict[str, dict[str, object]]:
286
+ if len(examples) == 0:
287
+ return {}
288
+
289
+ response_examples: dict[str, object] = {}
290
+ for example in examples:
291
+ response_examples[example.ref_name] = {
292
+ "summary": example.summary,
293
+ "description": example.description,
294
+ "value": example.data,
295
+ }
296
+ return {"examples": response_examples}
297
+
298
+
299
+ def _emit_endpoint_description(
300
+ description: str, guides: list[EmitOpenAPIGuide]
301
+ ) -> dict[str, str]:
302
+ full_guides = "<br/>".join([_write_guide_as_html(guide) for guide in guides])
303
+ return {
304
+ "description": description
305
+ if len(guides) == 0
306
+ else f"{description}<br/>{full_guides}"
307
+ }
308
+
309
+
165
310
  def _emit_namespace(
166
311
  gctx: EmitOpenAPIGlobalContext,
167
312
  ctx: EmitOpenAPIContext,
168
313
  namespace: builder.SpecNamespace,
169
314
  *,
170
315
  config: OpenAPIConfig,
316
+ examples: dict[str, list[builder.SpecEndpointExample]],
317
+ guides: dict[builder.SpecGuideKey, list[builder.SpecGuide]],
171
318
  ) -> None:
172
319
  for stype in namespace.types.values():
173
320
  _emit_type(ctx, stype, config=config)
174
321
 
175
322
  if namespace.endpoint is not None:
176
- _emit_endpoint(gctx, ctx, namespace, namespace.endpoint)
323
+ endpoint_examples = examples.get(namespace.endpoint.resolved_path, [])
324
+ endpoint_guides = guides.get(
325
+ EndpointGuideKey(path=namespace.endpoint.resolved_path), []
326
+ )
327
+ _emit_endpoint(
328
+ gctx, ctx, namespace, namespace.endpoint, endpoint_examples, endpoint_guides
329
+ )
177
330
 
178
331
  oa_components: dict[str, object] = dict()
179
332
 
180
333
  if ctx.endpoint is not None:
181
334
  endpoint = ctx.endpoint
182
-
183
335
  argument_type = ctx.types.get("Arguments")
184
336
  oa_endpoint = dict()
185
337
  oa_endpoint[endpoint.method] = (
186
338
  {
187
339
  "tags": endpoint.tags,
188
340
  "summary": endpoint.summary,
189
- "description": endpoint.description,
190
341
  }
342
+ | _emit_endpoint_description(endpoint.description, ctx.endpoint.guides)
191
343
  | _emit_is_beta(endpoint.is_beta)
192
- | _emit_endpoint_parameters(argument_type)
344
+ | _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
345
+ | _emit_endpoint_request_body(endpoint, argument_type, ctx.endpoint.examples)
193
346
  | {
194
347
  "responses": {
195
348
  "200": {
@@ -198,6 +351,7 @@ def _emit_namespace(
198
351
  "application/json": {
199
352
  "schema": {"$ref": "#/components/schema/Data"}
200
353
  }
354
+ | _emit_endpoint_response_examples(ctx.endpoint.examples)
201
355
  },
202
356
  }
203
357
  },
@@ -236,10 +390,7 @@ def _emit_namespace(
236
390
 
237
391
  oa_components["schema"] = cast(
238
392
  object,
239
- {
240
- name: (value.asdict() if name != "Arguments" else value.asarguments())
241
- for name, value in types.items()
242
- },
393
+ {name: value.asdict() for name, value in types.items()},
243
394
  )
244
395
 
245
396
  path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
@@ -348,6 +499,8 @@ def _emit_endpoint(
348
499
  ctx: EmitOpenAPIContext,
349
500
  namespace: builder.SpecNamespace,
350
501
  endpoint: builder.SpecEndpoint,
502
+ endpoint_examples: list[builder.SpecEndpointExample],
503
+ endpoint_guides: list[builder.SpecGuide],
351
504
  ) -> None:
352
505
  assert namespace.endpoint is not None
353
506
  assert namespace.path[0] == "api"
@@ -397,6 +550,23 @@ def _emit_endpoint(
397
550
  summary=f"{'/'.join(namespace.path[path_cutoff:])}",
398
551
  description=description,
399
552
  is_beta=namespace.endpoint.is_beta,
553
+ examples=[
554
+ EmitOpenAPIEndpointExample(
555
+ ref_name=f"ex_{i}",
556
+ summary=example.summary,
557
+ description=example.description,
558
+ arguments=example.arguments,
559
+ data=example.data,
560
+ )
561
+ for i, example in enumerate(endpoint_examples)
562
+ ],
563
+ guides=[
564
+ EmitOpenAPIGuide(
565
+ title=guide.title,
566
+ html_content=guide.html_content,
567
+ )
568
+ for guide in endpoint_guides
569
+ ],
400
570
  )
401
571
 
402
572
 
@@ -464,6 +634,10 @@ def open_api_type(
464
634
  [open_api_type(ctx, p, config=config) for p in stype.parameters],
465
635
  description="TupleType",
466
636
  )
637
+ if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
638
+ return OpenAPIArrayType(
639
+ open_api_type(ctx, stype.parameters[0], config=config)
640
+ )
467
641
 
468
642
  # TODO: generics are not supported by OpenAPI
469
643
  # map to Free-Form Object and add description
@@ -43,6 +43,12 @@ class EmitOpenAPIServer:
43
43
  url: str
44
44
 
45
45
 
46
+ @dataclass(kw_only=True)
47
+ class EmitOpenAPIGuide:
48
+ title: str
49
+ html_content: str
50
+
51
+
46
52
  @dataclass
47
53
  class EmitOpenAPIGlobalContext:
48
54
  version: str
@@ -56,6 +62,15 @@ class EmitOpenAPIGlobalContext:
56
62
  paths: list[EmitOpenAPIPath] = field(default_factory=list)
57
63
 
58
64
 
65
+ @dataclass(kw_only=True)
66
+ class EmitOpenAPIEndpointExample:
67
+ ref_name: str
68
+ summary: str
69
+ description: str
70
+ arguments: dict[str, object]
71
+ data: dict[str, object]
72
+
73
+
59
74
  @dataclass
60
75
  class EmitOpenAPIEndpoint:
61
76
  method: str
@@ -63,6 +78,8 @@ class EmitOpenAPIEndpoint:
63
78
  summary: str
64
79
  description: str
65
80
  is_beta: bool
81
+ examples: list[EmitOpenAPIEndpointExample]
82
+ guides: list[EmitOpenAPIGuide]
66
83
 
67
84
 
68
85
  @dataclass
@@ -29,7 +29,7 @@ ASYNC_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
29
29
  namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="AsyncBatchRequest"
30
30
  )
31
31
  QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
32
- namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedBatchRequest"
32
+ namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
33
33
  )
34
34
 
35
35
 
@@ -819,6 +819,7 @@ base_name_map = {
819
819
  builder.BaseTypeName.s_opaque_key: "OpaqueKey",
820
820
  builder.BaseTypeName.s_string: "str",
821
821
  builder.BaseTypeName.s_tuple: "tuple",
822
+ builder.BaseTypeName.s_readonly_array: "tuple",
822
823
  builder.BaseTypeName.s_union: "typing.Union",
823
824
  builder.BaseTypeName.s_literal: "typing.Literal",
824
825
  }
@@ -827,6 +828,11 @@ base_name_map = {
827
828
  def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
828
829
  if isinstance(stype, builder.SpecTypeInstance):
829
830
  params = ", ".join([refer_to(ctx, p) for p in stype.parameters])
831
+
832
+ if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
833
+ assert len(stype.parameters) == 1, "Read Only Array takes one parameter"
834
+ params = f"{params}, ..."
835
+
830
836
  return f"{refer_to(ctx, stype.defn_type)}[{params}]"
831
837
 
832
838
  if isinstance(stype, builder.SpecTypeLiteralWrapper):
@@ -394,6 +394,9 @@ def refer_to_impl(
394
394
  if stype.defn_type.name == builder.BaseTypeName.s_list:
395
395
  spec, multi = refer_to_impl(ctx, stype.parameters[0])
396
396
  return f"({spec})[]" if multi else f"{spec}[]", False
397
+ if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
398
+ spec, multi = refer_to_impl(ctx, stype.parameters[0])
399
+ return f"readonly ({spec})[]" if multi else f"readonly {spec}[]", False
397
400
  if stype.defn_type.name == builder.BaseTypeName.s_union:
398
401
  return f'({" | ".join([refer_to(ctx, p) for p in stype.parameters])})', False
399
402
  if stype.defn_type.name == builder.BaseTypeName.s_literal:
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from collections.abc import Callable
3
+ from io import StringIO
3
4
  from typing import Optional
4
5
 
5
6
  import yaml
@@ -13,11 +14,28 @@ ext_map = {
13
14
  ".py": "python",
14
15
  }
15
16
 
17
+ _DOC_FILE_REFEX = ".*/docs/(examples|guides)/.*yaml"
18
+ _EXAMPLE_FILE_REGEX = ".*/docs/examples/.*yaml"
19
+ _GUIDE_FILE_REGEX = ".*/docs/guides/.*md"
20
+
16
21
 
17
22
  def find_and_handle_files(
18
- root_folder: str, name_regex: str, handler: Callable[[str, str], None]
23
+ *,
24
+ root_folder: str,
25
+ handler: Callable[[str, str], None],
26
+ name_regex: str | None = None,
27
+ not_name_regex: str | None = None,
28
+ whole_name_regex: str | None = None,
29
+ not_whole_name_regex: str | None = None,
19
30
  ) -> None:
20
- for file_name in fs.find(root_folder, name_regex=name_regex, relative=True):
31
+ for file_name in fs.find(
32
+ root_folder,
33
+ name_regex=name_regex,
34
+ not_name_regex=not_name_regex,
35
+ whole_name_regex=whole_name_regex,
36
+ not_whole_name_regex=not_whole_name_regex,
37
+ relative=True,
38
+ ):
21
39
  with open(os.path.join(root_folder, file_name), encoding="utf-8") as file:
22
40
  handler(file_name, file.read())
23
41
 
@@ -32,9 +50,16 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
32
50
  name, ext = os.path.splitext(by_name)
33
51
  handler(ext_map[ext], name, file_content)
34
52
 
53
+ def handle_builder_example_add(file_name: str, file_content: str) -> None:
54
+ yaml_content = yaml.safe_load(StringIO(file_content))
55
+ builder.add_example_file(yaml_content)
56
+
57
+ def handle_builder_guide_add(file_name: str, file_content: str) -> None:
58
+ builder.add_guide_file(file_content)
59
+
35
60
  for folder in config.type_spec_types:
36
61
  find_and_handle_files(
37
- folder,
62
+ root_folder=folder,
38
63
  name_regex=".*\\.(ts|py)\\.part",
39
64
  handler=lambda file_name, file_content: handle_builder_add(
40
65
  file_name, file_content, builder.add_part_file
@@ -43,7 +68,7 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
43
68
 
44
69
  for folder in config.type_spec_types:
45
70
  find_and_handle_files(
46
- folder,
71
+ root_folder=folder,
47
72
  name_regex=".*\\.(ts|py)\\.prepart",
48
73
  handler=lambda file_name, file_content: handle_builder_add(
49
74
  file_name, file_content, builder.add_prepart_file
@@ -64,9 +89,27 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
64
89
 
65
90
  for folder in config.type_spec_types:
66
91
  find_and_handle_files(
67
- folder, name_regex=".*\\.yaml", handler=builder_prescan_file
92
+ root_folder=folder,
93
+ name_regex=".*\\.yaml",
94
+ not_whole_name_regex=_DOC_FILE_REFEX,
95
+ handler=builder_prescan_file,
68
96
  )
69
97
 
98
+ if config.open_api is not None:
99
+ for folder in config.type_spec_types:
100
+ find_and_handle_files(
101
+ root_folder=folder,
102
+ whole_name_regex=_EXAMPLE_FILE_REGEX,
103
+ handler=handle_builder_example_add,
104
+ )
105
+
106
+ for folder in config.type_spec_types:
107
+ find_and_handle_files(
108
+ root_folder=folder,
109
+ whole_name_regex=_GUIDE_FILE_REGEX,
110
+ handler=handle_builder_guide_add,
111
+ )
112
+
70
113
  if not builder.process():
71
114
  return None
72
115
 
@@ -1,6 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import StrEnum
3
- from io import UnsupportedOperation
4
3
  from typing import Optional
5
4
 
6
5
 
@@ -16,9 +15,6 @@ class OpenAPIType(ABC):
16
15
  def asdict(self) -> dict[str, object]:
17
16
  pass
18
17
 
19
- def asarguments(self) -> dict[str, dict[str, object]]:
20
- raise UnsupportedOperation
21
-
22
18
  def add_addl_info(self, emitted: dict[str, object]) -> dict[str, object]:
23
19
  if self.description is not None:
24
20
  emitted["description"] = self.description
@@ -148,9 +144,6 @@ class OpenAPIFreeFormObjectType(OpenAPIType):
148
144
  def asdict(self) -> dict[str, object]:
149
145
  return self.add_addl_info({"type": "object"})
150
146
 
151
- def asarguments(self) -> dict[str, dict[str, object]]:
152
- return {}
153
-
154
147
 
155
148
  class OpenAPIObjectType(OpenAPIType):
156
149
  """
@@ -174,31 +167,30 @@ class OpenAPIObjectType(OpenAPIType):
174
167
  self.property_desc = property_desc
175
168
  super().__init__(description=description, nullable=nullable)
176
169
 
170
+ def _emit_property_desc(self, property_name: str) -> dict[str, str]:
171
+ desc = self.property_desc.get(property_name)
172
+ if desc is None or desc.strip() == "":
173
+ return {}
174
+
175
+ return {"description": desc}
176
+
177
177
  def asdict(self) -> dict[str, object]:
178
178
  return self.add_addl_info({
179
179
  "type": "object",
180
+ "required": [
181
+ property_name
182
+ for property_name, property_type in self.properties.items()
183
+ if not property_type.nullable
184
+ ],
180
185
  "properties": {
181
186
  property_name: {
182
187
  **property_type.asdict(),
183
- "description": self.property_desc.get(property_name),
184
188
  }
189
+ | self._emit_property_desc(property_name)
185
190
  for property_name, property_type in self.properties.items()
186
191
  },
187
192
  })
188
193
 
189
- def asarguments(self) -> dict[str, dict[str, object]]:
190
- argument_types: dict[str, dict[str, object]] = {}
191
- for property_name, property_type in self.properties.items():
192
- desc = self.property_desc.get(property_name)
193
- argument_types[property_name] = {
194
- "name": property_name,
195
- "in": "query",
196
- "schema": property_type.asdict(),
197
- "required": not property_type.nullable,
198
- "description": desc or "",
199
- }
200
- return argument_types
201
-
202
194
 
203
195
  class OpenAPIUnionType(OpenAPIType):
204
196
  """
@@ -220,12 +212,6 @@ class OpenAPIUnionType(OpenAPIType):
220
212
  # TODO: use parents description and nullable
221
213
  return {"oneOf": [base_type.asdict() for base_type in self.base_types]}
222
214
 
223
- def asarguments(self) -> dict[str, dict[str, object]]:
224
- # TODO handle inheritence (allOf and refs); need to inline here...
225
- # for now skip this endpoint
226
-
227
- return {}
228
-
229
215
 
230
216
  class OpenAPIIntersectionType(OpenAPIType):
231
217
  """
@@ -246,9 +232,3 @@ class OpenAPIIntersectionType(OpenAPIType):
246
232
  def asdict(self) -> dict[str, object]:
247
233
  # TODO: use parents description and nullable
248
234
  return {"allOf": [base_type.asdict() for base_type in self.base_types]}
249
-
250
- def asarguments(self) -> dict[str, dict[str, object]]:
251
- # TODO handle inheritence (allOf and refs); need to inline here...
252
- # for now skip this endpoint
253
-
254
- return {}
@@ -24,6 +24,7 @@ Arguments:
24
24
  Literal<entity.EntityType.lab_request>,
25
25
  Literal<entity.EntityType.approval>,
26
26
  Literal<entity.EntityType.custom_entity>,
27
+ Literal<entity.EntityType.inventory_amount>,
27
28
  Literal<entity.EntityType.task>,
28
29
  Literal<entity.EntityType.project>,
29
30
  Literal<entity.EntityType.equipment>,
@@ -31,6 +31,7 @@ Arguments:
31
31
  Literal<entity.EntityType.lab_request>,
32
32
  Literal<entity.EntityType.approval>,
33
33
  Literal<entity.EntityType.custom_entity>,
34
+ Literal<entity.EntityType.inventory_amount>,
34
35
  Literal<entity.EntityType.task>,
35
36
  Literal<entity.EntityType.project>,
36
37
  Literal<entity.EntityType.equipment>,
@@ -1,6 +1,5 @@
1
1
  $endpoint:
2
2
  is_sdk: true
3
- is_beta: true
4
3
  async_batch_path: edit_recipe_inputs
5
4
  method: post
6
5
  path: ${external}/recipes/edit_recipe_inputs
@@ -77,12 +76,10 @@ Arguments:
77
76
  type: identifier.IdentifierKey
78
77
  desc: "Identifier for the recipe"
79
78
  recipe_workflow_step_identifier:
80
- type: recipe_workflow_steps.RecipeWorkflowStepIdentifierType
79
+ type: recipe_workflow_steps.RecipeWorkflowStepIdentifier
81
80
  edits:
82
81
  type: List<RecipeInputEdit>
83
82
 
84
83
  Data:
85
84
  type: Object
86
85
  properties:
87
- result_id:
88
- type: ObjectId
@@ -1,4 +1,5 @@
1
1
  from .client import AuthDetailsApiKey, Client
2
2
  from .file_upload import MediaFileUpload, UploadedFile
3
+ from .async_batch import AsyncBatchProcessor
3
4
 
4
- __all__: list[str] = ["AuthDetailsApiKey", "Client", "MediaFileUpload", "UploadedFile"]
5
+ __all__: list[str] = ["AuthDetailsApiKey", "AsyncBatchProcessor", "Client", "MediaFileUpload", "UploadedFile"]
@@ -0,0 +1,22 @@
1
+ from uncountable.core.client import Client
2
+ from uncountable.types.async_batch import AsyncBatchRequest
3
+ from uncountable.types.async_batch_processor import AsyncBatchProcessorBase
4
+ from uncountable.types import async_batch_t, base_t
5
+
6
+
7
+ class AsyncBatchProcessor(AsyncBatchProcessorBase):
8
+ _client: Client
9
+ _queue: list[AsyncBatchRequest]
10
+
11
+ def __init__(self, *, client: Client) -> None:
12
+ super().__init__()
13
+ self._client = client
14
+ self._queue = []
15
+
16
+ def _enqueue(self, req: async_batch_t.AsyncBatchRequest) -> None:
17
+ self._queue.append(req)
18
+
19
+ def send(self) -> base_t.ObjectId:
20
+ job_id = self._client.execute_batch_load_async(requests=self._queue).job_id
21
+ self._queue = []
22
+ return job_id