nextmv 0.27.0__py3-none-any.whl → 0.28.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.
@@ -1,4 +1,26 @@
1
- """This module contains the application class."""
1
+ """
2
+ Application module for interacting with Nextmv Cloud applications.
3
+
4
+ This module provides functionality to interact with applications in Nextmv Cloud,
5
+ including application management, running applications, and managing experiments
6
+ and inputs.
7
+
8
+ Classes
9
+ -------
10
+ DownloadURL
11
+ Result of getting a download URL.
12
+ PollingOptions
13
+ Options for polling when waiting for run results.
14
+ UploadURL
15
+ Result of getting an upload URL.
16
+ Application
17
+ Class for interacting with applications in Nextmv Cloud.
18
+
19
+ Functions
20
+ ---------
21
+ poll
22
+ Function to poll for results with configurable options.
23
+ """
2
24
 
3
25
  import json
4
26
  import random
@@ -34,7 +56,7 @@ from nextmv.cloud.run import (
34
56
  RunResult,
35
57
  TrackedRun,
36
58
  )
37
- from nextmv.cloud.safe import name_and_id
59
+ from nextmv.cloud.safe import _name_and_id
38
60
  from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
39
61
  from nextmv.cloud.secrets import Secret, SecretsCollection, SecretsCollectionSummary
40
62
  from nextmv.cloud.status import StatusV2
@@ -45,13 +67,36 @@ from nextmv.model import Model, ModelConfiguration
45
67
  from nextmv.options import Options
46
68
  from nextmv.output import Output
47
69
 
70
+ # Maximum size of the run input/output in bytes. This constant defines the
71
+ # maximum allowed size for run inputs and outputs. When the size exceeds this
72
+ # value, the system will automatically use the large input upload and/or large
73
+ # result download endpoints.
48
74
  _MAX_RUN_SIZE: int = 5 * 1024 * 1024
49
- """Maximum size of the run input/output. This value is used to determine
50
- whether to use the large input upload and/or result download endpoints."""
51
75
 
52
76
 
53
77
  class DownloadURL(BaseModel):
54
- """Result of getting a download URL."""
78
+ """
79
+ Result of getting a download URL.
80
+
81
+ You can import the `DownloadURL` class directly from `cloud`:
82
+
83
+ ```python
84
+ from nextmv.cloud import DownloadURL
85
+ ```
86
+
87
+ This class represents a download URL that can be used to fetch content
88
+ from Nextmv Cloud, typically used for downloading large run results.
89
+
90
+ Attributes
91
+ ----------
92
+ url : str
93
+ URL to use for downloading the file.
94
+
95
+ Examples
96
+ --------
97
+ >>> download_url = DownloadURL(url="https://example.com/download")
98
+ >>> response = requests.get(download_url.url)
99
+ """
55
100
 
56
101
  url: str
57
102
  """URL to use for downloading the file."""
@@ -62,6 +107,12 @@ class PollingOptions:
62
107
  """
63
108
  Options to use when polling for a run result.
64
109
 
110
+ You can import the `PollingOptions` class directly from `cloud`:
111
+
112
+ ```python
113
+ from nextmv.cloud import PollingOptions
114
+ ```
115
+
65
116
  The Cloud API will be polled for the result. The polling stops if:
66
117
 
67
118
  * The maximum number of polls (tries) are exhausted. This is specified by
@@ -82,6 +133,43 @@ class PollingOptions:
82
133
  * Uniform is the uniform distribution.
83
134
 
84
135
  Note that the sleep duration is capped by the `max_delay` parameter.
136
+
137
+ Parameters
138
+ ----------
139
+ backoff : float, default=0.9
140
+ Exponential backoff factor, in seconds, to use between polls.
141
+ delay : float, default=0.1
142
+ Base delay to use between polls, in seconds.
143
+ initial_delay : float, default=1.0
144
+ Initial delay to use before starting the polling strategy, in seconds.
145
+ max_delay : float, default=20.0
146
+ Maximum delay to use between polls, in seconds.
147
+ max_duration : float, default=300.0
148
+ Maximum duration of the polling strategy, in seconds.
149
+ max_tries : int, default=100
150
+ Maximum number of tries to use.
151
+ jitter : float, default=1.0
152
+ Jitter to use for the polling strategy. A uniform distribution is sampled
153
+ between 0 and this number. The resulting random number is added to the
154
+ delay for each poll, adding a random noise. Set this to 0 to avoid using
155
+ random jitter.
156
+ verbose : bool, default=False
157
+ Whether to log the polling strategy. This is useful for debugging.
158
+ stop : callable, default=None
159
+ Function to call to check if the polling should stop. This is useful for
160
+ stopping the polling based on external conditions. The function should
161
+ return True to stop the polling and False to continue. The function does
162
+ not receive any arguments. The function is called before each poll.
163
+
164
+ Examples
165
+ --------
166
+ >>> from nextmv.cloud import PollingOptions
167
+ >>> # Create polling options with custom settings
168
+ >>> polling_options = PollingOptions(
169
+ ... max_tries=50,
170
+ ... max_duration=600,
171
+ ... verbose=True
172
+ ... )
85
173
  """
86
174
 
87
175
  backoff: float = 0.9
@@ -118,12 +206,39 @@ class PollingOptions:
118
206
  """
119
207
 
120
208
 
209
+ # Default polling options to use when polling for a run result. This constant
210
+ # provides the default values for `PollingOptions` used across the module.
211
+ # Using these defaults is recommended for most use cases unless specific timing
212
+ # needs are required.
121
213
  _DEFAULT_POLLING_OPTIONS: PollingOptions = PollingOptions()
122
- """Default polling options to use when polling for a run result."""
123
214
 
124
215
 
125
216
  class UploadURL(BaseModel):
126
- """Result of getting an upload URL."""
217
+ """
218
+ Result of getting an upload URL.
219
+
220
+ You can import the `UploadURL` class directly from `cloud`:
221
+
222
+ ```python
223
+ from nextmv.cloud import UploadURL
224
+ ```
225
+
226
+ This class represents an upload URL that can be used to send data to
227
+ Nextmv Cloud, typically used for uploading large inputs for runs.
228
+
229
+ Attributes
230
+ ----------
231
+ upload_id : str
232
+ ID of the upload, used to reference the uploaded content.
233
+ upload_url : str
234
+ URL to use for uploading the file.
235
+
236
+ Examples
237
+ --------
238
+ >>> upload_url = UploadURL(upload_id="123", upload_url="https://example.com/upload")
239
+ >>> with open("large_input.json", "rb") as f:
240
+ ... requests.put(upload_url.upload_url, data=f)
241
+ """
127
242
 
128
243
  upload_id: str
129
244
  """ID of the upload."""
@@ -133,7 +248,40 @@ class UploadURL(BaseModel):
133
248
 
134
249
  @dataclass
135
250
  class Application:
136
- """An application is a published decision model that can be executed."""
251
+ """
252
+ A published decision model that can be executed.
253
+
254
+ You can import the `Application` class directly from `cloud`:
255
+
256
+ ```python
257
+ from nextmv.cloud import Application
258
+ ```
259
+
260
+ This class represents an application in Nextmv Cloud, providing methods to
261
+ interact with the application, run it with different inputs, manage versions,
262
+ instances, experiments, and more.
263
+
264
+ Parameters
265
+ ----------
266
+ client : Client
267
+ Client to use for interacting with the Nextmv Cloud API.
268
+ id : str
269
+ ID of the application.
270
+ default_instance_id : str, default="devint"
271
+ Default instance ID to use for submitting runs.
272
+ endpoint : str, default="v1/applications/{id}"
273
+ Base endpoint for the application.
274
+ experiments_endpoint : str, default="{base}/experiments"
275
+ Base endpoint for the experiments in the application.
276
+
277
+ Examples
278
+ --------
279
+ >>> from nextmv.cloud import Client, Application
280
+ >>> client = Client(api_key="your-api-key")
281
+ >>> app = Application(client=client, id="your-app-id")
282
+ >>> # Retrieve app information
283
+ >>> instances = app.list_instances()
284
+ """
137
285
 
138
286
  client: Client
139
287
  """Client to use for interacting with the Nextmv Cloud API."""
@@ -148,25 +296,39 @@ class Application:
148
296
  """Base endpoint for the experiments in the application."""
149
297
 
150
298
  def __post_init__(self):
151
- """Logic to run after the class is initialized."""
299
+ """Initialize the endpoint and experiments_endpoint attributes.
152
300
 
301
+ This method is automatically called after class initialization to
302
+ format the endpoint and experiments_endpoint URLs with the application ID.
303
+ """
153
304
  self.endpoint = self.endpoint.format(id=self.id)
154
305
  self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
155
306
 
156
307
  def acceptance_test(self, acceptance_test_id: str) -> AcceptanceTest:
157
308
  """
158
- Get an acceptance test.
309
+ Retrieve details of an acceptance test.
159
310
 
160
- Args:
161
- acceptance_test_id: ID of the acceptance test.
311
+ Parameters
312
+ ----------
313
+ acceptance_test_id : str
314
+ ID of the acceptance test to retrieve.
162
315
 
163
- Returns:
164
- Acceptance test.
316
+ Returns
317
+ -------
318
+ AcceptanceTest
319
+ The requested acceptance test details.
165
320
 
166
- Raises:
167
- requests.HTTPError: If the response status code is not 2xx.
168
- """
321
+ Raises
322
+ ------
323
+ requests.HTTPError
324
+ If the response status code is not 2xx.
169
325
 
326
+ Examples
327
+ --------
328
+ >>> test = app.acceptance_test("test-123")
329
+ >>> print(test.name)
330
+ 'My Test'
331
+ """
170
332
  response = self.client.request(
171
333
  method="GET",
172
334
  endpoint=f"{self.experiments_endpoint}/acceptance/{acceptance_test_id}",
@@ -178,14 +340,26 @@ class Application:
178
340
  """
179
341
  Get a batch experiment.
180
342
 
181
- Args:
182
- batch_id: ID of the batch experiment.
343
+ Parameters
344
+ ----------
345
+ batch_id : str
346
+ ID of the batch experiment.
347
+
348
+ Returns
349
+ -------
350
+ BatchExperiment
351
+ The requested batch experiment details.
183
352
 
184
- Returns:
185
- Batch experiment.
353
+ Raises
354
+ ------
355
+ requests.HTTPError
356
+ If the response status code is not 2xx.
186
357
 
187
- Raises:
188
- requests.HTTPError: If the response status code is not 2xx.
358
+ Examples
359
+ --------
360
+ >>> batch_exp = app.batch_experiment("batch-123")
361
+ >>> print(batch_exp.name)
362
+ 'My Batch Experiment'
189
363
  """
190
364
 
191
365
  response = self.client.request(
@@ -199,11 +373,19 @@ class Application:
199
373
  """
200
374
  Cancel a run.
201
375
 
202
- Args:
203
- run_id: ID of the run.
376
+ Parameters
377
+ ----------
378
+ run_id : str
379
+ ID of the run to cancel.
380
+
381
+ Raises
382
+ ------
383
+ requests.HTTPError
384
+ If the response status code is not 2xx.
204
385
 
205
- Raises:
206
- requests.HTTPError: If the response status code is not 2xx.
386
+ Examples
387
+ --------
388
+ >>> app.cancel_run("run-456")
207
389
  """
208
390
 
209
391
  _ = self.client.request(
@@ -215,8 +397,16 @@ class Application:
215
397
  """
216
398
  Delete the application.
217
399
 
218
- Raises:
219
- requests.HTTPError: If the response status code is not 2xx.
400
+ Permanently removes the application from Nextmv Cloud.
401
+
402
+ Raises
403
+ ------
404
+ requests.HTTPError
405
+ If the response status code is not 2xx.
406
+
407
+ Examples
408
+ --------
409
+ >>> app.delete() # Permanently deletes the application
220
410
  """
221
411
 
222
412
  _ = self.client.request(
@@ -226,14 +416,24 @@ class Application:
226
416
 
227
417
  def delete_acceptance_test(self, acceptance_test_id: str) -> None:
228
418
  """
229
- Deletes an acceptance test, along with all the associated information
419
+ Delete an acceptance test.
420
+
421
+ Deletes an acceptance test along with all the associated information
230
422
  such as the underlying batch experiment.
231
423
 
232
- Args:
233
- acceptance_test_id: ID of the acceptance test.
424
+ Parameters
425
+ ----------
426
+ acceptance_test_id : str
427
+ ID of the acceptance test to delete.
428
+
429
+ Raises
430
+ ------
431
+ requests.HTTPError
432
+ If the response status code is not 2xx.
234
433
 
235
- Raises:
236
- requests.HTTPError: If the response status code is not 2xx.
434
+ Examples
435
+ --------
436
+ >>> app.delete_acceptance_test("test-123")
237
437
  """
238
438
 
239
439
  _ = self.client.request(
@@ -243,18 +443,24 @@ class Application:
243
443
 
244
444
  def delete_batch_experiment(self, batch_id: str) -> None:
245
445
  """
246
- Deletes a batch experiment, along with all the associated information,
446
+ Delete a batch experiment.
447
+
448
+ Deletes a batch experiment along with all the associated information,
247
449
  such as its runs.
248
450
 
249
451
  Parameters
250
452
  ----------
251
- batch_id: str
252
- ID of the batch experiment.
453
+ batch_id : str
454
+ ID of the batch experiment to delete.
253
455
 
254
456
  Raises
255
457
  ------
256
458
  requests.HTTPError
257
459
  If the response status code is not 2xx.
460
+
461
+ Examples
462
+ --------
463
+ >>> app.delete_batch_experiment("batch-123")
258
464
  """
259
465
 
260
466
  _ = self.client.request(
@@ -264,31 +470,45 @@ class Application:
264
470
 
265
471
  def delete_scenario_test(self, scenario_test_id: str) -> None:
266
472
  """
473
+ Delete a scenario test.
474
+
267
475
  Deletes a scenario test. Scenario tests are based on the batch
268
476
  experiments API, so this function summons `delete_batch_experiment`.
269
477
 
270
478
  Parameters
271
479
  ----------
272
- scenario_test_id: str
273
- ID of the scenario test.
480
+ scenario_test_id : str
481
+ ID of the scenario test to delete.
274
482
 
275
483
  Raises
276
484
  ------
277
485
  requests.HTTPError
278
486
  If the response status code is not 2xx.
487
+
488
+ Examples
489
+ --------
490
+ >>> app.delete_scenario_test("scenario-123")
279
491
  """
280
492
 
281
493
  self.delete_batch_experiment(batch_id=scenario_test_id)
282
494
 
283
495
  def delete_secrets_collection(self, secrets_collection_id: str) -> None:
284
496
  """
285
- Deletes a secrets collection.
497
+ Delete a secrets collection.
286
498
 
287
- Args:
288
- secrets_collection_id: ID of the secrets collection.
499
+ Parameters
500
+ ----------
501
+ secrets_collection_id : str
502
+ ID of the secrets collection to delete.
503
+
504
+ Raises
505
+ ------
506
+ requests.HTTPError
507
+ If the response status code is not 2xx.
289
508
 
290
- Raises:
291
- requests.HTTPError: If the response status code is not 2xx.
509
+ Examples
510
+ --------
511
+ >>> app.delete_secrets_collection("secrets-123")
292
512
  """
293
513
 
294
514
  _ = self.client.request(
@@ -301,12 +521,24 @@ class Application:
301
521
  """
302
522
  Check if an application exists.
303
523
 
304
- Args:
305
- client: Client to use for interacting with the Nextmv Cloud API.
306
- id: ID of the application.
524
+ Parameters
525
+ ----------
526
+ client : Client
527
+ Client to use for interacting with the Nextmv Cloud API.
528
+ id : str
529
+ ID of the application to check.
307
530
 
308
- Returns:
531
+ Returns
532
+ -------
533
+ bool
309
534
  True if the application exists, False otherwise.
535
+
536
+ Examples
537
+ --------
538
+ >>> from nextmv.cloud import Client
539
+ >>> client = Client(api_key="your-api-key")
540
+ >>> Application.exists(client, "app-123")
541
+ True
310
542
  """
311
543
 
312
544
  try:
@@ -326,14 +558,26 @@ class Application:
326
558
  """
327
559
  Get an input set.
328
560
 
329
- Args:
330
- input_set_id: ID of the input set.
561
+ Parameters
562
+ ----------
563
+ input_set_id : str
564
+ ID of the input set to retrieve.
565
+
566
+ Returns
567
+ -------
568
+ InputSet
569
+ The requested input set.
331
570
 
332
- Returns:
333
- Input set.
571
+ Raises
572
+ ------
573
+ requests.HTTPError
574
+ If the response status code is not 2xx.
334
575
 
335
- Raises:
336
- requests.HTTPError: If the response status code is not 2xx.
576
+ Examples
577
+ --------
578
+ >>> input_set = app.input_set("input-set-123")
579
+ >>> print(input_set.name)
580
+ 'My Input Set'
337
581
  """
338
582
 
339
583
  response = self.client.request(
@@ -347,14 +591,26 @@ class Application:
347
591
  """
348
592
  Get an instance.
349
593
 
350
- Args:
351
- instance_id: ID of the instance.
594
+ Parameters
595
+ ----------
596
+ instance_id : str
597
+ ID of the instance to retrieve.
352
598
 
353
- Returns:
354
- Instance.
599
+ Returns
600
+ -------
601
+ Instance
602
+ The requested instance details.
355
603
 
356
- Raises:
357
- requests.HTTPError: If the response status code is not 2xx.
604
+ Raises
605
+ ------
606
+ requests.HTTPError
607
+ If the response status code is not 2xx.
608
+
609
+ Examples
610
+ --------
611
+ >>> instance = app.instance("instance-123")
612
+ >>> print(instance.name)
613
+ 'Production Instance'
358
614
  """
359
615
 
360
616
  response = self.client.request(
@@ -368,11 +624,20 @@ class Application:
368
624
  """
369
625
  Check if an instance exists.
370
626
 
371
- Args:
372
- instance_id: ID of the instance.
627
+ Parameters
628
+ ----------
629
+ instance_id : str
630
+ ID of the instance to check.
373
631
 
374
- Returns:
632
+ Returns
633
+ -------
634
+ bool
375
635
  True if the instance exists, False otherwise.
636
+
637
+ Examples
638
+ --------
639
+ >>> app.instance_exists("instance-123")
640
+ True
376
641
  """
377
642
 
378
643
  try:
@@ -387,11 +652,23 @@ class Application:
387
652
  """
388
653
  List all acceptance tests.
389
654
 
390
- Returns:
391
- List of acceptance tests.
655
+ Returns
656
+ -------
657
+ list[AcceptanceTest]
658
+ List of all acceptance tests associated with this application.
659
+
660
+ Raises
661
+ ------
662
+ requests.HTTPError
663
+ If the response status code is not 2xx.
392
664
 
393
- Raises:
394
- requests.HTTPError: If the response status code is not 2xx.
665
+ Examples
666
+ --------
667
+ >>> tests = app.list_acceptance_tests()
668
+ >>> for test in tests:
669
+ ... print(test.name)
670
+ 'Test 1'
671
+ 'Test 2'
395
672
  """
396
673
 
397
674
  response = self.client.request(
@@ -428,11 +705,23 @@ class Application:
428
705
  """
429
706
  List all input sets.
430
707
 
431
- Returns:
432
- List of input sets.
708
+ Returns
709
+ -------
710
+ list[InputSet]
711
+ List of all input sets associated with this application.
433
712
 
434
- Raises:
435
- requests.HTTPError: If the response status code is not 2xx.
713
+ Raises
714
+ ------
715
+ requests.HTTPError
716
+ If the response status code is not 2xx.
717
+
718
+ Examples
719
+ --------
720
+ >>> input_sets = app.list_input_sets()
721
+ >>> for input_set in input_sets:
722
+ ... print(input_set.name)
723
+ 'Input Set 1'
724
+ 'Input Set 2'
436
725
  """
437
726
 
438
727
  response = self.client.request(
@@ -446,11 +735,23 @@ class Application:
446
735
  """
447
736
  List all instances.
448
737
 
449
- Returns:
450
- List of instances.
738
+ Returns
739
+ -------
740
+ list[Instance]
741
+ List of all instances associated with this application.
742
+
743
+ Raises
744
+ ------
745
+ requests.HTTPError
746
+ If the response status code is not 2xx.
451
747
 
452
- Raises:
453
- requests.HTTPError: If the response status code is not 2xx.
748
+ Examples
749
+ --------
750
+ >>> instances = app.list_instances()
751
+ >>> for instance in instances:
752
+ ... print(instance.name)
753
+ 'Development Instance'
754
+ 'Production Instance'
454
755
  """
455
756
 
456
757
  response = self.client.request(
@@ -511,11 +812,23 @@ class Application:
511
812
  """
512
813
  List all secrets collections.
513
814
 
514
- Returns:
515
- List of secrets collections.
815
+ Returns
816
+ -------
817
+ list[SecretsCollectionSummary]
818
+ List of all secrets collections associated with this application.
819
+
820
+ Raises
821
+ ------
822
+ requests.HTTPError
823
+ If the response status code is not 2xx.
516
824
 
517
- Raises:
518
- requests.HTTPError: If the response status code is not 2xx.
825
+ Examples
826
+ --------
827
+ >>> collections = app.list_secrets_collections()
828
+ >>> for collection in collections:
829
+ ... print(collection.name)
830
+ 'API Keys'
831
+ 'Database Credentials'
519
832
  """
520
833
 
521
834
  response = self.client.request(
@@ -529,11 +842,23 @@ class Application:
529
842
  """
530
843
  List all versions.
531
844
 
532
- Returns:
533
- List of versions.
845
+ Returns
846
+ -------
847
+ list[Version]
848
+ List of all versions associated with this application.
849
+
850
+ Raises
851
+ ------
852
+ requests.HTTPError
853
+ If the response status code is not 2xx.
534
854
 
535
- Raises:
536
- requests.HTTPError: If the response status code is not 2xx.
855
+ Examples
856
+ --------
857
+ >>> versions = app.list_versions()
858
+ >>> for version in versions:
859
+ ... print(version.name)
860
+ 'v1.0.0'
861
+ 'v1.1.0'
537
862
  """
538
863
 
539
864
  response = self.client.request(
@@ -583,17 +908,32 @@ class Application:
583
908
  """
584
909
  Create a new application.
585
910
 
586
- Args:
587
- client: Client to use for interacting with the Nextmv Cloud API.
588
- name: Name of the application.
589
- id: ID of the application. Will be generated if not provided.
590
- description: Description of the application.
591
- is_workflow: Whether the application is a Decision Workflow.
592
- exist_ok: If True and an application with the same ID already exists,
593
- return the existing application instead of creating a new one.
911
+ Parameters
912
+ ----------
913
+ client : Client
914
+ Client to use for interacting with the Nextmv Cloud API.
915
+ name : str
916
+ Name of the application.
917
+ id : str, optional
918
+ ID of the application. Will be generated if not provided.
919
+ description : str, optional
920
+ Description of the application.
921
+ is_workflow : bool, optional
922
+ Whether the application is a Decision Workflow.
923
+ exist_ok : bool, default=False
924
+ If True and an application with the same ID already exists,
925
+ return the existing application instead of creating a new one.
926
+
927
+ Returns
928
+ -------
929
+ Application
930
+ The newly created (or existing) application.
594
931
 
595
- Returns:
596
- The new application.
932
+ Examples
933
+ --------
934
+ >>> from nextmv.cloud import Client
935
+ >>> client = Client(api_key="your-api-key")
936
+ >>> app = Application.new(client=client, name="My New App", id="my-app")
597
937
  """
598
938
 
599
939
  if exist_ok and cls.exists(client=client, id=id):
@@ -629,30 +969,44 @@ class Application:
629
969
  description: Optional[str] = None,
630
970
  ) -> AcceptanceTest:
631
971
  """
632
- Create a new acceptance test. The acceptance test is based on a batch
633
- experiment. If you already started a batch experiment, you don't need
634
- to provide the input_set_id parameter. In that case, the ID of the
635
- acceptance test and the batch experiment must be the same. If the batch
636
- experiment does not exist, you can provide the input_set_id parameter
637
- and a new batch experiment will be created for you.
972
+ Create a new acceptance test.
638
973
 
639
- Args:
640
- candidate_instance_id: ID of the candidate instance.
641
- baseline_instance_id: ID of the baseline instance.
642
- id: ID of the acceptance test.
643
- metrics: List of metrics to use for the acceptance test.
644
- name: Name of the acceptance test.
645
- input_set_id: ID of the input set to use for the underlying batch
646
- experiment, in case it hasn't been started.
647
- description: Description of the acceptance test.
974
+ The acceptance test is based on a batch experiment. If you already
975
+ started a batch experiment, you don't need to provide the input_set_id
976
+ parameter. In that case, the ID of the acceptance test and the batch
977
+ experiment must be the same. If the batch experiment does not exist,
978
+ you can provide the input_set_id parameter and a new batch experiment
979
+ will be created for you.
648
980
 
649
- Returns:
650
- Acceptance test.
981
+ Parameters
982
+ ----------
983
+ candidate_instance_id : str
984
+ ID of the candidate instance.
985
+ baseline_instance_id : str
986
+ ID of the baseline instance.
987
+ id : str
988
+ ID of the acceptance test.
989
+ metrics : list[Union[Metric, dict[str, Any]]]
990
+ List of metrics to use for the acceptance test.
991
+ name : str
992
+ Name of the acceptance test.
993
+ input_set_id : Optional[str], default=None
994
+ ID of the input set to use for the underlying batch experiment,
995
+ in case it hasn't been started.
996
+ description : Optional[str], default=None
997
+ Description of the acceptance test.
998
+
999
+ Returns
1000
+ -------
1001
+ AcceptanceTest
1002
+ The created acceptance test.
651
1003
 
652
- Raises:
653
- requests.HTTPError: If the response status code is not 2xx.
654
- ValueError: If the batch experiment ID does not match the
655
- acceptance test ID.
1004
+ Raises
1005
+ ------
1006
+ requests.HTTPError
1007
+ If the response status code is not 2xx.
1008
+ ValueError
1009
+ If the batch experiment ID does not match the acceptance test ID.
656
1010
  """
657
1011
 
658
1012
  if input_set_id is None:
@@ -714,30 +1068,59 @@ class Application:
714
1068
  polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
715
1069
  ) -> AcceptanceTest:
716
1070
  """
717
- Create a new acceptance test and poll for the result. This is a
718
- convenience method that combines the new_acceptance_test with polling
1071
+ Create a new acceptance test and poll for the result.
1072
+
1073
+ This is a convenience method that combines the new_acceptance_test with polling
719
1074
  logic to check when the acceptance test is done.
720
1075
 
721
- Args:
722
- candidate_instance_id: ID of the candidate instance.
723
- baseline_instance_id: ID of the baseline instance.
724
- id: ID of the acceptance test.
725
- metrics: List of metrics to use for the acceptance test.
726
- name: Name of the acceptance test.
727
- input_set_id: ID of the input set to use for the underlying batch
728
- experiment, in case it hasn't been started.
729
- description: Description of the acceptance test.
730
- polling_options: Options to use when polling for the run result.
731
-
732
- Returns:
733
- Result of the acceptance test.
734
-
735
- Raises:
736
- requests.HTTPError: If the response status code is not 2xx.
737
- TimeoutError: If the acceptance test does not succeed after the
738
- polling strategy is exhausted based on time duration.
739
- RuntimeError: If the acceptance test does not succeed after the
740
- polling strategy is exhausted based on number of tries.
1076
+ Parameters
1077
+ ----------
1078
+ candidate_instance_id : str
1079
+ ID of the candidate instance.
1080
+ baseline_instance_id : str
1081
+ ID of the baseline instance.
1082
+ id : str
1083
+ ID of the acceptance test.
1084
+ metrics : list[Union[Metric, dict[str, Any]]]
1085
+ List of metrics to use for the acceptance test.
1086
+ name : str
1087
+ Name of the acceptance test.
1088
+ input_set_id : Optional[str], default=None
1089
+ ID of the input set to use for the underlying batch experiment,
1090
+ in case it hasn't been started.
1091
+ description : Optional[str], default=None
1092
+ Description of the acceptance test.
1093
+ polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
1094
+ Options to use when polling for the acceptance test result.
1095
+
1096
+ Returns
1097
+ -------
1098
+ AcceptanceTest
1099
+ The completed acceptance test with results.
1100
+
1101
+ Raises
1102
+ ------
1103
+ requests.HTTPError
1104
+ If the response status code is not 2xx.
1105
+ TimeoutError
1106
+ If the acceptance test does not succeed after the
1107
+ polling strategy is exhausted based on time duration.
1108
+ RuntimeError
1109
+ If the acceptance test does not succeed after the
1110
+ polling strategy is exhausted based on number of tries.
1111
+
1112
+ Examples
1113
+ --------
1114
+ >>> test = app.new_acceptance_test_with_result(
1115
+ ... candidate_instance_id="candidate-123",
1116
+ ... baseline_instance_id="baseline-456",
1117
+ ... id="test-789",
1118
+ ... metrics=[Metric(name="objective", type="numeric")],
1119
+ ... name="Performance Test",
1120
+ ... input_set_id="input-set-123"
1121
+ ... )
1122
+ >>> print(test.status)
1123
+ 'completed'
741
1124
  """
742
1125
  _ = self.new_acceptance_test(
743
1126
  candidate_instance_id=candidate_instance_id,
@@ -896,7 +1279,6 @@ class Application:
896
1279
  the input set from a list of inputs that are already available in
897
1280
  the application.
898
1281
 
899
-
900
1282
  Returns
901
1283
  -------
902
1284
  InputSet
@@ -947,20 +1329,49 @@ class Application:
947
1329
  """
948
1330
  Create a new instance and associate it with a version.
949
1331
 
950
- Args:
951
- version_id: ID of the version to associate the instance with.
952
- id: ID of the instance. Will be generated if not provided.
953
- name: Name of the instance. Will be generated if not provided.
954
- description: Description of the instance. Will be generated if not provided.
955
- configuration: Configuration to use for the instance.
956
- exist_ok: If True and an instance with the same ID already exists,
957
- return the existing instance instead of creating a new one.
1332
+ This method creates a new instance associated with a specific version of the application.
1333
+ Instances are configurations of an application version that can be executed.
1334
+
1335
+ Parameters
1336
+ ----------
1337
+ version_id : str
1338
+ ID of the version to associate the instance with.
1339
+ id : str
1340
+ ID of the instance. Will be generated if not provided.
1341
+ name : str
1342
+ Name of the instance. Will be generated if not provided.
1343
+ description : Optional[str], default=None
1344
+ Description of the instance.
1345
+ configuration : Optional[InstanceConfiguration], default=None
1346
+ Configuration to use for the instance. This can include resources,
1347
+ timeouts, and other execution parameters.
1348
+ exist_ok : bool, default=False
1349
+ If True and an instance with the same ID already exists,
1350
+ return the existing instance instead of creating a new one.
1351
+
1352
+ Returns
1353
+ -------
1354
+ Instance
1355
+ The newly created (or existing) instance.
958
1356
 
959
- Returns:
960
- Instance.
1357
+ Raises
1358
+ ------
1359
+ requests.HTTPError
1360
+ If the response status code is not 2xx.
1361
+ ValueError
1362
+ If exist_ok is True and id is None.
961
1363
 
962
- Raises:
963
- requests.HTTPError: If the response status code is not 2xx.
1364
+ Examples
1365
+ --------
1366
+ >>> # Create a new instance for a specific version
1367
+ >>> instance = app.new_instance(
1368
+ ... version_id="version-123",
1369
+ ... id="prod-instance",
1370
+ ... name="Production Instance",
1371
+ ... description="Instance for production use"
1372
+ ... )
1373
+ >>> print(instance.name)
1374
+ 'Production Instance'
964
1375
  """
965
1376
 
966
1377
  if exist_ok and id is None:
@@ -1135,11 +1546,11 @@ class Application:
1135
1546
 
1136
1547
  Raises
1137
1548
  ----------
1138
- requests.HTTPError: If the response status code is not 2xx.
1139
- ValueError:
1140
- If the `input` is of type `nextmv.Input` and the
1141
- `.input_format` is not `JSON`. If the final `options` are not
1142
- of type `dict[str,str]`.
1549
+ requests.HTTPError
1550
+ If the response status code is not 2xx.
1551
+ ValueError
1552
+ If the `input` is of type `nextmv.Input` and the .input_format` is
1553
+ not `JSON`. If the final `options` are not of type `dict[str,str]`.
1143
1554
  """
1144
1555
 
1145
1556
  input_data = None
@@ -1279,7 +1690,7 @@ class Application:
1279
1690
  batch_experiment_id: Optional[str]
1280
1691
  ID of a batch experiment to associate the run with. This is used
1281
1692
  when the run is part of a batch experiment.
1282
- external_result: Optional[Union[ExternalRunResult, dict[str, Any]]]
1693
+ external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None
1283
1694
  External result to use for the run. This can be a
1284
1695
  `cloud.ExternalRunResult` object or a dict. If the object is used,
1285
1696
  then the `.to_dict()` method is applied to extract the
@@ -1294,15 +1705,17 @@ class Application:
1294
1705
 
1295
1706
  Raises
1296
1707
  ----------
1297
- ValueError:
1298
- If the `input` is of type `nextmv.Input` and the
1299
- `.input_format` is not `JSON`.
1300
- If the final `options` are not of type `dict[str,str]`.
1301
- requests.HTTPError: If the response status code is not 2xx.
1302
- TimeoutError: If the run does not succeed after the polling
1303
- strategy is exhausted based on time duration.
1304
- RuntimeError: If the run does not succeed after the polling
1305
- strategy is exhausted based on number of tries.
1708
+ ValueError
1709
+ If the `input` is of type `nextmv.Input` and the `.input_format` is
1710
+ not `JSON`. If the final `options` are not of type `dict[str,str]`.
1711
+ requests.HTTPError
1712
+ If the response status code is not 2xx.
1713
+ TimeoutError
1714
+ If the run does not succeed after the polling strategy is exhausted
1715
+ based on time duration.
1716
+ RuntimeError
1717
+ If the run does not succeed after the polling strategy is exhausted
1718
+ based on number of tries.
1306
1719
  """
1307
1720
 
1308
1721
  run_id = self.new_run(
@@ -1451,23 +1864,53 @@ class Application:
1451
1864
  description: Optional[str] = None,
1452
1865
  ) -> SecretsCollectionSummary:
1453
1866
  """
1454
- Create a new secrets collection. If no secrets are provided, a
1455
- ValueError is raised.
1867
+ Create a new secrets collection.
1868
+
1869
+ This method creates a new secrets collection with the provided secrets.
1870
+ A secrets collection is a group of key-value pairs that can be used by
1871
+ your application instances during execution. If no secrets are provided,
1872
+ a ValueError is raised.
1456
1873
 
1457
- Args:
1458
- secrets: List of secrets to use for the secrets collection. id: ID
1459
- of the secrets collection. Will be generated if not provided.
1460
- name: Name of the secrets collection. Will be generated if not
1461
- provided.
1462
- description: Description of the secrets collection. Will be
1463
- generated if not provided.
1874
+ Parameters
1875
+ ----------
1876
+ secrets : list[Secret]
1877
+ List of secrets to use for the secrets collection. Each secret
1878
+ should be an instance of the Secret class containing a key and value.
1879
+ id : str
1880
+ ID of the secrets collection.
1881
+ name : str
1882
+ Name of the secrets collection.
1883
+ description : Optional[str], default=None
1884
+ Description of the secrets collection.
1464
1885
 
1465
- Returns:
1466
- SecretsCollectionSummary: Summary of the secrets collection.
1886
+ Returns
1887
+ -------
1888
+ SecretsCollectionSummary
1889
+ Summary of the secrets collection including its metadata.
1467
1890
 
1468
- Raises:
1469
- ValueError: If no secrets are provided. requests.HTTPError: If the
1470
- response status code is not 2xx.
1891
+ Raises
1892
+ ------
1893
+ ValueError
1894
+ If no secrets are provided.
1895
+ requests.HTTPError
1896
+ If the response status code is not 2xx.
1897
+
1898
+ Examples
1899
+ --------
1900
+ >>> # Create a new secrets collection with API keys
1901
+ >>> from nextmv.cloud import Secret
1902
+ >>> secrets = [
1903
+ ... Secret(key="API_KEY", value="your-api-key"),
1904
+ ... Secret(key="DATABASE_URL", value="your-database-url")
1905
+ ... ]
1906
+ >>> collection = app.new_secrets_collection(
1907
+ ... secrets=secrets,
1908
+ ... id="api-secrets",
1909
+ ... name="API Secrets",
1910
+ ... description="Collection of API secrets for external services"
1911
+ ... )
1912
+ >>> print(collection.id)
1913
+ 'api-secrets'
1471
1914
  """
1472
1915
 
1473
1916
  if len(secrets) == 0:
@@ -1502,18 +1945,51 @@ class Application:
1502
1945
  """
1503
1946
  Create a new version using the current dev binary.
1504
1947
 
1505
- Args:
1506
- id: ID of the version. Will be generated if not provided.
1507
- name: Name of the version. Will be generated if not provided.
1508
- description: Description of the version. Will be generated if not provided.
1509
- exist_ok: If True and a version with the same ID already exists,
1510
- return the existing version instead of creating a new one.
1948
+ This method creates a new version of the application using the current development
1949
+ binary. Application versions represent different iterations of your application's
1950
+ code and configuration that can be deployed.
1511
1951
 
1512
- Returns:
1513
- Version.
1952
+ Parameters
1953
+ ----------
1954
+ id : Optional[str], default=None
1955
+ ID of the version. If not provided, a unique ID will be generated.
1956
+ name : Optional[str], default=None
1957
+ Name of the version. If not provided, a name will be generated.
1958
+ description : Optional[str], default=None
1959
+ Description of the version. If not provided, a description will be generated.
1960
+ exist_ok : bool, default=False
1961
+ If True and a version with the same ID already exists,
1962
+ return the existing version instead of creating a new one.
1963
+ If True, the 'id' parameter must be provided.
1514
1964
 
1515
- Raises:
1516
- requests.HTTPError: If the response status code is not 2xx.
1965
+ Returns
1966
+ -------
1967
+ Version
1968
+ The newly created (or existing) version.
1969
+
1970
+ Raises
1971
+ ------
1972
+ ValueError
1973
+ If exist_ok is True and id is None.
1974
+ requests.HTTPError
1975
+ If the response status code is not 2xx.
1976
+
1977
+ Examples
1978
+ --------
1979
+ >>> # Create a new version
1980
+ >>> version = app.new_version(
1981
+ ... id="v1.0.0",
1982
+ ... name="Initial Release",
1983
+ ... description="First stable version"
1984
+ ... )
1985
+ >>> print(version.id)
1986
+ 'v1.0.0'
1987
+
1988
+ >>> # Get or create a version with exist_ok
1989
+ >>> version = app.new_version(
1990
+ ... id="v1.0.0",
1991
+ ... exist_ok=True
1992
+ ... )
1517
1993
  """
1518
1994
 
1519
1995
  if exist_ok and id is None:
@@ -1563,86 +2039,96 @@ class Application:
1563
2039
  `nextmv.Model`. The model is encoded, some dependencies and
1564
2040
  accompanying files are packaged, and the app is pushed to Nextmv Cloud.
1565
2041
 
1566
- Examples
1567
- -------
1568
-
1569
- 1. Push an app using an external strategy, i.e., specifying the app's
1570
- directory:
1571
- ```python
1572
- import os
1573
-
1574
- from nextmv import cloud
1575
-
1576
- client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
1577
- app = cloud.Application(client=client, id="<YOUR-APP-ID>")
1578
- app.push() # Use verbose=True for step-by-step output.
1579
- ```
1580
-
1581
- 2. Push an app using an internal strategy, i.e., specifying the model
1582
- and model configuration:
1583
- ```python
1584
- import os
1585
-
1586
- import nextroute
1587
-
1588
- import nextmv
1589
- import nextmv.cloud
1590
-
1591
-
1592
- # Define the model that makes decisions. This model uses the Nextroute
1593
- # library to solve a vehicle routing problem.
1594
- class DecisionModel(nextmv.Model):
1595
- def solve(self, input: nextmv.Input) -> nextmv.Output:
1596
- nextroute_input = nextroute.schema.Input.from_dict(input.data)
1597
- nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())
1598
- nextroute_output = nextroute.solve(nextroute_input, nextroute_options)
1599
-
1600
- return nextmv.Output(
1601
- options=input.options,
1602
- solution=nextroute_output.solutions[0].to_dict(),
1603
- statistics=nextroute_output.statistics.to_dict(),
1604
- )
1605
-
1606
-
1607
- # Define the options that the model needs.
1608
- opt = []
1609
- default_options = nextroute.Options()
1610
- for name, default_value in default_options.to_dict().items():
1611
- opt.append(nextmv.Option(name.lower(), type(default_value), default_value, name, False))
1612
-
1613
- options = nextmv.Options(*opt)
2042
+ Parameters
2043
+ ----------
2044
+ manifest : Optional[Manifest], default=None
2045
+ The manifest for the app. If None, an `app.yaml` file in the provided
2046
+ app directory will be used.
2047
+ app_dir : Optional[str], default=None
2048
+ The path to the app's root directory. If None, the current directory
2049
+ will be used. This is for the external strategy approach.
2050
+ verbose : bool, default=False
2051
+ Whether to print verbose output during the push process.
2052
+ model : Optional[Model], default=None
2053
+ The Python-native model to push. Must be specified together with
2054
+ `model_configuration`. This is for the internal strategy approach.
2055
+ model_configuration : Optional[ModelConfiguration], default=None
2056
+ Configuration for the Python-native model. Must be specified together
2057
+ with `model`.
1614
2058
 
1615
- # Instantiate the model and model configuration.
1616
- model = DecisionModel()
1617
- model_configuration = nextmv.ModelConfiguration(
1618
- name="python_nextroute_model",
1619
- requirements=[
1620
- "nextroute==1.8.1",
1621
- "nextmv==0.14.0.dev1",
1622
- ],
1623
- options=options,
1624
- )
2059
+ Returns
2060
+ -------
2061
+ None
1625
2062
 
1626
- # Define the Nextmv application and push the model to the cloud.
1627
- client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
1628
- app = cloud.Application(client=client, id="<YOUR-APP-ID>")
1629
- manifest = nextmv.cloud.default_python_manifest()
1630
- app.push(
1631
- manifest=manifest,
1632
- verbose=True,
1633
- model=model,
1634
- model_configuration=model_configuration,
1635
- )
1636
- ```
2063
+ Raises
2064
+ ------
2065
+ ValueError
2066
+ If neither app_dir nor model/model_configuration is provided correctly,
2067
+ or if only one of model and model_configuration is provided.
2068
+ TypeError
2069
+ If model is not an instance of nextmv.Model or if model_configuration
2070
+ is not an instance of nextmv.ModelConfiguration.
2071
+ Exception
2072
+ If there's an error in the build, packaging, or cleanup process.
1637
2073
 
1638
- Parameters
1639
- ----------
1640
- manifest : Optional[Manifest], optional
1641
- The manifest for the app, by default None.
1642
- app_dir : Optional[str], optional
1643
- The path to the app's directory, by default None.
1644
- verbose : bool, optional
1645
- Whether to print verbose output, by default False.
2074
+ Examples
2075
+ --------
2076
+ 1. Push an app using an external strategy (directory-based):
2077
+
2078
+ >>> import os
2079
+ >>> from nextmv import cloud
2080
+ >>> client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
2081
+ >>> app = cloud.Application(client=client, id="<YOUR-APP-ID>")
2082
+ >>> app.push() # Use verbose=True for step-by-step output.
2083
+
2084
+ 2. Push an app using an internal strategy (Python-native model):
2085
+
2086
+ >>> import os
2087
+ >>> import nextroute
2088
+ >>> import nextmv
2089
+ >>> import nextmv.cloud
2090
+ >>>
2091
+ >>> # Define the model that makes decisions
2092
+ >>> class DecisionModel(nextmv.Model):
2093
+ ... def solve(self, input: nextmv.Input) -> nextmv.Output:
2094
+ ... nextroute_input = nextroute.schema.Input.from_dict(input.data)
2095
+ ... nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())
2096
+ ... nextroute_output = nextroute.solve(nextroute_input, nextroute_options)
2097
+ ...
2098
+ ... return nextmv.Output(
2099
+ ... options=input.options,
2100
+ ... solution=nextroute_output.solutions[0].to_dict(),
2101
+ ... statistics=nextroute_output.statistics.to_dict(),
2102
+ ... )
2103
+ >>>
2104
+ >>> # Define the options that the model needs
2105
+ >>> opt = []
2106
+ >>> default_options = nextroute.Options()
2107
+ >>> for name, default_value in default_options.to_dict().items():
2108
+ ... opt.append(nextmv.Option(name.lower(), type(default_value), default_value, name, False))
2109
+ >>> options = nextmv.Options(*opt)
2110
+ >>>
2111
+ >>> # Instantiate the model and model configuration
2112
+ >>> model = DecisionModel()
2113
+ >>> model_configuration = nextmv.ModelConfiguration(
2114
+ ... name="python_nextroute_model",
2115
+ ... requirements=[
2116
+ ... "nextroute==1.8.1",
2117
+ ... "nextmv==0.14.0.dev1",
2118
+ ... ],
2119
+ ... options=options,
2120
+ ... )
2121
+ >>>
2122
+ >>> # Push the model to Nextmv Cloud
2123
+ >>> client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
2124
+ >>> app = cloud.Application(client=client, id="<YOUR-APP-ID>")
2125
+ >>> manifest = nextmv.cloud.default_python_manifest()
2126
+ >>> app.push(
2127
+ ... manifest=manifest,
2128
+ ... verbose=True,
2129
+ ... model=model,
2130
+ ... model_configuration=model_configuration,
2131
+ ... )
1646
2132
  """
1647
2133
 
1648
2134
  if verbose:
@@ -1677,14 +2163,31 @@ class Application:
1677
2163
  """
1678
2164
  Get the input of a run.
1679
2165
 
1680
- Args:
1681
- run_id: ID of the run.
2166
+ Retrieves the input data that was used for a specific run. This method
2167
+ handles both small and large inputs automatically - if the input size
2168
+ exceeds the maximum allowed size, it will fetch the input from a
2169
+ download URL.
2170
+
2171
+ Parameters
2172
+ ----------
2173
+ run_id : str
2174
+ ID of the run to retrieve the input for.
2175
+
2176
+ Returns
2177
+ -------
2178
+ dict[str, Any]
2179
+ Input data of the run as a dictionary.
1682
2180
 
1683
- Returns:
1684
- Input of the run.
2181
+ Raises
2182
+ ------
2183
+ requests.HTTPError
2184
+ If the response status code is not 2xx.
1685
2185
 
1686
- Raises:
1687
- requests.HTTPError: If the response status code is not 2xx.
2186
+ Examples
2187
+ --------
2188
+ >>> input_data = app.run_input("run-123")
2189
+ >>> print(input_data)
2190
+ {'locations': [...], 'vehicles': [...]}
1688
2191
  """
1689
2192
  run_information = self.run_metadata(run_id=run_id)
1690
2193
 
@@ -1713,16 +2216,31 @@ class Application:
1713
2216
 
1714
2217
  def run_metadata(self, run_id: str) -> RunInformation:
1715
2218
  """
1716
- Get the metadata of a run. The result does not include the run output.
2219
+ Get the metadata of a run.
1717
2220
 
1718
- Args:
1719
- run_id: ID of the run.
2221
+ Retrieves information about a run without including the run output.
2222
+ This is useful when you only need the run's status and metadata.
1720
2223
 
1721
- Returns:
1722
- Metadata of the run (Run result with no output).
2224
+ Parameters
2225
+ ----------
2226
+ run_id : str
2227
+ ID of the run to retrieve metadata for.
1723
2228
 
1724
- Raises:
1725
- requests.HTTPError: If the response status code is not 2xx.
2229
+ Returns
2230
+ -------
2231
+ RunInformation
2232
+ Metadata of the run (run information without output).
2233
+
2234
+ Raises
2235
+ ------
2236
+ requests.HTTPError
2237
+ If the response status code is not 2xx.
2238
+
2239
+ Examples
2240
+ --------
2241
+ >>> metadata = app.run_metadata("run-123")
2242
+ >>> print(metadata.metadata.status_v2)
2243
+ StatusV2.succeeded
1726
2244
  """
1727
2245
 
1728
2246
  response = self.client.request(
@@ -1739,14 +2257,26 @@ class Application:
1739
2257
  """
1740
2258
  Get the logs of a run.
1741
2259
 
1742
- Args:
1743
- run_id: ID of the run.
2260
+ Parameters
2261
+ ----------
2262
+ run_id : str
2263
+ ID of the run to get logs for.
1744
2264
 
1745
- Returns:
2265
+ Returns
2266
+ -------
2267
+ RunLog
1746
2268
  Logs of the run.
1747
2269
 
1748
- Raises:
1749
- requests.HTTPError: If the response status code is not 2xx.
2270
+ Raises
2271
+ ------
2272
+ requests.HTTPError
2273
+ If the response status code is not 2xx.
2274
+
2275
+ Examples
2276
+ --------
2277
+ >>> logs = app.run_logs("run-123")
2278
+ >>> print(logs.stderr)
2279
+ 'Warning: resource usage exceeded'
1750
2280
  """
1751
2281
  response = self.client.request(
1752
2282
  method="GET",
@@ -1756,16 +2286,30 @@ class Application:
1756
2286
 
1757
2287
  def run_result(self, run_id: str) -> RunResult:
1758
2288
  """
1759
- Get the result of a run. The result includes the run output.
2289
+ Get the result of a run.
1760
2290
 
1761
- Args:
1762
- run_id: ID of the run.
2291
+ Retrieves the complete result of a run, including the run output.
1763
2292
 
1764
- Returns:
1765
- Result of the run.
2293
+ Parameters
2294
+ ----------
2295
+ run_id : str
2296
+ ID of the run to get results for.
2297
+
2298
+ Returns
2299
+ -------
2300
+ RunResult
2301
+ Result of the run, including output.
2302
+
2303
+ Raises
2304
+ ------
2305
+ requests.HTTPError
2306
+ If the response status code is not 2xx.
1766
2307
 
1767
- Raises:
1768
- requests.HTTPError: If the response status code is not 2xx.
2308
+ Examples
2309
+ --------
2310
+ >>> result = app.run_result("run-123")
2311
+ >>> print(result.metadata.status_v2)
2312
+ 'succeeded'
1769
2313
  """
1770
2314
 
1771
2315
  run_information = self.run_metadata(run_id=run_id)
@@ -1778,19 +2322,44 @@ class Application:
1778
2322
  polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
1779
2323
  ) -> RunResult:
1780
2324
  """
1781
- Get the result of a run. The result includes the run output. This
1782
- method polls for the result until the run finishes executing or the
1783
- polling strategy is exhausted.
2325
+ Get the result of a run with polling.
1784
2326
 
1785
- Args:
1786
- run_id: ID of the run.
1787
- polling_options: Options to use when polling for the run result.
2327
+ Retrieves the result of a run including the run output. This method polls
2328
+ for the result until the run finishes executing or the polling strategy
2329
+ is exhausted.
1788
2330
 
1789
- Returns:
1790
- Result of the run.
2331
+ Parameters
2332
+ ----------
2333
+ run_id : str
2334
+ ID of the run to retrieve the result for.
2335
+ polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
2336
+ Options to use when polling for the run result.
2337
+
2338
+ Returns
2339
+ -------
2340
+ RunResult
2341
+ Complete result of the run including output data.
2342
+
2343
+ Raises
2344
+ ------
2345
+ requests.HTTPError
2346
+ If the response status code is not 2xx.
2347
+ TimeoutError
2348
+ If the run does not complete after the polling strategy is
2349
+ exhausted based on time duration.
2350
+ RuntimeError
2351
+ If the run does not complete after the polling strategy is
2352
+ exhausted based on number of tries.
1791
2353
 
1792
- Raises:
1793
- requests.HTTPError: If the response status code is not 2xx.
2354
+ Examples
2355
+ --------
2356
+ >>> from nextmv.cloud import PollingOptions
2357
+ >>> # Create custom polling options
2358
+ >>> polling_opts = PollingOptions(max_tries=50, max_duration=600)
2359
+ >>> # Get run result with polling
2360
+ >>> result = app.run_result_with_polling("run-123", polling_opts)
2361
+ >>> print(result.output)
2362
+ {'solution': {...}}
1794
2363
  """
1795
2364
 
1796
2365
  def polling_func() -> tuple[Any, bool]:
@@ -1810,24 +2379,34 @@ class Application:
1810
2379
 
1811
2380
  def scenario_test(self, scenario_test_id: str) -> BatchExperiment:
1812
2381
  """
1813
- Get the scenario test. Scenario tests are based on batch experiments,
1814
- so this function will return the corresponding batch experiment
1815
- associated to the scenario test.
2382
+ Get a scenario test.
2383
+
2384
+ Retrieves a scenario test by ID. Scenario tests are based on batch experiments,
2385
+ so this function returns the corresponding batch experiment associated with
2386
+ the scenario test.
1816
2387
 
1817
2388
  Parameters
1818
2389
  ----------
1819
2390
  scenario_test_id : str
1820
- ID of the scenario test.
2391
+ ID of the scenario test to retrieve.
1821
2392
 
1822
2393
  Returns
1823
2394
  -------
1824
2395
  BatchExperiment
1825
- The scenario test.
2396
+ The scenario test details as a batch experiment.
1826
2397
 
1827
2398
  Raises
1828
2399
  ------
1829
2400
  requests.HTTPError
1830
2401
  If the response status code is not 2xx.
2402
+
2403
+ Examples
2404
+ --------
2405
+ >>> test = app.scenario_test("scenario-123")
2406
+ >>> print(test.name)
2407
+ 'My Scenario Test'
2408
+ >>> print(test.type)
2409
+ 'scenario'
1831
2410
  """
1832
2411
 
1833
2412
  return self.batch_experiment(batch_id=scenario_test_id)
@@ -1845,7 +2424,7 @@ class Application:
1845
2424
  ----------
1846
2425
  tracked_run : TrackedRun
1847
2426
  The run to track.
1848
- instance_id: Optional[str]
2427
+ instance_id : Optional[str], default=None
1849
2428
  Optional instance ID if you want to associate your tracked run with
1850
2429
  an instance.
1851
2430
 
@@ -1860,7 +2439,16 @@ class Application:
1860
2439
  If the response status code is not 2xx.
1861
2440
  ValueError
1862
2441
  If the tracked run does not have an input or output.
2442
+
2443
+ Examples
2444
+ --------
2445
+ >>> from nextmv.cloud import Application
2446
+ >>> from nextmv.cloud.run import TrackedRun
2447
+ >>> app = Application(id="app_123")
2448
+ >>> tracked_run = TrackedRun(input={"data": [...]}, output={"solution": [...]})
2449
+ >>> run_id = app.track_run(tracked_run)
1863
2450
  """
2451
+
1864
2452
  url_input = self.upload_url()
1865
2453
 
1866
2454
  upload_input = tracked_run.input
@@ -1955,18 +2543,28 @@ class Application:
1955
2543
  """
1956
2544
  Update an instance.
1957
2545
 
1958
- Args:
1959
- id: ID of the instance to update.
1960
- version_id: ID of the version to associate the instance with.
1961
- name: Name of the instance.
1962
- description: Description of the instance.
1963
- configuration: Configuration to use for the instance.
2546
+ Parameters
2547
+ ----------
2548
+ id : str
2549
+ ID of the instance to update.
2550
+ name : str
2551
+ Name of the instance.
2552
+ version_id : Optional[str], default=None
2553
+ ID of the version to associate the instance with.
2554
+ description : Optional[str], default=None
2555
+ Description of the instance.
2556
+ configuration : Optional[InstanceConfiguration], default=None
2557
+ Configuration to use for the instance.
1964
2558
 
1965
- Returns:
1966
- Instance.
2559
+ Returns
2560
+ -------
2561
+ Instance
2562
+ The updated instance.
1967
2563
 
1968
- Raises:
1969
- requests.HTTPError: If the response status code is not 2xx.
2564
+ Raises
2565
+ ------
2566
+ requests.HTTPError
2567
+ If the response status code is not 2xx.
1970
2568
  """
1971
2569
 
1972
2570
  payload = {}
@@ -2075,28 +2673,40 @@ class Application:
2075
2673
  description: str,
2076
2674
  ) -> BatchExperimentInformation:
2077
2675
  """
2078
- Update a scenario test. Scenario tests use the batch experiments API,
2079
- so this method calls the `update_batch_experiment` method, and thus the
2080
- return type is the same.
2676
+ Update a scenario test.
2677
+
2678
+ Updates a scenario test with new name and description. Scenario tests
2679
+ use the batch experiments API, so this method calls the
2680
+ `update_batch_experiment` method, and thus the return type is the same.
2081
2681
 
2082
2682
  Parameters
2083
2683
  ----------
2084
2684
  scenario_test_id : str
2085
2685
  ID of the scenario test to update.
2086
2686
  name : str
2087
- Name of the scenario test.
2687
+ New name for the scenario test.
2088
2688
  description : str
2089
- Description of the scenario test.
2689
+ New description for the scenario test.
2090
2690
 
2091
2691
  Returns
2092
2692
  -------
2093
2693
  BatchExperimentInformation
2094
- The information with the updated scenario test.
2694
+ The information about the updated scenario test.
2095
2695
 
2096
2696
  Raises
2097
2697
  ------
2098
2698
  requests.HTTPError
2099
2699
  If the response status code is not 2xx.
2700
+
2701
+ Examples
2702
+ --------
2703
+ >>> info = app.update_scenario_test(
2704
+ ... scenario_test_id="scenario-123",
2705
+ ... name="Updated Test Name",
2706
+ ... description="Updated description for this test"
2707
+ ... )
2708
+ >>> print(info.name)
2709
+ 'Updated Test Name'
2100
2710
  """
2101
2711
 
2102
2712
  return self.update_batch_experiment(
@@ -2115,18 +2725,50 @@ class Application:
2115
2725
  """
2116
2726
  Update a secrets collection.
2117
2727
 
2118
- Args:
2119
- secrets_collection_id: ID of the secrets collection.
2120
- name: Name of the secrets collection.
2121
- description: Description of the secrets collection.
2122
- secrets: List of secrets to update.
2728
+ This method updates an existing secrets collection with new values for name,
2729
+ description, and secrets. A secrets collection is a group of key-value pairs
2730
+ that can be used by your application instances during execution.
2123
2731
 
2124
- Returns:
2125
- SecretsCollection.
2732
+ Parameters
2733
+ ----------
2734
+ secrets_collection_id : str
2735
+ ID of the secrets collection to update.
2736
+ name : str
2737
+ New name for the secrets collection.
2738
+ description : str
2739
+ New description for the secrets collection.
2740
+ secrets : list[Secret]
2741
+ List of secrets to update. Each secret should be an instance of the
2742
+ Secret class containing a key and value.
2743
+
2744
+ Returns
2745
+ -------
2746
+ SecretsCollectionSummary
2747
+ Summary of the updated secrets collection including its metadata.
2126
2748
 
2127
- Raises:
2128
- ValueError: If no secrets are provided.
2129
- requests.HTTPError: If the response status code is not 2xx.
2749
+ Raises
2750
+ ------
2751
+ ValueError
2752
+ If no secrets are provided.
2753
+ requests.HTTPError
2754
+ If the response status code is not 2xx.
2755
+
2756
+ Examples
2757
+ --------
2758
+ >>> # Update an existing secrets collection
2759
+ >>> from nextmv.cloud import Secret
2760
+ >>> updated_secrets = [
2761
+ ... Secret(key="API_KEY", value="new-api-key"),
2762
+ ... Secret(key="DATABASE_URL", value="new-database-url")
2763
+ ... ]
2764
+ >>> updated_collection = app.update_secrets_collection(
2765
+ ... secrets_collection_id="api-secrets",
2766
+ ... name="Updated API Secrets",
2767
+ ... description="Updated collection of API secrets",
2768
+ ... secrets=updated_secrets
2769
+ ... )
2770
+ >>> print(updated_collection.id)
2771
+ 'api-secrets'
2130
2772
  """
2131
2773
 
2132
2774
  if len(secrets) == 0:
@@ -2151,14 +2793,40 @@ class Application:
2151
2793
  upload_url: UploadURL,
2152
2794
  ) -> None:
2153
2795
  """
2154
- Upload the file located at the given path to the provided upload URL.
2796
+ Upload large input data to the provided upload URL.
2797
+
2798
+ This method allows uploading large input data (either a dictionary or string)
2799
+ to a pre-signed URL. If the input is a dictionary, it will be converted to
2800
+ a JSON string before upload.
2801
+
2802
+ Parameters
2803
+ ----------
2804
+ input : Union[dict[str, Any], str]
2805
+ Input data to upload. Can be either a dictionary that will be
2806
+ converted to JSON, or a pre-formatted JSON string.
2807
+ upload_url : UploadURL
2808
+ Upload URL object containing the pre-signed URL to use for uploading.
2809
+
2810
+ Returns
2811
+ -------
2812
+ None
2813
+ This method doesn't return anything.
2155
2814
 
2156
- Args:
2157
- upload_url: Upload URL to use for uploading the file.
2158
- input: Input to use for the run.
2815
+ Raises
2816
+ ------
2817
+ requests.HTTPError
2818
+ If the response status code is not 2xx.
2159
2819
 
2160
- Raises:
2161
- requests.HTTPError: If the response status code is not 2xx.
2820
+ Examples
2821
+ --------
2822
+ >>> # Upload a dictionary as JSON
2823
+ >>> data = {"locations": [...], "vehicles": [...]}
2824
+ >>> url = app.upload_url()
2825
+ >>> app.upload_large_input(input=data, upload_url=url)
2826
+ >>>
2827
+ >>> # Upload a pre-formatted JSON string
2828
+ >>> json_str = '{"locations": [...], "vehicles": [...]}'
2829
+ >>> app.upload_large_input(input=json_str, upload_url=url)
2162
2830
  """
2163
2831
 
2164
2832
  if isinstance(input, dict):
@@ -2173,11 +2841,27 @@ class Application:
2173
2841
  """
2174
2842
  Get an upload URL to use for uploading a file.
2175
2843
 
2176
- Returns:
2177
- Result of getting an upload URL.
2844
+ This method generates a pre-signed URL that can be used to upload large files
2845
+ to Nextmv Cloud. It's primarily used for uploading large input data, output
2846
+ results, or log files that exceed the size limits for direct API calls.
2847
+
2848
+ Returns
2849
+ -------
2850
+ UploadURL
2851
+ An object containing both the upload URL and an upload ID for reference.
2852
+ The upload URL is a pre-signed URL that allows temporary write access.
2853
+
2854
+ Raises
2855
+ ------
2856
+ requests.HTTPError
2857
+ If the response status code is not 2xx.
2178
2858
 
2179
- Raises:
2180
- requests.HTTPError: If the response status code is not 2xx.
2859
+ Examples
2860
+ --------
2861
+ >>> # Get an upload URL and upload large input data
2862
+ >>> upload_url = app.upload_url()
2863
+ >>> large_input = {"locations": [...], "vehicles": [...]}
2864
+ >>> app.upload_large_input(input=large_input, upload_url=upload_url)
2181
2865
  """
2182
2866
 
2183
2867
  response = self.client.request(
@@ -2191,14 +2875,38 @@ class Application:
2191
2875
  """
2192
2876
  Get a secrets collection.
2193
2877
 
2194
- Args:
2195
- secrets_collection_id: ID of the secrets collection.
2878
+ This method retrieves a secrets collection by its ID. A secrets collection
2879
+ is a group of key-value pairs that can be used by your application
2880
+ instances during execution.
2881
+
2882
+ Parameters
2883
+ ----------
2884
+ secrets_collection_id : str
2885
+ ID of the secrets collection to retrieve.
2196
2886
 
2197
- Returns:
2198
- SecretsCollection.
2887
+ Returns
2888
+ -------
2889
+ SecretsCollection
2890
+ The requested secrets collection, including all secret values
2891
+ and metadata.
2199
2892
 
2200
- Raises:
2201
- requests.HTTPError: If the response status code is not 2xx.
2893
+ Raises
2894
+ ------
2895
+ requests.HTTPError
2896
+ If the response status code is not 2xx.
2897
+
2898
+ Examples
2899
+ --------
2900
+ >>> # Retrieve a secrets collection
2901
+ >>> collection = app.secrets_collection("api-secrets")
2902
+ >>> print(collection.name)
2903
+ 'API Secrets'
2904
+ >>> print(len(collection.secrets))
2905
+ 2
2906
+ >>> for secret in collection.secrets:
2907
+ ... print(secret.location)
2908
+ 'API_KEY'
2909
+ 'DATABASE_URL'
2202
2910
  """
2203
2911
 
2204
2912
  response = self.client.request(
@@ -2212,14 +2920,32 @@ class Application:
2212
2920
  """
2213
2921
  Get a version.
2214
2922
 
2215
- Args:
2216
- version_id: ID of the version.
2923
+ Retrieves a specific version of the application by its ID. Application versions
2924
+ represent different iterations of your application's code and configuration.
2217
2925
 
2218
- Returns:
2219
- Version.
2926
+ Parameters
2927
+ ----------
2928
+ version_id : str
2929
+ ID of the version to retrieve.
2220
2930
 
2221
- Raises:
2222
- requests.HTTPError: If the response status code is not 2xx.
2931
+ Returns
2932
+ -------
2933
+ Version
2934
+ The version object containing details about the requested application version.
2935
+
2936
+ Raises
2937
+ ------
2938
+ requests.HTTPError
2939
+ If the response status code is not 2xx.
2940
+
2941
+ Examples
2942
+ --------
2943
+ >>> # Retrieve a specific version
2944
+ >>> version = app.version("v1.0.0")
2945
+ >>> print(version.id)
2946
+ 'v1.0.0'
2947
+ >>> print(version.name)
2948
+ 'Initial Release'
2223
2949
  """
2224
2950
 
2225
2951
  response = self.client.request(
@@ -2233,11 +2959,34 @@ class Application:
2233
2959
  """
2234
2960
  Check if a version exists.
2235
2961
 
2236
- Args:
2237
- version_id: ID of the version.
2962
+ This method checks if a specific version of the application exists by
2963
+ attempting to retrieve it. It handles HTTP errors for non-existent versions
2964
+ and returns a boolean indicating existence.
2965
+
2966
+ Parameters
2967
+ ----------
2968
+ version_id : str
2969
+ ID of the version to check for existence.
2970
+
2971
+ Returns
2972
+ -------
2973
+ bool
2974
+ True if the version exists, False otherwise.
2238
2975
 
2239
- Returns:
2240
- bool: True if the version exists, False otherwise.
2976
+ Raises
2977
+ ------
2978
+ requests.HTTPError
2979
+ If an HTTP error occurs that is not related to the non-existence
2980
+ of the version.
2981
+
2982
+ Examples
2983
+ --------
2984
+ >>> # Check if a version exists
2985
+ >>> exists = app.version_exists("v1.0.0")
2986
+ >>> if exists:
2987
+ ... print("Version exists!")
2988
+ ... else:
2989
+ ... print("Version does not exist.")
2241
2990
  """
2242
2991
 
2243
2992
  try:
@@ -2254,19 +3003,38 @@ class Application:
2254
3003
  run_information: RunInformation,
2255
3004
  ) -> RunResult:
2256
3005
  """
2257
- Get the result of a run. The result includes the run output. This is a
2258
- private method that is the base for retrieving a run result, regardless
2259
- of polling.
3006
+ Get the result of a run.
2260
3007
 
2261
- Args:
2262
- run_id: ID of the run.
2263
- run_information: Information of the run.
3008
+ This is a private method that retrieves the complete result of a run,
3009
+ including the output data. It handles both small and large outputs,
3010
+ automatically using the appropriate API endpoints based on the output
3011
+ size. This method serves as the base implementation for retrieving
3012
+ run results, regardless of polling strategy.
2264
3013
 
2265
- Returns:
2266
- Result of the run.
3014
+ Parameters
3015
+ ----------
3016
+ run_id : str
3017
+ ID of the run to retrieve the result for.
3018
+ run_information : RunInformation
3019
+ Information about the run, including metadata such as output size.
3020
+
3021
+ Returns
3022
+ -------
3023
+ RunResult
3024
+ Result of the run, including all metadata and output data.
3025
+ For large outputs, the method will fetch the output from
3026
+ a download URL.
3027
+
3028
+ Raises
3029
+ ------
3030
+ requests.HTTPError
3031
+ If the response status code is not 2xx.
2267
3032
 
2268
- Raises:
2269
- requests.HTTPError: If the response status code is not 2xx.
3033
+ Notes
3034
+ -----
3035
+ This method automatically handles large outputs by checking if the
3036
+ output size exceeds _MAX_RUN_SIZE. If it does, the method will request
3037
+ a download URL and fetch the output data separately.
2270
3038
  """
2271
3039
  query_params = None
2272
3040
  large_output = False
@@ -2364,7 +3132,7 @@ class Application:
2364
3132
  # If working with a list of managed inputs, we need to create an
2365
3133
  # input set.
2366
3134
  if scenario.scenario_input.scenario_input_type == ScenarioInputType.INPUT:
2367
- name, id = name_and_id(prefix="inpset", entity_id=scenario_id)
3135
+ name, id = _name_and_id(prefix="inpset", entity_id=scenario_id)
2368
3136
  input_set = self.new_input_set(
2369
3137
  id=id,
2370
3138
  name=name,
@@ -2384,7 +3152,7 @@ class Application:
2384
3152
  for data in scenario.scenario_input.scenario_input_data:
2385
3153
  upload_url = self.upload_url()
2386
3154
  self.upload_large_input(input=data, upload_url=upload_url)
2387
- name, id = name_and_id(prefix="man-input", entity_id=scenario_id)
3155
+ name, id = _name_and_id(prefix="man-input", entity_id=scenario_id)
2388
3156
  managed_input = self.new_managed_input(
2389
3157
  id=id,
2390
3158
  name=name,
@@ -2393,7 +3161,7 @@ class Application:
2393
3161
  )
2394
3162
  managed_inputs.append(managed_input)
2395
3163
 
2396
- name, id = name_and_id(prefix="inpset", entity_id=scenario_id)
3164
+ name, id = _name_and_id(prefix="inpset", entity_id=scenario_id)
2397
3165
  input_set = self.new_input_set(
2398
3166
  id=id,
2399
3167
  name=name,
@@ -2408,28 +3176,71 @@ class Application:
2408
3176
 
2409
3177
  def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any, bool]]) -> Any:
2410
3178
  """
2411
- Auxiliary function for polling.
3179
+ Poll a function until it succeeds or the polling strategy is exhausted.
3180
+
3181
+ You can import the `poll` function directly from `cloud`:
3182
+
3183
+ ```python
3184
+ from nextmv.cloud import poll
3185
+ ```
3186
+
3187
+ This function implements a flexible polling strategy with exponential backoff
3188
+ and jitter. It calls the provided polling function repeatedly until it indicates
3189
+ success, the maximum number of tries is reached, or the maximum duration is exceeded.
2412
3190
 
2413
3191
  The `polling_func` is a callable that must return a `tuple[Any, bool]`
2414
3192
  where the first element is the result of the polling and the second
2415
3193
  element is a boolean indicating if the polling was successful or should be
2416
3194
  retried.
2417
3195
 
2418
- This function will return the result of the `polling_func` if the polling
2419
- process is successful, otherwise it will raise a `TimeoutError` or
2420
- `RuntimeError` depending on the situation.
2421
-
2422
3196
  Parameters
2423
3197
  ----------
2424
3198
  polling_options : PollingOptions
2425
- Options for the polling process.
3199
+ Options for configuring the polling behavior, including retry counts,
3200
+ delays, timeouts, and verbosity settings.
2426
3201
  polling_func : callable
2427
- Function to call to check if the polling was successful.
3202
+ Function to call to check if the polling was successful. Must return a tuple
3203
+ where the first element is the result value and the second is a boolean
3204
+ indicating success (True) or need to retry (False).
2428
3205
 
2429
3206
  Returns
2430
3207
  -------
2431
3208
  Any
2432
- Result of the polling function.
3209
+ Result value from the polling function when successful.
3210
+
3211
+ Raises
3212
+ ------
3213
+ TimeoutError
3214
+ If the polling exceeds the maximum duration specified in polling_options.
3215
+ RuntimeError
3216
+ If the maximum number of tries is exhausted without success.
3217
+
3218
+ Examples
3219
+ --------
3220
+ >>> from nextmv.cloud import PollingOptions, poll
3221
+ >>> import time
3222
+ >>>
3223
+ >>> # Define a polling function that succeeds after 3 tries
3224
+ >>> counter = 0
3225
+ >>> def check_completion() -> tuple[str, bool]:
3226
+ ... global counter
3227
+ ... counter += 1
3228
+ ... if counter >= 3:
3229
+ ... return "Success", True
3230
+ ... return None, False
3231
+ ...
3232
+ >>> # Configure polling options
3233
+ >>> options = PollingOptions(
3234
+ ... max_tries=5,
3235
+ ... delay=0.1,
3236
+ ... backoff=0.2,
3237
+ ... verbose=True
3238
+ ... )
3239
+ >>>
3240
+ >>> # Poll until the function succeeds
3241
+ >>> result = poll(options, check_completion)
3242
+ >>> print(result)
3243
+ 'Success'
2433
3244
  """
2434
3245
 
2435
3246
  # Start by sleeping for the duration specified as initial delay.
@@ -2493,11 +3304,31 @@ def _is_not_exist_error(e: requests.HTTPError) -> bool:
2493
3304
  """
2494
3305
  Check if the error is a known 404 Not Found error.
2495
3306
 
2496
- Args:
2497
- e: HTTPError to check.
3307
+ This is an internal helper function that examines HTTPError objects to determine
3308
+ if they represent a "Not Found" (404) condition, either directly or through a
3309
+ nested exception.
3310
+
3311
+ Parameters
3312
+ ----------
3313
+ e : requests.HTTPError
3314
+ The HTTP error to check.
2498
3315
 
2499
- Returns:
3316
+ Returns
3317
+ -------
3318
+ bool
2500
3319
  True if the error is a 404 Not Found error, False otherwise.
3320
+
3321
+ Examples
3322
+ --------
3323
+ >>> try:
3324
+ ... response = requests.get('https://api.example.com/nonexistent')
3325
+ ... response.raise_for_status()
3326
+ ... except requests.HTTPError as err:
3327
+ ... if _is_not_exist_error(err):
3328
+ ... print("Resource does not exist")
3329
+ ... else:
3330
+ ... print("Another error occurred")
3331
+ Resource does not exist
2501
3332
  """
2502
3333
  if (
2503
3334
  # Check whether the error is caused by a 404 status code - meaning the app does not exist.