UncountablePythonSDK 0.0.7__py3-none-any.whl → 0.0.92__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 (311) hide show
  1. UncountablePythonSDK-0.0.92.dist-info/METADATA +61 -0
  2. UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
  3. {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
  4. {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
  5. docs/.gitignore +1 -0
  6. docs/conf.py +57 -0
  7. docs/index.md +13 -0
  8. docs/justfile +12 -0
  9. docs/quickstart.md +19 -0
  10. docs/requirements.txt +7 -0
  11. docs/static/favicons/android-chrome-192x192.png +0 -0
  12. docs/static/favicons/android-chrome-512x512.png +0 -0
  13. docs/static/favicons/apple-touch-icon.png +0 -0
  14. docs/static/favicons/browserconfig.xml +9 -0
  15. docs/static/favicons/favicon-16x16.png +0 -0
  16. docs/static/favicons/favicon-32x32.png +0 -0
  17. docs/static/favicons/manifest.json +18 -0
  18. docs/static/favicons/mstile-150x150.png +0 -0
  19. docs/static/favicons/safari-pinned-tab.svg +32 -0
  20. docs/static/logo_blue.png +0 -0
  21. examples/async_batch.py +35 -0
  22. examples/create_entity.py +22 -17
  23. examples/download_files.py +26 -0
  24. examples/edit_recipe_inputs.py +50 -0
  25. examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
  26. examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
  27. examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
  28. examples/integration-server/pyproject.toml +224 -0
  29. examples/invoke_uploader.py +26 -0
  30. examples/set_recipe_metadata_file.py +40 -0
  31. examples/set_recipe_output_file_sdk.py +26 -0
  32. examples/upload_files.py +18 -0
  33. pkgs/argument_parser/__init__.py +5 -0
  34. pkgs/argument_parser/_is_enum.py +1 -6
  35. pkgs/argument_parser/argument_parser.py +232 -76
  36. pkgs/argument_parser/case_convert.py +4 -3
  37. pkgs/filesystem_utils/__init__.py +20 -0
  38. pkgs/filesystem_utils/_blob_session.py +137 -0
  39. pkgs/filesystem_utils/_gdrive_session.py +309 -0
  40. pkgs/filesystem_utils/_local_session.py +69 -0
  41. pkgs/filesystem_utils/_s3_session.py +117 -0
  42. pkgs/filesystem_utils/_sftp_session.py +147 -0
  43. pkgs/filesystem_utils/file_type_utils.py +91 -0
  44. pkgs/filesystem_utils/filesystem_session.py +39 -0
  45. pkgs/py.typed +0 -0
  46. pkgs/serialization/__init__.py +8 -1
  47. pkgs/serialization/annotation.py +64 -0
  48. pkgs/serialization/opaque_key.py +1 -1
  49. pkgs/serialization/serial_alias.py +47 -0
  50. pkgs/serialization/serial_class.py +65 -50
  51. pkgs/serialization/serial_generic.py +16 -0
  52. pkgs/serialization/serial_union.py +84 -0
  53. pkgs/serialization/yaml.py +57 -0
  54. pkgs/serialization_util/__init__.py +7 -7
  55. pkgs/serialization_util/_get_type_for_serialization.py +1 -3
  56. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  57. pkgs/serialization_util/dataclasses.py +14 -0
  58. pkgs/serialization_util/serialization_helpers.py +118 -73
  59. pkgs/strenum_compat/strenum_compat.py +1 -9
  60. pkgs/type_spec/actions_registry/__init__.py +0 -0
  61. pkgs/type_spec/actions_registry/__main__.py +126 -0
  62. pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
  63. pkgs/type_spec/builder.py +475 -89
  64. pkgs/type_spec/config.py +24 -19
  65. pkgs/type_spec/emit_io_ts.py +5 -2
  66. pkgs/type_spec/emit_open_api.py +266 -32
  67. pkgs/type_spec/emit_open_api_util.py +32 -13
  68. pkgs/type_spec/emit_python.py +601 -150
  69. pkgs/type_spec/emit_typescript.py +74 -273
  70. pkgs/type_spec/emit_typescript_util.py +239 -5
  71. pkgs/type_spec/load_types.py +55 -10
  72. pkgs/type_spec/open_api_util.py +30 -41
  73. pkgs/type_spec/parts/base.py.prepart +4 -3
  74. pkgs/type_spec/type_info/emit_type_info.py +178 -16
  75. pkgs/type_spec/util.py +11 -11
  76. pkgs/type_spec/value_spec/__main__.py +3 -3
  77. pkgs/type_spec/value_spec/convert_type.py +8 -1
  78. pkgs/type_spec/value_spec/emit_python.py +13 -4
  79. uncountable/__init__.py +1 -2
  80. uncountable/core/__init__.py +12 -2
  81. uncountable/core/async_batch.py +37 -0
  82. uncountable/core/client.py +293 -43
  83. uncountable/core/environment.py +41 -0
  84. uncountable/core/file_upload.py +135 -0
  85. uncountable/core/types.py +17 -0
  86. uncountable/integration/__init__.py +0 -0
  87. uncountable/integration/cli.py +49 -0
  88. uncountable/integration/construct_client.py +51 -0
  89. uncountable/integration/cron.py +29 -0
  90. uncountable/integration/db/__init__.py +0 -0
  91. uncountable/integration/db/connect.py +18 -0
  92. uncountable/integration/db/session.py +25 -0
  93. uncountable/integration/entrypoint.py +13 -0
  94. uncountable/integration/executors/__init__.py +0 -0
  95. uncountable/integration/executors/executors.py +148 -0
  96. uncountable/integration/executors/generic_upload_executor.py +284 -0
  97. uncountable/integration/executors/script_executor.py +25 -0
  98. uncountable/integration/job.py +87 -0
  99. uncountable/integration/queue_runner/__init__.py +0 -0
  100. uncountable/integration/queue_runner/command_server/__init__.py +24 -0
  101. uncountable/integration/queue_runner/command_server/command_client.py +68 -0
  102. uncountable/integration/queue_runner/command_server/command_server.py +64 -0
  103. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  104. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
  105. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
  108. uncountable/integration/queue_runner/command_server/types.py +52 -0
  109. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  110. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
  111. uncountable/integration/queue_runner/datastore/interface.py +19 -0
  112. uncountable/integration/queue_runner/datastore/model.py +17 -0
  113. uncountable/integration/queue_runner/job_scheduler.py +163 -0
  114. uncountable/integration/queue_runner/queue_runner.py +26 -0
  115. uncountable/integration/queue_runner/types.py +7 -0
  116. uncountable/integration/queue_runner/worker.py +119 -0
  117. uncountable/integration/scan_profiles.py +67 -0
  118. uncountable/integration/scheduler.py +150 -0
  119. uncountable/integration/secret_retrieval/__init__.py +3 -0
  120. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  121. uncountable/integration/server.py +117 -0
  122. uncountable/integration/telemetry.py +209 -0
  123. uncountable/integration/webhook_server/entrypoint.py +170 -0
  124. uncountable/types/__init__.py +151 -5
  125. uncountable/types/api/batch/execute_batch.py +15 -7
  126. uncountable/types/api/batch/execute_batch_load_async.py +42 -0
  127. uncountable/types/api/chemical/__init__.py +1 -0
  128. uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
  129. uncountable/types/api/entity/create_entities.py +23 -10
  130. uncountable/types/api/entity/create_entity.py +21 -12
  131. uncountable/types/api/entity/get_entities_data.py +19 -29
  132. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  133. uncountable/types/api/entity/list_entities.py +28 -20
  134. uncountable/types/api/entity/lock_entity.py +45 -0
  135. uncountable/types/api/entity/resolve_entity_ids.py +19 -7
  136. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  137. uncountable/types/api/entity/set_values.py +13 -28
  138. uncountable/types/api/entity/transition_entity_phase.py +80 -0
  139. uncountable/types/api/entity/unlock_entity.py +44 -0
  140. uncountable/types/api/equipment/__init__.py +1 -0
  141. uncountable/types/api/equipment/associate_equipment_input.py +44 -0
  142. uncountable/types/api/field_options/__init__.py +1 -0
  143. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  144. uncountable/types/api/files/__init__.py +1 -0
  145. uncountable/types/api/files/download_file.py +77 -0
  146. uncountable/types/api/id_source/__init__.py +1 -0
  147. uncountable/types/api/id_source/list_id_source.py +56 -0
  148. uncountable/types/api/id_source/match_id_source.py +54 -0
  149. uncountable/types/api/input_groups/get_input_group_names.py +18 -7
  150. uncountable/types/api/inputs/create_inputs.py +25 -24
  151. uncountable/types/api/inputs/get_input_data.py +37 -31
  152. uncountable/types/api/inputs/get_input_names.py +20 -9
  153. uncountable/types/api/inputs/get_inputs_data.py +33 -27
  154. uncountable/types/api/inputs/set_input_attribute_values.py +18 -13
  155. uncountable/types/api/inputs/set_input_category.py +44 -0
  156. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  157. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  158. uncountable/types/api/material_families/__init__.py +1 -0
  159. uncountable/types/api/material_families/update_entity_material_families.py +48 -0
  160. uncountable/types/api/outputs/get_output_data.py +38 -29
  161. uncountable/types/api/outputs/get_output_names.py +20 -9
  162. uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
  163. uncountable/types/api/permissions/__init__.py +1 -0
  164. uncountable/types/api/permissions/set_core_permissions.py +105 -0
  165. uncountable/types/api/project/get_projects.py +23 -19
  166. uncountable/types/api/project/get_projects_data.py +26 -43
  167. uncountable/types/api/recipe_links/__init__.py +1 -0
  168. uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
  169. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  170. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +21 -10
  171. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  172. uncountable/types/api/recipes/archive_recipes.py +42 -0
  173. uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
  174. uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
  175. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  176. uncountable/types/api/recipes/create_recipe.py +51 -0
  177. uncountable/types/api/recipes/create_recipes.py +25 -24
  178. uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
  179. uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
  180. uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
  181. uncountable/types/api/recipes/get_curve.py +13 -27
  182. uncountable/types/api/recipes/get_recipe_calculations.py +21 -21
  183. uncountable/types/api/recipes/get_recipe_links.py +14 -6
  184. uncountable/types/api/recipes/get_recipe_names.py +18 -7
  185. uncountable/types/api/recipes/get_recipe_output_metadata.py +18 -19
  186. uncountable/types/api/recipes/get_recipes_data.py +83 -144
  187. uncountable/types/api/recipes/lock_recipes.py +63 -0
  188. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  189. uncountable/types/api/recipes/set_recipe_inputs.py +21 -11
  190. uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
  191. uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
  192. uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
  193. uncountable/types/api/recipes/set_recipe_outputs.py +28 -15
  194. uncountable/types/api/recipes/set_recipe_tags.py +109 -0
  195. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  196. uncountable/types/api/recipes/unlock_recipes.py +50 -0
  197. uncountable/types/api/triggers/__init__.py +1 -0
  198. uncountable/types/api/triggers/run_trigger.py +43 -0
  199. uncountable/types/api/uploader/__init__.py +1 -0
  200. uncountable/types/api/uploader/invoke_uploader.py +47 -0
  201. uncountable/types/async_batch.py +13 -0
  202. uncountable/types/async_batch_processor.py +384 -0
  203. uncountable/types/async_batch_t.py +97 -0
  204. uncountable/types/async_jobs.py +9 -0
  205. uncountable/types/async_jobs_t.py +53 -0
  206. uncountable/types/auth_retrieval.py +12 -0
  207. uncountable/types/auth_retrieval_t.py +75 -0
  208. uncountable/types/base.py +5 -78
  209. uncountable/types/base_t.py +85 -0
  210. uncountable/types/calculations.py +8 -0
  211. uncountable/types/calculations_t.py +27 -0
  212. uncountable/types/chemical_structure.py +8 -0
  213. uncountable/types/chemical_structure_t.py +28 -0
  214. uncountable/types/client_base.py +1115 -76
  215. uncountable/types/client_config.py +8 -0
  216. uncountable/types/client_config_t.py +26 -0
  217. uncountable/types/curves.py +10 -0
  218. uncountable/types/curves_t.py +51 -0
  219. uncountable/types/entity.py +8 -266
  220. uncountable/types/entity_t.py +393 -0
  221. uncountable/types/experiment_groups.py +8 -0
  222. uncountable/types/experiment_groups_t.py +27 -0
  223. uncountable/types/field_values.py +17 -23
  224. uncountable/types/field_values_t.py +204 -0
  225. uncountable/types/fields.py +8 -0
  226. uncountable/types/fields_t.py +28 -0
  227. uncountable/types/generic_upload.py +15 -0
  228. uncountable/types/generic_upload_t.py +119 -0
  229. uncountable/types/id_source.py +12 -0
  230. uncountable/types/id_source_t.py +68 -0
  231. uncountable/types/identifier.py +11 -0
  232. uncountable/types/identifier_t.py +63 -0
  233. uncountable/types/input_attributes.py +8 -0
  234. uncountable/types/input_attributes_t.py +30 -0
  235. uncountable/types/inputs.py +11 -0
  236. uncountable/types/inputs_t.py +83 -0
  237. uncountable/types/integration_server.py +9 -0
  238. uncountable/types/integration_server_t.py +42 -0
  239. uncountable/types/job_definition.py +27 -0
  240. uncountable/types/job_definition_t.py +260 -0
  241. uncountable/types/outputs.py +8 -0
  242. uncountable/types/outputs_t.py +30 -0
  243. uncountable/types/overrides.py +10 -0
  244. uncountable/types/overrides_t.py +49 -0
  245. uncountable/types/permissions.py +8 -0
  246. uncountable/types/permissions_t.py +46 -0
  247. uncountable/types/phases.py +8 -0
  248. uncountable/types/phases_t.py +27 -0
  249. uncountable/types/post_base.py +8 -0
  250. uncountable/types/post_base_t.py +30 -0
  251. uncountable/types/queued_job.py +16 -0
  252. uncountable/types/queued_job_t.py +123 -0
  253. uncountable/types/recipe_identifiers.py +12 -0
  254. uncountable/types/recipe_identifiers_t.py +76 -0
  255. uncountable/types/recipe_inputs.py +9 -0
  256. uncountable/types/recipe_inputs_t.py +30 -0
  257. uncountable/types/recipe_links.py +4 -44
  258. uncountable/types/recipe_links_t.py +54 -0
  259. uncountable/types/recipe_metadata.py +10 -0
  260. uncountable/types/recipe_metadata_t.py +58 -0
  261. uncountable/types/recipe_output_metadata.py +8 -0
  262. uncountable/types/recipe_output_metadata_t.py +28 -0
  263. uncountable/types/recipe_tags.py +8 -0
  264. uncountable/types/recipe_tags_t.py +27 -0
  265. uncountable/types/recipe_workflow_steps.py +14 -0
  266. uncountable/types/recipe_workflow_steps_t.py +95 -0
  267. uncountable/types/recipes.py +8 -0
  268. uncountable/types/recipes_t.py +25 -0
  269. uncountable/types/response.py +8 -0
  270. uncountable/types/response_t.py +26 -0
  271. uncountable/types/secret_retrieval.py +12 -0
  272. uncountable/types/secret_retrieval_t.py +75 -0
  273. uncountable/types/units.py +8 -0
  274. uncountable/types/units_t.py +27 -0
  275. uncountable/types/users.py +8 -0
  276. uncountable/types/users_t.py +28 -0
  277. uncountable/types/webhook_job.py +9 -0
  278. uncountable/types/webhook_job_t.py +37 -0
  279. uncountable/types/workflows.py +9 -0
  280. uncountable/types/workflows_t.py +39 -0
  281. UncountablePythonSDK-0.0.7.dist-info/METADATA +0 -27
  282. UncountablePythonSDK-0.0.7.dist-info/RECORD +0 -119
  283. examples/recipe-import/importer.py +0 -39
  284. type_spec/external/api/batch/execute_batch.yaml +0 -56
  285. type_spec/external/api/entity/create_entities.yaml +0 -33
  286. type_spec/external/api/entity/create_entity.yaml +0 -39
  287. type_spec/external/api/entity/get_entities_data.yaml +0 -55
  288. type_spec/external/api/entity/list_entities.yaml +0 -62
  289. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  290. type_spec/external/api/entity/set_values.yaml +0 -45
  291. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  292. type_spec/external/api/inputs/create_inputs.yaml +0 -61
  293. type_spec/external/api/inputs/get_input_data.yaml +0 -108
  294. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  295. type_spec/external/api/inputs/get_inputs_data.yaml +0 -95
  296. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -37
  297. type_spec/external/api/outputs/get_output_data.yaml +0 -103
  298. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  299. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  300. type_spec/external/api/project/get_projects.yaml +0 -52
  301. type_spec/external/api/project/get_projects_data.yaml +0 -86
  302. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  303. type_spec/external/api/recipes/create_recipes.yaml +0 -60
  304. type_spec/external/api/recipes/get_curve.yaml +0 -50
  305. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -49
  306. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  307. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  308. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -49
  309. type_spec/external/api/recipes/get_recipes_data.yaml +0 -372
  310. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
  311. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -56
@@ -1,19 +1,23 @@
1
1
  import copy
2
2
  import dataclasses
3
+ import decimal
3
4
  import io
4
5
  import json
5
- from typing import Any, Optional, TypeAlias, Union, cast
6
+ from typing import Any, cast
6
7
 
7
- from main.base.types import data_t
8
- from main.base.types.base import PureJsonValue
8
+ from main.base.types import data_t, type_info_t
9
+ from main.base.types.base_t import PureJsonValue
9
10
  from pkgs.argument_parser import CachedParser
10
- from pkgs.serialization_util import serialize_for_api, serialize_for_storage
11
+ from pkgs.serialization_util import (
12
+ serialize_for_api,
13
+ serialize_for_storage,
14
+ )
11
15
 
12
16
  from .. import builder, util
13
17
  from ..emit_typescript_util import MODIFY_NOTICE, ts_name
14
18
  from ..value_spec import convert_to_value_spec_type
15
19
 
16
- ext_info_parser = CachedParser(data_t.ExtInfo)
20
+ ext_info_parser = CachedParser(type_info_t.ExtInfo, strict_property_parsing=True)
17
21
 
18
22
 
19
23
  def type_path_of(stype: builder.SpecType) -> object: # NamePath
@@ -23,7 +27,7 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
23
27
  extended scopes, generics, and enum literal values.
24
28
  - Scoped Type: [ (namespace-string)..., type-string ]
25
29
  - Instance Type: [ "$instance", Scoped-Type-Base, [TypePath-Parameters...] ]
26
- - Literal Type: [ "$literal", [ "$value", value ]... ]
30
+ - Literal Type: [ "$literal", [ "$value", value, value-type-string ]... ]
27
31
 
28
32
  @return (string-specific, multiple-types)
29
33
  """
@@ -34,11 +38,15 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
34
38
 
35
39
  if isinstance(stype, builder.SpecTypeInstance):
36
40
  if stype.defn_type.name == builder.BaseTypeName.s_literal:
37
- parts: list[str | list[str | bool]] = ["$literal"]
41
+ parts: list[object] = ["$literal"]
38
42
  for parameter in stype.parameters:
39
43
  assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
40
44
  # This allows expansion to enum literal values later
41
- parts.append(["$value", parameter.value])
45
+ parts.append([
46
+ "$value",
47
+ parameter.value,
48
+ type_path_of(parameter.value_type),
49
+ ])
42
50
  return parts
43
51
 
44
52
  return [
@@ -65,12 +73,21 @@ def _dict_null_strip(data: dict[str, object]) -> dict[str, object]:
65
73
  }
66
74
 
67
75
 
76
+ class JsonEncoder(json.JSONEncoder):
77
+ """We have some defaults of special types that we need to emit"""
78
+
79
+ def default(self, obj: object) -> object:
80
+ if isinstance(obj, decimal.Decimal):
81
+ return str(obj)
82
+ return json.JSONEncoder.default(self, obj)
83
+
84
+
68
85
  def emit_type_info(build: builder.SpecBuilder, output: str) -> None:
69
86
  type_map = _build_map_all(build)
70
87
 
71
88
  # sort for stability, indent for smaller diffs
72
89
  stripped = _dict_null_strip(dataclasses.asdict(type_map))
73
- serial = json.dumps(stripped, sort_keys=True, indent=2)
90
+ serial = json.dumps(stripped, sort_keys=True, indent=2, cls=JsonEncoder)
74
91
  type_map_out = io.StringIO()
75
92
  type_map_out.write(MODIFY_NOTICE)
76
93
  type_map_out.write(f"export const TYPE_MAP = {serial}")
@@ -117,7 +134,7 @@ class MapStringEnum(MapTypeBase):
117
134
  values: dict[str, str]
118
135
 
119
136
 
120
- MapType: TypeAlias = Union[MapTypeObject, MapTypeAlias, MapStringEnum]
137
+ type MapType = MapTypeObject | MapTypeAlias | MapStringEnum
121
138
 
122
139
 
123
140
  @dataclasses.dataclass
@@ -148,6 +165,129 @@ def _build_map_all(build: builder.SpecBuilder) -> MapAll:
148
165
  return map_all
149
166
 
150
167
 
168
+ @dataclasses.dataclass(kw_only=True)
169
+ class InheritablePropertyParts:
170
+ """This uses only the "soft" information for now, things that aren't relevant
171
+ to the language emitted types. There are some fields that should be inherited
172
+ at that level, but that needs to be done in builder. When that is done, the
173
+ "label" and "desc" could probably be removed from this list."""
174
+
175
+ label: str | None = None
176
+ desc: str | None = None
177
+ ext_info: type_info_t.ExtInfo | None = None
178
+
179
+
180
+ def _extract_inheritable_property_parts(
181
+ stype: builder.SpecTypeDefnObject,
182
+ prop: builder.SpecProperty,
183
+ ) -> InheritablePropertyParts:
184
+ if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefn):
185
+ base_prop = (stype.base.properties or {}).get(prop.name)
186
+ if base_prop is None:
187
+ base_parts = InheritablePropertyParts()
188
+ else:
189
+ base_parts = _extract_inheritable_property_parts(stype.base, base_prop)
190
+ # Layout should not be inherited, as it'd end up hiding properties in the derived type
191
+ if base_parts.ext_info is not None:
192
+ base_parts.ext_info.layout = None
193
+ else:
194
+ base_parts = InheritablePropertyParts()
195
+
196
+ label = prop.label or base_parts.label
197
+ desc = prop.desc or base_parts.desc
198
+ local_ext_info = _parse_ext_info(prop.ext_info)
199
+ if local_ext_info is None:
200
+ ext_info = base_parts.ext_info
201
+ elif base_parts.ext_info is None:
202
+ ext_info = local_ext_info
203
+ else:
204
+ ext_info = dataclasses.replace(
205
+ local_ext_info,
206
+ **{
207
+ field.name: getattr(base_parts.ext_info, field.name)
208
+ for field in dataclasses.fields(type_info_t.ExtInfo)
209
+ if getattr(base_parts.ext_info, field.name) is not None
210
+ },
211
+ )
212
+
213
+ return InheritablePropertyParts(label=label, desc=desc, ext_info=ext_info)
214
+
215
+
216
+ ExtInfoLayout = dict[str, set[str]]
217
+ ALL_FIELDS_GROUP = "*all_fields"
218
+
219
+
220
+ def _extract_and_validate_layout(
221
+ stype: builder.SpecTypeDefnObject,
222
+ ext_info: type_info_t.ExtInfo,
223
+ base_layout: ExtInfoLayout | None,
224
+ ) -> ExtInfoLayout:
225
+ """
226
+ Produce a map of groups to fields, for validation.
227
+ """
228
+ if ext_info.layout is None:
229
+ return {}
230
+ assert stype.properties is not None
231
+
232
+ all_fields_group: set[str] = set()
233
+ layout: ExtInfoLayout = {ALL_FIELDS_GROUP: all_fields_group}
234
+
235
+ for group in ext_info.layout.groups:
236
+ fields = set(group.fields or [])
237
+ for field in fields:
238
+ assert field in stype.properties, f"layout-refers-to-missing-field:{field}"
239
+
240
+ local_ref_name = None
241
+ if group.ref_name is not None:
242
+ assert base_layout is None or base_layout.get(group.ref_name) is None, (
243
+ f"group-name-duplicate-in-base:{group.ref_name}"
244
+ )
245
+ local_ref_name = group.ref_name
246
+
247
+ if group.extends:
248
+ assert base_layout is not None, "missing-base-layout"
249
+ base_group = base_layout.get(group.extends)
250
+ assert base_group is not None, f"missing-base-group:{group.extends}"
251
+ fields.update(base_group)
252
+ local_ref_name = group.extends
253
+
254
+ assert local_ref_name not in layout, f"duplicate-group:{local_ref_name}"
255
+ if local_ref_name is not None:
256
+ layout[local_ref_name] = fields
257
+ all_fields_group.update(fields)
258
+
259
+ for group_ref_name in base_layout or {}:
260
+ assert group_ref_name in layout, f"missing-base-group:{group_ref_name}"
261
+
262
+ for prop_ref_name in stype.properties:
263
+ assert prop_ref_name in all_fields_group, (
264
+ f"layout-missing-field:{prop_ref_name}"
265
+ )
266
+
267
+ return layout
268
+
269
+
270
+ def _validate_type_ext_info(
271
+ stype: builder.SpecTypeDefnObject,
272
+ ) -> tuple[ExtInfoLayout | None, type_info_t.ExtInfo | None]:
273
+ ext_info = _parse_ext_info(stype.ext_info)
274
+ if ext_info is None:
275
+ return None, None
276
+
277
+ if ext_info.label_fields is not None:
278
+ assert stype.properties is not None
279
+ for name in ext_info.label_fields:
280
+ prop = stype.properties.get(name)
281
+ assert prop is not None, f"missing-label-field:{name}"
282
+
283
+ if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefnObject):
284
+ base_layout, _ = _validate_type_ext_info(stype.base)
285
+ else:
286
+ base_layout = None
287
+
288
+ return _extract_and_validate_layout(stype, ext_info, base_layout), ext_info
289
+
290
+
151
291
  def _build_map_type(
152
292
  build: builder.SpecBuilder, stype: builder.SpecTypeDefn
153
293
  ) -> MapType | None:
@@ -158,6 +298,8 @@ def _build_map_type(
158
298
  and not stype.is_base
159
299
  and stype.base is not None
160
300
  ):
301
+ _, ext_info = _validate_type_ext_info(stype)
302
+
161
303
  properties: dict[str, MapProperty] = {}
162
304
  map_type = MapTypeObject(
163
305
  type_name=stype.name,
@@ -165,19 +307,22 @@ def _build_map_type(
165
307
  properties=properties,
166
308
  desc=stype.desc,
167
309
  base_type_path=type_path_of(stype.base),
168
- ext_info=_convert_ext_info(stype.ext_info),
310
+ ext_info=serialize_for_api(ext_info), # type: ignore[arg-type]
169
311
  )
170
312
 
171
313
  if stype.properties is not None:
172
314
  for prop in stype.properties.values():
315
+ parts = _extract_inheritable_property_parts(stype, prop)
316
+ # Propertis can't have layouts
317
+ assert parts.ext_info is None or parts.ext_info.layout is None
173
318
  map_property = MapProperty(
174
319
  type_name=prop.name,
175
- label=prop.label,
320
+ label=parts.label,
176
321
  api_name=ts_name(prop.name, prop.name_case),
177
322
  extant=prop.extant,
178
323
  type_path=type_path_of(prop.spec_type),
179
- ext_info=_convert_ext_info(prop.ext_info),
180
- desc=prop.desc,
324
+ ext_info=serialize_for_api(parts.ext_info), # type: ignore[arg-type]
325
+ desc=parts.desc,
181
326
  default=prop.default,
182
327
  )
183
328
  map_type.properties[prop.name] = map_property
@@ -194,6 +339,19 @@ def _build_map_type(
194
339
  discriminator=stype.discriminator,
195
340
  )
196
341
 
342
+ if isinstance(stype, builder.SpecTypeDefnUnion):
343
+ # Emit as a basic alias for now, as the front-end supports only those for now
344
+ # IMPROVE: We should emit a proper union type and support that
345
+ backing = stype.get_backing_type()
346
+ return MapTypeAlias(
347
+ type_name=stype.name,
348
+ label=stype.label,
349
+ desc=stype.desc,
350
+ alias_type_path=type_path_of(backing),
351
+ ext_info=_convert_ext_info(stype.ext_info),
352
+ discriminator=stype.discriminator,
353
+ )
354
+
197
355
  if isinstance(stype, builder.SpecTypeDefnStringEnum):
198
356
  return MapStringEnum(
199
357
  type_name=stype.name,
@@ -211,7 +369,7 @@ def _build_map_type(
211
369
  return None
212
370
 
213
371
 
214
- def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
372
+ def _parse_ext_info(in_ext: Any) -> type_info_t.ExtInfo | None:
215
373
  if in_ext is None:
216
374
  return None
217
375
  assert isinstance(in_ext, dict)
@@ -229,6 +387,10 @@ def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
229
387
  df["result_type"] = serialize_for_storage(converted)
230
388
  mod_ext["data_format"] = df
231
389
 
232
- parsed = ext_info_parser.parse_storage(mod_ext)
390
+ return ext_info_parser.parse_storage(mod_ext)
391
+
392
+
393
+ def _convert_ext_info(in_ext: Any) -> PureJsonValue | None:
233
394
  # we need to convert this to API storage since it'll be used as-is in the UI
395
+ parsed = _parse_ext_info(in_ext)
234
396
  return cast(PureJsonValue, serialize_for_api(parsed))
pkgs/type_spec/util.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  from dataclasses import dataclass
4
- from typing import Dict, List, Optional, TypeVar, Union
4
+ from typing import TypeVar, Union
5
5
 
6
6
  import regex as re
7
7
 
@@ -29,11 +29,11 @@ LiteralTypeValue = Union[str, bool]
29
29
  class ParsedTypePart:
30
30
  name: str
31
31
  # An empty list is distinct from None
32
- parameters: Optional[List["ParsedTypePath"]] = None
33
- literal_value: Optional[LiteralTypeValue] = None
32
+ parameters: list["ParsedTypePath"] | None = None
33
+ literal_value: LiteralTypeValue | None = None
34
34
 
35
35
 
36
- ParsedTypePath = List[ParsedTypePart]
36
+ ParsedTypePath = list[ParsedTypePart]
37
37
 
38
38
 
39
39
  @dataclass
@@ -48,7 +48,7 @@ def consume_parameter(
48
48
  ) -> ConsumedParameter:
49
49
  if bits[at] != "'":
50
50
  return ConsumedParameter(at=at, part=ParsedTypePart(name=bits[at]))
51
- quote_stack: List[str] = []
51
+ quote_stack: list[str] = []
52
52
  at += 1
53
53
  while at < len(bits):
54
54
  if bits[at] == "'":
@@ -68,7 +68,7 @@ def parse_type_str(type_str: str) -> ParsedTypePath:
68
68
  """
69
69
  IMPROVE: will not detect all errors yet, focuses on correct cases
70
70
  """
71
- raw_bits: List[str] = re.split(r"([.<>,'])", type_str)
71
+ raw_bits: list[str] = re.split(r"([.<>,'])", type_str)
72
72
  bits = [
73
73
  stripped_bit
74
74
  for stripped_bit in (padded_bit.strip() for padded_bit in raw_bits)
@@ -81,7 +81,7 @@ def parse_type_str(type_str: str) -> ParsedTypePath:
81
81
  cur_path = result
82
82
  cur_path.append(cur_part)
83
83
 
84
- path_stack: List[ParsedTypePath] = []
84
+ path_stack: list[ParsedTypePath] = []
85
85
 
86
86
  at = 1
87
87
  while at < len(bits):
@@ -156,19 +156,19 @@ def is_valid_property_name(name: str) -> bool:
156
156
  return re_pattern_property_name.match(name) is not None
157
157
 
158
158
 
159
- def check_fields(data: Dict[str, T], allowed: List[str]) -> None:
159
+ def check_fields(data: dict[str, T], allowed: list[str]) -> None:
160
160
  for key in data:
161
161
  if key not in allowed:
162
- raise Exception(f"unexpected-field: {key}")
162
+ raise Exception(f"unexpected-field: {key}. Allowed: {allowed}")
163
163
 
164
164
 
165
- def split_any_name(name: str) -> List[str]:
165
+ def split_any_name(name: str) -> list[str]:
166
166
  """
167
167
  Splits a name on case and underscores.
168
168
  myName => [my, name]
169
169
  my_name => [my, name]
170
170
  """
171
- bits: List[str] = re_pattern_split_name.split(name)
171
+ bits: list[str] = re_pattern_split_name.split(name)
172
172
  return [s.lower() for s in filter(lambda x: x is not None and x != "_", bits)]
173
173
 
174
174
 
@@ -17,12 +17,12 @@ If null is allowed as a legitimate value, such as in conditionals like `if`, the
17
17
  """
18
18
 
19
19
  import sys
20
- from typing import Type, TypeVar, cast
20
+ from typing import TypeVar, cast
21
21
 
22
22
  import regex as re
23
- import yaml
24
23
 
25
24
  from main.base.types import base_t, value_spec_t
25
+ from pkgs.serialization import yaml
26
26
 
27
27
  from ..util import parse_type_str, rewrite_file
28
28
  from .convert_type import convert_to_value_spec_type
@@ -177,7 +177,7 @@ def main() -> None:
177
177
  raise Exception(f"missing-{key}:{get_where()}")
178
178
  return cast(base_t.PureJsonValue, x)
179
179
 
180
- def get_as(node: base_t.PureJsonValue, key: str, type_: Type[TypeT]) -> TypeT:
180
+ def get_as(node: base_t.PureJsonValue, key: str, type_: type[TypeT]) -> TypeT:
181
181
  raw = get(node, key)
182
182
  assert isinstance(raw, type_)
183
183
 
@@ -25,10 +25,17 @@ TYPE_MAP = {
25
25
  "List": MappedType(base_type=value_spec_t.BaseType.LIST, param_count=1),
26
26
  "Optional": MappedType(base_type=value_spec_t.BaseType.OPTIONAL, param_count=1),
27
27
  "String": MappedType(base_type=value_spec_t.BaseType.STRING),
28
- "Union": MappedType(base_type=value_spec_t.BaseType.UNION, variable_param_count=True),
28
+ "Union": MappedType(
29
+ base_type=value_spec_t.BaseType.UNION, variable_param_count=True
30
+ ),
29
31
  # not part of type_spec's types now
30
32
  "Symbol": MappedType(base_type=value_spec_t.BaseType.SYMBOL),
31
33
  "Any": MappedType(base_type=value_spec_t.BaseType.ANY),
34
+ "None": MappedType(base_type=value_spec_t.BaseType.NONE),
35
+ "Tuple": MappedType(
36
+ base_type=value_spec_t.BaseType.TUPLE, variable_param_count=True
37
+ ),
38
+ "Never": MappedType(base_type=value_spec_t.BaseType.NEVER),
32
39
  }
33
40
 
34
41
 
@@ -22,6 +22,7 @@ def emit_functions(functions: list[value_spec_t.Function]) -> str:
22
22
  out.write(
23
23
  f"""{MODIFY_NOTICE}
24
24
  {LINT_HEADER}
25
+ import datetime
25
26
  from typing import cast, Union
26
27
 
27
28
  from decimal import Decimal
@@ -159,7 +160,9 @@ def _emit_function(function: value_spec_t.Function, indent: str) -> str:
159
160
  sub_indent = indent + INDENT
160
161
  out.write(f"{_function_symbol_name(function)} = value_spec_t.Function(\n")
161
162
  out.write(f"{sub_indent}name={encode_common_string(function.name)},\n")
162
- out.write(f"{sub_indent}description={encode_common_string(function.description)},\n")
163
+ out.write(
164
+ f"{sub_indent}description={encode_common_string(function.description)},\n"
165
+ )
163
166
  out.write(f"{sub_indent}brief={encode_common_string(function.brief)},\n")
164
167
  out.write(
165
168
  f"{sub_indent}return_value={_emit_function_return(function.return_value, sub_indent)},\n"
@@ -183,16 +186,22 @@ def _emit_argument(argument: value_spec_t.FunctionArgument, indent: str) -> str:
183
186
  out.write("value_spec_t.FunctionArgument(\n")
184
187
  out.write(f"{sub_indent}ref_name={encode_common_string(argument.ref_name)},\n")
185
188
  out.write(f"{sub_indent}name={encode_common_string(argument.name)},\n")
186
- out.write(f"{sub_indent}description={encode_common_string(argument.description)},\n")
189
+ out.write(
190
+ f"{sub_indent}description={encode_common_string(argument.description)},\n"
191
+ )
187
192
  out.write(f"{sub_indent}pass_null={str(argument.pass_null)},\n")
188
- out.write(f"{sub_indent}extant=value_spec_t.ArgumentExtant.{argument.extant.name},\n")
193
+ out.write(
194
+ f"{sub_indent}extant=value_spec_t.ArgumentExtant.{argument.extant.name},\n"
195
+ )
189
196
  out.write(f"{sub_indent}type={_emit_type(argument.type, sub_indent)},\n")
190
197
  out.write(f"{indent})")
191
198
 
192
199
  return out.getvalue()
193
200
 
194
201
 
195
- def _emit_function_return(return_value: value_spec_t.FunctionReturn, indent: str) -> str:
202
+ def _emit_function_return(
203
+ return_value: value_spec_t.FunctionReturn, indent: str
204
+ ) -> str:
196
205
  out = io.StringIO()
197
206
 
198
207
  sub_indent = indent + INDENT
uncountable/__init__.py CHANGED
@@ -1,5 +1,4 @@
1
- from . import types
2
- from . import core
1
+ from . import core, types
3
2
 
4
3
  __all__ = []
5
4
  __all__ += types.__all__
@@ -1,3 +1,13 @@
1
- from .client import AuthDetailsApiKey, Client
1
+ from .async_batch import AsyncBatchProcessor
2
+ from .client import Client
3
+ from .file_upload import MediaFileUpload, UploadedFile
4
+ from .types import AuthDetailsApiKey, AuthDetailsOAuth
2
5
 
3
- __all__: list[str] = ["AuthDetailsApiKey", "Client"]
6
+ __all__: list[str] = [
7
+ "AuthDetailsApiKey",
8
+ "AuthDetailsOAuth",
9
+ "AsyncBatchProcessor",
10
+ "Client",
11
+ "MediaFileUpload",
12
+ "UploadedFile",
13
+ ]
@@ -0,0 +1,37 @@
1
+ from uncountable.core.client import Client
2
+ from uncountable.types import async_batch_t, base_t
3
+ from uncountable.types.async_batch import AsyncBatchRequest
4
+ from uncountable.types.async_batch_processor import AsyncBatchProcessorBase
5
+
6
+
7
+ class AsyncBatchSubmissionError(Exception):
8
+ pass
9
+
10
+
11
+ class AsyncBatchProcessor(AsyncBatchProcessorBase):
12
+ _client: Client
13
+ _queue: list[AsyncBatchRequest]
14
+ _submitted_job_ids: list[base_t.ObjectId]
15
+
16
+ def __init__(self, *, client: Client) -> None:
17
+ super().__init__()
18
+ self._client = client
19
+ self._queue = []
20
+ self._submitted_job_ids = []
21
+
22
+ def _enqueue(self, req: async_batch_t.AsyncBatchRequest) -> None:
23
+ self._queue.append(req)
24
+
25
+ def current_queue_size(self) -> int:
26
+ return len(self._queue)
27
+
28
+ def send(self) -> base_t.ObjectId:
29
+ if len(self._queue) == 0:
30
+ raise AsyncBatchSubmissionError("queue is empty")
31
+ job_id = self._client.execute_batch_load_async(requests=self._queue).job_id
32
+ self._submitted_job_ids.append(job_id)
33
+ self._queue = []
34
+ return job_id
35
+
36
+ def get_submitted_job_ids(self) -> list[base_t.ObjectId]:
37
+ return self._submitted_job_ids