|
29 | 29 | import tarfile
|
30 | 30 | import tempfile
|
31 | 31 | import time
|
| 32 | +import uuid |
32 | 33 | from collections.abc import Callable
|
33 | 34 | from dataclasses import dataclass
|
34 | 35 | from datetime import datetime
|
@@ -311,6 +312,156 @@ def __post_init__(self):
|
311 | 312 | self.endpoint = self.endpoint.format(id=self.id)
|
312 | 313 | self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
|
313 | 314 |
|
| 315 | + @classmethod |
| 316 | + def initialize( |
| 317 | + cls, |
| 318 | + name: str, |
| 319 | + id: Optional[str] = None, |
| 320 | + description: Optional[str] = None, |
| 321 | + destination: Optional[str] = None, |
| 322 | + client: Optional[Client] = None, |
| 323 | + ) -> "Application": |
| 324 | + """ |
| 325 | + Initialize a Nextmv application, locally. |
| 326 | +
|
| 327 | + This method will create a new application in the local file system. The |
| 328 | + application is a folder with the name given by `name`, under the |
| 329 | + location given by `destination`. If the `destination` parameter is not |
| 330 | + specified, the current working directory is used as default. This |
| 331 | + method will scaffold the application with the necessary files and |
| 332 | + directories to have an opinionated structure for your decision model. |
| 333 | + Once the application is initialized, you are encouraged to complete it |
| 334 | + with the decision model itself, so that the application can be run, |
| 335 | + locally or remotely. |
| 336 | +
|
| 337 | + This method differs from the `Application.new` method in that it |
| 338 | + creates the application locally rather than in the Cloud. |
| 339 | +
|
| 340 | + Although not required, you are encouraged to specify the `client` |
| 341 | + parameter, so that the application can be pushed and synced remotely, |
| 342 | + with the Nextmv Cloud. If you don't specify the `client`, and intend to |
| 343 | + interact with the Nextmv Cloud, you will encounter an error. Make sure |
| 344 | + you set the `client` parameter on the `Application` instance after |
| 345 | + initialization, if you don't provide it here. |
| 346 | +
|
| 347 | + Use the `destination` parameter to specify where you want the app to be |
| 348 | + initialized, using the current working directory by default. |
| 349 | +
|
| 350 | + Parameters |
| 351 | + ---------- |
| 352 | + name : str |
| 353 | + Name of the application. |
| 354 | + id : str, optional |
| 355 | + ID of the application. Will be generated if not provided. |
| 356 | + description : str, optional |
| 357 | + Description of the application. |
| 358 | + destination : str, optional |
| 359 | + Destination directory where the application will be initialized. If |
| 360 | + not provided, the current working directory will be used. |
| 361 | + client : Client, optional |
| 362 | + Client to use for interacting with the Nextmv Cloud API. |
| 363 | +
|
| 364 | + Returns |
| 365 | + ------- |
| 366 | + Application |
| 367 | + The initialized application instance. |
| 368 | + """ |
| 369 | + |
| 370 | + destination_dir = os.getcwd() if destination is None else destination |
| 371 | + app_id = id if id is not None else str(uuid.uuid4()) |
| 372 | + |
| 373 | + # Create the new directory with the given name. |
| 374 | + app_dir = os.path.join(destination_dir, name) |
| 375 | + os.makedirs(app_dir, exist_ok=True) |
| 376 | + |
| 377 | + # Get the path to the initial app structure template. |
| 378 | + current_file_dir = os.path.dirname(os.path.abspath(__file__)) |
| 379 | + initial_app_structure_path = os.path.join(current_file_dir, "..", "default_app") |
| 380 | + initial_app_structure_path = os.path.normpath(initial_app_structure_path) |
| 381 | + |
| 382 | + # Copy everything from initial_app_structure to the new directory. |
| 383 | + if os.path.exists(initial_app_structure_path): |
| 384 | + for item in os.listdir(initial_app_structure_path): |
| 385 | + source_path = os.path.join(initial_app_structure_path, item) |
| 386 | + dest_path = os.path.join(app_dir, item) |
| 387 | + |
| 388 | + if os.path.isdir(source_path): |
| 389 | + shutil.copytree(source_path, dest_path, dirs_exist_ok=True) |
| 390 | + continue |
| 391 | + |
| 392 | + shutil.copy2(source_path, dest_path) |
| 393 | + |
| 394 | + return cls( |
| 395 | + id=app_id, |
| 396 | + client=client, |
| 397 | + ) |
| 398 | + |
| 399 | + @classmethod |
| 400 | + def new( |
| 401 | + cls, |
| 402 | + client: Client, |
| 403 | + name: str, |
| 404 | + id: Optional[str] = None, |
| 405 | + description: Optional[str] = None, |
| 406 | + is_workflow: Optional[bool] = None, |
| 407 | + exist_ok: bool = False, |
| 408 | + ) -> "Application": |
| 409 | + """ |
| 410 | + Create a new application directly in Nextmv Cloud. |
| 411 | +
|
| 412 | + The application is created as an empty shell, and executable code must |
| 413 | + be pushed to the app before running it remotely. |
| 414 | +
|
| 415 | + Parameters |
| 416 | + ---------- |
| 417 | + client : Client |
| 418 | + Client to use for interacting with the Nextmv Cloud API. |
| 419 | + name : str |
| 420 | + Name of the application. |
| 421 | + id : str, optional |
| 422 | + ID of the application. Will be generated if not provided. |
| 423 | + description : str, optional |
| 424 | + Description of the application. |
| 425 | + is_workflow : bool, optional |
| 426 | + Whether the application is a Decision Workflow. |
| 427 | + exist_ok : bool, default=False |
| 428 | + If True and an application with the same ID already exists, |
| 429 | + return the existing application instead of creating a new one. |
| 430 | +
|
| 431 | + Returns |
| 432 | + ------- |
| 433 | + Application |
| 434 | + The newly created (or existing) application. |
| 435 | +
|
| 436 | + Examples |
| 437 | + -------- |
| 438 | + >>> from nextmv.cloud import Client |
| 439 | + >>> client = Client(api_key="your-api-key") |
| 440 | + >>> app = Application.new(client=client, name="My New App", id="my-app") |
| 441 | + """ |
| 442 | + |
| 443 | + if exist_ok and cls.exists(client=client, id=id): |
| 444 | + return Application(client=client, id=id) |
| 445 | + |
| 446 | + payload = { |
| 447 | + "name": name, |
| 448 | + } |
| 449 | + |
| 450 | + if description is not None: |
| 451 | + payload["description"] = description |
| 452 | + if id is not None: |
| 453 | + payload["id"] = id |
| 454 | + if is_workflow is not None: |
| 455 | + payload["is_pipeline"] = is_workflow |
| 456 | + |
| 457 | + response = client.request( |
| 458 | + method="POST", |
| 459 | + endpoint="v1/applications", |
| 460 | + payload=payload, |
| 461 | + ) |
| 462 | + |
| 463 | + return cls(client=client, id=response.json()["id"]) |
| 464 | + |
314 | 465 | def acceptance_test(self, acceptance_test_id: str) -> AcceptanceTest:
|
315 | 466 | """
|
316 | 467 | Retrieve details of an acceptance test.
|
@@ -902,69 +1053,6 @@ def managed_input(self, managed_input_id: str) -> ManagedInput:
|
902 | 1053 |
|
903 | 1054 | return ManagedInput.from_dict(response.json())
|
904 | 1055 |
|
905 |
| - @classmethod |
906 |
| - def new( |
907 |
| - cls, |
908 |
| - client: Client, |
909 |
| - name: str, |
910 |
| - id: Optional[str] = None, |
911 |
| - description: Optional[str] = None, |
912 |
| - is_workflow: Optional[bool] = None, |
913 |
| - exist_ok: bool = False, |
914 |
| - ) -> "Application": |
915 |
| - """ |
916 |
| - Create a new application. |
917 |
| -
|
918 |
| - Parameters |
919 |
| - ---------- |
920 |
| - client : Client |
921 |
| - Client to use for interacting with the Nextmv Cloud API. |
922 |
| - name : str |
923 |
| - Name of the application. |
924 |
| - id : str, optional |
925 |
| - ID of the application. Will be generated if not provided. |
926 |
| - description : str, optional |
927 |
| - Description of the application. |
928 |
| - is_workflow : bool, optional |
929 |
| - Whether the application is a Decision Workflow. |
930 |
| - exist_ok : bool, default=False |
931 |
| - If True and an application with the same ID already exists, |
932 |
| - return the existing application instead of creating a new one. |
933 |
| -
|
934 |
| - Returns |
935 |
| - ------- |
936 |
| - Application |
937 |
| - The newly created (or existing) application. |
938 |
| -
|
939 |
| - Examples |
940 |
| - -------- |
941 |
| - >>> from nextmv.cloud import Client |
942 |
| - >>> client = Client(api_key="your-api-key") |
943 |
| - >>> app = Application.new(client=client, name="My New App", id="my-app") |
944 |
| - """ |
945 |
| - |
946 |
| - if exist_ok and cls.exists(client=client, id=id): |
947 |
| - return Application(client=client, id=id) |
948 |
| - |
949 |
| - payload = { |
950 |
| - "name": name, |
951 |
| - } |
952 |
| - |
953 |
| - if description is not None: |
954 |
| - payload["description"] = description |
955 |
| - if id is not None: |
956 |
| - payload["id"] = id |
957 |
| - if is_workflow is not None: |
958 |
| - payload["is_pipeline"] = is_workflow |
959 |
| - |
960 |
| - response = client.request( |
961 |
| - method="POST", |
962 |
| - endpoint="v1/applications", |
963 |
| - payload=payload, |
964 |
| - ) |
965 |
| - |
966 |
| - return cls(client=client, id=response.json()["id"]) |
967 |
| - |
968 | 1056 | def new_acceptance_test(
|
969 | 1057 | self,
|
970 | 1058 | candidate_instance_id: str,
|
|
0 commit comments