From b526a07e194edccc36dfa4eaf64f49482d300dea Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 30 Nov 2021 11:10:36 +0100 Subject: [PATCH] Added demo for SQL Support --- .../connection/database_connection.py | 23 ++++++++++++-- .../connection/database_connection_abc.py | 5 +++ .../database/context/database_context.py | 18 ++++++----- .../database/context/database_context_abc.py | 24 +++++++++++--- src/cpl_core/database/table_abc.py | 4 +-- src/tests/custom/database/src/application.py | 6 ++-- .../custom/database/src/model/city_model.py | 29 ++++++++--------- .../custom/database/src/model/user_model.py | 30 +++++++++--------- .../custom/database/src/model/user_repo.py | 31 +++++++++++++------ .../database/src/model/user_repo_abc.py | 7 +++++ 10 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/cpl_core/database/connection/database_connection.py b/src/cpl_core/database/connection/database_connection.py index d19c816a..5c9d9dc6 100644 --- a/src/cpl_core/database/connection/database_connection.py +++ b/src/cpl_core/database/connection/database_connection.py @@ -8,6 +8,7 @@ from cpl_core.database.connection.database_connection_abc import \ from cpl_core.database.database_settings import DatabaseSettings from cpl_core.utils import CredentialManager from mysql.connector.abstracts import MySQLConnectionAbstract +from mysql.connector.cursor import MySQLCursorBuffered class DatabaseConnection(DatabaseConnectionABC): @@ -17,15 +18,30 @@ class DatabaseConnection(DatabaseConnectionABC): def __init__(self): DatabaseConnectionABC.__init__(self) - self._database_server: Optional[MySQLConnectionAbstract] = None + self._database: Optional[MySQLConnectionAbstract] = None + self._cursor: Optional[MySQLCursorBuffered] = None @property def server(self) -> MySQLConnectionAbstract: - return self._database_server + return self._database + + @property + def cursor(self) -> MySQLCursorBuffered: + return self._cursor def connect(self, database_settings: DatabaseSettings): try: - self._database_server = sql.connect( + connection = sql.connect( + host=database_settings.host, + user=database_settings.user, + passwd=CredentialManager.decrypt(database_settings.password), + charset=database_settings.charset, + use_unicode=database_settings.use_unicode, + buffered=database_settings.buffered, + auth_plugin=database_settings.auth_plugin + ) + connection.cursor().execute(f'CREATE DATABASE IF NOT EXISTS `{database_settings.database}`;') + self._database = sql.connect( host=database_settings.host, user=database_settings.user, passwd=CredentialManager.decrypt(database_settings.password), @@ -35,6 +51,7 @@ class DatabaseConnection(DatabaseConnectionABC): buffered=database_settings.buffered, auth_plugin=database_settings.auth_plugin ) + self._cursor = self._database.cursor() Console.set_foreground_color(ForegroundColorEnum.green) Console.write_line(f'[{__name__}] Connected to database') Console.set_foreground_color(ForegroundColorEnum.default) diff --git a/src/cpl_core/database/connection/database_connection_abc.py b/src/cpl_core/database/connection/database_connection_abc.py index 448e26e8..c9564d8b 100644 --- a/src/cpl_core/database/connection/database_connection_abc.py +++ b/src/cpl_core/database/connection/database_connection_abc.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from cpl_core.database.database_settings import DatabaseSettings from mysql.connector.abstracts import MySQLConnectionAbstract +from mysql.connector.cursor import MySQLCursorBuffered class DatabaseConnectionABC(ABC): @@ -14,6 +15,10 @@ class DatabaseConnectionABC(ABC): @abstractmethod def server(self) -> MySQLConnectionAbstract: pass + @property + @abstractmethod + def cursor(self) -> MySQLCursorBuffered: pass + @abstractmethod def connect(self, database_settings: DatabaseSettings): r"""Connects to a database by connection string diff --git a/src/cpl_core/database/context/database_context.py b/src/cpl_core/database/context/database_context.py index 3d6c8b58..2793b622 100644 --- a/src/cpl_core/database/context/database_context.py +++ b/src/cpl_core/database/context/database_context.py @@ -8,6 +8,7 @@ from cpl_core.database.connection.database_connection_abc import \ from cpl_core.database.context.database_context_abc import DatabaseContextABC from cpl_core.database.database_settings import DatabaseSettings from cpl_core.database.table_abc import TableABC +from mysql.connector.cursor import MySQLCursorBuffered class DatabaseContext(DatabaseContextABC): @@ -22,21 +23,22 @@ class DatabaseContext(DatabaseContextABC): DatabaseContextABC.__init__(self, database_settings) self._db: DatabaseConnectionABC = DatabaseConnection() - self._cursor: Optional[str] = None self._tables: list[TableABC] = TableABC.__subclasses__() @property - def cursor(self): - return self._cursor - + def cursor(self) -> MySQLCursorBuffered: + return self._db.cursor + def connect(self, database_settings: DatabaseSettings): self._db.connect(database_settings) - c = self._db.server.cursor() - self._cursor = c Console.write_line(f"Ts: {self._tables}") for table in self._tables: - Console.write_line(f"{table}, {table.create_string}") - c.execute(table.create_string) + Console.write_line(f"{table}, {table.get_create_string()}") + self._db.cursor.execute(table.get_create_string()) def save_changes(self): self._db.server.commit() + + def select(self, statement: str) -> list: + self._db.cursor.execute(statement) + return self._db.cursor.fetchall() diff --git a/src/cpl_core/database/context/database_context_abc.py b/src/cpl_core/database/context/database_context_abc.py index ddf0bf2a..c4401068 100644 --- a/src/cpl_core/database/context/database_context_abc.py +++ b/src/cpl_core/database/context/database_context_abc.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from cpl_core.database.database_settings import DatabaseSettings +from mysql.connector.cursor import MySQLCursorBuffered class DatabaseContextABC(ABC): @@ -11,9 +12,9 @@ class DatabaseContextABC(ABC): pass @property - @abstractmethod - def cursor(self): pass - + def cursor(self) -> MySQLCursorBuffered: + return self._cursor + @abstractmethod def connect(self, database_settings: DatabaseSettings): r"""Connects to a database by connection settings @@ -23,7 +24,22 @@ class DatabaseContextABC(ABC): database_settings :class:`cpl_core.database.database_settings.DatabaseSettings` """ pass - + + @abstractmethod def save_changes(self): r"""Saves changes of the database""" pass + + @abstractmethod + def select(self, statement: str) -> list: + r"""Runs SQL Statements + + Parameter + --------- + statement: :class:`str` + + Returns + ------- + list: Fetched list of selected elements + """ + pass diff --git a/src/cpl_core/database/table_abc.py b/src/cpl_core/database/table_abc.py index 5777f866..8d552156 100644 --- a/src/cpl_core/database/table_abc.py +++ b/src/cpl_core/database/table_abc.py @@ -18,9 +18,9 @@ class TableABC(ABC): def LastModifiedAt(self) -> datetime: return self._modified_at - @property + @staticmethod @abstractmethod - def create_string(self) -> str: pass + def get_create_string() -> str: pass @property @abstractmethod diff --git a/src/tests/custom/database/src/application.py b/src/tests/custom/database/src/application.py index 83dabeb6..1d4cc60d 100644 --- a/src/tests/custom/database/src/application.py +++ b/src/tests/custom/database/src/application.py @@ -26,10 +26,12 @@ class Application(ApplicationABC): self._logger.debug(__name__, f'Customer: {self._configuration.environment.customer}') user_repo: UserRepo = self._services.get_service(UserRepoABC) - user_repo.add_test_user() + if len(user_repo.get_users()) == 0: + user_repo.add_test_user() + Console.write_line('Users:') for user in user_repo.get_users(): - Console.write_line(user.UserId, user.Name, user.CityId, user.City.CityId, user.City.Name, user.City.ZIP) + Console.write_line(user.UserId, user.Name, user.City) Console.write_line('Cities:') for city in user_repo.get_cities(): diff --git a/src/tests/custom/database/src/model/city_model.py b/src/tests/custom/database/src/model/city_model.py index 26461dbc..912a77f7 100644 --- a/src/tests/custom/database/src/model/city_model.py +++ b/src/tests/custom/database/src/model/city_model.py @@ -3,44 +3,45 @@ from cpl_core.database import TableABC class CityModel(TableABC): - def __init__(self, name: str, zip_code: str): - self.CityId = 0 + def __init__(self, name: str, zip_code: str, id = 0): + self.CityId = id self.Name = name self.ZIP = zip_code - @property - def create_string(self) -> str: - return f""" + @staticmethod + def get_create_string() -> str: + return str(f""" CREATE TABLE IF NOT EXISTS `City` ( `CityId` INT(30) NOT NULL AUTO_INCREMENT, - `Name` VARCHAR NOT NULL, - `ZIP` VARCHAR NOT NULL, + `Name` VARCHAR(64) NOT NULL, + `ZIP` VARCHAR(5) NOT NULL, + PRIMARY KEY(`CityId`) ); - """ + """) @property def insert_string(self) -> str: - return f""" + return str(f""" INSERT INTO `City` ( `Name`, `ZIP` ) VALUES ( '{self.Name}', '{self.ZIP}' ); - """ + """) @property def udpate_string(self) -> str: - return f""" + return str(f""" UPDATE `City` SET `Name` = '{self.Name}', `ZIP` = '{self.ZIP}', WHERE `CityId` = {self.Id}; - """ + """) @property def delete_string(self) -> str: - return f""" + return str(f""" DELETE FROM `City` WHERE `CityId` = {self.Id}; - """ + """) diff --git a/src/tests/custom/database/src/model/user_model.py b/src/tests/custom/database/src/model/user_model.py index 8b83850f..7cb992ba 100644 --- a/src/tests/custom/database/src/model/user_model.py +++ b/src/tests/custom/database/src/model/user_model.py @@ -5,46 +5,46 @@ from .city_model import CityModel class UserModel(TableABC): - def __init__(self, name: str, city: CityModel): - self.UserId = 0 + def __init__(self, name: str, city: CityModel, id = 0): + self.UserId = id self.Name = name - self.CityId = city.CityId + self.CityId = city.CityId if city is not None else 0 self.City = city - @property - def create_string(self) -> str: - return f""" + @staticmethod + def get_create_string() -> str: + return str(f""" CREATE TABLE IF NOT EXISTS `User` ( `UserId` INT(30) NOT NULL AUTO_INCREMENT, - `Name` VARCHAR NOT NULL, - `CityId` VARCHAR NOT NULL, + `Name` VARCHAR(64) NOT NULL, + `CityId` INT(30), FOREIGN KEY (`UserId`) REFERENCES City(`CityId`), PRIMARY KEY(`UserId`) ); - """ + """) @property def insert_string(self) -> str: - return f""" + return str(f""" INSERT INTO `User` ( `Name` ) VALUES ( '{self.Name}' ); - """ + """) @property def udpate_string(self) -> str: - return f""" + return str(f""" UPDATE `User` SET `Name` = '{self.Name}', `CityId` = {self.CityId}, WHERE `UserId` = {self.UserId}; - """ + """) @property def delete_string(self) -> str: - return f""" + return str(f""" DELETE FROM `User` WHERE `UserId` = {self.UserId}; - """ + """) diff --git a/src/tests/custom/database/src/model/user_repo.py b/src/tests/custom/database/src/model/user_repo.py index 082b45f4..2f71bb11 100644 --- a/src/tests/custom/database/src/model/user_repo.py +++ b/src/tests/custom/database/src/model/user_repo.py @@ -1,4 +1,6 @@ +from cpl_core.console import Console from cpl_core.database.context import DatabaseContextABC + from .city_model import CityModel from .user_model import UserModel from .user_repo_abc import UserRepoABC @@ -9,21 +11,32 @@ class UserRepo(UserRepoABC): def __init__(self, db_context: DatabaseContextABC): UserRepoABC.__init__(self) - self._db: DatabaseContextABC = db_context - - def create(self): pass + self._db_context: DatabaseContextABC = db_context def add_test_user(self): city = CityModel('Haren', '49733') city2 = CityModel('Meppen', '49716') - self._db.cursor.execute(city2.insert_string) + self._db_context.cursor.execute(city2.insert_string) user = UserModel('TestUser', city) - self._db.cursor.execute(user.insert_string) + self._db_context.cursor.execute(user.insert_string) + self._db_context.save_changes() def get_users(self) -> list[UserModel]: - self._db.cursor.execute(f"""SELECT * FROM `User`""") - return self._db.cursor.fetchall() + users = [] + results = self._db_context.select('SELECT * FROM `User`') + for result in results: + users.append(UserModel(result[1], self.get_city_by_id(result[2]), id = result[0])) + return users def get_cities(self) -> list[CityModel]: - self._db.cursor.execute(f"""SELECT * FROM `City`""") - return self._db.cursor.fetchall() + cities = [] + results = self._db_context.select('SELECT * FROM `City`') + for result in results: + cities.append(CityModel(result[1], result[2], id = result[0])) + return cities + + def get_city_by_id(self, id: int) -> CityModel: + if id is None: + return None + result = self._db_context.select(f'SELECT * FROM `City` WHERE `Id` = {id}') + return CityModel(result[1], result[2], id = result[0]) diff --git a/src/tests/custom/database/src/model/user_repo_abc.py b/src/tests/custom/database/src/model/user_repo_abc.py index f19a2a3e..9ffb7a38 100644 --- a/src/tests/custom/database/src/model/user_repo_abc.py +++ b/src/tests/custom/database/src/model/user_repo_abc.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod +from .city_model import CityModel from .user_model import UserModel @@ -10,3 +11,9 @@ class UserRepoABC(ABC): @abstractmethod def get_users(self) -> list[UserModel]: pass + + @abstractmethod + def get_cities(self) -> list[CityModel]: pass + + @abstractmethod + def get_city_by_id(self, id: int) -> CityModel: pass