aiverify-moonshot 0.4.1__py3-none-any.whl → 0.4.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.
Files changed (59) hide show
  1. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/METADATA +2 -2
  2. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/RECORD +59 -49
  3. moonshot/__main__.py +77 -35
  4. moonshot/api.py +14 -0
  5. moonshot/integrations/cli/benchmark/benchmark.py +29 -13
  6. moonshot/integrations/cli/benchmark/cookbook.py +36 -6
  7. moonshot/integrations/cli/benchmark/datasets.py +33 -3
  8. moonshot/integrations/cli/benchmark/metrics.py +33 -3
  9. moonshot/integrations/cli/benchmark/recipe.py +36 -6
  10. moonshot/integrations/cli/benchmark/result.py +33 -3
  11. moonshot/integrations/cli/benchmark/run.py +34 -3
  12. moonshot/integrations/cli/common/common.py +12 -6
  13. moonshot/integrations/cli/common/connectors.py +73 -9
  14. moonshot/integrations/cli/common/prompt_template.py +38 -3
  15. moonshot/integrations/cli/redteam/attack_module.py +75 -24
  16. moonshot/integrations/cli/redteam/context_strategy.py +77 -23
  17. moonshot/integrations/cli/redteam/prompt_template.py +1 -1
  18. moonshot/integrations/cli/redteam/redteam.py +52 -6
  19. moonshot/integrations/cli/redteam/session.py +539 -43
  20. moonshot/integrations/web_api/__main__.py +2 -0
  21. moonshot/integrations/web_api/app.py +6 -6
  22. moonshot/integrations/web_api/container.py +12 -2
  23. moonshot/integrations/web_api/routes/bookmark.py +173 -0
  24. moonshot/integrations/web_api/schemas/bookmark_create_dto.py +13 -0
  25. moonshot/integrations/web_api/services/bookmark_service.py +90 -0
  26. moonshot/integrations/web_api/services/utils/file_manager.py +52 -0
  27. moonshot/integrations/web_api/status_updater/moonshot_ui_webhook.py +0 -1
  28. moonshot/integrations/web_api/temp/.gitkeep +0 -0
  29. moonshot/src/api/api_bookmark.py +95 -0
  30. moonshot/src/api/api_connector_endpoint.py +1 -1
  31. moonshot/src/api/api_context_strategy.py +2 -2
  32. moonshot/src/api/api_session.py +1 -1
  33. moonshot/src/bookmark/bookmark.py +257 -0
  34. moonshot/src/bookmark/bookmark_arguments.py +38 -0
  35. moonshot/src/configs/env_variables.py +12 -2
  36. moonshot/src/connectors/connector.py +15 -7
  37. moonshot/src/connectors_endpoints/connector_endpoint.py +65 -49
  38. moonshot/src/cookbooks/cookbook.py +57 -37
  39. moonshot/src/datasets/dataset.py +9 -5
  40. moonshot/src/metrics/metric.py +8 -4
  41. moonshot/src/metrics/metric_interface.py +8 -2
  42. moonshot/src/prompt_templates/prompt_template.py +5 -1
  43. moonshot/src/recipes/recipe.py +38 -25
  44. moonshot/src/redteaming/attack/attack_module.py +18 -8
  45. moonshot/src/redteaming/attack/context_strategy.py +6 -2
  46. moonshot/src/redteaming/session/session.py +15 -11
  47. moonshot/src/results/result.py +7 -3
  48. moonshot/src/runners/runner.py +65 -42
  49. moonshot/src/runs/run.py +15 -11
  50. moonshot/src/runs/run_progress.py +7 -3
  51. moonshot/src/storage/db_interface.py +14 -0
  52. moonshot/src/storage/storage.py +33 -2
  53. moonshot/src/utils/find_feature.py +45 -0
  54. moonshot/src/utils/log.py +66 -0
  55. moonshot/src/utils/timeit.py +8 -1
  56. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/WHEEL +0 -0
  57. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/licenses/AUTHORS.md +0 -0
  58. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/licenses/LICENSE.md +0 -0
  59. {aiverify_moonshot-0.4.1.dist-info → aiverify_moonshot-0.4.2.dist-info}/licenses/NOTICES.md +0 -0
@@ -17,6 +17,10 @@ from moonshot.src.runs.run_status import RunStatus
17
17
  from moonshot.src.storage.db_interface import DBInterface
18
18
  from moonshot.src.storage.storage import Storage
19
19
  from moonshot.src.utils.import_modules import get_instance
20
+ from moonshot.src.utils.log import configure_logger
21
+
22
+ # Create a logger for this module
23
+ logger = configure_logger(__name__)
20
24
 
21
25
 
22
26
  class SessionMetadata:
@@ -283,13 +287,13 @@ class Session:
283
287
 
284
288
  # check if the session metadata record already exists
285
289
  if session_metadata_records:
286
- print("[Session] Session already exists.")
290
+ logger.info("[Session] Session already exists.")
287
291
  self.session_metadata = SessionMetadata.from_tuple(
288
292
  session_metadata_records[0]
289
293
  )
290
294
  # create a new record if session metadata does not exist
291
295
  else:
292
- print("[Session] Creating new session.")
296
+ logger.info("[Session] Creating new session.")
293
297
 
294
298
  # create chat history table for each endpoint
295
299
 
@@ -379,13 +383,13 @@ class Session:
379
383
  # ------------------------------------------------------------------------------
380
384
  # Part 1: Get asyncio running loop
381
385
  # ------------------------------------------------------------------------------
382
- print("[Session] Part 1: Loading asyncio running loop...")
386
+ logger.debug("[Session] Part 1: Loading asyncio running loop...")
383
387
  loop = asyncio.get_running_loop()
384
388
 
385
389
  # ------------------------------------------------------------------------------
386
390
  # Part 2: Load runner processing module
387
391
  # ------------------------------------------------------------------------------
388
- print("[Session] Part 2: Loading runner processing module...")
392
+ logger.debug("[Session] Part 2: Loading runner processing module...")
389
393
  start_time = time.perf_counter()
390
394
  runner_module_instance = None
391
395
  try:
@@ -414,20 +418,20 @@ class Session:
414
418
  )
415
419
 
416
420
  except Exception as e:
417
- print(
421
+ logger.error(
418
422
  f"[Session] Failed to load runner processing module in Part 2 due to error: {str(e)}"
419
423
  )
420
424
  raise e
421
425
 
422
426
  finally:
423
- print(
427
+ logger.debug(
424
428
  f"[Session] Loading runner processing module took {(time.perf_counter() - start_time):.4f}s"
425
429
  )
426
430
 
427
431
  # ------------------------------------------------------------------------------
428
432
  # Part 3: Run runner processing module
429
433
  # ------------------------------------------------------------------------------
430
- print("[Session] Part 3: Running runner processing module...")
434
+ logger.debug("[Session] Part 3: Running runner processing module...")
431
435
  start_time = time.perf_counter()
432
436
  runner_results = {}
433
437
 
@@ -446,7 +450,7 @@ class Session:
446
450
  raise RuntimeError("Failed to initialise runner module instance.")
447
451
 
448
452
  except Exception as e:
449
- print(
453
+ logger.error(
450
454
  f"[Session] Failed to run runner processing module in Part 3 due to error: {str(e)}"
451
455
  )
452
456
  raise e
@@ -455,14 +459,14 @@ class Session:
455
459
  self.red_teaming_progress.status = RunStatus.COMPLETED
456
460
  if self.check_redteaming_type() == RedTeamingType.AUTOMATED:
457
461
  self.red_teaming_progress.notify_progress()
458
- print(
462
+ logger.debug(
459
463
  f"[Session] Running runner processing module took {(time.perf_counter() - start_time):.4f}s"
460
464
  )
461
465
 
462
466
  # ------------------------------------------------------------------------------
463
467
  # Part 4: Wrap up run
464
468
  # ------------------------------------------------------------------------------
465
- print("[Session] Part 4: Wrap up run...")
469
+ logger.debug("[Session] Part 4: Wrap up run...")
466
470
  return runner_results
467
471
 
468
472
  def cancel(self) -> None:
@@ -476,7 +480,7 @@ class Session:
476
480
  Returns:
477
481
  None
478
482
  """
479
- print("[Session] Cancelling automated red teaming...")
483
+ logger.warning("[Session] Cancelling automated red teaming...")
480
484
  self.cancel_event.set()
481
485
 
482
486
  def check_redteaming_type(self) -> RedTeamingType:
@@ -6,6 +6,10 @@ from pydantic import validate_call
6
6
 
7
7
  from moonshot.src.configs.env_variables import EnvVariables
8
8
  from moonshot.src.storage.storage import Storage
9
+ from moonshot.src.utils.log import configure_logger
10
+
11
+ # Create a logger for this module
12
+ logger = configure_logger(__name__)
9
13
 
10
14
 
11
15
  class Result:
@@ -35,7 +39,7 @@ class Result:
35
39
  raise RuntimeError("Result ID is empty")
36
40
 
37
41
  except Exception as e:
38
- print(f"Failed to read result: {str(e)}")
42
+ logger.error(f"Failed to read result: {str(e)}")
39
43
  raise e
40
44
 
41
45
  @staticmethod
@@ -85,7 +89,7 @@ class Result:
85
89
  return True
86
90
 
87
91
  except Exception as e:
88
- print(f"Failed to delete result: {str(e)}")
92
+ logger.error(f"Failed to delete result: {str(e)}")
89
93
  raise e
90
94
 
91
95
  @staticmethod
@@ -115,5 +119,5 @@ class Result:
115
119
  return retn_results_ids, retn_results
116
120
 
117
121
  except Exception as e:
118
- print(f"Failed to get available results: {str(e)}")
122
+ logger.error(f"Failed to get available results: {str(e)}")
119
123
  raise e
@@ -13,6 +13,10 @@ from moonshot.src.runners.runner_arguments import RunnerArguments
13
13
  from moonshot.src.runners.runner_type import RunnerType
14
14
  from moonshot.src.runs.run import Run
15
15
  from moonshot.src.storage.storage import Storage
16
+ from moonshot.src.utils.log import configure_logger
17
+
18
+ # Create a logger for this module
19
+ logger = configure_logger(__name__)
16
20
 
17
21
 
18
22
  class Runner:
@@ -82,13 +86,13 @@ class Runner:
82
86
  )
83
87
  runner_args = Runner.read(runner_id)
84
88
  runner_args.database_instance = Storage.create_database_connection(
85
- EnvVariables.DATABASES.name, runner_id, "db"
89
+ EnvVariables.DATABASES.name, Path(runner_args.database_file).stem, "db"
86
90
  )
87
91
  runner_args.progress_callback_func = progress_callback_func
88
92
  return cls(runner_args)
89
93
 
90
94
  except Exception as e:
91
- print(f"[Runner] Failed to load runner: {str(e)}")
95
+ logger.error(f"[Runner] Failed to load runner: {str(e)}")
92
96
  raise e
93
97
 
94
98
  @classmethod
@@ -114,6 +118,14 @@ class Runner:
114
118
  """
115
119
  try:
116
120
  runner_id = slugify(runner_args.name, lowercase=True)
121
+ runner_info = {
122
+ "name": runner_args.name,
123
+ "database_file": Storage.get_filepath(
124
+ EnvVariables.DATABASES.name, runner_id, "db", True
125
+ ),
126
+ "endpoints": runner_args.endpoints,
127
+ "description": runner_args.description,
128
+ }
117
129
 
118
130
  # Check if runner file exists. If it exists, raise an error.
119
131
  if Storage.is_object_exists(EnvVariables.RUNNERS.name, runner_id, "json"):
@@ -129,35 +141,27 @@ class Runner:
129
141
  f"[Runner] Connector endpoint {endpoint} does not exist."
130
142
  )
131
143
 
132
- runner_info = {
133
- "id": runner_id,
134
- "name": runner_args.name,
135
- "endpoints": runner_args.endpoints,
136
- "database_file": Storage.get_filepath(
137
- EnvVariables.DATABASES.name, runner_id, "db", True
138
- ),
139
- "progress_callback_func": runner_args.progress_callback_func,
140
- "description": runner_args.description,
141
- }
142
- runner_args = RunnerArguments(**runner_info)
143
- runner_args.database_instance = Storage.create_database_connection(
144
- EnvVariables.DATABASES.name, runner_id, "db"
145
- )
146
-
147
144
  # Create runner file
148
145
  Storage.create_object(
149
- EnvVariables.RUNNERS.name, runner_id, runner_args.to_dict(), "json"
146
+ EnvVariables.RUNNERS.name, runner_id, runner_info, "json"
147
+ )
148
+
149
+ # Add additional attributes (id, database instance and update progress_callback_func)
150
+ runner_info["id"] = runner_id
151
+ runner_info["database_instance"] = Storage.create_database_connection(
152
+ EnvVariables.DATABASES.name, runner_id, "db"
150
153
  )
154
+ runner_info["progress_callback_func"] = runner_args.progress_callback_func
151
155
 
152
156
  # Create runner cache table
153
157
  Storage.create_database_table(
154
- runner_args.database_instance, Runner.sql_create_runner_cache_table
158
+ runner_info["database_instance"], Runner.sql_create_runner_cache_table
155
159
  )
156
160
 
157
- return cls(runner_args)
161
+ return cls(RunnerArguments(**runner_info))
158
162
 
159
163
  except Exception as e:
160
- print(f"[Runner] Failed to create runner: {str(e)}")
164
+ logger.error(f"[Runner] Failed to create runner: {str(e)}")
161
165
  raise e
162
166
 
163
167
  @staticmethod
@@ -179,17 +183,40 @@ class Runner:
179
183
  Exception: If an error occurs during the data retrieval or any other operation within the method.
180
184
  """
181
185
  try:
182
- if runner_id:
183
- return RunnerArguments(
184
- **Storage.read_object(EnvVariables.RUNNERS.name, runner_id, "json")
185
- )
186
- else:
187
- raise RuntimeError("Runner ID is empty")
186
+ if not runner_id:
187
+ raise RuntimeError("Runner ID is empty.")
188
+
189
+ runner_details = Runner._read_runner(runner_id)
190
+ if not runner_details:
191
+ raise RuntimeError(f"Runner with ID '{runner_id}' does not exist.")
192
+
193
+ return RunnerArguments(**runner_details)
188
194
 
189
195
  except Exception as e:
190
- print(f"[Runner] Failed to read runner: {str(e)}")
196
+ logger.error(f"[Runner] Failed to read runner: {str(e)}")
191
197
  raise e
192
198
 
199
+ @staticmethod
200
+ def _read_runner(runner_id: str) -> dict:
201
+ """
202
+ Retrieves the runner's information from a JSON file.
203
+
204
+ This internal method is designed to fetch the details of a specific runner by its ID. It searches for the
205
+ corresponding JSON file within the directory specified by `EnvVariables.RUNNERS`. The method returns a
206
+ dictionary containing the runner's information.
207
+
208
+ Args:
209
+ runner_id (str): The unique identifier of the runner whose information is being retrieved.
210
+
211
+ Returns:
212
+ dict: A dictionary with the runner's information.
213
+ """
214
+ runner_info = {"id": runner_id}
215
+ runner_info.update(
216
+ Storage.read_object(EnvVariables.RUNNERS.name, runner_id, "json")
217
+ )
218
+ return runner_info
219
+
193
220
  @staticmethod
194
221
  @validate_call
195
222
  def delete(runner_id: str) -> bool:
@@ -216,7 +243,7 @@ class Runner:
216
243
  return True
217
244
 
218
245
  except Exception as e:
219
- print(f"[Runner] Failed to delete runner: {str(e)}")
246
+ logger.error(f"[Runner] Failed to delete runner: {str(e)}")
220
247
  raise e
221
248
 
222
249
  @staticmethod
@@ -245,18 +272,14 @@ class Runner:
245
272
  if "__" in runner:
246
273
  continue
247
274
 
248
- runner_info = RunnerArguments(
249
- **Storage.read_object(
250
- EnvVariables.RUNNERS.name, Path(runner).stem, "json"
251
- )
252
- )
275
+ runner_info = RunnerArguments(**Runner._read_runner(Path(runner).stem))
253
276
  retn_runners.append(runner_info)
254
277
  retn_runners_ids.append(runner_info.id)
255
278
 
256
279
  return retn_runners_ids, retn_runners
257
280
 
258
281
  except Exception as e:
259
- print(f"[Runner] Failed to get available runners: {str(e)}")
282
+ logger.error(f"[Runner] Failed to get available runners: {str(e)}")
260
283
  raise e
261
284
 
262
285
  def close(self) -> None:
@@ -285,7 +308,7 @@ class Runner:
285
308
  """
286
309
  async with self.current_operation_lock:
287
310
  if self.current_operation:
288
- print(f"[Runner] {self.id} - Cancelling current operation...")
311
+ logger.warning(f"[Runner] {self.id} - Cancelling current operation...")
289
312
  self.current_operation.cancel()
290
313
  self.current_operation = None # Reset the current operation
291
314
 
@@ -328,7 +351,7 @@ class Runner:
328
351
  """
329
352
  async with self.current_operation_lock: # Acquire the lock
330
353
  # Create new benchmark recipe test run
331
- print(f"[Runner] {self.id} - Running benchmark recipe run...")
354
+ logger.info(f"[Runner] {self.id} - Running benchmark recipe run...")
332
355
  self.current_operation = Run(
333
356
  self.id,
334
357
  RunnerType.BENCHMARK,
@@ -354,7 +377,7 @@ class Runner:
354
377
  # After completion, reset current_operation to None within the lock
355
378
  async with self.current_operation_lock:
356
379
  self.current_operation = None
357
- print(f"[Runner] {self.id} - Benchmark recipe run completed and reset.")
380
+ logger.info(f"[Runner] {self.id} - Benchmark recipe run completed.")
358
381
 
359
382
  async def run_cookbooks(
360
383
  self,
@@ -396,7 +419,7 @@ class Runner:
396
419
  """
397
420
  async with self.current_operation_lock: # Acquire the lock
398
421
  # Create new benchmark cookbook test run
399
- print(f"[Runner] {self.id} - Running benchmark cookbook run...")
422
+ logger.info(f"[Runner] {self.id} - Running benchmark cookbook run...")
400
423
  self.current_operation = Run(
401
424
  self.id,
402
425
  RunnerType.BENCHMARK,
@@ -422,7 +445,7 @@ class Runner:
422
445
  # After completion, reset current_operation to None within the lock
423
446
  async with self.current_operation_lock:
424
447
  self.current_operation = None
425
- print(f"[Runner] {self.id} - Benchmark cookbook run completed and reset.")
448
+ logger.info(f"[Runner] {self.id} - Benchmark cookbook run completed.")
426
449
 
427
450
  async def run_red_teaming(
428
451
  self,
@@ -450,7 +473,7 @@ class Runner:
450
473
  Exception: If any error occurs during the setup or execution of the red teaming session.
451
474
  """
452
475
  async with self.current_operation_lock: # Acquire the lock
453
- print(f"[Runner] {self.id} - Running red teaming session...")
476
+ logger.info(f"[Runner] {self.id} - Running red teaming session...")
454
477
  self.current_operation = Session(
455
478
  self.id,
456
479
  RunnerType.REDTEAM,
@@ -471,6 +494,6 @@ class Runner:
471
494
  # After completion, reset current_operation to None within the lock
472
495
  async with self.current_operation_lock:
473
496
  self.current_operation = None
474
- print(f"[Runner] {self.id} - Red teaming run completed.")
497
+ logger.info(f"[Runner] {self.id} - Red teaming run completed.")
475
498
 
476
499
  return red_teaming_results
moonshot/src/runs/run.py CHANGED
@@ -13,6 +13,10 @@ from moonshot.src.runs.run_status import RunStatus
13
13
  from moonshot.src.storage.db_interface import DBInterface
14
14
  from moonshot.src.storage.storage import Storage
15
15
  from moonshot.src.utils.import_modules import get_instance
16
+ from moonshot.src.utils.log import configure_logger
17
+
18
+ # Create a logger for this module
19
+ logger = configure_logger(__name__)
16
20
 
17
21
 
18
22
  class Run:
@@ -164,7 +168,7 @@ class Run:
164
168
  Returns:
165
169
  None
166
170
  """
167
- print("[Run] Cancelling run...")
171
+ logger.warning("[Run] Cancelling run...")
168
172
  self.cancel_event.set()
169
173
 
170
174
  async def run(self) -> ResultArguments | None:
@@ -184,7 +188,7 @@ class Run:
184
188
  # ------------------------------------------------------------------------------
185
189
  # Part 0: Initialise
186
190
  # ------------------------------------------------------------------------------
187
- print("[Run] Part 0: Initialising run...")
191
+ logger.debug("[Run] Part 0: Initialising run...")
188
192
  start_time = time.perf_counter()
189
193
  try:
190
194
  # Initialise the run
@@ -219,20 +223,20 @@ class Run:
219
223
  self.run_progress.notify_error(error_message)
220
224
 
221
225
  finally:
222
- print(
226
+ logger.debug(
223
227
  f"[Run] Initialise run took {(time.perf_counter() - start_time):.4f}s"
224
228
  )
225
229
 
226
230
  # ------------------------------------------------------------------------------
227
231
  # Part 1: Get asyncio running loop
228
232
  # ------------------------------------------------------------------------------
229
- print("[Run] Part 1: Loading asyncio running loop...")
233
+ logger.debug("[Run] Part 1: Loading asyncio running loop...")
230
234
  loop = asyncio.get_running_loop()
231
235
 
232
236
  # ------------------------------------------------------------------------------
233
237
  # Part 2: Load runner and result processing module
234
238
  # ------------------------------------------------------------------------------
235
- print("[Run] Part 2: Loading modules...")
239
+ logger.debug("[Run] Part 2: Loading modules...")
236
240
  start_time = time.perf_counter()
237
241
  runner_module_instance = None
238
242
  result_module_instance = None
@@ -246,14 +250,14 @@ class Run:
246
250
  except Exception as e:
247
251
  self.run_progress.notify_error(f"[Run] Module loading error: {e}")
248
252
  finally:
249
- print(
253
+ logger.debug(
250
254
  f"[Run] Module loading took {(time.perf_counter() - start_time):.4f}s"
251
255
  )
252
256
 
253
257
  # ------------------------------------------------------------------------------
254
258
  # Part 3: Run runner processing module
255
259
  # ------------------------------------------------------------------------------
256
- print("[Run] Part 3: Running runner processing module...")
260
+ logger.debug("[Run] Part 3: Running runner processing module...")
257
261
  start_time = time.perf_counter()
258
262
  runner_results = None
259
263
  try:
@@ -274,14 +278,14 @@ class Run:
274
278
  self.run_progress.notify_error(error_message)
275
279
 
276
280
  finally:
277
- print(
281
+ logger.debug(
278
282
  f"[Run] Running runner processing module took {(time.perf_counter() - start_time):.4f}s"
279
283
  )
280
284
 
281
285
  # ------------------------------------------------------------------------------
282
286
  # Part 4: Run result processing module
283
287
  # ------------------------------------------------------------------------------
284
- print("[Run] Part 4: Running result processing module...")
288
+ logger.debug("[Run] Part 4: Running result processing module...")
285
289
  start_time = time.perf_counter()
286
290
  updated_runner_results = None
287
291
  try:
@@ -301,14 +305,14 @@ class Run:
301
305
  self.run_progress.notify_error(error_message)
302
306
 
303
307
  finally:
304
- print(
308
+ logger.debug(
305
309
  f"[Run] Running result processing module took {(time.perf_counter() - start_time):.4f}s"
306
310
  )
307
311
 
308
312
  # ------------------------------------------------------------------------------
309
313
  # Part 5: Wrap up run
310
314
  # ------------------------------------------------------------------------------
311
- print("[Run] Part 5: Wrap up run...")
315
+ logger.debug("[Run] Part 5: Wrap up run...")
312
316
  return updated_runner_results
313
317
 
314
318
  def _load_module(self, arg_key: str, env_var: str):
@@ -4,6 +4,10 @@ from typing import Callable
4
4
  from moonshot.src.runs.run_arguments import RunArguments
5
5
  from moonshot.src.runs.run_status import RunStatus
6
6
  from moonshot.src.storage.storage import Storage
7
+ from moonshot.src.utils.log import configure_logger
8
+
9
+ # Create a logger for this module
10
+ logger = configure_logger(__name__)
7
11
 
8
12
 
9
13
  class RunProgress:
@@ -40,7 +44,7 @@ class RunProgress:
40
44
  error_message (str): The error message to be logged and recorded.
41
45
  """
42
46
  # Update error message
43
- print(error_message)
47
+ logger.error(error_message)
44
48
  self.run_arguments.error_messages.append(error_message)
45
49
 
46
50
  # Update progress status
@@ -108,8 +112,8 @@ class RunProgress:
108
112
  RunProgress.sql_update_run_record,
109
113
  )
110
114
  else:
111
- print(
112
- "[RunProgress] Failed to update run progress: db_instance is not initialised."
115
+ logger.warning(
116
+ "[RunProgress] Failed to update run progress: database instance is not initialised."
113
117
  )
114
118
 
115
119
  # If a callback function is provided, call it with the updated run arguments
@@ -100,6 +100,20 @@ class DBInterface:
100
100
  """
101
101
  pass
102
102
 
103
+ @abstractmethod
104
+ def delete_records_in_table(self, delete_record_sql: str) -> None:
105
+ """
106
+ This method is used to delete all records from a specified table in the database. The details of the operation
107
+ are implementation specific and should be provided by the concrete class that inherits from this abstract class.
108
+
109
+ Args:
110
+ delete_record_sql (str): The SQL query to delete all records from a table.
111
+
112
+ Returns:
113
+ None
114
+ """
115
+ pass
116
+
103
117
  @abstractmethod
104
118
  def check_database_table_exists(self, table_name: str) -> bool:
105
119
  """
@@ -22,7 +22,7 @@ class Storage:
22
22
  obj_info: dict,
23
23
  obj_extension: str,
24
24
  obj_mod_type: str = "jsonio",
25
- ) -> bool:
25
+ ) -> str:
26
26
  """
27
27
  Writes the object information to a file.
28
28
 
@@ -32,6 +32,9 @@ class Storage:
32
32
  obj_info (dict): A dictionary containing the object information.
33
33
  obj_extension (str): The file extension (e.g., 'json', 'py').
34
34
  obj_mod_type (str, optional): The module type for object serialization. Defaults to 'jsonio'.
35
+
36
+ Returns:
37
+ str: A filepath string of the object that has just been created.
35
38
  """
36
39
  if not hasattr(EnvironmentVars, obj_type):
37
40
  raise RuntimeError(
@@ -45,7 +48,11 @@ class Storage:
45
48
  Storage.get_filepath(EnvVariables.IO_MODULES.name, obj_mod_type, "py"),
46
49
  )
47
50
  if obj_mod_instance and callable(obj_mod_instance):
48
- return obj_mod_instance(obj_filepath).create_file(obj_info)
51
+ try:
52
+ obj_mod_instance(obj_filepath).create_file(obj_info)
53
+ return obj_filepath
54
+ except Exception as e:
55
+ raise e
49
56
  else:
50
57
  raise RuntimeError(
51
58
  f"Unable to get defined object module instance - {obj_mod_instance}"
@@ -479,6 +486,30 @@ class Storage:
479
486
  else:
480
487
  raise RuntimeError("Database instance is not initialised.")
481
488
 
489
+ @staticmethod
490
+ def delete_database_record_in_table(
491
+ database_instance: DBInterface, sql_delete_record: str
492
+ ) -> None:
493
+ """
494
+ Deletes records from a table in the database based on a SQL condition.
495
+
496
+ This method is used to delete records from a specific table in the database
497
+ that meet the condition specified in the SQL delete statement.
498
+ If the database instance is not initialised, it raises a RuntimeError.
499
+ Otherwise, it calls the delete_records_in_table method of the database instance with the provided SQL query.
500
+
501
+ Args:
502
+ database_instance (DBInterface): The database accessor instance.
503
+ sql_delete_record (str): The SQL query to delete records from a table.
504
+
505
+ Returns:
506
+ None
507
+ """
508
+ if database_instance:
509
+ database_instance.delete_records_in_table(sql_delete_record)
510
+ else:
511
+ raise RuntimeError("Database instance is not initialised.")
512
+
482
513
  @staticmethod
483
514
  def check_database_table_exists(
484
515
  database_instance: DBInterface, table_name: str
@@ -0,0 +1,45 @@
1
+ from typing import Any, Union
2
+
3
+
4
+ def find_keyword(
5
+ keyword: str, target: Union[str, list[str], list[list[str]], list[dict], list, dict]
6
+ ) -> Any:
7
+ """
8
+ Find the keyword in the target and return the matching elements.
9
+
10
+ Args:
11
+ keyword (str): The keyword to search for.
12
+ target: The target to search within.
13
+
14
+ Returns:
15
+ Any: The matching elements or None if no match is found.
16
+ """
17
+
18
+ # find in single string
19
+ if isinstance(target, str):
20
+ if target.find(keyword) != -1:
21
+ return target
22
+ return ""
23
+
24
+ # find in single dict
25
+ elif isinstance(target, dict):
26
+ if any(str(keyword) in str(value).lower() for value in target.values()):
27
+ return target
28
+ return {}
29
+
30
+ # find in list
31
+ elif isinstance(target, list):
32
+ # list of string
33
+ if all(isinstance(item, str) for item in target):
34
+ return [item for item in target if str(item).lower().find(keyword) != -1]
35
+
36
+ # list of dict
37
+ elif all(isinstance(item, dict) for item in target):
38
+ return [
39
+ item
40
+ for item in target
41
+ if isinstance(item, dict)
42
+ and any(str(keyword) in str(value).lower() for value in item.values())
43
+ ]
44
+
45
+ return None