autoassembler 0.3.1__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.
Files changed (198) hide show
  1. autoassembler/.fernignore +7 -0
  2. autoassembler/__init__.py +537 -0
  3. autoassembler/_custom/__init__.py +56 -0
  4. autoassembler/_custom/assemblies.py +226 -0
  5. autoassembler/_custom/assemblies_api.py +188 -0
  6. autoassembler/_custom/credentials.py +248 -0
  7. autoassembler/_custom/http.py +42 -0
  8. autoassembler/_custom/operations.py +258 -0
  9. autoassembler/_custom/planning.py +598 -0
  10. autoassembler/_custom/planning_api.py +225 -0
  11. autoassembler/_default_clients.py +32 -0
  12. autoassembler/_version.py +8 -0
  13. autoassembler/alpha/__init__.py +63 -0
  14. autoassembler/alpha/client.py +82 -0
  15. autoassembler/alpha/jobs/__init__.py +39 -0
  16. autoassembler/alpha/jobs/client.py +338 -0
  17. autoassembler/alpha/jobs/raw_client.py +641 -0
  18. autoassembler/alpha/jobs/types/__init__.py +42 -0
  19. autoassembler/alpha/jobs/types/cancel_job_jobs_response.py +12 -0
  20. autoassembler/alpha/jobs/types/list_jobs_jobs_response.py +30 -0
  21. autoassembler/alpha/jobs/types/list_jobs_jobs_response_pagination.py +22 -0
  22. autoassembler/alpha/jobs/types/retry_job_jobs_response.py +16 -0
  23. autoassembler/alpha/planning/__init__.py +49 -0
  24. autoassembler/alpha/planning/client.py +201 -0
  25. autoassembler/alpha/planning/raw_client.py +268 -0
  26. autoassembler/alpha/planning/types/__init__.py +47 -0
  27. autoassembler/alpha/planning/types/apply_ops_planning_request_namespace.py +5 -0
  28. autoassembler/alpha/planning/types/apply_ops_planning_request_ops_item.py +13 -0
  29. autoassembler/alpha/planning/types/apply_ops_planning_request_ops_item_op.py +58 -0
  30. autoassembler/alpha/planning/types/apply_ops_planning_response.py +13 -0
  31. autoassembler/alpha/raw_client.py +13 -0
  32. autoassembler/assemblies/__init__.py +73 -0
  33. autoassembler/assemblies/client.py +1177 -0
  34. autoassembler/assemblies/raw_client.py +1557 -0
  35. autoassembler/assemblies/types/__init__.py +71 -0
  36. autoassembler/assemblies/types/delete_assembly_response.py +12 -0
  37. autoassembler/assemblies/types/download_assembly_cad_request_namespace.py +5 -0
  38. autoassembler/assemblies/types/get_assembly_info_request_namespace.py +5 -0
  39. autoassembler/assemblies/types/get_status_request_namespace.py +5 -0
  40. autoassembler/assemblies/types/get_status_response.py +16 -0
  41. autoassembler/assemblies/types/get_status_response_progress.py +13 -0
  42. autoassembler/assemblies/types/get_status_response_status.py +7 -0
  43. autoassembler/assemblies/types/get_structure_request_namespace.py +5 -0
  44. autoassembler/assemblies/types/list_assembly_versions_request_namespace.py +5 -0
  45. autoassembler/assemblies/types/list_connections_request_namespace.py +5 -0
  46. autoassembler/assemblies/types/process_request_namespace.py +5 -0
  47. autoassembler/assemblies/types/update_assembly_info_request_namespace.py +5 -0
  48. autoassembler/auth/__init__.py +34 -0
  49. autoassembler/auth/client.py +282 -0
  50. autoassembler/auth/raw_client.py +413 -0
  51. autoassembler/auth/types/__init__.py +36 -0
  52. autoassembler/auth/types/create_access_token_request_grant_type.py +7 -0
  53. autoassembler/base_client.py +328 -0
  54. autoassembler/client.py +262 -0
  55. autoassembler/core/__init__.py +127 -0
  56. autoassembler/core/api_error.py +23 -0
  57. autoassembler/core/client_wrapper.py +147 -0
  58. autoassembler/core/datetime_utils.py +70 -0
  59. autoassembler/core/file.py +67 -0
  60. autoassembler/core/force_multipart.py +18 -0
  61. autoassembler/core/http_client.py +965 -0
  62. autoassembler/core/http_response.py +63 -0
  63. autoassembler/core/http_sse/__init__.py +42 -0
  64. autoassembler/core/http_sse/_api.py +455 -0
  65. autoassembler/core/http_sse/_decoders.py +74 -0
  66. autoassembler/core/http_sse/_exceptions.py +7 -0
  67. autoassembler/core/http_sse/_models.py +17 -0
  68. autoassembler/core/jsonable_encoder.py +133 -0
  69. autoassembler/core/logging.py +107 -0
  70. autoassembler/core/parse_error.py +36 -0
  71. autoassembler/core/pydantic_utilities.py +486 -0
  72. autoassembler/core/query_encoder.py +58 -0
  73. autoassembler/core/remove_none_from_dict.py +11 -0
  74. autoassembler/core/request_options.py +40 -0
  75. autoassembler/core/serialization.py +347 -0
  76. autoassembler/environment.py +7 -0
  77. autoassembler/errors/__init__.py +56 -0
  78. autoassembler/errors/bad_request_error.py +10 -0
  79. autoassembler/errors/conflict_error.py +10 -0
  80. autoassembler/errors/forbidden_error.py +11 -0
  81. autoassembler/errors/internal_server_error.py +11 -0
  82. autoassembler/errors/not_found_error.py +11 -0
  83. autoassembler/errors/unauthorized_error.py +10 -0
  84. autoassembler/errors/unprocessable_entity_error.py +11 -0
  85. autoassembler/operation.py +196 -0
  86. autoassembler/operations/__init__.py +4 -0
  87. autoassembler/operations/client.py +209 -0
  88. autoassembler/operations/raw_client.py +343 -0
  89. autoassembler/planning/__init__.py +70 -0
  90. autoassembler/planning/client.py +1174 -0
  91. autoassembler/planning/raw_client.py +1349 -0
  92. autoassembler/planning/types/__init__.py +68 -0
  93. autoassembler/planning/types/adapt_request_namespace.py +5 -0
  94. autoassembler/planning/types/create_mpp_request_body.py +8 -0
  95. autoassembler/planning/types/create_mpp_request_namespace.py +5 -0
  96. autoassembler/planning/types/delete_mpp_request_namespace.py +5 -0
  97. autoassembler/planning/types/delete_mpp_response.py +12 -0
  98. autoassembler/planning/types/evaluate_dfa_request_namespace.py +5 -0
  99. autoassembler/planning/types/get_mpp_request_namespace.py +5 -0
  100. autoassembler/planning/types/get_mpp_response.py +27 -0
  101. autoassembler/planning/types/list_mpps_request_namespace.py +5 -0
  102. autoassembler/planning/types/plan_request_namespace.py +5 -0
  103. autoassembler/planning/types/validate_request_namespace.py +5 -0
  104. autoassembler/py.typed +0 -0
  105. autoassembler/sessions/__init__.py +4 -0
  106. autoassembler/sessions/client.py +102 -0
  107. autoassembler/sessions/raw_client.py +126 -0
  108. autoassembler/types/__init__.py +344 -0
  109. autoassembler/types/access_token_response.py +14 -0
  110. autoassembler/types/active_session.py +25 -0
  111. autoassembler/types/active_session_namespace.py +5 -0
  112. autoassembler/types/adapt_mpp_accepted.py +26 -0
  113. autoassembler/types/assembly.py +30 -0
  114. autoassembler/types/assembly_connection.py +21 -0
  115. autoassembler/types/assembly_connection_relationship.py +5 -0
  116. autoassembler/types/assembly_connections_response.py +13 -0
  117. autoassembler/types/assembly_diff_metadata.py +35 -0
  118. autoassembler/types/assembly_diff_response.py +34 -0
  119. autoassembler/types/assembly_info.py +36 -0
  120. autoassembler/types/assembly_json_response.py +15 -0
  121. autoassembler/types/assembly_process_metadata.py +32 -0
  122. autoassembler/types/assembly_process_response.py +18 -0
  123. autoassembler/types/assembly_search_hit.py +31 -0
  124. autoassembler/types/assembly_version_summary.py +27 -0
  125. autoassembler/types/conflict_error_body.py +19 -0
  126. autoassembler/types/conflict_error_body_error.py +5 -0
  127. autoassembler/types/create_mpp_request.py +13 -0
  128. autoassembler/types/device_authorization_response.py +17 -0
  129. autoassembler/types/diff_accepted.py +42 -0
  130. autoassembler/types/diff_context_node.py +20 -0
  131. autoassembler/types/diff_details.py +73 -0
  132. autoassembler/types/diff_error_response.py +38 -0
  133. autoassembler/types/diff_flags.py +74 -0
  134. autoassembler/types/diff_ml_semantics.py +55 -0
  135. autoassembler/types/diff_node.py +26 -0
  136. autoassembler/types/diff_node_pair_with_parts.py +90 -0
  137. autoassembler/types/diff_node_with_parent.py +15 -0
  138. autoassembler/types/diff_reference_target_nodes.py +25 -0
  139. autoassembler/types/diff_report.py +24 -0
  140. autoassembler/types/diff_rigid_alignment.py +30 -0
  141. autoassembler/types/diff_summary.py +121 -0
  142. autoassembler/types/diff_summary_assembly_labels.py +44 -0
  143. autoassembler/types/diff_summary_geometric_diffs.py +24 -0
  144. autoassembler/types/diff_summary_persistent_edges_congruence.py +24 -0
  145. autoassembler/types/diff_summary_persistent_edges_congruence_pose.py +24 -0
  146. autoassembler/types/diff_summary_persistent_nodes_congruence.py +24 -0
  147. autoassembler/types/diff_summary_persistent_nodes_congruence_pose.py +24 -0
  148. autoassembler/types/diff_summary_total_parts.py +24 -0
  149. autoassembler/types/diff_swap.py +65 -0
  150. autoassembler/types/diff_swap_confidence.py +5 -0
  151. autoassembler/types/diff_swap_group.py +92 -0
  152. autoassembler/types/diff_validation_issue.py +38 -0
  153. autoassembler/types/diff_validation_issue_severity.py +5 -0
  154. autoassembler/types/diff_validation_result.py +35 -0
  155. autoassembler/types/diff_warning.py +79 -0
  156. autoassembler/types/diff_warning_reason.py +18 -0
  157. autoassembler/types/diff_warning_severity.py +5 -0
  158. autoassembler/types/error_response.py +30 -0
  159. autoassembler/types/filter.py +19 -0
  160. autoassembler/types/filter_field.py +7 -0
  161. autoassembler/types/filter_operator.py +5 -0
  162. autoassembler/types/job.py +123 -0
  163. autoassembler/types/job_progress.py +24 -0
  164. autoassembler/types/job_status.py +5 -0
  165. autoassembler/types/list_operations_response.py +25 -0
  166. autoassembler/types/manufacturing_process_plan.py +57 -0
  167. autoassembler/types/mpp_adapt_metadata.py +48 -0
  168. autoassembler/types/mpp_adapt_response.py +22 -0
  169. autoassembler/types/mpp_evaluate_dfa_metadata.py +47 -0
  170. autoassembler/types/mpp_evaluate_dfa_response.py +22 -0
  171. autoassembler/types/mpp_plan_metadata.py +47 -0
  172. autoassembler/types/mpp_plan_response.py +22 -0
  173. autoassembler/types/mpp_summary.py +20 -0
  174. autoassembler/types/mpp_validate_metadata.py +47 -0
  175. autoassembler/types/mpp_validate_response.py +22 -0
  176. autoassembler/types/o_auth_error_response.py +14 -0
  177. autoassembler/types/o_auth_error_response_error.py +18 -0
  178. autoassembler/types/operation.py +27 -0
  179. autoassembler/types/operation_error.py +21 -0
  180. autoassembler/types/operation_metadata.py +148 -0
  181. autoassembler/types/operation_response.py +94 -0
  182. autoassembler/types/process_assembly_accepted.py +14 -0
  183. autoassembler/types/process_assembly_response.py +55 -0
  184. autoassembler/types/process_assembly_skipped.py +14 -0
  185. autoassembler/types/search_assemblies_response.py +14 -0
  186. autoassembler/types/seq_plan_accepted.py +24 -0
  187. autoassembler/types/sever.py +33 -0
  188. autoassembler/types/sever_kind.py +5 -0
  189. autoassembler/types/sever_source.py +5 -0
  190. autoassembler/types/sort.py +8 -0
  191. autoassembler/types/upload_response.py +19 -0
  192. autoassembler/types/user_info.py +19 -0
  193. autoassembler/types/user_info_response.py +23 -0
  194. autoassembler/types/user_info_response_providers.py +14 -0
  195. autoassembler-0.3.1.dist-info/METADATA +209 -0
  196. autoassembler-0.3.1.dist-info/RECORD +198 -0
  197. autoassembler-0.3.1.dist-info/WHEEL +4 -0
  198. autoassembler-0.3.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,347 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import collections
4
+ import inspect
5
+ import typing
6
+
7
+ import pydantic
8
+ import typing_extensions
9
+
10
+
11
+ class FieldMetadata:
12
+ """
13
+ Metadata class used to annotate fields to provide additional information.
14
+
15
+ Example:
16
+ class MyDict(TypedDict):
17
+ field: typing.Annotated[str, FieldMetadata(alias="field_name")]
18
+
19
+ Will serialize: `{"field": "value"}`
20
+ To: `{"field_name": "value"}`
21
+ """
22
+
23
+ alias: str
24
+
25
+ def __init__(self, *, alias: str) -> None:
26
+ self.alias = alias
27
+
28
+
29
+ # Resolving type hints (typing.get_type_hints) is expensive because it eval/compiles
30
+ # forward-reference annotations. The result is constant for a given type, so we cache it.
31
+ # This is critical for hot paths like SSE event parsing, where the same (often large
32
+ # discriminated-union) type is converted on every single event.
33
+ _type_hints_cache: typing.Dict[typing.Any, typing.Dict[str, typing.Any]] = {}
34
+
35
+
36
+ def _get_cached_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]:
37
+ try:
38
+ cached = _type_hints_cache.get(expected_type)
39
+ except TypeError:
40
+ # Unhashable type; resolve without caching.
41
+ return _resolve_type_hints(expected_type)
42
+ if cached is None:
43
+ cached = _resolve_type_hints(expected_type)
44
+ _type_hints_cache[expected_type] = cached
45
+ return cached
46
+
47
+
48
+ def _resolve_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]:
49
+ try:
50
+ return typing_extensions.get_type_hints(expected_type, include_extras=True)
51
+ except NameError:
52
+ # The type contains a circular reference, so we use the __annotations__ attribute directly.
53
+ return getattr(expected_type, "__annotations__", {})
54
+
55
+
56
+ # Whether convert_and_respect_annotation_metadata can possibly rewrite anything for a given
57
+ # annotation, i.e. whether any reachable model/TypedDict field carries a FieldMetadata alias.
58
+ # This is constant per type, so we cache it and use it to short-circuit the recursive walk.
59
+ _requires_conversion_cache: typing.Dict[typing.Any, bool] = {}
60
+
61
+
62
+ def _requires_conversion(type_: typing.Any) -> bool:
63
+ try:
64
+ cached = _requires_conversion_cache.get(type_)
65
+ except TypeError:
66
+ # Unhashable annotation; compute without caching.
67
+ return _compute_requires_conversion(type_, set())
68
+ if cached is None:
69
+ cached = _compute_requires_conversion(type_, set())
70
+ _requires_conversion_cache[type_] = cached
71
+ return cached
72
+
73
+
74
+ def _compute_requires_conversion(type_: typing.Any, seen: typing.Set[typing.Any]) -> bool:
75
+ clean_type = _remove_annotations(type_)
76
+
77
+ try:
78
+ if clean_type in seen:
79
+ return False
80
+ seen = seen | {clean_type}
81
+ except TypeError:
82
+ # Unhashable type; skip cycle tracking (the type graph is finite in practice).
83
+ pass
84
+
85
+ # Models / TypedDicts: a field alias here means we must dealias; otherwise recurse into fields.
86
+ if (inspect.isclass(clean_type) and issubclass(clean_type, pydantic.BaseModel)) or typing_extensions.is_typeddict(
87
+ clean_type
88
+ ):
89
+ annotations = _get_cached_type_hints(clean_type)
90
+ if _get_alias_to_field_name(annotations):
91
+ return True
92
+ return any(_compute_requires_conversion(hint, seen) for hint in annotations.values())
93
+
94
+ # Containers / unions: recurse into the type arguments (List/Set/Sequence/Dict/Union/etc.).
95
+ return any(_compute_requires_conversion(arg, seen) for arg in typing_extensions.get_args(clean_type))
96
+
97
+
98
+ def convert_and_respect_annotation_metadata(
99
+ *,
100
+ object_: typing.Any,
101
+ annotation: typing.Any,
102
+ inner_type: typing.Optional[typing.Any] = None,
103
+ direction: typing.Literal["read", "write"],
104
+ ) -> typing.Any:
105
+ """
106
+ Respect the metadata annotations on a field, such as aliasing. This function effectively
107
+ manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for
108
+ TypedDicts, which cannot support aliasing out of the box, and can be extended for additional
109
+ utilities, such as defaults.
110
+
111
+ Parameters
112
+ ----------
113
+ object_ : typing.Any
114
+
115
+ annotation : type
116
+ The type we're looking to apply typing annotations from
117
+
118
+ inner_type : typing.Optional[type]
119
+
120
+ Returns
121
+ -------
122
+ typing.Any
123
+ """
124
+
125
+ if object_ is None:
126
+ return None
127
+ if inner_type is None:
128
+ inner_type = annotation
129
+ # The only thing this function ever rewrites is keys that carry a FieldMetadata
130
+ # alias. If nothing in the (cached) type graph has such an alias, the conversion is
131
+ # a content-identity transform, so we can skip the entire recursive walk. This is
132
+ # the hot path for SSE streaming, where a large discriminated union would otherwise
133
+ # be traversed on every single event.
134
+ if not _requires_conversion(annotation):
135
+ return object_
136
+
137
+ clean_type = _remove_annotations(inner_type)
138
+ # Pydantic models
139
+ if (
140
+ inspect.isclass(clean_type)
141
+ and issubclass(clean_type, pydantic.BaseModel)
142
+ and isinstance(object_, typing.Mapping)
143
+ ):
144
+ return _convert_mapping(object_, clean_type, direction)
145
+ # TypedDicts
146
+ if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping):
147
+ return _convert_mapping(object_, clean_type, direction)
148
+
149
+ if (
150
+ typing_extensions.get_origin(clean_type) == typing.Dict
151
+ or typing_extensions.get_origin(clean_type) == dict
152
+ or clean_type == typing.Dict
153
+ ) and isinstance(object_, typing.Dict):
154
+ key_type = typing_extensions.get_args(clean_type)[0]
155
+ value_type = typing_extensions.get_args(clean_type)[1]
156
+
157
+ return {
158
+ key: convert_and_respect_annotation_metadata(
159
+ object_=value,
160
+ annotation=annotation,
161
+ inner_type=value_type,
162
+ direction=direction,
163
+ )
164
+ for key, value in object_.items()
165
+ }
166
+
167
+ # If you're iterating on a string, do not bother to coerce it to a sequence.
168
+ if not isinstance(object_, str):
169
+ if (
170
+ typing_extensions.get_origin(clean_type) == typing.Set
171
+ or typing_extensions.get_origin(clean_type) == set
172
+ or clean_type == typing.Set
173
+ ) and isinstance(object_, typing.Set):
174
+ inner_type = typing_extensions.get_args(clean_type)[0]
175
+ return {
176
+ convert_and_respect_annotation_metadata(
177
+ object_=item,
178
+ annotation=annotation,
179
+ inner_type=inner_type,
180
+ direction=direction,
181
+ )
182
+ for item in object_
183
+ }
184
+ elif (
185
+ (
186
+ typing_extensions.get_origin(clean_type) == typing.List
187
+ or typing_extensions.get_origin(clean_type) == list
188
+ or clean_type == typing.List
189
+ )
190
+ and isinstance(object_, typing.List)
191
+ ) or (
192
+ (
193
+ typing_extensions.get_origin(clean_type) == typing.Sequence
194
+ or typing_extensions.get_origin(clean_type) == collections.abc.Sequence
195
+ or clean_type == typing.Sequence
196
+ )
197
+ and isinstance(object_, typing.Sequence)
198
+ ):
199
+ inner_type = typing_extensions.get_args(clean_type)[0]
200
+ return [
201
+ convert_and_respect_annotation_metadata(
202
+ object_=item,
203
+ annotation=annotation,
204
+ inner_type=inner_type,
205
+ direction=direction,
206
+ )
207
+ for item in object_
208
+ ]
209
+
210
+ if typing_extensions.get_origin(clean_type) == typing.Union:
211
+ # We should be able to ~relatively~ safely try to convert keys against all
212
+ # member types in the union, the edge case here is if one member aliases a field
213
+ # of the same name to a different name from another member
214
+ # Or if another member aliases a field of the same name that another member does not.
215
+ for member in typing_extensions.get_args(clean_type):
216
+ object_ = convert_and_respect_annotation_metadata(
217
+ object_=object_,
218
+ annotation=annotation,
219
+ inner_type=member,
220
+ direction=direction,
221
+ )
222
+ return object_
223
+
224
+ annotated_type = _get_annotation(annotation)
225
+ if annotated_type is None:
226
+ return object_
227
+
228
+ # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.)
229
+ # Then we can safely call it on the recursive conversion.
230
+ return object_
231
+
232
+
233
+ def _convert_mapping(
234
+ object_: typing.Mapping[str, object],
235
+ expected_type: typing.Any,
236
+ direction: typing.Literal["read", "write"],
237
+ ) -> typing.Mapping[str, object]:
238
+ converted_object: typing.Dict[str, object] = {}
239
+ annotations = _get_cached_type_hints(expected_type)
240
+ aliases_to_field_names = _get_alias_to_field_name(annotations)
241
+ for key, value in object_.items():
242
+ if direction == "read" and key in aliases_to_field_names:
243
+ dealiased_key = aliases_to_field_names.get(key)
244
+ if dealiased_key is not None:
245
+ type_ = annotations.get(dealiased_key)
246
+ else:
247
+ type_ = annotations.get(key)
248
+ # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map
249
+ #
250
+ # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias
251
+ # then we can just pass the value through as is
252
+ if type_ is None:
253
+ converted_object[key] = value
254
+ elif direction == "read" and key not in aliases_to_field_names:
255
+ converted_object[key] = convert_and_respect_annotation_metadata(
256
+ object_=value, annotation=type_, direction=direction
257
+ )
258
+ else:
259
+ converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = (
260
+ convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction)
261
+ )
262
+ return converted_object
263
+
264
+
265
+ def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]:
266
+ maybe_annotated_type = typing_extensions.get_origin(type_)
267
+ if maybe_annotated_type is None:
268
+ return None
269
+
270
+ if maybe_annotated_type == typing_extensions.NotRequired:
271
+ type_ = typing_extensions.get_args(type_)[0]
272
+ maybe_annotated_type = typing_extensions.get_origin(type_)
273
+
274
+ if maybe_annotated_type == typing_extensions.Annotated:
275
+ return type_
276
+
277
+ return None
278
+
279
+
280
+ def _remove_annotations(type_: typing.Any) -> typing.Any:
281
+ maybe_annotated_type = typing_extensions.get_origin(type_)
282
+ if maybe_annotated_type is None:
283
+ return type_
284
+
285
+ if maybe_annotated_type == typing_extensions.NotRequired:
286
+ return _remove_annotations(typing_extensions.get_args(type_)[0])
287
+
288
+ if maybe_annotated_type == typing_extensions.Annotated:
289
+ return _remove_annotations(typing_extensions.get_args(type_)[0])
290
+
291
+ return type_
292
+
293
+
294
+ def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]:
295
+ annotations = _get_cached_type_hints(type_)
296
+ return _get_alias_to_field_name(annotations)
297
+
298
+
299
+ def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]:
300
+ annotations = _get_cached_type_hints(type_)
301
+ return _get_field_to_alias_name(annotations)
302
+
303
+
304
+ def _get_alias_to_field_name(
305
+ field_to_hint: typing.Dict[str, typing.Any],
306
+ ) -> typing.Dict[str, str]:
307
+ aliases = {}
308
+ for field, hint in field_to_hint.items():
309
+ maybe_alias = _get_alias_from_type(hint)
310
+ if maybe_alias is not None:
311
+ aliases[maybe_alias] = field
312
+ return aliases
313
+
314
+
315
+ def _get_field_to_alias_name(
316
+ field_to_hint: typing.Dict[str, typing.Any],
317
+ ) -> typing.Dict[str, str]:
318
+ aliases = {}
319
+ for field, hint in field_to_hint.items():
320
+ maybe_alias = _get_alias_from_type(hint)
321
+ if maybe_alias is not None:
322
+ aliases[field] = maybe_alias
323
+ return aliases
324
+
325
+
326
+ def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]:
327
+ maybe_annotated_type = _get_annotation(type_)
328
+
329
+ if maybe_annotated_type is not None:
330
+ # The actual annotations are 1 onward, the first is the annotated type
331
+ annotations = typing_extensions.get_args(maybe_annotated_type)[1:]
332
+
333
+ for annotation in annotations:
334
+ if isinstance(annotation, FieldMetadata) and annotation.alias is not None:
335
+ return annotation.alias
336
+ return None
337
+
338
+
339
+ def _alias_key(
340
+ key: str,
341
+ type_: typing.Any,
342
+ direction: typing.Literal["read", "write"],
343
+ aliases_to_field_names: typing.Dict[str, str],
344
+ ) -> str:
345
+ if direction == "read":
346
+ return aliases_to_field_names.get(key, key)
347
+ return _get_alias_from_type(type_=type_) or key
@@ -0,0 +1,7 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import enum
4
+
5
+
6
+ class AutoAssemblerEnvironment(enum.Enum):
7
+ PRODUCTION = "https://app.autoassembler.com"
@@ -0,0 +1,56 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ # isort: skip_file
4
+
5
+ import typing
6
+ from importlib import import_module
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from .bad_request_error import BadRequestError
10
+ from .conflict_error import ConflictError
11
+ from .forbidden_error import ForbiddenError
12
+ from .internal_server_error import InternalServerError
13
+ from .not_found_error import NotFoundError
14
+ from .unauthorized_error import UnauthorizedError
15
+ from .unprocessable_entity_error import UnprocessableEntityError
16
+ _dynamic_imports: typing.Dict[str, str] = {
17
+ "BadRequestError": ".bad_request_error",
18
+ "ConflictError": ".conflict_error",
19
+ "ForbiddenError": ".forbidden_error",
20
+ "InternalServerError": ".internal_server_error",
21
+ "NotFoundError": ".not_found_error",
22
+ "UnauthorizedError": ".unauthorized_error",
23
+ "UnprocessableEntityError": ".unprocessable_entity_error",
24
+ }
25
+
26
+
27
+ def __getattr__(attr_name: str) -> typing.Any:
28
+ module_name = _dynamic_imports.get(attr_name)
29
+ if module_name is None:
30
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
31
+ try:
32
+ module = import_module(module_name, __package__)
33
+ if module_name == f".{attr_name}":
34
+ return module
35
+ else:
36
+ return getattr(module, attr_name)
37
+ except ImportError as e:
38
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
39
+ except AttributeError as e:
40
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
41
+
42
+
43
+ def __dir__():
44
+ lazy_attrs = list(_dynamic_imports.keys())
45
+ return sorted(lazy_attrs)
46
+
47
+
48
+ __all__ = [
49
+ "BadRequestError",
50
+ "ConflictError",
51
+ "ForbiddenError",
52
+ "InternalServerError",
53
+ "NotFoundError",
54
+ "UnauthorizedError",
55
+ "UnprocessableEntityError",
56
+ ]
@@ -0,0 +1,10 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+
7
+
8
+ class BadRequestError(ApiError):
9
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
10
+ super().__init__(status_code=400, headers=headers, body=body)
@@ -0,0 +1,10 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+
7
+
8
+ class ConflictError(ApiError):
9
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
10
+ super().__init__(status_code=409, headers=headers, body=body)
@@ -0,0 +1,11 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+ from ..types.error_response import ErrorResponse
7
+
8
+
9
+ class ForbiddenError(ApiError):
10
+ def __init__(self, body: ErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None):
11
+ super().__init__(status_code=403, headers=headers, body=body)
@@ -0,0 +1,11 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+ from ..types.o_auth_error_response import OAuthErrorResponse
7
+
8
+
9
+ class InternalServerError(ApiError):
10
+ def __init__(self, body: OAuthErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None):
11
+ super().__init__(status_code=500, headers=headers, body=body)
@@ -0,0 +1,11 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+ from ..types.error_response import ErrorResponse
7
+
8
+
9
+ class NotFoundError(ApiError):
10
+ def __init__(self, body: ErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None):
11
+ super().__init__(status_code=404, headers=headers, body=body)
@@ -0,0 +1,10 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+
7
+
8
+ class UnauthorizedError(ApiError):
9
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
10
+ super().__init__(status_code=401, headers=headers, body=body)
@@ -0,0 +1,11 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from ..core.api_error import ApiError
6
+ from ..types.error_response import ErrorResponse
7
+
8
+
9
+ class UnprocessableEntityError(ApiError):
10
+ def __init__(self, body: ErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None):
11
+ super().__init__(status_code=422, headers=headers, body=body)
@@ -0,0 +1,196 @@
1
+ """Handles for awaiting the server's long-running operations.
2
+
3
+ Custom (hand-written) code — NOT generated by Fern. Protected via `.fernignore`.
4
+
5
+ An endpoint whose work continues after it answers returns an operation
6
+ (`autoassembler.types.Operation`, shaped after Google's
7
+ [AIP-151](https://google.aip.dev/151)): ``done`` once the server has recorded the outcome,
8
+ then exactly one of ``response`` or ``error``. That model is a snapshot of one GET.
9
+ `Operation` wraps it, like ``google.api_core.operation.Operation`` wraps the
10
+ ``google.longrunning.Operation`` message: `done` fetches the latest state while the
11
+ operation runs, and `result` short-polls with exponential backoff until it is done, then
12
+ returns the response (or what ``resolve`` makes of it) or raises `OperationFailed`.
13
+ """
14
+
15
+ import asyncio
16
+ import time
17
+ from collections.abc import Awaitable, Callable
18
+ from typing import Any, cast
19
+
20
+ from .types.operation import Operation as OperationResource
21
+ from .types.operation_error import OperationError
22
+
23
+ # Backoff between polls in `result`: seconds before the first re-poll, its growth per poll, its cap.
24
+ POLL_INTERVAL = 1.0
25
+ POLL_MULTIPLIER = 1.5
26
+ MAX_POLL_INTERVAL = 20.0
27
+
28
+
29
+ class OperationFailed(Exception):
30
+ """The operation finished with an error.
31
+
32
+ ``code`` is its ``google.rpc.Code`` (e.g. 1 cancelled, 13 internal, 14 unavailable) and
33
+ ``operation`` the finished operation.
34
+ """
35
+
36
+ def __init__(self, operation: OperationResource):
37
+ error = operation.error or OperationError(code=2, message="finished without a response")
38
+ super().__init__(f"{operation.name} failed ({error.code}): {error.message}")
39
+ self.operation = operation
40
+ self.code = error.code
41
+ self.message = error.message
42
+
43
+
44
+ class OperationTimeout(TimeoutError):
45
+ """The operation did not finish within the time `result` was given; it may still finish."""
46
+
47
+ def __init__(self, operation: OperationResource, timeout: float):
48
+ super().__init__(f"{operation.name} did not finish within {timeout}s")
49
+ self.operation = operation
50
+
51
+
52
+ def operation_id(name: str) -> str:
53
+ """The id `get_operation` takes: the part of ``name`` after ``operations/``."""
54
+ return name.removeprefix("operations/")
55
+
56
+
57
+ class _Backoff:
58
+ """Seconds between polls: growing by `POLL_MULTIPLIER` up to a cap, and within the deadline."""
59
+
60
+ def __init__(self, poll_interval: float, max_poll_interval: float, timeout: float | None):
61
+ self._next = poll_interval
62
+ self._max = max_poll_interval
63
+ self._deadline = None if timeout is None else time.monotonic() + timeout
64
+
65
+ def expired(self) -> bool:
66
+ return self._deadline is not None and time.monotonic() >= self._deadline
67
+
68
+ def delay(self) -> float:
69
+ delay = min(self._next, self._max)
70
+ if self._deadline is not None:
71
+ delay = max(0.0, min(delay, self._deadline - time.monotonic()))
72
+ self._next *= POLL_MULTIPLIER
73
+ return delay
74
+
75
+
76
+ class Operation[T]:
77
+ """A long-running operation the server is running, and its eventual result.
78
+
79
+ Args:
80
+ operation: The operation as last fetched, e.g. from the endpoint's 202.
81
+ refresh: Fetches the operation's current state.
82
+ resolve: Maps the finished operation to the result; defaults to its ``response``.
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ operation: OperationResource,
88
+ *,
89
+ refresh: Callable[[], OperationResource],
90
+ resolve: Callable[[OperationResource], T] | None = None,
91
+ ):
92
+ self._operation = operation
93
+ self._refresh = refresh
94
+ self._resolve = resolve
95
+
96
+ @property
97
+ def name(self) -> str:
98
+ return self._operation.name
99
+
100
+ @property
101
+ def operation(self) -> OperationResource:
102
+ """The operation as last fetched; `done` fetches it again while it runs."""
103
+ return self._operation
104
+
105
+ @property
106
+ def metadata(self) -> Any:
107
+ return self._operation.metadata
108
+
109
+ def done(self) -> bool:
110
+ """Whether the operation has finished; fetches its current state unless it already had."""
111
+ if not self._operation.done:
112
+ self._operation = self._refresh()
113
+ return self._operation.done
114
+
115
+ def result(
116
+ self,
117
+ *,
118
+ timeout: float | None = None,
119
+ poll_interval: float = POLL_INTERVAL,
120
+ max_poll_interval: float = MAX_POLL_INTERVAL,
121
+ ) -> T:
122
+ """Wait until the operation finishes and return its result.
123
+
124
+ Polls with exponential backoff from ``poll_interval`` up to ``max_poll_interval``
125
+ seconds. Raises `OperationFailed` if it finished with an error and `OperationTimeout`
126
+ if it is still running after ``timeout`` seconds (``None`` waits indefinitely).
127
+ """
128
+ backoff = _Backoff(poll_interval, max_poll_interval, timeout)
129
+ while not self.done():
130
+ if backoff.expired():
131
+ raise OperationTimeout(self._operation, timeout or 0.0)
132
+ time.sleep(backoff.delay())
133
+ if self._operation.error is not None or self._operation.response is None:
134
+ raise OperationFailed(self._operation)
135
+ if self._resolve is not None:
136
+ return self._resolve(self._operation)
137
+ return cast(T, self._operation.response)
138
+
139
+ def __repr__(self) -> str:
140
+ return f"Operation({self.name!r}, done={self._operation.done})"
141
+
142
+
143
+ class AsyncOperation[T]:
144
+ """The async mirror of `Operation`: `done` and `result` are coroutines."""
145
+
146
+ def __init__(
147
+ self,
148
+ operation: OperationResource,
149
+ *,
150
+ refresh: Callable[[], Awaitable[OperationResource]],
151
+ resolve: Callable[[OperationResource], Awaitable[T]] | None = None,
152
+ ):
153
+ self._operation = operation
154
+ self._refresh = refresh
155
+ self._resolve = resolve
156
+
157
+ @property
158
+ def name(self) -> str:
159
+ return self._operation.name
160
+
161
+ @property
162
+ def operation(self) -> OperationResource:
163
+ """The operation as last fetched; `done` fetches it again while it runs."""
164
+ return self._operation
165
+
166
+ @property
167
+ def metadata(self) -> Any:
168
+ return self._operation.metadata
169
+
170
+ async def done(self) -> bool:
171
+ """Whether the operation has finished; fetches its current state unless it already had."""
172
+ if not self._operation.done:
173
+ self._operation = await self._refresh()
174
+ return self._operation.done
175
+
176
+ async def result(
177
+ self,
178
+ *,
179
+ timeout: float | None = None,
180
+ poll_interval: float = POLL_INTERVAL,
181
+ max_poll_interval: float = MAX_POLL_INTERVAL,
182
+ ) -> T:
183
+ """Wait until the operation finishes and return its result; see `Operation.result`."""
184
+ backoff = _Backoff(poll_interval, max_poll_interval, timeout)
185
+ while not await self.done():
186
+ if backoff.expired():
187
+ raise OperationTimeout(self._operation, timeout or 0.0)
188
+ await asyncio.sleep(backoff.delay())
189
+ if self._operation.error is not None or self._operation.response is None:
190
+ raise OperationFailed(self._operation)
191
+ if self._resolve is not None:
192
+ return await self._resolve(self._operation)
193
+ return cast(T, self._operation.response)
194
+
195
+ def __repr__(self) -> str:
196
+ return f"AsyncOperation({self.name!r}, done={self._operation.done})"