-
Notifications
You must be signed in to change notification settings - Fork 0
draft #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
michaelkerry
wants to merge
37
commits into
main
Choose a base branch
from
development
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
draft #1
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
eabb6b0
update .gitignore
mkerry-fas d1fffc2
draft
mkerry-fas 2638a4b
update docs; refactor
mkerry-fas b01f923
rename for clarity
mkerry-fas a8023ac
cleanup
mkerry-fas 7526428
rename for clarity
mkerry-fas df48b87
revise logger setup
mkerry-fas 8b7da32
revise logger setup
mkerry-fas db21323
revise logger setup
mkerry-fas 5c9a7c4
fix bug
mkerry-fas fc74b9c
add interface
mkerry-fas d6b2c86
refactor init params and handling
mkerry-fas f51d83c
cleanup
mkerry-fas a7e27dc
add cleanup method
mkerry-fas 701d5b2
rename Database enum to DatabaseType; add sql alchemy
mkerry-fas c1d0628
update setup
mkerry-fas deb22af
update version
mkerry-fas 29c3b16
rename
mkerry-fas 549ca1a
update handling of execute_query
mkerry-fas eaa53f6
update documentation
mkerry-fas 62641f9
update documentation
mkerry-fas f92e138
update documentation
mkerry-fas a261ec4
update documentation formatting
mkerry-fas 197e803
update documentation formatting
mkerry-fas c0d30cc
update documentation formatting
mkerry-fas 52a8db3
update documentation formatting
mkerry-fas dec7124
update documentation
mkerry-fas 38009ea
update documentation
mkerry-fas e8d1027
update pylog version
mkerry-fas c5b9294
update pylog version url
mkerry-fas 71eeed0
move creation of logger
mkerry-fas 32eadeb
fix line spacing
mkerry-fas e3bc452
update docs
mkerry-fas ce67314
cleanup per PR feedback
mkerry-fas aa683ce
add import
mkerry-fas 1a9ea41
update readme
mkerry-fas 1318bb9
Update README.md
michaelkerry File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| .idea | ||
| # Byte-compiled / optimized / DLL files | ||
| __pycache__/ | ||
| *.py[cod] | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,91 @@ | ||
| # pydb | ||
| # pydb | ||
|
|
||
| a tool for facilitating connection to databases with python and performing basic operations | ||
|
|
||
| ## Purpose and intended audience | ||
|
|
||
| For many APIs the responses are based upon data residing in a relational database; this module provides a standardized | ||
| way for python programmers to access a database, via straight SQL calls to `execute_query()` and `execute_update()` and | ||
| also permits users to access SQLAlchemy for ORM-based interactions with database objects. | ||
|
|
||
| ## Requirements | ||
|
|
||
| python >= 3.7 | ||
| (see setup.py for additional packages) | ||
|
|
||
| ## Installation and setup | ||
|
|
||
| In a suitable python3 (>=3.7) virtual env, using pip: | ||
| ``` | ||
| pip install https://github.com/huit/pydb/archive/refs/tags/v0.0.2.tar.gz#egg=pydb | ||
| # import the module for the specific type of db you'd like to use | ||
| from pydb.oracle_db import OracleDB | ||
| ``` | ||
| * creating an OracleDB instance requires host, port, service, user, pwd. | ||
| * other db types may have other requirements - see specific module for details | ||
| * logging_level is optional, and will default to `logging.CRITICAL` | ||
| * logging_format is optional, and will default to pylog default formatting | ||
| * see https://github.com/huit/pylog for details | ||
|
|
||
| ``` | ||
| db = OracleDb(host="valid_host", port=8003, service="SERVICE_NAME", user="username", pwd="pwd") | ||
| # where 8003 is a valid port | ||
| ``` | ||
| ## Basic operations | ||
|
|
||
| create_connection() | ||
| provides a connection to the db host for more 'direct' access | ||
|
|
||
| get_session() | ||
| Specific to SQL Alchemy; allows interaction with SQL Alchemy entities | ||
| see https://docs.sqlalchemy.org/en/14/orm/session.html?highlight=session#module-sqlalchemy.orm.session | ||
|
|
||
| execute_query(self, query_string: str, args: dict = None) -> list | ||
| executes a sql query, return a list of dictionaries representing rows | ||
| 'args' represents a dictionary of parameterized values for the query; see examples below | ||
|
|
||
| execute_update(self, query_string, args: dict = None) | ||
| used to execute an insert, update, or delete sql statement | ||
| 'args' represents a dictionary of parameterized values for the query; see examples below | ||
|
|
||
| health_check() | ||
| Performs a basic query against the db to ensure connectivity | ||
|
|
||
| cleanup() | ||
| Attempts to release any 'live' objects/connections to the host - to be run before exiting program | ||
|
|
||
| ## Examples | ||
|
|
||
| Given a valid connection, and a table called `EMP`... | ||
|
|
||
| To query the `EMP` table for all records: | ||
| ``` | ||
| result = db.execute_query("select * from emp") | ||
| ``` | ||
| To query the `EMP` table for a specific record: | ||
| ``` | ||
| result = db.execute_query("select * from emp where ename= :ename", {'ename':'JOHNSON'}) | ||
| ``` | ||
| Results for the individual rows would be in the following form: | ||
| ``` | ||
| {'EMPNO': 7935, 'ENAME': 'JOHNSON', 'JOB': 'CLERK', 'MGR': 7839, 'HIREDATE': datetime.datetime(1981, 5, 1, 0, 0), 'SAL': 2850.0, 'COMM': None, 'DEPTNO': 30} | ||
| ``` | ||
| Row results may vary somewhat depending on the exact module... e.g., for SqlAlchemyOracleDB the following would be received: | ||
| ``` | ||
| {'empno': 7935, 'ename': 'JOHNSON', 'job': 'CLERK', 'mgr': 7839, 'hiredate': datetime.datetime(1981, 5, 1, 0, 0), 'sal': Decimal('2850'), 'comm': None, 'deptno': 30} | ||
| ``` | ||
|
|
||
| ### integrated example | ||
| ``` | ||
| pip install https://github.com/huit/pydb/archive/refs/tags/v0.0.2.tar.gz#egg=pydb | ||
|
|
||
| from pydb.oracle_db import OracleDB | ||
| db = OracleDb(host="valid_host", port=8003, service="SERVICE_NAME", user="username", pwd="pwd") | ||
|
|
||
| result = db.execute_query("select * from emp where ename= :ename", {'ename':'JOHNSON'}) | ||
| print(result) | ||
| ``` | ||
| produces | ||
| ``` | ||
| {'EMPNO': 7935, 'ENAME': 'JOHNSON', 'JOB': 'CLERK', 'MGR': 7839, 'HIREDATE': datetime.datetime(1981, 5, 1, 0, 0), 'SAL': 2850.0, 'COMM': None, 'DEPTNO': 30} | ||
| ``` |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import abc | ||
|
|
||
| from enum import Enum | ||
|
|
||
|
|
||
| class DatabaseType(Enum): | ||
| """ | ||
| not implemented: other databases | ||
| """ | ||
| ORACLE = "oracle" | ||
| SQL_ALCHEMY_ORACLE = "sql_alchemy_oracle" | ||
|
|
||
|
|
||
| class DBInterface(metaclass=abc.ABCMeta): | ||
|
|
||
| @abc.abstractmethod | ||
| def execute_query(self, query_string: str, args=None) -> list: | ||
| """ | ||
| executes a sql query, return a list of dictionaries representing rows | ||
| :param self: | ||
| :param query_string: | ||
| :param args: | ||
| :return: | ||
| """ | ||
| raise NotImplementedError | ||
|
|
||
| @abc.abstractmethod | ||
| def execute_update(self, query_string, args=None): | ||
| """ | ||
| used to execute an insert, update, or delete sql statement | ||
| :param self: | ||
| :param query_string: | ||
| :param args: | ||
| :return: | ||
| """ | ||
| raise NotImplementedError | ||
|
|
||
| @abc.abstractmethod | ||
| def health_check(): | ||
| """ | ||
| Performs a basic query against the db to ensure connectivity | ||
| :return: | ||
| """ | ||
| raise NotImplementedError | ||
|
|
||
| @abc.abstractmethod | ||
| def cleanup(): | ||
| """ | ||
| Attempts to release any 'live' objects/connections to the host | ||
| :return: | ||
| """ | ||
| raise NotImplementedError | ||
|
|
||
| @abc.abstractmethod | ||
| def create_connection(): | ||
| """ | ||
| provides a connection to the db host for more 'direct' access | ||
| :return: | ||
| """ | ||
| raise NotImplementedError | ||
|
|
||
| @abc.abstractmethod | ||
| def get_session(): | ||
| """ | ||
| Specific to SQL Alchemy; allows interaction with SQL Alchemy entities | ||
| :return: | ||
| """ | ||
| raise NotImplementedError |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Module for interacting with an oracle database | ||
| """ | ||
| # -*- encoding: utf-8 -*- | ||
|
|
||
| #============================================================================================ | ||
| # Imports | ||
| #============================================================================================ | ||
| # Standard imports | ||
| import logging | ||
|
|
||
| # Third-party imports | ||
| import cx_Oracle | ||
|
|
||
| from pylog.pylog import get_common_logger_for_module | ||
|
|
||
| from .database import DBInterface | ||
|
|
||
| # Local imports | ||
|
|
||
|
|
||
| class OracleDB(DBInterface): | ||
| """ | ||
| Class for interacting with an oracle database | ||
| """ | ||
|
|
||
| def __init__(self, host: str, port: int, service: str, user: str, pwd: str, | ||
| logging_level: int = 50, logging_format: logging.Formatter = None): | ||
| """ | ||
| Setup for oracle db connections. oracle_config must be a python dictionary with the following fields: | ||
|
|
||
| :param host: | ||
| :param port: | ||
| :param service: | ||
| :param user: | ||
| :param pwd: | ||
| :param logging_level: | ||
| :param logging_format: | ||
| :param logging_level: defaults to logging.CRITICAL | ||
| :param logging_format: defaults to None here, which translates to the pylog.get_commong_logging_format | ||
| """ | ||
|
|
||
| self.host = host | ||
| self.port = port | ||
| self.service = service | ||
| self.user = user | ||
| self.pwd = pwd | ||
| self.logger = get_common_logger_for_module(module_name=__name__, level=logging_level, log_format=logging_format) | ||
|
|
||
| self._pool = self.set_up_session_pool() | ||
|
|
||
| def set_up_session_pool(self): | ||
| try: | ||
| dsn_str = cx_Oracle.makedsn(self.host, self.port, service_name=self.service) | ||
| pool = cx_Oracle.SessionPool( | ||
| user=self.user, | ||
| password=self.pwd, | ||
| dsn=dsn_str, | ||
| min=2, | ||
| max=5, | ||
| increment=1, | ||
| threaded=True, | ||
| encoding="UTF-8" | ||
| ) | ||
| return pool | ||
|
|
||
| except cx_Oracle.DatabaseError as err: | ||
| obj, = err.args | ||
| self.logger.error("Error creating pool") | ||
| self.logger.error("Context: %s", obj.context) | ||
| self.logger.error("Message: %s", obj.message) | ||
| raise Exception(f"Error creating pool: {obj.message}") | ||
|
|
||
| def create_connection(self): | ||
| """ | ||
| Function for creating a connection with the database from a session pool | ||
| """ | ||
| try: | ||
| return self._pool.acquire() | ||
|
|
||
| except cx_Oracle.DatabaseError as err: | ||
| obj, = err.args | ||
| self.logger.error("Error acquiring database connection from the session pool") | ||
| self.logger.error("Context: %s", obj.context) | ||
| self.logger.error("Message: %s", obj.message) | ||
| raise Exception("Error acquiring database connection from the session pool") | ||
|
|
||
| @staticmethod | ||
| def make_dict(cursor): | ||
| """ | ||
| Function for converting a query result row into a dictionary | ||
| """ | ||
| column_names = [d[0] for d in cursor.description] | ||
|
|
||
| def create_row(*args): | ||
| return dict(zip(column_names, args)) | ||
| return create_row | ||
|
|
||
| def execute_query(self, query_string: str, args=None) -> dict: | ||
| """ | ||
| Function for executing a query against the database via the session pool | ||
| """ | ||
| try: | ||
| connection = self.create_connection() | ||
| cursor = connection.cursor() | ||
| if args is not None: | ||
| cursor.execute(query_string, args) | ||
| else: | ||
| cursor.execute(query_string) | ||
| cursor.rowfactory = self.make_dict(cursor) | ||
| query_result = cursor.fetchall() | ||
| return query_result | ||
|
|
||
| except cx_Oracle.DatabaseError as err: | ||
| obj, = err.args | ||
| self.logger.error("Error in execute_query") | ||
| self.logger.error("Context: %s", obj.context) | ||
| self.logger.error("Message: %s", obj.message) | ||
| raise Exception(f"Error executing query: {query_string}") | ||
|
|
||
| finally: | ||
| cursor.close() | ||
| self._pool.release(connection) | ||
|
|
||
| def execute_update(self, query_string, args=None): | ||
| """ | ||
| Function for executing an insert/update query against the database via the session pool | ||
| """ | ||
| try: | ||
| connection = self.create_connection() | ||
| cursor = connection.cursor() | ||
| if args is not None: | ||
| cursor.execute(query_string, args) | ||
| else: | ||
| cursor.execute(query_string) | ||
| connection.commit() | ||
|
|
||
| except cx_Oracle.DatabaseError as err: | ||
| obj, = err.args | ||
| self.logger.error("Error in execute_update") | ||
| self.logger.error("Context: %s", obj.context) | ||
| self.logger.error("Message: %s", obj.message) | ||
| raise Exception(f"Error executing update: {query_string}") | ||
|
|
||
| finally: | ||
| cursor.close() | ||
| self._pool.release(connection) | ||
|
|
||
| def health_check(self): | ||
| """ | ||
| provides a means to verify DB connectivity with a simple query | ||
| :return: | ||
| """ | ||
| return self.execute_query("SELECT 1 FROM DUAL") | ||
|
|
||
| def cleanup(self): | ||
| if self._pool is not None: | ||
| self.logger.info("Active session pool found. Attempting to close session pool.") | ||
| try: | ||
| self._pool.close(force=True) | ||
| self.logger.info("Session pool successfully closed.") | ||
|
|
||
| except cx_Oracle.Error as err: | ||
| self.logger.error("Unable to close the active session.", exc_info=True) | ||
| obj, = err.args | ||
| self.logger.error("Context:", obj.context) | ||
| self.logger.error("Message:", obj.message) | ||
|
|
||
| def get_session(self): | ||
| return None | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean
return create_row()here or do you actually mean to return the function?If the latter this is something a lambda could do relatively easily:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I 'inherited' this syntax - and am opting to leave as is since it is working as expected