Partial correction persistence

This commit is contained in:
Guille Gutierrez 2023-05-10 17:06:51 -04:00
parent 82e5d4ea5e
commit 7b369bae27
9 changed files with 169 additions and 142 deletions

View File

@ -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. 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. * 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. * Maximize your code coverage by ensuring that you are testing as much of your code as possible.

View File

@ -48,11 +48,11 @@ Use properties whenever it is possible. Encapsulate the access to all the calcul
```python ```python
@property @property
def object_attribute(self): def object_attribute(cls):
if self._object_attribute is None: if cls._object_attribute is None:
self._object_attribute = ... 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 ```python
@property @property
def object_changeable_attribute(self): def object_changeable_attribute(cls):
return self._object_changeable_attribute return cls._object_changeable_attribute
@object_changeable_attribute.setter @object_changeable_attribute.setter
def object_changeable_attribute(self, value): def object_changeable_attribute(cls, value):
self._object_changeable_attribute = 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 ```python
@property @property
def complex_object(self) -> ComplexObject: def complex_object(cls) -> ComplexObject:
return self._object_changeable_attribute return cls._object_changeable_attribute
def new_complex_object(self, first_param, second_param) -> ComplexObject: def new_complex_object(cls, first_param, second_param) -> ComplexObject:
other_needed_property = self.other_needed_property other_needed_property = cls.other_needed_property
return ComplexObject(first_param, second_param, 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 ```python
@property @property
def object_attribute(self): def object_attribute(cls):
return self._object_attribute return cls._object_attribute
def operation(self, first_param, second_param): def operation(cls, first_param, second_param):
return self.object_attribute * 2 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 MyClass class perform models class operations
""" """
def __init__(self): def __init__(cls):
@property @property
def object_attribute(self): def object_attribute(cls):
""" """
Get my class object attribute Get my class object attribute
:return: int :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 Multiplies object_attribute by two
:return: int :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 ```python
@property @property
def object_attribute(self): def object_attribute(cls):
""" """
Get object attribute Get object attribute
:return: int :return: int
""" """
return self._object_attribute return cls._object_attribute
@object_attribute.setter @object_attribute.setter
def object_attribute(self, value): def object_attribute(cls, value):
""" """
Set object attribute Set object attribute
:param value: int :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 ```python
@property @property
def distance(self): def distance(cls):
""" """
My class distance in meters My class distance in meters
:return: float :return: float
""" """
return self._distance return cls._distance
``` ```
#### To do's. #### To do's.

View File

@ -70,13 +70,8 @@ class GeometryHelper:
@staticmethod @staticmethod
def city_mapping(city, building_names=None, plot=False): def city_mapping(city, building_names=None, plot=False):
""" """
Returns a shared_information dictionary
Returns a shared_information dictionary like
{
"building_name" : [{line: 0 coordinate_1: [x,y,z], coordinate_2:[x, y, z], points: 0}]
}
""" """
lines_information = {} lines_information = {}
if building_names is None: if building_names is None:

View File

@ -6,6 +6,7 @@ Project Coder Peter Yefi peteryefi@gmail.com
""" """
import os import os
from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from hub.hub_logger import logger from hub.hub_logger import logger
@ -25,7 +26,11 @@ class Configuration:
""" """
try: try:
# load environmental variables # 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) load_dotenv(dotenv_path=dotenv_path)
self._db_name = db_name self._db_name = db_name
self._db_host = os.getenv(f'{app_env}_DB_HOST') self._db_host = os.getenv(f'{app_env}_DB_HOST')
self._db_user = os.getenv(f'{app_env}_DB_USER') self._db_user = os.getenv(f'{app_env}_DB_USER')
@ -35,14 +40,26 @@ class Configuration:
except KeyError as err: except KeyError as err:
logger.error(f'Error with credentials: {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: 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_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}' 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 return self._db_user
@property
def db_name(self):
"""
retrieve the configured database name
"""
return self._db_name

View File

@ -9,6 +9,8 @@ import datetime
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
from sqlalchemy import DateTime from sqlalchemy import DateTime
from hub.city_model_structure.building import Building
from hub.persistence.configuration import Models from hub.persistence.configuration import Models
class CityObject(Models): class CityObject(Models):
@ -26,16 +28,21 @@ class CityObject(Models):
usage = Column(String, nullable=True) usage = Column(String, nullable=True)
volume = Column(Float, nullable=False) volume = Column(Float, nullable=False)
area = 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) created = Column(DateTime, default=datetime.datetime.utcnow)
updated = 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.city_id = city_id
self.name = name self.name = building.name
self.alias = alias self.alias = building.alias
self.type = object_type self.type = building.type
self.year_of_construction = year_of_construction self.year_of_construction = building.year_of_construction
self.function = function self.function = building.function
self.usage = usage self.usage = building.usages_percentage
self.volume = volume self.volume = building.volume
self.area = area self.area = building.floor_area

View File

@ -57,23 +57,8 @@ class City(Repository):
self.session.flush() self.session.flush()
self.session.commit() self.session.commit()
for building in city.buildings: 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, db_city_object = CityObject(db_city.id,
building.name, building)
building.alias,
building.type,
building.year_of_construction,
building.function,
object_usage,
building.volume,
building.floor_area)
self.session.add(db_city_object) self.session.add(db_city_object)
self.session.flush() self.session.flush()
self.session.commit() self.session.commit()

View File

@ -15,7 +15,7 @@ class Repository:
def __init__(self, db_name, dotenv_path: str, app_env='TEST'): def __init__(self, db_name, dotenv_path: str, app_env='TEST'):
try: try:
self.configuration = Configuration(db_name, dotenv_path, app_env) 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) self.session = Session(self.engine)
except ValueError as err: except ValueError as err:
print(f'Missing value for credentials: {err}') print(f'Missing value for credentials: {err}')

View File

@ -1,16 +1,17 @@
""" """
Test EnergySystemsFactory and various heatpump models Test db factory
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com Project Coder Peter Yefi peteryefi@gmail.com
""" """
import os
import unittest import unittest
from unittest import TestCase from unittest import TestCase
from pathlib import Path
import sqlalchemy.exc import sqlalchemy.exc
from hub.imports.geometry_factory import GeometryFactory 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.imports.user_factory import UserFactory
from hub.exports.db_factory import DBFactory as ExportDBFactory from hub.exports.db_factory import DBFactory as ExportDBFactory
from hub.persistence.repository import Repository from hub.persistence.repository import Repository
@ -20,103 +21,125 @@ from hub.persistence.models import User, UserRoles
from sqlalchemy.exc import ProgrammingError from sqlalchemy.exc import ProgrammingError
import uuid import uuid
class Skip: class Configure:
_value = False _skip_test = False
_message = 'PostgreSQL not properly installed in host machine' _skip_reason = 'PostgreSQL not properly installed in host machine'
def __init__(self): def __init__(self):
"""
Test
setup
:return: None
"""
self._skip_test = False
# Create test database # Create test database
env = '/usr/local/etc/hub/.env' dotenv_path = str(Path("{}/.local/etc/hub/.env".format(os.path.expanduser('~'))).resolve())
repo = Repository(db_name='test_db', app_env='TEST', dotenv_path=env) repository = Repository(db_name='hub_unittest', app_env='TEST', dotenv_path=dotenv_path)
eng = create_engine(f'postgresql://{repo.configuration.get_db_user()}@/{repo.configuration.get_db_user()}') engine = create_engine(repository.configuration.connection_string)
try: try:
# delete test database if it exists # delete test database if it exists
conn = eng.connect() connection = engine.connect()
conn.execute('commit') connection.execute('commit')
conn.execute('DROP DATABASE test_db') connection.close()
conn.close() except ProgrammingError:
except ProgrammingError as err:
print(f'Database does not exist. Nothing to delete') print(f'Database does not exist. Nothing to delete')
except sqlalchemy.exc.OperationalError: except sqlalchemy.exc.OperationalError as operational_error:
self._value = True 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 @property
def value(self): def import_db_factory(self):
return self._value 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 @property
def message(self): def message(self):
return self._message return self._skip_message
@value.setter @property
def value(self, skip_value): def city(self):
self._value = skip_value return self._city
skip = Skip() @property
def pickle_path(self):
return self._pickle_path
configure = Configure()
class TestDBFactory(TestCase): class TestDBFactory(TestCase):
""" """
TestDBFactory TestDBFactory
""" """
@classmethod @unittest.skipIf(configure.skip_test, configure.skip_reason)
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)
def test_save_application(self): def test_save_application(self):
self.assertEqual(self.application.name, "test") self.assertEqual(configure.application.name, "test")
self.assertEqual(self.application.description, "test application") self.assertEqual(configure.application.description, "test application")
self.assertEqual(str(self.application.application_uuid), self.unique_id) 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): def test_save_city(self):
self.city.name = "Montréal" configure.city.name = "Montreal"
saved_city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) saved_city = configure.import_db_factory.persist_city(
self.assertEqual(saved_city.name, 'Montréal') 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.pickle_path, self.pickle_path)
self.assertEqual(saved_city.level_of_detail, self.city.level_of_detail.geometry) self.assertEqual(saved_city.level_of_detail, self.city.level_of_detail.geometry)
self._db_factory.delete_city(saved_city.id) 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): def test_get_city_by_name(self):
city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) 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) 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.assertEqual(retrieved_city[0].user_id, self._user.id)
self._db_factory.delete_city(city.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): 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) retrieved_city = self._export_db_factory.get_city_by_user(self._user.id)
self.assertEqual(retrieved_city[0].pickle_path, self.pickle_path) self.assertEqual(retrieved_city[0].pickle_path, self.pickle_path)
self._db_factory.delete_city(city.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_id(self): def test_get_city_by_id(self):
city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) 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) retrieved_city = self._export_db_factory.get_city(city.id)
self.assertEqual(retrieved_city.level_of_detail, self.city.level_of_detail.geometry) self.assertEqual(retrieved_city.level_of_detail, self.city.level_of_detail.geometry)
self._db_factory.delete_city(city.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_update_city(self): def test_get_update_city(self):
city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id)
self.city.name = "Ottawa" self.city.name = "Ottawa"