Source code for _dbi_oracle

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
:Purpose:   This module contains the library's *Oracle* database methods
            and attribute accessors; which are a specialised version of
            the :class:`_dbi_base._DBIBase` class methods.

:Platform:  Linux/Windows | Python 3.10+
:Developer: J Berendt
:Email:     support@s3dev.uk

:Comments:  n/a

:Example:

    For class-specific usage examples, please refer to the docstring
    for the following classes:

        - :class:`_DBIOracle`

"""
# pylint: disable=wrong-import-order
# Silence the spurious IDE-based error.
# pylint: disable=import-error

import cx_Oracle
import pandas as pd
from utils4.reporterror import reporterror
from utils4.user_interface import ui
# locals
from _dbi_base import _DBIBase


[docs] class _DBIOracle(_DBIBase): """This *private* class holds the methods and properties which are used for accessing Oracle databases. Note: This class is *not* designed to be interacted with directly. Rather, please use the :class:`database.DBInterface` class instead, as the proper interface class has an automatic switch for database interfaces, based on the ``sqlalchemy.Engine`` object which is created from the connection string. Args: connstr (str): The database-specific SQLAlchemy connection string. :Example Use: This low-level generalised class is designed to be inherited by the calling/wrapping class as:: >>> from dblib.database import DBInterface class MyDB(DBInterface): def __init__(self, connstr: str): super().__init__(connstr=('oracle+cx_oracle://' '<user>:<pwd>' '@(DESCRIPTION=(ADDRESS=' '(PROTOCOL=TCP)' '(HOST=<host>)' '(PORT=<port>))' '(CONNECT_DATA=' '(SERVICE_NAME=<svcname>)))')) """ _ERROR_NI = ('Due to restrictions on the development environment, ' 'this method is currently not implemented.') # The __init__ method is implemented in the parent class.
[docs] def call_procedure(self, proc: str, params: list | tuple = None, return_status: bool=False) -> pd.DataFrame | tuple[pd.DataFrame | bool]: """Call a stored procedure, and return as a DataFrame. Args: proc (str): Name of the stored procedure to call. params (list | tuple, optional): A list (or tuple) of parameters to pass into the procedure. Defaults to None. return_status (bool, optional): Return the method's success status. Defaults to False. Returns: pd.DataFrame | tuple[pd.DataFrame | bool]: If the ``return_status`` argument is True, a tuple of the data and the method's return status is returned as:: (df, status) Otherwise, only the data is returned, as a pd.DataFrame. """ df = pd.DataFrame() success = False try: with self.engine.connect() as conn: cur = conn.connection.cursor() refcur = conn.connection.cursor() cur.callproc(proc, params + [refcur]) df = self._result_to_df__refcursor(refcur=refcur) cur.close() refcur.close() success = not df.empty except cx_Oracle.DatabaseError as err: msg = f'Error occurred while running the USP: {proc}.' self._report_cxo_error(msg=msg, error=err) except Exception as err: reporterror(error=err) return (df, success) if return_status else df
[docs] def call_procedure_update(self): """Call an *update* or *insert* stored procedure. Warning: Due to the restrictions on the Oracle development environment, database UPDATE-like methods are not allowed. Therefore, this method is *currently* not implemented. Raises: NotImplementedError: Warn the user that this method is currently not implemented. """ raise NotImplementedError(self._ERROR_NI)
[docs] def call_procedure_update_many(self): """Call an *update* or *insert* stored procedure for an iterable. Warning: Due to the restrictions on the Oracle development environment, database UPDATE-like methods are not allowed. Therefore, this method is *currently* not implemented. Raises: NotImplementedError: Warn the user that this method is currently not implemented. """ raise NotImplementedError(self._ERROR_NI)
[docs] def call_procedure_update_raw(self): """Call an *update* or *insert* stored procedure, without error handling. Warning: Due to the restrictions on the Oracle development environment, database UPDATE-like methods are not allowed. Therefore, this method is *currently* not implemented. Raises: NotImplementedError: Warn the user that this method is currently not implemented. """ raise NotImplementedError(self._ERROR_NI)
[docs] def table_exists(self, table_name: str, verbose: bool=False) -> bool: """Using the ``engine`` object, test if the given table exists. Args: table_name (str): Name of the table to test. verbose (bool, optional): Print a message if the table does not exist. Defaults to False. Note: As most Oracle objects are UPPERCASE, the table name is converted to upper case before being passed into the query. Returns: bool: True if the given table exists, otherwise False. """ params = {'table_name': table_name.upper()} stmt = 'select count(*) from all_tables where table_name = :table_name' exists = bool(self.execute_query(stmt, params=params, raw=True)[0][0]) if (not exists) & verbose: msg = f'Table does not exist: {table_name}, for user {self._engine.url.username}.' ui.print_warning(text=msg) return exists
[docs] def _report_cxo_error(self, msg: str, error: cx_Oracle.DatabaseError): """Report cx_Oracle error to the terminal. Args: msg (str): Additional error to be displayed. This message will be automatically prefixed with '[DatabaseError]: ' error (cx_Oracle.DatabaseError): Caught error object from the try/except block. """ msg = f'{self._PREFIX} {msg}' errr = f'- Error: {error.args[0].message}' ui.print_alert(text=msg) ui.print_alert(text=errr)
[docs] @staticmethod def _result_to_df__refcursor(refcur: cx_Oracle.Cursor) -> pd.DataFrame: """Convert a ``cx_Oracle.Cursor`` object to a DataFrame. If the cursor did not return results, an empty DataFrame containing the column names only, is returned. Args: refcur (cx_Oracle.Cursor): Object to be converted. Returns: pd.DataFrame: A ``pandas.DataFrame`` object containing the cursor's data. """ return pd.DataFrame(refcur, columns=[i[0] for i in refcur.description])