UncountablePythonSDK 0.0.8__py3-none-any.whl → 0.0.92__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

Files changed (312) hide show
  1. UncountablePythonSDK-0.0.92.dist-info/METADATA +61 -0
  2. UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
  3. {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
  4. {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
  5. docs/.gitignore +1 -0
  6. docs/conf.py +57 -0
  7. docs/index.md +13 -0
  8. docs/justfile +12 -0
  9. docs/quickstart.md +19 -0
  10. docs/requirements.txt +7 -0
  11. docs/static/favicons/android-chrome-192x192.png +0 -0
  12. docs/static/favicons/android-chrome-512x512.png +0 -0
  13. docs/static/favicons/apple-touch-icon.png +0 -0
  14. docs/static/favicons/browserconfig.xml +9 -0
  15. docs/static/favicons/favicon-16x16.png +0 -0
  16. docs/static/favicons/favicon-32x32.png +0 -0
  17. docs/static/favicons/manifest.json +18 -0
  18. docs/static/favicons/mstile-150x150.png +0 -0
  19. docs/static/favicons/safari-pinned-tab.svg +32 -0
  20. docs/static/logo_blue.png +0 -0
  21. examples/async_batch.py +35 -0
  22. examples/create_entity.py +22 -17
  23. examples/download_files.py +26 -0
  24. examples/edit_recipe_inputs.py +50 -0
  25. examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
  26. examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
  27. examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
  28. examples/integration-server/pyproject.toml +224 -0
  29. examples/invoke_uploader.py +26 -0
  30. examples/set_recipe_metadata_file.py +40 -0
  31. examples/set_recipe_output_file_sdk.py +26 -0
  32. examples/upload_files.py +18 -0
  33. pkgs/argument_parser/__init__.py +5 -0
  34. pkgs/argument_parser/_is_enum.py +1 -6
  35. pkgs/argument_parser/argument_parser.py +232 -76
  36. pkgs/argument_parser/case_convert.py +4 -3
  37. pkgs/filesystem_utils/__init__.py +20 -0
  38. pkgs/filesystem_utils/_blob_session.py +137 -0
  39. pkgs/filesystem_utils/_gdrive_session.py +309 -0
  40. pkgs/filesystem_utils/_local_session.py +69 -0
  41. pkgs/filesystem_utils/_s3_session.py +117 -0
  42. pkgs/filesystem_utils/_sftp_session.py +147 -0
  43. pkgs/filesystem_utils/file_type_utils.py +91 -0
  44. pkgs/filesystem_utils/filesystem_session.py +39 -0
  45. pkgs/py.typed +0 -0
  46. pkgs/serialization/__init__.py +8 -1
  47. pkgs/serialization/annotation.py +64 -0
  48. pkgs/serialization/missing_sentry.py +1 -1
  49. pkgs/serialization/opaque_key.py +1 -1
  50. pkgs/serialization/serial_alias.py +47 -0
  51. pkgs/serialization/serial_class.py +65 -50
  52. pkgs/serialization/serial_generic.py +16 -0
  53. pkgs/serialization/serial_union.py +84 -0
  54. pkgs/serialization/yaml.py +57 -0
  55. pkgs/serialization_util/__init__.py +7 -7
  56. pkgs/serialization_util/_get_type_for_serialization.py +1 -3
  57. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  58. pkgs/serialization_util/dataclasses.py +14 -0
  59. pkgs/serialization_util/serialization_helpers.py +116 -74
  60. pkgs/strenum_compat/strenum_compat.py +1 -9
  61. pkgs/type_spec/actions_registry/__init__.py +0 -0
  62. pkgs/type_spec/actions_registry/__main__.py +126 -0
  63. pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
  64. pkgs/type_spec/builder.py +475 -89
  65. pkgs/type_spec/config.py +24 -19
  66. pkgs/type_spec/emit_io_ts.py +5 -2
  67. pkgs/type_spec/emit_open_api.py +266 -32
  68. pkgs/type_spec/emit_open_api_util.py +32 -13
  69. pkgs/type_spec/emit_python.py +599 -151
  70. pkgs/type_spec/emit_typescript.py +74 -273
  71. pkgs/type_spec/emit_typescript_util.py +239 -5
  72. pkgs/type_spec/load_types.py +55 -10
  73. pkgs/type_spec/open_api_util.py +30 -41
  74. pkgs/type_spec/parts/base.py.prepart +4 -3
  75. pkgs/type_spec/type_info/emit_type_info.py +178 -16
  76. pkgs/type_spec/util.py +11 -11
  77. pkgs/type_spec/value_spec/__main__.py +3 -3
  78. pkgs/type_spec/value_spec/convert_type.py +8 -1
  79. pkgs/type_spec/value_spec/emit_python.py +13 -4
  80. uncountable/__init__.py +1 -2
  81. uncountable/core/__init__.py +12 -2
  82. uncountable/core/async_batch.py +37 -0
  83. uncountable/core/client.py +293 -43
  84. uncountable/core/environment.py +41 -0
  85. uncountable/core/file_upload.py +135 -0
  86. uncountable/core/types.py +17 -0
  87. uncountable/integration/__init__.py +0 -0
  88. uncountable/integration/cli.py +49 -0
  89. uncountable/integration/construct_client.py +51 -0
  90. uncountable/integration/cron.py +29 -0
  91. uncountable/integration/db/__init__.py +0 -0
  92. uncountable/integration/db/connect.py +18 -0
  93. uncountable/integration/db/session.py +25 -0
  94. uncountable/integration/entrypoint.py +13 -0
  95. uncountable/integration/executors/__init__.py +0 -0
  96. uncountable/integration/executors/executors.py +148 -0
  97. uncountable/integration/executors/generic_upload_executor.py +284 -0
  98. uncountable/integration/executors/script_executor.py +25 -0
  99. uncountable/integration/job.py +87 -0
  100. uncountable/integration/queue_runner/__init__.py +0 -0
  101. uncountable/integration/queue_runner/command_server/__init__.py +24 -0
  102. uncountable/integration/queue_runner/command_server/command_client.py +68 -0
  103. uncountable/integration/queue_runner/command_server/command_server.py +64 -0
  104. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  105. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
  108. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
  109. uncountable/integration/queue_runner/command_server/types.py +52 -0
  110. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  111. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
  112. uncountable/integration/queue_runner/datastore/interface.py +19 -0
  113. uncountable/integration/queue_runner/datastore/model.py +17 -0
  114. uncountable/integration/queue_runner/job_scheduler.py +163 -0
  115. uncountable/integration/queue_runner/queue_runner.py +26 -0
  116. uncountable/integration/queue_runner/types.py +7 -0
  117. uncountable/integration/queue_runner/worker.py +119 -0
  118. uncountable/integration/scan_profiles.py +67 -0
  119. uncountable/integration/scheduler.py +150 -0
  120. uncountable/integration/secret_retrieval/__init__.py +3 -0
  121. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  122. uncountable/integration/server.py +117 -0
  123. uncountable/integration/telemetry.py +209 -0
  124. uncountable/integration/webhook_server/entrypoint.py +170 -0
  125. uncountable/types/__init__.py +136 -20
  126. uncountable/types/api/batch/execute_batch.py +15 -7
  127. uncountable/types/api/batch/execute_batch_load_async.py +42 -0
  128. uncountable/types/api/chemical/__init__.py +1 -0
  129. uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
  130. uncountable/types/api/entity/create_entities.py +23 -11
  131. uncountable/types/api/entity/create_entity.py +21 -12
  132. uncountable/types/api/entity/get_entities_data.py +18 -8
  133. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  134. uncountable/types/api/entity/list_entities.py +27 -12
  135. uncountable/types/api/entity/lock_entity.py +45 -0
  136. uncountable/types/api/entity/resolve_entity_ids.py +17 -7
  137. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  138. uncountable/types/api/entity/set_values.py +14 -7
  139. uncountable/types/api/entity/transition_entity_phase.py +80 -0
  140. uncountable/types/api/entity/unlock_entity.py +44 -0
  141. uncountable/types/api/equipment/__init__.py +1 -0
  142. uncountable/types/api/equipment/associate_equipment_input.py +44 -0
  143. uncountable/types/api/field_options/__init__.py +1 -0
  144. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  145. uncountable/types/api/files/__init__.py +1 -0
  146. uncountable/types/api/files/download_file.py +77 -0
  147. uncountable/types/api/id_source/__init__.py +1 -0
  148. uncountable/types/api/id_source/list_id_source.py +56 -0
  149. uncountable/types/api/id_source/match_id_source.py +54 -0
  150. uncountable/types/api/input_groups/get_input_group_names.py +16 -6
  151. uncountable/types/api/inputs/create_inputs.py +24 -11
  152. uncountable/types/api/inputs/get_input_data.py +32 -13
  153. uncountable/types/api/inputs/get_input_names.py +18 -8
  154. uncountable/types/api/inputs/get_inputs_data.py +29 -10
  155. uncountable/types/api/inputs/set_input_attribute_values.py +16 -9
  156. uncountable/types/api/inputs/set_input_category.py +44 -0
  157. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  158. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  159. uncountable/types/api/material_families/__init__.py +1 -0
  160. uncountable/types/api/material_families/update_entity_material_families.py +48 -0
  161. uncountable/types/api/outputs/get_output_data.py +32 -16
  162. uncountable/types/api/outputs/get_output_names.py +18 -8
  163. uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
  164. uncountable/types/api/permissions/__init__.py +1 -0
  165. uncountable/types/api/permissions/set_core_permissions.py +105 -0
  166. uncountable/types/api/project/get_projects.py +17 -7
  167. uncountable/types/api/project/get_projects_data.py +21 -11
  168. uncountable/types/api/recipe_links/__init__.py +1 -0
  169. uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
  170. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  171. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +18 -8
  172. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  173. uncountable/types/api/recipes/archive_recipes.py +42 -0
  174. uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
  175. uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
  176. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  177. uncountable/types/api/recipes/create_recipe.py +51 -0
  178. uncountable/types/api/recipes/create_recipes.py +25 -12
  179. uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
  180. uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
  181. uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
  182. uncountable/types/api/recipes/get_curve.py +15 -7
  183. uncountable/types/api/recipes/get_recipe_calculations.py +17 -10
  184. uncountable/types/api/recipes/get_recipe_links.py +13 -6
  185. uncountable/types/api/recipes/get_recipe_names.py +16 -6
  186. uncountable/types/api/recipes/get_recipe_output_metadata.py +14 -7
  187. uncountable/types/api/recipes/get_recipes_data.py +63 -38
  188. uncountable/types/api/recipes/lock_recipes.py +63 -0
  189. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  190. uncountable/types/api/recipes/set_recipe_inputs.py +19 -10
  191. uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
  192. uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
  193. uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
  194. uncountable/types/api/recipes/set_recipe_outputs.py +26 -12
  195. uncountable/types/api/recipes/set_recipe_tags.py +109 -0
  196. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  197. uncountable/types/api/recipes/unlock_recipes.py +50 -0
  198. uncountable/types/api/triggers/__init__.py +1 -0
  199. uncountable/types/api/triggers/run_trigger.py +43 -0
  200. uncountable/types/api/uploader/__init__.py +1 -0
  201. uncountable/types/api/uploader/invoke_uploader.py +47 -0
  202. uncountable/types/async_batch.py +13 -0
  203. uncountable/types/async_batch_processor.py +384 -0
  204. uncountable/types/async_batch_t.py +97 -0
  205. uncountable/types/async_jobs.py +9 -0
  206. uncountable/types/async_jobs_t.py +53 -0
  207. uncountable/types/auth_retrieval.py +12 -0
  208. uncountable/types/auth_retrieval_t.py +75 -0
  209. uncountable/types/base.py +5 -78
  210. uncountable/types/base_t.py +85 -0
  211. uncountable/types/calculations.py +3 -18
  212. uncountable/types/calculations_t.py +27 -0
  213. uncountable/types/chemical_structure.py +8 -0
  214. uncountable/types/chemical_structure_t.py +28 -0
  215. uncountable/types/client_base.py +1093 -54
  216. uncountable/types/client_config.py +8 -0
  217. uncountable/types/client_config_t.py +26 -0
  218. uncountable/types/curves.py +5 -42
  219. uncountable/types/curves_t.py +51 -0
  220. uncountable/types/entity.py +8 -269
  221. uncountable/types/entity_t.py +393 -0
  222. uncountable/types/experiment_groups.py +3 -18
  223. uncountable/types/experiment_groups_t.py +27 -0
  224. uncountable/types/field_values.py +17 -60
  225. uncountable/types/field_values_t.py +204 -0
  226. uncountable/types/fields.py +3 -19
  227. uncountable/types/fields_t.py +28 -0
  228. uncountable/types/generic_upload.py +15 -0
  229. uncountable/types/generic_upload_t.py +119 -0
  230. uncountable/types/id_source.py +12 -0
  231. uncountable/types/id_source_t.py +68 -0
  232. uncountable/types/identifier.py +11 -0
  233. uncountable/types/identifier_t.py +63 -0
  234. uncountable/types/input_attributes.py +3 -24
  235. uncountable/types/input_attributes_t.py +30 -0
  236. uncountable/types/inputs.py +6 -56
  237. uncountable/types/inputs_t.py +83 -0
  238. uncountable/types/integration_server.py +9 -0
  239. uncountable/types/integration_server_t.py +42 -0
  240. uncountable/types/job_definition.py +27 -0
  241. uncountable/types/job_definition_t.py +260 -0
  242. uncountable/types/outputs.py +3 -21
  243. uncountable/types/outputs_t.py +30 -0
  244. uncountable/types/overrides.py +10 -0
  245. uncountable/types/overrides_t.py +49 -0
  246. uncountable/types/permissions.py +8 -0
  247. uncountable/types/permissions_t.py +46 -0
  248. uncountable/types/phases.py +3 -18
  249. uncountable/types/phases_t.py +27 -0
  250. uncountable/types/post_base.py +8 -0
  251. uncountable/types/post_base_t.py +30 -0
  252. uncountable/types/queued_job.py +16 -0
  253. uncountable/types/queued_job_t.py +123 -0
  254. uncountable/types/recipe_identifiers.py +12 -0
  255. uncountable/types/recipe_identifiers_t.py +76 -0
  256. uncountable/types/recipe_inputs.py +9 -0
  257. uncountable/types/recipe_inputs_t.py +30 -0
  258. uncountable/types/recipe_links.py +4 -45
  259. uncountable/types/recipe_links_t.py +54 -0
  260. uncountable/types/recipe_metadata.py +5 -45
  261. uncountable/types/recipe_metadata_t.py +58 -0
  262. uncountable/types/recipe_output_metadata.py +3 -19
  263. uncountable/types/recipe_output_metadata_t.py +28 -0
  264. uncountable/types/recipe_tags.py +3 -18
  265. uncountable/types/recipe_tags_t.py +27 -0
  266. uncountable/types/recipe_workflow_steps.py +14 -0
  267. uncountable/types/recipe_workflow_steps_t.py +95 -0
  268. uncountable/types/recipes.py +8 -0
  269. uncountable/types/recipes_t.py +25 -0
  270. uncountable/types/response.py +3 -20
  271. uncountable/types/response_t.py +26 -0
  272. uncountable/types/secret_retrieval.py +12 -0
  273. uncountable/types/secret_retrieval_t.py +75 -0
  274. uncountable/types/units.py +3 -18
  275. uncountable/types/units_t.py +27 -0
  276. uncountable/types/users.py +3 -19
  277. uncountable/types/users_t.py +28 -0
  278. uncountable/types/webhook_job.py +9 -0
  279. uncountable/types/webhook_job_t.py +37 -0
  280. uncountable/types/workflows.py +4 -27
  281. uncountable/types/workflows_t.py +39 -0
  282. UncountablePythonSDK-0.0.8.dist-info/METADATA +0 -27
  283. UncountablePythonSDK-0.0.8.dist-info/RECORD +0 -134
  284. examples/recipe-import/importer.py +0 -39
  285. type_spec/external/api/batch/execute_batch.yaml +0 -56
  286. type_spec/external/api/entity/create_entities.yaml +0 -33
  287. type_spec/external/api/entity/create_entity.yaml +0 -39
  288. type_spec/external/api/entity/get_entities_data.yaml +0 -29
  289. type_spec/external/api/entity/list_entities.yaml +0 -52
  290. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  291. type_spec/external/api/entity/set_values.yaml +0 -18
  292. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  293. type_spec/external/api/inputs/create_inputs.yaml +0 -48
  294. type_spec/external/api/inputs/get_input_data.yaml +0 -95
  295. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  296. type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
  297. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
  298. type_spec/external/api/outputs/get_output_data.yaml +0 -92
  299. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  300. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  301. type_spec/external/api/project/get_projects.yaml +0 -42
  302. type_spec/external/api/project/get_projects_data.yaml +0 -50
  303. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  304. type_spec/external/api/recipes/create_recipes.yaml +0 -47
  305. type_spec/external/api/recipes/get_curve.yaml +0 -18
  306. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
  307. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  308. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  309. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
  310. type_spec/external/api/recipes/get_recipes_data.yaml +0 -238
  311. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
  312. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
@@ -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)
@@ -1,27 +1,35 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from typing import Any, Callable, Optional, TypeVar, cast
4
+ from collections.abc import Callable
5
+ from enum import StrEnum
6
+ from typing import Any, TypeVar, cast
5
7
 
6
- from pkgs.strenum_compat import StrEnum
8
+ from .annotation import SerialBase, SerialInspector
7
9
 
8
10
  _ClassT = TypeVar("_ClassT")
9
11
 
10
12
 
11
- @dataclasses.dataclass
12
- class _SerialClassData:
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,
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,
25
33
  ) -> Callable[[_ClassT], _ClassT]:
26
34
  """
27
35
  An additional decorator to a dataclass that specifies serialization options.
@@ -42,6 +50,8 @@ 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
57
  def decorate(orig_class: _ClassT) -> _ClassT:
@@ -50,65 +60,70 @@ def serial_class(
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,7 +134,7 @@ class _SerialStringEnumData:
119
134
 
120
135
 
121
136
  def serial_string_enum(
122
- *, labels: Optional[dict[str, str]] = None, deprecated: Optional[set[str]] = None
137
+ *, labels: dict[str, str] | None = None, deprecated: set[str] | None = None
123
138
  ) -> Callable[[_ClassT], _ClassT]:
124
139
  """
125
140
  A decorator for enums to provide serialization data, including labels.
@@ -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
@@ -0,0 +1,84 @@
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
+ class IdentityHashWrapper(typing.Generic[T]):
10
+ """This allows unhashable types to be used in the SerialUnion, like dict.
11
+ Since we have only one copy of the types themselves, we rely on
12
+ object identity for the hashing."""
13
+
14
+ def __init__(self, inner: T) -> None:
15
+ self.inner = inner
16
+
17
+ def __hash__(self) -> int:
18
+ return id(self.inner)
19
+
20
+
21
+ @dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
22
+ class _SerialUnion(SerialBase):
23
+ """
24
+ This class is to be kept private, to provide flexibility in registration/lookup.
25
+ Places that need the data should access it via help classes/methods.
26
+ """
27
+
28
+ # If specified, indicates the Union has a discriminator which should be used to
29
+ # determine which type to parse.
30
+ discriminator: str | None = None
31
+ discriminator_map: IdentityHashWrapper[dict[str, type]] | None = None
32
+
33
+
34
+ def serial_union_annotation(
35
+ *,
36
+ discriminator: str | None = None,
37
+ discriminator_map: dict[str, type] | None = None,
38
+ named_type_path: str | None = None,
39
+ is_dynamic_allowed: bool = False,
40
+ ) -> _SerialUnion:
41
+ return _SerialUnion(
42
+ discriminator=discriminator,
43
+ discriminator_map=IdentityHashWrapper(discriminator_map)
44
+ if discriminator_map is not None
45
+ else None,
46
+ named_type_path=named_type_path,
47
+ from_decorator=True,
48
+ is_dynamic_allowed=is_dynamic_allowed,
49
+ )
50
+
51
+
52
+ def _get_serial_union(parsed_type: type[T]) -> _SerialUnion | None:
53
+ serial = get_serial_annotation(parsed_type)
54
+ if not isinstance(serial, _SerialUnion):
55
+ return None
56
+ return serial
57
+
58
+
59
+ class SerialClassInspector(SerialInspector[T]):
60
+ def __init__(self, parsed_type: type[T], serial_union: _SerialUnion) -> None:
61
+ super().__init__(parsed_type, serial_union)
62
+ self._parsed_type = parsed_type
63
+ self._serial_union = serial_union
64
+
65
+ def get_union_underlying(self) -> type[T]:
66
+ return typing.get_args(self._parsed_type)[0] # type:ignore[no-any-return]
67
+
68
+ @property
69
+ def discriminator(self) -> str | None:
70
+ return self._serial_union.discriminator
71
+
72
+ @property
73
+ def discriminator_map(self) -> dict[str, type] | None:
74
+ if self._serial_union.discriminator_map is None:
75
+ return None
76
+ return self._serial_union.discriminator_map.inner
77
+
78
+
79
+ def get_serial_union_data(parsed_type: type[T]) -> SerialClassInspector[T] | None:
80
+ serial = _get_serial_union(parsed_type)
81
+ if serial is None:
82
+ return None
83
+
84
+ return SerialClassInspector(parsed_type, serial)
@@ -0,0 +1,57 @@
1
+ from decimal import Decimal
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ import yaml
5
+
6
+ if TYPE_CHECKING:
7
+ from _typeshed import SupportsRead as SupportsReadT
8
+ from _typeshed import SupportsWrite as SupportsWriteT
9
+
10
+ SupportsRead = SupportsReadT[Any]
11
+ SupportsWrite = SupportsWriteT[Any]
12
+ else:
13
+ SupportsRead = object
14
+ SupportsWrite = object
15
+
16
+
17
+ def _decimal_constructor(loader, node): # type:ignore
18
+ value = loader.construct_scalar(node)
19
+ return Decimal(value)
20
+
21
+
22
+ # A semi-acceptable patch to force a number to be parsed as a decimal, since pyyaml
23
+ # parses them as lossy floats otherwise. Though a bit ugly, at least this way we have
24
+ # support for decimal constants
25
+ yaml.SafeLoader.add_constructor("!decimal", _decimal_constructor)
26
+
27
+
28
+ class YAMLError(BaseException):
29
+ pass
30
+
31
+
32
+ def dumps(obj: Any, sort_keys: bool = False) -> str:
33
+ return yaml.dump(obj, sort_keys=sort_keys)
34
+
35
+
36
+ def dump(obj: Any, f: SupportsWrite, sort_keys: bool = False) -> None:
37
+ yaml.dump(obj, f, sort_keys=sort_keys)
38
+
39
+
40
+ def safe_load(src: str | bytes | SupportsRead) -> Any:
41
+ try:
42
+ return yaml.safe_load(src)
43
+ except yaml.YAMLError as e:
44
+ raise YAMLError() from e
45
+
46
+
47
+ def safe_dump(
48
+ obj: Any,
49
+ sort_keys: bool = False,
50
+ indent: int | None = None,
51
+ width: int | None = None,
52
+ ) -> str:
53
+ return yaml.safe_dump(obj, sort_keys=sort_keys, indent=indent, width=width)
54
+
55
+
56
+ def c_load(f: SupportsRead) -> Any:
57
+ return yaml.load(f, Loader=yaml.CLoader)
@@ -1,8 +1,8 @@
1
+ from .convert_to_snakecase import convert_dict_to_snake_case
2
+ from .dataclasses import dict_fields as dict_fields
3
+ from .dataclasses import iterate_fields as iterate_fields
1
4
  from .serialization_helpers import (
2
- convert_dict_to_snake_case,
3
- convert_to_camelcase,
4
- resolve_missing_to_none,
5
- serialize,
5
+ JsonValue,
6
6
  serialize_for_api,
7
7
  serialize_for_storage,
8
8
  serialize_for_storage_dict,
@@ -10,10 +10,10 @@ from .serialization_helpers import (
10
10
 
11
11
  __all__: list[str] = [
12
12
  "convert_dict_to_snake_case",
13
- "convert_to_camelcase",
14
- "resolve_missing_to_none",
15
- "serialize",
16
13
  "serialize_for_api",
17
14
  "serialize_for_storage",
18
15
  "serialize_for_storage_dict",
16
+ "iterate_fields",
17
+ "dict_fields",
18
+ "JsonValue",
19
19
  ]
@@ -1,11 +1,9 @@
1
1
  import dataclasses
2
2
  import datetime
3
3
  import functools
4
- from enum import Enum
4
+ from enum import Enum, StrEnum
5
5
  from typing import Any
6
6
 
7
- from pkgs.strenum_compat import StrEnum
8
-
9
7
 
10
8
  class SerializationType(StrEnum):
11
9
  NAMED_TUPLE = "NAMED_TUPLE"
@@ -0,0 +1,27 @@
1
+ from typing import (
2
+ Any,
3
+ )
4
+
5
+ from pkgs.argument_parser import camel_to_snake_case
6
+ from pkgs.serialization import (
7
+ MISSING_SENTRY,
8
+ OpaqueKey,
9
+ )
10
+
11
+
12
+ def _key_convert_to_snake_case(o: Any) -> Any:
13
+ if isinstance(o, OpaqueKey):
14
+ return o
15
+ if isinstance(o, str):
16
+ return camel_to_snake_case(o)
17
+ return o
18
+
19
+
20
+ def convert_dict_to_snake_case(data: Any) -> Any:
21
+ return {
22
+ _key_convert_to_snake_case(k): convert_dict_to_snake_case(v)
23
+ if isinstance(v, dict)
24
+ else v
25
+ for k, v in data.items()
26
+ if v != MISSING_SENTRY
27
+ }