UncountablePythonSDK 0.0.82__py3-none-any.whl → 0.0.132__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 (298) hide show
  1. docs/conf.py +54 -7
  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 +6 -4
  8. examples/basic_auth.py +7 -0
  9. examples/create_ingredient_sdk.py +34 -0
  10. examples/download_files.py +26 -0
  11. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  12. examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
  13. examples/integration-server/jobs/materials_auto/example_http.py +47 -0
  14. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  15. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  16. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  17. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
  18. examples/integration-server/jobs/materials_auto/example_wh.py +17 -9
  19. examples/integration-server/jobs/materials_auto/profile.yaml +61 -0
  20. examples/integration-server/pyproject.toml +10 -10
  21. examples/oauth.py +7 -0
  22. examples/set_recipe_metadata_file.py +1 -1
  23. examples/upload_files.py +1 -2
  24. pkgs/argument_parser/__init__.py +8 -0
  25. pkgs/argument_parser/_is_namedtuple.py +3 -0
  26. pkgs/argument_parser/argument_parser.py +196 -63
  27. pkgs/filesystem_utils/__init__.py +1 -0
  28. pkgs/filesystem_utils/_blob_session.py +144 -0
  29. pkgs/filesystem_utils/_gdrive_session.py +5 -5
  30. pkgs/filesystem_utils/_s3_session.py +2 -1
  31. pkgs/filesystem_utils/_sftp_session.py +6 -3
  32. pkgs/filesystem_utils/file_type_utils.py +30 -10
  33. pkgs/serialization/__init__.py +7 -2
  34. pkgs/serialization/annotation.py +64 -0
  35. pkgs/serialization/missing_sentry.py +1 -1
  36. pkgs/serialization/opaque_key.py +1 -1
  37. pkgs/serialization/serial_alias.py +47 -0
  38. pkgs/serialization/serial_class.py +40 -48
  39. pkgs/serialization/serial_generic.py +16 -0
  40. pkgs/serialization/serial_union.py +16 -16
  41. pkgs/serialization_util/__init__.py +6 -0
  42. pkgs/serialization_util/dataclasses.py +14 -0
  43. pkgs/serialization_util/serialization_helpers.py +15 -5
  44. pkgs/type_spec/actions_registry/__main__.py +0 -4
  45. pkgs/type_spec/actions_registry/emit_typescript.py +2 -4
  46. pkgs/type_spec/builder.py +248 -70
  47. pkgs/type_spec/builder_types.py +9 -0
  48. pkgs/type_spec/config.py +40 -7
  49. pkgs/type_spec/cross_output_links.py +99 -0
  50. pkgs/type_spec/emit_open_api.py +121 -34
  51. pkgs/type_spec/emit_open_api_util.py +5 -5
  52. pkgs/type_spec/emit_python.py +277 -86
  53. pkgs/type_spec/emit_typescript.py +102 -29
  54. pkgs/type_spec/emit_typescript_util.py +66 -10
  55. pkgs/type_spec/load_types.py +16 -3
  56. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  57. pkgs/type_spec/open_api_util.py +29 -4
  58. pkgs/type_spec/parts/base.py.prepart +11 -8
  59. pkgs/type_spec/parts/base.ts.prepart +4 -0
  60. pkgs/type_spec/type_info/__main__.py +3 -1
  61. pkgs/type_spec/type_info/emit_type_info.py +115 -22
  62. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  63. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  64. pkgs/type_spec/util.py +3 -3
  65. pkgs/type_spec/value_spec/__main__.py +26 -9
  66. pkgs/type_spec/value_spec/convert_type.py +18 -0
  67. pkgs/type_spec/value_spec/emit_python.py +13 -3
  68. pkgs/type_spec/value_spec/types.py +1 -1
  69. uncountable/core/async_batch.py +1 -1
  70. uncountable/core/client.py +133 -34
  71. uncountable/core/environment.py +3 -3
  72. uncountable/core/file_upload.py +39 -15
  73. uncountable/integration/cli.py +116 -23
  74. uncountable/integration/construct_client.py +3 -3
  75. uncountable/integration/executors/executors.py +12 -2
  76. uncountable/integration/executors/generic_upload_executor.py +66 -14
  77. uncountable/integration/http_server/__init__.py +5 -0
  78. uncountable/integration/http_server/types.py +69 -0
  79. uncountable/integration/job.py +192 -7
  80. uncountable/integration/queue_runner/command_server/__init__.py +4 -0
  81. uncountable/integration/queue_runner/command_server/command_client.py +65 -0
  82. uncountable/integration/queue_runner/command_server/command_server.py +83 -5
  83. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  84. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +36 -0
  85. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +28 -11
  86. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +77 -1
  87. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +135 -0
  88. uncountable/integration/queue_runner/command_server/types.py +25 -2
  89. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +168 -11
  90. uncountable/integration/queue_runner/datastore/interface.py +10 -0
  91. uncountable/integration/queue_runner/datastore/model.py +8 -1
  92. uncountable/integration/queue_runner/job_scheduler.py +63 -23
  93. uncountable/integration/queue_runner/queue_runner.py +10 -2
  94. uncountable/integration/queue_runner/worker.py +22 -17
  95. uncountable/integration/scan_profiles.py +1 -1
  96. uncountable/integration/scheduler.py +74 -25
  97. uncountable/integration/secret_retrieval/retrieve_secret.py +1 -1
  98. uncountable/integration/server.py +42 -12
  99. uncountable/integration/telemetry.py +63 -10
  100. uncountable/integration/webhook_server/entrypoint.py +39 -112
  101. uncountable/types/__init__.py +58 -1
  102. uncountable/types/api/batch/execute_batch.py +5 -6
  103. uncountable/types/api/batch/execute_batch_load_async.py +2 -3
  104. uncountable/types/api/chemical/convert_chemical_formats.py +10 -5
  105. uncountable/types/api/condition_parameters/__init__.py +1 -0
  106. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  107. uncountable/types/api/entity/create_entities.py +7 -7
  108. uncountable/types/api/entity/create_entity.py +8 -8
  109. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  110. uncountable/types/api/entity/export_entities.py +59 -0
  111. uncountable/types/api/entity/get_entities_data.py +3 -4
  112. uncountable/types/api/entity/grant_entity_permissions.py +6 -6
  113. uncountable/types/api/entity/list_aggregate.py +79 -0
  114. uncountable/types/api/entity/list_entities.py +34 -10
  115. uncountable/types/api/entity/lock_entity.py +4 -4
  116. uncountable/types/api/entity/lookup_entity.py +116 -0
  117. uncountable/types/api/entity/resolve_entity_ids.py +5 -6
  118. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  119. uncountable/types/api/entity/set_values.py +3 -3
  120. uncountable/types/api/entity/transition_entity_phase.py +14 -7
  121. uncountable/types/api/entity/unlock_entity.py +3 -3
  122. uncountable/types/api/equipment/associate_equipment_input.py +2 -3
  123. uncountable/types/api/field_options/upsert_field_options.py +7 -7
  124. uncountable/types/api/files/__init__.py +1 -0
  125. uncountable/types/api/files/download_file.py +77 -0
  126. uncountable/types/api/id_source/list_id_source.py +6 -7
  127. uncountable/types/api/id_source/match_id_source.py +4 -5
  128. uncountable/types/api/input_groups/get_input_group_names.py +3 -4
  129. uncountable/types/api/inputs/create_inputs.py +10 -9
  130. uncountable/types/api/inputs/get_input_data.py +11 -12
  131. uncountable/types/api/inputs/get_input_names.py +6 -7
  132. uncountable/types/api/inputs/get_inputs_data.py +6 -7
  133. uncountable/types/api/inputs/set_input_attribute_values.py +5 -6
  134. uncountable/types/api/inputs/set_input_category.py +5 -5
  135. uncountable/types/api/inputs/set_input_subcategories.py +3 -3
  136. uncountable/types/api/inputs/set_intermediate_type.py +4 -4
  137. uncountable/types/api/integrations/__init__.py +1 -0
  138. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  139. uncountable/types/api/integrations/push_notification.py +49 -0
  140. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  141. uncountable/types/api/listing/__init__.py +1 -0
  142. uncountable/types/api/listing/fetch_listing.py +58 -0
  143. uncountable/types/api/material_families/update_entity_material_families.py +3 -4
  144. uncountable/types/api/notebooks/__init__.py +1 -0
  145. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  146. uncountable/types/api/outputs/get_output_data.py +12 -13
  147. uncountable/types/api/outputs/get_output_names.py +5 -6
  148. uncountable/types/api/outputs/get_output_organization.py +173 -0
  149. uncountable/types/api/outputs/resolve_output_conditions.py +7 -8
  150. uncountable/types/api/permissions/set_core_permissions.py +16 -10
  151. uncountable/types/api/project/get_projects.py +6 -7
  152. uncountable/types/api/project/get_projects_data.py +7 -8
  153. uncountable/types/api/recipe_links/create_recipe_link.py +5 -5
  154. uncountable/types/api/recipe_links/remove_recipe_link.py +4 -4
  155. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +6 -7
  156. uncountable/types/api/recipes/add_recipe_to_project.py +3 -3
  157. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  158. uncountable/types/api/recipes/archive_recipes.py +4 -4
  159. uncountable/types/api/recipes/associate_recipe_as_input.py +5 -5
  160. uncountable/types/api/recipes/associate_recipe_as_lot.py +3 -3
  161. uncountable/types/api/recipes/clear_recipe_outputs.py +3 -3
  162. uncountable/types/api/recipes/create_mix_order.py +44 -0
  163. uncountable/types/api/recipes/create_recipe.py +8 -9
  164. uncountable/types/api/recipes/create_recipes.py +8 -9
  165. uncountable/types/api/recipes/disassociate_recipe_as_input.py +3 -3
  166. uncountable/types/api/recipes/edit_recipe_inputs.py +101 -24
  167. uncountable/types/api/recipes/get_column_calculation_values.py +4 -5
  168. uncountable/types/api/recipes/get_curve.py +4 -5
  169. uncountable/types/api/recipes/get_recipe_calculations.py +6 -7
  170. uncountable/types/api/recipes/get_recipe_links.py +3 -4
  171. uncountable/types/api/recipes/get_recipe_names.py +3 -4
  172. uncountable/types/api/recipes/get_recipe_output_metadata.py +5 -6
  173. uncountable/types/api/recipes/get_recipes_data.py +62 -34
  174. uncountable/types/api/recipes/lock_recipes.py +9 -8
  175. uncountable/types/api/recipes/remove_recipe_from_project.py +3 -3
  176. uncountable/types/api/recipes/set_recipe_inputs.py +9 -10
  177. uncountable/types/api/recipes/set_recipe_metadata.py +3 -3
  178. uncountable/types/api/recipes/set_recipe_output_annotations.py +11 -12
  179. uncountable/types/api/recipes/set_recipe_output_file.py +5 -6
  180. uncountable/types/api/recipes/set_recipe_outputs.py +24 -13
  181. uncountable/types/api/recipes/set_recipe_tags.py +14 -9
  182. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  183. uncountable/types/api/recipes/unarchive_recipes.py +3 -3
  184. uncountable/types/api/recipes/unlock_recipes.py +7 -6
  185. uncountable/types/api/runsheet/__init__.py +1 -0
  186. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  187. uncountable/types/api/triggers/run_trigger.py +4 -4
  188. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  189. uncountable/types/api/uploader/invoke_uploader.py +4 -5
  190. uncountable/types/api/user/__init__.py +1 -0
  191. uncountable/types/api/user/get_current_user_info.py +40 -0
  192. uncountable/types/async_batch.py +1 -1
  193. uncountable/types/async_batch_processor.py +506 -23
  194. uncountable/types/async_batch_t.py +35 -8
  195. uncountable/types/async_jobs.py +0 -1
  196. uncountable/types/async_jobs_t.py +1 -2
  197. uncountable/types/auth_retrieval.py +0 -1
  198. uncountable/types/auth_retrieval_t.py +6 -6
  199. uncountable/types/base.py +0 -1
  200. uncountable/types/base_t.py +11 -9
  201. uncountable/types/calculations.py +0 -1
  202. uncountable/types/calculations_t.py +1 -2
  203. uncountable/types/chemical_structure.py +0 -1
  204. uncountable/types/chemical_structure_t.py +5 -5
  205. uncountable/types/client_base.py +614 -69
  206. uncountable/types/client_config.py +1 -1
  207. uncountable/types/client_config_t.py +13 -3
  208. uncountable/types/curves.py +0 -1
  209. uncountable/types/curves_t.py +6 -7
  210. uncountable/types/data.py +12 -0
  211. uncountable/types/data_t.py +103 -0
  212. uncountable/types/entity.py +1 -1
  213. uncountable/types/entity_t.py +90 -10
  214. uncountable/types/experiment_groups.py +0 -1
  215. uncountable/types/experiment_groups_t.py +1 -2
  216. uncountable/types/exports.py +8 -0
  217. uncountable/types/exports_t.py +34 -0
  218. uncountable/types/field_values.py +19 -1
  219. uncountable/types/field_values_t.py +242 -9
  220. uncountable/types/fields.py +0 -1
  221. uncountable/types/fields_t.py +1 -2
  222. uncountable/types/generic_upload.py +0 -1
  223. uncountable/types/generic_upload_t.py +14 -14
  224. uncountable/types/id_source.py +0 -1
  225. uncountable/types/id_source_t.py +13 -7
  226. uncountable/types/identifier.py +0 -1
  227. uncountable/types/identifier_t.py +10 -5
  228. uncountable/types/input_attributes.py +0 -1
  229. uncountable/types/input_attributes_t.py +3 -4
  230. uncountable/types/inputs.py +0 -1
  231. uncountable/types/inputs_t.py +3 -4
  232. uncountable/types/integration_server.py +0 -1
  233. uncountable/types/integration_server_t.py +13 -4
  234. uncountable/types/integration_session.py +10 -0
  235. uncountable/types/integration_session_t.py +60 -0
  236. uncountable/types/integrations.py +10 -0
  237. uncountable/types/integrations_t.py +62 -0
  238. uncountable/types/job_definition.py +2 -1
  239. uncountable/types/job_definition_t.py +57 -32
  240. uncountable/types/listing.py +9 -0
  241. uncountable/types/listing_t.py +51 -0
  242. uncountable/types/notices.py +8 -0
  243. uncountable/types/notices_t.py +37 -0
  244. uncountable/types/notifications.py +11 -0
  245. uncountable/types/notifications_t.py +74 -0
  246. uncountable/types/outputs.py +0 -1
  247. uncountable/types/outputs_t.py +2 -3
  248. uncountable/types/overrides.py +0 -1
  249. uncountable/types/overrides_t.py +10 -4
  250. uncountable/types/permissions.py +0 -1
  251. uncountable/types/permissions_t.py +1 -2
  252. uncountable/types/phases.py +0 -1
  253. uncountable/types/phases_t.py +1 -2
  254. uncountable/types/post_base.py +0 -1
  255. uncountable/types/post_base_t.py +1 -2
  256. uncountable/types/queued_job.py +2 -1
  257. uncountable/types/queued_job_t.py +29 -12
  258. uncountable/types/recipe_identifiers.py +0 -1
  259. uncountable/types/recipe_identifiers_t.py +18 -8
  260. uncountable/types/recipe_inputs.py +0 -1
  261. uncountable/types/recipe_inputs_t.py +1 -2
  262. uncountable/types/recipe_links.py +0 -1
  263. uncountable/types/recipe_links_t.py +3 -4
  264. uncountable/types/recipe_metadata.py +0 -1
  265. uncountable/types/recipe_metadata_t.py +9 -10
  266. uncountable/types/recipe_output_metadata.py +0 -1
  267. uncountable/types/recipe_output_metadata_t.py +1 -2
  268. uncountable/types/recipe_tags.py +0 -1
  269. uncountable/types/recipe_tags_t.py +1 -2
  270. uncountable/types/recipe_workflow_steps.py +0 -1
  271. uncountable/types/recipe_workflow_steps_t.py +7 -7
  272. uncountable/types/recipes.py +0 -1
  273. uncountable/types/recipes_t.py +2 -2
  274. uncountable/types/response.py +0 -1
  275. uncountable/types/response_t.py +2 -2
  276. uncountable/types/secret_retrieval.py +0 -1
  277. uncountable/types/secret_retrieval_t.py +7 -7
  278. uncountable/types/sockets.py +20 -0
  279. uncountable/types/sockets_t.py +169 -0
  280. uncountable/types/structured_filters.py +25 -0
  281. uncountable/types/structured_filters_t.py +248 -0
  282. uncountable/types/units.py +0 -1
  283. uncountable/types/units_t.py +1 -2
  284. uncountable/types/uploader.py +24 -0
  285. uncountable/types/uploader_t.py +222 -0
  286. uncountable/types/users.py +0 -1
  287. uncountable/types/users_t.py +1 -2
  288. uncountable/types/webhook_job.py +1 -1
  289. uncountable/types/webhook_job_t.py +14 -3
  290. uncountable/types/workflows.py +0 -1
  291. uncountable/types/workflows_t.py +3 -4
  292. uncountablepythonsdk-0.0.132.dist-info/METADATA +64 -0
  293. uncountablepythonsdk-0.0.132.dist-info/RECORD +363 -0
  294. {UncountablePythonSDK-0.0.82.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
  295. UncountablePythonSDK-0.0.82.dist-info/METADATA +0 -60
  296. UncountablePythonSDK-0.0.82.dist-info/RECORD +0 -292
  297. docs/quickstart.md +0 -19
  298. {UncountablePythonSDK-0.0.82.dist-info → uncountablepythonsdk-0.0.132.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,140 @@
1
+ from dataclasses import dataclass
2
+ from decimal import Decimal
3
+
4
+ from uncountable.integration.job import JobArguments, WebhookJob, register_job
5
+ from uncountable.types import (
6
+ base_t,
7
+ entity_t,
8
+ generic_upload_t,
9
+ identifier_t,
10
+ job_definition_t,
11
+ notifications_t,
12
+ uploader_t,
13
+ )
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class ParsePayload:
18
+ async_job_id: base_t.ObjectId
19
+
20
+
21
+ @register_job
22
+ class ParseExample(WebhookJob[ParsePayload]):
23
+ def run(
24
+ self, args: JobArguments, payload: ParsePayload
25
+ ) -> job_definition_t.JobResult:
26
+ user_id: base_t.ObjectId | None = None
27
+ recipe_id: base_t.ObjectId | None = None
28
+ file_name: str | None = None
29
+ data = args.client.get_entities_data(
30
+ entity_ids=[payload.async_job_id], entity_type=entity_t.EntityType.ASYNC_JOB
31
+ )
32
+ for field_value in data.entity_details[0].field_values:
33
+ if field_value.field_ref_name == "core_async_job_jobData":
34
+ assert isinstance(field_value.value, dict)
35
+ assert isinstance(field_value.value["user_id"], int)
36
+ user_id = field_value.value["user_id"]
37
+ elif (
38
+ field_value.field_ref_name
39
+ == "unc_async_job_custom_parser_recipe_ids_in_view"
40
+ ):
41
+ if field_value.value is None:
42
+ continue
43
+ assert isinstance(field_value.value, list)
44
+ if len(field_value.value) > 0:
45
+ assert isinstance(field_value.value[0], int)
46
+ recipe_id = field_value.value[0]
47
+ elif field_value.field_ref_name == "unc_async_job_custom_parser_input_file":
48
+ assert isinstance(field_value.value, list)
49
+ assert len(field_value.value) == 1
50
+ assert isinstance(field_value.value[0], dict)
51
+ assert isinstance(field_value.value[0]["name"], str)
52
+ file_name = field_value.value[0]["name"]
53
+
54
+ assert user_id is not None
55
+ assert file_name is not None
56
+
57
+ dummy_parsed_file_data: list[uploader_t.ParsedFileData] = [
58
+ uploader_t.ParsedFileData(
59
+ file_name=file_name,
60
+ file_structures=[
61
+ uploader_t.DataChannel(
62
+ type=uploader_t.StructureElementType.CHANNEL,
63
+ channel=uploader_t.TextChannelData(
64
+ name="column1",
65
+ type=uploader_t.ChannelType.TEXT_CHANNEL,
66
+ data=[
67
+ uploader_t.StringValue(value="value1"),
68
+ uploader_t.StringValue(value="value4"),
69
+ uploader_t.StringValue(value="value7"),
70
+ ],
71
+ ),
72
+ ),
73
+ uploader_t.DataChannel(
74
+ type=uploader_t.StructureElementType.CHANNEL,
75
+ channel=uploader_t.TextChannelData(
76
+ name="column2",
77
+ type=uploader_t.ChannelType.TEXT_CHANNEL,
78
+ data=[
79
+ uploader_t.StringValue(value="value2"),
80
+ uploader_t.StringValue(value="value5"),
81
+ uploader_t.StringValue(value="value8"),
82
+ ],
83
+ ),
84
+ ),
85
+ uploader_t.DataChannel(
86
+ type=uploader_t.StructureElementType.CHANNEL,
87
+ channel=uploader_t.TextChannelData(
88
+ name="column3",
89
+ type=uploader_t.ChannelType.TEXT_CHANNEL,
90
+ data=[
91
+ uploader_t.StringValue(value="value3"),
92
+ uploader_t.StringValue(value="value6"),
93
+ uploader_t.StringValue(value="value9"),
94
+ ],
95
+ ),
96
+ ),
97
+ uploader_t.HeaderEntry(
98
+ type=uploader_t.StructureElementType.HEADER,
99
+ value=uploader_t.TextHeaderData(
100
+ name="file_source",
101
+ type=uploader_t.HeaderType.TEXT_HEADER,
102
+ data=uploader_t.StringValue(value="my_file_to_upload.xlsx"),
103
+ ),
104
+ ),
105
+ uploader_t.HeaderEntry(
106
+ type=uploader_t.StructureElementType.HEADER,
107
+ value=uploader_t.NumericHeaderData(
108
+ name="file structure number",
109
+ data=uploader_t.DecimalValue(value=Decimal(99)),
110
+ ),
111
+ ),
112
+ ],
113
+ )
114
+ ]
115
+
116
+ complete_async_parse_req = args.batch_processor.complete_async_parse(
117
+ parsed_file_data=dummy_parsed_file_data,
118
+ async_job_key=identifier_t.IdentifierKeyId(id=payload.async_job_id),
119
+ upload_destination=generic_upload_t.UploadDestinationRecipe(
120
+ recipe_key=identifier_t.IdentifierKeyId(id=recipe_id or 1)
121
+ ),
122
+ )
123
+
124
+ args.batch_processor.push_notification(
125
+ depends_on=[complete_async_parse_req.batch_reference],
126
+ notification_targets=[
127
+ notifications_t.NotificationTargetUser(
128
+ user_key=identifier_t.IdentifierKeyId(id=user_id)
129
+ )
130
+ ],
131
+ subject="Upload complete",
132
+ message="Your file has been uploaded",
133
+ display_notice=True,
134
+ )
135
+
136
+ return job_definition_t.JobResult(success=True)
137
+
138
+ @property
139
+ def webhook_payload_type(self) -> type:
140
+ return ParsePayload
@@ -0,0 +1,61 @@
1
+ import random
2
+ from dataclasses import dataclass
3
+ from datetime import UTC, datetime
4
+ from decimal import Decimal
5
+
6
+ from uncountable.integration.job import JobArguments, WebhookJob, register_job
7
+ from uncountable.types import (
8
+ base_t,
9
+ identifier_t,
10
+ job_definition_t,
11
+ recipe_links_t,
12
+ set_recipe_outputs_t,
13
+ )
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class PredictionsPayload:
18
+ output_id: base_t.ObjectId
19
+ recipe_ids: list[base_t.ObjectId]
20
+
21
+
22
+ @register_job
23
+ class PredictionsExample(WebhookJob[PredictionsPayload]):
24
+ def run(
25
+ self, args: JobArguments, payload: PredictionsPayload
26
+ ) -> job_definition_t.JobResult:
27
+ recipe_data = args.client.get_recipes_data(recipe_ids=payload.recipe_ids)
28
+ formatted_datetime = datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S")
29
+
30
+ for recipe in recipe_data.recipes:
31
+ test_sample_name = f"Predictions Model ({formatted_datetime})"
32
+ created_recipe_id = args.client.create_recipe(
33
+ name=test_sample_name,
34
+ material_family_id=1,
35
+ workflow_id=1,
36
+ definition_key=identifier_t.IdentifierKeyRefName(
37
+ ref_name="unc_test_sample"
38
+ ),
39
+ ).result_id
40
+ args.client.set_recipe_outputs(
41
+ output_data=[
42
+ set_recipe_outputs_t.RecipeOutputValue(
43
+ recipe_id=created_recipe_id,
44
+ output_id=payload.output_id,
45
+ experiment_num=1,
46
+ value_numeric=Decimal(random.random() * 10),
47
+ )
48
+ ]
49
+ )
50
+ args.client.create_recipe_link(
51
+ recipe_from_key=identifier_t.IdentifierKeyId(id=recipe.recipe_id),
52
+ recipe_to_key=identifier_t.IdentifierKeyId(id=created_recipe_id),
53
+ link_type=recipe_links_t.RecipeLinkType.CHILD,
54
+ name=test_sample_name,
55
+ )
56
+
57
+ return job_definition_t.JobResult(success=True)
58
+
59
+ @property
60
+ def webhook_payload_type(self) -> type:
61
+ return PredictionsPayload
@@ -0,0 +1,39 @@
1
+ from io import BytesIO
2
+
3
+ from uncountable.core.file_upload import DataFileUpload, FileUpload
4
+ from uncountable.integration.job import JobArguments, RunsheetWebhookJob, register_job
5
+ from uncountable.types import (
6
+ download_file_t,
7
+ entity_t,
8
+ identifier_t,
9
+ webhook_job_t,
10
+ )
11
+
12
+
13
+ @register_job
14
+ class StandardRunsheetGenerator(RunsheetWebhookJob):
15
+ def build_runsheet(
16
+ self,
17
+ *,
18
+ args: JobArguments,
19
+ payload: webhook_job_t.RunsheetWebhookPayload,
20
+ ) -> FileUpload:
21
+ args.logger.log_info("Retrieving pre-exported runsheet file from async job")
22
+
23
+ file_query = download_file_t.FileDownloadQueryEntityField(
24
+ entity=entity_t.EntityIdentifier(
25
+ type=entity_t.EntityType.ASYNC_JOB,
26
+ identifier_key=identifier_t.IdentifierKeyId(id=payload.async_job_id),
27
+ ),
28
+ field_key=identifier_t.IdentifierKeyRefName(
29
+ ref_name="unc_async_job_export_runsheet_recipe_export"
30
+ ),
31
+ )
32
+
33
+ downloaded_files = args.client.download_files(file_query=file_query)
34
+
35
+ file_data = downloaded_files[0].data.read()
36
+ return DataFileUpload(
37
+ data=BytesIO(file_data),
38
+ name=downloaded_files[0].name,
39
+ )
@@ -1,15 +1,23 @@
1
- import typing
1
+ from dataclasses import dataclass
2
2
 
3
3
  from uncountable.integration.job import JobArguments, WebhookJob, register_job
4
- from uncountable.types.job_definition_t import JobResult
4
+ from uncountable.types import job_definition_t
5
5
 
6
6
 
7
- @register_job
8
- class WebhookExample(WebhookJob):
9
- @property
10
- def payload_type(self) -> typing.Any:
11
- return super().payload_type
7
+ @dataclass(kw_only=True)
8
+ class ExampleWebhookPayload:
9
+ id: int
10
+ message: str
11
+
12
12
 
13
- def run(self, args: JobArguments, payload: typing.Any) -> JobResult:
13
+ @register_job
14
+ class WebhookExample(WebhookJob[ExampleWebhookPayload]):
15
+ def run(
16
+ self, args: JobArguments, payload: ExampleWebhookPayload
17
+ ) -> job_definition_t.JobResult:
14
18
  args.logger.log_info(f"webhook invoked with payload: {payload}")
15
- return JobResult(success=True)
19
+ return job_definition_t.JobResult(success=True)
20
+
21
+ @property
22
+ def webhook_payload_type(self) -> type:
23
+ return ExampleWebhookPayload
@@ -22,6 +22,25 @@ jobs:
22
22
  - admin:
23
23
  type: ref_name
24
24
  ref_name: admin
25
+ - id: concurrent_cron_1
26
+ enabled: true
27
+ type: cron
28
+ name: MyConcurrentCron - 1
29
+ cron_spec: "* * * * *"
30
+ executor:
31
+ type: script
32
+ import_path: concurrent_cron
33
+ subqueue_name: subqueue_1
34
+ - id: concurrent_cron_2
35
+ enabled: true
36
+ type: cron
37
+ name: MyConcurrentCron - 2
38
+ cron_spec: "* * * * *"
39
+ executor:
40
+ type: script
41
+ import_path: concurrent_cron
42
+ subqueue_name: subqueue_2
43
+
25
44
 
26
45
  - id: example_wh1
27
46
  type: webhook
@@ -41,3 +60,45 @@ jobs:
41
60
  executor:
42
61
  type: script
43
62
  import_path: example_wh
63
+ - id: example_http
64
+ type: custom_http
65
+ name: Custom HTTP
66
+ executor:
67
+ type: script
68
+ import_path: example_http
69
+ - id: example_runsheet_wh
70
+ type: webhook
71
+ name: Runsheet Webhook
72
+ signature_key_secret:
73
+ type: env
74
+ env_key: WH_RUNSHEET_SIGNATURE_KEY
75
+ executor:
76
+ type: script
77
+ import_path: example_runsheet_wh
78
+ - id: example_instrument
79
+ type: webhook
80
+ name: Webhook Instrument Connection
81
+ signature_key_secret:
82
+ type: env
83
+ env_key: WH_INSTRUMENT_SIGNATURE_KEY
84
+ executor:
85
+ type: script
86
+ import_path: example_instrument
87
+ - id: example_predictions
88
+ type: webhook
89
+ name: Webook Predictions
90
+ signature_key_secret:
91
+ type: env
92
+ env_key: WH_PREDICTIONS_SIGNATURE_KEY
93
+ executor:
94
+ type: script
95
+ import_path: example_predictions
96
+ - id: example_parse
97
+ type: webhook
98
+ name: Webhook Parse
99
+ signature_key_secret:
100
+ type: env
101
+ env_key: WH_PARSE_SIGNATURE_KEY
102
+ executor:
103
+ type: script
104
+ import_path: example_parse
@@ -9,14 +9,16 @@ dependencies = [
9
9
  "ruff == 0.*",
10
10
  "openpyxl == 3.*",
11
11
  "more_itertools == 10.*",
12
- "types-paramiko ==3.5.0.20240918",
12
+ "types-paramiko ==4.0.0.20250822",
13
13
  "types-openpyxl == 3.*",
14
14
  "types-pysftp == 0.*",
15
- "types-pytz == 2024.*",
15
+ "types-pytz ==2025.*",
16
16
  "types-requests == 2.*",
17
17
  "types-simplejson == 3.*",
18
18
  "pandas-stubs",
19
- "xlrd == 2.*"
19
+ "xlrd == 2.*",
20
+ "msgspec == 0.19.*",
21
+ "websockets==15.0.1",
20
22
  ]
21
23
 
22
24
  [tool.mypy]
@@ -113,7 +115,6 @@ lint.ignore = [
113
115
  "PD010", # .pivottable. Should add
114
116
  "PD011", # use .to_numpy. Skip
115
117
  "PD015", # use .merge. Should add
116
- "PD901", # avoid generic df name. Skip
117
118
  "PERF203", # avoid try except in loop. Skip
118
119
  "PERF401", # use list comprehension. Skip
119
120
  "PERF402", # use list.copy. Skip
@@ -173,9 +174,9 @@ lint.ignore = [
173
174
  "SLOT000", # subclass of string should define slot. should add
174
175
  "T201", # print. skip
175
176
  "T203", # pprint. skip
176
- "TCH001", # move application import into type checking. skip
177
- "TCH002", # move third party import into type checking. consider
178
- "TCH003", # move library into type checking lbock. consider
177
+ "TC001", # move application import into type checking. skip
178
+ "TC002", # move third party import into type checking. consider
179
+ "TC003", # move library into type checking lbock. consider
179
180
  "TD", # todo comments. skip
180
181
  "TID252", # eliminate relative imports from parents. skip
181
182
  "TRY002", # create your own exception. skip
@@ -193,11 +194,10 @@ lint.ignore = [
193
194
  "RUF022", # __all__ is not sorted. skip due to isort complication
194
195
  "UP017", # use datetime.UTC, TODO add back in
195
196
  "UP035", # replacing List with list, TODO add back in
196
- "UP038", # isinstance X | Y instead of (X, Y), TODO add back in
197
197
  # ## FROM RUFF UPGRADE
198
198
  "PLC2701", # private name imports. should add
199
199
  "PLR1702", # too many nested blocks -- add with config. skip
200
- "RUF025", # unnecessary dict comprehension. skip
200
+ "C420", # unnecessary dict comprehension. skip
201
201
  "SIM113", # index variable should use enumerate. should add.
202
202
  "PLR0914" # too many local variables. configure below. should add with config
203
203
  ]
@@ -212,6 +212,7 @@ exclude = [
212
212
 
213
213
  [tool.ruff.lint.isort]
214
214
  split-on-trailing-comma = true
215
+ known-first-party = ["pkgs"]
215
216
 
216
217
  [tool.ruff.lint.mccabe]
217
218
  max-complexity = 130 # goal would be to bring this down to ~50 or so
@@ -221,4 +222,3 @@ max-locals=50
221
222
 
222
223
  [tool.setuptools]
223
224
  py-modules = []
224
-
examples/oauth.py ADDED
@@ -0,0 +1,7 @@
1
+ from uncountable.core.client import Client
2
+ from uncountable.core.types import AuthDetailsOAuth
3
+
4
+ client = Client(
5
+ base_url="https://app.uncountable.com",
6
+ auth_details=AuthDetailsOAuth(refresh_token="x"),
7
+ )
@@ -18,7 +18,7 @@ client = Client(
18
18
  )
19
19
  uploaded_file = client.upload_files(
20
20
  file_uploads=[
21
- MediaFileUpload(path="/home/logan/Downloads/my_file_to_upload.csv"),
21
+ MediaFileUpload(path="Downloads/my_file_to_upload.csv"),
22
22
  ]
23
23
  )[0]
24
24
 
examples/upload_files.py CHANGED
@@ -12,8 +12,7 @@ client = Client(
12
12
  )
13
13
  uploaded = client.upload_files(
14
14
  file_uploads=[
15
- MediaFileUpload(path="/my/file/path.doc"),
16
- MediaFileUpload(path="/my/file/path2.doc"),
15
+ MediaFileUpload(path="Downloads/file"),
17
16
  ]
18
17
  )
19
18
  pprint(uploaded)
@@ -1,7 +1,15 @@
1
+ from ._is_enum import is_string_enum_class as is_string_enum_class
1
2
  from .argument_parser import CachedParser as CachedParser
3
+ from .argument_parser import ParserBase as ParserBase
4
+ from .argument_parser import ParserError as ParserError
5
+ from .argument_parser import ParserExtraFieldsError as ParserExtraFieldsError
2
6
  from .argument_parser import ParserFunction as ParserFunction
3
7
  from .argument_parser import ParserOptions as ParserOptions
8
+ from .argument_parser import SourceEncoding as SourceEncoding
4
9
  from .argument_parser import build_parser as build_parser
10
+ from .argument_parser import is_missing as is_missing
11
+ from .argument_parser import is_optional as is_optional
12
+ from .argument_parser import is_union as is_union
5
13
  from .case_convert import camel_to_snake_case as camel_to_snake_case
6
14
  from .case_convert import kebab_to_pascal_case as kebab_to_pascal_case
7
15
  from .case_convert import snake_to_camel_case as snake_to_camel_case
@@ -5,6 +5,9 @@ def is_namedtuple_type(x: Any) -> bool:
5
5
  if not hasattr(x, "__annotations__"):
6
6
  return False
7
7
 
8
+ if not hasattr(x, "__bases__"):
9
+ return False
10
+
8
11
  b = x.__bases__
9
12
  if len(b) != 1 or b[0] is not tuple:
10
13
  return False