forked from s_ranjbar/city_retrofit
Partial correction persistence
This commit is contained in:
parent
82e5d4ea5e
commit
7b369bae27
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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}')
|
||||
|
@ -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"
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user