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.
* 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.

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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}')

View File

@ -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"