UncountablePythonSDK 0.0.20__py3-none-any.whl → 0.0.22__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.

Files changed (55) hide show
  1. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/METADATA +3 -1
  2. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/RECORD +55 -34
  3. examples/async_batch.py +36 -0
  4. examples/upload_files.py +19 -0
  5. pkgs/type_spec/actions_registry/__main__.py +35 -23
  6. pkgs/type_spec/actions_registry/emit_typescript.py +71 -9
  7. pkgs/type_spec/builder.py +125 -8
  8. pkgs/type_spec/config.py +1 -0
  9. pkgs/type_spec/emit_open_api.py +197 -16
  10. pkgs/type_spec/emit_open_api_util.py +18 -0
  11. pkgs/type_spec/emit_python.py +241 -55
  12. pkgs/type_spec/load_types.py +48 -5
  13. pkgs/type_spec/open_api_util.py +13 -33
  14. pkgs/type_spec/type_info/emit_type_info.py +129 -8
  15. type_spec/external/api/entity/create_entities.yaml +13 -1
  16. type_spec/external/api/entity/create_entity.yaml +13 -1
  17. type_spec/external/api/entity/transition_entity_phase.yaml +44 -0
  18. type_spec/external/api/permissions/set_core_permissions.yaml +69 -0
  19. type_spec/external/api/recipes/associate_recipe_as_input.yaml +4 -4
  20. type_spec/external/api/recipes/create_recipe.yaml +2 -1
  21. type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +16 -0
  22. type_spec/external/api/recipes/edit_recipe_inputs.yaml +86 -0
  23. type_spec/external/api/recipes/get_curve.yaml +4 -1
  24. type_spec/external/api/recipes/get_recipes_data.yaml +6 -0
  25. type_spec/external/api/recipes/set_recipe_metadata.yaml +1 -0
  26. type_spec/external/api/recipes/set_recipe_tags.yaml +62 -0
  27. uncountable/core/__init__.py +3 -1
  28. uncountable/core/async_batch.py +22 -0
  29. uncountable/core/client.py +84 -10
  30. uncountable/core/file_upload.py +95 -0
  31. uncountable/core/types.py +22 -0
  32. uncountable/types/__init__.py +18 -0
  33. uncountable/types/api/entity/create_entities.py +1 -1
  34. uncountable/types/api/entity/create_entity.py +1 -1
  35. uncountable/types/api/entity/transition_entity_phase.py +66 -0
  36. uncountable/types/api/permissions/__init__.py +1 -0
  37. uncountable/types/api/permissions/set_core_permissions.py +89 -0
  38. uncountable/types/api/recipes/associate_recipe_as_input.py +4 -3
  39. uncountable/types/api/recipes/create_recipe.py +1 -1
  40. uncountable/types/api/recipes/disassociate_recipe_as_input.py +35 -0
  41. uncountable/types/api/recipes/edit_recipe_inputs.py +106 -0
  42. uncountable/types/api/recipes/get_curve.py +2 -1
  43. uncountable/types/api/recipes/get_recipes_data.py +2 -0
  44. uncountable/types/api/recipes/set_recipe_tags.py +91 -0
  45. uncountable/types/async_batch.py +10 -0
  46. uncountable/types/async_batch_processor.py +154 -0
  47. uncountable/types/client_base.py +113 -48
  48. uncountable/types/identifier.py +3 -3
  49. uncountable/types/permissions.py +46 -0
  50. uncountable/types/post_base.py +30 -0
  51. uncountable/types/recipe_inputs.py +30 -0
  52. uncountable/types/recipe_metadata.py +2 -0
  53. uncountable/types/recipe_workflow_steps.py +77 -0
  54. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/WHEEL +0 -0
  55. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/top_level.txt +0 -0
pkgs/type_spec/builder.py CHANGED
@@ -10,7 +10,7 @@ import re
10
10
  from collections import defaultdict
11
11
  from dataclasses import MISSING, dataclass
12
12
  from enum import Enum, StrEnum, auto
13
- from typing import Any, Optional
13
+ from typing import Any, Optional, Self
14
14
 
15
15
  from . import util
16
16
  from .util import parse_type_str, unused
@@ -184,6 +184,34 @@ class SpecTypeInstance(SpecType):
184
184
  return defn_type + self.parameters
185
185
 
186
186
 
187
+ @dataclass(kw_only=True)
188
+ class SpecEndpointExample:
189
+ summary: str
190
+ description: str
191
+ arguments: dict[str, object]
192
+ data: dict[str, object]
193
+
194
+
195
+ @dataclass(kw_only=True)
196
+ class SpecGuide:
197
+ title: str
198
+ markdown_content: str
199
+ html_content: str
200
+
201
+
202
+ @dataclass(kw_only=True, frozen=True)
203
+ class RootGuideKey:
204
+ pass
205
+
206
+
207
+ @dataclass(kw_only=True, frozen=True)
208
+ class EndpointGuideKey:
209
+ path: str
210
+
211
+
212
+ SpecGuideKey = RootGuideKey | EndpointGuideKey
213
+
214
+
187
215
  class SpecTypeLiteralWrapper(SpecType):
188
216
  def __init__(
189
217
  self,
@@ -672,6 +700,32 @@ class ResultType(StrEnum):
672
700
  RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
673
701
 
674
702
 
703
+ @dataclass(kw_only=True, frozen=True)
704
+ class _EndpointPathDetails:
705
+ root: str
706
+ root_path: str
707
+ resolved_path: str
708
+
709
+
710
+ def _resolve_endpoint_path(
711
+ path: str, api_endpoints: dict[str, str]
712
+ ) -> _EndpointPathDetails:
713
+ root_path_source = path.split("/")[0]
714
+ root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
715
+ if root_match is None:
716
+ raise Exception(f"invalid-api-path-root:{root_path_source}")
717
+
718
+ root_var = root_match.group(1)
719
+ root_path = api_endpoints[root_var]
720
+
721
+ _, *rest_path = path.split("/", 1)
722
+ resolved_path = "/".join([root_path] + rest_path)
723
+
724
+ return _EndpointPathDetails(
725
+ root=root_var, root_path=root_path, resolved_path=resolved_path
726
+ )
727
+
728
+
675
729
  class SpecEndpoint:
676
730
  method: RouteMethod
677
731
  root: str
@@ -680,9 +734,11 @@ class SpecEndpoint:
680
734
  path_basename: str
681
735
  data_loader: bool
682
736
  is_sdk: bool
737
+ is_beta: bool
683
738
  # Don't emit TypeScript endpoint code
684
739
  suppress_ts: bool
685
740
  function: Optional[str]
741
+ async_batch_path: str | None = None
686
742
  result_type: ResultType = ResultType.json
687
743
  has_attachment: bool = False
688
744
  desc: str | None = None
@@ -701,6 +757,8 @@ class SpecEndpoint:
701
757
  "path",
702
758
  "data_loader",
703
759
  "is_sdk",
760
+ "is_beta",
761
+ "async_batch_path",
704
762
  "function",
705
763
  "suppress_ts",
706
764
  "desc",
@@ -727,6 +785,15 @@ class SpecEndpoint:
727
785
  assert isinstance(is_sdk, bool)
728
786
  self.is_sdk = is_sdk
729
787
 
788
+ is_beta = data.get("is_beta", False)
789
+ assert isinstance(is_beta, bool)
790
+ self.is_beta = is_beta
791
+
792
+ async_batch_path = data.get("async_batch_path")
793
+ if async_batch_path is not None:
794
+ assert isinstance(async_batch_path, str)
795
+ self.async_batch_path = async_batch_path
796
+
730
797
  self.function = data.get("function")
731
798
 
732
799
  suppress_ts = data.get("suppress_ts", False)
@@ -735,14 +802,10 @@ class SpecEndpoint:
735
802
 
736
803
  self.result_type = ResultType(data.get("result_type", ResultType.json.value))
737
804
 
805
+ path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
806
+ self.root = path_details.root
807
+ self.path_root = path_details.root_path
738
808
  self.desc = data.get("desc")
739
-
740
- root_match = RE_ENDPOINT_ROOT.fullmatch(path[0])
741
- if root_match is None:
742
- raise Exception(f"invalid-api-path-root:{path[0]}")
743
-
744
- self.root = root_match.group(1)
745
- self.path_root = builder.api_endpoints[self.root]
746
809
  # IMPROVE: remove need for is_external flag
747
810
  self.is_external = self.path_root == "api/external"
748
811
  self.has_attachment = data.get("has_attachment", False)
@@ -751,6 +814,10 @@ class SpecEndpoint:
751
814
  not is_sdk or self.desc is not None
752
815
  ), f"Endpoint description required for SDK endpoints, missing: {path}"
753
816
 
817
+ @property
818
+ def resolved_path(self: Self) -> str:
819
+ return f"{self.path_root}/{self.path_dirname}/{self.path_basename}"
820
+
754
821
 
755
822
  def _parse_const(
756
823
  builder: SpecBuilder,
@@ -1001,6 +1068,8 @@ class SpecBuilder:
1001
1068
  self.pending: list[NamespaceDataPair] = []
1002
1069
  self.parts: dict[str, dict[str, str]] = defaultdict(dict)
1003
1070
  self.preparts: dict[str, dict[str, str]] = defaultdict(dict)
1071
+ self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
1072
+ self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
1004
1073
  self.api_endpoints = api_endpoints
1005
1074
  base_namespace = SpecNamespace(name=base_namespace_name)
1006
1075
  for base_type in BaseTypeName:
@@ -1185,5 +1254,53 @@ class SpecBuilder:
1185
1254
  def add_prepart_file(self, target: str, name: str, data: str) -> None:
1186
1255
  self.preparts[target][name] = data
1187
1256
 
1257
+ def add_example_file(self, data: dict[str, object]) -> None:
1258
+ path_details = _resolve_endpoint_path(str(data["path"]), self.api_endpoints)
1259
+
1260
+ examples_data = data["examples"]
1261
+ if not isinstance(examples_data, list):
1262
+ raise Exception(
1263
+ f"'examples' in example files are expected to be a list, endpoint_path={path_details.resolved_path}"
1264
+ )
1265
+ for example in examples_data:
1266
+ arguments = example["arguments"]
1267
+ data_example = example["data"]
1268
+ if not isinstance(arguments, dict) or not isinstance(data_example, dict):
1269
+ raise Exception(
1270
+ f"'arguments' and 'data' fields must be dictionaries for each endpoint example, endpoint={path_details.resolved_path}"
1271
+ )
1272
+ self.examples[path_details.resolved_path].append(
1273
+ SpecEndpointExample(
1274
+ summary=str(example["summary"]),
1275
+ description=str(example["description"]),
1276
+ arguments=arguments,
1277
+ data=data_example,
1278
+ )
1279
+ )
1280
+
1281
+ def add_guide_file(self, file_content: str) -> None:
1282
+ import markdown
1283
+
1284
+ md = markdown.Markdown(extensions=["meta"])
1285
+ html = md.convert(file_content)
1286
+ meta: dict[str, list[str]] = md.Meta # type: ignore[attr-defined]
1287
+ title_meta: list[str] | None = meta.get("title")
1288
+ if title_meta is None:
1289
+ raise Exception("guides requier a title in the meta section")
1290
+
1291
+ path_meta: list[str] | None = meta.get("path")
1292
+ guide_key: SpecGuideKey = RootGuideKey()
1293
+ if path_meta is not None:
1294
+ path_details = _resolve_endpoint_path("".join(path_meta), self.api_endpoints)
1295
+ guide_key = EndpointGuideKey(path=path_details.resolved_path)
1296
+
1297
+ self.guides[guide_key].append(
1298
+ SpecGuide(
1299
+ title="".join(title_meta),
1300
+ html_content=html,
1301
+ markdown_content=file_content,
1302
+ )
1303
+ )
1304
+
1188
1305
  def resolve_proper_name(self, stype: SpecTypeDefn) -> str:
1189
1306
  return f"{'.'.join(stype.namespace.path)}.{stype.name}"
pkgs/type_spec/config.py CHANGED
@@ -56,6 +56,7 @@ class PythonConfig(BaseLanguageConfig):
56
56
  emit_api_argument_lookup: bool = (
57
57
  False # emit a lookup for api endpoint path to argument type.
58
58
  )
59
+ emit_async_batch_processor: bool = False # emit the async batch wrapping functions
59
60
  emit_client_class: bool = False # emit the base class for the api client
60
61
  all_named_type_exports: bool = False # emit __all__ for all named type exports
61
62
  sdk_endpoints_only: bool = False # only emit is_sdk endpoints
@@ -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,15 +105,23 @@ 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
 
99
119
  for namespace in sorted(builder.namespaces.values(), key=lambda ns: ns.name):
100
120
  ctx = EmitOpenAPIContext(namespace=namespace)
101
121
 
122
+ if ctx.namespace.endpoint is not None and ctx.namespace.endpoint.is_beta:
123
+ continue
124
+
102
125
  if ctx.namespace.name == "base":
103
126
  # TODO: add additional base defintions here
104
127
  ctx.types["ObjectId"] = OpenAPIIntegerT()
@@ -109,6 +132,8 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
109
132
  ctx,
110
133
  namespace=namespace,
111
134
  config=config,
135
+ examples=builder.examples,
136
+ guides=builder.guides,
112
137
  )
113
138
 
114
139
  _rewrite_with_notice(
@@ -141,15 +166,144 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
141
166
  return yaml.dump(oa_root, sort_keys=False)
142
167
 
143
168
 
144
- def _emit_endpoint_parameters(typ: OpenAPIType | None) -> dict[str, list[dict[str, str]]]:
169
+ def _is_empty_object_type(typ: OpenAPIType) -> bool:
145
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
+ ):
146
230
  return {}
147
231
 
148
232
  return {
149
233
  "parameters": [
150
- {"$ref": f"#/components/schema/Arguments/{prop_name}"}
151
- 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
+ }
152
244
  ]
245
+ } | _emit_endpoint_parameter_examples(examples)
246
+
247
+
248
+ def _emit_is_beta(is_beta: bool) -> DictApiSchema:
249
+ if is_beta:
250
+ return {"x-beta": True}
251
+ return {}
252
+
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}"
153
307
  }
154
308
 
155
309
 
@@ -159,27 +313,36 @@ def _emit_namespace(
159
313
  namespace: builder.SpecNamespace,
160
314
  *,
161
315
  config: OpenAPIConfig,
316
+ examples: dict[str, list[builder.SpecEndpointExample]],
317
+ guides: dict[builder.SpecGuideKey, list[builder.SpecGuide]],
162
318
  ) -> None:
163
319
  for stype in namespace.types.values():
164
320
  _emit_type(ctx, stype, config=config)
165
321
 
166
322
  if namespace.endpoint is not None:
167
- _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
+ )
168
330
 
169
331
  oa_components: dict[str, object] = dict()
170
332
 
171
333
  if ctx.endpoint is not None:
172
334
  endpoint = ctx.endpoint
173
-
174
335
  argument_type = ctx.types.get("Arguments")
175
336
  oa_endpoint = dict()
176
337
  oa_endpoint[endpoint.method] = (
177
338
  {
178
339
  "tags": endpoint.tags,
179
340
  "summary": endpoint.summary,
180
- "description": endpoint.description,
181
341
  }
182
- | _emit_endpoint_parameters(argument_type)
342
+ | _emit_endpoint_description(endpoint.description, ctx.endpoint.guides)
343
+ | _emit_is_beta(endpoint.is_beta)
344
+ | _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
345
+ | _emit_endpoint_request_body(endpoint, argument_type, ctx.endpoint.examples)
183
346
  | {
184
347
  "responses": {
185
348
  "200": {
@@ -188,6 +351,7 @@ def _emit_namespace(
188
351
  "application/json": {
189
352
  "schema": {"$ref": "#/components/schema/Data"}
190
353
  }
354
+ | _emit_endpoint_response_examples(ctx.endpoint.examples)
191
355
  },
192
356
  }
193
357
  },
@@ -226,10 +390,7 @@ def _emit_namespace(
226
390
 
227
391
  oa_components["schema"] = cast(
228
392
  object,
229
- {
230
- name: (value.asdict() if name != "Arguments" else value.asarguments())
231
- for name, value in types.items()
232
- },
393
+ {name: value.asdict() for name, value in types.items()},
233
394
  )
234
395
 
235
396
  path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
@@ -338,6 +499,8 @@ def _emit_endpoint(
338
499
  ctx: EmitOpenAPIContext,
339
500
  namespace: builder.SpecNamespace,
340
501
  endpoint: builder.SpecEndpoint,
502
+ endpoint_examples: list[builder.SpecEndpointExample],
503
+ endpoint_guides: list[builder.SpecGuide],
341
504
  ) -> None:
342
505
  assert namespace.endpoint is not None
343
506
  assert namespace.path[0] == "api"
@@ -386,6 +549,24 @@ def _emit_endpoint(
386
549
  tags=[tag_name],
387
550
  summary=f"{'/'.join(namespace.path[path_cutoff:])}",
388
551
  description=description,
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
+ ],
389
570
  )
390
571
 
391
572
 
@@ -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,12 +62,24 @@ 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
62
77
  tags: list[str]
63
78
  summary: str
64
79
  description: str
80
+ is_beta: bool
81
+ examples: list[EmitOpenAPIEndpointExample]
82
+ guides: list[EmitOpenAPIGuide]
65
83
 
66
84
 
67
85
  @dataclass