UncountablePythonSDK 0.0.20__py3-none-any.whl → 0.0.22__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 (55) hide show
  1. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/METADATA +3 -1
  2. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/RECORD +55 -34
  3. examples/async_batch.py +36 -0
  4. examples/upload_files.py +19 -0
  5. pkgs/type_spec/actions_registry/__main__.py +35 -23
  6. pkgs/type_spec/actions_registry/emit_typescript.py +71 -9
  7. pkgs/type_spec/builder.py +125 -8
  8. pkgs/type_spec/config.py +1 -0
  9. pkgs/type_spec/emit_open_api.py +197 -16
  10. pkgs/type_spec/emit_open_api_util.py +18 -0
  11. pkgs/type_spec/emit_python.py +241 -55
  12. pkgs/type_spec/load_types.py +48 -5
  13. pkgs/type_spec/open_api_util.py +13 -33
  14. pkgs/type_spec/type_info/emit_type_info.py +129 -8
  15. type_spec/external/api/entity/create_entities.yaml +13 -1
  16. type_spec/external/api/entity/create_entity.yaml +13 -1
  17. type_spec/external/api/entity/transition_entity_phase.yaml +44 -0
  18. type_spec/external/api/permissions/set_core_permissions.yaml +69 -0
  19. type_spec/external/api/recipes/associate_recipe_as_input.yaml +4 -4
  20. type_spec/external/api/recipes/create_recipe.yaml +2 -1
  21. type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +16 -0
  22. type_spec/external/api/recipes/edit_recipe_inputs.yaml +86 -0
  23. type_spec/external/api/recipes/get_curve.yaml +4 -1
  24. type_spec/external/api/recipes/get_recipes_data.yaml +6 -0
  25. type_spec/external/api/recipes/set_recipe_metadata.yaml +1 -0
  26. type_spec/external/api/recipes/set_recipe_tags.yaml +62 -0
  27. uncountable/core/__init__.py +3 -1
  28. uncountable/core/async_batch.py +22 -0
  29. uncountable/core/client.py +84 -10
  30. uncountable/core/file_upload.py +95 -0
  31. uncountable/core/types.py +22 -0
  32. uncountable/types/__init__.py +18 -0
  33. uncountable/types/api/entity/create_entities.py +1 -1
  34. uncountable/types/api/entity/create_entity.py +1 -1
  35. uncountable/types/api/entity/transition_entity_phase.py +66 -0
  36. uncountable/types/api/permissions/__init__.py +1 -0
  37. uncountable/types/api/permissions/set_core_permissions.py +89 -0
  38. uncountable/types/api/recipes/associate_recipe_as_input.py +4 -3
  39. uncountable/types/api/recipes/create_recipe.py +1 -1
  40. uncountable/types/api/recipes/disassociate_recipe_as_input.py +35 -0
  41. uncountable/types/api/recipes/edit_recipe_inputs.py +106 -0
  42. uncountable/types/api/recipes/get_curve.py +2 -1
  43. uncountable/types/api/recipes/get_recipes_data.py +2 -0
  44. uncountable/types/api/recipes/set_recipe_tags.py +91 -0
  45. uncountable/types/async_batch.py +10 -0
  46. uncountable/types/async_batch_processor.py +154 -0
  47. uncountable/types/client_base.py +113 -48
  48. uncountable/types/identifier.py +3 -3
  49. uncountable/types/permissions.py +46 -0
  50. uncountable/types/post_base.py +30 -0
  51. uncountable/types/recipe_inputs.py +30 -0
  52. uncountable/types/recipe_metadata.py +2 -0
  53. uncountable/types/recipe_workflow_steps.py +77 -0
  54. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/WHEEL +0 -0
  55. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/top_level.txt +0 -0
@@ -6,11 +6,16 @@ from enum import StrEnum
6
6
  from urllib.parse import urljoin
7
7
 
8
8
  import requests
9
+ from requests.exceptions import JSONDecodeError
9
10
 
10
11
  from pkgs.argument_parser import CachedParser
11
12
  from pkgs.serialization_util import serialize_for_api
13
+ from pkgs.serialization_util.serialization_helpers import JsonValue
12
14
  from uncountable.types.client_base import APIRequest, ClientMethods
13
15
 
16
+ from .file_upload import FileUpload, FileUploader, UploadedFile
17
+ from .types import AuthDetails, AuthDetailsApiKey
18
+
14
19
  DT = typing.TypeVar("DT")
15
20
 
16
21
 
@@ -43,23 +48,77 @@ class HTTPPostRequest(HTTPRequestBase):
43
48
  HTTPRequest = HTTPPostRequest | HTTPGetRequest
44
49
 
45
50
 
51
+
46
52
  @dataclass(kw_only=True)
47
- class AuthDetailsApiKey:
48
- api_id: str
49
- api_secret_key: str
53
+ class ClientConfig():
54
+ allow_insecure_tls: bool = False
55
+
56
+
57
+ class APIResponseError(BaseException):
58
+ status_code: int
59
+ message: str
60
+ extra_details: dict[str, JsonValue] | None
61
+
62
+ def __init__(
63
+ self, status_code: int, message: str, extra_details: dict[str, JsonValue] | None
64
+ ) -> None:
65
+ super().__init__(status_code, message, extra_details)
66
+ self.status_code = status_code
67
+ self.message = message
68
+ self.extra_details = extra_details
69
+
70
+ @classmethod
71
+ def construct_error(
72
+ cls, status_code: int, extra_details: dict[str, JsonValue] | None
73
+ ) -> "APIResponseError":
74
+ message: str
75
+ match status_code:
76
+ case 403:
77
+ message = "unexpected: unauthorized"
78
+ case 410:
79
+ message = "unexpected: not found"
80
+ case 400:
81
+ message = "unexpected: bad arguments"
82
+ case 501:
83
+ message = "unexpected: unimplemented"
84
+ case 504:
85
+ message = "unexpected: timeout"
86
+ case 404:
87
+ message = "not found"
88
+ case 409:
89
+ message = "bad arguments"
90
+ case 422:
91
+ message = "unprocessable"
92
+ case _:
93
+ message = "unknown error"
94
+ return APIResponseError(
95
+ status_code=status_code, message=message, extra_details=extra_details
96
+ )
50
97
 
51
98
 
52
- AuthDetails = AuthDetailsApiKey
99
+ class SDKError(BaseException):
100
+ message: str
101
+
102
+ def __init__(self, message: str) -> None:
103
+ super().__init__(message)
104
+ self.message = message
105
+
106
+ def __str__(self) -> str:
107
+ return f"internal SDK error, please contact Uncountable support: {self.message}"
53
108
 
54
109
 
55
110
  class Client(ClientMethods):
56
111
  _parser_map: dict[type, CachedParser] = {}
57
112
  _auth_details: AuthDetails
58
113
  _base_url: str
114
+ _file_uploader: FileUploader
115
+ _cfg: ClientConfig
59
116
 
60
- def __init__(self, *, base_url: str, auth_details: AuthDetails):
117
+ def __init__(self, *, base_url: str, auth_details: AuthDetails, config: ClientConfig | None = None):
61
118
  self._auth_details = auth_details
62
119
  self._base_url = base_url
120
+ self._file_uploader = FileUploader(self._base_url, self._auth_details)
121
+ self._cfg = config or ClientConfig()
63
122
 
64
123
  def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
65
124
  http_request = self._build_http_request(api_request=api_request)
@@ -69,6 +128,7 @@ class Client(ClientMethods):
69
128
  http_request.url,
70
129
  headers=http_request.headers,
71
130
  params=http_request.query_params,
131
+ verify=not self._cfg.allow_insecure_tls
72
132
  )
73
133
  case HTTPPostRequest():
74
134
  response = requests.post(
@@ -76,19 +136,27 @@ class Client(ClientMethods):
76
136
  headers=http_request.headers,
77
137
  data=http_request.body,
78
138
  params=http_request.query_params,
139
+ verify=not self._cfg.allow_insecure_tls
79
140
  )
80
141
  case _:
81
142
  typing.assert_never(http_request)
82
143
  if response.status_code < 200 or response.status_code > 299:
83
- # TODO: handle_error
84
- pass
144
+ extra_details: dict[str, JsonValue] | None = None
145
+ try:
146
+ data = response.json()
147
+ if "error" in data:
148
+ extra_details = data["error"]
149
+ except JSONDecodeError:
150
+ pass
151
+ raise APIResponseError.construct_error(
152
+ status_code=response.status_code, extra_details=extra_details
153
+ )
85
154
  cached_parser = self._get_cached_parser(return_type)
86
155
  try:
87
156
  data = response.json()["data"]
88
157
  return cached_parser.parse_api(data)
89
- except ValueError as err:
90
- # TODO: handle parse error
91
- raise err
158
+ except ValueError | JSONDecodeError:
159
+ raise SDKError("unable to process response")
92
160
 
93
161
  def _get_cached_parser(self, data_type: type[DT]) -> CachedParser[DT]:
94
162
  if data_type not in self._parser_map:
@@ -125,3 +193,9 @@ class Client(ClientMethods):
125
193
  )
126
194
  case _:
127
195
  raise ValueError(f"unsupported request method: {method}")
196
+
197
+ def upload_files(
198
+ self: typing.Self, *, file_uploads: list[FileUpload]
199
+ ) -> list[UploadedFile]:
200
+ """Upload files to uncountable, returning file ids that are usable with other SDK operations."""
201
+ return self._file_uploader.upload_files(file_uploads=file_uploads)
@@ -0,0 +1,95 @@
1
+ import asyncio
2
+ from contextlib import contextmanager
3
+ from dataclasses import dataclass
4
+ from enum import StrEnum
5
+ from io import BytesIO
6
+ from pathlib import Path
7
+ from typing import Generator, Literal, Self
8
+
9
+ import aiohttp
10
+ import aiotus
11
+
12
+ from .types import AuthDetails
13
+
14
+ _CHUNK_SIZE = 5 * 1024 * 1024 # s3 requires 5MiB minimum
15
+
16
+
17
+ class FileUploadType(StrEnum):
18
+ MEDIA_FILE_UPLOAD = "MEDIA_FILE_UPLOAD"
19
+
20
+
21
+ @dataclass(kw_only=True)
22
+ class MediaFileUpload:
23
+ """Upload file from a path on disk"""
24
+
25
+ path: str
26
+ type: FileUploadType.MEDIA_FILE_UPLOAD = FileUploadType.MEDIA_FILE_UPLOAD
27
+
28
+
29
+ FileUpload = MediaFileUpload
30
+
31
+
32
+ @dataclass(kw_only=True)
33
+ class FileBytes:
34
+ name: str
35
+ bytes_data: BytesIO
36
+
37
+
38
+ @contextmanager
39
+ def file_upload_data(file_upload: FileUpload) -> Generator[FileBytes, None, None]:
40
+ match file_upload.type:
41
+ case FileUploadType.MEDIA_FILE_UPLOAD:
42
+ with open(file_upload.path, "rb") as f:
43
+ yield FileBytes(
44
+ name=Path(file_upload.path).name, bytes_data=BytesIO(f.read())
45
+ )
46
+
47
+
48
+ @dataclass(kw_only=True)
49
+ class UploadedFile:
50
+ name: str
51
+ file_id: int
52
+
53
+
54
+ class UploadFailed(Exception):
55
+ pass
56
+
57
+
58
+ class FileUploader:
59
+ _auth_details: AuthDetails
60
+ _base_url: str
61
+
62
+ def __init__(self: Self, base_url: str, auth_details: AuthDetails) -> None:
63
+ self._base_url = base_url
64
+ self._auth_details = auth_details
65
+
66
+ async def _upload_file(self: Self, file_upload: FileUpload) -> UploadedFile:
67
+ creation_url = f"{self._base_url}/api/external/file_upload/files"
68
+ auth = aiohttp.BasicAuth(
69
+ self._auth_details.api_id, self._auth_details.api_secret_key
70
+ )
71
+ async with (
72
+ aiohttp.ClientSession(
73
+ auth=auth, headers={"Origin": self._base_url}
74
+ ) as session,
75
+ ):
76
+ with file_upload_data(file_upload) as file_bytes:
77
+ location = await aiotus.upload(
78
+ creation_url,
79
+ file_bytes.bytes_data,
80
+ {"filename": file_bytes.name.encode()},
81
+ client_session=session,
82
+ chunksize=_CHUNK_SIZE,
83
+ )
84
+ if location is None:
85
+ raise UploadFailed(f"Failed to upload: {file_bytes.name}")
86
+ return UploadedFile(
87
+ name=file_bytes.name, file_id=int(location.path.split("/")[-1])
88
+ )
89
+
90
+ def upload_files(
91
+ self: Self, *, file_uploads: list[FileUpload]
92
+ ) -> list[UploadedFile]:
93
+ return [
94
+ asyncio.run(self._upload_file(file_upload)) for file_upload in file_uploads
95
+ ]
@@ -0,0 +1,22 @@
1
+ import base64
2
+ import json
3
+ import typing
4
+ from dataclasses import dataclass
5
+ from enum import StrEnum
6
+ from urllib.parse import urljoin
7
+
8
+ import aiohttp
9
+ import requests
10
+
11
+ from pkgs.argument_parser import CachedParser
12
+ from pkgs.serialization_util import serialize_for_api
13
+ from uncountable.types.client_base import APIRequest, ClientMethods
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class AuthDetailsApiKey:
18
+ api_id: str
19
+ api_secret_key: str
20
+
21
+
22
+ AuthDetails = AuthDetailsApiKey
@@ -17,6 +17,8 @@ from .api.recipes import create_recipe as create_recipe_t
17
17
  from .api.recipe_links import create_recipe_link as create_recipe_link_t
18
18
  from .api.recipes import create_recipes as create_recipes_t
19
19
  from . import curves as curves_t
20
+ from .api.recipes import disassociate_recipe_as_input as disassociate_recipe_as_input_t
21
+ from .api.recipes import edit_recipe_inputs as edit_recipe_inputs_t
20
22
  from . import entity as entity_t
21
23
  from .api.batch import execute_batch as execute_batch_t
22
24
  from .api.batch import execute_batch_load_async as execute_batch_load_async_t
@@ -47,21 +49,28 @@ from .api.entity import list_entities as list_entities_t
47
49
  from .api.id_source import list_id_source as list_id_source_t
48
50
  from .api.id_source import match_id_source as match_id_source_t
49
51
  from . import outputs as outputs_t
52
+ from . import permissions as permissions_t
50
53
  from . import phases as phases_t
54
+ from . import post_base as post_base_t
51
55
  from . import recipe_identifiers as recipe_identifiers_t
56
+ from . import recipe_inputs as recipe_inputs_t
52
57
  from . import recipe_links as recipe_links_t
53
58
  from . import recipe_metadata as recipe_metadata_t
54
59
  from . import recipe_output_metadata as recipe_output_metadata_t
55
60
  from . import recipe_tags as recipe_tags_t
61
+ from . import recipe_workflow_steps as recipe_workflow_steps_t
56
62
  from .api.entity import resolve_entity_ids as resolve_entity_ids_t
57
63
  from .api.outputs import resolve_output_conditions as resolve_output_conditions_t
58
64
  from . import response as response_t
59
65
  from .api.triggers import run_trigger as run_trigger_t
66
+ from .api.permissions import set_core_permissions as set_core_permissions_t
60
67
  from .api.inputs import set_input_attribute_values as set_input_attribute_values_t
61
68
  from .api.recipes import set_recipe_inputs as set_recipe_inputs_t
62
69
  from .api.recipes import set_recipe_metadata as set_recipe_metadata_t
63
70
  from .api.recipes import set_recipe_outputs as set_recipe_outputs_t
71
+ from .api.recipes import set_recipe_tags as set_recipe_tags_t
64
72
  from .api.entity import set_values as set_values_t
73
+ from .api.entity import transition_entity_phase as transition_entity_phase_t
65
74
  from . import units as units_t
66
75
  from . import users as users_t
67
76
  from . import workflows as workflows_t
@@ -82,6 +91,8 @@ __all__: list[str] = [
82
91
  "create_recipe_link_t",
83
92
  "create_recipes_t",
84
93
  "curves_t",
94
+ "disassociate_recipe_as_input_t",
95
+ "edit_recipe_inputs_t",
85
96
  "entity_t",
86
97
  "execute_batch_t",
87
98
  "execute_batch_load_async_t",
@@ -112,21 +123,28 @@ __all__: list[str] = [
112
123
  "list_id_source_t",
113
124
  "match_id_source_t",
114
125
  "outputs_t",
126
+ "permissions_t",
115
127
  "phases_t",
128
+ "post_base_t",
116
129
  "recipe_identifiers_t",
130
+ "recipe_inputs_t",
117
131
  "recipe_links_t",
118
132
  "recipe_metadata_t",
119
133
  "recipe_output_metadata_t",
120
134
  "recipe_tags_t",
135
+ "recipe_workflow_steps_t",
121
136
  "resolve_entity_ids_t",
122
137
  "resolve_output_conditions_t",
123
138
  "response_t",
124
139
  "run_trigger_t",
140
+ "set_core_permissions_t",
125
141
  "set_input_attribute_values_t",
126
142
  "set_recipe_inputs_t",
127
143
  "set_recipe_metadata_t",
128
144
  "set_recipe_outputs_t",
145
+ "set_recipe_tags_t",
129
146
  "set_values_t",
147
+ "transition_entity_phase_t",
130
148
  "units_t",
131
149
  "users_t",
132
150
  "workflows_t",
@@ -34,7 +34,7 @@ class EntityToCreate:
34
34
  @dataclass(kw_only=True)
35
35
  class Arguments:
36
36
  definition_id: base_t.ObjectId
37
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS]]
37
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK]]
38
38
  entities_to_create: list[EntityToCreate]
39
39
 
40
40
 
@@ -40,7 +40,7 @@ class EntityFieldInitialValue:
40
40
  @dataclass(kw_only=True)
41
41
  class Arguments:
42
42
  definition_id: base_t.ObjectId
43
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS]]
43
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK]]
44
44
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None
45
45
 
46
46
 
@@ -0,0 +1,66 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # flake8: noqa: F821
3
+ # ruff: noqa: E402
4
+ # fmt: off
5
+ # isort: skip_file
6
+ from __future__ import annotations
7
+ import typing # noqa: F401
8
+ import datetime # noqa: F401
9
+ from decimal import Decimal # noqa: F401
10
+ from dataclasses import dataclass
11
+ from pkgs.serialization import serial_class
12
+ from ... import entity as entity_t
13
+ from ... import identifier as identifier_t
14
+ from ... import response as response_t
15
+
16
+ __all__: list[str] = [
17
+ "Arguments",
18
+ "Data",
19
+ "ENDPOINT_METHOD",
20
+ "ENDPOINT_PATH",
21
+ "TransitionIdentifier",
22
+ "TransitionIdentifierPhases",
23
+ "TransitionIdentifierTransition",
24
+ ]
25
+
26
+ ENDPOINT_METHOD = "POST"
27
+ ENDPOINT_PATH = "api/external/entity/transition_entity_phase"
28
+
29
+
30
+ # DO NOT MODIFY -- This file is generated by type_spec
31
+ @serial_class(
32
+ parse_require={"type"},
33
+ )
34
+ @dataclass(kw_only=True)
35
+ class TransitionIdentifierPhases:
36
+ type: typing.Literal["phases"] = "phases"
37
+ phase_from_key: identifier_t.IdentifierKey
38
+ phase_to_key: identifier_t.IdentifierKey
39
+
40
+
41
+ # DO NOT MODIFY -- This file is generated by type_spec
42
+ @serial_class(
43
+ parse_require={"type"},
44
+ )
45
+ @dataclass(kw_only=True)
46
+ class TransitionIdentifierTransition:
47
+ type: typing.Literal["transition"] = "transition"
48
+ transition_key: identifier_t.IdentifierKey
49
+
50
+
51
+ # DO NOT MODIFY -- This file is generated by type_spec
52
+ TransitionIdentifier = typing.Union[TransitionIdentifierPhases, TransitionIdentifierTransition]
53
+
54
+
55
+ # DO NOT MODIFY -- This file is generated by type_spec
56
+ @dataclass(kw_only=True)
57
+ class Arguments:
58
+ entity: entity_t.Entity
59
+ transition: TransitionIdentifier
60
+
61
+
62
+ # DO NOT MODIFY -- This file is generated by type_spec
63
+ @dataclass(kw_only=True)
64
+ class Data(response_t.Response):
65
+ pass
66
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,89 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # flake8: noqa: F821
3
+ # ruff: noqa: E402
4
+ # fmt: off
5
+ # isort: skip_file
6
+ from __future__ import annotations
7
+ import typing # noqa: F401
8
+ import datetime # noqa: F401
9
+ from decimal import Decimal # noqa: F401
10
+ from dataclasses import dataclass
11
+ from pkgs.serialization import serial_class
12
+ from ... import identifier as identifier_t
13
+ from ... import permissions as permissions_t
14
+ from ... import post_base as post_base_t
15
+
16
+ __all__: list[str] = [
17
+ "Arguments",
18
+ "Data",
19
+ "ENDPOINT_METHOD",
20
+ "ENDPOINT_PATH",
21
+ "PermissionsScope",
22
+ "PermissionsScopeAllMaterialFamilies",
23
+ "PermissionsScopeMaterialFamily",
24
+ "PermissionsScopeProject",
25
+ "PermissionsScopeRecipe",
26
+ ]
27
+
28
+ ENDPOINT_METHOD = "POST"
29
+ ENDPOINT_PATH = "api/external/permissions/external_set_core_permissions"
30
+
31
+
32
+ # DO NOT MODIFY -- This file is generated by type_spec
33
+ @serial_class(
34
+ parse_require={"type"},
35
+ )
36
+ @dataclass(kw_only=True)
37
+ class PermissionsScopeProject:
38
+ type: typing.Literal["project"] = "project"
39
+ project_key: identifier_t.IdentifierKey
40
+
41
+
42
+ # DO NOT MODIFY -- This file is generated by type_spec
43
+ @serial_class(
44
+ parse_require={"type"},
45
+ )
46
+ @dataclass(kw_only=True)
47
+ class PermissionsScopeRecipe:
48
+ type: typing.Literal["recipe"] = "recipe"
49
+ recipe_key: identifier_t.IdentifierKey
50
+
51
+
52
+ # DO NOT MODIFY -- This file is generated by type_spec
53
+ @serial_class(
54
+ parse_require={"type"},
55
+ )
56
+ @dataclass(kw_only=True)
57
+ class PermissionsScopeMaterialFamily:
58
+ type: typing.Literal["material_family"] = "material_family"
59
+ material_family_key: identifier_t.IdentifierKey
60
+
61
+
62
+ # DO NOT MODIFY -- This file is generated by type_spec
63
+ @serial_class(
64
+ parse_require={"type"},
65
+ )
66
+ @dataclass(kw_only=True)
67
+ class PermissionsScopeAllMaterialFamilies:
68
+ type: typing.Literal["all_material_families"] = "all_material_families"
69
+
70
+
71
+ # DO NOT MODIFY -- This file is generated by type_spec
72
+ PermissionsScope = typing.Union[PermissionsScopeProject, PermissionsScopeRecipe, PermissionsScopeMaterialFamily, PermissionsScopeAllMaterialFamilies]
73
+
74
+
75
+ # DO NOT MODIFY -- This file is generated by type_spec
76
+ @dataclass(kw_only=True)
77
+ class Arguments:
78
+ scope: PermissionsScope
79
+ permissions_types: list[permissions_t.CorePermissionType]
80
+ update_type: post_base_t.UpdateType
81
+ user_group_ids: typing.Optional[list[int]] = None
82
+ user_ids: typing.Optional[list[int]] = None
83
+
84
+
85
+ # DO NOT MODIFY -- This file is generated by type_spec
86
+ @dataclass(kw_only=True)
87
+ class Data:
88
+ pass
89
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -8,7 +8,7 @@ import typing # noqa: F401
8
8
  import datetime # noqa: F401
9
9
  from decimal import Decimal # noqa: F401
10
10
  from dataclasses import dataclass
11
- from ... import base as base_t
11
+ from ... import async_batch as async_batch_t
12
12
  from ... import identifier as identifier_t
13
13
 
14
14
  __all__: list[str] = [
@@ -26,10 +26,11 @@ ENDPOINT_PATH = "api/external/recipes/associate_recipe_as_input"
26
26
  @dataclass(kw_only=True)
27
27
  class Arguments:
28
28
  recipe_key: identifier_t.IdentifierKey
29
+ input_key: typing.Optional[identifier_t.IdentifierKey] = None
29
30
 
30
31
 
31
32
  # DO NOT MODIFY -- This file is generated by type_spec
32
33
  @dataclass(kw_only=True)
33
- class Data:
34
- result_id: base_t.ObjectId
34
+ class Data(async_batch_t.AsyncBatchActionReturn):
35
+ pass
35
36
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -29,10 +29,10 @@ ENDPOINT_PATH = "api/external/recipes/create_recipe"
29
29
  class Arguments:
30
30
  material_family_id: base_t.ObjectId
31
31
  workflow_id: base_t.ObjectId
32
- identifiers: recipe_identifiers_t.RecipeIdentifiers
33
32
  name: typing.Optional[str] = None
34
33
  workflow_variant_id: typing.Optional[typing.Optional[base_t.ObjectId]] = None
35
34
  recipe_metadata: typing.Optional[list[recipe_metadata_t.MetadataValue]] = None
35
+ identifiers: typing.Optional[recipe_identifiers_t.RecipeIdentifiers] = None
36
36
  definition_key: typing.Optional[identifier_t.IdentifierKey] = None
37
37
 
38
38
 
@@ -0,0 +1,35 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # flake8: noqa: F821
3
+ # ruff: noqa: E402
4
+ # fmt: off
5
+ # isort: skip_file
6
+ from __future__ import annotations
7
+ import typing # noqa: F401
8
+ import datetime # noqa: F401
9
+ from decimal import Decimal # noqa: F401
10
+ from dataclasses import dataclass
11
+ from ... import async_batch as async_batch_t
12
+ from ... import identifier as identifier_t
13
+
14
+ __all__: list[str] = [
15
+ "Arguments",
16
+ "Data",
17
+ "ENDPOINT_METHOD",
18
+ "ENDPOINT_PATH",
19
+ ]
20
+
21
+ ENDPOINT_METHOD = "POST"
22
+ ENDPOINT_PATH = "api/external/recipes/disassociate_recipe_as_input"
23
+
24
+
25
+ # DO NOT MODIFY -- This file is generated by type_spec
26
+ @dataclass(kw_only=True)
27
+ class Arguments:
28
+ recipe_key: identifier_t.IdentifierKey
29
+
30
+
31
+ # DO NOT MODIFY -- This file is generated by type_spec
32
+ @dataclass(kw_only=True)
33
+ class Data(async_batch_t.AsyncBatchActionReturn):
34
+ pass
35
+ # DO NOT MODIFY -- This file is generated by type_spec