From 7b369bae27518f821e8e9052c41c0af255463c72 Mon Sep 17 00:00:00 2001 From: guille Date: Wed, 10 May 2023 17:06:51 -0400 Subject: [PATCH] Partial correction persistence --- hub/CONTRIBUTING_EXTERNALS.md | 2 +- hub/PYGUIDE.md | 54 +++---- hub/helpers/geometry_helper.py | 7 +- hub/persistence/configuration.py | 27 +++- hub/persistence/models/city_object.py | 25 ++-- hub/persistence/repositories/city.py | 17 +-- hub/persistence/repository.py | 2 +- hub/unittests/test_db_factory.py | 177 +++++++++++++---------- hub/unittests/tests_data/pickle_path.bz2 | Bin 25178 -> 1806 bytes 9 files changed, 169 insertions(+), 142 deletions(-) diff --git a/hub/CONTRIBUTING_EXTERNALS.md b/hub/CONTRIBUTING_EXTERNALS.md index 47694bf4..48fb5112 100644 --- a/hub/CONTRIBUTING_EXTERNALS.md +++ b/hub/CONTRIBUTING_EXTERNALS.md @@ -90,7 +90,7 @@ pylint --rcfile=pylintrc myfile.py Before any pull request, the code must been manually and automatically tested to ensure at least some quality minimum. There are a few practices for unit tests that we believe are important, so we encourage you to follow it. -* The test should be self-contained, which implies that your tests will prepare and clean up everything before and after the test execution. +* The test should be cls-contained, which implies that your tests will prepare and clean up everything before and after the test execution. * We encourage you to create if possible functional tests that cover the complete workflow of the implemented functionality. * Maximize your code coverage by ensuring that you are testing as much of your code as possible. diff --git a/hub/PYGUIDE.md b/hub/PYGUIDE.md index 85a6113c..60c3dfdd 100644 --- a/hub/PYGUIDE.md +++ b/hub/PYGUIDE.md @@ -48,11 +48,11 @@ Use properties whenever it is possible. Encapsulate the access to all the calcul ```python @property - def object_attribute(self): - if self._object_attribute is None: - self._object_attribute = ... + def object_attribute(cls): + if cls._object_attribute is None: + cls._object_attribute = ... ... - return self._object_attribute + return cls._object_attribute ``` @@ -61,12 +61,12 @@ And like in the following example for read and write properties: ```python @property - def object_changeable_attribute(self): - return self._object_changeable_attribute + def object_changeable_attribute(cls): + return cls._object_changeable_attribute @object_changeable_attribute.setter - def object_changeable_attribute(self, value): - self._object_changeable_attribute = value + def object_changeable_attribute(cls, value): + cls._object_changeable_attribute = value ``` @@ -75,11 +75,11 @@ If your method or attribute returns a complex object, use type hints as in this ```python @property - def complex_object(self) -> ComplexObject: - return self._object_changeable_attribute + def complex_object(cls) -> ComplexObject: + return cls._object_changeable_attribute - def new_complex_object(self, first_param, second_param) -> ComplexObject: - other_needed_property = self.other_needed_property + def new_complex_object(cls, first_param, second_param) -> ComplexObject: + other_needed_property = cls.other_needed_property return ComplexObject(first_param, second_param, other_needed_property) ``` @@ -89,11 +89,11 @@ Always access your variable through the method and avoid to access directly. ```python @property - def object_attribute(self): - return self._object_attribute + def object_attribute(cls): + return cls._object_attribute - def operation(self, first_param, second_param): - return self.object_attribute * 2 + def operation(cls, first_param, second_param): + return cls.object_attribute * 2 ``` @@ -110,23 +110,23 @@ All public classes, properties, and methods must have code comments. Code commen MyClass class perform models class operations """ - def __init__(self): + def __init__(cls): @property - def object_attribute(self): + def object_attribute(cls): """ Get my class object attribute :return: int """ - return self._object_attribute + return cls._object_attribute - def operation(self, first_param, second_param): + def operation(cls, first_param, second_param): """ Multiplies object_attribute by two :return: int """ - return self.object_attribute * 2 + return cls.object_attribute * 2 ``` @@ -135,20 +135,20 @@ Comments at getters and setters always start with Get and Set, and identity the ```python @property - def object_attribute(self): + def object_attribute(cls): """ Get object attribute :return: int """ - return self._object_attribute + return cls._object_attribute @object_attribute.setter - def object_attribute(self, value): + def object_attribute(cls, value): """ Set object attribute :param value: int """ - self._object_attribute = value + cls._object_attribute = value ``` @@ -157,12 +157,12 @@ Attributes with known units should be explicit in method's comment. ```python @property - def distance(self): + def distance(cls): """ My class distance in meters :return: float """ - return self._distance + return cls._distance ``` #### To do's. diff --git a/hub/helpers/geometry_helper.py b/hub/helpers/geometry_helper.py index e9b9a6d9..ade42a16 100644 --- a/hub/helpers/geometry_helper.py +++ b/hub/helpers/geometry_helper.py @@ -70,13 +70,8 @@ class GeometryHelper: @staticmethod def city_mapping(city, building_names=None, plot=False): - """ - - Returns a shared_information dictionary like - { - "building_name" : [{line: 0 coordinate_1: [x,y,z], coordinate_2:[x, y, z], points: 0}] - } + Returns a shared_information dictionary """ lines_information = {} if building_names is None: diff --git a/hub/persistence/configuration.py b/hub/persistence/configuration.py index a48b2c91..f1da1642 100644 --- a/hub/persistence/configuration.py +++ b/hub/persistence/configuration.py @@ -6,6 +6,7 @@ Project Coder Peter Yefi peteryefi@gmail.com """ import os +from pathlib import Path from dotenv import load_dotenv from sqlalchemy.ext.declarative import declarative_base from hub.hub_logger import logger @@ -25,7 +26,11 @@ class Configuration: """ try: # load environmental variables + if not Path(dotenv_path).exists(): + logger.error(f'File not exist: {dotenv_path}') + raise FileNotFoundError(f'dotenv file doesn\'t exists at {dotenv_path}') load_dotenv(dotenv_path=dotenv_path) + self._db_name = db_name self._db_host = os.getenv(f'{app_env}_DB_HOST') self._db_user = os.getenv(f'{app_env}_DB_USER') @@ -35,14 +40,26 @@ class Configuration: except KeyError as err: logger.error(f'Error with credentials: {err}') - def conn_string(self): + @property + def connection_string(self): + """ + Returns a connection string postgresql + :return: connection string """ - Returns a connection string postgresql - :return: connection string - """ if self._db_pass: return f'postgresql://{self._db_user}:{self._db_pass}@{self._db_host}:{self._db_port}/{self._db_name}' return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}' - def get_db_user(self): + @property + def db_user(self): + """ + retrieve the configured user name + """ return self._db_user + + @property + def db_name(self): + """ + retrieve the configured database name + """ + return self._db_name diff --git a/hub/persistence/models/city_object.py b/hub/persistence/models/city_object.py index 7229a2ac..64eb5905 100644 --- a/hub/persistence/models/city_object.py +++ b/hub/persistence/models/city_object.py @@ -9,6 +9,8 @@ import datetime from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float from sqlalchemy import DateTime + +from hub.city_model_structure.building import Building from hub.persistence.configuration import Models class CityObject(Models): @@ -26,16 +28,21 @@ class CityObject(Models): usage = Column(String, nullable=True) volume = Column(Float, nullable=False) area = Column(Float, nullable=False) + total_heating_area = Column(Float, nullable=False) + wall_area = Column(Float, nullable=False) + windows_area = Column(Float, nullable=False) + system_name = Column(String, nullable=False) created = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow) - def __init__(self, city_id, name, alias, object_type, year_of_construction, function, usage, volume, area): + # def __init__(self, city_id, name, alias, object_type, year_of_construction, function, usage, volume, area): + def __init__(self, city_id, building: Building): self.city_id = city_id - self.name = name - self.alias = alias - self.type = object_type - self.year_of_construction = year_of_construction - self.function = function - self.usage = usage - self.volume = volume - self.area = area + self.name = building.name + self.alias = building.alias + self.type = building.type + self.year_of_construction = building.year_of_construction + self.function = building.function + self.usage = building.usages_percentage + self.volume = building.volume + self.area = building.floor_area diff --git a/hub/persistence/repositories/city.py b/hub/persistence/repositories/city.py index a242286a..7515bfef 100644 --- a/hub/persistence/repositories/city.py +++ b/hub/persistence/repositories/city.py @@ -57,23 +57,8 @@ class City(Repository): self.session.flush() self.session.commit() for building in city.buildings: - object_usage = '' - for internal_zone in building.internal_zones: - if internal_zone is None or internal_zone.usages is None: - object_usage = 'Unknown' - else: - for usage in internal_zone.usages: - object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' - object_usage = object_usage.rstrip() db_city_object = CityObject(db_city.id, - building.name, - building.alias, - building.type, - building.year_of_construction, - building.function, - object_usage, - building.volume, - building.floor_area) + building) self.session.add(db_city_object) self.session.flush() self.session.commit() diff --git a/hub/persistence/repository.py b/hub/persistence/repository.py index d9cea244..e7c7332f 100644 --- a/hub/persistence/repository.py +++ b/hub/persistence/repository.py @@ -15,7 +15,7 @@ class Repository: def __init__(self, db_name, dotenv_path: str, app_env='TEST'): try: self.configuration = Configuration(db_name, dotenv_path, app_env) - self.engine = create_engine(self.configuration.conn_string()) + self.engine = create_engine(self.configuration.connection_string) self.session = Session(self.engine) except ValueError as err: print(f'Missing value for credentials: {err}') diff --git a/hub/unittests/test_db_factory.py b/hub/unittests/test_db_factory.py index ee5730af..6e92c294 100644 --- a/hub/unittests/test_db_factory.py +++ b/hub/unittests/test_db_factory.py @@ -1,16 +1,17 @@ """ -Test EnergySystemsFactory and various heatpump models +Test db factory SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ +import os import unittest from unittest import TestCase - +from pathlib import Path import sqlalchemy.exc from hub.imports.geometry_factory import GeometryFactory -from hub.imports.db_factory import DBFactory +from hub.imports.db_factory import DBFactory as ImportDBFactory from hub.imports.user_factory import UserFactory from hub.exports.db_factory import DBFactory as ExportDBFactory from hub.persistence.repository import Repository @@ -20,103 +21,125 @@ from hub.persistence.models import User, UserRoles from sqlalchemy.exc import ProgrammingError import uuid -class Skip: +class Configure: - _value = False - _message = 'PostgreSQL not properly installed in host machine' + _skip_test = False + _skip_reason = 'PostgreSQL not properly installed in host machine' def __init__(self): + """ + Test + setup + :return: None + """ + self._skip_test = False # Create test database - env = '/usr/local/etc/hub/.env' - repo = Repository(db_name='test_db', app_env='TEST', dotenv_path=env) - eng = create_engine(f'postgresql://{repo.configuration.get_db_user()}@/{repo.configuration.get_db_user()}') + dotenv_path = str(Path("{}/.local/etc/hub/.env".format(os.path.expanduser('~'))).resolve()) + repository = Repository(db_name='hub_unittest', app_env='TEST', dotenv_path=dotenv_path) + engine = create_engine(repository.configuration.connection_string) try: # delete test database if it exists - conn = eng.connect() - conn.execute('commit') - conn.execute('DROP DATABASE test_db') - conn.close() - except ProgrammingError as err: + connection = engine.connect() + connection.execute('commit') + connection.close() + except ProgrammingError: print(f'Database does not exist. Nothing to delete') - except sqlalchemy.exc.OperationalError: - self._value = True + except sqlalchemy.exc.OperationalError as operational_error: + self._skip_test = True + self._skip_reason = f'{operational_error}' + return + + Application.__table__.create(bind=repository.engine, checkfirst=True) + User.__table__.create(bind=repository.engine, checkfirst=True) + City.__table__.create(bind=repository.engine, checkfirst=True) + CityObject.__table__.create(bind=repository.engine, checkfirst=True) + + city_file = "tests_data/FZK_Haus_LoD_2.gml" + self._city = GeometryFactory('citygml', city_file).city + self._import_db_factory = ImportDBFactory( + db_name=repository.configuration.db_name, + app_env='TEST', + dotenv_path=dotenv_path) + self._export_db_factory = ExportDBFactory( + db_name=repository.configuration.db_name, + app_env='TEST', + dotenv_path=dotenv_path) + user_factory = UserFactory( + db_name=repository.configuration.db_name, + app_env='TEST', + dotenv_path=dotenv_path) + self._unique_id = str(uuid.uuid4()) + self._application = self.import_db_factory.persist_application("test", "test application", self.unique_id) + self._user = user_factory.create_user("Admin", self.application.id, "Admin@123", UserRoles.Admin) + self._pickle_path = 'tests_data/pickle_path.bz2' @property - def value(self): - return self._value + def import_db_factory(self): + return self._import_db_factory + + @property + def export_db_factory(self): + return self._export_db_factory + + @property + def unique_id(self): + return self._unique_id + + @property + def application(self): + return self._application + + @property + def user(self): + return self._user + + @property + def skip_test(self): + return self._skip_test + + @property + def skip_reason(self): + return self._skip_reason @property def message(self): - return self._message + return self._skip_message - @value.setter - def value(self, skip_value): - self._value = skip_value + @property + def city(self): + return self._city -skip = Skip() + @property + def pickle_path(self): + return self._pickle_path + +configure = Configure() class TestDBFactory(TestCase): """ TestDBFactory """ - @classmethod - def setUpClass(cls) -> None: - """ - Test setup - :return: None - """ - # Create test database - env = '/usr/local/etc/hub/.env' - repo = Repository(db_name='test_db', app_env='TEST', dotenv_path=env) - eng = create_engine(f'postgresql://{repo.configuration.get_db_user()}@/{repo.configuration.get_db_user()}') - try: - # delete test database if it exists - conn = eng.connect() - conn.execute('commit') - conn.execute('DROP DATABASE test_db') - conn.close() - except ProgrammingError as err: - print(f'Database does not exist. Nothing to delete') - except sqlalchemy.exc.OperationalError: - skip.value = True - return - cnn = eng.connect() - cnn.execute('commit') - cnn.execute("CREATE DATABASE test_db") - cnn.close() - - Application.__table__.create(bind=repo.engine, checkfirst=True) - User.__table__.create(bind=repo.engine, checkfirst=True) - City.__table__.create(bind=repo.engine, checkfirst=True) - CityObject.__table__.create(bind=repo.engine, checkfirst=True) - - city_file = "tests_data/C40_Final.gml" - cls.city = GeometryFactory('citygml', city_file).city - cls._db_factory = DBFactory(db_name='test_db', app_env='TEST', dotenv_path=env) - cls._export_db_factory = ExportDBFactory(db_name='test_db', app_env='TEST', dotenv_path=env) - user_factory = UserFactory(db_name='test_db', app_env='TEST', dotenv_path=env) - cls.unique_id = str(uuid.uuid4()) - cls.application = cls._db_factory.persist_application("test", "test application", cls.unique_id) - cls._user = user_factory.create_user("Admin", cls.application.id, "Admin@123", UserRoles.Admin) - cls.pickle_path = 'tests_data/pickle_path.bz2' - - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_save_application(self): - self.assertEqual(self.application.name, "test") - self.assertEqual(self.application.description, "test application") - self.assertEqual(str(self.application.application_uuid), self.unique_id) + self.assertEqual(configure.application.name, "test") + self.assertEqual(configure.application.description, "test application") + self.assertEqual(str(configure.application.application_uuid), configure.unique_id) - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_save_city(self): - self.city.name = "Montréal" - saved_city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) - self.assertEqual(saved_city.name, 'Montréal') + configure.city.name = "Montreal" + saved_city = configure.import_db_factory.persist_city( + configure.city, + configure.pickle_path, + configure.application.id, + configure.user.id) + self.assertEqual(saved_city.name, 'Montreal') self.assertEqual(saved_city.pickle_path, self.pickle_path) self.assertEqual(saved_city.level_of_detail, self.city.level_of_detail.geometry) self._db_factory.delete_city(saved_city.id) - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_get_city_by_name(self): city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) retrieved_city = self._export_db_factory.get_city_by_name(city.name) @@ -124,21 +147,21 @@ class TestDBFactory(TestCase): self.assertEqual(retrieved_city[0].user_id, self._user.id) self._db_factory.delete_city(city.id) - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_get_city_by_user(self): - city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) + city = self._import_db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) retrieved_city = self._export_db_factory.get_city_by_user(self._user.id) self.assertEqual(retrieved_city[0].pickle_path, self.pickle_path) self._db_factory.delete_city(city.id) - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_get_city_by_id(self): city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) retrieved_city = self._export_db_factory.get_city(city.id) self.assertEqual(retrieved_city.level_of_detail, self.city.level_of_detail.geometry) self._db_factory.delete_city(city.id) - @unittest.skipIf(skip.value, skip.message) + @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_get_update_city(self): city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) self.city.name = "Ottawa" diff --git a/hub/unittests/tests_data/pickle_path.bz2 b/hub/unittests/tests_data/pickle_path.bz2 index b49164896e9329d76cd82bb90f6f258774091a94..8a80bdae96822a0090802b07aa67e395c82927f1 100644 GIT binary patch literal 1806 zcmV+p2l4nqT4*^jL0KkKS&~v}i~t8HfB*mg|L>xE|NVad{=fhC-}Ynl4i*pyKp@aS zU?>nks6pTc{|3FUxh;5C8|LG#WGmH2@7D0yGAui4COC13&{NjSU8X000cs00000005NGL`2b)%_->A z%_b+P1JrthKmY&$00000000016Dom`kYY5+p`gJ75unMCXlOEE6F>p!1_;20AOHq} z3<;(aOiWD~Fa*F8L4cZI0GTiV0x(7LYN%=r+2DOSN$-n1QsVh|*FZ9V39H00zJm zhX1+{we8p1gp5dX=+|2eiV1}yjykKCi6OK3thy)0Tgn)>INJs=kh=fcc3qZz5 z1~y0mmXcRaNx)8}l7xEF(=lJUYJ7bQE(KOJ^{PFL2DJ857XS_t6}C2M;TKtRu-Z`V zG&J-KmyFyFLFo#>xng`;;Y2L4RuqiEb;a5jY*fXn6v09)LGE*+b>l{y6D$$g!EtI2 z#S!4a+dQ3R!6fz`tT3r!MS6>H-<9RN4Z!Fm7D-9b`j?j7)QPbS z`@=p7&ea%EVol?7AjP%7=Msc50!1PSb4O}&dyJQoLH|1}4Sj*wFsT=MFl?67-I^9= zww^G?aQ6C{gwWhj^o5MeOCcv>1iU(6t&ryx%Xa9P21IHH*P&2fL>16FXY27ri^O9l)rh5S&iP>p;iX9YhL1!Um!w zCP^rzq$a?{VWK0 zo^qm8z56A6)f*5RR-$!fmc}}=SWGAO;MY~IjVR2upBLDu(W`0P!u+L07)BF>=``9X z7FySvv8vGUi*{(LHR==UVpdB&o8?T9g$6ZxBH$)VW0oeSHWZZ5n9Q`}&pNT%M;(&u zZq<+h0T_b!*;OG{h^Y+#N?s3Ylx4I?`r@+&S|h;#i8o1VlZm1;jBu+nG&Ja&OS|6( zM4L$D|Jz$b2N+;GjzR{v5&=2_-ib>%pne?@*w*3!hJ^0M#rX-kRO^y>Pcj|PYCq-l zKH50G1|%{WB$hZvS$T?7GR8uQAsF8%w4$pWnuLUj2pR9Z^D{u_20KMD$TtE$-eHiL zCQVry7`vLUyYDt`*>9WAyh0Z}N~oe!*+d!>tZ;IW`_Pe$lMee_5pf9e3fthK5jiYt zxpHB>K{caF`?kx#)(MFU(wLq@Ihx0BqTaS~MowVAKrN+Xe^eqwSz zePCa1#)5C21c+`O@rImjl9m%Mgl=%gZa>HUhcQx-k1Ew5=jXpl1U*LG8~}{XdA23(_ozAr2pdXNT&)C5=u=`fUxZ_WdHyG literal 25178 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 Z95VS|uN9UT;Di6g+>uTcBpLiIbU-6#j_m*d