UncountablePythonSDK 0.0.115__py3-none-any.whl → 0.0.142.dev0__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.
- docs/conf.py +52 -5
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +1 -1
- docs/requirements.txt +3 -2
- examples/basic_auth.py +7 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
- examples/integration-server/jobs/materials_auto/example_http.py +19 -7
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +57 -16
- examples/integration-server/jobs/materials_auto/profile.yaml +27 -0
- examples/integration-server/pyproject.toml +4 -4
- examples/oauth.py +7 -0
- pkgs/argument_parser/__init__.py +1 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +22 -3
- pkgs/serialization_util/serialization_helpers.py +3 -1
- pkgs/type_spec/builder.py +66 -19
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +26 -5
- pkgs/type_spec/cross_output_links.py +10 -16
- pkgs/type_spec/emit_open_api.py +72 -22
- pkgs/type_spec/emit_open_api_util.py +1 -0
- pkgs/type_spec/emit_python.py +76 -12
- pkgs/type_spec/emit_typescript.py +48 -32
- pkgs/type_spec/emit_typescript_util.py +44 -6
- pkgs/type_spec/load_types.py +2 -2
- pkgs/type_spec/open_api_util.py +16 -1
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/emit_type_info.py +37 -4
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +1 -0
- pkgs/type_spec/value_spec/__main__.py +2 -2
- pkgs/type_spec/value_spec/emit_python.py +6 -1
- uncountable/core/client.py +10 -3
- uncountable/integration/cli.py +175 -23
- uncountable/integration/executors/executors.py +1 -2
- uncountable/integration/executors/generic_upload_executor.py +1 -1
- uncountable/integration/http_server/types.py +3 -1
- uncountable/integration/job.py +35 -3
- uncountable/integration/queue_runner/command_server/__init__.py +4 -0
- uncountable/integration/queue_runner/command_server/command_client.py +89 -0
- uncountable/integration/queue_runner/command_server/command_server.py +117 -5
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +51 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +34 -11
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +102 -1
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +180 -0
- uncountable/integration/queue_runner/command_server/types.py +44 -1
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +189 -8
- uncountable/integration/queue_runner/datastore/interface.py +13 -0
- uncountable/integration/queue_runner/datastore/model.py +8 -1
- uncountable/integration/queue_runner/job_scheduler.py +85 -21
- uncountable/integration/queue_runner/queue_runner.py +10 -2
- uncountable/integration/queue_runner/types.py +2 -0
- uncountable/integration/queue_runner/worker.py +28 -29
- uncountable/integration/scheduler.py +121 -23
- uncountable/integration/server.py +36 -6
- uncountable/integration/telemetry.py +129 -8
- uncountable/integration/webhook_server/entrypoint.py +2 -0
- uncountable/types/__init__.py +38 -0
- uncountable/types/api/entity/create_or_update_entity.py +1 -0
- uncountable/types/api/entity/export_entities.py +13 -0
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +25 -0
- uncountable/types/api/entity/set_barcode.py +43 -0
- uncountable/types/api/entity/transition_entity_phase.py +2 -1
- uncountable/types/api/files/download_file.py +15 -1
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +57 -0
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/recipes/edit_recipe_inputs.py +1 -1
- uncountable/types/api/recipes/get_recipe_output_metadata.py +2 -2
- uncountable/types/api/recipes/get_recipes_data.py +29 -0
- uncountable/types/api/recipes/lock_recipes.py +2 -1
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unlock_recipes.py +2 -1
- uncountable/types/api/runsheet/export_default_runsheet.py +44 -0
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch_processor.py +266 -0
- uncountable/types/async_batch_t.py +5 -0
- uncountable/types/client_base.py +432 -2
- uncountable/types/client_config.py +1 -0
- uncountable/types/client_config_t.py +10 -0
- uncountable/types/entity_t.py +9 -1
- uncountable/types/exports_t.py +1 -0
- uncountable/types/integration_server_t.py +2 -0
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/listing.py +46 -0
- uncountable/types/listing_t.py +533 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/queued_job.py +2 -0
- uncountable/types/queued_job_t.py +20 -2
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/METADATA +5 -2
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/RECORD +118 -79
- docs/quickstart.md +0 -19
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
|
|
6
|
-
from
|
|
5
|
+
from . import builder
|
|
6
|
+
from .builder_types import CrossOutputPaths
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_python_stub_file_path(
|
|
@@ -17,14 +17,6 @@ def get_python_stub_file_path(
|
|
|
17
17
|
return api_stub_file
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
@dataclass(kw_only=True, frozen=True)
|
|
21
|
-
class CrossOutputPaths:
|
|
22
|
-
python_types_output: str
|
|
23
|
-
typescript_types_output: str
|
|
24
|
-
typescript_routes_output: str
|
|
25
|
-
typespec_files_input: list[str]
|
|
26
|
-
|
|
27
|
-
|
|
28
20
|
def get_python_api_file_path(
|
|
29
21
|
cross_output_paths: CrossOutputPaths,
|
|
30
22
|
namespace: builder.SpecNamespace,
|
|
@@ -35,10 +27,9 @@ def get_python_api_file_path(
|
|
|
35
27
|
def get_typescript_api_file_path(
|
|
36
28
|
cross_output_paths: CrossOutputPaths,
|
|
37
29
|
namespace: builder.SpecNamespace,
|
|
30
|
+
endpoint_key: builder.EndpointKey,
|
|
38
31
|
) -> str:
|
|
39
|
-
return (
|
|
40
|
-
f"{cross_output_paths.typescript_routes_output}/{'/'.join(namespace.path)}.tsx"
|
|
41
|
-
)
|
|
32
|
+
return f"{cross_output_paths.typescript_routes_output_by_endpoint[endpoint_key]}/{'/'.join(namespace.path)}.tsx"
|
|
42
33
|
|
|
43
34
|
|
|
44
35
|
def get_yaml_api_file_path(
|
|
@@ -68,13 +59,16 @@ def get_path_links(
|
|
|
68
59
|
namespace: builder.SpecNamespace,
|
|
69
60
|
*,
|
|
70
61
|
current_path_type: str,
|
|
62
|
+
endpoint: builder.SpecEndpoint,
|
|
71
63
|
) -> str:
|
|
72
64
|
if cross_output_paths is None:
|
|
73
65
|
return ""
|
|
74
66
|
|
|
75
67
|
api_paths = {
|
|
76
68
|
"Python": get_python_api_file_path(cross_output_paths, namespace),
|
|
77
|
-
"TypeScript": get_typescript_api_file_path(
|
|
69
|
+
"TypeScript": get_typescript_api_file_path(
|
|
70
|
+
cross_output_paths, namespace, endpoint.default_endpoint_key
|
|
71
|
+
),
|
|
78
72
|
"YAML": get_yaml_api_file_path(cross_output_paths, namespace),
|
|
79
73
|
}
|
|
80
74
|
|
|
@@ -95,11 +89,11 @@ def get_path_links(
|
|
|
95
89
|
|
|
96
90
|
if namespace.endpoint is not None:
|
|
97
91
|
for (
|
|
98
|
-
|
|
92
|
+
endpoint_key,
|
|
99
93
|
path_specific_endpoint,
|
|
100
94
|
) in namespace.endpoint.path_per_api_endpoint.items():
|
|
101
95
|
path_from_root = get_python_stub_file_path(path_specific_endpoint.function)
|
|
102
96
|
if path_from_root is None:
|
|
103
97
|
continue
|
|
104
|
-
paths_string += f"{comment_prefix} Implementation for {
|
|
98
|
+
paths_string += f"{comment_prefix} Implementation for {endpoint_key}: file://./{return_to_root_path}{path_from_root}\n"
|
|
105
99
|
return paths_string
|
pkgs/type_spec/emit_open_api.py
CHANGED
|
@@ -261,6 +261,10 @@ def _emit_endpoint_parameters(
|
|
|
261
261
|
} | _emit_endpoint_parameter_examples(examples)
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
def _emit_endpoint_deprecated(deprecated: bool) -> DictApiSchema:
|
|
265
|
+
return {"deprecated": True} if deprecated else {}
|
|
266
|
+
|
|
267
|
+
|
|
264
268
|
def _emit_stability_level(
|
|
265
269
|
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
266
270
|
) -> DictApiSchema:
|
|
@@ -275,7 +279,12 @@ def _emit_stability_level(
|
|
|
275
279
|
case EmitOpenAPIStabilityLevel.draft:
|
|
276
280
|
stability_info["x-beta"] = True
|
|
277
281
|
case EmitOpenAPIStabilityLevel.beta:
|
|
278
|
-
stability_info["x-badges"] = [
|
|
282
|
+
stability_info["x-badges"] = [
|
|
283
|
+
{
|
|
284
|
+
"name": "Beta",
|
|
285
|
+
"color": "DarkOrange",
|
|
286
|
+
}
|
|
287
|
+
]
|
|
279
288
|
case EmitOpenAPIStabilityLevel.stable:
|
|
280
289
|
pass
|
|
281
290
|
case _:
|
|
@@ -330,18 +339,57 @@ def _emit_endpoint_response_examples(
|
|
|
330
339
|
return {"examples": response_examples}
|
|
331
340
|
|
|
332
341
|
|
|
342
|
+
def _create_warning_banner(api_type: str, message: str) -> str:
|
|
343
|
+
return (
|
|
344
|
+
f'<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; '
|
|
345
|
+
f'border-radius: 4px; padding: 12px; margin-bottom: 16px;">'
|
|
346
|
+
f"<strong>⚠️ {api_type} API:</strong> {message}"
|
|
347
|
+
f"</div>"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _get_stability_warning(
|
|
352
|
+
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
353
|
+
) -> str:
|
|
354
|
+
resolved_stability_level = (
|
|
355
|
+
stability_level
|
|
356
|
+
if stability_level is not None
|
|
357
|
+
else EmitOpenAPIStabilityLevel.stable
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
match resolved_stability_level:
|
|
361
|
+
case EmitOpenAPIStabilityLevel.draft:
|
|
362
|
+
return _create_warning_banner(
|
|
363
|
+
"Draft",
|
|
364
|
+
"This endpoint is in draft status and may change significantly. Not recommended for production use.",
|
|
365
|
+
)
|
|
366
|
+
case EmitOpenAPIStabilityLevel.beta:
|
|
367
|
+
return _create_warning_banner(
|
|
368
|
+
"Beta",
|
|
369
|
+
"This endpoint is in beta and its required parameters may change. Use with caution in production environments.",
|
|
370
|
+
)
|
|
371
|
+
case EmitOpenAPIStabilityLevel.stable:
|
|
372
|
+
return ""
|
|
373
|
+
|
|
374
|
+
|
|
333
375
|
def _emit_endpoint_description(
|
|
334
|
-
description: str,
|
|
376
|
+
description: str,
|
|
377
|
+
guides: list[EmitOpenAPIGuide],
|
|
378
|
+
stability_level: EmitOpenAPIStabilityLevel | None = None,
|
|
335
379
|
) -> dict[str, str]:
|
|
380
|
+
stability_warning = _get_stability_warning(stability_level)
|
|
381
|
+
|
|
336
382
|
full_guides = "<br/>".join([
|
|
337
383
|
_write_guide_as_html(guide, is_open=False)
|
|
338
384
|
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
339
385
|
])
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
386
|
+
|
|
387
|
+
full_description_parts = [
|
|
388
|
+
part for part in [stability_warning, description, full_guides] if part
|
|
389
|
+
]
|
|
390
|
+
full_description = "<br/>".join(full_description_parts)
|
|
391
|
+
|
|
392
|
+
return {"description": full_description}
|
|
345
393
|
|
|
346
394
|
|
|
347
395
|
def _emit_namespace(
|
|
@@ -376,7 +424,10 @@ def _emit_namespace(
|
|
|
376
424
|
"tags": endpoint.tags,
|
|
377
425
|
"summary": endpoint.summary,
|
|
378
426
|
}
|
|
379
|
-
|
|
|
427
|
+
| _emit_endpoint_deprecated(endpoint.deprecated)
|
|
428
|
+
| _emit_endpoint_description(
|
|
429
|
+
endpoint.description, ctx.endpoint.guides, endpoint.stability_level
|
|
430
|
+
)
|
|
380
431
|
| _emit_stability_level(endpoint.stability_level)
|
|
381
432
|
| _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
|
|
382
433
|
| _emit_endpoint_request_body(
|
|
@@ -474,8 +525,18 @@ def _emit_type(
|
|
|
474
525
|
return
|
|
475
526
|
|
|
476
527
|
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
477
|
-
|
|
478
|
-
|
|
528
|
+
converted_discriminator_map: dict[str, OpenAPIRefType] = dict()
|
|
529
|
+
if stype.discriminator_map is not None:
|
|
530
|
+
for discriminator_value, base_type in stype.discriminator_map.items():
|
|
531
|
+
converted_base_type = open_api_type(ctx, base_type, config=config)
|
|
532
|
+
assert isinstance(converted_base_type, OpenAPIRefType)
|
|
533
|
+
converted_discriminator_map[discriminator_value] = converted_base_type
|
|
534
|
+
ctx.types[stype.name] = OpenAPIUnionType(
|
|
535
|
+
[open_api_type(ctx, p, config=config) for p in stype.types],
|
|
536
|
+
discriminator=stype.discriminator,
|
|
537
|
+
discriminator_map=converted_discriminator_map
|
|
538
|
+
if stype.discriminator_map is not None
|
|
539
|
+
else None,
|
|
479
540
|
)
|
|
480
541
|
return
|
|
481
542
|
|
|
@@ -549,18 +610,6 @@ def _emit_type(
|
|
|
549
610
|
ctx.types[stype.name] = final_type
|
|
550
611
|
|
|
551
612
|
|
|
552
|
-
def _emit_constant(ctx: EmitOpenAPIContext, sconst: builder.SpecConstant) -> None:
|
|
553
|
-
if sconst.value_type.is_base_type(builder.BaseTypeName.s_string):
|
|
554
|
-
value = util.encode_common_string(cast(str, sconst.value))
|
|
555
|
-
elif sconst.value_type.is_base_type(builder.BaseTypeName.s_integer):
|
|
556
|
-
value = str(sconst.value)
|
|
557
|
-
else:
|
|
558
|
-
raise Exception("invalid constant type", sconst.name)
|
|
559
|
-
|
|
560
|
-
const_name = sconst.name.upper()
|
|
561
|
-
print("_emit_constant", value, const_name)
|
|
562
|
-
|
|
563
|
-
|
|
564
613
|
def _emit_endpoint(
|
|
565
614
|
gctx: EmitOpenAPIGlobalContext,
|
|
566
615
|
ctx: EmitOpenAPIContext,
|
|
@@ -617,6 +666,7 @@ def _emit_endpoint(
|
|
|
617
666
|
tags=[tag_name],
|
|
618
667
|
summary=f"{'/'.join(namespace.path[path_cutoff:])}",
|
|
619
668
|
description=description,
|
|
669
|
+
deprecated=namespace.endpoint.deprecated,
|
|
620
670
|
stability_level=namespace.endpoint.stability_level,
|
|
621
671
|
examples=[
|
|
622
672
|
EmitOpenAPIEndpointExample(
|
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -35,6 +35,11 @@ QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
|
35
35
|
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
CLIENT_CONFIG_TYPE_NAMESPACE = builder.SpecNamespace(name="client_config")
|
|
39
|
+
REQUEST_OPTIONS_STYPE = builder.SpecTypeDefnObject(
|
|
40
|
+
namespace=CLIENT_CONFIG_TYPE_NAMESPACE, name="RequestOptions"
|
|
41
|
+
)
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
@dataclasses.dataclass(kw_only=True)
|
|
40
45
|
class TrackingContext:
|
|
@@ -117,26 +122,36 @@ def _check_type_match(stype: builder.SpecType, value: Any) -> bool:
|
|
|
117
122
|
raise Exception("invalid type", stype, value)
|
|
118
123
|
|
|
119
124
|
|
|
120
|
-
def _emit_value(
|
|
125
|
+
def _emit_value(
|
|
126
|
+
ctx: TrackingContext, stype: builder.SpecType, value: Any, indent: int = 0
|
|
127
|
+
) -> str:
|
|
121
128
|
literal = builder.unwrap_literal_type(stype)
|
|
122
129
|
if literal is not None:
|
|
123
130
|
return _emit_value(ctx, literal.value_type, literal.value)
|
|
124
131
|
|
|
125
132
|
if stype.is_base_type(builder.BaseTypeName.s_string):
|
|
126
|
-
assert isinstance(value, str)
|
|
133
|
+
assert isinstance(value, str), (
|
|
134
|
+
f"Expected str value for {stype.name} but got {value}"
|
|
135
|
+
)
|
|
127
136
|
return util.encode_common_string(value)
|
|
128
137
|
elif stype.is_base_type(builder.BaseTypeName.s_integer):
|
|
129
|
-
assert isinstance(value, int)
|
|
138
|
+
assert isinstance(value, int), (
|
|
139
|
+
f"Expected int value for {stype.name} but got {value}"
|
|
140
|
+
)
|
|
130
141
|
return str(value)
|
|
131
142
|
elif stype.is_base_type(builder.BaseTypeName.s_boolean):
|
|
132
|
-
assert isinstance(value, bool)
|
|
143
|
+
assert isinstance(value, bool), (
|
|
144
|
+
f"Expected bool value for {stype.name} but got {value}"
|
|
145
|
+
)
|
|
133
146
|
return "True" if value else "False"
|
|
134
147
|
elif stype.is_base_type(builder.BaseTypeName.s_decimal) or stype.is_base_type(
|
|
135
148
|
builder.BaseTypeName.s_lossy_decimal
|
|
136
149
|
):
|
|
137
150
|
# Note that decimal requires the `!decimal 123.12` style notation in the YAML
|
|
138
151
|
# file since PyYaml parses numbers as float, unfortuantely
|
|
139
|
-
assert isinstance(value, (Decimal, int))
|
|
152
|
+
assert isinstance(value, (Decimal, int)), (
|
|
153
|
+
f"Expected decimal value for {stype.name} but got {value} (type: {type(value)})"
|
|
154
|
+
)
|
|
140
155
|
if isinstance(value, int):
|
|
141
156
|
# skip quotes for integers
|
|
142
157
|
return f"Decimal({value})"
|
|
@@ -151,14 +166,14 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
151
166
|
key_type = stype.parameters[0]
|
|
152
167
|
value_type = stype.parameters[1]
|
|
153
168
|
return (
|
|
154
|
-
"{\n
|
|
155
|
-
+ ",\n
|
|
169
|
+
f"{{\n{INDENT * (indent + 1)}"
|
|
170
|
+
+ f",\n{INDENT * (indent + 1)}".join(
|
|
156
171
|
_emit_value(ctx, key_type, dkey)
|
|
157
172
|
+ ": "
|
|
158
|
-
+ _emit_value(ctx, value_type, dvalue)
|
|
173
|
+
+ _emit_value(ctx, value_type, dvalue, indent=indent + 1)
|
|
159
174
|
for dkey, dvalue in value.items()
|
|
160
175
|
)
|
|
161
|
-
+ "\n}"
|
|
176
|
+
+ f"\n{INDENT * indent}}}"
|
|
162
177
|
)
|
|
163
178
|
|
|
164
179
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
@@ -184,6 +199,34 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
184
199
|
return f"{refer_to(ctx, stype)}.{_resolve_enum_name(value, stype.name_case)}"
|
|
185
200
|
elif isinstance(stype, builder.SpecTypeDefnAlias):
|
|
186
201
|
return _emit_value(ctx, stype.alias, value)
|
|
202
|
+
elif isinstance(stype, builder.SpecTypeDefnObject):
|
|
203
|
+
assert isinstance(value, dict), (
|
|
204
|
+
f"Expected dict value for {stype.name} but got {value}"
|
|
205
|
+
)
|
|
206
|
+
if not stype.is_hashable:
|
|
207
|
+
raise Exception("invalid constant object type, non-hashable", value, stype)
|
|
208
|
+
obj_out = f"{refer_to(ctx, stype)}("
|
|
209
|
+
emitted_fields: set[str] = set()
|
|
210
|
+
for prop_name, prop in (stype.properties or {}).items():
|
|
211
|
+
if prop_name not in value:
|
|
212
|
+
continue
|
|
213
|
+
else:
|
|
214
|
+
value_to_emit = value[prop_name]
|
|
215
|
+
emitted_fields.add(prop_name)
|
|
216
|
+
py_name = python_field_name(prop.name, prop.name_case)
|
|
217
|
+
obj_out += f"\n{INDENT * (indent + 1)}{py_name}={_emit_value(ctx, prop.spec_type, value_to_emit, indent=indent + 1)},"
|
|
218
|
+
whitespace = f"\n{INDENT * indent}" if len(emitted_fields) > 0 else ""
|
|
219
|
+
obj_out += f"{whitespace})"
|
|
220
|
+
|
|
221
|
+
if emitted_fields != set(value.keys()):
|
|
222
|
+
raise Exception(
|
|
223
|
+
"invalid object type, extra fields found:",
|
|
224
|
+
value,
|
|
225
|
+
stype,
|
|
226
|
+
set(value.keys()) - emitted_fields,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return obj_out
|
|
187
230
|
|
|
188
231
|
raise Exception("invalid constant type", value, stype)
|
|
189
232
|
|
|
@@ -357,7 +400,10 @@ def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
|
357
400
|
endpoint = namespace.endpoint
|
|
358
401
|
if endpoint is not None:
|
|
359
402
|
path_links = get_path_links(
|
|
360
|
-
ctx.builder.cross_output_paths,
|
|
403
|
+
ctx.builder.cross_output_paths,
|
|
404
|
+
namespace,
|
|
405
|
+
current_path_type="Python",
|
|
406
|
+
endpoint=endpoint,
|
|
361
407
|
)
|
|
362
408
|
if path_links != "":
|
|
363
409
|
ctx.out.write("\n")
|
|
@@ -468,6 +514,19 @@ def _emit_endpoint_invocation_function_signature(
|
|
|
468
514
|
else []
|
|
469
515
|
) + (extra_params if extra_params is not None else [])
|
|
470
516
|
|
|
517
|
+
request_options_property = builder.SpecProperty(
|
|
518
|
+
name="_request_options",
|
|
519
|
+
label="_request_options",
|
|
520
|
+
spec_type=REQUEST_OPTIONS_STYPE,
|
|
521
|
+
extant=builder.PropertyExtant.optional,
|
|
522
|
+
convert_value=builder.PropertyConvertValue.auto,
|
|
523
|
+
name_case=builder.NameCase.convert,
|
|
524
|
+
default=None,
|
|
525
|
+
has_default=True,
|
|
526
|
+
desc=None,
|
|
527
|
+
)
|
|
528
|
+
all_arguments.append(request_options_property)
|
|
529
|
+
|
|
471
530
|
# All endpoints share a function name
|
|
472
531
|
function = endpoint.path_per_api_endpoint[endpoint.default_endpoint_key].function
|
|
473
532
|
assert function is not None
|
|
@@ -638,6 +697,7 @@ def _emit_endpoint_invocation_function(
|
|
|
638
697
|
method={refer_to(ctx=ctx, stype=endpoint_method_stype)},
|
|
639
698
|
endpoint={refer_to(ctx=ctx, stype=endpoint_path_stype)},
|
|
640
699
|
args=args,
|
|
700
|
+
request_options=_request_options,
|
|
641
701
|
)
|
|
642
702
|
return self.do_request(api_request=api_request, return_type={refer_to(ctx=ctx, stype=data_type)})"""
|
|
643
703
|
)
|
|
@@ -1393,7 +1453,7 @@ CLIENT_CLASS_IMPORTS = [
|
|
|
1393
1453
|
"import dataclasses",
|
|
1394
1454
|
]
|
|
1395
1455
|
ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
1396
|
-
|
|
1456
|
+
ASYNC_BATCH_PROCESSOR_BASE_IMPORTS = [
|
|
1397
1457
|
"import uuid",
|
|
1398
1458
|
"from abc import ABC, abstractmethod",
|
|
1399
1459
|
"from pkgs.serialization_util import serialize_for_api",
|
|
@@ -1431,8 +1491,11 @@ def _emit_async_batch_processor(
|
|
|
1431
1491
|
config=config,
|
|
1432
1492
|
)
|
|
1433
1493
|
|
|
1494
|
+
imports = ASYNC_BATCH_PROCESSOR_BASE_IMPORTS.copy()
|
|
1495
|
+
if ctx.use_dataclass:
|
|
1496
|
+
imports.append("import dataclasses")
|
|
1434
1497
|
async_batch_processor_out.write(
|
|
1435
|
-
f"""{LINE_BREAK.join(
|
|
1498
|
+
f"""{LINE_BREAK.join(imports)}
|
|
1436
1499
|
|
|
1437
1500
|
|
|
1438
1501
|
class AsyncBatchProcessorBase(ABC):
|
|
@@ -1495,6 +1558,7 @@ class APIRequest:
|
|
|
1495
1558
|
method: str
|
|
1496
1559
|
endpoint: str
|
|
1497
1560
|
args: typing.Any
|
|
1561
|
+
request_options: {refer_to(ctx=ctx, stype=REQUEST_OPTIONS_STYPE)} | None = None
|
|
1498
1562
|
|
|
1499
1563
|
|
|
1500
1564
|
class ClientMethods(ABC):
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import os
|
|
3
|
+
from typing import assert_never
|
|
3
4
|
|
|
4
5
|
from . import builder, util
|
|
5
|
-
from .builder import EndpointKey, EndpointSpecificPath
|
|
6
|
+
from .builder import EndpointKey, EndpointSpecificPath, PathMapping
|
|
6
7
|
from .config import TypeScriptConfig
|
|
7
8
|
from .cross_output_links import get_path_links
|
|
8
9
|
from .emit_io_ts import emit_type_io_ts
|
|
9
10
|
from .emit_typescript_util import (
|
|
10
11
|
MODIFY_NOTICE,
|
|
11
12
|
EmitTypescriptContext,
|
|
13
|
+
emit_constant_ts,
|
|
12
14
|
emit_namespace_imports_ts,
|
|
13
15
|
emit_type_ts,
|
|
14
|
-
emit_value_ts,
|
|
15
16
|
resolve_namespace_name,
|
|
16
17
|
resolve_namespace_ref,
|
|
17
18
|
ts_type_name,
|
|
@@ -37,6 +38,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
|
|
|
37
38
|
out=io.StringIO(),
|
|
38
39
|
namespace=namespace,
|
|
39
40
|
cross_output_paths=builder.cross_output_paths,
|
|
41
|
+
api_endpoints=builder.api_endpoints,
|
|
40
42
|
)
|
|
41
43
|
|
|
42
44
|
_emit_namespace(ctx, config, namespace)
|
|
@@ -78,6 +80,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
|
|
|
78
80
|
full.write("\n")
|
|
79
81
|
full.write(MODIFY_NOTICE)
|
|
80
82
|
full.write(f"// === START section from {namespace.name}.ts.part ===\n")
|
|
83
|
+
full.write("\n")
|
|
81
84
|
full.write(part)
|
|
82
85
|
full.write(f"// === END section from {namespace.name}.ts.part ===\n")
|
|
83
86
|
|
|
@@ -112,7 +115,7 @@ def _emit_namespace(
|
|
|
112
115
|
emit_type_ts(ctx, stype)
|
|
113
116
|
|
|
114
117
|
for sconst in namespace.constants.values():
|
|
115
|
-
|
|
118
|
+
emit_constant_ts(ctx, sconst)
|
|
116
119
|
|
|
117
120
|
if namespace.endpoint is not None:
|
|
118
121
|
_emit_endpoint(ctx, config, namespace, namespace.endpoint)
|
|
@@ -145,7 +148,10 @@ def _emit_endpoint(
|
|
|
145
148
|
assert endpoint.result_type == builder.ResultType.json
|
|
146
149
|
|
|
147
150
|
paths_string = get_path_links(
|
|
148
|
-
ctx.cross_output_paths,
|
|
151
|
+
ctx.cross_output_paths,
|
|
152
|
+
namespace,
|
|
153
|
+
current_path_type="TypeScript",
|
|
154
|
+
endpoint=endpoint,
|
|
149
155
|
)
|
|
150
156
|
|
|
151
157
|
data_loader_head = ""
|
|
@@ -155,7 +161,7 @@ def _emit_endpoint(
|
|
|
155
161
|
assert has_data
|
|
156
162
|
|
|
157
163
|
data_loader_head = (
|
|
158
|
-
'import {
|
|
164
|
+
'import { argsKey, buildApiDataLoader } from "unc_base/data_manager"\n'
|
|
159
165
|
)
|
|
160
166
|
data_loader_body = (
|
|
161
167
|
"\nexport const data = buildApiDataLoader(argsKey(), apiCall)\n"
|
|
@@ -175,38 +181,56 @@ def _emit_endpoint(
|
|
|
175
181
|
unc_base_api_imports = (
|
|
176
182
|
f"appSpecificApiPath, {wrap_name}" if has_multiple_endpoints else wrap_name
|
|
177
183
|
)
|
|
184
|
+
path_mapping = ctx.api_endpoints[endpoint.default_endpoint_key].path_mapping
|
|
185
|
+
|
|
186
|
+
match path_mapping:
|
|
187
|
+
case PathMapping.NO_MAPPING:
|
|
188
|
+
path_mapping_part = (
|
|
189
|
+
"\n { pathMapping: ApplicationT.APIPathMapping.noMapping },"
|
|
190
|
+
)
|
|
191
|
+
case PathMapping.DEFAULT_MAPPING:
|
|
192
|
+
path_mapping_part = ""
|
|
193
|
+
case _:
|
|
194
|
+
assert_never(path_mapping)
|
|
195
|
+
|
|
178
196
|
unc_types_imports = (
|
|
179
|
-
'import { ApplicationT } from "unc_types"\n'
|
|
197
|
+
'import { ApplicationT } from "unc_types"\n'
|
|
198
|
+
if has_multiple_endpoints or path_mapping_part != ""
|
|
199
|
+
else ""
|
|
180
200
|
)
|
|
181
201
|
|
|
182
202
|
type_path = f"unc_types/{'/'.join(namespace.path)}"
|
|
183
203
|
|
|
184
204
|
if is_binary:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
{
|
|
205
|
+
tsx_response_head = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
|
|
206
|
+
"""
|
|
207
|
+
tsx_response_part = f"""import type {{ Arguments }} from "{type_path}"
|
|
208
|
+
|
|
188
209
|
export type {{ Arguments }}
|
|
189
210
|
"""
|
|
190
211
|
elif has_data and endpoint.has_attachment:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
212
|
+
tsx_response_head = f"""import {{ type AttachmentResponse, {unc_base_api_imports} }} from "unc_base/api"
|
|
213
|
+
"""
|
|
214
|
+
tsx_response_part = f"""import type {{ Arguments, Data }} from "{type_path}"
|
|
215
|
+
|
|
194
216
|
export type {{ Arguments, Data }}
|
|
195
217
|
export type Response = AttachmentResponse<Data>
|
|
196
218
|
"""
|
|
197
219
|
elif has_data:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{
|
|
220
|
+
tsx_response_head = f"""import {{ {unc_base_api_imports}, type JsonResponse }} from "unc_base/api"
|
|
221
|
+
"""
|
|
222
|
+
tsx_response_part = f"""import type {{ Arguments, Data }} from "{type_path}"
|
|
223
|
+
|
|
201
224
|
export type {{ Arguments, Data }}
|
|
202
225
|
export type Response = JsonResponse<Data>
|
|
203
226
|
"""
|
|
204
227
|
|
|
205
228
|
else:
|
|
206
229
|
assert has_deprecated_result
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{
|
|
230
|
+
tsx_response_head = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
|
|
231
|
+
"""
|
|
232
|
+
tsx_response_part = f"""import type {{ Arguments, DeprecatedResult }} from "{type_path}"
|
|
233
|
+
|
|
210
234
|
export type {{ Arguments }}
|
|
211
235
|
export type Response = DeprecatedResult
|
|
212
236
|
"""
|
|
@@ -247,41 +271,33 @@ export type Response = DeprecatedResult
|
|
|
247
271
|
|
|
248
272
|
# tsx_api = f"""{MODIFY_NOTICE}
|
|
249
273
|
tsx_api = f"""{MODIFY_NOTICE}{paths_string}
|
|
250
|
-
{data_loader_head}{tsx_response_part}
|
|
274
|
+
{tsx_response_head}{data_loader_head}{unc_types_imports}{tsx_response_part}
|
|
251
275
|
export const apiCall = {wrap_call}(
|
|
252
|
-
{endpoint_path_part}
|
|
276
|
+
{endpoint_path_part}{path_mapping_part}
|
|
253
277
|
)
|
|
254
278
|
{data_loader_body}"""
|
|
255
279
|
|
|
256
|
-
output = f"{config.
|
|
280
|
+
output = f"{config.endpoint_to_routes_output[endpoint.default_endpoint_key]}/{'/'.join(namespace.path)}.tsx"
|
|
257
281
|
util.rewrite_file(output, tsx_api)
|
|
258
282
|
|
|
259
283
|
# Hacky index support, until enough is migrated to regen entirely
|
|
260
284
|
# Emits the import into the UI API index file
|
|
261
|
-
index_path = f"{config.
|
|
285
|
+
index_path = f"{config.endpoint_to_routes_output[endpoint.default_endpoint_key]}/{'/'.join(namespace.path[0:-1])}/index.tsx"
|
|
262
286
|
api_name = f"Api{ts_type_name(namespace.path[0 - 1])}"
|
|
263
287
|
if os.path.exists(index_path):
|
|
264
|
-
with open(index_path) as index:
|
|
288
|
+
with open(index_path, encoding="utf-8") as index:
|
|
265
289
|
index_data = index.read()
|
|
266
290
|
need_index = index_data.find(api_name) == -1
|
|
267
291
|
else:
|
|
268
292
|
need_index = True
|
|
269
293
|
|
|
270
294
|
if need_index:
|
|
271
|
-
with open(index_path, "a") as index:
|
|
295
|
+
with open(index_path, "a", encoding="utf-8") as index:
|
|
272
296
|
print(f"Updated API Index {index_path}")
|
|
273
297
|
index.write(f'import * as {api_name} from "./{namespace.path[-1]}"\n\n')
|
|
274
298
|
index.write(f"export {{ {api_name} }}\n")
|
|
275
299
|
|
|
276
300
|
|
|
277
|
-
def _emit_constant(ctx: EmitTypescriptContext, sconst: builder.SpecConstant) -> None:
|
|
278
|
-
ctx.out.write("\n\n")
|
|
279
|
-
ctx.out.write(MODIFY_NOTICE)
|
|
280
|
-
value = emit_value_ts(ctx, sconst.value_type, sconst.value)
|
|
281
|
-
const_name = sconst.name.upper()
|
|
282
|
-
ctx.out.write(f"export const {const_name} = {value}\n")
|
|
283
|
-
|
|
284
|
-
|
|
285
301
|
def _emit_id_source(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
|
|
286
302
|
id_source_output = config.id_source_output
|
|
287
303
|
if id_source_output is None:
|