idmtools-test 0.0.0.dev0__py3-none-any.whl → 0.0.3__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.
- idmtools_test/__init__.py +16 -8
- idmtools_test/inputs/__init__.py +0 -0
- idmtools_test/inputs/assets/collections/1/a.txt +0 -0
- idmtools_test/inputs/assets/collections/1/b.txt +0 -0
- idmtools_test/inputs/assets/collections/2/c.txt +0 -0
- idmtools_test/inputs/assets/collections/d.txt +0 -0
- idmtools_test/inputs/builder/sweeps.csv +6 -0
- idmtools_test/inputs/builder/sweeps.yaml +8 -0
- idmtools_test/inputs/compsplatform/__init__.py +0 -0
- idmtools_test/inputs/compsplatform/failing_model.py +5 -0
- idmtools_test/inputs/compsplatform/mixed_model.py +10 -0
- idmtools_test/inputs/compsplatform/working_model.py +5 -0
- idmtools_test/inputs/configuration/idmtools_test.ini +71 -0
- idmtools_test/inputs/custom/Eradication.exe +0 -0
- idmtools_test/inputs/custom/Local_Migration.bin +0 -0
- idmtools_test/inputs/custom/Local_Migration.bin.json +12 -0
- idmtools_test/inputs/custom/Regional_Migration.bin +0 -0
- idmtools_test/inputs/custom/Regional_Migration.bin.json +12 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_air_temperature_daily.bin +0 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_air_temperature_daily.bin.json +26 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_rainfall_daily.bin +0 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_rainfall_daily.bin.json +26 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_relative_humidity_daily.bin +0 -0
- idmtools_test/inputs/custom/Zambia_30arcsec_relative_humidity_daily.bin.json +26 -0
- idmtools_test/inputs/custom/campaign.json +95384 -0
- idmtools_test/inputs/custom/config.json +943 -0
- idmtools_test/inputs/custom/custom_reports.json +163 -0
- idmtools_test/inputs/custom/demo.json +1258 -0
- idmtools_test/inputs/custom/emodules_map.json +9 -0
- idmtools_test/inputs/custom/reporter_plugins/libReportMalariaFiltered.dll +0 -0
- idmtools_test/inputs/custom/reporter_plugins/libSpatialReportMalariaFiltered.dll +0 -0
- idmtools_test/inputs/custom/reporter_plugins/libreporteventcounter.dll +0 -0
- idmtools_test/inputs/duplicated_model/exe/Eradication +0 -0
- idmtools_test/inputs/duplicated_model/f1 +0 -0
- idmtools_test/inputs/emod/Eradication.exe +0 -0
- idmtools_test/inputs/emod_files/campaign.json +21 -0
- idmtools_test/inputs/emod_files/config.json +125 -0
- idmtools_test/inputs/emod_files/demographics.json +81 -0
- idmtools_test/inputs/fakemodels/AnotherOne +0 -0
- idmtools_test/inputs/fakemodels/Eradication +0 -0
- idmtools_test/inputs/fakemodels/Eradication-2.11.custom.exe +0 -0
- idmtools_test/inputs/fakemodels/Eradication.exe +0 -0
- idmtools_test/inputs/files/campaign.json +21 -0
- idmtools_test/inputs/files/config.json +119 -0
- idmtools_test/inputs/files/demographics.json +82 -0
- idmtools_test/inputs/files/hello.txt +1 -0
- idmtools_test/inputs/id_files/slurm.example_python_experiment.id +1 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_air_temperature_daily.bin +0 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_air_temperature_daily.bin.json +26 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_demographics.json +559 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_rainfall_daily.bin +0 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_rainfall_daily.bin.json +26 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_relative_humidity_daily.bin +0 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_relative_humidity_daily.bin.json +26 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Eradication +0 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Eradication.exe +0 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/campaign.json +4 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/config.json +667 -0
- idmtools_test/inputs/malaria_brazil_central_west_spatial/malaria_brazil_central_west_spatial-ERA5Input_demo.csv +37 -0
- idmtools_test/inputs/python/Assets/MyExternalLibrary/__init__.py +0 -0
- idmtools_test/inputs/python/Assets/MyExternalLibrary/functions.py +15 -0
- idmtools_test/inputs/python/Assets/MyLib/functions.py +2 -0
- idmtools_test/inputs/python/Assets/MyLib/temp.py +271 -0
- idmtools_test/inputs/python/Assets/__init__.py +0 -0
- idmtools_test/inputs/python/__init__.py +0 -0
- idmtools_test/inputs/python/folder_dup_file/__init__.py +0 -0
- idmtools_test/inputs/python/folder_dup_file/model1.py +19 -0
- idmtools_test/inputs/python/hello_world.py +1 -0
- idmtools_test/inputs/python/model.py +26 -0
- idmtools_test/inputs/python/model1.py +20 -0
- idmtools_test/inputs/python/model3.py +21 -0
- idmtools_test/inputs/python/newmodel2.py +20 -0
- idmtools_test/inputs/python/output_generator/generate.py +39 -0
- idmtools_test/inputs/python/realpath_verify.py +6 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python36/dtk_generic_intrahost.pyd +0 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python36/dtk_nodedemog.pyd +0 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python37/dtk_generic_intrahost.pyd +0 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python37/dtk_nodedemog.pyd +0 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/SEIR_model.py +252 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/SEIR_model_slurm.py +242 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/config_sim.py +48 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/custom_csv_analyzer.py +133 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/python.sh +4 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/requirements.txt +4 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/templates/config.json +68 -0
- idmtools_test/inputs/python/ye_seir_model/Assets/templates/demographics_template.json +44 -0
- idmtools_test/inputs/python/ye_seir_model/__init__.py +0 -0
- idmtools_test/inputs/python_experiments/__init__.py +0 -0
- idmtools_test/inputs/python_experiments/model.py +10 -0
- idmtools_test/inputs/r/model1.R +1 -0
- idmtools_test/inputs/r/ncov_analysis/individual_dynamics_estimates/estimate_incubation_period.R +89 -0
- idmtools_test/inputs/regression/107/Assets/__init__.py +0 -0
- idmtools_test/inputs/regression/107/Assets/model.py +1 -0
- idmtools_test/inputs/regression/107/__init__.py +0 -0
- idmtools_test/inputs/regression/125/Assets/__init__.py +0 -0
- idmtools_test/inputs/regression/125/Assets/model.py +1 -0
- idmtools_test/inputs/regression/125/Assets2/__init__.py +0 -0
- idmtools_test/inputs/regression/125/Assets2/dir1/__init__.py +0 -0
- idmtools_test/inputs/regression/125/Assets2/dir1/model.py +1 -0
- idmtools_test/inputs/regression/125/Assets2/dir2/__init__.py +0 -0
- idmtools_test/inputs/regression/125/Assets2/dir2/model.py +1 -0
- idmtools_test/inputs/regression/125/__init__.py +0 -0
- idmtools_test/inputs/regression/__init__.py +0 -0
- idmtools_test/inputs/scheduling/hpc/WorkOrder.json +7 -0
- idmtools_test/inputs/scheduling/slurm/WorkOrder.json +11 -0
- idmtools_test/inputs/scheduling/slurm/WorkOrder1.json +11 -0
- idmtools_test/inputs/scheduling/slurm/WorkOrder2.json +13 -0
- idmtools_test/inputs/scheduling/slurm/commandline_model.py +22 -0
- idmtools_test/inputs/serialization/Eradication.exe +0 -0
- idmtools_test/inputs/serialization/single_node_demographics.json +82 -0
- idmtools_test/inputs/singularity/alpine_simple/Singularity.def +28 -0
- idmtools_test/inputs/singularity/alpine_simple/run_model.py +41 -0
- idmtools_test/inputs/singularity/alpine_template/Singularity.jinja +22 -0
- idmtools_test/test_precreate_hooks.py +25 -0
- idmtools_test/utils/__init__.py +0 -0
- idmtools_test/utils/cli.py +41 -0
- idmtools_test/utils/common_experiments.py +79 -0
- idmtools_test/utils/comps.py +152 -0
- idmtools_test/utils/decorators.py +208 -0
- idmtools_test/utils/execute_operations/__init__.py +0 -0
- idmtools_test/utils/execute_operations/experiment_operations.py +237 -0
- idmtools_test/utils/execute_operations/simulate_operations.py +368 -0
- idmtools_test/utils/itest_with_persistence.py +25 -0
- idmtools_test/utils/operations/__init__.py +0 -0
- idmtools_test/utils/operations/experiment_operations.py +64 -0
- idmtools_test/utils/operations/simulation_operations.py +114 -0
- idmtools_test/utils/shared_functions.py +25 -0
- idmtools_test/utils/test_asset.py +89 -0
- idmtools_test/utils/test_asset_collection.py +223 -0
- idmtools_test/utils/test_execute_platform.py +137 -0
- idmtools_test/utils/test_platform.py +94 -0
- idmtools_test/utils/test_task.py +69 -0
- idmtools_test/utils/utils.py +146 -0
- idmtools_test-0.0.3.dist-info/METADATA +48 -0
- idmtools_test-0.0.3.dist-info/RECORD +139 -0
- idmtools_test-0.0.3.dist-info/entry_points.txt +9 -0
- idmtools_test-0.0.3.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools_test-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_test-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_test-0.0.0.dev0.dist-info → idmtools_test-0.0.3.dist-info}/WHEEL +0 -0
- {idmtools_test-0.0.0.dev0.dist-info → idmtools_test-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shlex
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from functools import partial
|
|
10
|
+
from logging import getLogger
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from threading import Lock
|
|
13
|
+
from typing import List, Dict, Any, Type, TYPE_CHECKING, Optional
|
|
14
|
+
from idmtools.assets import Asset, AssetCollection
|
|
15
|
+
from idmtools.core import EntityStatus
|
|
16
|
+
from idmtools.core.task_factory import TaskFactory
|
|
17
|
+
from idmtools.entities import CommandLine
|
|
18
|
+
from idmtools.entities.command_task import CommandTask
|
|
19
|
+
from idmtools.entities.experiment import Experiment
|
|
20
|
+
from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
|
|
21
|
+
from idmtools.entities.simulation import Simulation
|
|
22
|
+
from idmtools.utils.file import file_content_to_generator
|
|
23
|
+
from idmtools.utils.json import IDMJSONEncoder
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
26
|
+
from idmtools_test.utils.test_execute_platform import TestExecutePlatform
|
|
27
|
+
|
|
28
|
+
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
29
|
+
data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
|
|
30
|
+
|
|
31
|
+
logger = getLogger(__name__)
|
|
32
|
+
SIMULATION_LOCK = Lock()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SimulationDict(dict):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_simulation(simulation_id: Simulation, command: str, parent_uid: str, execute_directory, shell: bool = False):
|
|
40
|
+
simulation_path = os.path.join(execute_directory, str(parent_uid), str(simulation_id))
|
|
41
|
+
os.makedirs(simulation_path, exist_ok=True)
|
|
42
|
+
with open(os.path.join(simulation_path, "StdOut.txt"), "w") as out, \
|
|
43
|
+
open(os.path.join(simulation_path, "StdErr.txt"), "w") as err:
|
|
44
|
+
try:
|
|
45
|
+
cmd = str(command)
|
|
46
|
+
print(cmd)
|
|
47
|
+
print(execute_directory)
|
|
48
|
+
if cmd.startswith(execute_directory):
|
|
49
|
+
cmd = cmd.replace(execute_directory, "")
|
|
50
|
+
logger.info('Executing %s from working directory %s', cmd, simulation_path)
|
|
51
|
+
err.write(f"{cmd}\n")
|
|
52
|
+
|
|
53
|
+
# Run our task
|
|
54
|
+
if sys.platform in ['win32', 'cygwin']:
|
|
55
|
+
cmd = shlex.split(cmd.replace("\\", "/"))
|
|
56
|
+
if os.path.exists(os.path.join(simulation_path, cmd[0])):
|
|
57
|
+
cmd[0] = os.path.join(simulation_path, cmd[0])
|
|
58
|
+
else:
|
|
59
|
+
cmd[0] = os.path.abspath(cmd[0])
|
|
60
|
+
cmd = subprocess.list2cmdline(cmd)
|
|
61
|
+
else:
|
|
62
|
+
cmd = shlex.split(cmd.replace("\\", "/"))
|
|
63
|
+
try:
|
|
64
|
+
if not os.access(cmd[0], os.X_OK):
|
|
65
|
+
os.chmod(cmd[0], 0o777)
|
|
66
|
+
except:
|
|
67
|
+
pass
|
|
68
|
+
logger.info(cmd)
|
|
69
|
+
if cmd[0].endswith(".sh"):
|
|
70
|
+
cmd.insert(0, "/bin/bash")
|
|
71
|
+
p = subprocess.Popen(
|
|
72
|
+
cmd,
|
|
73
|
+
cwd=simulation_path,
|
|
74
|
+
env=os.environ,
|
|
75
|
+
shell=shell,
|
|
76
|
+
stdout=out,
|
|
77
|
+
stderr=err
|
|
78
|
+
)
|
|
79
|
+
# store the pid in case we want to cancel later
|
|
80
|
+
logger.info(f"Process id: {p.pid}")
|
|
81
|
+
# Log that we have started this particular simulation
|
|
82
|
+
p.wait()
|
|
83
|
+
if p.returncode == 0:
|
|
84
|
+
status = EntityStatus.SUCCEEDED
|
|
85
|
+
else:
|
|
86
|
+
status = EntityStatus.FAILED
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.exception(e)
|
|
89
|
+
err.write(str(e))
|
|
90
|
+
status = EntityStatus.FAILED
|
|
91
|
+
return parent_uid, simulation_id, status
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class TestExecutePlatformSimulationOperation(IPlatformSimulationOperations):
|
|
96
|
+
platform: 'TestExecutePlatform'
|
|
97
|
+
platform_type: Type = SimulationDict
|
|
98
|
+
|
|
99
|
+
def get(self, simulation_id: str, experiment_id: str = None, **kwargs) -> Any:
|
|
100
|
+
if simulation_id and experiment_id:
|
|
101
|
+
exp_path = os.path.join(self.platform.execute_directory, str(experiment_id))
|
|
102
|
+
sim_path = os.path.join(exp_path, str(simulation_id))
|
|
103
|
+
metadata_src = os.path.join(sim_path, "simulation_metadata.json")
|
|
104
|
+
if not os.path.exists(metadata_src):
|
|
105
|
+
logger.error("Cannot find the simulation at {metadata}")
|
|
106
|
+
raise ValueError(f"Cannot find the simulation at {metadata_src}")
|
|
107
|
+
with open(metadata_src, 'r') as metadata_in:
|
|
108
|
+
metadata = json.load(metadata_in)
|
|
109
|
+
return SimulationDict(metadata)
|
|
110
|
+
|
|
111
|
+
def platform_create(self, simulation: Simulation, **kwargs) -> Simulation:
|
|
112
|
+
simulation.platform = self
|
|
113
|
+
experiment_id = simulation.parent_id
|
|
114
|
+
self.save_metadata(simulation)
|
|
115
|
+
return simulation
|
|
116
|
+
|
|
117
|
+
def batch_create(self, sims: List[Simulation], **kwargs) -> List[Simulation]:
|
|
118
|
+
|
|
119
|
+
simulations = []
|
|
120
|
+
experiment_id = None
|
|
121
|
+
for simulation in sims:
|
|
122
|
+
if simulation.status is None:
|
|
123
|
+
self.pre_create(simulation, **kwargs)
|
|
124
|
+
experiment_id = simulation.parent_id
|
|
125
|
+
self.save_metadata(simulation)
|
|
126
|
+
self.post_create(simulation, **kwargs)
|
|
127
|
+
simulations.append(simulation)
|
|
128
|
+
else:
|
|
129
|
+
simulations.append(simulation)
|
|
130
|
+
return simulations
|
|
131
|
+
|
|
132
|
+
def save_metadata(self, simulation: Simulation, update_data: dict = None):
|
|
133
|
+
exp_path = os.path.join(self.platform.execute_directory, str(simulation.parent_id))
|
|
134
|
+
sim_path = os.path.join(exp_path, str(simulation.id))
|
|
135
|
+
metadata_file = os.path.join(sim_path, "simulation_metadata.json")
|
|
136
|
+
os.makedirs(sim_path, exist_ok=True)
|
|
137
|
+
if update_data and os.path.exists(metadata_file):
|
|
138
|
+
with open(metadata_file, 'r') as metadata_src:
|
|
139
|
+
metadata = json.load(metadata_src)
|
|
140
|
+
metadata.update(update_data)
|
|
141
|
+
else:
|
|
142
|
+
metadata = simulation.to_dict()
|
|
143
|
+
|
|
144
|
+
with open(metadata_file, 'w') as out:
|
|
145
|
+
out.write(json.dumps(metadata, cls=IDMJSONEncoder))
|
|
146
|
+
|
|
147
|
+
def get_parent(self, simulation: Any, **kwargs) -> Any:
|
|
148
|
+
return self.platform._experiments.experiments.get(simulation.parent_id)
|
|
149
|
+
|
|
150
|
+
def platform_run_item(self, simulation: Simulation, **kwargs):
|
|
151
|
+
"""
|
|
152
|
+
Run the item on the test platform
|
|
153
|
+
|
|
154
|
+
This method executes the simulation in a thread pool
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
simulation: Simulation to run
|
|
158
|
+
**kwargs:
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
logger.info(f"Running simulation {simulation.id}")
|
|
164
|
+
self.send_assets(simulation, **kwargs)
|
|
165
|
+
future = self.platform.pool.submit(
|
|
166
|
+
run_simulation,
|
|
167
|
+
simulation.id,
|
|
168
|
+
simulation.task.command,
|
|
169
|
+
simulation.parent.uid,
|
|
170
|
+
self.platform.execute_directory
|
|
171
|
+
)
|
|
172
|
+
self.platform.queue.append(future)
|
|
173
|
+
|
|
174
|
+
def send_assets(self, simulation: Any, **kwargs):
|
|
175
|
+
"""
|
|
176
|
+
Send assets to the test platform. This method uses the execute directory and first tries to link files
|
|
177
|
+
If that fails, files are copied into the directory
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
simulation: Simulation assets to send
|
|
181
|
+
**kwargs:
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
exp_path = self.platform._experiments.get_experiment_path(simulation.parent.uid)
|
|
187
|
+
sim_path = self.get_simulation_asset_path(simulation, experiment_path=exp_path)
|
|
188
|
+
logger.info(f"Creating {exp_path}")
|
|
189
|
+
os.makedirs(sim_path, exist_ok=True)
|
|
190
|
+
self.__copy_simulation_assets_to_simulation_directory(sim_path, simulation)
|
|
191
|
+
exp_path = os.path.join(exp_path, "Assets")
|
|
192
|
+
self._copy_or_link_parent_assets(exp_path, sim_path, simulation)
|
|
193
|
+
|
|
194
|
+
def get_simulation_asset_path(self, simulation: Simulation, experiment_path: str = None) -> str:
|
|
195
|
+
"""
|
|
196
|
+
Get path to simulation assets
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
simulation: Simulation Assets to get path to
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Str path to assets
|
|
203
|
+
"""
|
|
204
|
+
if experiment_path is None:
|
|
205
|
+
experiment_path = self.platform._experiments.get_experiment_path(simulation.parent.uid)
|
|
206
|
+
return os.path.join(experiment_path, str(simulation.id))
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _copy_or_link_parent_assets(exp_path: str, sim_path: str, simulation: Simulation):
|
|
210
|
+
"""
|
|
211
|
+
Link or Copy a simulation parent assets into its directory
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
exp_path: Path to experiment assets
|
|
215
|
+
sim_path: Simulation path
|
|
216
|
+
simulation: Simulation
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
for asset in simulation.parent.assets:
|
|
222
|
+
remote_path = Path(exp_path).joinpath(asset.relative_path) if asset.relative_path else Path(exp_path)
|
|
223
|
+
sim_assets = Path(sim_path).joinpath("Assets")
|
|
224
|
+
src_path = remote_path.joinpath(asset.filename)
|
|
225
|
+
dest_path = sim_assets.joinpath(asset.filename)
|
|
226
|
+
sim_assets.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
if sys.platform in ['win32']:
|
|
228
|
+
import win32file
|
|
229
|
+
link_worked = True
|
|
230
|
+
try:
|
|
231
|
+
logger.info("Trying to link the files")
|
|
232
|
+
if link_worked:
|
|
233
|
+
win32file.CreateSymbolicLink(src_path, dest_path, 1)
|
|
234
|
+
else:
|
|
235
|
+
shutil.copy(src_path, dest_path)
|
|
236
|
+
except Exception:
|
|
237
|
+
link_worked = False
|
|
238
|
+
logger.info("Linking failed. Copying instread")
|
|
239
|
+
shutil.copy(src_path, dest_path)
|
|
240
|
+
else:
|
|
241
|
+
os.symlink(src_path, dest_path)
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def __copy_simulation_assets_to_simulation_directory(sim_path:str, simulation: Simulation):
|
|
245
|
+
for asset in simulation.assets:
|
|
246
|
+
remote_path = os.path.join(sim_path, asset.relative_path) if asset.relative_path else sim_path
|
|
247
|
+
remote_path = os.path.join(remote_path, asset.filename)
|
|
248
|
+
if asset.absolute_path:
|
|
249
|
+
logger.info(f"Copying {asset.absolute_path} to {remote_path}")
|
|
250
|
+
shutil.copy(asset.absolute_path, remote_path)
|
|
251
|
+
else:
|
|
252
|
+
logger.info(f"Writing {asset.absolute_path} to {remote_path}")
|
|
253
|
+
with open(os.path.join(remote_path), 'wb') as out:
|
|
254
|
+
out.write(asset.content.encode())
|
|
255
|
+
|
|
256
|
+
def refresh_status(self, simulation: Simulation, **kwargs):
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
def get_assets(self, simulation: Simulation, files: List[str], **kwargs) -> Dict[str, bytearray]:
|
|
260
|
+
"""
|
|
261
|
+
Get list of files
|
|
262
|
+
Args:
|
|
263
|
+
simulation:
|
|
264
|
+
files:
|
|
265
|
+
**kwargs:
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
logger.info(f'Listing assets for {simulation.id}')
|
|
271
|
+
assets = {}
|
|
272
|
+
for root, dirs, actual_files in os.walk(self.get_simulation_asset_path(simulation)):
|
|
273
|
+
for file in actual_files:
|
|
274
|
+
if file in files:
|
|
275
|
+
fp = os.path.abspath(os.path.join(root, file))
|
|
276
|
+
with open(fp, 'rb') as i:
|
|
277
|
+
assets[file] = i.read()
|
|
278
|
+
return assets
|
|
279
|
+
|
|
280
|
+
def list_assets(self, simulation: Simulation, **kwargs) -> List[Asset]:
|
|
281
|
+
"""
|
|
282
|
+
List assets for an item
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
simulation: Simulation to list assets for
|
|
286
|
+
**kwargs:
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
logger.info(f'Listing assets for {simulation.id}')
|
|
292
|
+
assets = []
|
|
293
|
+
for root, dirs, files in os.walk(self.get_simulation_asset_path(simulation)):
|
|
294
|
+
for file in files:
|
|
295
|
+
fp = os.path.abspath(os.path.join(root, file))
|
|
296
|
+
asset = Asset(absolute_path=fp, filename=file)
|
|
297
|
+
asset.download_generator_hook = partial(file_content_to_generator, fp)
|
|
298
|
+
cksum_hash = hashlib.md5()
|
|
299
|
+
with open(fp, 'rb') as fin:
|
|
300
|
+
cksum_hash.update(fin.read())
|
|
301
|
+
asset.checksum = cksum_hash.hexdigest()
|
|
302
|
+
assets.append(asset)
|
|
303
|
+
return assets
|
|
304
|
+
|
|
305
|
+
def to_entity(self, dict_sim: Dict, load_task: bool = False, parent: Optional[Experiment] = None,
|
|
306
|
+
**kwargs) -> Simulation:
|
|
307
|
+
# convert the dictionary to simulation
|
|
308
|
+
sim: Simulation = Simulation(**{k: v for k, v in dict_sim.items() if k not in ['platform_id', 'item_type']})
|
|
309
|
+
try:
|
|
310
|
+
# load status from str
|
|
311
|
+
sim.status = EntityStatus[sim.status.upper()]
|
|
312
|
+
except:
|
|
313
|
+
pass
|
|
314
|
+
# set platform before we load assets
|
|
315
|
+
sim.platform = self.platform
|
|
316
|
+
# and our parent
|
|
317
|
+
if parent:
|
|
318
|
+
sim.parent = parent
|
|
319
|
+
# get path to our assets
|
|
320
|
+
sim_path = self.get_simulation_asset_path(sim)
|
|
321
|
+
# set task to a blank slate
|
|
322
|
+
sim.task = None
|
|
323
|
+
# load assets first
|
|
324
|
+
if dict_sim['assets']:
|
|
325
|
+
# load the assets
|
|
326
|
+
ac = AssetCollection()
|
|
327
|
+
for dict_asset in dict_sim['assets']:
|
|
328
|
+
if dict_asset['absolute_path'] is None:
|
|
329
|
+
if dict_asset['relative_path']:
|
|
330
|
+
dict_asset['absolute_path'] = os.path.join(sim_path, dict_asset['relative_path'], dict_asset['filename'])
|
|
331
|
+
else:
|
|
332
|
+
dict_asset['absolute_path'] = os.path.join(sim_path, dict_asset['filename'])
|
|
333
|
+
asset = Asset(**dict_asset)
|
|
334
|
+
asset.persisted = True
|
|
335
|
+
asset.download_generator_hook = partial(file_content_to_generator, asset.absolute_path)
|
|
336
|
+
ac.add_asset(asset)
|
|
337
|
+
sim.assets = ac
|
|
338
|
+
# should we fully load the task?
|
|
339
|
+
if load_task:
|
|
340
|
+
if parent.task_type:
|
|
341
|
+
try:
|
|
342
|
+
sim.task = TaskFactory().create(parent.task_type, **dict_sim['task'])
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.exception(e)
|
|
345
|
+
|
|
346
|
+
cli = self._detect_command_line_from_simulation(dict_sim)
|
|
347
|
+
# if we could not find task, set it now, otherwise rebuild the cli
|
|
348
|
+
if sim.task is None:
|
|
349
|
+
sim.task = CommandTask(CommandLine.from_string(cli))
|
|
350
|
+
else:
|
|
351
|
+
sim.task.command = CommandLine.from_string(cli)
|
|
352
|
+
# call task load options(load configs from files, etc)
|
|
353
|
+
sim.task.reload_from_simulation(sim)
|
|
354
|
+
else:
|
|
355
|
+
cli = self._detect_command_line_from_simulation(dict_sim)
|
|
356
|
+
sim.task = CommandTask(cli)
|
|
357
|
+
|
|
358
|
+
# load assets
|
|
359
|
+
return sim
|
|
360
|
+
|
|
361
|
+
def _detect_command_line_from_simulation(self, dict_sim):
|
|
362
|
+
if 'task' in dict_sim and 'command' in dict_sim['task']:
|
|
363
|
+
return dict_sim['task']['command']
|
|
364
|
+
return ''
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from idmtools.services.ipersistance_service import IPersistenceService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ITestWithPersistence(unittest.TestCase):
|
|
9
|
+
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
10
|
+
|
|
11
|
+
def setUp(self) -> None:
|
|
12
|
+
self.data_dir = os.path.join(self.current_directory, "data")
|
|
13
|
+
try:
|
|
14
|
+
os.makedirs(self.data_dir, exist_ok=True)
|
|
15
|
+
except FileExistsError:
|
|
16
|
+
# Very Very Very rarely, high thread counts, we hit this error
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
IPersistenceService.cache_directory = self.data_dir
|
|
20
|
+
|
|
21
|
+
def tearDown(self) -> None:
|
|
22
|
+
try:
|
|
23
|
+
shutil.rmtree(self.data_dir)
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import field, dataclass
|
|
3
|
+
from logging import getLogger, DEBUG
|
|
4
|
+
from threading import Lock
|
|
5
|
+
from typing import List, Any, Type, Dict, Union, TYPE_CHECKING
|
|
6
|
+
from idmtools.core import EntityStatus, UnknownItemException
|
|
7
|
+
from idmtools.entities.experiment import Experiment
|
|
8
|
+
from idmtools.entities.iplatform_ops.iplatform_experiment_operations import IPlatformExperimentOperations
|
|
9
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
10
|
+
from idmtools_test.utils.test_platform import TestPlatform
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
14
|
+
data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
|
|
15
|
+
EXPERIMENTS_LOCK = Lock()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class TestPlatformExperimentOperation(IPlatformExperimentOperations):
|
|
20
|
+
platform: 'TestPlatform'
|
|
21
|
+
platform_type: Type = Experiment
|
|
22
|
+
experiments: Dict[str, Experiment] = field(default_factory=dict, compare=False, metadata={"pickle_ignore": True})
|
|
23
|
+
|
|
24
|
+
def get(self, experiment_id: str, **kwargs) -> Experiment:
|
|
25
|
+
e = self.experiments.get(experiment_id)
|
|
26
|
+
if e is None:
|
|
27
|
+
raise UnknownItemException(f"Cannot find the experiment with the ID of: {experiment_id}")
|
|
28
|
+
e.platform = self.platform
|
|
29
|
+
return e
|
|
30
|
+
|
|
31
|
+
def platform_create(self, experiment: Experiment, **kwargs) -> Experiment:
|
|
32
|
+
if logger.isEnabledFor(DEBUG):
|
|
33
|
+
logger.debug('Creating Experiment')
|
|
34
|
+
EXPERIMENTS_LOCK.acquire()
|
|
35
|
+
self.experiments[experiment.uid] = experiment
|
|
36
|
+
EXPERIMENTS_LOCK.release()
|
|
37
|
+
self.platform._simulations._save_simulations_to_cache(experiment.uid, list(), overwrite=True)
|
|
38
|
+
logger.debug(f"Created Experiment {experiment.uid}")
|
|
39
|
+
return experiment
|
|
40
|
+
|
|
41
|
+
def get_children(self, experiment: Experiment, **kwargs) -> List[Any]:
|
|
42
|
+
return self.platform._simulations.simulations.get(experiment.uid)
|
|
43
|
+
|
|
44
|
+
def get_parent(self, experiment: Any, **kwargs) -> Any:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def platform_run_item(self, experiment: Experiment, **kwargs):
|
|
48
|
+
self.platform._simulations.set_simulation_status(experiment.uid, EntityStatus.RUNNING)
|
|
49
|
+
|
|
50
|
+
def send_assets(self, experiment: Any, **kwargs):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def refresh_status(self, experiment: Experiment, **kwargs):
|
|
54
|
+
if logger.isEnabledFor(DEBUG):
|
|
55
|
+
logger.debug(f'Refreshing status for Experiment: {experiment.uid}')
|
|
56
|
+
for simulation in self.platform._simulations.simulations.get(experiment.uid):
|
|
57
|
+
for esim in experiment.simulations:
|
|
58
|
+
if esim == simulation:
|
|
59
|
+
logger.debug(f'Setting {simulation.uid} Status to {simulation.status}')
|
|
60
|
+
esim.status = simulation.status
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
def list_assets(self, experiment: Experiment, **kwargs) -> List[str]:
|
|
64
|
+
pass
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from logging import getLogger, DEBUG
|
|
4
|
+
from threading import Lock
|
|
5
|
+
from typing import List, Dict, Any, Type, TYPE_CHECKING
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
import numpy as np
|
|
8
|
+
from idmtools.assets import Asset
|
|
9
|
+
from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
|
|
10
|
+
from idmtools.entities.simulation import Simulation
|
|
11
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
12
|
+
from idmtools_test.utils.test_platform import TestPlatform
|
|
13
|
+
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
14
|
+
data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
|
|
15
|
+
|
|
16
|
+
logger = getLogger(__name__)
|
|
17
|
+
SIMULATION_LOCK = Lock()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class TestPlatformSimulationOperation(IPlatformSimulationOperations):
|
|
22
|
+
platform: 'TestPlatform'
|
|
23
|
+
|
|
24
|
+
def all_files(self, simulation: Simulation, **kwargs):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
platform_type: Type = Simulation
|
|
28
|
+
simulations: dict = field(default_factory=dict, compare=False, metadata={"pickle_ignore": True})
|
|
29
|
+
|
|
30
|
+
def get(self, simulation_id: str, **kwargs) -> Any:
|
|
31
|
+
obj = None
|
|
32
|
+
for eid in self.simulations:
|
|
33
|
+
sims = self.simulations.get(eid)
|
|
34
|
+
if sims:
|
|
35
|
+
for sim in self.simulations.get(eid):
|
|
36
|
+
if sim.uid == simulation_id:
|
|
37
|
+
obj = sim
|
|
38
|
+
break
|
|
39
|
+
if obj:
|
|
40
|
+
break
|
|
41
|
+
obj.platform = self.platform
|
|
42
|
+
return obj
|
|
43
|
+
|
|
44
|
+
def platform_create(self, simulation: Simulation, **kwargs) -> Simulation:
|
|
45
|
+
experiment_id = simulation.parent_id
|
|
46
|
+
simulation.uid = str(uuid4())
|
|
47
|
+
|
|
48
|
+
self._save_simulations_to_cache(experiment_id, [simulation])
|
|
49
|
+
return simulation
|
|
50
|
+
|
|
51
|
+
def _save_simulations_to_cache(self, experiment_id, simulations: List[Simulation], overwrite: bool = False):
|
|
52
|
+
if logger.isEnabledFor(DEBUG):
|
|
53
|
+
logger.debug(f'Saving {len(simulations)} to Experiment {experiment_id}')
|
|
54
|
+
SIMULATION_LOCK.acquire()
|
|
55
|
+
existing_simulations = [] if overwrite else self.simulations.pop(experiment_id)
|
|
56
|
+
self.simulations[experiment_id] = existing_simulations + simulations
|
|
57
|
+
SIMULATION_LOCK.release()
|
|
58
|
+
logger.debug(f'Saved sims')
|
|
59
|
+
|
|
60
|
+
def batch_create(self, sims: List[Simulation], **kwargs) -> List[Simulation]:
|
|
61
|
+
simulations = []
|
|
62
|
+
experiment_id = None
|
|
63
|
+
for simulation in sims:
|
|
64
|
+
if simulation.status is None:
|
|
65
|
+
self.pre_create(simulation)
|
|
66
|
+
experiment_id = simulation.parent_id
|
|
67
|
+
self.post_create(simulation)
|
|
68
|
+
simulations.append(simulation)
|
|
69
|
+
|
|
70
|
+
if experiment_id:
|
|
71
|
+
self._save_simulations_to_cache(experiment_id, simulations)
|
|
72
|
+
return simulations
|
|
73
|
+
|
|
74
|
+
def get_parent(self, simulation: Any, **kwargs) -> Any:
|
|
75
|
+
return self.platform._experiments.experiments.get(simulation.parent_id)
|
|
76
|
+
|
|
77
|
+
def platform_run_item(self, simulation: Simulation, **kwargs):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def send_assets(self, simulation: Any, **kwargs):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
def refresh_status(self, simulation: Simulation, **kwargs):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def get_assets(self, simulation: Simulation, files: List[str], **kwargs) -> Dict[str, bytearray]:
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
def list_assets(self, simulation: Simulation, **kwargs) -> List[Asset]:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def set_simulation_status(self, experiment_uid, status):
|
|
93
|
+
self.set_simulation_prob_status(experiment_uid, {status: 1})
|
|
94
|
+
|
|
95
|
+
def set_simulation_prob_status(self, experiment_uid, status):
|
|
96
|
+
if logger.isEnabledFor(DEBUG):
|
|
97
|
+
logger.debug(f'Setting status for sim s on exp {experiment_uid} to {status}')
|
|
98
|
+
simulations = self.simulations.get(experiment_uid)
|
|
99
|
+
for simulation in simulations:
|
|
100
|
+
new_status = np.random.choice(
|
|
101
|
+
a=list(status.keys()),
|
|
102
|
+
p=list(status.values())
|
|
103
|
+
)
|
|
104
|
+
simulation.status = new_status
|
|
105
|
+
self._save_simulations_to_cache(experiment_uid, simulations, True)
|
|
106
|
+
|
|
107
|
+
def set_simulation_num_status(self, experiment_uid, status, number):
|
|
108
|
+
simulations = self.simulations.get(experiment_uid)
|
|
109
|
+
for simulation in simulations:
|
|
110
|
+
simulation.status = status
|
|
111
|
+
number -= 1
|
|
112
|
+
if number <= 0:
|
|
113
|
+
break
|
|
114
|
+
self._save_simulations_to_cache(experiment_uid, simulations, True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from operator import itemgetter
|
|
2
|
+
from COMPS.Data import Experiment as COMPSExperiment, QueryCriteria
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def validate_output(self, exp_id, expected_sim_count):
|
|
6
|
+
sim_count = 0
|
|
7
|
+
for simulation in COMPSExperiment.get(exp_id).get_simulations():
|
|
8
|
+
sim_count = sim_count + 1
|
|
9
|
+
result_file_string = simulation.retrieve_output_files(paths=['output/result.json'])
|
|
10
|
+
print(result_file_string)
|
|
11
|
+
config_string = simulation.retrieve_output_files(paths=['config.json'])
|
|
12
|
+
print(config_string)
|
|
13
|
+
self.assertEqual(result_file_string, config_string)
|
|
14
|
+
|
|
15
|
+
self.assertEqual(sim_count, expected_sim_count)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_sim_tags(self, exp_id, expected_tags):
|
|
19
|
+
tags = []
|
|
20
|
+
for simulation in COMPSExperiment.get(exp_id).get_simulations():
|
|
21
|
+
tags.append(simulation.get(simulation.id, QueryCriteria().select_children('tags')).tags)
|
|
22
|
+
|
|
23
|
+
sorted_tags = sorted(tags, key=itemgetter('a'))
|
|
24
|
+
sorted_expected_tags = sorted(expected_tags, key=itemgetter('a'))
|
|
25
|
+
self.assertEqual(sorted_tags, sorted_expected_tags)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import TypeVar, Union, List, Callable, Any
|
|
3
|
+
from idmtools.frozen.ifrozen import IFrozen
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Asset(IFrozen):
|
|
7
|
+
"""
|
|
8
|
+
A class representing an asset. An asset can either be related to a physical
|
|
9
|
+
asset present on the computer or directly specified by a filename and content.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, absolute_path: 'str' = None, relative_path: 'str' = None, filename: 'str' = None,
|
|
13
|
+
content: 'Any' = None, handler: 'Callable' = str, checksum: str = None):
|
|
14
|
+
"""
|
|
15
|
+
A constructor.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
absolute_path: The absolute path of the asset. Optional if **filename** and **content** are given.
|
|
19
|
+
relative_path: The relative path (compared to the simulation root folder).
|
|
20
|
+
filename: Name of the file. Optional if **absolute_path** is given.
|
|
21
|
+
content: The content of the file. Optional if **absolute_path** is given.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
super().__init__()
|
|
25
|
+
if not absolute_path and (not filename and not content):
|
|
26
|
+
raise ValueError("Impossible to create the asset without either absolute path or filename and content!")
|
|
27
|
+
|
|
28
|
+
self.absolute_path = absolute_path
|
|
29
|
+
self.relative_path = relative_path
|
|
30
|
+
self.filename = filename or os.path.basename(self.absolute_path)
|
|
31
|
+
self._content = content
|
|
32
|
+
self.persisted = False
|
|
33
|
+
self.handler = handler
|
|
34
|
+
self.checksum = checksum
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
return f"<Asset: {os.path.join(self.relative_path, self.filename)} from {self.absolute_path}>"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def relative_path(self):
|
|
41
|
+
return self._relative_path or ""
|
|
42
|
+
|
|
43
|
+
@relative_path.setter
|
|
44
|
+
def relative_path(self, relative_path):
|
|
45
|
+
self._relative_path = relative_path.strip(" \\/") if relative_path else None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def bytes(self):
|
|
49
|
+
if isinstance(self.content, bytes):
|
|
50
|
+
return self.content
|
|
51
|
+
return str.encode(self.handler(self.content))
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def content(self):
|
|
55
|
+
"""
|
|
56
|
+
Returns:
|
|
57
|
+
The content of the file, either from the content attribute or by opening the absolute path.
|
|
58
|
+
"""
|
|
59
|
+
if not self._content:
|
|
60
|
+
with open(self.absolute_path, "rb") as fp:
|
|
61
|
+
self._content = fp.read()
|
|
62
|
+
|
|
63
|
+
return self._content
|
|
64
|
+
|
|
65
|
+
# region Equality and Hashing
|
|
66
|
+
def __eq__(self, other):
|
|
67
|
+
return self.__key() == other.__key()
|
|
68
|
+
|
|
69
|
+
def __key(self):
|
|
70
|
+
if self.absolute_path:
|
|
71
|
+
return self.absolute_path
|
|
72
|
+
|
|
73
|
+
if self.filename and self.relative_path:
|
|
74
|
+
return self.filename, self.relative_path
|
|
75
|
+
|
|
76
|
+
return self._content, self.filename
|
|
77
|
+
|
|
78
|
+
def __hash__(self):
|
|
79
|
+
return hash(self.__key())
|
|
80
|
+
# endregion
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
TAsset = TypeVar("TAsset", bound=Asset)
|
|
84
|
+
# Assets types
|
|
85
|
+
TAssetList = List[TAsset]
|
|
86
|
+
|
|
87
|
+
# Filters types
|
|
88
|
+
TAssetFilter = Union[Callable[[TAsset], bool], Callable]
|
|
89
|
+
TAssetFilterList = List[TAssetFilter]
|