idmtools-platform-container 0.0.0.dev0__py3-none-any.whl → 0.0.2__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.
- docker_image/BASE_VERSION +1 -0
- docker_image/Dockerfile +48 -0
- docker_image/Dockerfile_buildenv +46 -0
- docker_image/ImageName +1 -0
- docker_image/README.md +78 -0
- docker_image/__init__.py +6 -0
- docker_image/build_docker_image.py +145 -0
- docker_image/debian/BASE_VERSION +1 -0
- docker_image/debian/Dockerfile +40 -0
- docker_image/debian/ImageName +1 -0
- docker_image/debian/README.md +48 -0
- docker_image/debian/pip.conf +3 -0
- docker_image/debian/requirements.txt +1 -0
- docker_image/docker_image_history.py +101 -0
- docker_image/pip.conf +3 -0
- docker_image/push_docker_image.py +62 -0
- docker_image/requirements.txt +1 -0
- docker_image/rocky_meta_runtime.txt +37 -0
- idmtools_platform_container/__init__.py +18 -8
- idmtools_platform_container/cli/__init__.py +5 -0
- idmtools_platform_container/cli/container.py +682 -0
- idmtools_platform_container/container_operations/__init__.py +5 -0
- idmtools_platform_container/container_operations/docker_operations.py +593 -0
- idmtools_platform_container/container_platform.py +375 -0
- idmtools_platform_container/platform_operations/__init__.py +5 -0
- idmtools_platform_container/platform_operations/experiment_operations.py +112 -0
- idmtools_platform_container/platform_operations/simulation_operations.py +58 -0
- idmtools_platform_container/plugin_info.py +79 -0
- idmtools_platform_container/utils/__init__.py +5 -0
- idmtools_platform_container/utils/general.py +136 -0
- idmtools_platform_container/utils/status.py +130 -0
- idmtools_platform_container-0.0.2.dist-info/METADATA +212 -0
- idmtools_platform_container-0.0.2.dist-info/RECORD +69 -0
- idmtools_platform_container-0.0.2.dist-info/entry_points.txt +5 -0
- idmtools_platform_container-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- {idmtools_platform_container-0.0.0.dev0.dist-info → idmtools_platform_container-0.0.2.dist-info}/top_level.txt +2 -0
- tests/inputs/Assets/MyLib/functions.py +2 -0
- tests/inputs/__init__.py +0 -0
- tests/inputs/model.py +28 -0
- tests/inputs/model1.py +31 -0
- tests/inputs/model3.py +21 -0
- tests/inputs/model_file.py +18 -0
- tests/inputs/run.sh +1 -0
- tests/inputs/sleep.py +9 -0
- tests/test_container_cli/__init__.py +0 -0
- tests/test_container_cli/helper.py +57 -0
- tests/test_container_cli/test_base.py +14 -0
- tests/test_container_cli/test_cancel.py +96 -0
- tests/test_container_cli/test_clear_results.py +54 -0
- tests/test_container_cli/test_container.py +72 -0
- tests/test_container_cli/test_file_container_cli.py +121 -0
- tests/test_container_cli/test_get_detail.py +60 -0
- tests/test_container_cli/test_history.py +136 -0
- tests/test_container_cli/test_history_count.py +53 -0
- tests/test_container_cli/test_inspect.py +53 -0
- tests/test_container_cli/test_install.py +48 -0
- tests/test_container_cli/test_is_running.py +69 -0
- tests/test_container_cli/test_jobs.py +138 -0
- tests/test_container_cli/test_list_containers.py +99 -0
- tests/test_container_cli/test_packages.py +41 -0
- tests/test_container_cli/test_path.py +96 -0
- tests/test_container_cli/test_ps.py +47 -0
- tests/test_container_cli/test_remove_container.py +78 -0
- tests/test_container_cli/test_status.py +149 -0
- tests/test_container_cli/test_stop_container.py +71 -0
- tests/test_container_cli/test_sync_history.py +98 -0
- tests/test_container_cli/test_verify_docker.py +28 -0
- tests/test_container_cli/test_volume.py +28 -0
- idmtools_platform_container-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_container-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_platform_container-0.0.0.dev0.dist-info → idmtools_platform_container-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the ContainerPlatform utils.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import uuid
|
|
8
|
+
import math
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Union
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
import platform as platform
|
|
13
|
+
from logging import getLogger
|
|
14
|
+
|
|
15
|
+
logger = getLogger(__name__)
|
|
16
|
+
user_logger = getLogger('user')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#############################
|
|
20
|
+
# Utility Functions
|
|
21
|
+
#############################
|
|
22
|
+
|
|
23
|
+
def normalize_path(path: Union[str, Path]):
|
|
24
|
+
"""
|
|
25
|
+
Normalize the binding path to handle case insensitivity and path separators for Windows.
|
|
26
|
+
Args:
|
|
27
|
+
path (str): The path to normalize.
|
|
28
|
+
Returns:
|
|
29
|
+
str: The normalized path.
|
|
30
|
+
"""
|
|
31
|
+
path = str(path)
|
|
32
|
+
if platform.system() == 'Windows':
|
|
33
|
+
path = path.lower()
|
|
34
|
+
path = os.path.normpath(path).replace('\\', '/')
|
|
35
|
+
return path.rstrip('/')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def map_container_path(source_binding, destination_binding, source_path) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Map a source path to its corresponding destination path within the container.
|
|
41
|
+
Args:
|
|
42
|
+
source_binding (str): The source directory in the binding (e.g., /abc/my_path).
|
|
43
|
+
destination_binding (str): The destination directory in the container (e.g., /home/my_path).
|
|
44
|
+
source_path (str): The source file or folder path to map (e.g., /abc/my_path/file_or_folder_path).
|
|
45
|
+
Returns:
|
|
46
|
+
str: The corresponding destination path within the container (e.g., /home/my_path/file_or_folder_path).
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: If the source path does not start with the source binding path.
|
|
50
|
+
"""
|
|
51
|
+
source_path = str(source_path)
|
|
52
|
+
# Normalize binding paths
|
|
53
|
+
normalized_source_binding = normalize_path(source_binding)
|
|
54
|
+
normalized_source_path = normalize_path(source_path)
|
|
55
|
+
|
|
56
|
+
# Ensure the source path starts with the source binding
|
|
57
|
+
if not normalized_source_path.startswith(normalized_source_binding):
|
|
58
|
+
raise ValueError("Source path does not start with the source binding path")
|
|
59
|
+
|
|
60
|
+
# Compute the relative path from the source binding to the source path
|
|
61
|
+
relative_path = os.path.relpath(source_path, source_binding)
|
|
62
|
+
|
|
63
|
+
# Combine the destination binding with the relative path
|
|
64
|
+
destination_path = os.path.join(destination_binding, relative_path)
|
|
65
|
+
|
|
66
|
+
# Normalize the destination path to ensure proper path separators and convert to Unix-style path
|
|
67
|
+
destination_path = normalize_path(destination_path)
|
|
68
|
+
|
|
69
|
+
return destination_path
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Function to convert ISO 8601 format string to a datetime object
|
|
73
|
+
def parse_iso8601(date_str):
|
|
74
|
+
"""
|
|
75
|
+
Convert an ISO 8601 format string to a datetime object.
|
|
76
|
+
Args:
|
|
77
|
+
date_str: time string in ISO 8601 format
|
|
78
|
+
Returns:
|
|
79
|
+
datetime object
|
|
80
|
+
"""
|
|
81
|
+
# Truncate the fractional seconds to a maximum of 6 digits
|
|
82
|
+
if '.' in date_str:
|
|
83
|
+
date_str = date_str[:date_str.index('.') + 7] + 'Z'
|
|
84
|
+
return datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def format_timestamp(timestamp) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Format the timestamp to a human-readable format.
|
|
90
|
+
Args:
|
|
91
|
+
timestamp: timestamp in ISO 8601 format
|
|
92
|
+
Returns:
|
|
93
|
+
Human-readable timestamp
|
|
94
|
+
"""
|
|
95
|
+
# Remove the 'Z' at the end and truncate the fractional seconds to 6 digits
|
|
96
|
+
if timestamp.endswith('Z'):
|
|
97
|
+
timestamp = timestamp[:-1]
|
|
98
|
+
if '.' in timestamp:
|
|
99
|
+
timestamp = timestamp[:timestamp.index('.') + 7]
|
|
100
|
+
dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f")
|
|
101
|
+
formatted_timestamp = dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
102
|
+
return formatted_timestamp
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def is_valid_uuid(uuid_to_test, version=4) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Check if the provided string is a valid UUID.
|
|
108
|
+
Args:
|
|
109
|
+
uuid_to_test: UUID string to test
|
|
110
|
+
version: test against a specific UUID version
|
|
111
|
+
Returns:
|
|
112
|
+
True/False
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
# check for validity of Uuid
|
|
116
|
+
_ = uuid.UUID(uuid_to_test, version=version)
|
|
117
|
+
return True
|
|
118
|
+
except ValueError:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def convert_byte_size(size_bytes: int) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Convert byte size to human-readable.
|
|
125
|
+
Args:
|
|
126
|
+
size_bytes: byte site in integer
|
|
127
|
+
Returns:
|
|
128
|
+
str: human-readable size
|
|
129
|
+
"""
|
|
130
|
+
if size_bytes == 0:
|
|
131
|
+
return "0B"
|
|
132
|
+
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
133
|
+
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
134
|
+
p = math.pow(1024, i)
|
|
135
|
+
s = round(size_bytes / p, 2)
|
|
136
|
+
return "%s %s" % (s, size_name[i])
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the ContainerPlatform status utils.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from typing import List, NoReturn
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from idmtools_platform_container.utils.general import normalize_path
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
user_logger = getLogger('user')
|
|
14
|
+
|
|
15
|
+
#############################
|
|
16
|
+
# Check Status
|
|
17
|
+
#############################
|
|
18
|
+
|
|
19
|
+
status_mapping = {
|
|
20
|
+
'0': 'SUCCEEDED',
|
|
21
|
+
'100': 'RUNNING',
|
|
22
|
+
'-1': 'FAILED'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
FILE_NAME = 'job_status.txt'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_simulation_status(sim_path: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Get the status of a simulation.
|
|
31
|
+
Args:
|
|
32
|
+
sim_path: Simulation Directory Path
|
|
33
|
+
Returns:
|
|
34
|
+
simulation status
|
|
35
|
+
"""
|
|
36
|
+
status_file_path = os.path.join(sim_path, FILE_NAME)
|
|
37
|
+
|
|
38
|
+
if os.path.isfile(status_file_path):
|
|
39
|
+
with open(status_file_path, 'r') as file:
|
|
40
|
+
content = file.read().strip()
|
|
41
|
+
|
|
42
|
+
status = status_mapping.get(content, 'Pending')
|
|
43
|
+
return status
|
|
44
|
+
else:
|
|
45
|
+
return 'Pending'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def append_with_limit(lst: List, item: str, limit: int) -> List:
|
|
49
|
+
"""
|
|
50
|
+
Append an item to a list with a limit.
|
|
51
|
+
Args:
|
|
52
|
+
lst: list of items
|
|
53
|
+
item: item to be added
|
|
54
|
+
limit: max number of items in the list
|
|
55
|
+
Returns:
|
|
56
|
+
list: updated list
|
|
57
|
+
"""
|
|
58
|
+
if len(lst) < limit:
|
|
59
|
+
lst.append(item)
|
|
60
|
+
elif len(lst) == limit:
|
|
61
|
+
lst.append('...')
|
|
62
|
+
return lst
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def summarize_status_files(exp_dir: str, max_display: int = 10, verbose: bool = False) -> NoReturn:
|
|
66
|
+
"""
|
|
67
|
+
Summarize the status of simulations.
|
|
68
|
+
Args:
|
|
69
|
+
exp_dir: Experiment Directory Path
|
|
70
|
+
max_display: the maximum number of items to display
|
|
71
|
+
verbose: whether to display the simulation details
|
|
72
|
+
Returns:
|
|
73
|
+
None
|
|
74
|
+
"""
|
|
75
|
+
summary = {
|
|
76
|
+
'SUCCEEDED': [],
|
|
77
|
+
'RUNNING': [],
|
|
78
|
+
'FAILED': [],
|
|
79
|
+
'PENDING': []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
counter = {
|
|
83
|
+
'SUCCEEDED': 0,
|
|
84
|
+
'RUNNING': 0,
|
|
85
|
+
'FAILED': 0,
|
|
86
|
+
'PENDING': 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
total_simulation_count = 0
|
|
90
|
+
|
|
91
|
+
# Traverse through all sub-folders in the given folder path
|
|
92
|
+
for sub_folder in os.listdir(exp_dir):
|
|
93
|
+
sub_folder_path = os.path.join(exp_dir, sub_folder)
|
|
94
|
+
|
|
95
|
+
if os.path.isdir(sub_folder_path):
|
|
96
|
+
# Check if the sub-folder contains metadata.json
|
|
97
|
+
if not os.path.exists(os.path.join(sub_folder_path, 'metadata.json')):
|
|
98
|
+
continue
|
|
99
|
+
total_simulation_count += 1
|
|
100
|
+
status_file_path = os.path.join(sub_folder_path, FILE_NAME)
|
|
101
|
+
|
|
102
|
+
if os.path.isfile(status_file_path):
|
|
103
|
+
with open(status_file_path, 'r') as file:
|
|
104
|
+
content = file.read().strip()
|
|
105
|
+
|
|
106
|
+
status = status_mapping.get(content, 'PENDING')
|
|
107
|
+
summary[status] = append_with_limit(summary[status], sub_folder, max_display)
|
|
108
|
+
counter[status] += 1
|
|
109
|
+
else:
|
|
110
|
+
summary['PENDING'] = append_with_limit(summary['PENDING'], sub_folder, max_display)
|
|
111
|
+
counter['PENDING'] += 1
|
|
112
|
+
|
|
113
|
+
# Print out the results
|
|
114
|
+
console = Console()
|
|
115
|
+
console.print(f'\n[bold][cyan]Experiment Directory[/][/]: \n{normalize_path(exp_dir)}\n')
|
|
116
|
+
console.print(f"[bold][cyan]Simulation Count[/][/]: [yellow]{total_simulation_count}[/]\n")
|
|
117
|
+
|
|
118
|
+
for status in ['SUCCEEDED', 'FAILED', 'RUNNING', 'PENDING']:
|
|
119
|
+
# console.print(f"{status} ({counter[status]})")
|
|
120
|
+
if status == 'SUCCEEDED':
|
|
121
|
+
console.print(f"[bold][green]{status}[/][/] ({counter[status]})")
|
|
122
|
+
elif status == 'FAILED':
|
|
123
|
+
console.print(f"[bold][red]{status}[/][/] ({counter[status]})")
|
|
124
|
+
elif status == 'RUNNING':
|
|
125
|
+
console.print(f"[bold][cyan]{status}[/][/] ({counter[status]})")
|
|
126
|
+
elif status == 'PENDING':
|
|
127
|
+
console.print(f"[bold][grey69]{status}[/][/] ({counter[status]})")
|
|
128
|
+
if verbose:
|
|
129
|
+
for sim in summary[status]:
|
|
130
|
+
console.print(f"{sim:>40}")
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: idmtools_platform_container
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Container platform for IDM-Tools
|
|
5
|
+
Author-email: Zhaowei Du <zdu@idmod.org>, Sharon Chen <shchen@idmod.org>, Clinton Collins <ccollins@idmod.org>, Benoit Raybaud <braybaud@idmod.org>, Clark Kirkman IV <ckirkman@idmod.org>, Ye Chen <yechen@idmod.org>, Mary Fisher <mafisher@idmod.org>, Mandy Izzo <mizzo@idmod.org>, Jen Schripsema <jschripsema@idmod.org>, Ross Carter <rcarter@idmod.org>
|
|
6
|
+
Project-URL: Homepage, https://github.com/InstituteforDiseaseModeling/idmtools
|
|
7
|
+
Keywords: modeling,IDM
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.TXT
|
|
16
|
+
Requires-Dist: idmtools_platform_general<1.0.0,>=0.0.0
|
|
17
|
+
Requires-Dist: docker>5.0
|
|
18
|
+
Requires-Dist: rich
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: idmtools[test]; extra == "test"
|
|
21
|
+
Requires-Dist: idmtools_test; extra == "test"
|
|
22
|
+
Requires-Dist: idmtools_models; extra == "test"
|
|
23
|
+
Provides-Extra: packaging
|
|
24
|
+
Requires-Dist: flake8; extra == "packaging"
|
|
25
|
+
Requires-Dist: coverage; extra == "packaging"
|
|
26
|
+
Requires-Dist: bump2version; extra == "packaging"
|
|
27
|
+
Requires-Dist: twine; extra == "packaging"
|
|
28
|
+
Requires-Dist: natsort; extra == "packaging"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
# Idmtools platform container
|
|
34
|
+
|
|
35
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
36
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
37
|
+
**Table of Contents**
|
|
38
|
+
|
|
39
|
+
- [Introduction](#introduction)
|
|
40
|
+
- [Pre-requisites](#pre-requisites)
|
|
41
|
+
- [Installation](#installation)
|
|
42
|
+
- [Examples for container platform](#examples-for-container-platform)
|
|
43
|
+
- [Initialize platform](#initialize-platform)
|
|
44
|
+
- [Container Examples](#container-examples)
|
|
45
|
+
- [Check result with CLI commands](#check-result-with-cli-commands)
|
|
46
|
+
- [Check result files](#check-result-files)
|
|
47
|
+
- [Folder structure](#folder-structure)
|
|
48
|
+
- [Basic CLI commands](#basic-cli-commands)
|
|
49
|
+
- [List running jobs](#list-running-jobs)
|
|
50
|
+
- [Check status](#check-status)
|
|
51
|
+
- [Cancel job](#cancel-job)
|
|
52
|
+
- [View Experiments history](#view-experiments-history)
|
|
53
|
+
- [Note](#note)
|
|
54
|
+
|
|
55
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
56
|
+
|
|
57
|
+
# Introduction
|
|
58
|
+
|
|
59
|
+
**ContainerPlatform** is a platform designed to facilitate the execution of experiments and simulations within Docker containers. It provides a robust environment with all necessary tools and dependencies installed, allowing for seamless integration and execution of computational tasks.
|
|
60
|
+
|
|
61
|
+
## Pre-requisites
|
|
62
|
+
- Python 3.8/3.9/3.10/3.11/3.12 x64-bit
|
|
63
|
+
- OS:
|
|
64
|
+
- Windows 10 Pro or Enterprise
|
|
65
|
+
- Linux
|
|
66
|
+
- macOS (10.15 Catalina or later)
|
|
67
|
+
- Docker or Docker Desktop(required for the container platform)
|
|
68
|
+
On Windows, please use Docker Desktop 4.0.0 or later
|
|
69
|
+
- **Mac user**: Only support Intel based **x86_64** architecture if you want to run emodpy related disease models on **Docker** container platform. Apple based ARM architecture currently is not supported.
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
- **Install python**
|
|
74
|
+
|
|
75
|
+
Ensure you have Python 3.8+ installed on your system.
|
|
76
|
+
|
|
77
|
+
- Create and activate a virtual environment:
|
|
78
|
+
```
|
|
79
|
+
python -m venv venv
|
|
80
|
+
source venv/bin/activate # On macOS/Linux
|
|
81
|
+
venv\Scripts\activate # On Windows
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Install all **container** platform related packages
|
|
85
|
+
```bash
|
|
86
|
+
pip install idmtools[container] --index-url=https://packages.idmod.org/api/pypi/pypi-production/simple
|
|
87
|
+
```
|
|
88
|
+
- Optional: Install all **idmtools** packages
|
|
89
|
+
```bash
|
|
90
|
+
pip install idmtools[full] --index-url=https://packages.idmod.org/api/pypi/pypi-production/simple
|
|
91
|
+
```
|
|
92
|
+
- To **override** existing idmtools container related packages after installing emodpy, run this command
|
|
93
|
+
```bash
|
|
94
|
+
pip install idmtools[container] --index-url=https://packages.idmod.org/api/pypi/pypi-production/simple --force-reinstall --no-cache-dir --upgrade
|
|
95
|
+
```
|
|
96
|
+
**Mac user**: You map need to escape the square brackets with a backslash like `\[container\]` or `\[full\]` in above command.
|
|
97
|
+
|
|
98
|
+
- Extra steps for Windows user:
|
|
99
|
+
- Enable **Developer Mode** on Windows
|
|
100
|
+
|
|
101
|
+
If you are running the script on Windows, you need to enable Developer Mode. To enable Developer Mode, go to Settings -> Update & Security -> For developers and select Developer Mode on, or refer to this [guide](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development).
|
|
102
|
+
|
|
103
|
+
- Enable **long file path** for Windows
|
|
104
|
+
|
|
105
|
+
Due to the file/folder structure design outlined in the section below, if running the script on Windows, be aware of the file path length limitation (less than 255 characters).
|
|
106
|
+
|
|
107
|
+
To allow longer file paths, you can enable Long Path Support in the Windows Group Policy Editor.
|
|
108
|
+
Refer to this [guide](https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/The-Windows-10-default-path-length-limitation-MAX-PATH-is-256-characters.html) for detailed instructions.
|
|
109
|
+
|
|
110
|
+
## Examples for container platform
|
|
111
|
+
|
|
112
|
+
### Initialize platform
|
|
113
|
+
- This is the example using Container Platform
|
|
114
|
+
```python
|
|
115
|
+
from idmtools.core.platform_factory import Platform
|
|
116
|
+
platform = Platform('CONTAINER', job_directory='<user job directory>')
|
|
117
|
+
```
|
|
118
|
+
- To trigger MPI, use ntasks >=2:
|
|
119
|
+
```python
|
|
120
|
+
from idmtools.core.platform_factory import Platform
|
|
121
|
+
platform = Platform('CONTAINER', job_directory='<user job directory>', ntasks=2)
|
|
122
|
+
```
|
|
123
|
+
- More options for container platform initialization:
|
|
124
|
+
refer to [ContainerPlatform attributes](hhttps://docs.idmod.org/projects/idmtools/en/latest/platforms/container/index.html#containerplatform-attributes)
|
|
125
|
+
|
|
126
|
+
### Container Examples
|
|
127
|
+
- idmtools examples: https://github.com/InstituteforDiseaseModeling/idmtools/tree/main/examples/platform_container
|
|
128
|
+
- emodpy-malaria examples: https://github.com/EMOD-Hub/emodpy-malaria/tree/main/examples-container
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### Check result with CLI commands
|
|
132
|
+
```bash
|
|
133
|
+
idmtools container status <experiment id>
|
|
134
|
+
```
|
|
135
|
+
### Check result files
|
|
136
|
+
- on host: `<job_directory>/<suite_path>/<experiment_path>/<simulation_path>/`
|
|
137
|
+
- in container: `/home/container-data/<suite_path>/<experiment_path>/<simulation_path>/`
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### Folder structure
|
|
141
|
+
By default, `idmtools` now generates simulations with the following structure:
|
|
142
|
+
`job_directory/s_<suite_name>_<suite_uuid>/e_<experiment_name>_<experiment_uuid>/simulation_uuid`
|
|
143
|
+
- `job_directory` — The base directory that contains all suite, experiment, and simulation folders.
|
|
144
|
+
- `s_<suite_name>_<suite_uuid>` — The suite directory, where the suite name (truncated to a maximum of 30 characters) is prefixed with s_, followed by its unique suite UUID.
|
|
145
|
+
- `e_<experiment_name>_<experiment_uuid>` — The experiment directory, where the experiment name (also truncated to 30 characters) is prefixed with e_, followed by its unique experiment UUID.
|
|
146
|
+
- `simulation_uuid` — The simulation folder identified only by its UUID.
|
|
147
|
+
|
|
148
|
+
Suite is optional. If the user does not specify a suite, the folder will be:
|
|
149
|
+
`job_directory/e_<experiment_name>_<experiment_uuid>/simulation_uuid`
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
|
|
153
|
+
If you create a suite named: `my_very_long_suite_name_for_malaria_experiment`
|
|
154
|
+
|
|
155
|
+
and an experiment named: `test_experiment_with_calibration_phase`
|
|
156
|
+
`idmtools` will automatically truncate both names to a maximum of 30 characters and apply the prefixes s_ for suites and e_ for experiments, resulting in a path like:
|
|
157
|
+
```
|
|
158
|
+
job_directory/
|
|
159
|
+
└── s_my_very_long_suite_name_for_m_12345678-9abc-def0-1234-56789abcdef0/
|
|
160
|
+
└── e_test_experiment_with_calibrati_abcd1234-5678-90ef-abcd-1234567890ef/
|
|
161
|
+
└── 7c9e6679-7425-40de-944b-e07fc1f90ae7/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Or for no suite case:
|
|
165
|
+
```
|
|
166
|
+
job_directory/
|
|
167
|
+
└── e_test_experiment_with_calibrati_abcd1234-5678-90ef-abcd-1234567890ef/
|
|
168
|
+
└── 7c9e6679-7425-40de-944b-e07fc1f90ae7/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Users can customize this structure through the idmtools.ini configuration file:
|
|
172
|
+
- `name_directory = False` — Excludes the suite and experiment names (and their prefixes) from the simulation path.
|
|
173
|
+
- `sim_name_directory = True` — Includes the simulation name in the simulation folder path when name_directory = True.
|
|
174
|
+
|
|
175
|
+
## Basic CLI commands
|
|
176
|
+
|
|
177
|
+
**ContainerPlatform** provides several CLI commands to manage and monitor experiments and simulations. Below are some basic commands:
|
|
178
|
+
|
|
179
|
+
### List running jobs
|
|
180
|
+
|
|
181
|
+
To list running experiment or simulation jobs:
|
|
182
|
+
```bash
|
|
183
|
+
idmtools container jobs [<container-id>] [-l <limit>] [-n <next>]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Check status
|
|
187
|
+
|
|
188
|
+
To check the status of an experiment or simulation:
|
|
189
|
+
```bash
|
|
190
|
+
idmtools container status <item-id> [-c <container_id>] [-l <limit>] [--verbose/--no-verbose]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Cancel job
|
|
194
|
+
|
|
195
|
+
To cancel an experiment or simulation job:
|
|
196
|
+
```bash
|
|
197
|
+
idmtools container cancel <item-id> [-c <container_id>]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### View Experiments history
|
|
201
|
+
|
|
202
|
+
To view experiments history:
|
|
203
|
+
```bash
|
|
204
|
+
idmtools container history [<container-id>] [-l <limit>] [-n <next>]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
## Note
|
|
209
|
+
|
|
210
|
+
- **WorkItem** is not supported on the Container Platform as it is not needed in most cases since the code already runs on user's local computer.
|
|
211
|
+
- **AssetCollection** creation or referencing to an existing AssetCollection are not supported on the Container Platform with current release. If you've used the COMPS Platform, you may have scripts using these objects. You would need to update these scripts without using these objects in order to run them on the Container Platform.
|
|
212
|
+
- Run with **Singularity** is not needed with Container Platform. If you take existing COMPS example and try to run it with Container Platform, you may need to remove the code that setups the singularity image.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
docker_image/BASE_VERSION,sha256=iu1kK_URi508hZvUvjXsrHW26HPM4057b1VLBvdVUNc,3
|
|
2
|
+
docker_image/Dockerfile,sha256=x6io_BemPMYjb7CriX6BbQLGA-3zeOBE-sYY0PYP0hE,1471
|
|
3
|
+
docker_image/Dockerfile_buildenv,sha256=clHM8WLuGHqPX01ku9Xnku6fHYWsUSm0ECeGbsbSx7k,1304
|
|
4
|
+
docker_image/ImageName,sha256=z7uULwoWFVCcS6LaN05T5Fc1aZ9n-ZTGP2vcM9B3JUI,23
|
|
5
|
+
docker_image/README.md,sha256=8jXYYLGcJPecRv9mdYBBdp2d4I-08ia_CEa0A-rtqk4,4038
|
|
6
|
+
docker_image/__init__.py,sha256=tKN1Oxn8Y7VjaLoKeOviyb_MNrdyTru1NRkxaKWRyM8,130
|
|
7
|
+
docker_image/build_docker_image.py,sha256=MIrNJ-Q1CGT4uggM8g8l2YzK0hUq3skJ0l4zILVtLvY,6336
|
|
8
|
+
docker_image/docker_image_history.py,sha256=rxZDRTEuxKeImtwgrvUNE_CspOy1GkhHQ0bdSjWPTog,3204
|
|
9
|
+
docker_image/pip.conf,sha256=MhSPF_n4_RBgWzXantlhrNKlvSI9cVXevxI0IuJbBeM,122
|
|
10
|
+
docker_image/push_docker_image.py,sha256=auMAnZFagzD1S1rYf5HEly3sBWG497TC7JfcxVvRixc,2976
|
|
11
|
+
docker_image/requirements.txt,sha256=db9pK33GEypTjh9szgb2bGSG58iI7K6r15P-ORQKkd0,14
|
|
12
|
+
docker_image/rocky_meta_runtime.txt,sha256=7dkzlquXN2H9r4_4HWg9WyUM0_k51dizz-a2i1PbbCg,13366
|
|
13
|
+
docker_image/debian/BASE_VERSION,sha256=iu1kK_URi508hZvUvjXsrHW26HPM4057b1VLBvdVUNc,3
|
|
14
|
+
docker_image/debian/Dockerfile,sha256=8hTe0q3GKcyWFvmhCwua0BF9SVjFmdbIx-4JlchFpns,855
|
|
15
|
+
docker_image/debian/ImageName,sha256=pxh7Oi9ShDj7D4Tg543SPmn0PbKRgBU2SEvt65nRNr4,24
|
|
16
|
+
docker_image/debian/README.md,sha256=x7VmCKVtLTaewHCsomVvb8-FudB-TmULPSGXQbJSlQ0,1950
|
|
17
|
+
docker_image/debian/pip.conf,sha256=hKl-m_qBXwyQ3QU_lq5tber-7MVizUomo9Kxw5Snb-I,157
|
|
18
|
+
docker_image/debian/requirements.txt,sha256=hI7mG_x1qwwdbWPzTyfXgI_yHCs5IzMDXhNekia2IGg,15
|
|
19
|
+
idmtools_platform_container/__init__.py,sha256=VKbu2chvH-WEWNujss9GhaSg6Cg3XAhxz8wNGAUelIE,494
|
|
20
|
+
idmtools_platform_container/container_platform.py,sha256=ypbie6NDZsV0iDtaOItWQLpbaEWm2S2pUIUVKbek8qo,14566
|
|
21
|
+
idmtools_platform_container/plugin_info.py,sha256=Qt_AFxw6kM-tSNerAHjw_Tr9lPhPwjLf4c8MRApVFyY,2203
|
|
22
|
+
idmtools_platform_container/cli/__init__.py,sha256=oZS_5RXwYEXLC3T8c7MwQHDF4uc27NPT1wOt24C4K60,115
|
|
23
|
+
idmtools_platform_container/cli/container.py,sha256=mSSR_FZKw1X47isTd45b-IHvfQKjwwgNTzWd0pG0jmk,24909
|
|
24
|
+
idmtools_platform_container/container_operations/__init__.py,sha256=LHSc16p3SrzCtTxMWFa9dHsTm9dh8f_bkkvAADprTBE,132
|
|
25
|
+
idmtools_platform_container/container_operations/docker_operations.py,sha256=HE33V9EkdB5HD_PVW2sEBakv-1_9CTo939V8i4QebXg,21794
|
|
26
|
+
idmtools_platform_container/platform_operations/__init__.py,sha256=Qkbsx1fmWVBSrLjNv7F3BB39abweyLjdVbXDhXmJ5ao,139
|
|
27
|
+
idmtools_platform_container/platform_operations/experiment_operations.py,sha256=UvN_nCcGHQ0R4JzTNa22U0PAFRG-eqyEBHSvy0h9p5c,4286
|
|
28
|
+
idmtools_platform_container/platform_operations/simulation_operations.py,sha256=z4tVDbkySah9Sd0tHExxdqgRCAy4djSqheMDBQUkxOA,2183
|
|
29
|
+
idmtools_platform_container/utils/__init__.py,sha256=Qkbsx1fmWVBSrLjNv7F3BB39abweyLjdVbXDhXmJ5ao,139
|
|
30
|
+
idmtools_platform_container/utils/general.py,sha256=KDO5rSCV9cu_9bH1Cm71GMTkRdqON1EJim0oWW_TfQQ,4542
|
|
31
|
+
idmtools_platform_container/utils/status.py,sha256=NIsTYLqWemBRVn9GxR8e2xla8dLc5rTlo5FmPSHkvNc,4147
|
|
32
|
+
idmtools_platform_container-0.0.2.dist-info/licenses/LICENSE.TXT,sha256=l9S8Ydr_LcejxKoqK8191ZAOsmVX-nJLSPoLKZDUgcg,197
|
|
33
|
+
tests/inputs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
tests/inputs/model.py,sha256=cUm56BVt7KmbzNmyUl732CvC02SFRhyziDwOJTUIpTI,534
|
|
35
|
+
tests/inputs/model1.py,sha256=5NPHOFBZEagyeRlFagVIDCXJoorv83t3_HmCC_83byQ,645
|
|
36
|
+
tests/inputs/model3.py,sha256=21m0BFiyLKMM88pc7bK7U6J6Awu3jHX4gV-X4pudwxI,592
|
|
37
|
+
tests/inputs/model_file.py,sha256=UwYvw7DWzWI6DggsMglOlgkxvSbCFng0VL8QwGqDJi8,375
|
|
38
|
+
tests/inputs/run.sh,sha256=wypwLeCqUMa2EAm6AafKT79U0iRqtLIjF8XgKEhf1Dw,19
|
|
39
|
+
tests/inputs/sleep.py,sha256=gNPxSpDzETWSd8STl_Nvi1RnAYXJ0zWki9Rc40YTk1g,176
|
|
40
|
+
tests/inputs/Assets/MyLib/functions.py,sha256=kGM70PHGJHQbn-eQlLFerRQJm8s64G2-ybiiErX2Ssk,42
|
|
41
|
+
tests/test_container_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
tests/test_container_cli/helper.py,sha256=fVsKIGV9cpS5-8AshKZktn0eS7n78qrfX-WqGWIsoWY,1439
|
|
43
|
+
tests/test_container_cli/test_base.py,sha256=g27tFhv6Hfq1pwJmXQkJ6p3GxocZZ6zrvZKEEVsPh_g,432
|
|
44
|
+
tests/test_container_cli/test_cancel.py,sha256=OPACLDyqh1NoA0fJKYTdocCHJ4Vr2Y_Ujard7tkvdUo,4339
|
|
45
|
+
tests/test_container_cli/test_clear_results.py,sha256=_vkduO5KHKkSFOCDVdrCZ4uR1HbV1SduAZH3qzqho3M,2869
|
|
46
|
+
tests/test_container_cli/test_container.py,sha256=7qDQVYLrC0dn5r3oNqAV25K5ypiGn4rgpmfMbmoT2Yw,4100
|
|
47
|
+
tests/test_container_cli/test_file_container_cli.py,sha256=qMO6znb5CsZw4l69hjtmwBguiJ-pn6FdYgUuHHp0-NU,5647
|
|
48
|
+
tests/test_container_cli/test_get_detail.py,sha256=Loul9ZDEHGKGx2gaIf4IUT9Y5ewL20iKePVjBQT45WE,2777
|
|
49
|
+
tests/test_container_cli/test_history.py,sha256=Q9MSGcYc5moztc7MsJ6ihazKN5w3qHL9J53boWs7dUE,7449
|
|
50
|
+
tests/test_container_cli/test_history_count.py,sha256=YEDMLQuWI8I3GXIsS11ZvbA7To0wwRAaVk-Ax3t4dgc,2293
|
|
51
|
+
tests/test_container_cli/test_inspect.py,sha256=8fS-kNxIvHZBp5vA19Xw4BW6niuTDGhkjrQn44zrJjs,2593
|
|
52
|
+
tests/test_container_cli/test_install.py,sha256=Thvm5mS4NazDsGlTjgWzzwa2i9PHuMHw3RkQdo00w5s,2293
|
|
53
|
+
tests/test_container_cli/test_is_running.py,sha256=_EWymlD_ggs56KjNf9uPpHoSUtDwsAAX3vfgV-EB5aQ,3011
|
|
54
|
+
tests/test_container_cli/test_jobs.py,sha256=c1BzFmxx-evpqmKydlX-6iEiwLs52ZfqW1meUxO-e7Q,6178
|
|
55
|
+
tests/test_container_cli/test_list_containers.py,sha256=nLNSpa1e12t7JngX0W4NVhl7jIpqKFRQ64Vs-bAQpL0,4879
|
|
56
|
+
tests/test_container_cli/test_packages.py,sha256=wEvmrFgbl73H8d4faSlGY2FhQlNShnG5JEiKudKNl_E,1766
|
|
57
|
+
tests/test_container_cli/test_path.py,sha256=j2zrXUp8apL23H5uqrlceVA5o5AdzFM2FW3U2QDg_3U,5027
|
|
58
|
+
tests/test_container_cli/test_ps.py,sha256=YHikTOW1xbwYglXFx65DKNviLYoGWAiayQhQ70KmEQY,2127
|
|
59
|
+
tests/test_container_cli/test_remove_container.py,sha256=cN-RSudBTwCZcmZeLj6RsNTf4ljZ40pn2F0zXYiRaNE,4021
|
|
60
|
+
tests/test_container_cli/test_status.py,sha256=aTMW5PUcyqsBP5SRmraFY2AullEdWqnzjVFAL-Z3yqE,8848
|
|
61
|
+
tests/test_container_cli/test_stop_container.py,sha256=YDij_oVWS0yVE3sUgARVOVL1s7zmSEU3bhgCfKiStJs,3496
|
|
62
|
+
tests/test_container_cli/test_sync_history.py,sha256=cs4QhU1b_cVErU_5gAp8cPfhlxiQNCnUTwZY5qCAC44,4900
|
|
63
|
+
tests/test_container_cli/test_verify_docker.py,sha256=DadjUIUHQHqLEpqT-Kpv-xwS-tuwr_MuQEcWzI7Jo_o,1129
|
|
64
|
+
tests/test_container_cli/test_volume.py,sha256=06gyO4EbOTDluxqFnrArZSQxFX5qhJ60GDF-JLA1hFo,1088
|
|
65
|
+
idmtools_platform_container-0.0.2.dist-info/METADATA,sha256=BYSnsZ13SrWyo1XpXJVjBbDSqA10foBavZy9c0UUeeo,9941
|
|
66
|
+
idmtools_platform_container-0.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
67
|
+
idmtools_platform_container-0.0.2.dist-info/entry_points.txt,sha256=3gzJFC6SgI7l7irtXt9t8ahEo5zs8Yqh0lZBi2eurEo,213
|
|
68
|
+
idmtools_platform_container-0.0.2.dist-info/top_level.txt,sha256=iPMwxX4IWibuh2suAyZ-K9cdu4F84wVtSNijCEUjOE8,47
|
|
69
|
+
idmtools_platform_container-0.0.2.dist-info/RECORD,,
|
tests/inputs/__init__.py
ADDED
|
File without changes
|
tests/inputs/model.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import astor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
# Sample Python code
|
|
7
|
+
source_code = """
|
|
8
|
+
def add(a, b):
|
|
9
|
+
return a + b
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Parse the source code into an AST
|
|
13
|
+
tree = ast.parse(source_code)
|
|
14
|
+
|
|
15
|
+
# Dump the AST tree to a string
|
|
16
|
+
tree_str = astor.dump_tree(tree)
|
|
17
|
+
|
|
18
|
+
# File path to save the AST dump
|
|
19
|
+
file_path = 'ast_dump.txt'
|
|
20
|
+
|
|
21
|
+
# Write the AST string to a file
|
|
22
|
+
with open(file_path, 'w') as file:
|
|
23
|
+
file.write(tree_str)
|
|
24
|
+
|
|
25
|
+
print(f"AST dump written to {file_path}")
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
tests/inputs/model1.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import astor
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
# Sample Python code
|
|
8
|
+
source_code = """
|
|
9
|
+
def add(a, b):
|
|
10
|
+
return a + b
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Parse the source code into an AST
|
|
14
|
+
tree = ast.parse(source_code)
|
|
15
|
+
|
|
16
|
+
# Dump the AST tree to a string
|
|
17
|
+
tree_str = astor.dump_tree(tree)
|
|
18
|
+
|
|
19
|
+
# File path to save the AST dump
|
|
20
|
+
file_path = 'ast_dump.txt'
|
|
21
|
+
|
|
22
|
+
# Write the AST string to a file under output dir
|
|
23
|
+
os.makedirs("output", exist_ok=True)
|
|
24
|
+
with open(Path("output") / file_path, 'w') as file:
|
|
25
|
+
file.write(tree_str)
|
|
26
|
+
|
|
27
|
+
print(f"AST dump written to {file_path}")
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
31
|
+
|
tests/inputs/model3.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from MyLib.functions import add
|
|
4
|
+
current_dir = os.path.abspath(os.getcwd())
|
|
5
|
+
if __name__ == "__main__":
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
with open("config.json", 'r') as fp:
|
|
9
|
+
config = json.load(fp)
|
|
10
|
+
parameters = config["parameters"]
|
|
11
|
+
|
|
12
|
+
result = add(parameters["a"], parameters["b"])
|
|
13
|
+
print(result)
|
|
14
|
+
print(config["parameters"])
|
|
15
|
+
|
|
16
|
+
output_dir = os.path.join(current_dir, "output")
|
|
17
|
+
if not os.path.exists(output_dir):
|
|
18
|
+
os.mkdir(output_dir)
|
|
19
|
+
with open(os.path.join(output_dir, "result.txt"), "w") as fp:
|
|
20
|
+
fp.write("result:")
|
|
21
|
+
fp.write(str(result))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import ast
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
dir_path = os.path.dirname(__file__) # working
|
|
7
|
+
sys.path.append(os.path.join(dir_path, "site-packages")) # Need to site-packages level!!!
|
|
8
|
+
|
|
9
|
+
import astor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def dump_tree():
|
|
13
|
+
src = inspect.getsource(ast)
|
|
14
|
+
expr_ast = ast.parse(src)
|
|
15
|
+
print(astor.dump_tree(expr_ast)) # one line
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
dump_tree()
|