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
@@ -0,0 +1,151 @@
1
+ import os
2
+ from collections.abc import Iterable
3
+ from io import BytesIO
4
+
5
+ import paramiko
6
+ import pysftp
7
+
8
+ from pkgs.filesystem_utils.file_type_utils import (
9
+ FileObjectData,
10
+ FileSystemFileReference,
11
+ FileSystemObject,
12
+ FileSystemSFTPConfig,
13
+ FileTransfer,
14
+ IncompatibleFileReference,
15
+ )
16
+
17
+ from .filesystem_session import FileSystemSession
18
+
19
+
20
+ def move_sftp_files(
21
+ connection: pysftp.Connection,
22
+ src_filepath: str,
23
+ dest_filepath: str,
24
+ ) -> None:
25
+ connection.rename(src_filepath, dest_filepath)
26
+
27
+
28
+ def list_sftp_files(
29
+ connection: pysftp.Connection,
30
+ dir_path: str,
31
+ *,
32
+ valid_extensions: Iterable[str] | None = None,
33
+ parent_dir_path: str | None = None,
34
+ recursive: bool = True,
35
+ ) -> list[str]:
36
+ file_paths: list[str] = []
37
+ if recursive:
38
+
39
+ def _skip(name: str) -> None:
40
+ return
41
+
42
+ def _add_file(path: str) -> None:
43
+ if (
44
+ valid_extensions is None
45
+ or os.path.splitext(path)[1] in valid_extensions
46
+ ) and (parent_dir_path is None or os.path.dirname(path) == parent_dir_path):
47
+ file_paths.append(path)
48
+
49
+ connection.walktree(
50
+ dir_path, fcallback=_add_file, dcallback=_skip, ucallback=_skip
51
+ )
52
+ else:
53
+ file_paths.extend([
54
+ os.path.join(dir_path, file)
55
+ for file in connection.listdir(dir_path)
56
+ if connection.isfile(os.path.join(dir_path, file))
57
+ and (
58
+ valid_extensions is None
59
+ or os.path.splitext(file)[1] in valid_extensions
60
+ )
61
+ ])
62
+ return file_paths
63
+
64
+
65
+ class SFTPSession(FileSystemSession):
66
+ def __init__(self, sftp_config: FileSystemSFTPConfig) -> None:
67
+ super().__init__()
68
+ self.host: str = sftp_config.ip
69
+ self.username: str = sftp_config.username
70
+ self.key_file: str | paramiko.RSAKey | None = (
71
+ sftp_config.pem_path
72
+ if sftp_config.pem_path is not None
73
+ else sftp_config.pem_key
74
+ )
75
+ self.password: str | None = sftp_config.password
76
+
77
+ def start(self) -> None:
78
+ cnopts = pysftp.CnOpts()
79
+ cnopts.hostkeys = None
80
+ if self.key_file is not None:
81
+ self.connection = pysftp.Connection(
82
+ self.host,
83
+ username=self.username,
84
+ private_key=self.key_file,
85
+ cnopts=cnopts,
86
+ )
87
+ elif self.password is not None:
88
+ self.connection = pysftp.Connection(
89
+ self.host,
90
+ username=self.username,
91
+ password=self.password,
92
+ cnopts=cnopts,
93
+ )
94
+ else:
95
+ raise pysftp.CredentialException(
96
+ "Must specify either a private key path or a password."
97
+ )
98
+
99
+ def __enter__(self) -> "SFTPSession":
100
+ self.start()
101
+ return self
102
+
103
+ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
104
+ self.connection.close()
105
+
106
+ def list_files(
107
+ self,
108
+ dir_path: FileSystemObject,
109
+ *,
110
+ recursive: bool = True,
111
+ valid_extensions: list[str] | None = None,
112
+ ) -> list[FileSystemObject]:
113
+ if not isinstance(
114
+ dir_path, FileSystemFileReference
115
+ ) or not self.connection.isdir(dir_path.filepath):
116
+ raise IncompatibleFileReference()
117
+
118
+ return [
119
+ FileSystemFileReference(file_path)
120
+ for file_path in list_sftp_files(
121
+ self.connection,
122
+ dir_path.filepath,
123
+ recursive=recursive,
124
+ valid_extensions=valid_extensions,
125
+ )
126
+ ]
127
+
128
+ def download_files(self, filepaths: list[FileSystemObject]) -> list[FileObjectData]:
129
+ downloaded_files: list[FileObjectData] = []
130
+ for file_object in filepaths:
131
+ if (
132
+ not isinstance(file_object, FileSystemFileReference)
133
+ or file_object.filename is None
134
+ ):
135
+ raise IncompatibleFileReference()
136
+ filepath = file_object.filepath
137
+ file_data = self.connection.open(filepath).read()
138
+ downloaded_file = FileObjectData(
139
+ file_data, BytesIO(file_data), file_object.filename, filepath=filepath
140
+ )
141
+ if downloaded_file is not None:
142
+ downloaded_files.append(downloaded_file)
143
+ return downloaded_files
144
+
145
+ def move_files(self, file_mappings: list[FileTransfer]) -> None:
146
+ for src_file, dest_file in file_mappings:
147
+ if not isinstance(src_file, FileSystemFileReference) or not isinstance(
148
+ dest_file, FileSystemFileReference
149
+ ):
150
+ raise IncompatibleFileReference()
151
+ move_sftp_files(self.connection, src_file.filepath, dest_file.filepath)
@@ -0,0 +1,91 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from io import BytesIO
4
+ from typing import Union
5
+
6
+ import paramiko
7
+ from azure.core.credentials import (
8
+ AzureNamedKeyCredential,
9
+ AzureSasCredential,
10
+ TokenCredential,
11
+ )
12
+ from azure.storage.blob import ContainerProperties
13
+
14
+
15
+ @dataclass
16
+ class FileObjectData:
17
+ file_data: bytes
18
+ file_IO: BytesIO
19
+ filename: str
20
+ filepath: str | None = None
21
+ mime_type: str | None = None
22
+ metadata: dict[str, str] | None = None
23
+
24
+
25
+ @dataclass
26
+ class FileSystemFileReference:
27
+ filepath: str
28
+
29
+ @property
30
+ def filename(self) -> str:
31
+ return os.path.basename(self.filepath)
32
+
33
+ @property
34
+ def dirname(self) -> str:
35
+ return os.path.dirname(self.filepath)
36
+
37
+
38
+ @dataclass
39
+ class RemoteObjectReference:
40
+ file_id: str
41
+ mime_type: str
42
+ filename: str | None = None
43
+
44
+ @property
45
+ def is_dir(self) -> bool:
46
+ return "folder" in self.mime_type
47
+
48
+
49
+ FileSystemObject = Union[FileSystemFileReference, RemoteObjectReference, FileObjectData]
50
+
51
+
52
+ FileTransfer = tuple[FileSystemObject, FileSystemObject]
53
+
54
+
55
+ class IncompatibleFileReference(Exception):
56
+ pass
57
+
58
+
59
+ @dataclass(frozen=True, kw_only=True)
60
+ class FileSystemSFTPConfig:
61
+ ip: str
62
+ username: str
63
+ pem_path: str | None
64
+ pem_key: paramiko.RSAKey | None = None
65
+ password: str | None = None
66
+ valid_extensions: tuple[str] | None = None
67
+ recursive: bool = True
68
+
69
+
70
+ @dataclass(kw_only=True)
71
+ class FileSystemS3Config:
72
+ endpoint_url: str
73
+ bucket_name: str
74
+ region_name: str | None
75
+ access_key_id: str | None
76
+ secret_access_key: str | None
77
+ session_token: str | None
78
+
79
+
80
+ @dataclass(kw_only=True)
81
+ class FileSystemBlobConfig:
82
+ account_url: str
83
+ credential: (
84
+ str
85
+ | dict[str, str]
86
+ | AzureNamedKeyCredential
87
+ | AzureSasCredential
88
+ | TokenCredential
89
+ | None
90
+ )
91
+ container: ContainerProperties | str
@@ -0,0 +1,39 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from pkgs.filesystem_utils.file_type_utils import (
4
+ FileObjectData,
5
+ FileSystemObject,
6
+ FileTransfer,
7
+ )
8
+
9
+
10
+ class FileSystemSession(ABC):
11
+ def __init__(self) -> None:
12
+ return
13
+
14
+ @abstractmethod
15
+ def start(self) -> None:
16
+ raise NotImplementedError
17
+
18
+ @abstractmethod
19
+ def list_files(
20
+ self, dir_path: FileSystemObject, *, recursive: bool = True
21
+ ) -> list[FileSystemObject]:
22
+ raise NotImplementedError
23
+
24
+ @abstractmethod
25
+ def move_files(self, file_mappings: list[FileTransfer]) -> None:
26
+ raise NotImplementedError
27
+
28
+ @abstractmethod
29
+ def download_files(self, filepaths: list[FileSystemObject]) -> list[FileObjectData]:
30
+ raise NotImplementedError
31
+
32
+ def delete_files(self, filepaths: list[FileSystemObject]) -> None:
33
+ raise NotImplementedError
34
+
35
+ @abstractmethod
36
+ def __enter__(self) -> "FileSystemSession": ...
37
+
38
+ @abstractmethod
39
+ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ...
pkgs/py.typed ADDED
File without changes
@@ -1,10 +1,17 @@
1
- # flake8:noqa
1
+ from .annotation import unwrap_annotated as unwrap_annotated
2
2
  from .missing_sentry import MISSING_SENTRY as MISSING_SENTRY
3
3
  from .missing_sentry import MissingSentryType as MissingSentryType
4
4
  from .missing_sentry import MissingType as MissingType
5
5
  from .missing_sentry import coalesce_missing_sentry as coalesce_missing_sentry
6
6
  from .opaque_key import OpaqueKey as OpaqueKey
7
+ from .serial_alias import SerialAliasInspector as SerialAliasInspector
8
+ from .serial_alias import get_serial_alias_data as get_serial_alias_data
9
+ from .serial_alias import serial_alias_annotation as serial_alias_annotation
10
+ from .serial_class import SerialClassDataInspector as SerialClassDataInspector
7
11
  from .serial_class import get_serial_class_data as get_serial_class_data
8
12
  from .serial_class import get_serial_string_enum_data as get_serial_string_enum_data
9
13
  from .serial_class import serial_class as serial_class
10
14
  from .serial_class import serial_string_enum as serial_string_enum
15
+ from .serial_generic import get_serial_data as get_serial_data
16
+ from .serial_union import get_serial_union_data as get_serial_union_data
17
+ from .serial_union import serial_union_annotation as serial_union_annotation
@@ -0,0 +1,64 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ T = typing.TypeVar("T")
5
+
6
+
7
+ @dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
8
+ class SerialBase:
9
+ named_type_path: str | None = None
10
+ # Indicates this type is allowed in dynamic lookups, such as via a named_type_path
11
+ # This isn't meant to be limting, but to catalog all the types where we need it
12
+ is_dynamic_allowed: bool = False
13
+ # Tracks if this data was provided as a decorator to the type.
14
+ # This is used to track "proper types" which are appropriate
15
+ # for serialization and/or dynamic discovery
16
+ from_decorator: bool = False
17
+
18
+
19
+ def get_serial_annotation(parsed_type: type[T]) -> SerialBase | None:
20
+ if not hasattr(parsed_type, "__metadata__"):
21
+ return None
22
+ metadata = parsed_type.__metadata__ # type:ignore[attr-defined]
23
+ if not isinstance(metadata, tuple) or len(metadata) != 1:
24
+ return None
25
+ serial = metadata[0]
26
+ if not isinstance(serial, SerialBase):
27
+ return None
28
+ return serial
29
+
30
+
31
+ class SerialInspector(typing.Generic[T]):
32
+ def __init__(self, parsed_type: type[T], serial_base: SerialBase) -> None:
33
+ self._parsed_type = parsed_type
34
+ self._serial_base = serial_base
35
+
36
+ @property
37
+ def named_type_path(self) -> str | None:
38
+ return self._serial_base.named_type_path
39
+
40
+ @property
41
+ def from_decorator(self) -> bool:
42
+ return self._serial_base.from_decorator
43
+
44
+ @property
45
+ def is_field_proper(self) -> bool:
46
+ return (
47
+ self._serial_base.from_decorator
48
+ and self._serial_base.named_type_path is not None
49
+ )
50
+
51
+ @property
52
+ def is_dynamic_allowed(self) -> bool:
53
+ return self._serial_base.is_dynamic_allowed
54
+
55
+
56
+ def unwrap_annotated(parsed_type: type[T]) -> type[T]:
57
+ """
58
+ If the type is an annotated type then return the origin of it.
59
+ Otherwise return the original type.
60
+ """
61
+ if typing.get_origin(parsed_type) is typing.Annotated:
62
+ # It's unclear if there's anyway to type this correctly
63
+ return parsed_type.__origin__ # type:ignore[attr-defined, no-any-return]
64
+ return parsed_type
@@ -26,5 +26,5 @@ MISSING_SENTRY = MissingSentryType()
26
26
  MissingType = Union[MissingSentryType, ClassT]
27
27
 
28
28
 
29
- def coalesce_missing_sentry(value: MissingType[ClassT]) -> Optional[ClassT]:
29
+ def coalesce_missing_sentry(value: MissingType[ClassT]) -> ClassT | None:
30
30
  return None if isinstance(value, MissingSentryType) else value
@@ -1,4 +1,4 @@
1
1
  # Blocks a string key value from being interpreted for case conversion
2
- class OpaqueKey(str):
2
+ class OpaqueKey(str): # noqa: FURB189
3
3
  def __new__(cls, key: str) -> "OpaqueKey":
4
4
  return str.__new__(cls, key)
@@ -0,0 +1,47 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from .annotation import SerialBase, SerialInspector, get_serial_annotation
5
+
6
+ T = typing.TypeVar("T")
7
+
8
+
9
+ @dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
10
+ class _SerialAlias(SerialBase):
11
+ """
12
+ This class is to be kept private, to provide flexibility in registration/lookup.
13
+ Places that need the data should access it via help classes/methods.
14
+ """
15
+
16
+
17
+ def serial_alias_annotation(
18
+ *,
19
+ named_type_path: str | None = None,
20
+ is_dynamic_allowed: bool = False,
21
+ ) -> _SerialAlias:
22
+ return _SerialAlias(
23
+ named_type_path=named_type_path,
24
+ from_decorator=True,
25
+ is_dynamic_allowed=is_dynamic_allowed,
26
+ )
27
+
28
+
29
+ def _get_serial_alias(parsed_type: type[T]) -> _SerialAlias | None:
30
+ serial = get_serial_annotation(parsed_type)
31
+ if not isinstance(serial, _SerialAlias):
32
+ return None
33
+ return serial
34
+
35
+
36
+ class SerialAliasInspector(SerialInspector[T]):
37
+ def __init__(self, parsed_type: type[T], serial_alias: _SerialAlias) -> None:
38
+ super().__init__(parsed_type, serial_alias)
39
+ self._serial_alias = serial_alias
40
+
41
+
42
+ def get_serial_alias_data(parsed_type: type[T]) -> SerialAliasInspector[T] | None:
43
+ serial = _get_serial_alias(parsed_type)
44
+ if serial is None:
45
+ return None
46
+
47
+ return SerialAliasInspector(parsed_type, serial)
@@ -3,26 +3,34 @@ from __future__ import annotations
3
3
  import dataclasses
4
4
  from collections.abc import Callable
5
5
  from enum import StrEnum
6
- from typing import Any, Optional, TypeVar, cast
6
+ from typing import Any, TypeVar, cast
7
7
 
8
- _ClassT = TypeVar("_ClassT")
8
+ from .annotation import SerialBase, SerialInspector
9
9
 
10
+ ClassT = TypeVar("ClassT")
10
11
 
11
- @dataclasses.dataclass
12
- class _SerialClassData:
12
+
13
+ @dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
14
+ class _SerialClassData(SerialBase):
13
15
  unconverted_keys: set[str] = dataclasses.field(default_factory=set)
14
16
  unconverted_values: set[str] = dataclasses.field(default_factory=set)
15
17
  to_string_values: set[str] = dataclasses.field(default_factory=set)
16
18
  parse_require: set[str] = dataclasses.field(default_factory=set)
19
+ named_type_path: str | None = None
20
+
21
+
22
+ EMPTY_SERIAL_CLASS_DATA = _SerialClassData()
17
23
 
18
24
 
19
25
  def serial_class(
20
26
  *,
21
- unconverted_keys: Optional[set[str]] = None,
22
- unconverted_values: Optional[set[str]] = None,
23
- to_string_values: Optional[set[str]] = None,
24
- parse_require: Optional[set[str]] = None,
25
- ) -> Callable[[_ClassT], _ClassT]:
27
+ unconverted_keys: set[str] | None = None,
28
+ unconverted_values: set[str] | None = None,
29
+ to_string_values: set[str] | None = None,
30
+ parse_require: set[str] | None = None,
31
+ named_type_path: str | None = None,
32
+ is_dynamic_allowed: bool = False,
33
+ ) -> Callable[[ClassT], ClassT]:
26
34
  """
27
35
  An additional decorator to a dataclass that specifies serialization options.
28
36
 
@@ -42,73 +50,80 @@ def serial_class(
42
50
  This field is always required while parsing, even if it has a default in the definition.
43
51
  This allows supporting literal type defaults for Python instantiation, but
44
52
  requiring them for the API input.
53
+ @param named_type_path
54
+ The type_spec type-path to this type. This applies only to named types.
45
55
  """
46
56
 
47
- def decorate(orig_class: _ClassT) -> _ClassT:
57
+ def decorate(orig_class: ClassT) -> ClassT:
48
58
  cast(Any, orig_class).__unc_serial_data = _SerialClassData(
49
59
  unconverted_keys=unconverted_keys or set(),
50
60
  unconverted_values=unconverted_values or set(),
51
61
  to_string_values=to_string_values or set(),
52
62
  parse_require=parse_require or set(),
63
+ named_type_path=named_type_path,
64
+ from_decorator=True,
65
+ is_dynamic_allowed=is_dynamic_allowed,
53
66
  )
54
67
  return orig_class
55
68
 
56
69
  return decorate
57
70
 
58
71
 
59
- class SerialClassDataInspector:
60
- bases: list[SerialClassDataInspector]
61
-
72
+ class SerialClassDataInspector(SerialInspector[ClassT]):
62
73
  def __init__(
63
- self, bases: list[SerialClassDataInspector], current: _SerialClassData
74
+ self,
75
+ parsed_type: type[ClassT],
76
+ current: _SerialClassData,
64
77
  ) -> None:
65
- self.bases = bases
78
+ super().__init__(parsed_type, current)
66
79
  self.current = current
67
80
 
68
81
  def has_unconverted_key(self, key: str) -> bool:
69
- if key in self.current.unconverted_keys:
70
- return True
71
- for base in self.bases:
72
- if base.has_unconverted_key(key):
73
- return True
74
- return False
82
+ return key in self.current.unconverted_keys
75
83
 
76
84
  def has_unconverted_value(self, key: str) -> bool:
77
- if key in self.current.unconverted_values:
78
- return True
79
- for base in self.bases:
80
- if base.has_unconverted_value(key):
81
- return True
82
- return False
85
+ return key in self.current.unconverted_values
83
86
 
84
87
  def has_to_string_value(self, key: str) -> bool:
85
- if key in self.current.to_string_values:
86
- return True
87
- for base in self.bases:
88
- if base.has_to_string_value(key):
89
- return True
90
- return False
88
+ return key in self.current.to_string_values
91
89
 
92
90
  def has_parse_require(self, key: str) -> bool:
93
- if key in self.current.parse_require:
94
- return True
95
- for base in self.bases:
96
- if base.has_parse_require(key):
97
- return True
98
- return False
99
-
100
-
101
- def get_serial_class_data(type_class: type[Any]) -> SerialClassDataInspector:
102
- bases = (
103
- [get_serial_class_data(base) for base in type_class.__bases__]
104
- if type_class.__bases__ is not None
105
- else []
106
- )
107
- return SerialClassDataInspector(
108
- bases,
91
+ return key in self.current.parse_require
92
+
93
+
94
+ def get_merged_serial_class_data(type_class: type[Any]) -> _SerialClassData | None:
95
+ base_class_data = (
109
96
  cast(_SerialClassData, type_class.__unc_serial_data)
110
97
  if hasattr(type_class, "__unc_serial_data")
111
- else _SerialClassData(),
98
+ else None
99
+ )
100
+ if base_class_data is None:
101
+ return None
102
+
103
+ # IMPROVE: We should cache this result on the type
104
+ if type_class.__bases__ is not None:
105
+ for base in type_class.__bases__:
106
+ curr_base_class_data = get_merged_serial_class_data(base)
107
+ if curr_base_class_data is not None:
108
+ base_class_data = dataclasses.replace(
109
+ base_class_data,
110
+ unconverted_keys=base_class_data.unconverted_keys
111
+ | curr_base_class_data.unconverted_keys,
112
+ unconverted_values=base_class_data.unconverted_values
113
+ | curr_base_class_data.unconverted_values,
114
+ to_string_values=base_class_data.to_string_values
115
+ | curr_base_class_data.to_string_values,
116
+ parse_require=base_class_data.parse_require
117
+ | curr_base_class_data.parse_require,
118
+ )
119
+ return base_class_data
120
+
121
+
122
+ def get_serial_class_data(
123
+ type_class: type[ClassT],
124
+ ) -> SerialClassDataInspector[ClassT]:
125
+ return SerialClassDataInspector(
126
+ type_class, get_merged_serial_class_data(type_class) or EMPTY_SERIAL_CLASS_DATA
112
127
  )
113
128
 
114
129
 
@@ -119,13 +134,13 @@ class _SerialStringEnumData:
119
134
 
120
135
 
121
136
  def serial_string_enum(
122
- *, labels: Optional[dict[str, str]] = None, deprecated: Optional[set[str]] = None
123
- ) -> Callable[[_ClassT], _ClassT]:
137
+ *, labels: dict[str, str] | None = None, deprecated: set[str] | None = None
138
+ ) -> Callable[[ClassT], ClassT]:
124
139
  """
125
140
  A decorator for enums to provide serialization data, including labels.
126
141
  """
127
142
 
128
- def decorate(orig_class: _ClassT) -> _ClassT:
143
+ def decorate(orig_class: ClassT) -> ClassT:
129
144
  cast(Any, orig_class).__unc_serial_string_enum_data = _SerialStringEnumData(
130
145
  labels=labels or {}, deprecated=deprecated or set()
131
146
  )
@@ -138,7 +153,7 @@ class SerialStringEnumInspector:
138
153
  def __init__(self, current: _SerialStringEnumData) -> None:
139
154
  self.current = current
140
155
 
141
- def get_label(self, value: str) -> Optional[str]:
156
+ def get_label(self, value: str) -> str | None:
142
157
  return self.current.labels.get(value)
143
158
 
144
159
  def get_deprecated(self, value: str) -> bool:
@@ -0,0 +1,16 @@
1
+ import typing
2
+
3
+ from .annotation import SerialInspector, get_serial_annotation
4
+ from .serial_class import get_merged_serial_class_data
5
+
6
+ T = typing.TypeVar("T")
7
+
8
+
9
+ def get_serial_data(parsed_type: type[T]) -> SerialInspector[T] | None:
10
+ serial = get_serial_annotation(parsed_type)
11
+ if serial is None:
12
+ serial = get_merged_serial_class_data(parsed_type)
13
+
14
+ if serial is not None:
15
+ return SerialInspector(parsed_type, serial)
16
+ return None