nextmv 0.39.0.dev1__py3-none-any.whl → 1.0.0__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 (161) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +86 -0
  110. nextmv/cli/community/list.py +200 -0
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +228 -0
  113. nextmv/cli/configuration/create.py +94 -0
  114. nextmv/cli/configuration/delete.py +67 -0
  115. nextmv/cli/configuration/list.py +77 -0
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +161 -3
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +220 -0
  120. nextmv/cli/version.py +22 -2
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cloud/application.py +0 -4204
  158. nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
  159. nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
  160. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  161. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
nextmv/options.py CHANGED
@@ -24,155 +24,6 @@ from dataclasses import dataclass
24
24
  from typing import Any
25
25
 
26
26
  from nextmv.base_model import BaseModel
27
- from nextmv.deprecated import deprecated
28
-
29
-
30
- @dataclass
31
- class Parameter:
32
- """
33
- !!! warning
34
- `Parameter` is deprecated, use `Option` instead.
35
-
36
- Parameter that is used in a `Configuration`. When a parameter is required,
37
- it is a good practice to provide a default value for it. This is because
38
- the configuration will raise an error if a required parameter is not
39
- provided through a command-line argument, an environment variable or a
40
- default value.
41
-
42
- Parameters
43
- ----------
44
- name : str
45
- The name of the parameter.
46
-
47
- param_type : type
48
- The type of the parameter.
49
-
50
- default : Any, optional
51
- The default value of the parameter. Even though this is optional, it is
52
- recommended to provide a default value for all parameters.
53
-
54
- description : str, optional
55
- An optional description of the parameter. This is useful for generating
56
- help messages for the configuration.
57
-
58
- required : bool, optional
59
- Whether the parameter is required. If a parameter is required, it will
60
- be an error to not provide a value for it, either through a command-line
61
- argument, an environment variable or a default value.
62
-
63
- choices : list[Optional[Any]], optional
64
- Limits values to a specific set of choices.
65
-
66
- Examples
67
- --------
68
- >>> from nextmv.options import Parameter
69
- >>> parameter = Parameter("timeout", int, 60, "The maximum timeout in seconds", required=True)
70
- """
71
-
72
- name: str
73
- """The name of the parameter."""
74
- param_type: type
75
- """The type of the parameter."""
76
-
77
- default: Any | None = None
78
- """The default value of the parameter. Even though this is optional, it is
79
- recommended to provide a default value for all parameters."""
80
- description: str | None = None
81
- """An optional description of the parameter. This is useful for generating
82
- help messages for the configuration."""
83
- required: bool = False
84
- """Whether the parameter is required. If a parameter is required, it will
85
- be an error to not provide a value for it, either trough a command-line
86
- argument, an environment variable or a default value."""
87
- choices: list[Any | None] = None
88
- """Limits values to a specific set of choices."""
89
-
90
- def __post_init__(self):
91
- """
92
- Post-initialization hook that marks this class as deprecated.
93
-
94
- This method is automatically called after the object is initialized.
95
- It displays a deprecation warning to inform users to use the `Option` class instead.
96
- """
97
- deprecated(
98
- name="Parameter",
99
- reason="`Parameter` is deprecated, use `Option` instead",
100
- )
101
-
102
- @classmethod
103
- def from_dict(cls, data: dict[str, Any]) -> "Parameter":
104
- """
105
- !!! warning
106
- `Parameter` is deprecated, use `Option` instead.
107
- `Parameter.from_dict` -> `Option.from_dict`
108
-
109
- Creates an instance of `Parameter` from a dictionary.
110
-
111
- Parameters
112
- ----------
113
- data : dict[str, Any]
114
- The dictionary representation of a parameter.
115
-
116
- Returns
117
- -------
118
- Parameter
119
- An instance of `Parameter`.
120
- """
121
-
122
- deprecated(
123
- name="Parameter.from_dict",
124
- reason="`Parameter` is deprecated, use `Option` instead. Parameter.from_dict -> Option.from_dict",
125
- )
126
-
127
- param_type_string = data["param_type"]
128
- param_type = getattr(builtins, param_type_string.split("'")[1])
129
-
130
- return Parameter(
131
- name=data["name"],
132
- param_type=param_type,
133
- default=data.get("default"),
134
- description=data.get("description"),
135
- required=data.get("required", False),
136
- choices=data.get("choices"),
137
- )
138
-
139
- def to_dict(self) -> dict[str, Any]:
140
- """
141
- !!! warning
142
- `Parameter` is deprecated, use `Option` instead.
143
- `Parameter.to_dict` -> `Option.to_dict`
144
-
145
- Converts the parameter to a dict.
146
-
147
- Returns
148
- -------
149
- dict[str, Any]
150
- The parameter as a dict with its name, type, default value,
151
- description, required flag, and choices.
152
-
153
- Examples
154
- --------
155
- >>> param = Parameter("timeout", int, 60, "Maximum time in seconds", True)
156
- >>> param_dict = param.to_dict()
157
- >>> param_dict["name"]
158
- 'timeout'
159
- >>> param_dict["default"]
160
- 60
161
- """
162
-
163
- deprecated(
164
- name="Parameter.to_dict",
165
- reason="`Parameter` is deprecated, use `Option` instead. Parameter.to_dict -> Option.to_dict",
166
- )
167
-
168
- return {
169
- "name": self.name,
170
- "param_type": str(self.param_type),
171
- "default": self.default,
172
- "description": self.description,
173
- "required": self.required,
174
- "choices": self.choices,
175
- }
176
27
 
177
28
 
178
29
  @dataclass
@@ -425,8 +276,7 @@ class Options:
425
276
  If a required option is not provided through a command-line
426
277
  argument, an environment variable or a default value.
427
278
  TypeError
428
- If an option is not either an `Option` or `Parameter` (deprecated)
429
- object.
279
+ If an option is not an `Option`
430
280
  ValueError
431
281
  If an environment variable is not of the type of the corresponding
432
282
  parameter.
@@ -522,27 +372,6 @@ class Options:
522
372
 
523
373
  return cloud_dict
524
374
 
525
- def parameters_dict(self) -> list[dict[str, Any]]:
526
- """
527
- !!! warning
528
- `Parameter` is deprecated, use `Option` instead. `Options.parameters_dict` -> `Options.options_dict`
529
-
530
- Converts the options to a list of dicts. Each dict is the dict
531
- representation of a `Parameter`.
532
-
533
- Returns
534
- -------
535
- list[dict[str, Any]]
536
- The list of dictionaries (parameter entries).
537
- """
538
-
539
- deprecated(
540
- name="Options.parameters_dict",
541
- reason="`Parameter` is deprecated, use `Option` instead. Options.parameters_dict -> Options.options_dict",
542
- )
543
-
544
- return [param.to_dict() for param in self.options]
545
-
546
375
  def options_dict(self) -> list[dict[str, Any]]:
547
376
  """
548
377
  Converts the `Options` to a list of dicts. Each dict is the dict
@@ -606,7 +435,7 @@ class Options:
606
435
  If a required option is not provided through a command-line
607
436
  argument, an environment variable or a default value.
608
437
  TypeError
609
- If an option is not an `Option` or `Parameter` (deprecated) object.
438
+ If an option is not an `Option` object.
610
439
  ValueError
611
440
  If an environment variable is not of the type of the corresponding
612
441
  parameter.
@@ -728,41 +557,6 @@ class Options:
728
557
 
729
558
  return cls(*options)
730
559
 
731
- @classmethod
732
- def from_parameters_dict(cls, parameters_dict: list[dict[str, Any]]) -> "Options":
733
- """
734
- !!! warning
735
-
736
- `Parameter` is deprecated, use `Option` instead.
737
- `Options.from_parameters_dict` -> `Options.from_options_dict`
738
-
739
- Creates an instance of `Options` from parameters in dict form. Each
740
- entry is the dict representation of a `Parameter`.
741
-
742
- Parameters
743
- ----------
744
- parameters_dict : list[dict[str, Any]]
745
- The list of dictionaries (parameter entries).
746
-
747
- Returns
748
- -------
749
- Options
750
- An instance of `Options`.
751
- """
752
-
753
- deprecated(
754
- name="Options.from_parameters_dict",
755
- reason="`Parameter` is deprecated, use `Option` instead. "
756
- "Options.from_parameters_dict -> Options.from_options_dict",
757
- )
758
-
759
- parameters = []
760
- for parameter_dict in parameters_dict:
761
- parameter = Parameter.from_dict(parameter_dict)
762
- parameters.append(parameter)
763
-
764
- return cls(*parameters)
765
-
766
560
  @classmethod
767
561
  def from_options_dict(cls, options_dict: list[dict[str, Any]]) -> "Options":
768
562
  """
@@ -837,7 +631,7 @@ class Options:
837
631
  If a required option is not provided through a command-line
838
632
  argument, an environment variable or a default value.
839
633
  TypeError
840
- If an option is not an `Option` or `Parameter` (deprecated) object.
634
+ If an option is not an `Option` object.
841
635
  ValueError
842
636
  If an environment variable is not of the type of the corresponding
843
637
  parameter.
@@ -858,10 +652,8 @@ class Options:
858
652
  options_by_field_name: dict[str, Option] = {}
859
653
 
860
654
  for ix, option in enumerate(self.options):
861
- if not isinstance(option, Option) and not isinstance(option, Parameter):
862
- raise TypeError(
863
- f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)} in index {ix}"
864
- )
655
+ if not isinstance(option, Option):
656
+ raise TypeError(f"expected an <Option> object, but got {type(option)} in index {ix}")
865
657
 
866
658
  # See comment below about ipykernel adding a `-f` argument. We
867
659
  # restrict options from having the name 'f' or 'fff' for that
@@ -876,7 +668,7 @@ class Options:
876
668
  option.name = option.name.lstrip("-")
877
669
 
878
670
  kwargs = {
879
- "type": self._option_type(option) if self._option_type(option) is not bool else str,
671
+ "type": option.option_type if option.option_type is not bool else str,
880
672
  "help": self._description(option),
881
673
  }
882
674
 
@@ -903,7 +695,7 @@ class Options:
903
695
  help=argparse.SUPPRESS,
904
696
  default="1",
905
697
  )
906
- args = parser.parse_args()
698
+ args, _ = parser.parse_known_args()
907
699
 
908
700
  for arg in vars(args):
909
701
  if arg == "fff" or arg == "f":
@@ -925,12 +717,10 @@ class Options:
925
717
  env_value = os.getenv(upper_name)
926
718
  if env_value is not None:
927
719
  try:
928
- typed_env_value = (
929
- self._option_type(option)(env_value) if self._option_type(option) is not bool else env_value
930
- )
720
+ typed_env_value = option.option_type(env_value) if option.option_type is not bool else env_value
931
721
  except ValueError:
932
722
  raise ValueError(
933
- f'environment variable "{upper_name}" is not of type {self._option_type(option)}'
723
+ f'environment variable "{upper_name}" is not of type {option.option_type}'
934
724
  ) from None
935
725
 
936
726
  value = self._option_value(option, typed_env_value)
@@ -968,8 +758,6 @@ class Options:
968
758
  """
969
759
 
970
760
  description = ""
971
- if isinstance(option, Parameter):
972
- description = "DEPRECATED (initialized with <Parameter>, use <Option> instead) "
973
761
 
974
762
  description += f"[env var: {option.name.upper()}]"
975
763
 
@@ -979,7 +767,7 @@ class Options:
979
767
  if option.default is not None:
980
768
  description += f" (default: {option.default})"
981
769
 
982
- description += f" (type: {self._option_type(option).__name__})"
770
+ description += f" (type: {option.option_type.__name__})"
983
771
 
984
772
  if isinstance(option, Option) and option.additional_attributes is not None:
985
773
  description += f" (additional attributes: {option.additional_attributes})"
@@ -1020,7 +808,7 @@ class Options:
1020
808
  other values are converted to False.
1021
809
  """
1022
810
 
1023
- opt_type = self._option_type(option)
811
+ opt_type = option.option_type
1024
812
  if opt_type is not bool:
1025
813
  return value
1026
814
 
@@ -1031,39 +819,6 @@ class Options:
1031
819
 
1032
820
  return False
1033
821
 
1034
- @staticmethod
1035
- def _option_type(option: Option | Parameter) -> type:
1036
- """
1037
- Get the type of an option.
1038
-
1039
- This auxiliary function was introduced for backwards compatibility with
1040
- the deprecated `Parameter` class. Once `Parameter` is removed, this function
1041
- can be removed as well. When the function is removed, use the
1042
- `option.option_type` attribute directly, instead of calling this function.
1043
-
1044
- Parameters
1045
- ----------
1046
- option : Union[Option, Parameter]
1047
- The option to get the type for.
1048
-
1049
- Returns
1050
- -------
1051
- type
1052
- The type of the option.
1053
-
1054
- Raises
1055
- ------
1056
- TypeError
1057
- If the option is not an `Option` or `Parameter` object.
1058
- """
1059
-
1060
- if isinstance(option, Option):
1061
- return option.option_type
1062
- elif isinstance(option, Parameter):
1063
- return option.param_type
1064
- else:
1065
- raise TypeError(f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)}")
1066
-
1067
822
 
1068
823
  class OptionsEnforcement:
1069
824
  """
nextmv/output.py CHANGED
@@ -66,7 +66,6 @@ from pydantic import AliasChoices, Field
66
66
 
67
67
  from nextmv._serialization import serialize_json
68
68
  from nextmv.base_model import BaseModel
69
- from nextmv.deprecated import deprecated
70
69
  from nextmv.logger import reset_stdout
71
70
  from nextmv.options import Options
72
71
 
@@ -1518,67 +1517,6 @@ class LocalOutputWriter(OutputWriter):
1518
1517
  )
1519
1518
 
1520
1519
 
1521
- def write_local(
1522
- output: Output | dict[str, Any],
1523
- path: str | None = None,
1524
- skip_stdout_reset: bool = False,
1525
- ) -> None:
1526
- """
1527
- !!! warning
1528
- `write_local` is deprecated, use `write` instead.
1529
-
1530
- Write the output to the local filesystem or stdout.
1531
-
1532
- This is a convenience function for instantiating a `LocalOutputWriter` and
1533
- calling its `write` method.
1534
-
1535
- Parameters
1536
- ----------
1537
- output : Union[Output, dict[str, Any]]
1538
- Output data to write. Can be an Output object or a dictionary.
1539
- path : str, optional
1540
- Path to write the output data to. The interpretation depends on the
1541
- output format:
1542
-
1543
- - For `OutputFormat.JSON`: File path for the JSON output. If None or
1544
- empty, writes to stdout.
1545
- - For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
1546
- or empty, writes to a directory named "output" in the current working
1547
- directory.
1548
- skip_stdout_reset : bool, optional
1549
- Skip resetting stdout before writing the output data. Default is False.
1550
-
1551
- Raises
1552
- ------
1553
- ValueError
1554
- If the Output.output_format is not supported.
1555
- TypeError
1556
- If the output is of an unsupported type.
1557
-
1558
- Notes
1559
- -----
1560
- This function detects if stdout was redirected and resets it to avoid
1561
- unexpected behavior. If you want to skip this behavior, set the
1562
- skip_stdout_reset parameter to True.
1563
-
1564
- Examples
1565
- --------
1566
- >>> from nextmv.output import write_local, Output
1567
- >>> # Write JSON to a file
1568
- >>> write_local(Output(solution={"result": 42}), path="result.json")
1569
- >>> # Write JSON to stdout
1570
- >>> write_local({"simple": "data"})
1571
- """
1572
-
1573
- deprecated(
1574
- name="write_local",
1575
- reason="`write_local` is deprecated, use `write` instead",
1576
- )
1577
-
1578
- writer = LocalOutputWriter()
1579
- writer.write(output, path, skip_stdout_reset)
1580
-
1581
-
1582
1520
  _LOCAL_OUTPUT_WRITER = LocalOutputWriter()
1583
1521
  """Default LocalOutputWriter instance used by the write function."""
1584
1522
 
nextmv/polling.py CHANGED
@@ -128,17 +128,49 @@ class PollingOptions:
128
128
  return True to stop the polling and False to continue. The function does
129
129
  not receive any arguments. The function is called before each poll.
130
130
  """
131
+ sleep_duration_func: Callable[[], float] | None = None
132
+ """
133
+ Optional function to calculate the sleep duration between polls. If provided,
134
+ this function will be called to determine how long to sleep instead of using
135
+ the default exponential backoff calculation. The function should return a
136
+ float representing the sleep duration in seconds. The function does not
137
+ receive any arguments and is called before each sleep.
138
+ """
131
139
 
132
140
 
133
141
  DEFAULT_POLLING_OPTIONS: PollingOptions = PollingOptions()
134
142
  """
143
+ !!! warning
144
+ `DEFAULT_POLLING_OPTIONS` is a mutable global variable. Use the `default_polling_options`
145
+ function to obtain a fresh instance of `PollingOptions` with default settings.
146
+
135
147
  Default polling options to use when polling for a run result. This constant
136
148
  provides the default values for `PollingOptions` used across the module.
137
- Using these defaults is recommended for most use cases unless specific timing
138
- needs are required.
139
149
  """
140
150
 
141
151
 
152
+ def default_polling_options() -> PollingOptions:
153
+ """
154
+ Returns a new instance of PollingOptions with default settings.
155
+
156
+ This function can be used to obtain a fresh set of default polling options
157
+ that can be modified as needed without affecting the global defaults.
158
+
159
+ You can import the `default_polling_options` function directly from `nextmv`:
160
+
161
+ ```python
162
+ from nextmv import default_polling_options
163
+ ```
164
+
165
+ Returns
166
+ -------
167
+ PollingOptions
168
+ A new instance of PollingOptions with default values.
169
+ """
170
+
171
+ return PollingOptions()
172
+
173
+
142
174
  def poll( # noqa: C901
143
175
  polling_options: PollingOptions,
144
176
  polling_func: Callable[[], tuple[Any, bool]],
@@ -255,23 +287,29 @@ def poll( # noqa: C901
255
287
  )
256
288
 
257
289
  # Calculate the delay.
258
- if max_reached:
259
- # If we already reached the maximum, we don't want to further calculate the
260
- # delay to avoid overflows.
261
- delay = polling_options.max_delay
262
- delay += random.uniform(0, polling_options.jitter) # Add jitter.
290
+ if polling_options.sleep_duration_func is not None:
291
+ # Use the custom sleep duration function if provided.
292
+ sleep_duration = polling_options.sleep_duration_func()
263
293
  else:
264
- delay = polling_options.delay # Base
265
- delay += polling_options.backoff * (2**ix) # Add exponential backoff.
266
- delay += random.uniform(0, polling_options.jitter) # Add jitter.
294
+ # Calculate delay using exponential backoff with jitter.
295
+ if max_reached:
296
+ # If we already reached the maximum, we don't want to further calculate the
297
+ # delay to avoid overflows.
298
+ delay = polling_options.max_delay
299
+ else:
300
+ delay = polling_options.delay # Base
301
+ delay += polling_options.backoff * (2**ix) # Add exponential backoff.
302
+
303
+ # We cannot exceed the max delay.
304
+ if delay >= polling_options.max_delay:
305
+ max_reached = True
306
+ delay = polling_options.max_delay
307
+
308
+ # Add jitter.
309
+ delay += random.uniform(0, polling_options.jitter)
267
310
 
268
- # We cannot exceed the max delay.
269
- if delay >= polling_options.max_delay:
270
- max_reached = True
271
- delay = polling_options.max_delay
311
+ sleep_duration = delay
272
312
 
273
- # Sleep for the calculated delay.
274
- sleep_duration = delay
275
313
  if polling_options.verbose:
276
314
  log(f"polling | sleeping for duration: {sleep_duration}")
277
315