UncountablePythonSDK 0.0.24__py3-none-any.whl → 0.0.131__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 (373) hide show
  1. docs/conf.py +60 -8
  2. docs/index.md +107 -4
  3. docs/integration_examples/create_ingredient.md +43 -0
  4. docs/integration_examples/create_output.md +56 -0
  5. docs/integration_examples/index.md +6 -0
  6. docs/justfile +2 -2
  7. docs/requirements.txt +7 -5
  8. examples/async_batch.py +5 -6
  9. examples/basic_auth.py +7 -0
  10. examples/create_entity.py +4 -6
  11. examples/create_ingredient_sdk.py +34 -0
  12. examples/download_files.py +26 -0
  13. examples/edit_recipe_inputs.py +50 -0
  14. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  15. examples/integration-server/jobs/materials_auto/example_cron.py +21 -0
  16. examples/integration-server/jobs/materials_auto/example_http.py +47 -0
  17. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  18. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  19. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  20. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
  21. examples/integration-server/jobs/materials_auto/example_wh.py +23 -0
  22. examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
  23. examples/integration-server/pyproject.toml +224 -0
  24. examples/invoke_uploader.py +26 -0
  25. examples/oauth.py +7 -0
  26. examples/set_recipe_metadata_file.py +40 -0
  27. examples/set_recipe_output_file_sdk.py +26 -0
  28. examples/upload_files.py +2 -3
  29. pkgs/argument_parser/__init__.py +9 -0
  30. pkgs/argument_parser/_is_namedtuple.py +3 -0
  31. pkgs/argument_parser/argument_parser.py +295 -74
  32. pkgs/argument_parser/case_convert.py +4 -3
  33. pkgs/filesystem_utils/__init__.py +20 -0
  34. pkgs/filesystem_utils/_blob_session.py +144 -0
  35. pkgs/filesystem_utils/_gdrive_session.py +309 -0
  36. pkgs/filesystem_utils/_local_session.py +69 -0
  37. pkgs/filesystem_utils/_s3_session.py +118 -0
  38. pkgs/filesystem_utils/_sftp_session.py +151 -0
  39. pkgs/filesystem_utils/file_type_utils.py +91 -0
  40. pkgs/filesystem_utils/filesystem_session.py +39 -0
  41. pkgs/py.typed +0 -0
  42. pkgs/serialization/__init__.py +8 -1
  43. pkgs/serialization/annotation.py +64 -0
  44. pkgs/serialization/missing_sentry.py +1 -1
  45. pkgs/serialization/opaque_key.py +1 -1
  46. pkgs/serialization/serial_alias.py +47 -0
  47. pkgs/serialization/serial_class.py +69 -54
  48. pkgs/serialization/serial_generic.py +16 -0
  49. pkgs/serialization/serial_union.py +84 -0
  50. pkgs/serialization/yaml.py +57 -0
  51. pkgs/serialization_util/__init__.py +7 -7
  52. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  53. pkgs/serialization_util/dataclasses.py +14 -0
  54. pkgs/serialization_util/serialization_helpers.py +117 -71
  55. pkgs/type_spec/actions_registry/__main__.py +0 -4
  56. pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
  57. pkgs/type_spec/builder.py +438 -109
  58. pkgs/type_spec/builder_types.py +9 -0
  59. pkgs/type_spec/config.py +52 -24
  60. pkgs/type_spec/cross_output_links.py +99 -0
  61. pkgs/type_spec/emit_io_ts.py +1 -1
  62. pkgs/type_spec/emit_open_api.py +160 -41
  63. pkgs/type_spec/emit_open_api_util.py +13 -7
  64. pkgs/type_spec/emit_python.py +450 -136
  65. pkgs/type_spec/emit_typescript.py +117 -250
  66. pkgs/type_spec/emit_typescript_util.py +293 -4
  67. pkgs/type_spec/load_types.py +20 -5
  68. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  69. pkgs/type_spec/open_api_util.py +29 -4
  70. pkgs/type_spec/parts/base.py.prepart +13 -10
  71. pkgs/type_spec/parts/base.ts.prepart +4 -0
  72. pkgs/type_spec/type_info/__main__.py +3 -1
  73. pkgs/type_spec/type_info/emit_type_info.py +161 -32
  74. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  75. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  76. pkgs/type_spec/util.py +4 -4
  77. pkgs/type_spec/value_spec/__main__.py +27 -10
  78. pkgs/type_spec/value_spec/convert_type.py +21 -1
  79. pkgs/type_spec/value_spec/emit_python.py +25 -7
  80. pkgs/type_spec/value_spec/types.py +1 -1
  81. uncountable/__init__.py +1 -2
  82. uncountable/core/__init__.py +11 -3
  83. uncountable/core/async_batch.py +16 -1
  84. uncountable/core/client.py +247 -52
  85. uncountable/core/environment.py +41 -0
  86. uncountable/core/file_upload.py +67 -22
  87. uncountable/core/types.py +8 -13
  88. uncountable/integration/cli.py +142 -0
  89. uncountable/integration/construct_client.py +43 -27
  90. uncountable/integration/cron.py +12 -11
  91. uncountable/integration/db/connect.py +12 -2
  92. uncountable/integration/db/session.py +25 -0
  93. uncountable/integration/entrypoint.py +4 -34
  94. uncountable/integration/executors/executors.py +147 -0
  95. uncountable/integration/executors/generic_upload_executor.py +336 -0
  96. uncountable/integration/executors/script_executor.py +15 -9
  97. uncountable/integration/http_server/__init__.py +5 -0
  98. uncountable/integration/http_server/types.py +69 -0
  99. uncountable/integration/job.py +246 -19
  100. uncountable/integration/queue_runner/__init__.py +0 -0
  101. uncountable/integration/queue_runner/command_server/__init__.py +28 -0
  102. uncountable/integration/queue_runner/command_server/command_client.py +133 -0
  103. uncountable/integration/queue_runner/command_server/command_server.py +142 -0
  104. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  105. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
  108. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
  109. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
  110. uncountable/integration/queue_runner/command_server/types.py +75 -0
  111. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  112. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
  113. uncountable/integration/queue_runner/datastore/interface.py +29 -0
  114. uncountable/integration/queue_runner/datastore/model.py +24 -0
  115. uncountable/integration/queue_runner/job_scheduler.py +200 -0
  116. uncountable/integration/queue_runner/queue_runner.py +34 -0
  117. uncountable/integration/queue_runner/types.py +7 -0
  118. uncountable/integration/queue_runner/worker.py +116 -0
  119. uncountable/integration/scan_profiles.py +67 -0
  120. uncountable/integration/scheduler.py +199 -0
  121. uncountable/integration/secret_retrieval/__init__.py +3 -0
  122. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  123. uncountable/integration/server.py +103 -54
  124. uncountable/integration/telemetry.py +251 -0
  125. uncountable/integration/webhook_server/entrypoint.py +97 -0
  126. uncountable/types/__init__.py +149 -30
  127. uncountable/types/api/batch/execute_batch.py +16 -9
  128. uncountable/types/api/batch/execute_batch_load_async.py +13 -7
  129. uncountable/types/api/chemical/convert_chemical_formats.py +20 -8
  130. uncountable/types/api/condition_parameters/__init__.py +1 -0
  131. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  132. uncountable/types/api/entity/create_entities.py +24 -12
  133. uncountable/types/api/entity/create_entity.py +22 -13
  134. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  135. uncountable/types/api/entity/export_entities.py +59 -0
  136. uncountable/types/api/entity/get_entities_data.py +18 -9
  137. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  138. uncountable/types/api/entity/list_aggregate.py +79 -0
  139. uncountable/types/api/entity/list_entities.py +53 -14
  140. uncountable/types/api/entity/lock_entity.py +45 -0
  141. uncountable/types/api/entity/lookup_entity.py +116 -0
  142. uncountable/types/api/entity/resolve_entity_ids.py +19 -10
  143. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  144. uncountable/types/api/entity/set_values.py +15 -8
  145. uncountable/types/api/entity/transition_entity_phase.py +27 -12
  146. uncountable/types/api/entity/unlock_entity.py +44 -0
  147. uncountable/types/api/equipment/__init__.py +1 -0
  148. uncountable/types/api/equipment/associate_equipment_input.py +43 -0
  149. uncountable/types/api/field_options/__init__.py +1 -0
  150. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  151. uncountable/types/api/files/__init__.py +1 -0
  152. uncountable/types/api/files/download_file.py +77 -0
  153. uncountable/types/api/id_source/list_id_source.py +20 -11
  154. uncountable/types/api/id_source/match_id_source.py +15 -10
  155. uncountable/types/api/input_groups/get_input_group_names.py +16 -7
  156. uncountable/types/api/inputs/create_inputs.py +28 -14
  157. uncountable/types/api/inputs/get_input_data.py +34 -16
  158. uncountable/types/api/inputs/get_input_names.py +19 -10
  159. uncountable/types/api/inputs/get_inputs_data.py +29 -11
  160. uncountable/types/api/inputs/set_input_attribute_values.py +16 -10
  161. uncountable/types/api/inputs/set_input_category.py +44 -0
  162. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  163. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  164. uncountable/types/api/integrations/__init__.py +1 -0
  165. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  166. uncountable/types/api/integrations/push_notification.py +49 -0
  167. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  168. uncountable/types/api/listing/__init__.py +1 -0
  169. uncountable/types/api/listing/fetch_listing.py +58 -0
  170. uncountable/types/api/material_families/__init__.py +1 -0
  171. uncountable/types/api/material_families/update_entity_material_families.py +47 -0
  172. uncountable/types/api/notebooks/__init__.py +1 -0
  173. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  174. uncountable/types/api/outputs/get_output_data.py +32 -17
  175. uncountable/types/api/outputs/get_output_names.py +18 -9
  176. uncountable/types/api/outputs/get_output_organization.py +173 -0
  177. uncountable/types/api/outputs/resolve_output_conditions.py +23 -11
  178. uncountable/types/api/permissions/set_core_permissions.py +31 -15
  179. uncountable/types/api/project/get_projects.py +20 -11
  180. uncountable/types/api/project/get_projects_data.py +23 -14
  181. uncountable/types/api/recipe_links/create_recipe_link.py +17 -10
  182. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  183. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +19 -10
  184. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  185. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  186. uncountable/types/api/recipes/archive_recipes.py +14 -7
  187. uncountable/types/api/recipes/associate_recipe_as_input.py +16 -8
  188. uncountable/types/api/recipes/associate_recipe_as_lot.py +14 -7
  189. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  190. uncountable/types/api/recipes/create_mix_order.py +44 -0
  191. uncountable/types/api/recipes/create_recipe.py +21 -14
  192. uncountable/types/api/recipes/create_recipes.py +25 -13
  193. uncountable/types/api/recipes/disassociate_recipe_as_input.py +14 -7
  194. uncountable/types/api/recipes/edit_recipe_inputs.py +208 -19
  195. uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
  196. uncountable/types/api/recipes/get_curve.py +15 -9
  197. uncountable/types/api/recipes/get_recipe_calculations.py +17 -11
  198. uncountable/types/api/recipes/get_recipe_links.py +14 -8
  199. uncountable/types/api/recipes/get_recipe_names.py +16 -7
  200. uncountable/types/api/recipes/get_recipe_output_metadata.py +16 -10
  201. uncountable/types/api/recipes/get_recipes_data.py +96 -45
  202. uncountable/types/api/recipes/lock_recipes.py +64 -0
  203. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  204. uncountable/types/api/recipes/set_recipe_inputs.py +19 -13
  205. uncountable/types/api/recipes/set_recipe_metadata.py +14 -7
  206. uncountable/types/api/recipes/set_recipe_output_annotations.py +114 -0
  207. uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
  208. uncountable/types/api/recipes/set_recipe_outputs.py +40 -15
  209. uncountable/types/api/recipes/set_recipe_tags.py +30 -13
  210. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  211. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  212. uncountable/types/api/recipes/unlock_recipes.py +51 -0
  213. uncountable/types/api/runsheet/__init__.py +1 -0
  214. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  215. uncountable/types/api/triggers/run_trigger.py +15 -8
  216. uncountable/types/api/uploader/__init__.py +1 -0
  217. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  218. uncountable/types/api/uploader/invoke_uploader.py +46 -0
  219. uncountable/types/api/user/__init__.py +1 -0
  220. uncountable/types/api/user/get_current_user_info.py +40 -0
  221. uncountable/types/async_batch.py +8 -52
  222. uncountable/types/async_batch_processor.py +694 -18
  223. uncountable/types/async_batch_t.py +108 -0
  224. uncountable/types/async_jobs.py +8 -0
  225. uncountable/types/async_jobs_t.py +52 -0
  226. uncountable/types/auth_retrieval.py +11 -0
  227. uncountable/types/auth_retrieval_t.py +75 -0
  228. uncountable/types/base.py +5 -80
  229. uncountable/types/base_t.py +87 -0
  230. uncountable/types/calculations.py +3 -19
  231. uncountable/types/calculations_t.py +26 -0
  232. uncountable/types/chemical_structure.py +3 -23
  233. uncountable/types/chemical_structure_t.py +28 -0
  234. uncountable/types/client_base.py +1170 -88
  235. uncountable/types/client_config.py +8 -0
  236. uncountable/types/client_config_t.py +36 -0
  237. uncountable/types/curves.py +5 -43
  238. uncountable/types/curves_t.py +50 -0
  239. uncountable/types/data.py +12 -0
  240. uncountable/types/data_t.py +103 -0
  241. uncountable/types/entity.py +8 -270
  242. uncountable/types/entity_t.py +446 -0
  243. uncountable/types/experiment_groups.py +3 -19
  244. uncountable/types/experiment_groups_t.py +26 -0
  245. uncountable/types/exports.py +8 -0
  246. uncountable/types/exports_t.py +34 -0
  247. uncountable/types/field_values.py +25 -61
  248. uncountable/types/field_values_t.py +302 -0
  249. uncountable/types/fields.py +3 -20
  250. uncountable/types/fields_t.py +27 -0
  251. uncountable/types/generic_upload.py +14 -0
  252. uncountable/types/generic_upload_t.py +119 -0
  253. uncountable/types/id_source.py +7 -45
  254. uncountable/types/id_source_t.py +68 -0
  255. uncountable/types/identifier.py +6 -50
  256. uncountable/types/identifier_t.py +62 -0
  257. uncountable/types/input_attributes.py +3 -25
  258. uncountable/types/input_attributes_t.py +29 -0
  259. uncountable/types/inputs.py +6 -57
  260. uncountable/types/inputs_t.py +82 -0
  261. uncountable/types/integration_server.py +8 -0
  262. uncountable/types/integration_server_t.py +46 -0
  263. uncountable/types/integration_session.py +10 -0
  264. uncountable/types/integration_session_t.py +60 -0
  265. uncountable/types/integrations.py +10 -0
  266. uncountable/types/integrations_t.py +62 -0
  267. uncountable/types/job_definition.py +28 -0
  268. uncountable/types/job_definition_t.py +285 -0
  269. uncountable/types/listing.py +9 -0
  270. uncountable/types/listing_t.py +51 -0
  271. uncountable/types/notices.py +8 -0
  272. uncountable/types/notices_t.py +37 -0
  273. uncountable/types/notifications.py +11 -0
  274. uncountable/types/notifications_t.py +74 -0
  275. uncountable/types/outputs.py +3 -22
  276. uncountable/types/outputs_t.py +29 -0
  277. uncountable/types/overrides.py +9 -0
  278. uncountable/types/overrides_t.py +49 -0
  279. uncountable/types/permissions.py +3 -42
  280. uncountable/types/permissions_t.py +45 -0
  281. uncountable/types/phases.py +3 -19
  282. uncountable/types/phases_t.py +26 -0
  283. uncountable/types/post_base.py +3 -26
  284. uncountable/types/post_base_t.py +29 -0
  285. uncountable/types/queued_job.py +17 -0
  286. uncountable/types/queued_job_t.py +140 -0
  287. uncountable/types/recipe_identifiers.py +7 -58
  288. uncountable/types/recipe_identifiers_t.py +75 -0
  289. uncountable/types/recipe_inputs.py +4 -26
  290. uncountable/types/recipe_inputs_t.py +29 -0
  291. uncountable/types/recipe_links.py +4 -46
  292. uncountable/types/recipe_links_t.py +53 -0
  293. uncountable/types/recipe_metadata.py +5 -48
  294. uncountable/types/recipe_metadata_t.py +57 -0
  295. uncountable/types/recipe_output_metadata.py +3 -20
  296. uncountable/types/recipe_output_metadata_t.py +27 -0
  297. uncountable/types/recipe_tags.py +3 -19
  298. uncountable/types/recipe_tags_t.py +26 -0
  299. uncountable/types/recipe_workflow_steps.py +9 -73
  300. uncountable/types/recipe_workflow_steps_t.py +95 -0
  301. uncountable/types/recipes.py +7 -0
  302. uncountable/types/recipes_t.py +25 -0
  303. uncountable/types/response.py +3 -21
  304. uncountable/types/response_t.py +26 -0
  305. uncountable/types/secret_retrieval.py +11 -0
  306. uncountable/types/secret_retrieval_t.py +75 -0
  307. uncountable/types/sockets.py +20 -0
  308. uncountable/types/sockets_t.py +169 -0
  309. uncountable/types/structured_filters.py +25 -0
  310. uncountable/types/structured_filters_t.py +248 -0
  311. uncountable/types/units.py +3 -19
  312. uncountable/types/units_t.py +26 -0
  313. uncountable/types/uploader.py +24 -0
  314. uncountable/types/uploader_t.py +222 -0
  315. uncountable/types/users.py +3 -20
  316. uncountable/types/users_t.py +27 -0
  317. uncountable/types/webhook_job.py +9 -0
  318. uncountable/types/webhook_job_t.py +48 -0
  319. uncountable/types/workflows.py +4 -28
  320. uncountable/types/workflows_t.py +38 -0
  321. uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
  322. uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
  323. {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
  324. {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -1
  325. UncountablePythonSDK-0.0.24.dist-info/METADATA +0 -47
  326. UncountablePythonSDK-0.0.24.dist-info/RECORD +0 -216
  327. docs/quickstart.md +0 -19
  328. examples/recipe-import/importer.py +0 -39
  329. type_spec/external/api/batch/execute_batch.yaml +0 -56
  330. type_spec/external/api/batch/execute_batch_load_async.yaml +0 -18
  331. type_spec/external/api/chemical/convert_chemical_formats.yaml +0 -33
  332. type_spec/external/api/entity/create_entities.yaml +0 -45
  333. type_spec/external/api/entity/create_entity.yaml +0 -51
  334. type_spec/external/api/entity/get_entities_data.yaml +0 -29
  335. type_spec/external/api/entity/list_entities.yaml +0 -52
  336. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  337. type_spec/external/api/entity/set_values.yaml +0 -18
  338. type_spec/external/api/entity/transition_entity_phase.yaml +0 -44
  339. type_spec/external/api/id_source/list_id_source.yaml +0 -35
  340. type_spec/external/api/id_source/match_id_source.yaml +0 -32
  341. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  342. type_spec/external/api/inputs/create_inputs.yaml +0 -48
  343. type_spec/external/api/inputs/get_input_data.yaml +0 -95
  344. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  345. type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
  346. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
  347. type_spec/external/api/outputs/get_output_data.yaml +0 -92
  348. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  349. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  350. type_spec/external/api/permissions/set_core_permissions.yaml +0 -69
  351. type_spec/external/api/project/get_projects.yaml +0 -42
  352. type_spec/external/api/project/get_projects_data.yaml +0 -50
  353. type_spec/external/api/recipe_links/create_recipe_link.yaml +0 -25
  354. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  355. type_spec/external/api/recipes/archive_recipes.yaml +0 -20
  356. type_spec/external/api/recipes/associate_recipe_as_input.yaml +0 -19
  357. type_spec/external/api/recipes/associate_recipe_as_lot.yaml +0 -19
  358. type_spec/external/api/recipes/create_recipe.yaml +0 -39
  359. type_spec/external/api/recipes/create_recipes.yaml +0 -47
  360. type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +0 -16
  361. type_spec/external/api/recipes/edit_recipe_inputs.yaml +0 -85
  362. type_spec/external/api/recipes/get_curve.yaml +0 -21
  363. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
  364. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  365. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  366. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
  367. type_spec/external/api/recipes/get_recipes_data.yaml +0 -244
  368. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -42
  369. type_spec/external/api/recipes/set_recipe_metadata.yaml +0 -20
  370. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
  371. type_spec/external/api/recipes/set_recipe_tags.yaml +0 -62
  372. type_spec/external/api/triggers/run_trigger.yaml +0 -18
  373. uncountable/integration/types.py +0 -89
@@ -1,22 +1,33 @@
1
1
  import base64
2
- import json
2
+ import datetime
3
+ import re
3
4
  import typing
4
5
  from dataclasses import dataclass
6
+ from datetime import UTC, timedelta
5
7
  from enum import StrEnum
6
- from urllib.parse import urljoin
8
+ from io import BytesIO
9
+ from urllib.parse import unquote, urljoin
10
+ from uuid import uuid4
7
11
 
8
12
  import requests
13
+ import simplejson as json
14
+ from opentelemetry.sdk.resources import Attributes
9
15
  from requests.exceptions import JSONDecodeError
10
16
 
11
17
  from pkgs.argument_parser import CachedParser
12
- from pkgs.serialization_util import serialize_for_api
13
- from pkgs.serialization_util.serialization_helpers import JsonValue
18
+ from pkgs.serialization_util import JsonValue, serialize_for_api
19
+ from uncountable.core.environment import get_version
20
+ from uncountable.integration.telemetry import Logger, push_scope_optional
21
+ from uncountable.types import download_file_t
14
22
  from uncountable.types.client_base import APIRequest, ClientMethods
23
+ from uncountable.types.client_config import ClientConfigOptions
15
24
 
16
25
  from .file_upload import FileUpload, FileUploader, UploadedFile
17
- from .types import AuthDetails, AuthDetailsApiKey
26
+ from .types import AuthDetailsAll, AuthDetailsApiKey, AuthDetailsOAuth
18
27
 
19
28
  DT = typing.TypeVar("DT")
29
+ UNC_REQUEST_ID_HEADER = "X-UNC-REQUEST-ID"
30
+ UNC_SDK_VERSION_HEADER = "X-UNC-SDK-VERSION"
20
31
 
21
32
 
22
33
  class EndpointMethod(StrEnum):
@@ -29,47 +40,58 @@ class HTTPRequestBase:
29
40
  method: EndpointMethod
30
41
  url: str
31
42
  headers: dict[str, str]
32
- body: typing.Optional[typing.Union[str, dict[str, str]]] = None
33
- query_params: typing.Optional[dict[str, str]] = None
34
43
 
35
44
 
36
45
  @dataclass(kw_only=True)
37
46
  class HTTPGetRequest(HTTPRequestBase):
38
- method: typing.Literal[EndpointMethod.GET]
47
+ method: EndpointMethod = EndpointMethod.GET
39
48
  query_params: dict[str, str]
40
49
 
41
50
 
42
51
  @dataclass(kw_only=True)
43
52
  class HTTPPostRequest(HTTPRequestBase):
44
- method: typing.Literal[EndpointMethod.POST]
45
- body: typing.Union[str, dict[str, str]]
53
+ method: EndpointMethod = EndpointMethod.POST
54
+ body: str | dict[str, str]
46
55
 
47
56
 
48
57
  HTTPRequest = HTTPPostRequest | HTTPGetRequest
49
58
 
50
59
 
51
-
52
60
  @dataclass(kw_only=True)
53
- class ClientConfig():
54
- allow_insecure_tls: bool = False
55
-
61
+ class ClientConfig(ClientConfigOptions):
62
+ transform_request: typing.Callable[[requests.Request], requests.Request] | None = (
63
+ None
64
+ )
65
+ logger: Logger | None = None
66
+
67
+
68
+ OAUTH_REFRESH_WINDOW_SECONDS = 60 * 5
56
69
 
57
- class APIResponseError(BaseException):
70
+
71
+ class APIResponseError(Exception):
58
72
  status_code: int
59
73
  message: str
60
74
  extra_details: dict[str, JsonValue] | None
61
75
 
62
76
  def __init__(
63
- self, status_code: int, message: str, extra_details: dict[str, JsonValue] | None
77
+ self,
78
+ status_code: int,
79
+ message: str,
80
+ extra_details: dict[str, JsonValue] | None,
81
+ request_id: str,
64
82
  ) -> None:
65
83
  super().__init__(status_code, message, extra_details)
66
84
  self.status_code = status_code
67
85
  self.message = message
68
86
  self.extra_details = extra_details
87
+ self.request_id = request_id
69
88
 
70
89
  @classmethod
71
90
  def construct_error(
72
- cls, status_code: int, extra_details: dict[str, JsonValue] | None
91
+ cls,
92
+ status_code: int,
93
+ extra_details: dict[str, JsonValue] | None,
94
+ request_id: str,
73
95
  ) -> "APIResponseError":
74
96
  message: str
75
97
  match status_code:
@@ -92,77 +114,197 @@ class APIResponseError(BaseException):
92
114
  case _:
93
115
  message = "unknown error"
94
116
  return APIResponseError(
95
- status_code=status_code, message=message, extra_details=extra_details
117
+ status_code=status_code,
118
+ message=message,
119
+ extra_details=extra_details,
120
+ request_id=request_id,
96
121
  )
97
122
 
123
+ def __str__(self) -> str:
124
+ details_obj = {
125
+ "request_id": self.request_id,
126
+ "status_code": self.status_code,
127
+ "extra_details": self.extra_details,
128
+ }
129
+ details = json.dumps(details_obj)
130
+ return f"API response error ({self.status_code}): '{self.message}'. Details: {details}"
131
+
98
132
 
99
- class SDKError(BaseException):
133
+ class SDKError(Exception):
100
134
  message: str
135
+ request_id: str
101
136
 
102
- def __init__(self, message: str) -> None:
137
+ def __init__(self, message: str, *, request_id: str) -> None:
103
138
  super().__init__(message)
104
139
  self.message = message
140
+ self.request_id = request_id
105
141
 
106
142
  def __str__(self) -> str:
107
- return f"internal SDK error, please contact Uncountable support: {self.message}"
143
+ return f"internal SDK error (request id {self.request_id}), please contact Uncountable support: {self.message}"
144
+
145
+
146
+ @dataclass(kw_only=True)
147
+ class OAuthBearerTokenCache:
148
+ token: str
149
+ expires_at: datetime.datetime
150
+
151
+
152
+ @dataclass(kw_only=True)
153
+ class GetOauthBearerTokenData:
154
+ access_token: str
155
+ expires_in: int
156
+ token_type: str
157
+ scope: str
158
+
159
+
160
+ oauth_bearer_token_data_parser = CachedParser(GetOauthBearerTokenData)
161
+
162
+
163
+ @dataclass
164
+ class DownloadedFile:
165
+ name: str
166
+ size: int
167
+ data: BytesIO
168
+
169
+
170
+ DownloadedFiles = list[DownloadedFile]
108
171
 
109
172
 
110
173
  class Client(ClientMethods):
111
174
  _parser_map: dict[type, CachedParser] = {}
112
- _auth_details: AuthDetails
175
+ _auth_details: AuthDetailsAll
113
176
  _base_url: str
114
177
  _file_uploader: FileUploader
115
178
  _cfg: ClientConfig
179
+ _oauth_bearer_token_cache: OAuthBearerTokenCache | None = None
180
+ _session: requests.Session
116
181
 
117
- def __init__(self, *, base_url: str, auth_details: AuthDetails, config: ClientConfig | None = None):
182
+ def __init__(
183
+ self,
184
+ *,
185
+ base_url: str,
186
+ auth_details: AuthDetailsAll,
187
+ config: ClientConfig | None = None,
188
+ ):
118
189
  self._auth_details = auth_details
119
190
  self._base_url = base_url
120
- self._file_uploader = FileUploader(self._base_url, self._auth_details)
121
191
  self._cfg = config or ClientConfig()
192
+ self._session = requests.Session()
193
+ self._session.verify = not self._cfg.allow_insecure_tls
194
+ self._file_uploader = FileUploader(
195
+ self._base_url,
196
+ self._auth_details,
197
+ self._cfg.allow_insecure_tls,
198
+ logger=self._cfg.logger,
199
+ )
122
200
 
123
- def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
124
- http_request = self._build_http_request(api_request=api_request)
125
- match http_request:
126
- case HTTPGetRequest():
127
- response = requests.get(
128
- http_request.url,
129
- headers=http_request.headers,
130
- params=http_request.query_params,
131
- verify=not self._cfg.allow_insecure_tls
132
- )
133
- case HTTPPostRequest():
134
- response = requests.post(
135
- http_request.url,
136
- headers=http_request.headers,
137
- data=http_request.body,
138
- params=http_request.query_params,
139
- verify=not self._cfg.allow_insecure_tls
140
- )
141
- case _:
142
- typing.assert_never(http_request)
201
+ @classmethod
202
+ def _validate_response_status(
203
+ cls, response: requests.Response, request_id: str
204
+ ) -> None:
143
205
  if response.status_code < 200 or response.status_code > 299:
144
206
  extra_details: dict[str, JsonValue] | None = None
145
207
  try:
146
208
  data = response.json()
147
- if "error" in data:
148
- extra_details = data["error"]
209
+ extra_details = data
149
210
  except JSONDecodeError:
150
- pass
211
+ extra_details = {
212
+ "body": response.text,
213
+ }
151
214
  raise APIResponseError.construct_error(
152
- status_code=response.status_code, extra_details=extra_details
215
+ status_code=response.status_code,
216
+ extra_details=extra_details,
217
+ request_id=request_id,
218
+ )
219
+
220
+ def _get_response_json(
221
+ self, response: requests.Response, request_id: str
222
+ ) -> dict[str, JsonValue]:
223
+ self._validate_response_status(response, request_id)
224
+ try:
225
+ return typing.cast(dict[str, JsonValue], response.json())
226
+ except JSONDecodeError as e:
227
+ raise SDKError("unable to process response", request_id=request_id) from e
228
+
229
+ def _send_request(
230
+ self, request: requests.Request, *, timeout: float | None = None
231
+ ) -> requests.Response:
232
+ if self._cfg.extra_headers is not None:
233
+ request.headers = {**request.headers, **self._cfg.extra_headers}
234
+ if self._cfg.transform_request is not None:
235
+ request = self._cfg.transform_request(request)
236
+ prepared_request = request.prepare()
237
+ response = self._session.send(prepared_request, timeout=timeout)
238
+ return response
239
+
240
+ def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
241
+ request_id = str(uuid4())
242
+ http_request = self._build_http_request(
243
+ api_request=api_request, request_id=request_id
244
+ )
245
+ match http_request:
246
+ case HTTPGetRequest():
247
+ request = requests.Request("GET", http_request.url)
248
+ request.params = http_request.query_params
249
+ case HTTPPostRequest():
250
+ request = requests.Request("POST", http_request.url)
251
+ request.data = http_request.body
252
+ case _:
253
+ typing.assert_never(http_request)
254
+ request.headers = http_request.headers
255
+ attributes: Attributes = {
256
+ "method": http_request.method,
257
+ "endpoint": api_request.endpoint,
258
+ }
259
+ with push_scope_optional(self._cfg.logger, "api_call", attributes=attributes):
260
+ if self._cfg.logger is not None:
261
+ self._cfg.logger.log_info(api_request.endpoint, attributes=attributes)
262
+ timeout = (
263
+ api_request.request_options.timeout_secs
264
+ if api_request.request_options is not None
265
+ else None
153
266
  )
267
+ response = self._send_request(request, timeout=timeout)
268
+ response_data = self._get_response_json(response, request_id=request_id)
154
269
  cached_parser = self._get_cached_parser(return_type)
155
270
  try:
156
- data = response.json()["data"]
271
+ data = response_data["data"]
157
272
  return cached_parser.parse_api(data)
158
- except ValueError | JSONDecodeError:
159
- raise SDKError("unable to process response")
273
+ except (ValueError, JSONDecodeError, KeyError) as e:
274
+ raise SDKError("unable to process response", request_id=request_id) from e
160
275
 
161
276
  def _get_cached_parser(self, data_type: type[DT]) -> CachedParser[DT]:
162
277
  if data_type not in self._parser_map:
163
278
  self._parser_map[data_type] = CachedParser(data_type)
164
279
  return self._parser_map[data_type]
165
280
 
281
+ def _get_oauth_bearer_token(self, *, oauth_details: AuthDetailsOAuth) -> str:
282
+ if (
283
+ self._oauth_bearer_token_cache is None
284
+ or (
285
+ self._oauth_bearer_token_cache.expires_at
286
+ - datetime.datetime.now(tz=UTC)
287
+ ).total_seconds()
288
+ < OAUTH_REFRESH_WINDOW_SECONDS
289
+ ):
290
+ refresh_url = urljoin(self._base_url, "/token/get_bearer_token")
291
+ request = requests.Request("POST", refresh_url)
292
+ request.data = {
293
+ "client_secret": oauth_details.refresh_token,
294
+ "scope": oauth_details.scope,
295
+ "grant_type": "client_credentials",
296
+ }
297
+ response = self._send_request(request)
298
+ data = self._get_response_json(response, request_id=str(uuid4()))
299
+ token_data = oauth_bearer_token_data_parser.parse_storage(data)
300
+ self._oauth_bearer_token_cache = OAuthBearerTokenCache(
301
+ token=token_data.access_token,
302
+ expires_at=datetime.datetime.now(tz=UTC)
303
+ + timedelta(seconds=token_data.expires_in),
304
+ )
305
+
306
+ return self._oauth_bearer_token_cache.token
307
+
166
308
  def _build_auth_headers(self) -> dict[str, str]:
167
309
  match self._auth_details:
168
310
  case AuthDetailsApiKey():
@@ -170,10 +312,17 @@ class Client(ClientMethods):
170
312
  f"{self._auth_details.api_id}:{self._auth_details.api_secret_key}".encode()
171
313
  ).decode("utf-8")
172
314
  return {"Authorization": f"Basic {encoded}"}
315
+ case AuthDetailsOAuth():
316
+ token = self._get_oauth_bearer_token(oauth_details=self._auth_details)
317
+ return {"Authorization": f"Bearer {token}"}
173
318
  typing.assert_never(self._auth_details)
174
319
 
175
- def _build_http_request(self, *, api_request: APIRequest) -> HTTPRequest:
320
+ def _build_http_request(
321
+ self, *, api_request: APIRequest, request_id: str
322
+ ) -> HTTPRequest:
176
323
  headers = self._build_auth_headers()
324
+ headers[UNC_REQUEST_ID_HEADER] = request_id
325
+ headers[UNC_SDK_VERSION_HEADER] = get_version()
177
326
  method = api_request.method.lower()
178
327
  data = {"data": json.dumps(serialize_for_api(api_request.args))}
179
328
  match method:
@@ -194,6 +343,52 @@ class Client(ClientMethods):
194
343
  case _:
195
344
  raise ValueError(f"unsupported request method: {method}")
196
345
 
346
+ def _get_downloaded_filename(self, *, cd: str | None) -> str:
347
+ if not cd:
348
+ return "Unknown"
349
+
350
+ fname = re.findall(r"filename\*=UTF-8''(.+)", cd)
351
+ if fname:
352
+ return unquote(fname[0])
353
+
354
+ fname = re.findall(r'filename="?(.+)"?', cd)
355
+ if fname:
356
+ return str(fname[0].strip('"'))
357
+
358
+ return "Unknown"
359
+
360
+ def download_files(
361
+ self, *, file_query: download_file_t.FileDownloadQuery
362
+ ) -> DownloadedFiles:
363
+ """Download a file from uncountable."""
364
+ request_id = str(uuid4())
365
+ api_request = APIRequest(
366
+ method=download_file_t.ENDPOINT_METHOD,
367
+ endpoint=download_file_t.ENDPOINT_PATH,
368
+ args=download_file_t.Arguments(
369
+ file_query=file_query,
370
+ ),
371
+ )
372
+ http_request = self._build_http_request(
373
+ api_request=api_request, request_id=request_id
374
+ )
375
+ request = requests.Request(http_request.method.value, http_request.url)
376
+ request.headers = http_request.headers
377
+ assert isinstance(http_request, HTTPGetRequest)
378
+ request.params = http_request.query_params
379
+ response = self._send_request(request)
380
+ self._validate_response_status(response, request_id)
381
+
382
+ content = response.content
383
+ content_disposition = response.headers.get("Content-Disposition", None)
384
+ return [
385
+ DownloadedFile(
386
+ name=self._get_downloaded_filename(cd=content_disposition),
387
+ size=len(content),
388
+ data=BytesIO(content),
389
+ )
390
+ ]
391
+
197
392
  def upload_files(
198
393
  self: typing.Self, *, file_uploads: list[FileUpload]
199
394
  ) -> list[UploadedFile]:
@@ -0,0 +1,41 @@
1
+ import functools
2
+ import os
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from uncountable.types import integration_server_t
6
+
7
+
8
+ @functools.cache
9
+ def get_version() -> str:
10
+ try:
11
+ version_str = version("UncountablePythonSDK")
12
+ except PackageNotFoundError:
13
+ version_str = "unknown"
14
+ return version_str
15
+
16
+
17
+ def get_server_env() -> str | None:
18
+ return os.environ.get("UNC_SERVER_ENV")
19
+
20
+
21
+ def get_http_server_port() -> int:
22
+ return int(os.environ.get("UNC_WEBHOOK_SERVER_PORT", "5001"))
23
+
24
+
25
+ def get_local_admin_server_port() -> int:
26
+ return int(os.environ.get("UNC_ADMIN_SERVER_PORT", "50051"))
27
+
28
+
29
+ def get_otel_enabled() -> bool:
30
+ return os.environ.get("UNC_OTEL_ENABLED") == "true"
31
+
32
+
33
+ def get_profiles_module() -> str:
34
+ return os.environ["UNC_PROFILES_MODULE"]
35
+
36
+
37
+ def get_integration_envs() -> list[integration_server_t.IntegrationEnvironment]:
38
+ return [
39
+ integration_server_t.IntegrationEnvironment(env)
40
+ for env in os.environ.get("UNC_INTEGRATION_ENVS", "prod").split(",")
41
+ ]
@@ -4,18 +4,21 @@ from dataclasses import dataclass
4
4
  from enum import StrEnum
5
5
  from io import BytesIO
6
6
  from pathlib import Path
7
- from typing import Generator, Literal, Self
7
+ from typing import Generator, Literal, Self, assert_never
8
8
 
9
9
  import aiohttp
10
10
  import aiotus
11
11
 
12
- from .types import AuthDetails
12
+ from uncountable.integration.telemetry import Logger, push_scope_optional
13
+
14
+ from .types import AuthDetailsAll, AuthDetailsApiKey
13
15
 
14
16
  _CHUNK_SIZE = 5 * 1024 * 1024 # s3 requires 5MiB minimum
15
17
 
16
18
 
17
19
  class FileUploadType(StrEnum):
18
20
  MEDIA_FILE_UPLOAD = "MEDIA_FILE_UPLOAD"
21
+ DATA_FILE_UPLOAD = "DATA_FILE_UPLOAD"
19
22
 
20
23
 
21
24
  @dataclass(kw_only=True)
@@ -23,10 +26,17 @@ class MediaFileUpload:
23
26
  """Upload file from a path on disk"""
24
27
 
25
28
  path: str
26
- type: FileUploadType.MEDIA_FILE_UPLOAD = FileUploadType.MEDIA_FILE_UPLOAD
29
+ type: Literal[FileUploadType.MEDIA_FILE_UPLOAD] = FileUploadType.MEDIA_FILE_UPLOAD
30
+
31
+
32
+ @dataclass(kw_only=True)
33
+ class DataFileUpload:
34
+ data: BytesIO
35
+ name: str
36
+ type: Literal[FileUploadType.DATA_FILE_UPLOAD] = FileUploadType.DATA_FILE_UPLOAD
27
37
 
28
38
 
29
- FileUpload = MediaFileUpload
39
+ FileUpload = MediaFileUpload | DataFileUpload
30
40
 
31
41
 
32
42
  @dataclass(kw_only=True)
@@ -37,12 +47,14 @@ class FileBytes:
37
47
 
38
48
  @contextmanager
39
49
  def file_upload_data(file_upload: FileUpload) -> Generator[FileBytes, None, None]:
40
- match file_upload.type:
41
- case FileUploadType.MEDIA_FILE_UPLOAD:
50
+ match file_upload:
51
+ case MediaFileUpload():
42
52
  with open(file_upload.path, "rb") as f:
43
53
  yield FileBytes(
44
54
  name=Path(file_upload.path).name, bytes_data=BytesIO(f.read())
45
55
  )
56
+ case DataFileUpload():
57
+ yield FileBytes(name=file_upload.name, bytes_data=file_upload.data)
46
58
 
47
59
 
48
60
  @dataclass(kw_only=True)
@@ -56,15 +68,27 @@ class UploadFailed(Exception):
56
68
 
57
69
 
58
70
  class FileUploader:
59
- _auth_details: AuthDetails
71
+ _auth_details: AuthDetailsAll
60
72
  _base_url: str
61
-
62
- def __init__(self: Self, base_url: str, auth_details: AuthDetails) -> None:
73
+ _allow_insecure_tls: bool
74
+
75
+ def __init__(
76
+ self: Self,
77
+ base_url: str,
78
+ auth_details: AuthDetailsAll,
79
+ allow_insecure_tls: bool = False,
80
+ logger: Logger | None = None,
81
+ ) -> None:
63
82
  self._base_url = base_url
64
83
  self._auth_details = auth_details
84
+ self._allow_insecure_tls = allow_insecure_tls
85
+ self._logger = logger
65
86
 
66
87
  async def _upload_file(self: Self, file_upload: FileUpload) -> UploadedFile:
67
88
  creation_url = f"{self._base_url}/api/external/file_upload/files"
89
+ if not isinstance(self._auth_details, AuthDetailsApiKey):
90
+ raise NotImplementedError("Unsupported authentication method.")
91
+
68
92
  auth = aiohttp.BasicAuth(
69
93
  self._auth_details.api_id, self._auth_details.api_secret_key
70
94
  )
@@ -73,19 +97,40 @@ class FileUploader:
73
97
  auth=auth, headers={"Origin": self._base_url}
74
98
  ) as session,
75
99
  ):
76
- with file_upload_data(file_upload) as file_bytes:
77
- location = await aiotus.upload(
78
- creation_url,
79
- file_bytes.bytes_data,
80
- {"filename": file_bytes.name.encode()},
81
- client_session=session,
82
- chunksize=_CHUNK_SIZE,
83
- )
84
- if location is None:
85
- raise UploadFailed(f"Failed to upload: {file_bytes.name}")
86
- return UploadedFile(
87
- name=file_bytes.name, file_id=int(location.path.split("/")[-1])
88
- )
100
+ attributes = {}
101
+ match file_upload:
102
+ case MediaFileUpload():
103
+ attributes["file_path"] = file_upload.path
104
+ case DataFileUpload():
105
+ attributes["file_name"] = file_upload.name
106
+ case _:
107
+ assert_never(file_upload)
108
+ with push_scope_optional(
109
+ self._logger, "upload_file", attributes=attributes
110
+ ):
111
+ if self._logger is not None:
112
+ self._logger.log_info("Uploading file", attributes=attributes)
113
+ with file_upload_data(file_upload) as file_bytes:
114
+ if file_bytes.bytes_data.read(1) == b"":
115
+ raise UploadFailed(
116
+ f"Failed to upload empty file: {file_bytes.name}"
117
+ )
118
+ file_bytes.bytes_data.seek(0)
119
+ location = await aiotus.upload(
120
+ creation_url,
121
+ file_bytes.bytes_data,
122
+ {"filename": file_bytes.name.encode()},
123
+ client_session=session,
124
+ config=aiotus.RetryConfiguration(
125
+ ssl=not self._allow_insecure_tls
126
+ ),
127
+ chunksize=_CHUNK_SIZE,
128
+ )
129
+ if location is None:
130
+ raise UploadFailed(f"Failed to upload: {file_bytes.name}")
131
+ return UploadedFile(
132
+ name=file_bytes.name, file_id=int(location.path.split("/")[-1])
133
+ )
89
134
 
90
135
  def upload_files(
91
136
  self: Self, *, file_uploads: list[FileUpload]
uncountable/core/types.py CHANGED
@@ -1,16 +1,4 @@
1
- import base64
2
- import json
3
- import typing
4
1
  from dataclasses import dataclass
5
- from enum import StrEnum
6
- from urllib.parse import urljoin
7
-
8
- import aiohttp
9
- import requests
10
-
11
- from pkgs.argument_parser import CachedParser
12
- from pkgs.serialization_util import serialize_for_api
13
- from uncountable.types.client_base import APIRequest, ClientMethods
14
2
 
15
3
 
16
4
  @dataclass(kw_only=True)
@@ -19,4 +7,11 @@ class AuthDetailsApiKey:
19
7
  api_secret_key: str
20
8
 
21
9
 
22
- AuthDetails = AuthDetailsApiKey
10
+ @dataclass(kw_only=True)
11
+ class AuthDetailsOAuth:
12
+ refresh_token: str
13
+ scope: str = "unc.rnd"
14
+
15
+
16
+ AuthDetails = AuthDetailsApiKey # Legacy Mapping
17
+ AuthDetailsAll = AuthDetailsApiKey | AuthDetailsOAuth