Skip to content

Commit f615215

Browse files
gustavocidornelaswhoseoyster
authored andcommitted
Completes OPEN-3604 Unify and organize the commit bundle validation logs
1 parent 617a95b commit f615215

File tree

3 files changed

+106
-45
lines changed

3 files changed

+106
-45
lines changed

openlayer/models.py

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import logging
12
import os
23
import shutil
34
import subprocess
45
import tempfile
56
from enum import Enum
6-
from typing import List, Set
7+
from typing import List, Optional, Set
78

89
import pandas as pd
910

@@ -78,16 +79,17 @@ class CondaEnvironment:
7879
Path to the requirements file.
7980
python_version_file_path : str
8081
Path to the python version file.
81-
logs_file_path : str
82-
Path to the logs file.
82+
logs_file_path : str, optional
83+
Where to log the output of the conda commands.
84+
If None, the output is shown in stdout.
8385
"""
8486

8587
def __init__(
8688
self,
8789
env_name: str,
8890
requirements_file_path: str,
8991
python_version_file_path: str,
90-
logs_file_path: str,
92+
logs_file_path: Optional[str] = None,
9193
):
9294
if not self._conda_available():
9395
raise Exception("Conda is not available on this machine.")
@@ -97,21 +99,21 @@ def __init__(
9799
self.python_version_file_path = python_version_file_path
98100
self._conda_prefix = self._get_conda_prefix()
99101
self._logs_file_path = logs_file_path
100-
self._logs_file = subprocess.PIPE
102+
self._logs = None
101103

102104
def __enter__(self):
103-
self._logs_file = open(self._logs_file_path, "wb")
105+
self._logs = open(self._logs_file_path, "w")
104106
existing_envs = self.get_existing_envs()
105107
if self.env_name in existing_envs:
106-
print(f"Found existing conda environment '{self.env_name}'.")
108+
logging.info("Found existing conda environment '%s'.", self.env_name)
107109
else:
108110
self.create()
109111
self.install_requirements()
110112
return self
111113

112114
def __exit__(self, exc_type, exc_value, traceback):
113115
self.deactivate()
114-
self._logs_file.close()
116+
self._logs.close()
115117

116118
def _conda_available(self) -> bool:
117119
"""Checks if conda is available on the machine."""
@@ -129,7 +131,7 @@ def _get_conda_prefix(self) -> str:
129131

130132
def create(self):
131133
"""Creates a conda environment with the specified name and python version."""
132-
print(f"Creating a new conda environment '{self.env_name}'...")
134+
logging.info("Creating a new conda environment '%s'...", self.env_name)
133135

134136
with open(
135137
self.python_version_file_path, "r", encoding="UTF-8"
@@ -147,55 +149,60 @@ def create(self):
147149
f"python={python_version}",
148150
"--yes",
149151
],
150-
stdout=self._logs_file,
151-
stderr=self._logs_file,
152+
stdout=self._logs,
153+
stderr=subprocess.STDOUT,
152154
)
153155
except subprocess.CalledProcessError as err:
154156
raise Exception(
155157
f"Failed to create conda environment '{self.env_name}' with python "
156158
f"version {python_version}."
157-
f"Error {err.returncode}: {err.output}"
159+
" Please check the model logs for details. \n"
160+
f"- Error code returned {err.returncode}: {err.output}"
158161
) from None
159162

160163
def delete(self):
161164
"""Deletes the conda environment with the specified name."""
162-
print(f"Deleting conda environment '{self.env_name}'...")
165+
logging.info("Deleting conda environment '%s'...", self.env_name)
163166

164167
try:
165168
subprocess.check_call(
166169
["conda", "env", "remove", "-n", f"{self.env_name}", "--yes"],
167-
stdout=self._logs_file,
168-
stderr=self._logs_file,
170+
stdout=subprocess.DEVNULL,
171+
stderr=subprocess.STDOUT,
169172
)
170173
except subprocess.CalledProcessError as err:
171174
raise Exception(
172175
f"Failed to delete conda environment '{self.env_name}'."
173-
f"Error {err.returncode}: {err.output}"
176+
" Please check the model logs for details. \n"
177+
f"- Error code returned {err.returncode}: {err.output}"
174178
) from None
175179

176180
def get_existing_envs(self) -> Set[str]:
177181
"""Gets the names of all existing conda environments."""
178-
print("Checking existing conda environments...")
182+
logging.info("Checking existing conda environments...")
183+
179184
list_envs_command = """
180185
conda env list | awk '{print $1}'
181186
"""
187+
182188
try:
183189
envs = subprocess.check_output(
184190
list_envs_command,
185191
shell=True,
186-
stderr=self._logs_file,
192+
stderr=self._logs,
187193
)
188194
except subprocess.CalledProcessError as err:
189195
raise Exception(
190196
f"Failed to list conda environments."
191-
f"Error {err.returncode}: {err.output}"
197+
" Please check the model logs for details. \n"
198+
f"- Error code returned {err.returncode}: {err.output}"
192199
) from None
193200
envs = set(envs.decode("UTF-8").split("\n"))
194201
return envs
195202

196203
def activate(self):
197204
"""Activates the conda environment with the specified name."""
198-
print(f"Activating conda environment '{self.env_name}'...")
205+
logging.info("Activating conda environment '%s'...", self.env_name)
199206

200207
activation_command = f"""
201208
eval $(conda shell.bash hook)
@@ -205,19 +212,20 @@ def activate(self):
205212
try:
206213
subprocess.check_call(
207214
activation_command,
208-
stdout=self._logs_file,
209-
stderr=self._logs_file,
215+
stdout=self._logs,
216+
stderr=subprocess.STDOUT,
210217
shell=True,
211218
)
212219
except subprocess.CalledProcessError as err:
213220
raise Exception(
214221
f"Failed to activate conda environment '{self.env_name}'."
215-
f"Error {err.returncode}: {err.output}"
222+
" Please check the model logs for details. \n"
223+
f"- Error code returned {err.returncode}: {err.output}"
216224
) from None
217225

218226
def deactivate(self):
219227
"""Deactivates the conda environment with the specified name."""
220-
print(f"Deactivating conda environment '{self.env_name}'...")
228+
logging.info("Deactivating conda environment '%s'...", self.env_name)
221229

222230
deactivation_command = f"""
223231
eval $(conda shell.bash hook)
@@ -228,27 +236,31 @@ def deactivate(self):
228236
subprocess.check_call(
229237
deactivation_command,
230238
shell=True,
231-
stdout=self._logs_file,
232-
stderr=self._logs_file,
239+
stdout=self._logs,
240+
stderr=subprocess.STDOUT,
233241
)
234242
except subprocess.CalledProcessError as err:
235243
raise Exception(
236244
f"Failed to deactivate conda environment '{self.env_name}'."
237-
f"Error {err.returncode}: {err.output}"
245+
" Please check the model logs for details. \n"
246+
f"- Error code returned {err.returncode}: {err.output}"
238247
) from None
239248

240249
def install_requirements(self):
241250
"""Installs the requirements from the specified requirements file."""
242-
print(f"Installing requirements in conda environment '{self.env_name}'...")
251+
logging.info(
252+
"Installing requirements in conda environment '%s'...", self.env_name
253+
)
243254

244255
try:
245256
self.run_commands(
246257
["pip", "install", "-r", self.requirements_file_path],
247258
)
248259
except subprocess.CalledProcessError as err:
249260
raise Exception(
250-
f"Failed to install requirements from {self.requirements_file_path}."
251-
f"Error {err.returncode}: {err.output}"
261+
f"Failed to install the depencies specified in the requirements.txt file."
262+
" Please check the model logs for details. \n"
263+
f"- Error code returned {err.returncode}: {err.output}"
252264
) from None
253265

254266
def run_commands(self, commands: List[str]):
@@ -266,24 +278,33 @@ def run_commands(self, commands: List[str]):
266278
{" ".join(commands)}
267279
"""
268280
subprocess.check_call(
269-
full_command,
270-
shell=True,
271-
stdout=self._logs_file,
272-
stderr=self._logs_file,
281+
full_command, shell=True, stdout=self._logs, stderr=subprocess.STDOUT
273282
)
274283

275284

276285
class ModelRunner:
277286
"""Wraps the model package and provides a uniform run method."""
278287

279-
def __init__(self, model_package: str):
288+
def __init__(self, model_package: str, logs: Optional[str] = None):
280289
self.model_package = model_package
290+
291+
# Save log to the model package if logs is not specified
292+
if logs is None:
293+
logs_file_path = f"{model_package}/model_run_logs.txt"
294+
295+
logging.basicConfig(
296+
filename=logs_file_path,
297+
format="[%(asctime)s] %(levelname)s - %(message)s",
298+
level=logging.INFO,
299+
datefmt="%Y-%m-%d %H:%M:%S",
300+
)
301+
281302
# TODO: change env name to the model id
282303
self._conda_environment = CondaEnvironment(
283304
env_name="new-openlayer",
284305
requirements_file_path=f"{model_package}/requirements.txt",
285306
python_version_file_path=f"{model_package}/python_version",
286-
logs_file_path=f"{model_package}/logs.txt",
307+
logs_file_path=logs_file_path,
287308
)
288309

289310
def __del__(self):
@@ -317,6 +338,7 @@ def run(self, input_data: pd.DataFrame) -> pd.DataFrame:
317338

318339
# Run the model in the conda environment
319340
with self._conda_environment as env:
341+
logging.info("Running %s rows through the model...", len(input_data))
320342
try:
321343
env.run_commands(
322344
[
@@ -329,11 +351,16 @@ def run(self, input_data: pd.DataFrame) -> pd.DataFrame:
329351
]
330352
)
331353
except subprocess.CalledProcessError as err:
354+
logging.error(
355+
"Failed to run the model. Check the stacktrace above for details."
356+
)
332357
raise Exception(
333-
f"Failed to run the model in conda environment '{env.env_name}'."
334-
f"Error {err.returncode}: {err.output}"
358+
"Failed to run the model in the conda environment."
359+
" Please check the model logs for details. \n"
360+
f" Error {err.returncode}: {err.output}"
335361
) from None
336362

363+
logging.info("Successfully ran data through the model!")
337364
# Read the output data from the csv file
338365
output_data = pd.read_csv(f"{temp_dir}/output_data.csv")
339366

openlayer/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@
1010

1111

1212
# -------------------------- Helper context managers ------------------------- #
13+
class LogStdout:
14+
"""Helper class that suppresses the prints and writes them to the `log_file_path` file."""
15+
16+
def __init__(self, log_file_path: str):
17+
self.log_file_path = log_file_path
18+
19+
def __enter__(self):
20+
self._original_stdout = sys.stdout
21+
sys.stdout = open(self.log_file_path, "w")
22+
23+
def __exit__(self, exc_type, exc_val, exc_tb):
24+
sys.stdout.close()
25+
sys.stdout = self._original_stdout
26+
27+
1328
class HidePrints:
1429
"""Helper class that suppresses the prints and warnings to stdout and Jupyter's stdout.
1530

openlayer/validators.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,11 @@ def _validate_bundle_resources(self):
276276
)
277277
bundle_resources_failed_validations.extend(model_validator.validate())
278278

279+
# Skip printing for now, since it's redundant with the other validations
279280
# Print results of the validation
280-
if bundle_resources_failed_validations:
281-
print("Push failed validations: \n")
282-
_list_failed_validation_messages(bundle_resources_failed_validations)
281+
# if bundle_resources_failed_validations:
282+
# print("Bundle resources failed validations: \n")
283+
# _list_failed_validation_messages(bundle_resources_failed_validations)
283284

284285
# Add the bundle resources failed validations to the list of all failed validations
285286
self.failed_validations.extend(bundle_resources_failed_validations)
@@ -331,11 +332,22 @@ def validate(self) -> List[str]:
331332
List[str]
332333
A list of failed validations.
333334
"""
335+
print(
336+
"----------------------------------------------------------------------------\n"
337+
" Validating commit bundle \n"
338+
"----------------------------------------------------------------------------\n"
339+
)
334340
self._validate_bundle_state()
335341
self._validate_bundle_resources()
336342

337343
if not self.failed_validations:
338-
print("All commit bundle validations passed!")
344+
print(
345+
"\n----------------------------------------------------------------------------\n"
346+
" All commit bundle validations passed! \n"
347+
"----------------------------------------------------------------------------\n"
348+
)
349+
else:
350+
print("Please fix the all the issues above and push again.")
339351

340352
return self.failed_validations
341353

@@ -384,6 +396,9 @@ def validate(self) -> List[str]:
384396
List[str]
385397
A list of failed validations.
386398
"""
399+
print(
400+
"\n------------------------ Commit message validations ------------------------\n"
401+
)
387402
self._validate_commit_message()
388403

389404
if not self.failed_validations:
@@ -833,6 +848,9 @@ def validate(self) -> List[str]:
833848
List[str]
834849
List of all failed validations.
835850
"""
851+
print(
852+
"\n---------------------------- Dataset validations ---------------------------\n"
853+
)
836854
self._validate_dataset_config()
837855
if self.dataset_file_path:
838856
self._validate_dataset_file()
@@ -1134,9 +1152,7 @@ def _validate_model_runner(self):
11341152
try:
11351153
model_runner.run(self.sample_data)
11361154
except Exception as exc:
1137-
model_runner_failed_validations.append(
1138-
f"Failed to run the model with the following error: \n {exc}"
1139-
)
1155+
model_runner_failed_validations.append(f"{exc}")
11401156

11411157
# Print results of the validation
11421158
if model_runner_failed_validations:
@@ -1156,6 +1172,9 @@ def validate(self) -> List[str]:
11561172
List[str]
11571173
A list of all failed validations.
11581174
"""
1175+
print(
1176+
"\n----------------------------- Model validations ----------------------------\n"
1177+
)
11591178
if self.model_package_dir:
11601179
self._validate_model_package_dir()
11611180
if self._use_runner:

0 commit comments

Comments
 (0)