nextmv 0.31.0__py3-none-any.whl → 0.33.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.
nextmv/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "v0.31.0"
1
+ __version__ = "v0.33.0"
nextmv/__init__.py CHANGED
@@ -3,8 +3,6 @@
3
3
  from .__about__ import __version__
4
4
  from .base_model import BaseModel as BaseModel
5
5
  from .base_model import from_dict as from_dict
6
- from .input import DEFAULT_INPUT_JSON_FILE as DEFAULT_INPUT_JSON_FILE
7
- from .input import INPUTS_KEY as INPUTS_KEY
8
6
  from .input import DataFile as DataFile
9
7
  from .input import Input as Input
10
8
  from .input import InputFormat as InputFormat
@@ -23,6 +21,7 @@ from .manifest import Manifest as Manifest
23
21
  from .manifest import ManifestBuild as ManifestBuild
24
22
  from .manifest import ManifestOption as ManifestOption
25
23
  from .manifest import ManifestPython as ManifestPython
24
+ from .manifest import ManifestPythonArch as ManifestPythonArch
26
25
  from .manifest import ManifestPythonModel as ManifestPythonModel
27
26
  from .manifest import ManifestRuntime as ManifestRuntime
28
27
  from .manifest import ManifestType as ManifestType
@@ -31,13 +30,6 @@ from .model import ModelConfiguration as ModelConfiguration
31
30
  from .options import Option as Option
32
31
  from .options import Options as Options
33
32
  from .options import Parameter as Parameter
34
- from .output import ASSETS_KEY as ASSETS_KEY
35
- from .output import DEFAULT_OUTPUT_JSON_FILE as DEFAULT_OUTPUT_JSON_FILE
36
- from .output import LOGS_FILE as LOGS_FILE
37
- from .output import LOGS_KEY as LOGS_KEY
38
- from .output import OUTPUTS_KEY as OUTPUTS_KEY
39
- from .output import SOLUTIONS_KEY as SOLUTIONS_KEY
40
- from .output import STATISTICS_KEY as STATISTICS_KEY
41
33
  from .output import Asset as Asset
42
34
  from .output import DataPoint as DataPoint
43
35
  from .output import LocalOutputWriter as LocalOutputWriter
@@ -66,13 +58,17 @@ from .run import Format as Format
66
58
  from .run import FormatInput as FormatInput
67
59
  from .run import FormatOutput as FormatOutput
68
60
  from .run import Metadata as Metadata
61
+ from .run import OptionsSummaryItem as OptionsSummaryItem
62
+ from .run import Run as Run
69
63
  from .run import RunConfiguration as RunConfiguration
70
64
  from .run import RunInformation as RunInformation
65
+ from .run import RunInfoStatistics as RunInfoStatistics
71
66
  from .run import RunLog as RunLog
72
67
  from .run import RunQueuing as RunQueuing
73
68
  from .run import RunResult as RunResult
74
69
  from .run import RunType as RunType
75
70
  from .run import RunTypeConfiguration as RunTypeConfiguration
71
+ from .run import StatisticsIndicator as StatisticsIndicator
76
72
  from .run import TrackedRun as TrackedRun
77
73
  from .run import TrackedRunStatus as TrackedRunStatus
78
74
  from .run import run_duration as run_duration
nextmv/cloud/__init__.py CHANGED
@@ -47,6 +47,7 @@ from .acceptance_test import MetricParams as MetricParams
47
47
  from .acceptance_test import MetricResult as MetricResult
48
48
  from .acceptance_test import MetricStatistics as MetricStatistics
49
49
  from .acceptance_test import MetricTolerance as MetricTolerance
50
+ from .acceptance_test import MetricToleranceType as MetricToleranceType
50
51
  from .acceptance_test import MetricType as MetricType
51
52
  from .acceptance_test import ResultStatistics as ResultStatistics
52
53
  from .acceptance_test import StatisticType as StatisticType
@@ -63,6 +64,12 @@ from .batch_experiment import BatchExperimentRun as BatchExperimentRun
63
64
  from .batch_experiment import ExperimentStatus as ExperimentStatus
64
65
  from .client import Client as Client
65
66
  from .client import get_size as get_size
67
+ from .ensemble import EnsembleDefinition as EnsembleDefinition
68
+ from .ensemble import EvaluationRule as EvaluationRule
69
+ from .ensemble import RuleObjective as RuleObjective
70
+ from .ensemble import RuleTolerance as RuleTolerance
71
+ from .ensemble import RuleToleranceType as RuleToleranceType
72
+ from .ensemble import RunGroup as RunGroup
66
73
  from .input_set import InputSet as InputSet
67
74
  from .input_set import ManagedInput as ManagedInput
68
75
  from .instance import Instance as Instance
@@ -47,6 +47,7 @@ from typing import Optional
47
47
 
48
48
  from nextmv.base_model import BaseModel
49
49
  from nextmv.cloud.batch_experiment import ExperimentStatus
50
+ from nextmv.deprecated import deprecated
50
51
 
51
52
 
52
53
  class MetricType(str, Enum):
@@ -214,6 +215,9 @@ class Comparison(str, Enum):
214
215
 
215
216
  class ToleranceType(str, Enum):
216
217
  """
218
+ !!! warning
219
+ `ToleranceType` is deprecated, use `MetricToleranceType` instead.
220
+
217
221
  Type of tolerance used for a metric.
218
222
 
219
223
  You can import the `ToleranceType` class directly from `cloud`:
@@ -242,6 +246,66 @@ class ToleranceType(str, Enum):
242
246
  <ToleranceType.absolute: 'absolute'>
243
247
  """
244
248
 
249
+ undefined = ""
250
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
251
+ Undefined tolerance type."""
252
+ absolute = "absolute"
253
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
254
+ Absolute tolerance type."""
255
+ relative = "relative"
256
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
257
+ Relative tolerance type."""
258
+
259
+
260
+ # Override __getattribute__ to emit deprecation warnings when enum values are accessed
261
+ _original_getattribute = ToleranceType.__class__.__getattribute__
262
+
263
+
264
+ def _deprecated_getattribute(cls, name: str):
265
+ # Only emit deprecation warning if this is specifically the ToleranceType class
266
+ if cls is ToleranceType and name in ("undefined", "absolute", "relative"):
267
+ deprecated(
268
+ f"ToleranceType.{name}",
269
+ "ToleranceType is deprecated and will be removed in a future version. "
270
+ "Please use MetricToleranceType instead",
271
+ )
272
+
273
+ return _original_getattribute(cls, name)
274
+
275
+
276
+ ToleranceType.__class__.__getattribute__ = _deprecated_getattribute
277
+
278
+
279
+ class MetricToleranceType(str, Enum):
280
+ """
281
+ Type of tolerance used for a metric.
282
+
283
+ You can import the `MetricToleranceType` class directly from `cloud`:
284
+
285
+ ```python
286
+ from nextmv.cloud import MetricToleranceType
287
+ ```
288
+
289
+ This enumeration defines the different types of tolerances that can be used
290
+ when comparing metrics in acceptance tests.
291
+
292
+ Attributes
293
+ ----------
294
+ undefined : str
295
+ Undefined tolerance type (empty string).
296
+ absolute : str
297
+ Absolute tolerance type, using a fixed value.
298
+ relative : str
299
+ Relative tolerance type, using a percentage.
300
+
301
+ Examples
302
+ --------
303
+ >>> from nextmv.cloud import MetricToleranceType
304
+ >>> tol_type = MetricToleranceType.absolute
305
+ >>> tol_type
306
+ <MetricToleranceType.absolute: 'absolute'>
307
+ """
308
+
245
309
  undefined = ""
246
310
  """Undefined tolerance type."""
247
311
  absolute = "absolute"
@@ -265,22 +329,22 @@ class MetricTolerance(BaseModel):
265
329
 
266
330
  Attributes
267
331
  ----------
268
- type : ToleranceType
332
+ type : MetricToleranceType
269
333
  Type of tolerance (absolute or relative).
270
334
  value : float
271
335
  Value of the tolerance.
272
336
 
273
337
  Examples
274
338
  --------
275
- >>> from nextmv.cloud import MetricTolerance, ToleranceType
276
- >>> tolerance = MetricTolerance(type=ToleranceType.absolute, value=0.1)
339
+ >>> from nextmv.cloud import MetricTolerance, MetricToleranceType
340
+ >>> tolerance = MetricTolerance(type=MetricToleranceType.absolute, value=0.1)
277
341
  >>> tolerance.type
278
- <ToleranceType.absolute: 'absolute'>
342
+ <MetricToleranceType.absolute: 'absolute'>
279
343
  >>> tolerance.value
280
344
  0.1
281
345
  """
282
346
 
283
- type: ToleranceType
347
+ type: MetricToleranceType
284
348
  """Type of tolerance."""
285
349
  value: float
286
350
  """Value of the tolerance."""
@@ -46,6 +46,7 @@ from nextmv.cloud.batch_experiment import (
46
46
  to_runs,
47
47
  )
48
48
  from nextmv.cloud.client import Client, get_size
49
+ from nextmv.cloud.ensemble import EnsembleDefinition, EvaluationRule, RunGroup
49
50
  from nextmv.cloud.input_set import InputSet, ManagedInput
50
51
  from nextmv.cloud.instance import Instance, InstanceConfiguration
51
52
  from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
@@ -64,6 +65,7 @@ from nextmv.run import (
64
65
  Format,
65
66
  FormatInput,
66
67
  FormatOutput,
68
+ Run,
67
69
  RunConfiguration,
68
70
  RunInformation,
69
71
  RunLog,
@@ -128,6 +130,8 @@ class Application:
128
130
  """Base endpoint for the application."""
129
131
  experiments_endpoint: str = "{base}/experiments"
130
132
  """Base endpoint for the experiments in the application."""
133
+ ensembles_endpoint: str = "{base}/ensembles"
134
+ """Base endpoint for managing the ensemble definitions in the application"""
131
135
 
132
136
  def __post_init__(self):
133
137
  """Initialize the endpoint and experiments_endpoint attributes.
@@ -137,6 +141,7 @@ class Application:
137
141
  """
138
142
  self.endpoint = self.endpoint.format(id=self.id)
139
143
  self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
144
+ self.ensembles_endpoint = self.ensembles_endpoint.format(base=self.endpoint)
140
145
 
141
146
  @classmethod
142
147
  def new(
@@ -289,7 +294,8 @@ class Application:
289
294
 
290
295
  def batch_experiment(self, batch_id: str) -> BatchExperiment:
291
296
  """
292
- Get a batch experiment.
297
+ Get a batch experiment. This method also returns the runs of the batch
298
+ experiment under the `.runs` attribute.
293
299
 
294
300
  Parameters
295
301
  ----------
@@ -318,7 +324,17 @@ class Application:
318
324
  endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
319
325
  )
320
326
 
321
- return BatchExperiment.from_dict(response.json())
327
+ exp = BatchExperiment.from_dict(response.json())
328
+
329
+ runs_response = self.client.request(
330
+ method="GET",
331
+ endpoint=f"{self.experiments_endpoint}/batch/{batch_id}/runs",
332
+ )
333
+
334
+ runs = [Run.from_dict(run) for run in runs_response.json().get("runs", [])]
335
+ exp.runs = runs
336
+
337
+ return exp
322
338
 
323
339
  def batch_experiment_metadata(self, batch_id: str) -> BatchExperimentMetadata:
324
340
  """
@@ -503,6 +519,30 @@ class Application:
503
519
  endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
504
520
  )
505
521
 
522
+ def delete_ensemble_definition(self, ensemble_definition_id: str) -> None:
523
+ """
524
+ Delete an ensemble definition.
525
+
526
+ Parameters
527
+ ----------
528
+ ensemble_definition_id : str
529
+ ID of the ensemble definition to delete.
530
+
531
+ Raises
532
+ ------
533
+ requests.HTTPError
534
+ If the response status code is not 2xx.
535
+
536
+ Examples
537
+ --------
538
+ >>> app.delete_ensemble_definition("development-ensemble-definition")
539
+ """
540
+
541
+ _ = self.client.request(
542
+ method="DELETE",
543
+ endpoint=f"{self.ensembles_endpoint}/{ensemble_definition_id}",
544
+ )
545
+
506
546
  def delete_scenario_test(self, scenario_test_id: str) -> None:
507
547
  """
508
548
  Delete a scenario test.
@@ -551,6 +591,39 @@ class Application:
551
591
  endpoint=f"{self.endpoint}/secrets/{secrets_collection_id}",
552
592
  )
553
593
 
594
+ def ensemble_definition(self, ensemble_definition_id: str) -> EnsembleDefinition:
595
+ """
596
+ Get an ensemble definition.
597
+
598
+ Parameters
599
+ ----------
600
+ ensemble_definition_id : str
601
+ ID of the ensemble definition to retrieve.
602
+
603
+ Returns
604
+ -------
605
+ EnsembleDefintion
606
+ The requested ensemble definition details.
607
+
608
+ Raises
609
+ ------
610
+ requests.HTTPError
611
+ If the response status code is not 2xx.
612
+
613
+ Examples
614
+ --------
615
+ >>> ensemble_definition = app.ensemble_definition("instance-123")
616
+ >>> print(ensemble_definition.name)
617
+ 'Production Ensemble Definition'
618
+ """
619
+
620
+ response = self.client.request(
621
+ method="GET",
622
+ endpoint=f"{self.ensembles_endpoint}/{ensemble_definition_id}",
623
+ )
624
+
625
+ return EnsembleDefinition.from_dict(response.json())
626
+
554
627
  @staticmethod
555
628
  def exists(client: Client, id: str) -> bool:
556
629
  """
@@ -736,6 +809,36 @@ class Application:
736
809
 
737
810
  return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
738
811
 
812
+ def list_ensemble_definitions(self) -> list[EnsembleDefinition]:
813
+ """
814
+ List all ensemble_definitions.
815
+
816
+ Returns
817
+ -------
818
+ list[EnsembleDefinition]
819
+ List of all ensemble definitions associated with this application.
820
+
821
+ Raises
822
+ ------
823
+ requests.HTTPError
824
+ If the response status code is not 2xx.
825
+
826
+ Examples
827
+ --------
828
+ >>> ensemble_definitions = app.list_ensemble_definitions()
829
+ >>> for ensemble_definition in ensemble_definitions:
830
+ ... print(ensemble_definition.name)
831
+ 'Development Ensemble Definition'
832
+ 'Production Ensemble Definition'
833
+ """
834
+
835
+ response = self.client.request(
836
+ method="GET",
837
+ endpoint=f"{self.ensembles_endpoint}",
838
+ )
839
+
840
+ return [EnsembleDefinition.from_dict(ensemble_definition) for ensemble_definition in response.json()["items"]]
841
+
739
842
  def list_input_sets(self) -> list[InputSet]:
740
843
  """
741
844
  List all input sets.
@@ -818,6 +921,28 @@ class Application:
818
921
 
819
922
  return [ManagedInput.from_dict(managed_input) for managed_input in response.json()]
820
923
 
924
+ def list_runs(self) -> list[Run]:
925
+ """
926
+ List all runs.
927
+
928
+ Returns
929
+ -------
930
+ list[Run]
931
+ List of runs.
932
+
933
+ Raises
934
+ ------
935
+ requests.HTTPError
936
+ If the response status code is not 2xx.
937
+ """
938
+
939
+ response = self.client.request(
940
+ method="GET",
941
+ endpoint=f"{self.endpoint}/runs",
942
+ )
943
+
944
+ return [Run.from_dict(run) for run in response.json().get("runs", [])]
945
+
821
946
  def list_scenario_tests(self) -> list[BatchExperimentMetadata]:
822
947
  """
823
948
  List all batch scenario tests. Scenario tests are based on the batch
@@ -1280,6 +1405,53 @@ class Application:
1280
1405
 
1281
1406
  return self.batch_experiment_with_polling(batch_id=batch_id, polling_options=polling_options)
1282
1407
 
1408
+ def new_ensemble_defintion(
1409
+ self,
1410
+ id: str,
1411
+ run_groups: list[RunGroup],
1412
+ rules: list[EvaluationRule],
1413
+ name: Optional[str] = None,
1414
+ description: Optional[str] = None,
1415
+ ) -> EnsembleDefinition:
1416
+ """
1417
+ Create a new ensemble definition.
1418
+
1419
+ Parameters
1420
+ ----------
1421
+ id: str
1422
+ ID of the ensemble defintion.
1423
+ run_groups: list[RunGroup]
1424
+ Information to facilitate the execution of child runs.
1425
+ rules: list[EvaluationRule]
1426
+ Information to facilitate the selection of
1427
+ a result for the ensemble run from child runs.
1428
+ name: Optional[str]
1429
+ Name of the ensemble definition.
1430
+ description: Optional[str]
1431
+ Description of the ensemble definition.
1432
+ """
1433
+
1434
+ if name is None:
1435
+ name = id
1436
+ if description is None:
1437
+ description = name
1438
+
1439
+ payload = {
1440
+ "id": id,
1441
+ "run_groups": [run_group.to_dict() for run_group in run_groups],
1442
+ "rules": [rule.to_dict() for rule in rules],
1443
+ "name": name,
1444
+ "description": description,
1445
+ }
1446
+
1447
+ response = self.client.request(
1448
+ method="POST",
1449
+ endpoint=f"{self.ensembles_endpoint}",
1450
+ payload=payload,
1451
+ )
1452
+
1453
+ return EnsembleDefinition.from_dict(response.json())
1454
+
1283
1455
  def new_input_set(
1284
1456
  self,
1285
1457
  id: str,
@@ -2202,13 +2374,14 @@ class Application:
2202
2374
 
2203
2375
  if id is None:
2204
2376
  id = safe_id(prefix="version")
2377
+ if name is None:
2378
+ name = id
2205
2379
 
2206
2380
  payload = {
2207
2381
  "id": id,
2382
+ "name": name,
2208
2383
  }
2209
2384
 
2210
- if name is not None:
2211
- payload["name"] = name
2212
2385
  if description is not None:
2213
2386
  payload["description"] = description
2214
2387
 
@@ -2759,7 +2932,7 @@ class Application:
2759
2932
  Examples
2760
2933
  --------
2761
2934
  >>> from nextmv.cloud import Application
2762
- >>> from nextmv.cloud.run import TrackedRun
2935
+ >>> from nextmv import TrackedRun
2763
2936
  >>> app = Application(id="app_123")
2764
2937
  >>> tracked_run = TrackedRun(input={"data": [...]}, output={"solution": [...]})
2765
2938
  >>> run_id = app.track_run(tracked_run)
@@ -2900,6 +3073,98 @@ class Application:
2900
3073
  output_dir_path=output_dir_path,
2901
3074
  )
2902
3075
 
3076
+ def update_batch_experiment(
3077
+ self,
3078
+ batch_experiment_id: str,
3079
+ name: Optional[str] = None,
3080
+ description: Optional[str] = None,
3081
+ ) -> BatchExperimentInformation:
3082
+ """
3083
+ Update a batch experiment.
3084
+
3085
+ Parameters
3086
+ ----------
3087
+ batch_experiment_id : str
3088
+ ID of the batch experiment to update.
3089
+ name : Optional[str], default=None
3090
+ Optional name of the batch experiment.
3091
+ description : Optional[str], default=None
3092
+ Optional description of the batch experiment.
3093
+
3094
+ Returns
3095
+ -------
3096
+ BatchExperimentInformation
3097
+ The information with the updated batch experiment.
3098
+
3099
+ Raises
3100
+ ------
3101
+ requests.HTTPError
3102
+ If the response status code is not 2xx.
3103
+ """
3104
+
3105
+ payload = {}
3106
+
3107
+ if name is not None:
3108
+ payload["name"] = name
3109
+ if description is not None:
3110
+ payload["description"] = description
3111
+
3112
+ response = self.client.request(
3113
+ method="PATCH",
3114
+ endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
3115
+ payload=payload,
3116
+ )
3117
+
3118
+ return BatchExperimentInformation.from_dict(response.json())
3119
+
3120
+ def update_ensemble_definition(
3121
+ self,
3122
+ id: str,
3123
+ name: Optional[str] = None,
3124
+ description: Optional[str] = None,
3125
+ ) -> EnsembleDefinition:
3126
+ """
3127
+ Update an ensemble definition.
3128
+
3129
+ Parameters
3130
+ ----------
3131
+ id : str
3132
+ ID of the ensemble definition to update.
3133
+ name : Optional[str], default=None
3134
+ Optional name of the ensemble definition.
3135
+ description : Optional[str], default=None
3136
+ Optional description of the ensemble definition.
3137
+
3138
+ Returns
3139
+ -------
3140
+ EnsembleDefinition
3141
+ The updated ensemble definition.
3142
+
3143
+ Raises
3144
+ ------
3145
+ ValueError
3146
+ If neither name nor description is updated
3147
+ requests.HTTPError
3148
+ If the response status code is not 2xx.
3149
+ """
3150
+
3151
+ payload = {}
3152
+
3153
+ if name is None and description is None:
3154
+ raise ValueError("Must define at least one value among name and description to modify")
3155
+ if name is not None:
3156
+ payload["name"] = name
3157
+ if description is not None:
3158
+ payload["description"] = description
3159
+
3160
+ response = self.client.request(
3161
+ method="PATCH",
3162
+ endpoint=f"{self.ensembles_endpoint}/{id}",
3163
+ payload=payload,
3164
+ )
3165
+
3166
+ return EnsembleDefinition.from_dict(response.json())
3167
+
2903
3168
  def update_instance(
2904
3169
  self,
2905
3170
  id: str,
@@ -2963,50 +3228,6 @@ class Application:
2963
3228
 
2964
3229
  return Instance.from_dict(response.json())
2965
3230
 
2966
- def update_batch_experiment(
2967
- self,
2968
- batch_experiment_id: str,
2969
- name: Optional[str] = None,
2970
- description: Optional[str] = None,
2971
- ) -> BatchExperimentInformation:
2972
- """
2973
- Update a batch experiment.
2974
-
2975
- Parameters
2976
- ----------
2977
- batch_experiment_id : str
2978
- ID of the batch experiment to update.
2979
- name : Optional[str], default=None
2980
- Optional name of the batch experiment.
2981
- description : Optional[str], default=None
2982
- Optional description of the batch experiment.
2983
-
2984
- Returns
2985
- -------
2986
- BatchExperimentInformation
2987
- The information with the updated batch experiment.
2988
-
2989
- Raises
2990
- ------
2991
- requests.HTTPError
2992
- If the response status code is not 2xx.
2993
- """
2994
-
2995
- payload = {}
2996
-
2997
- if name is not None:
2998
- payload["name"] = name
2999
- if description is not None:
3000
- payload["description"] = description
3001
-
3002
- response = self.client.request(
3003
- method="PATCH",
3004
- endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
3005
- payload=payload,
3006
- )
3007
-
3008
- return BatchExperimentInformation.from_dict(response.json())
3009
-
3010
3231
  def update_managed_input(
3011
3232
  self,
3012
3233
  managed_input_id: str,
@@ -3647,65 +3868,40 @@ class Application:
3647
3868
  """
3648
3869
  Auxiliary function to validate the directory path and configuration.
3649
3870
  """
3871
+ input_type = self.__get_input_type(configuration)
3650
3872
 
3651
- if input_dir_path is None or input_dir_path == "":
3873
+ # If no explicit input type is defined, there is nothing to validate.
3874
+ if input_type is None:
3652
3875
  return
3653
3876
 
3654
- if configuration is None:
3655
- raise ValueError(
3656
- "If dir_path is provided, a RunConfiguration must also be provided.",
3657
- )
3658
-
3659
- config_format = self.__extract_config_format(configuration)
3660
-
3661
- if config_format is None:
3877
+ # Validate that the input directory path is provided when explicitly required.
3878
+ dir_types = (InputFormat.MULTI_FILE, InputFormat.CSV_ARCHIVE)
3879
+ if input_type in dir_types and not input_dir_path:
3662
3880
  raise ValueError(
3663
- "If dir_path is provided, RunConfiguration.format must also be provided.",
3881
+ f"If RunConfiguration.format.format_input.input_type is set to {input_type}, "
3882
+ "then input_dir_path must be provided.",
3664
3883
  )
3665
3884
 
3666
- input_type = self.__extract_input_type(config_format)
3667
-
3668
- if input_type is None or input_type in (InputFormat.JSON, InputFormat.TEXT):
3669
- raise ValueError(
3670
- "If dir_path is provided, RunConfiguration.format.format_input.input_type must be set to a valid type. "
3671
- f"Valid types are: {[InputFormat.CSV_ARCHIVE, InputFormat.MULTI_FILE]}",
3672
- )
3673
-
3674
- def __extract_config_format(self, configuration: Union[RunConfiguration, dict[str, Any]]) -> Any:
3675
- """Extract format from configuration, handling both RunConfiguration objects and dicts."""
3676
- if isinstance(configuration, RunConfiguration):
3677
- return configuration.format
3678
-
3679
- if isinstance(configuration, dict):
3680
- config_format = configuration.get("format")
3681
- if config_format is not None and isinstance(config_format, dict):
3682
- return Format.from_dict(config_format) if hasattr(Format, "from_dict") else config_format
3683
-
3684
- return config_format
3685
-
3686
- raise ValueError("Configuration must be a RunConfiguration object or a dict.")
3687
-
3688
- def __extract_input_type(self, config_format: Any) -> Any:
3689
- """Extract input type from config format."""
3690
- if isinstance(config_format, dict):
3691
- format_input = config_format.get("format_input") or config_format.get("input")
3692
- if format_input is None:
3693
- raise ValueError(
3694
- "If dir_path is provided, RunConfiguration.format.format_input must also be provided.",
3695
- )
3885
+ def __get_input_type(self, config: Union[RunConfiguration, dict[str, Any]]) -> Optional[InputFormat]:
3886
+ """
3887
+ Auxiliary function to extract the input type from the run configuration.
3888
+ """
3696
3889
 
3697
- if isinstance(format_input, dict):
3698
- return format_input.get("input_type") or format_input.get("type")
3890
+ if config is None:
3891
+ return None
3699
3892
 
3700
- return getattr(format_input, "input_type", None)
3893
+ if isinstance(config, dict):
3894
+ config = RunConfiguration.from_dict(config)
3701
3895
 
3702
- # Handle Format object
3703
- if config_format.format_input is None:
3704
- raise ValueError(
3705
- "If dir_path is provided, RunConfiguration.format.format_input must also be provided.",
3706
- )
3896
+ if (
3897
+ isinstance(config, RunConfiguration)
3898
+ and config.format is not None
3899
+ and config.format.format_input is not None
3900
+ and config.format.format_input.input_type is not None
3901
+ ):
3902
+ return config.format.format_input.input_type
3707
3903
 
3708
- return config_format.format_input.input_type
3904
+ return None
3709
3905
 
3710
3906
  def __package_inputs(self, dir_path: str) -> str:
3711
3907
  """