Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
sql_manager.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: sql_manager.py
14# CREATION DATE: 11-10-2025
15# LAST Modified: 14:52:25 19-12-2025
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE:
21# File in charge of containing the interfacing between an sql library and the program.
22# This contains functions that simplify the process of interracting with databases as well as check for injection attempts.
23# /STOP
24# // AR
25# +==== END CatFeeder =================+
26
27"""
28
29from typing import Optional, Union, List, Dict, Tuple, Literal, Any, overload
30
31from datetime import datetime
32
33from display_tty import Disp, initialise_logger
34from redis import Redis
35try:
36 import mysql.connector as mysql_connector # type: ignore
37 _MYSQL_ERROR_TUPLE: tuple = (mysql_connector.Error,)
38except ImportError: # mysql-connector may be optional in some deployments
39 mysql_connector = None # type: ignore
40 _MYSQL_ERROR_TUPLE = tuple()
41
42from .sql_time_manipulation import SQLTimeManipulation
43from .sql_connections import SQLManageConnections
44from .sql_query_boilerplates import SQLQueryBoilerplates
45from .sql_cache_orchestrator import SQLCacheOrchestrator
46from .sql_redis_cache_rebinds import RedisCaching, RedisArgs, SQLRedisCacheRebinds
47from ..core import FinalClass
48
49# Specific exception tuple used when closing/destroying pool resources without
50# resorting to a broad Exception catch. Includes AttributeError/RuntimeError and
51# mysql connector Error when available.
52_EXC_POOL_CLOSE = (AttributeError, RuntimeError) + _MYSQL_ERROR_TUPLE
53
54
55class SQL(metaclass=FinalClass):
56 """Manage database access and provide high-level query helpers.
57
58 This class wraps a low-level connection manager and exposes convenience
59 methods for common operations. Can auto-initialize via RuntimeManager or
60 use :py:meth:`create` for explicit factory initialization.
61
62 Attributes:
63 disp (Disp): Logger instance for debugging and error reporting.
64 sql_manage_connections (SQLManageConnections): Manages database connections.
65 sql_time_manipulation (SQLTimeManipulation): Handles time-related SQL operations.
66 sql_cache_orchestrator (SQLQueryBoilerplates): Provides query boilerplates.
67 debug (bool): Debug mode flag.
68 success (int): Numeric success code.
69 error (int): Numeric error code.
70 url (str): Database URL.
71 port (int): Database port.
72 username (str): Database username.
73 password (str): Database password.
74 db_name (str): Database name.
75 """
76
77 # --------------------------------------------------------------------------
78 # STATIC CLASS VALUES
79 # --------------------------------------------------------------------------
80
81 # -------------- Initialise the logger globally in the class. --------------
82 disp: Disp = initialise_logger(__qualname__, False)
83
84 # ------------------ Runtime error for undefined elements ------------------
85 _runtime_error_string: str = "SQLCacheOrchestrator method not initialized"
86
87 # Docstring wrapper notice
88 _wrapper_notice_begin: str = "(Wrapper) Delegates to SQLQueryBoilerplates."
89 _wrapper_notice_end: str = "\n\nOriginal docstring:\n"
90
91 # --- Instance tracker to avoid creating unnecessary duplicate instances ---
92 _instace: Optional['SQL'] = None
93 _initialization_attempted: bool = False
94 _initialization_failed: bool = False
95
96 # --------------------------------------------------------------------------
97 # CONSTRUCTOR & DESTRUCTOR
98 # --------------------------------------------------------------------------
99
100 def __init__(self, url: str, port: int, username: str, password: str, db_name: str, *, success: int = 0, error: int = 84, redis: Optional[Union[Redis, RedisArgs, RedisCaching]] = None, redis_namespace: str = "sql", redis_db_label: Optional[str] = None, redis_default_ttls: Optional[Dict[str, int]] = None, auto_initialize: bool = True, debug: bool = False):
101 """Initialize the SQL facade instance.
102
103 Args:
104 url (str): Database URL.
105 port (int): Database port.
106 username (str): Database username.
107 password (str): Database password.
108 db_name (str): Database name.
109 success (int, optional): Numeric success code. Defaults to 0.
110 error (int, optional): Numeric error code. Defaults to 84.
111 redis (union[Redis, RedisArgs, RedisCaching], optional): The arguments to initialise the redis caching instance. Defaults to None.
112 redis_namespace (str, optional): The name of the namespace to use in the redis environement. Defaults to sql.
113 redis_db_label (str, optional): Optional database label to isolate keys per DB. Defaults to the `DB_NAME` environment variable or "default". Defaults to None.
114 redis_default_ttls (Dict[str, str], optional): Optional TTLs in seconds per category (`version`, `schema`, `data`, `count`). Defaults to None.
115 auto_initialize (bool, optional): If True, automatically initialize connection pool. Defaults to True.
116 debug (bool, optional): Enable debug logging. Defaults to False.
117 """
118 # ------------------------ The logging function ------------------------
119 self.dispdisp.update_disp_debug(debug)
120 self.dispdisp.log_debug("Initialising...")
121 # -------------------------- Inherited values --------------------------
122 self.debug: bool = debug
123 self.success: int = success
124 self.error: int = error
125 self.url: str = url
126 self.port: int = port
127 self.username: str = username
128 self.password: str = password
129 self.db_name: str = db_name
130
132 self.debug
133 )
134 self._get_correct_now_value = self.sql_time_manipulation.get_correct_now_value
135 self._get_correct_current_date_value = self.sql_time_manipulation.get_correct_current_date_value
136 # --------------------------- debug section ---------------------------
137 # Note: pool initialisation is Use the factory `create` to
138 # obtain a fully-initialized SQL instance.
140 url=self.url,
141 port=self.port,
142 username=self.username,
143 password=self.password,
144 db_name=self.db_name,
145 success=self.success,
146 error=self.error,
147 debug=self.debug
148 )
149
150 # ---------------------------- Time logger ----------------------------
152 self.debug
153 )
154 self._get_correct_now_value = self.sql_time_manipulation.get_correct_now_value
155 self._get_correct_current_date_value = self.sql_time_manipulation.get_correct_current_date_value
156 # ----------------------- Redis caching instance -----------------------
157 self.redis_cacher: Optional[SQLRedisCacheRebinds] = None
158 self.redis_namespace: str = redis_namespace
159 self.redis_db_label: Optional[str] = redis_db_label
160 self.redis_default_ttls: Optional[Dict[str, int]] = redis_default_ttls
161 if isinstance(redis, SQLRedisCacheRebinds):
162 self.redis_cacher = redis
163 elif isinstance(redis, RedisCaching):
165 existing_instance=redis,
166 debug=self.debug
167 )
168 elif isinstance(redis, (Redis, RedisArgs)):
170 client=redis,
171 namespace=redis_namespace,
172 db_label=redis_db_label,
173 default_ttls=redis_default_ttls
174 )
175
176 # --------------------------- debug section ---------------------------
177 # Note: pool initialisation is Use the factory `create` to
178 # obtain a fully-initialized SQL instance.
179 self.sql_manage_connections.show_connection_info()
180 # sql_cache_orchestrator will be created by the factory once the
181 # connection pool is initialised.
182 self._sql_query_boilerplate: Optional[SQLQueryBoilerplates] = None
183 self.sql_cache_orchestrator: Optional[SQLCacheOrchestrator] = None
184 self._is_initialized: bool = False
185
186 # Auto-initialize if requested (for RuntimeManager compatibility)
187 if auto_initialize:
188 self._auto_initialize()
189
190 self.dispdisp.log_debug("Initialised")
191
192 def _auto_initialize(self) -> None:
193 """Automatically initialize connection pool and query helpers.
194
195 Called during __init__ when auto_initialize=True.
196 Tracks initialization attempts to provide better error messages.
197 """
198 SQL._initialization_attempted = True
199 try:
200 assert self.sql_manage_connections is not None, "sql_manage_connections not initialized"
201 if self.sql_manage_connections.initialise_pool() != self.success:
202 SQL._initialization_failed = True
203 msg = "Failed to initialise the connection pool."
204 self.dispdisp.log_critical(msg)
205 raise RuntimeError(f"Error: {msg}")
206
207 # Create the query helper now that the pool is ready
209 sql_pool=self.sql_manage_connections,
210 success=self.success,
211 error=self.error,
212 debug=self.debug
213 )
215 sql_query_boilerplates=self._sql_query_boilerplate,
216 redis_cacher=self.redis_cacher,
217 success=self.success,
218 error=self.error,
219 debug=self.debug
220 )
221 self._is_initialized = True
222 except Exception as e:
223 SQL._initialization_failed = True
224 self.dispdisp.log_critical(f"Initialization failed: {e}")
225 raise
226
227 def __del__(self) -> None:
228 """Best-effort cleanup invoked when the instance is garbage-collected.
229
230 This releases references to internal helpers so external resources
231 can be freed by the event loop later. Avoiding inside destructors.
232 """
233 if self.sql_manage_connections is not None:
235 self.sql_manage_connections = None
236 if self.sql_time_manipulation is not None:
237 del self.sql_time_manipulation
238 self.sql_time_manipulation = None
239 if self._sql_query_boilerplate is not None:
241 self._sql_query_boilerplate = None
242 if self.sql_cache_orchestrator is not None:
244 self.sql_cache_orchestrator = None
245
246 def _ensure_initialized(self) -> None:
247 """Ensure the SQL instance is initialized.
248
249 If initialization was never attempted, try to auto-initialize.
250 If initialization was attempted but failed, raise an error.
251
252 Raises:
253 RuntimeError: If initialization has failed or cannot be performed.
254 """
255 if self._is_initialized:
256 return
257
258 if SQL._initialization_failed:
259 raise RuntimeError(
260 "SQL initialization has previously failed. Cannot perform operations on a failed instance."
261 )
262
263 if not SQL._initialization_attempted:
264 self.dispdisp.log_debug(
265 "Attempting lazy initialization", "_ensure_initialized"
266 )
267 self._auto_initialize()
268 elif not self._is_initialized:
269 raise RuntimeError(
270 "SQL instance is not properly initialized. Use auto_initialize=True or call create() factory method."
271 )
272
273 # --------------------------------------------------------------------------
274 # WRAPPER DEFINITIONS
275 # --------------------------------------------------------------------------
276
277 def datetime_to_string(self, datetime_instance: datetime, date_only: bool = False, sql_mode: bool = False) -> str:
278 """(Wrapper) Delegates to SQLTimeManipulation.datetime_to_string
279
280 Raises:
281 Runtime error if the class is not yet declared.
282
283 Original docstring:
284
285 Format a :class:`datetime` to the project's string representation.
286
287 Args:
288 datetime_instance (datetime): Datetime to format.
289 date_only (bool): When True, return only the date portion.
290 sql_mode (bool): When True, include millisecond precision suitable
291 for insertion into SQL text fields.
292
293 Raises:
294 ValueError: If ``datetime_instance`` is not a :class:`datetime`.
295
296 Returns:
297 str: Formatted date/time string.
298 """
299 if self.sql_time_manipulation is None:
300 raise RuntimeError(self._runtime_error_string)
301 return self.sql_time_manipulation.datetime_to_string(datetime_instance, date_only, sql_mode)
302
303 def string_to_datetime(self, datetime_string_instance: str, date_only: bool = False) -> datetime:
304 """(Wrapper) Delegates to SQLTimeManipulation.string_to_datetime
305
306 Raises:
307 Runtime error if the class is not yet declared.
308
309 Original docstring:
310
311 Parse a formatted date/time string into a :class:`datetime`.
312
313 Args:
314 datetime_string_instance (str): The string to parse.
315 date_only (bool): When True, parse using the date-only format.
316
317 Raises:
318 ValueError: If the input is not a string or cannot be parsed.
319
320 Returns:
321 datetime: Parsed :class:`datetime` instance.
322 """
323 if self.sql_time_manipulation is None:
324 raise RuntimeError(self._runtime_error_string)
325 return self.sql_time_manipulation.string_to_datetime(datetime_string_instance, date_only)
326
327 def is_connected(self) -> bool:
328 """(Wrapper) Delegates to SQLManageConnection.is_connected
329
330 Raises:
331 Runtime error if the class is not yet declared.
332
333 Original docstring:
334 Check if there is an active connection to the database.
335
336 Returns:
337 bool: The state of the connection.
338 """
339 if self.sql_manage_connections is None:
340 raise RuntimeError(self._runtime_error_string)
342
343 def get_correct_now_value(self) -> str:
344 """(Wrapper) Delegates to SQLTimeManipulation.get_correct_now_value
345
346 Raises:
347 Runtime error if the class is not yet declared.
348
349 Original docstring:
350
351 Return the current date/time formatted using the project's pattern.
352
353 Returns:
354 str: Formatted current date/time string.
355 """
356 if self.sql_time_manipulation is None:
357 raise RuntimeError(self._runtime_error_string)
359
361 """(Wrapper) Delegates to SQLTimeManipulation.get_correct_current_date_value
362
363 Raises:
364 Runtime error if the class is not yet declared.
365
366 Original docstring:
367
368 Return the current date formatted using the project's date-only pattern.
369
370 Returns:
371 str: Formatted current date string.
372 """
373 if self.sql_time_manipulation is None:
374 raise RuntimeError(self._runtime_error_string)
376
377 def get_database_version(self) -> Optional[Tuple[int, int, int]]:
378 """(Wrapper) Delegates to SQLQueryBoilerplates.create_table
379
380 Raises:
381 Runtime error if the class is not yet declared.
382
383 Original docstring:
384
385 Fetch and parse the database version.
386
387 Returns:
388 Optional[Tuple[Union[int, str], ...]]:
389 A tuple representing the database version, or None if the query fails.
390 """
392 if self.sql_cache_orchestrator is None:
393 raise RuntimeError(self._runtime_error_string)
395
396 def update_redis_cacher(self, redis_cacher: Optional[RedisCaching] = None) -> None:
397 """(Wrapper) Delegates to SQLQueryBoilerplates.create_table
398
399 Raises:
400 Runtime error if the class is not yet declared.
401
402 Original docstring:
403
404 Update the redis caching instance only with an initialised RedisCaching class.
405 This function has no effect if a non-initialised RedisCaching class or other arguments are passed.
406
407 Args:
408 redis_cacher (Optional[RedisCaching], optional): The initialised RedisCaching class instance. Defaults to None.
409 """
410 if self.sql_cache_orchestrator is None:
411 raise RuntimeError(self._runtime_error_string)
412 if isinstance(redis_cacher, RedisCaching) and not callable(redis_cacher):
414 existing_instance=redis_cacher
415 )
417
418 def get_table_column_names(self, table_name: str) -> Union[List[str], int]:
419 """(Wrapper) Delegates to SQLQueryBoilerplates.get_table_column_names
420
421 Raises:
422 Runtime error if the class is not yet declared.
423
424 Original docstring:
425
426 Return the list of column names for ``table_name``.
427
428 Args:
429 table_name (str): Name of the table to inspect.
430
431 Returns:
432 Union[List[str], int]: List of column names on success, or
433 ``self.error`` on failure.
434 """
435 if self.sql_cache_orchestrator is None:
436 raise RuntimeError(self._runtime_error_string)
437 return self.sql_cache_orchestrator.get_table_column_names(table_name)
438
439 def get_table_names(self) -> Union[int, List[str]]:
440 """(Wrapper) Delegates to SQLQueryBoilerplates.get_table_names
441
442 Raises:
443 Runtime error if the class is not yet declared.
444
445 Original docstring:
446
447 Return a list of non-internal table names in the database.
448
449 Returns:
450 Union[int, List[str]]: List of table names or ``self.error`` on failure.
451 """
452 if self.sql_cache_orchestrator is None:
453 raise RuntimeError(self._runtime_error_string)
455
456 def get_triggers(self) -> Union[int, Dict[str, str]]:
457 """(Wrapper) Delegates to SQLQueryBoilerplates.get_triggers
458
459 Raises:
460 Runtime error if the class is not yet declared.
461
462 Original docstring:
463
464 Return a dictionary of all triggers and their SQL definitions.
465
466 Returns:
467 Union[int, Dict[str, str]]: Dict of {trigger_name: sql_definition}, or ``self.error``.
468 """
469 if self.sql_cache_orchestrator is None:
470 raise RuntimeError(self._runtime_error_string)
472
473 def get_trigger(self, trigger_name: str) -> Union[int, str]:
474 """(Wrapper) Delegates to SQLQueryBoilerplates.get_trigger
475
476 Raises:
477 Runtime error if the class is not yet declared.
478
479 Original docstring:
480
481 Return a dictionary of all triggers and their SQL definitions.
482
483 Returns:
484 Union[int, Dict[str, str]]: Dict of {trigger_name: sql_definition}, or ``self.error``.
485 """
486 if self.sql_cache_orchestrator is None:
487 raise RuntimeError(self._runtime_error_string)
488 return self.sql_cache_orchestrator.get_trigger(trigger_name)
489
490 def get_trigger_names(self) -> Union[int, List[str]]:
491 """(Wrapper) Delegates to SQLQueryBoilerplates.get_trigger_names
492
493 Raises:
494 Runtime error if the class is not yet declared.
495
496 Original docstring:
497
498 Return a list of non-internal trigger names in the database.
499
500 Returns:
501 Union[int, List[str]]: List of trigger names, or ``self.error`` on failure.
502 """
503 if self.sql_cache_orchestrator is None:
504 raise RuntimeError(self._runtime_error_string)
506
507 def describe_table(self, table: str) -> Union[int, List[Any]]:
508 """(Wrapper) Delegates to SQLQueryBoilerplates.describe_table
509
510 Raises:
511 Runtime error if the class is not yet declared.
512
513 Original docstring:
514
515 Fetch the schema description for a table.
516
517 This returns rows similar to SQLite's PRAGMA table_info but is
518 transformed so the first element is the column name (to remain
519 compatible with previous MySQL-style DESCRIBE results).
520
521 Args:
522 table (str): Name of the table to describe.
523
524 Raises:
525 RuntimeError: On critical SQLite errors (re-raised as RuntimeError).
526
527 Returns:
528 Union[int, List[Any]]: Transformed description rows on success,
529 or ``self.error`` on failure.
530 """
531 if self.sql_cache_orchestrator is None:
532 raise RuntimeError(self._runtime_error_string)
533 return self.sql_cache_orchestrator.describe_table(table)
534
535 def create_table(self, table: str, columns: List[Tuple[str, str]]) -> int:
536 """(Wrapper) Delegates to SQLQueryBoilerplates.create_table
537
538 Raises:
539 Runtime error if the class is not yet declared.
540
541 Original docstring:
542
543 Create a new table in the SQLite database.
544
545 Args:
546 table (str): Name of the new table.
547 columns (List[Tuple[str, str]]): List of (column_name, column_type) pairs.
548
549 Returns:
550 int: ``self.success`` on success, or ``self.error`` on failure.
551
552 Example:
553 Example usage to create a basic ``users`` table:
554
555 .. code-block:: python
556
557 # Define the table name and column definitions
558 table_name = "users"
559 columns = [
560 ("id", "INTEGER PRIMARY KEY AUTOINCREMENT"),
561 ("username", "TEXT NOT NULL UNIQUE"),
562 ("email", "TEXT NOT NULL"),
563 ("created_at", "DATETIME DEFAULT CURRENT_TIMESTAMP")
564 ]
565
566 # Create the table ronously
567 result = self.create_table(table_name, columns)
568
569 # Check if the operation succeeded
570 if result == self.success:
571 print(f"Table '{table_name}' created successfully.")
572 else:
573 print(f"Failed to create table '{table_name}'.")
574
575 Notes:
576 - This method automatically checks for SQL injection attempts using :class:`SQLInjection` before executing the query.
577 - Single quotes in table or column names are escaped defensively.
578 - The query uses ``CREATE TABLE IF NOT EXISTS`` to avoid errors if the table already exists.
579 """
580 if self.sql_cache_orchestrator is None:
581 raise RuntimeError(self._runtime_error_string)
582 return self.sql_cache_orchestrator.create_table(table, columns)
583
584 def create_trigger(self, trigger_name: str, table_name: str, timing_event: str, body: str) -> int:
585 """(Wrapper) Delegates to SQLQueryBoilerplates.insert_trigger
586
587 Raises:
588 Runtime error if the class is not yet declared.
589
590 Original docstring:
591
592 Insert (create) a new SQL trigger into a MySQL or MariaDB database.
593
594 Args:
595 trigger_name (str): The name of the trigger to create.
596 table_name (str): The name of the table the trigger is being applied to.
597 timing_event (str): The rule when the event is to be triggered. e.g., 'BEFORE INSERT'.
598 body (str): The full SQL CREATE TRIGGER statement.
599
600 Returns:
601 int: ``self.success`` on success, or ``self.error`` on failure.
602 """
603 if self.sql_cache_orchestrator is None:
604 raise RuntimeError(self._runtime_error_string)
605 return self.sql_cache_orchestrator.insert_trigger(trigger_name, table_name, timing_event, body)
606
607 def insert_trigger(self, trigger_name: str, table_name: str, timing_event: str, body: str) -> int:
608 """(Wrapper) Delegates to SQLQueryBoilerplates.insert_trigger
609
610 Raises:
611 Runtime error if the class is not yet declared.
612
613 Original docstring:
614
615 Insert (create) a new SQL trigger into a MySQL or MariaDB database.
616
617 Args:
618 trigger_name (str): The name of the trigger to create.
619 table_name (str): The name of the table the trigger is being applied to.
620 timing_event (str): The rule when the event is to be triggered. e.g., 'BEFORE INSERT'.
621 body (str): The full SQL CREATE TRIGGER statement.
622
623 Returns:
624 int: ``self.success`` on success, or ``self.error`` on failure.
625 """
626 if self.sql_cache_orchestrator is None:
627 raise RuntimeError(self._runtime_error_string)
628 return self.sql_cache_orchestrator.insert_trigger(trigger_name, table_name, timing_event, body)
629
630 def insert_data_into_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], column: Union[List[str], None] = None) -> int:
631 """(Wrapper) Delegates to SQLQueryBoilerplates.insert_data_into_table
632
633 Raises:
634 Runtime error if the class is not yet declared.
635
636 Original docstring:
637
638 Insert one or multiple rows into ``table``.
639
640 Args:
641 table (str): Table name.
642 data (Union[List[List[str]], List[str]]): Row data. Either a
643 single row (List[str]) or a list of rows (List[List[str]]).
644 column (List[str] | None): Optional list of columns to insert into.
645
646 Returns:
647 int: ``self.success`` on success or ``self.error`` on failure.
648 """
649 if self.sql_cache_orchestrator is None:
650 raise RuntimeError(self._runtime_error_string)
651 return self.sql_cache_orchestrator.insert_data_into_table(table, data, column)
652
653 @overload
655 self,
656 table: str,
657 column: Union[str, List[str]],
658 where: Union[str, List[str]] = "",
659 beautify: Literal[True] = True,
660 ) -> Union[int, List[Dict[str, Any]]]: ...
661
662 @overload
664 self,
665 table: str,
666 column: Union[str, List[str]],
667 where: Union[str, List[str]] = "",
668 beautify: Literal[False] = False,
669 ) -> Union[int, List[Tuple[Any, ...]]]: ...
670
671 def get_data_from_table(self, table: str, column: Union[str, List[str]], where: Union[str, List[str]] = "", beautify: bool = True) -> Union[int, Union[List[Dict[str, Any]], List[Tuple[Any, ...]]]]:
672 """(Wrapper) Delegates to SQLQueryBoilerplates.get_data_from_table
673
674 Raises:
675 Runtime error if the class is not yet declared.
676
677 Original docstring:
678
679 Query rows from ``table`` and optionally return them in a beautified form.
680
681 Args:
682 table (str): Table name.
683 column (Union[str, List[str]]): Column name(s) or '*' to select.
684 where (Union[str, List[str]], optional): WHERE clause or list of
685 conditions. Defaults to empty string.
686 beautify (bool, optional): If True, convert rows to list of dicts
687 keyed by column names. Defaults to True.
688
689 Returns:
690 Union[int, List[Dict[str, Any]], List[Tuple[str, Any]]]: Beautified list of Dictionaries on success and if beautify is True, otherwise, a list of tuples is beautify is set to False, or ``self.error`` on failure.
691 """
692 if self.sql_cache_orchestrator is None:
693 raise RuntimeError(self._runtime_error_string)
694 return self.sql_cache_orchestrator.get_data_from_table(table, column, where, beautify)
695
696 def get_table_size(self, table: str, column: Union[str, List[str]], where: Union[str, List[str]] = "") -> int:
697 """(Wrapper) Delegates to SQLQueryBoilerplates.get_table_size
698
699 Raises:
700 Runtime error if the class is not yet declared.
701
702 Original docstring:
703
704 Return the number of rows matching the optional WHERE clause.
705
706 Args:
707 table (str): Table name.
708 column (Union[str, List[str]]): Column to COUNT over (often '*').
709 where (Union[str, List[str]], optional): WHERE clause or list of
710 conditions. Defaults to empty string.
711
712 Returns:
713 int: Number of matching rows, or ``SCONST.GET_TABLE_SIZE_ERROR`` on error.
714 """
715 if self.sql_cache_orchestrator is None:
716 raise RuntimeError(self._runtime_error_string)
717 return self.sql_cache_orchestrator.get_table_size(table, column, where)
718
719 def update_data_in_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], column: List[str], where: Union[str, List[str]] = "") -> int:
720 """(Wrapper) Delegates to SQLQueryBoilerplates.update_data_in_table
721
722 Raises:
723 Runtime error if the class is not yet declared.
724
725 Original docstring:
726
727 Update rows in ``table`` matching ``where`` with values from ``data``.
728
729 Args:
730 table (str): Table name.
731 data (List[str]): New values to set.
732 column (List): Column names corresponding to data.
733 where (Union[str, List[str]], optional): WHERE clause or list of
734 conditions. Defaults to empty string.
735
736 Returns:
737 int: ``self.success`` on success, or ``self.error`` on failure.
738 """
739 if self.sql_cache_orchestrator is None:
740 raise RuntimeError(self._runtime_error_string)
741 return self.sql_cache_orchestrator.update_data_in_table(table, data, column, where)
742
743 def insert_or_update_data_into_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], columns: Union[List[str], None] = None) -> int:
744 """(Wrapper) Delegates to SQLQueryBoilerplates.insert_or_update_data_into_table
745
746 Raises:
747 Runtime error if the class is not yet declared.
748
749 Original docstring:
750
751 Insert new rows or update existing rows for ``table``.
752
753 This method determines column names if not provided and delegates
754 to the appropriate INSERT/UPDATE boilerplate.
755
756 Args:
757 table (str): Table name.
758 data (Union[List[List[str]], List[str]]): Data to insert or update.
759 columns (List[str] | None, optional): Column names. Defaults to None.
760
761 Returns:
762 int: ``self.success`` on success, or ``self.error`` on error.
763 """
764 if self.sql_cache_orchestrator is None:
765 raise RuntimeError(self._runtime_error_string)
766 return self.sql_cache_orchestrator.insert_or_update_data_into_table(table, data, columns)
767
768 def insert_or_update_trigger(self, trigger_name: str, table_name: str, timing_event: str, body: str) -> int:
769 """(Wrapper) Delegates to SQLQueryBoilerplates.insert_or_update_trigger
770
771 Raises:
772 Runtime error if the class is not yet declared.
773
774 Original docstring:
775
776 Insert (create) or update an SQL trigger into a MySQL or MariaDB database.
777
778 Args:
779 trigger_name (str): The name of the trigger to create.
780 table_name (str): The name of the table the trigger is being applied to.
781 timing_event (str): The rule when the event is to be triggered. e.g., 'BEFORE INSERT'.
782 body (str): The full SQL CREATE TRIGGER statement.
783
784 Returns:
785 int: ``self.success`` on success, or ``self.error`` on failure.
786 """
787 if self.sql_cache_orchestrator is None:
788 raise RuntimeError(self._runtime_error_string)
789 return self.sql_cache_orchestrator.insert_or_update_trigger(trigger_name, table_name, timing_event, body)
790
791 def remove_data_from_table(self, table: str, where: Union[str, List[str]] = "") -> int:
792 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_data_from_table
793
794 Raises:
795 Runtime error if the class is not yet declared.
796
797 Original docstring:
798
799 Delete rows from ``table`` matching ``where``.
800
801 Args:
802 table (str): Table name to delete rows from.
803 where (Union[str, List[str]], optional): WHERE clause or list of
804 conditions to filter rows. If empty, all rows are deleted.
805
806 Returns:
807 int: ``self.success`` on success or ``self.error`` on failure.
808 """
809 if self.sql_cache_orchestrator is None:
810 raise RuntimeError(self._runtime_error_string)
811 return self.sql_cache_orchestrator.remove_data_from_table(table, where)
812
813 def drop_data_from_table(self, table: str) -> int:
814 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_data_from_table
815
816 Raises:
817 Runtime error if the class is not yet declared.
818
819 Original docstring:
820
821 Delete rows from ``table`` matching ``where``.
822
823 Args:
824 table (str): Table name to delete rows from.
825 where (Union[str, List[str]], optional): WHERE clause or list of
826 conditions to filter rows. If empty, all rows are deleted.
827
828 Returns:
829 int: ``self.success`` on success or ``self.error`` on failure.
830 """
831 if self.sql_cache_orchestrator is None:
832 raise RuntimeError(self._runtime_error_string)
833 # alias for remove_data_from_table to preserve API consistency
835
836 def remove_table(self, table: str) -> int:
837 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_table
838
839 Raises:
840 Runtime error if the class is not yet declared.
841
842 Original docstring:
843
844 Drop/Remove (delete) a table from the SQLite database.
845
846 Args:
847 table (str): Name of the table to drop.
848
849 Returns:
850 int: ``self.success`` on success, or ``self.error`` on failure.
851
852 Example:
853 Example usage to drop the ``users`` table:
854
855 .. code-block:: python
856
857 table_name = "users"
858 result = self.drop_table(table_name)
859
860 if result == self.success:
861 print(f"Table '{table_name}' dropped successfully.")
862 else:
863 print(f"Failed to drop table '{table_name}'.")
864
865 Notes:
866 - The method performs SQL injection detection on the table name.
867 - If the table does not exist, no error is raised (uses ``DROP TABLE IF EXISTS`` internally).
868 """
869 if self.sql_cache_orchestrator is None:
870 raise RuntimeError(self._runtime_error_string)
871 return self.sql_cache_orchestrator.remove_table(table)
872
873 def drop_table(self, table: str) -> int:
874 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_table
875
876 Raises:
877 Runtime error if the class is not yet declared.
878
879 Original docstring:
880
881 Drop/Remove (delete) a table from the SQLite database.
882
883 Args:
884 table (str): Name of the table to drop.
885
886 Returns:
887 int: ``self.success`` on success, or ``self.error`` on failure.
888
889 Example:
890 Example usage to drop the ``users`` table:
891
892 .. code-block:: python
893
894 table_name = "users"
895 result = self.drop_table(table_name)
896
897 if result == self.success:
898 print(f"Table '{table_name}' dropped successfully.")
899 else:
900 print(f"Failed to drop table '{table_name}'.")
901
902 Notes:
903 - The method performs SQL injection detection on the table name.
904 - If the table does not exist, no error is raised (uses ``DROP TABLE IF EXISTS`` internally).
905 """
906 if self.sql_cache_orchestrator is None:
907 raise RuntimeError(self._runtime_error_string)
908 return self.sql_cache_orchestrator.remove_table(table)
909
910 def remove_trigger(self, trigger_name: str) -> int:
911 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_trigger
912
913 Raises:
914 Runtime error if the class is not yet declared.
915
916 Original docstring:
917
918 Drop/Remove an existing SQL trigger if it exists.
919
920 Args:
921 trigger_name (str): Name of the trigger to drop.
922
923 Returns:
924 int: ``self.success`` on success, or ``self.error`` on error.
925 """
926 if self.sql_cache_orchestrator is None:
927 raise RuntimeError(self._runtime_error_string)
928 return self.sql_cache_orchestrator.remove_trigger(trigger_name)
929
930 def drop_trigger(self, trigger_name: str) -> int:
931 """(Wrapper) Delegates to SQLQueryBoilerplates.remove_trigger
932
933 Raises:
934 Runtime error if the class is not yet declared.
935
936 Original docstring:
937
938 Drop/Remove an existing SQL trigger if it exists.
939
940 Args:
941 trigger_name (str): Name of the trigger to drop.
942
943 Returns:
944 int: ``self.success`` on success, or ``self.error`` on error.
945 """
946 if self.sql_cache_orchestrator is None:
947 raise RuntimeError(self._runtime_error_string)
948 return self.sql_cache_orchestrator.remove_trigger(trigger_name)
949
950 # --------------------------------------------------------------------------
951 # FACTORY + CLEANUP
952 # --------------------------------------------------------------------------
953 @classmethod
954 def create(cls, url: str, port: int, username: str, password: str, db_name: str, success: int = 0, error: int = 84, debug: bool = False) -> 'SQL':
955 """factory to create and initialise an SQL instance.
956
957 This factory completes ronous initialisation steps that the
958 synchronous constructor cannot perform (notably the connection
959 pool initialisation). After this call returns the instance is ready
960 for usage and convenience callables are bound on the
961 instance.
962
963 Args:
964 url (str): DB host or file path (for sqlite this is a filename).
965 port (int): DB port (unused for sqlite but retained for API
966 compatibility).
967 username (str): DB username (unused for sqlite).
968 password (str): DB password (unused for sqlite).
969 db_name (str): Database name or sqlite filename.
970 success (int, optional): numeric success code used across the
971 sql helpers. Defaults to 0.
972 error (int, optional): numeric error code used across the sql
973 helpers. Defaults to 84.
974 debug (bool, optional): enable debug logging. Defaults to False.
975
976 Returns:
977 SQL: Initialized SQL instance ready for operations.
978
979 Raises:
980 RuntimeError: If the connection pool cannot be initialised.
981
982 Example:
983 sql = SQL.create('db.sqlite', 0, '', '', 'db.sqlite')
984 sql.get_data_from_table('my_table')
985 """
986
987 self = cls(
988 url,
989 port,
990 username,
991 password,
992 db_name,
993 success=success,
994 error=error,
995 auto_initialize=False, # Don't auto-init, we'll do it manually
996 debug=debug
997 )
998 # Manually initialize the connection pool
999 # static checkers see `sql_manage_connections` as Optional; assert
1000 # it's available to narrow the type for the following calls.
1001 assert self.sql_manage_connections is not None
1002 assert self.dispdisp is not None
1003 if self.sql_manage_connections.initialise_pool() != self.success:
1004 msg = "Failed to initialise the connection pool."
1005 self.dispdisp.log_critical(msg)
1006 raise RuntimeError(f"Error: {msg}")
1007 # Create the query helper now that the pool is ready
1009 sql_pool=self.sql_manage_connections,
1010 success=self.success,
1011 error=self.error,
1012 debug=self.debug
1013 )
1015 sql_query_boilerplates=self._sql_query_boilerplate,
1016 redis_cacher=self.redis_cacher,
1017 success=self.success,
1018 error=self.error,
1019 debug=self.debug
1020 )
1021 self._is_initialized = True
1022 return self
1023
1024 def close(self) -> None:
1025 """Cleanly close resources like the connection pool."""
1026 if self.sql_manage_connections is not None:
1027 try:
1028 status = self.sql_manage_connections.destroy_pool()
1029 except _EXC_POOL_CLOSE as e: # type: ignore[misc]
1030 if self.dispdisp:
1031 self.dispdisp.log_error(
1032 f"Error while closing connection pool: {e}"
1033 )
1034 else:
1035 if hasattr(self, "disp") and self.dispdisp and status != self.success:
1036 self.dispdisp.log_error(
1037 "Destroying the connection pool returned an error status."
1038 )
1039 # Clean up all references
1040 self.sql_manage_connections = None
1041 self._sql_query_boilerplate = None
1042 self.sql_cache_orchestrator = None
1043 self.sql_time_manipulation = None
int create_table(self, str table, List[Tuple[str, str]] columns)
'SQL' create(cls, str url, int port, str username, str password, str db_name, int success=0, int error=84, bool debug=False)
int drop_data_from_table(self, str table)
Union[int, List[str]] get_table_names(self)
int update_data_in_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, List[str] column, Union[str, List[str]] where="")
Optional[SQLCacheOrchestrator] sql_cache_orchestrator
Optional[Tuple[int, int, int]] get_database_version(self)
Union[int, List[Dict[str, Any]]] get_data_from_table(self, str table, Union[str, List[str]] column, Union[str, List[str]] where="", Literal[True] beautify=True)
Optional[Dict[str, int]] redis_default_ttls
Union[int, List[Any]] describe_table(self, str table)
int remove_trigger(self, str trigger_name)
Union[int, str] get_trigger(self, str trigger_name)
Union[List[str], int] get_table_column_names(self, str table_name)
datetime string_to_datetime(self, str datetime_string_instance, bool date_only=False)
str datetime_to_string(self, datetime datetime_instance, bool date_only=False, bool sql_mode=False)
int insert_or_update_data_into_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, Union[List[str], None] columns=None)
None update_redis_cacher(self, Optional[RedisCaching] redis_cacher=None)
int create_trigger(self, str trigger_name, str table_name, str timing_event, str body)
int get_table_size(self, str table, Union[str, List[str]] column, Union[str, List[str]] where="")
int remove_data_from_table(self, str table, Union[str, List[str]] where="")
Optional[SQLQueryBoilerplates] _sql_query_boilerplate
Optional[SQLRedisCacheRebinds] redis_cacher
Union[int, Dict[str, str]] get_triggers(self)
int insert_data_into_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, Union[List[str], None] column=None)
Union[int, List[str]] get_trigger_names(self)
__init__(self, str url, int port, str username, str password, str db_name, *, int success=0, int error=84, Optional[Union[Redis, RedisArgs, RedisCaching]] redis=None, str redis_namespace="sql", Optional[str] redis_db_label=None, Optional[Dict[str, int]] redis_default_ttls=None, bool auto_initialize=True, bool debug=False)
int insert_or_update_trigger(self, str trigger_name, str table_name, str timing_event, str body)
int insert_trigger(self, str trigger_name, str table_name, str timing_event, str body)
int drop_trigger(self, str trigger_name)