From 03ba1d1847dec44ec9fa1131901f4c76df9962c0 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Fri, 11 Dec 2020 21:48:46 +0100 Subject: [PATCH] Added first version of database and orm --- .../base/database_connection_base.py | 9 ++-- .../connection/database_connection.py | 4 -- src/sh_edraft/database/context/__init__.py | 3 ++ .../database/context/base/__init__.py | 3 ++ .../context/base/database_context_base.py | 27 +++++++++++ .../database/context/database_context.py | 45 +++++++++++++++++++ src/sh_edraft/database/model/__init__.py | 1 + src/sh_edraft/database/model/dbmodel.py | 3 ++ .../providing/base/service_provider_base.py | 14 ++++-- .../service/providing/service_provider.py | 13 +++++- src/sh_edraft/utils/__init__.py | 1 + src/tests_dev/db/__init__.py | 0 src/tests_dev/db/city.py | 14 ++++++ src/tests_dev/db/user.py | 18 ++++++++ src/tests_dev/db/user_repo.py | 23 ++++++++++ src/tests_dev/db/user_repo_base.py | 10 +++++ src/tests_dev/program.py | 15 ++++--- 17 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 src/sh_edraft/database/context/__init__.py create mode 100644 src/sh_edraft/database/context/base/__init__.py create mode 100644 src/sh_edraft/database/context/base/database_context_base.py create mode 100644 src/sh_edraft/database/context/database_context.py create mode 100644 src/sh_edraft/database/model/dbmodel.py create mode 100644 src/tests_dev/db/__init__.py create mode 100644 src/tests_dev/db/city.py create mode 100644 src/tests_dev/db/user.py create mode 100644 src/tests_dev/db/user_repo.py create mode 100644 src/tests_dev/db/user_repo_base.py diff --git a/src/sh_edraft/database/connection/base/database_connection_base.py b/src/sh_edraft/database/connection/base/database_connection_base.py index c530f593..8a3e28ec 100644 --- a/src/sh_edraft/database/connection/base/database_connection_base.py +++ b/src/sh_edraft/database/connection/base/database_connection_base.py @@ -1,16 +1,13 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC from sqlalchemy import engine from sqlalchemy.orm import session -from sh_edraft.service.base.service_base import ServiceBase - -class DatabaseConnectionBase(ServiceBase): +class DatabaseConnectionBase(ABC): @abstractmethod - def __init__(self): - ServiceBase.__init__(self) + def __init__(self): pass @property @abstractmethod diff --git a/src/sh_edraft/database/connection/database_connection.py b/src/sh_edraft/database/connection/database_connection.py index 2c274ccf..eac20341 100644 --- a/src/sh_edraft/database/connection/database_connection.py +++ b/src/sh_edraft/database/connection/database_connection.py @@ -19,8 +19,6 @@ class DatabaseConnection(DatabaseConnectionBase): self._session: Optional[session] = None self._credentials: Optional[str] = None - self.create() - @property def engine(self) -> engine: return self._engine @@ -29,8 +27,6 @@ class DatabaseConnection(DatabaseConnectionBase): def session(self) -> session: return self._session - def create(self): pass - def connect(self, connection_string: str): try: self._engine = create_engine(connection_string) diff --git a/src/sh_edraft/database/context/__init__.py b/src/sh_edraft/database/context/__init__.py new file mode 100644 index 00000000..0e46ede4 --- /dev/null +++ b/src/sh_edraft/database/context/__init__.py @@ -0,0 +1,3 @@ +# imports: + +from .database_context import DatabaseContext diff --git a/src/sh_edraft/database/context/base/__init__.py b/src/sh_edraft/database/context/base/__init__.py new file mode 100644 index 00000000..ad5b61fb --- /dev/null +++ b/src/sh_edraft/database/context/base/__init__.py @@ -0,0 +1,3 @@ +# imports: + +from .database_context_base import DatabaseContextBase diff --git a/src/sh_edraft/database/context/base/database_context_base.py b/src/sh_edraft/database/context/base/database_context_base.py new file mode 100644 index 00000000..a18b6efe --- /dev/null +++ b/src/sh_edraft/database/context/base/database_context_base.py @@ -0,0 +1,27 @@ +from abc import abstractmethod + +from sqlalchemy import engine +from sqlalchemy.orm import session + +from sh_edraft.service.base.service_base import ServiceBase + + +class DatabaseContextBase(ServiceBase): + + @abstractmethod + def __init__(self): + ServiceBase.__init__(self) + + @property + @abstractmethod + def engine(self) -> engine: pass + + @property + @abstractmethod + def session(self) -> session: pass + + @abstractmethod + def connect(self, connection_string: str): pass + + @abstractmethod + def _create_tables(self): pass diff --git a/src/sh_edraft/database/context/database_context.py b/src/sh_edraft/database/context/database_context.py new file mode 100644 index 00000000..a9fd1b7e --- /dev/null +++ b/src/sh_edraft/database/context/database_context.py @@ -0,0 +1,45 @@ +from sqlalchemy import engine, Table +from sqlalchemy.orm import session + +from sh_edraft.database.connection.database_connection import DatabaseConnection +from sh_edraft.database.connection.base.database_connection_base import DatabaseConnectionBase +from sh_edraft.database.context.base.database_context_base import DatabaseContextBase +from sh_edraft.database.model.dbmodel import DBModel +from sh_edraft.database.model.database_settings import DatabaseSettings +from sh_edraft.utils.console import Console + + +class DatabaseContext(DatabaseContextBase): + + def __init__(self, database_settings: DatabaseSettings): + DatabaseContextBase.__init__(self) + + self._db: DatabaseConnectionBase = DatabaseConnection(database_settings) + self._tables: list[Table] = [] + + @property + def engine(self) -> engine: + return self._db.engine + + @property + def session(self) -> session: + return self._db.session + + def create(self): + pass + + def connect(self, connection_string: str): + self._db.connect(connection_string) + self._create_tables() + + def _create_tables(self): + try: + for subclass in DBModel.__subclasses__(): + self._tables.append(subclass.__table__) + + DBModel.metadata.drop_all(self._db.engine, self._tables) + DBModel.metadata.create_all(self._db.engine, self._tables, checkfirst=True) + Console.write_line(f'[{__name__}] Created tables', 'green') + except Exception as e: + Console.write_line(f'[{__name__}] Creating tables failed -> {e}', 'red') + exit() diff --git a/src/sh_edraft/database/model/__init__.py b/src/sh_edraft/database/model/__init__.py index c13a207e..b51f2dca 100644 --- a/src/sh_edraft/database/model/__init__.py +++ b/src/sh_edraft/database/model/__init__.py @@ -2,3 +2,4 @@ from .database_settings import DatabaseSettings from .database_settings_name import DatabaseSettingsName +from .dbmodel import DBModel diff --git a/src/sh_edraft/database/model/dbmodel.py b/src/sh_edraft/database/model/dbmodel.py new file mode 100644 index 00000000..145da02b --- /dev/null +++ b/src/sh_edraft/database/model/dbmodel.py @@ -0,0 +1,3 @@ +from sqlalchemy.ext.declarative import declarative_base + +DBModel: declarative_base = declarative_base() diff --git a/src/sh_edraft/service/providing/base/service_provider_base.py b/src/sh_edraft/service/providing/base/service_provider_base.py index 58d3a64f..28c986f0 100644 --- a/src/sh_edraft/service/providing/base/service_provider_base.py +++ b/src/sh_edraft/service/providing/base/service_provider_base.py @@ -1,15 +1,21 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC from collections import Callable from typing import Type +from sh_edraft.database.context.base.database_context_base import DatabaseContextBase from sh_edraft.service.base.service_base import ServiceBase -class ServiceProviderBase(ServiceBase): +class ServiceProviderBase(ABC): @abstractmethod - def __init__(self): - ServiceBase.__init__(self) + def __init__(self): pass + + @abstractmethod + def add_db_context(self, db_context: Type[DatabaseContextBase]): pass + + @abstractmethod + def get_db_context(self) -> Callable[DatabaseContextBase]: pass @abstractmethod def add_transient(self, service_type: Type[ServiceBase], service: Type[ServiceBase]): pass diff --git a/src/sh_edraft/service/providing/service_provider.py b/src/sh_edraft/service/providing/service_provider.py index 205f8359..b4defc4b 100644 --- a/src/sh_edraft/service/providing/service_provider.py +++ b/src/sh_edraft/service/providing/service_provider.py @@ -1,8 +1,9 @@ from collections import Callable from inspect import signature, Parameter -from typing import Type +from typing import Type, Optional from sh_edraft.configuration.base.configuration_model_base import ConfigurationModelBase +from sh_edraft.database.context.base.database_context_base import DatabaseContextBase from sh_edraft.hosting.base.application_runtime_base import ApplicationRuntimeBase from sh_edraft.service.providing.base.service_provider_base import ServiceProviderBase from sh_edraft.service.base.service_base import ServiceBase @@ -13,6 +14,7 @@ class ServiceProvider(ServiceProviderBase): def __init__(self, app_runtime: ApplicationRuntimeBase): ServiceProviderBase.__init__(self) self._app_runtime: ApplicationRuntimeBase = app_runtime + self._database_context: Optional[DatabaseContextBase] = None self._transient_services: dict[Type[ServiceBase], Type[ServiceBase]] = {} self._scoped_services: dict[Type[ServiceBase], Type[ServiceBase]] = {} @@ -29,6 +31,9 @@ class ServiceProvider(ServiceProviderBase): if issubclass(parameter.annotation, ApplicationRuntimeBase): params.append(self._app_runtime) + elif issubclass(parameter.annotation, DatabaseContextBase): + params.append(self._database_context) + elif issubclass(parameter.annotation, ServiceBase): params.append(self.get_service(parameter.annotation)) @@ -37,6 +42,12 @@ class ServiceProvider(ServiceProviderBase): return service(*params) + def add_db_context(self, db_context: Type[DatabaseContextBase]): + self._database_context = self._create_instance(db_context) + + def get_db_context(self) -> Callable[DatabaseContextBase]: + return self._database_context + def add_transient(self, service_type: Type[ServiceBase], service: Type[ServiceBase]): self._transient_services[service_type] = service diff --git a/src/sh_edraft/utils/__init__.py b/src/sh_edraft/utils/__init__.py index 4130c05d..10d2e9e5 100644 --- a/src/sh_edraft/utils/__init__.py +++ b/src/sh_edraft/utils/__init__.py @@ -21,6 +21,7 @@ from collections import namedtuple # imports: from .console import Console +from .credential_manager import CredentialManager VersionInfo = namedtuple('VersionInfo', 'major minor micro') version_info = VersionInfo(major=2020, minor=12, micro=5) diff --git a/src/tests_dev/db/__init__.py b/src/tests_dev/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tests_dev/db/city.py b/src/tests_dev/db/city.py new file mode 100644 index 00000000..baf07ded --- /dev/null +++ b/src/tests_dev/db/city.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, Integer, String + +from sh_edraft.database.model import DBModel + + +class City(DBModel): + __tablename__ = 'Cities' + Id = Column(Integer, primary_key=True, nullable=False, autoincrement=True) + Name = Column(String(64), nullable=False) + ZIP = Column(String(5), nullable=False) + + def __init__(self, name: str, zip_code: str): + self.Name = name + self.ZIP = zip_code diff --git a/src/tests_dev/db/user.py b/src/tests_dev/db/user.py new file mode 100644 index 00000000..e840df86 --- /dev/null +++ b/src/tests_dev/db/user.py @@ -0,0 +1,18 @@ +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship + +from sh_edraft.database.model import DBModel +from tests_dev.db.city import City as CityModel + + +class User(DBModel): + __tablename__ = 'Users' + Id = Column(Integer, primary_key=True, nullable=False, autoincrement=True) + Name = Column(String(64), nullable=False) + City_Id = Column(Integer, ForeignKey('Cities.Id'), nullable=False) + City = relationship("City") + + def __init__(self, name: str, city: CityModel): + self.Name = name + self.City_Id = city.Id + self.City = city diff --git a/src/tests_dev/db/user_repo.py b/src/tests_dev/db/user_repo.py new file mode 100644 index 00000000..a1b3cffc --- /dev/null +++ b/src/tests_dev/db/user_repo.py @@ -0,0 +1,23 @@ +from sh_edraft.database.context.base import DatabaseContextBase +from tests_dev.db.city import City +from tests_dev.db.user import User +from tests_dev.db.user_repo_base import UserRepoBase + + +class UserRepo(UserRepoBase): + + def __init__(self, db_context: DatabaseContextBase): + UserRepoBase.__init__(self) + + self._session = db_context.session + self._user_query = db_context.session.query(User) + + def create(self): pass + + def add_test_user(self): + city = City('Haren', '49733') + city2 = City('Meppen', '49716') + self._session.add(city2) + user = User('TestUser', city) + self._session.add(user) + self._session.commit() diff --git a/src/tests_dev/db/user_repo_base.py b/src/tests_dev/db/user_repo_base.py new file mode 100644 index 00000000..c48be0b2 --- /dev/null +++ b/src/tests_dev/db/user_repo_base.py @@ -0,0 +1,10 @@ +from abc import abstractmethod + +from sh_edraft.service.base import ServiceBase + + +class UserRepoBase(ServiceBase): + + @abstractmethod + def __init__(self): + ServiceBase.__init__(self) diff --git a/src/tests_dev/program.py b/src/tests_dev/program.py index 94796c55..baf29064 100644 --- a/src/tests_dev/program.py +++ b/src/tests_dev/program.py @@ -1,15 +1,17 @@ from typing import Optional from sh_edraft.configuration.base import ConfigurationBase -from sh_edraft.database.connection import DatabaseConnection -from sh_edraft.database.connection.base import DatabaseConnectionBase +from sh_edraft.database.context import DatabaseContext from sh_edraft.database.model import DatabaseSettings from sh_edraft.hosting import ApplicationHost from sh_edraft.hosting.base import ApplicationBase from sh_edraft.logging import Logger from sh_edraft.logging.base import LoggerBase from sh_edraft.service.providing.base import ServiceProviderBase -from sh_edraft.utils.credential_manager import CredentialManager +from sh_edraft.utils import CredentialManager + +from tests_dev.db.user_repo import UserRepo +from tests_dev.db.user_repo_base import UserRepoBase class Program(ApplicationBase): @@ -38,10 +40,12 @@ class Program(ApplicationBase): def create_services(self): # Create and connect to database db_settings: DatabaseSettings = self._configuration.get_configuration(DatabaseSettings) - self._services.add_singleton(DatabaseConnectionBase, DatabaseConnection) - db: DatabaseConnectionBase = self._services.get_service(DatabaseConnectionBase) + self._services.add_db_context(DatabaseContext) + db: DatabaseContext = self._services.get_db_context() db.connect(CredentialManager.build_string(db_settings.connection_string, db_settings.credentials)) + self._services.add_scoped(UserRepoBase, UserRepo) + # Add and create logger self._services.add_singleton(LoggerBase, Logger) self._logger = self._services.get_service(LoggerBase) @@ -51,3 +55,4 @@ class Program(ApplicationBase): self._logger.debug(__name__, f'Host: {self._configuration.environment.host_name}') self._logger.debug(__name__, f'Environment: {self._configuration.environment.environment_name}') self._logger.debug(__name__, f'Customer: {self._configuration.environment.customer}') + self._services.get_service(UserRepoBase).add_test_user()