nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__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 (175) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +8 -13
  3. nextmv/__init__.py +53 -0
  4. nextmv/_serialization.py +96 -0
  5. nextmv/base_model.py +54 -9
  6. nextmv/cli/CONTRIBUTING.md +511 -0
  7. nextmv/cli/__init__.py +0 -0
  8. nextmv/cli/cloud/__init__.py +47 -0
  9. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  10. nextmv/cli/cloud/acceptance/create.py +393 -0
  11. nextmv/cli/cloud/acceptance/delete.py +68 -0
  12. nextmv/cli/cloud/acceptance/get.py +104 -0
  13. nextmv/cli/cloud/acceptance/list.py +62 -0
  14. nextmv/cli/cloud/acceptance/update.py +95 -0
  15. nextmv/cli/cloud/account/__init__.py +28 -0
  16. nextmv/cli/cloud/account/create.py +83 -0
  17. nextmv/cli/cloud/account/delete.py +60 -0
  18. nextmv/cli/cloud/account/get.py +66 -0
  19. nextmv/cli/cloud/account/update.py +70 -0
  20. nextmv/cli/cloud/app/__init__.py +35 -0
  21. nextmv/cli/cloud/app/create.py +141 -0
  22. nextmv/cli/cloud/app/delete.py +58 -0
  23. nextmv/cli/cloud/app/exists.py +44 -0
  24. nextmv/cli/cloud/app/get.py +66 -0
  25. nextmv/cli/cloud/app/list.py +61 -0
  26. nextmv/cli/cloud/app/push.py +137 -0
  27. nextmv/cli/cloud/app/update.py +124 -0
  28. nextmv/cli/cloud/batch/__init__.py +29 -0
  29. nextmv/cli/cloud/batch/create.py +454 -0
  30. nextmv/cli/cloud/batch/delete.py +68 -0
  31. nextmv/cli/cloud/batch/get.py +104 -0
  32. nextmv/cli/cloud/batch/list.py +63 -0
  33. nextmv/cli/cloud/batch/metadata.py +66 -0
  34. nextmv/cli/cloud/batch/update.py +95 -0
  35. nextmv/cli/cloud/data/__init__.py +26 -0
  36. nextmv/cli/cloud/data/upload.py +162 -0
  37. nextmv/cli/cloud/ensemble/__init__.py +31 -0
  38. nextmv/cli/cloud/ensemble/create.py +414 -0
  39. nextmv/cli/cloud/ensemble/delete.py +67 -0
  40. nextmv/cli/cloud/ensemble/get.py +65 -0
  41. nextmv/cli/cloud/ensemble/update.py +103 -0
  42. nextmv/cli/cloud/input_set/__init__.py +30 -0
  43. nextmv/cli/cloud/input_set/create.py +170 -0
  44. nextmv/cli/cloud/input_set/get.py +63 -0
  45. nextmv/cli/cloud/input_set/list.py +63 -0
  46. nextmv/cli/cloud/input_set/update.py +123 -0
  47. nextmv/cli/cloud/instance/__init__.py +35 -0
  48. nextmv/cli/cloud/instance/create.py +290 -0
  49. nextmv/cli/cloud/instance/delete.py +62 -0
  50. nextmv/cli/cloud/instance/exists.py +39 -0
  51. nextmv/cli/cloud/instance/get.py +62 -0
  52. nextmv/cli/cloud/instance/list.py +60 -0
  53. nextmv/cli/cloud/instance/update.py +216 -0
  54. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  55. nextmv/cli/cloud/managed_input/create.py +146 -0
  56. nextmv/cli/cloud/managed_input/delete.py +65 -0
  57. nextmv/cli/cloud/managed_input/get.py +63 -0
  58. nextmv/cli/cloud/managed_input/list.py +60 -0
  59. nextmv/cli/cloud/managed_input/update.py +97 -0
  60. nextmv/cli/cloud/run/__init__.py +37 -0
  61. nextmv/cli/cloud/run/cancel.py +37 -0
  62. nextmv/cli/cloud/run/create.py +530 -0
  63. nextmv/cli/cloud/run/get.py +199 -0
  64. nextmv/cli/cloud/run/input.py +86 -0
  65. nextmv/cli/cloud/run/list.py +80 -0
  66. nextmv/cli/cloud/run/logs.py +167 -0
  67. nextmv/cli/cloud/run/metadata.py +67 -0
  68. nextmv/cli/cloud/run/track.py +501 -0
  69. nextmv/cli/cloud/scenario/__init__.py +29 -0
  70. nextmv/cli/cloud/scenario/create.py +451 -0
  71. nextmv/cli/cloud/scenario/delete.py +65 -0
  72. nextmv/cli/cloud/scenario/get.py +102 -0
  73. nextmv/cli/cloud/scenario/list.py +63 -0
  74. nextmv/cli/cloud/scenario/metadata.py +67 -0
  75. nextmv/cli/cloud/scenario/update.py +93 -0
  76. nextmv/cli/cloud/secrets/__init__.py +33 -0
  77. nextmv/cli/cloud/secrets/create.py +206 -0
  78. nextmv/cli/cloud/secrets/delete.py +67 -0
  79. nextmv/cli/cloud/secrets/get.py +66 -0
  80. nextmv/cli/cloud/secrets/list.py +60 -0
  81. nextmv/cli/cloud/secrets/update.py +147 -0
  82. nextmv/cli/cloud/shadow/__init__.py +33 -0
  83. nextmv/cli/cloud/shadow/create.py +184 -0
  84. nextmv/cli/cloud/shadow/delete.py +68 -0
  85. nextmv/cli/cloud/shadow/get.py +61 -0
  86. nextmv/cli/cloud/shadow/list.py +63 -0
  87. nextmv/cli/cloud/shadow/metadata.py +66 -0
  88. nextmv/cli/cloud/shadow/start.py +43 -0
  89. nextmv/cli/cloud/shadow/stop.py +43 -0
  90. nextmv/cli/cloud/shadow/update.py +95 -0
  91. nextmv/cli/cloud/upload/__init__.py +22 -0
  92. nextmv/cli/cloud/upload/create.py +39 -0
  93. nextmv/cli/cloud/version/__init__.py +33 -0
  94. nextmv/cli/cloud/version/create.py +97 -0
  95. nextmv/cli/cloud/version/delete.py +62 -0
  96. nextmv/cli/cloud/version/exists.py +39 -0
  97. nextmv/cli/cloud/version/get.py +62 -0
  98. nextmv/cli/cloud/version/list.py +60 -0
  99. nextmv/cli/cloud/version/update.py +92 -0
  100. nextmv/cli/community/__init__.py +24 -0
  101. nextmv/cli/community/clone.py +270 -0
  102. nextmv/cli/community/list.py +265 -0
  103. nextmv/cli/configuration/__init__.py +23 -0
  104. nextmv/cli/configuration/config.py +195 -0
  105. nextmv/cli/configuration/create.py +94 -0
  106. nextmv/cli/configuration/delete.py +67 -0
  107. nextmv/cli/configuration/list.py +77 -0
  108. nextmv/cli/main.py +188 -0
  109. nextmv/cli/message.py +153 -0
  110. nextmv/cli/options.py +206 -0
  111. nextmv/cli/version.py +38 -0
  112. nextmv/cloud/__init__.py +71 -17
  113. nextmv/cloud/acceptance_test.py +757 -51
  114. nextmv/cloud/account.py +406 -17
  115. nextmv/cloud/application/__init__.py +957 -0
  116. nextmv/cloud/application/_acceptance.py +419 -0
  117. nextmv/cloud/application/_batch_scenario.py +860 -0
  118. nextmv/cloud/application/_ensemble.py +251 -0
  119. nextmv/cloud/application/_input_set.py +227 -0
  120. nextmv/cloud/application/_instance.py +289 -0
  121. nextmv/cloud/application/_managed_input.py +227 -0
  122. nextmv/cloud/application/_run.py +1393 -0
  123. nextmv/cloud/application/_secrets.py +294 -0
  124. nextmv/cloud/application/_shadow.py +314 -0
  125. nextmv/cloud/application/_utils.py +54 -0
  126. nextmv/cloud/application/_version.py +303 -0
  127. nextmv/cloud/assets.py +48 -0
  128. nextmv/cloud/batch_experiment.py +294 -33
  129. nextmv/cloud/client.py +307 -66
  130. nextmv/cloud/ensemble.py +247 -0
  131. nextmv/cloud/input_set.py +120 -2
  132. nextmv/cloud/instance.py +133 -8
  133. nextmv/cloud/integration.py +533 -0
  134. nextmv/cloud/package.py +168 -53
  135. nextmv/cloud/scenario.py +410 -0
  136. nextmv/cloud/secrets.py +234 -0
  137. nextmv/cloud/shadow.py +190 -0
  138. nextmv/cloud/url.py +73 -0
  139. nextmv/cloud/version.py +132 -4
  140. nextmv/default_app/.gitignore +1 -0
  141. nextmv/default_app/README.md +32 -0
  142. nextmv/default_app/app.yaml +12 -0
  143. nextmv/default_app/input.json +5 -0
  144. nextmv/default_app/main.py +37 -0
  145. nextmv/default_app/requirements.txt +2 -0
  146. nextmv/default_app/src/__init__.py +0 -0
  147. nextmv/default_app/src/visuals.py +36 -0
  148. nextmv/deprecated.py +47 -0
  149. nextmv/input.py +861 -90
  150. nextmv/local/__init__.py +5 -0
  151. nextmv/local/application.py +1251 -0
  152. nextmv/local/executor.py +1042 -0
  153. nextmv/local/geojson_handler.py +323 -0
  154. nextmv/local/local.py +97 -0
  155. nextmv/local/plotly_handler.py +61 -0
  156. nextmv/local/runner.py +274 -0
  157. nextmv/logger.py +80 -9
  158. nextmv/manifest.py +1466 -0
  159. nextmv/model.py +241 -66
  160. nextmv/options.py +708 -115
  161. nextmv/output.py +1301 -274
  162. nextmv/polling.py +325 -0
  163. nextmv/run.py +1702 -0
  164. nextmv/safe.py +145 -0
  165. nextmv/status.py +122 -0
  166. nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
  167. nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
  168. {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
  169. nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
  170. nextmv/cloud/application.py +0 -1405
  171. nextmv/cloud/manifest.py +0 -234
  172. nextmv/cloud/status.py +0 -29
  173. nextmv-0.18.0.dist-info/METADATA +0 -770
  174. nextmv-0.18.0.dist-info/RECORD +0 -25
  175. {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ Application mixin for managing app instances.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import requests
8
+
9
+ from nextmv.cloud.application._utils import _is_not_exist_error
10
+ from nextmv.cloud.instance import Instance, InstanceConfiguration
11
+ from nextmv.safe import safe_id
12
+
13
+ if TYPE_CHECKING:
14
+ from . import Application
15
+
16
+
17
+ class ApplicationInstanceMixin:
18
+ """
19
+ Mixin class for managing app instances within an application.
20
+ """
21
+
22
+ def delete_instance(self: "Application", instance_id: str) -> None:
23
+ """
24
+ Delete an instance.
25
+
26
+ Permanently removes the specified instance from the application.
27
+
28
+ Parameters
29
+ ----------
30
+ instance_id : str
31
+ ID of the instance to delete.
32
+
33
+ Raises
34
+ ------
35
+ requests.HTTPError
36
+ If the response status code is not 2xx.
37
+
38
+ Examples
39
+ --------
40
+ >>> app.delete_instance("prod-instance") # Permanently deletes the instance
41
+ """
42
+
43
+ _ = self.client.request(
44
+ method="DELETE",
45
+ endpoint=f"{self.endpoint}/instances/{instance_id}",
46
+ )
47
+
48
+ def instance(self: "Application", instance_id: str) -> Instance:
49
+ """
50
+ Get an instance.
51
+
52
+ Parameters
53
+ ----------
54
+ instance_id : str
55
+ ID of the instance to retrieve.
56
+
57
+ Returns
58
+ -------
59
+ Instance
60
+ The requested instance details.
61
+
62
+ Raises
63
+ ------
64
+ requests.HTTPError
65
+ If the response status code is not 2xx.
66
+
67
+ Examples
68
+ --------
69
+ >>> instance = app.instance("instance-123")
70
+ >>> print(instance.name)
71
+ 'Production Instance'
72
+ """
73
+
74
+ response = self.client.request(
75
+ method="GET",
76
+ endpoint=f"{self.endpoint}/instances/{instance_id}",
77
+ )
78
+
79
+ return Instance.from_dict(response.json())
80
+
81
+ def instance_exists(self: "Application", instance_id: str) -> bool:
82
+ """
83
+ Check if an instance exists.
84
+
85
+ Parameters
86
+ ----------
87
+ instance_id : str
88
+ ID of the instance to check.
89
+
90
+ Returns
91
+ -------
92
+ bool
93
+ True if the instance exists, False otherwise.
94
+
95
+ Examples
96
+ --------
97
+ >>> app.instance_exists("instance-123")
98
+ True
99
+ """
100
+
101
+ try:
102
+ self.instance(instance_id=instance_id)
103
+ return True
104
+ except requests.HTTPError as e:
105
+ if _is_not_exist_error(e):
106
+ return False
107
+ raise e
108
+
109
+ def list_instances(self: "Application") -> list[Instance]:
110
+ """
111
+ List all instances.
112
+
113
+ Returns
114
+ -------
115
+ list[Instance]
116
+ List of all instances associated with this application.
117
+
118
+ Raises
119
+ ------
120
+ requests.HTTPError
121
+ If the response status code is not 2xx.
122
+
123
+ Examples
124
+ --------
125
+ >>> instances = app.list_instances()
126
+ >>> for instance in instances:
127
+ ... print(instance.name)
128
+ 'Development Instance'
129
+ 'Production Instance'
130
+ """
131
+
132
+ response = self.client.request(
133
+ method="GET",
134
+ endpoint=f"{self.endpoint}/instances",
135
+ )
136
+
137
+ return [Instance.from_dict(instance) for instance in response.json()]
138
+
139
+ def new_instance(
140
+ self: "Application",
141
+ version_id: str,
142
+ id: str | None = None,
143
+ name: str | None = None,
144
+ description: str | None = None,
145
+ configuration: InstanceConfiguration | None = None,
146
+ exist_ok: bool = False,
147
+ ) -> Instance:
148
+ """
149
+ Create a new instance and associate it with a version.
150
+
151
+ This method creates a new instance associated with a specific version
152
+ of the application. Instances are configurations of an application
153
+ version that can be executed.
154
+
155
+ Parameters
156
+ ----------
157
+ version_id : str
158
+ ID of the version to associate the instance with.
159
+ id : str | None, default=None
160
+ ID of the instance. Will be generated if not provided.
161
+ name : str | None, default=None
162
+ Name of the instance. Will be generated if not provided.
163
+ description : Optional[str], default=None
164
+ Description of the instance.
165
+ configuration : Optional[InstanceConfiguration], default=None
166
+ Configuration to use for the instance. This can include resources,
167
+ timeouts, and other execution parameters.
168
+ exist_ok : bool, default=False
169
+ If True and an instance with the same ID already exists,
170
+ return the existing instance instead of creating a new one.
171
+
172
+ Returns
173
+ -------
174
+ Instance
175
+ The newly created (or existing) instance.
176
+
177
+ Raises
178
+ ------
179
+ requests.HTTPError
180
+ If the response status code is not 2xx.
181
+ ValueError
182
+ If exist_ok is True and id is None.
183
+
184
+ Examples
185
+ --------
186
+ >>> # Create a new instance for a specific version
187
+ >>> instance = app.new_instance(
188
+ ... version_id="version-123",
189
+ ... id="prod-instance",
190
+ ... name="Production Instance",
191
+ ... description="Instance for production use"
192
+ ... )
193
+ >>> print(instance.name)
194
+ 'Production Instance'
195
+ """
196
+
197
+ if exist_ok and id is None:
198
+ raise ValueError("If exist_ok is True, id must be provided")
199
+
200
+ if exist_ok and self.instance_exists(instance_id=id):
201
+ return self.instance(instance_id=id)
202
+
203
+ if id is None:
204
+ id = safe_id(prefix="instance")
205
+ if name is None:
206
+ name = id
207
+
208
+ payload = {
209
+ "id": id,
210
+ "name": name,
211
+ "version_id": version_id,
212
+ }
213
+
214
+ if description is not None:
215
+ payload["description"] = description
216
+ if configuration is not None:
217
+ payload["configuration"] = configuration.to_dict()
218
+
219
+ response = self.client.request(
220
+ method="POST",
221
+ endpoint=f"{self.endpoint}/instances",
222
+ payload=payload,
223
+ )
224
+
225
+ return Instance.from_dict(response.json())
226
+
227
+ def update_instance(
228
+ self: "Application",
229
+ id: str,
230
+ name: str | None = None,
231
+ version_id: str | None = None,
232
+ description: str | None = None,
233
+ configuration: InstanceConfiguration | dict[str, Any] | None = None,
234
+ ) -> Instance:
235
+ """
236
+ Update an instance.
237
+
238
+ Parameters
239
+ ----------
240
+ id : str
241
+ ID of the instance to update.
242
+ name : Optional[str], default=None
243
+ Optional name of the instance.
244
+ version_id : Optional[str], default=None
245
+ Optional ID of the version to associate the instance with.
246
+ description : Optional[str], default=None
247
+ Optional description of the instance.
248
+ configuration : Optional[InstanceConfiguration | dict[str, Any]], default=None
249
+ Optional configuration to use for the instance.
250
+
251
+ Returns
252
+ -------
253
+ Instance
254
+ The updated instance.
255
+
256
+ Raises
257
+ ------
258
+ requests.HTTPError
259
+ If the response status code is not 2xx.
260
+ """
261
+
262
+ # Get the instance as it currently exsits.
263
+ instance = self.instance(id)
264
+ instance_dict = instance.to_dict()
265
+ payload = instance_dict.copy()
266
+
267
+ if name is not None:
268
+ payload["name"] = name
269
+ if version_id is not None:
270
+ payload["version_id"] = version_id
271
+ if description is not None:
272
+ payload["description"] = description
273
+ if configuration is not None:
274
+ if isinstance(configuration, dict):
275
+ config_dict = configuration
276
+ elif isinstance(configuration, InstanceConfiguration):
277
+ config_dict = configuration.to_dict()
278
+ else:
279
+ raise TypeError("configuration must be either a dict or InstanceConfiguration object")
280
+
281
+ payload["configuration"] = config_dict
282
+
283
+ response = self.client.request(
284
+ method="PUT",
285
+ endpoint=f"{self.endpoint}/instances/{id}",
286
+ payload=payload,
287
+ )
288
+
289
+ return Instance.from_dict(response.json())
@@ -0,0 +1,227 @@
1
+ """
2
+ Application mixin for handling app managed inputs.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from nextmv.cloud.input_set import ManagedInput
8
+ from nextmv.input import InputFormat
9
+ from nextmv.output import OutputFormat
10
+ from nextmv.run import Format, FormatInput, FormatOutput
11
+ from nextmv.safe import safe_id
12
+
13
+ if TYPE_CHECKING:
14
+ from . import Application
15
+
16
+
17
+ class ApplicationManagedInputMixin:
18
+ """
19
+ Mixin class for handling app managed inputs within an application.
20
+ """
21
+
22
+ def delete_managed_input(self: "Application", managed_input_id: str) -> None:
23
+ """
24
+ Delete a managed input.
25
+
26
+ Permanently removes the specified managed input from the application.
27
+
28
+ Parameters
29
+ ----------
30
+ managed_input_id : str
31
+ ID of the managed input to delete.
32
+
33
+ Raises
34
+ ------
35
+ requests.HTTPError
36
+ If the response status code is not 2xx.
37
+
38
+ Examples
39
+ --------
40
+ >>> app.delete_managed_input("inp_123456789") # Permanently deletes the managed input
41
+ """
42
+
43
+ _ = self.client.request(
44
+ method="DELETE",
45
+ endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
46
+ )
47
+
48
+ def list_managed_inputs(self: "Application") -> list[ManagedInput]:
49
+ """
50
+ List all managed inputs.
51
+
52
+ Returns
53
+ -------
54
+ list[ManagedInput]
55
+ List of managed inputs.
56
+
57
+ Raises
58
+ ------
59
+ requests.HTTPError
60
+ If the response status code is not 2xx.
61
+ """
62
+
63
+ response = self.client.request(
64
+ method="GET",
65
+ endpoint=f"{self.endpoint}/inputs",
66
+ )
67
+
68
+ return [ManagedInput.from_dict(managed_input) for managed_input in response.json()]
69
+
70
+ def managed_input(self: "Application", managed_input_id: str) -> ManagedInput:
71
+ """
72
+ Get a managed input.
73
+
74
+ Parameters
75
+ ----------
76
+ managed_input_id: str
77
+ ID of the managed input.
78
+
79
+ Returns
80
+ -------
81
+ ManagedInput
82
+ The managed input.
83
+
84
+ Raises
85
+ ------
86
+ requests.HTTPError
87
+ If the response status code is not 2xx.
88
+ """
89
+
90
+ response = self.client.request(
91
+ method="GET",
92
+ endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
93
+ )
94
+
95
+ return ManagedInput.from_dict(response.json())
96
+
97
+ def new_managed_input(
98
+ self: "Application",
99
+ id: str | None = None,
100
+ name: str | None = None,
101
+ description: str | None = None,
102
+ upload_id: str | None = None,
103
+ run_id: str | None = None,
104
+ format: Format | dict[str, Any] | None = None,
105
+ ) -> ManagedInput:
106
+ """
107
+ Create a new managed input. There are two methods for creating a
108
+ managed input:
109
+
110
+ 1. Specifying the `upload_id` parameter. You may use the `upload_url`
111
+ method to obtain the upload ID and the `upload_data` method
112
+ to upload the data to it.
113
+ 2. Specifying the `run_id` parameter. The managed input will be
114
+ created from the run specified by the `run_id` parameter.
115
+
116
+ Either the `upload_id` or the `run_id` parameter must be specified.
117
+
118
+ Parameters
119
+ ----------
120
+ id: Optional[str], default=None
121
+ ID of the managed input. Will be generated if not provided.
122
+ name: Optional[str], default=None
123
+ Name of the managed input. Will be generated if not provided.
124
+ description: Optional[str], default=None
125
+ Optional description of the managed input.
126
+ upload_id: Optional[str], default=None
127
+ ID of the upload to use for the managed input.
128
+ run_id: Optional[str], default=None
129
+ ID of the run to use for the managed input.
130
+ format: Optional[Format], default=None
131
+ Format of the managed input. Default will be formatted as `JSON`.
132
+
133
+ Returns
134
+ -------
135
+ ManagedInput
136
+ The new managed input.
137
+
138
+ Raises
139
+ ------
140
+ requests.HTTPError
141
+ If the response status code is not 2xx.
142
+ ValueError
143
+ If neither the `upload_id` nor the `run_id` parameter is
144
+ specified.
145
+ """
146
+
147
+ if upload_id is None and run_id is None:
148
+ raise ValueError("Either upload_id or run_id must be specified")
149
+
150
+ if id is None:
151
+ id = safe_id(prefix="managed-input")
152
+ if name is None:
153
+ name = id
154
+
155
+ payload = {
156
+ "id": id,
157
+ "name": name,
158
+ }
159
+
160
+ if description is not None:
161
+ payload["description"] = description
162
+ if upload_id is not None:
163
+ payload["upload_id"] = upload_id
164
+ if run_id is not None:
165
+ payload["run_id"] = run_id
166
+
167
+ if format is not None:
168
+ payload["format"] = format.to_dict() if isinstance(format, Format) else format
169
+ else:
170
+ payload["format"] = Format(
171
+ format_input=FormatInput(input_type=InputFormat.JSON),
172
+ format_output=FormatOutput(output_type=OutputFormat.JSON),
173
+ ).to_dict()
174
+
175
+ response = self.client.request(
176
+ method="POST",
177
+ endpoint=f"{self.endpoint}/inputs",
178
+ payload=payload,
179
+ )
180
+
181
+ return ManagedInput.from_dict(response.json())
182
+
183
+ def update_managed_input(
184
+ self: "Application",
185
+ managed_input_id: str,
186
+ name: str | None = None,
187
+ description: str | None = None,
188
+ ) -> ManagedInput:
189
+ """
190
+ Update a managed input.
191
+
192
+ Parameters
193
+ ----------
194
+ managed_input_id : str
195
+ ID of the managed input to update.
196
+ name : Optional[str], default=None
197
+ Optional new name for the managed input.
198
+ description : Optional[str], default=None
199
+ Optional new description for the managed input.
200
+
201
+ Returns
202
+ -------
203
+ ManagedInput
204
+ The updated managed input.
205
+
206
+ Raises
207
+ ------
208
+ requests.HTTPError
209
+ If the response status code is not 2xx.
210
+ """
211
+
212
+ managed_input = self.managed_input(managed_input_id)
213
+ managed_input_dict = managed_input.to_dict()
214
+ payload = managed_input_dict.copy()
215
+
216
+ if name is not None:
217
+ payload["name"] = name
218
+ if description is not None:
219
+ payload["description"] = description
220
+
221
+ response = self.client.request(
222
+ method="PUT",
223
+ endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
224
+ payload=payload,
225
+ )
226
+
227
+ return ManagedInput.from_dict(response.json())