Merge remote-tracking branch 'origin/db_persistence'

# Conflicts:
#	helpers/enrich_city.py
This commit is contained in:
Guille Gutierrez 2023-01-16 04:36:51 -05:00
commit 6c54e82ea3
11 changed files with 76 additions and 27 deletions

View File

@ -31,6 +31,13 @@ class DBFactory:
""" """
return self._city_repo.get_by_name(city_name) return self._city_repo.get_by_name(city_name)
def get_city_by_user(self, user_id):
"""
Retrieve cities created by user
:param user_id: the id of the user
"""
return self._city_repo.get_by_user(user_id)
def get_hp_simulation(self, hp_sim_id: int): def get_hp_simulation(self, hp_sim_id: int):
""" """
Retrieve a single heat pump simulation from postgres Retrieve a single heat pump simulation from postgres

View File

@ -9,6 +9,7 @@ from typing import List, Union, Dict
import yaml import yaml
from string import Template from string import Template
import pandas as pd import pandas as pd
from hub_logger import logger
class HeatPumpExport: class HeatPumpExport:
@ -56,13 +57,15 @@ class HeatPumpExport:
insel_file_handler.write(insel_template) insel_file_handler.write(insel_template)
# Now run insel # Now run insel
self._delete_existing_output_files() self._delete_existing_output_files()
os.system('/usr/local/bin/insel {}'.format(insel_file)) logger.info(f'Running Insel with user input: {user_input} and coefficients {capacity_coeff}')
os.system('insel {}'.format(insel_file))
# Writer headers to csv output files generated by insel # Writer headers to csv output files generated by insel
self._write_insel_output_headers() self._write_insel_output_headers()
# User output # User output
return self._get_user_out_put() return self._get_user_out_put()
except IOError as err: except IOError as err:
print("I/O exception: {}".format(err)) print("I/O exception: {}".format(err))
logger.error(f'An I/O error occurred while running insel: {err}')
finally: finally:
insel_file_handler.close() insel_file_handler.close()
insel_template_handler.close() insel_template_handler.close()

0
helpers/enrich_city.py Normal file
View File

View File

@ -12,6 +12,7 @@ from catalog_factories.construction_catalog_factory import ConstructionCatalogFa
from city_model_structure.building_demand.layer import Layer from city_model_structure.building_demand.layer import Layer
from city_model_structure.building_demand.material import Material from city_model_structure.building_demand.material import Material
from imports.construction.helpers.construction_helper import ConstructionHelper from imports.construction.helpers.construction_helper import ConstructionHelper
from hub_logger import logger
class UsPhysicsParameters(NrelPhysicsInterface): class UsPhysicsParameters(NrelPhysicsInterface):
@ -34,9 +35,14 @@ class UsPhysicsParameters(NrelPhysicsInterface):
try: try:
archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone) archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone)
except KeyError: except KeyError:
logger.error(f'Building {building.name} has unknown archetype for building function: {building.function} '
f'and building year of construction: {building.year_of_construction} '
f'and climate zone reference norm {self._climate_zone}\n')
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} ' sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} '
f'and building year of construction: {building.year_of_construction} ' f'and building year of construction: {building.year_of_construction} '
f'and climate zone reference norm {self._climate_zone}\n') f'and climate zone reference norm {self._climate_zone}\n')
return return
# if building has no thermal zones defined from geometry, and the building will be divided in storeys, # if building has no thermal zones defined from geometry, and the building will be divided in storeys,

View File

@ -19,11 +19,11 @@ class DBFactory:
self._city_repo = CityRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) self._city_repo = CityRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
def persist_city(self): def persist_city(self, user_id: int):
""" """
Persist city into postgres database Persist city into postgres database
""" """
return self._city_repo.insert(self._city) return self._city_repo.insert(self._city, user_id)
def update_city(self, city_id, city): def update_city(self, city_id, city):
""" """

View File

@ -17,9 +17,9 @@ class DBSetup:
:param dotenv_path: :param dotenv_path:
""" """
repo = BaseRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) repo = BaseRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
User.__table__.create(bind=repo.engine, checkfirst=True)
City.__table__.create(bind=repo.engine, checkfirst=True) City.__table__.create(bind=repo.engine, checkfirst=True)
HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True) HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True)
User.__table__.create(bind=repo.engine, checkfirst=True)
self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
self._create_admin_user(self._user_repo) self._create_admin_user(self._user_repo)
@ -29,9 +29,8 @@ class DBSetup:
print('Creating default admin user...') print('Creating default admin user...')
user = user_repo.insert('Administrator', email, password, UserRoles.Admin) user = user_repo.insert('Administrator', email, password, UserRoles.Admin)
if type(user) is dict: if type(user) is dict:
print(user)
logger.info(user) logger.info(user)
else: else:
print(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin}') print(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin.value}')
logger.info(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin}') logger.info(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin.value}')
print('Remember to change the admin default password and email address with the UserFactory') print('Remember to change the admin default password and email address with the UserFactory')

View File

@ -5,10 +5,11 @@ Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com Project Coder Peter Yefi peteryefi@gmail.com
""" """
from sqlalchemy import Column, Integer, String, Sequence from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
from sqlalchemy import DateTime, PickleType, Float from sqlalchemy import DateTime, PickleType, Float
from persistence.db_config import Base from persistence.db_config import Base
from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
import datetime import datetime
@ -29,10 +30,14 @@ class City(Base):
upper_corner = Column(JSONB, nullable=False) upper_corner = Column(JSONB, nullable=False)
hub_release = Column(String, nullable=False) hub_release = Column(String, nullable=False)
city_version = Column(Integer, nullable=False) city_version = Column(Integer, nullable=False)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship("User", back_populates="cities")
created = Column(DateTime, default=datetime.datetime.utcnow) created = Column(DateTime, default=datetime.datetime.utcnow)
updated = Column(DateTime, default=datetime.datetime.utcnow)
def __init__(self, city, name, srs_name, country_code, l_corner, u_corner): def __init__(self, city, name, srs_name, country_code, l_corner, u_corner, user_id):
self.city = city self.city = city
self.user_id = user_id
self.name = name self.name = name
self.srs_name = srs_name self.srs_name = srs_name
self.country_code = country_code self.country_code = country_code

View File

@ -12,11 +12,12 @@ import datetime
from sqlalchemy.orm import validates from sqlalchemy.orm import validates
import re import re
import enum import enum
from sqlalchemy.orm import relationship
class UserRoles(enum.Enum): class UserRoles(enum.Enum):
Admin = 'ADMIN' Admin = 'Admin'
HubReader = 'HUB_READER' Hub_Reader = 'Hub_Reader'
class User(Base): class User(Base):
@ -27,7 +28,8 @@ class User(Base):
name = Column(String, nullable=False) name = Column(String, nullable=False)
email = Column(String, nullable=False, unique=True) email = Column(String, nullable=False, unique=True)
password = Column(String, nullable=False) password = Column(String, nullable=False)
role = Column(Enum(UserRoles), nullable=False, default=UserRoles.HubReader) role = Column(Enum(UserRoles), nullable=False, default=UserRoles.Hub_Reader)
cities = relationship("City", back_populates="user")
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)

View File

@ -15,6 +15,7 @@ import requests
from urllib3.exceptions import HTTPError from urllib3.exceptions import HTTPError
from typing import Union, Dict from typing import Union, Dict
from hub_logger import logger from hub_logger import logger
import datetime
class CityRepo(BaseRepo): class CityRepo(BaseRepo):
@ -31,9 +32,9 @@ class CityRepo(BaseRepo):
cls._instance = super(CityRepo, cls).__new__(cls) cls._instance = super(CityRepo, cls).__new__(cls)
return cls._instance return cls._instance
def insert(self, city: City) -> Union[City, Dict]: def insert(self, city: City, user_id: int) -> Union[City, Dict]:
db_city = DBCity(pickle.dumps(city), city.name, city.srs_name, city.country_code, city.lower_corner, db_city = DBCity(pickle.dumps(city), city.name, city.srs_name, city.country_code, city.lower_corner,
city.upper_corner) city.upper_corner, user_id)
db_city.climate_reference_city = city.climate_reference_city db_city.climate_reference_city = city.climate_reference_city
db_city.longitude = city.longitude db_city.longitude = city.longitude
db_city.latitude = city.latitude db_city.latitude = city.latitude
@ -107,6 +108,7 @@ class CityRepo(BaseRepo):
'name': city.name, 'srs_name': city.srs_name, 'country_code': city.country_code, 'longitude': city.longitude, 'name': city.name, 'srs_name': city.srs_name, 'country_code': city.country_code, 'longitude': city.longitude,
'latitude': city.latitude, 'time_zone': city.time_zone, 'lower_corner': city.lower_corner.tolist(), 'latitude': city.latitude, 'time_zone': city.time_zone, 'lower_corner': city.lower_corner.tolist(),
'upper_corner': city.upper_corner.tolist(), 'climate_reference_city': city.climate_reference_city, 'upper_corner': city.upper_corner.tolist(), 'climate_reference_city': city.climate_reference_city,
'updated': datetime.datetime.utcnow()
}) })
self.session.commit() self.session.commit()
@ -125,6 +127,18 @@ class CityRepo(BaseRepo):
except SQLAlchemyError as err: except SQLAlchemyError as err:
logger.error(f'Error while fetching city by name: {err}') logger.error(f'Error while fetching city by name: {err}')
def get_by_user(self, user_id: int) -> [DBCity]:
"""
Fetch city based on the user who created it
:param user_id: the id of the user
:return: [ModelCity] with the provided name
"""
try:
result_set = self.session.execute(select(DBCity).where(DBCity.user_id == user_id))
return [building[0] for building in result_set]
except SQLAlchemyError as err:
logger.error(f'Error while fetching city by name: {err}')
def delete_city(self, city_id: int): def delete_city(self, city_id: int):
""" """
Deletes a City with the id Deletes a City with the id

View File

@ -13,6 +13,7 @@ from persistence.models import UserRoles
from helpers.auth import Auth from helpers.auth import Auth
from typing import Union, Dict from typing import Union, Dict
from hub_logger import logger from hub_logger import logger
import datetime
class UserRepo(BaseRepo): class UserRepo(BaseRepo):
@ -44,7 +45,7 @@ class UserRepo(BaseRepo):
else: else:
return {'message': f'user with {email} email already exists'} return {'message': f'user with {email} email already exists'}
def update(self, user_id: int, name: str, email: str, password: str, role: UserRoles): def update(self, user_id: int, name: str, email: str, password: str, role: UserRoles) -> Union[Dict, None]:
""" """
Updates a user Updates a user
:param user_id: the id of the user to be updated :param user_id: the id of the user to be updated
@ -57,10 +58,12 @@ class UserRepo(BaseRepo):
try: try:
if Auth.validate_password(password): if Auth.validate_password(password):
self.session.query(User).filter(User.id == user_id) \ self.session.query(User).filter(User.id == user_id) \
.update({'name': name, 'email': email, 'password': Auth.hash_password(password), 'role': role}) .update({'name': name, 'email': email, 'password': Auth.hash_password(password), 'role': role,
'updated': datetime.datetime.utcnow()})
self.session.commit() self.session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logger.error(f'Error while updating user: {err}') logger.error(f'Error while updating user: {err}')
return {'err_msg': 'Error occured while updated user'}
def get_by_email(self, email: str) -> [User]: def get_by_email(self, email: str) -> [User]:
""" """

View File

@ -7,10 +7,12 @@ Project Coder Peter Yefi peteryefi@gmail.com
from unittest import TestCase from unittest import TestCase
from imports.geometry_factory import GeometryFactory from imports.geometry_factory import GeometryFactory
from imports.db_factory import DBFactory from imports.db_factory import DBFactory
from imports.user_factory import UserFactory
from exports.db_factory import DBFactory as ExportDBFactory from exports.db_factory import DBFactory as ExportDBFactory
from persistence.base_repo import BaseRepo from persistence.base_repo import BaseRepo
from sqlalchemy import create_engine from sqlalchemy import create_engine
from persistence.models import City from persistence.models import City
from persistence.models import User, UserRoles
from pickle import loads from pickle import loads
from sqlalchemy.exc import ProgrammingError from sqlalchemy.exc import ProgrammingError
@ -43,16 +45,18 @@ class TestDBFactory(TestCase):
cnn.execute('commit') cnn.execute('commit')
cnn.execute("CREATE DATABASE test_db") cnn.execute("CREATE DATABASE test_db")
cnn.close() cnn.close()
User.__table__.create(bind=repo.engine, checkfirst=True)
City.__table__.create(bind=repo.engine, checkfirst=True) City.__table__.create(bind=repo.engine, checkfirst=True)
city_file = "../unittests/tests_data/C40_Final.gml" city_file = "../unittests/tests_data/C40_Final.gml"
cls.city = GeometryFactory('citygml', city_file).city cls.city = GeometryFactory('citygml', city_file).city
cls._db_factory = DBFactory(city=cls.city, db_name='test_db', app_env='TEST', dotenv_path='../.env') cls._db_factory = DBFactory(city=cls.city, 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') 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._user = user_factory.create_user("Admin", "admin@hub.com", "Admin@123", UserRoles.Admin)
def test_save_city(self): def test_save_city(self):
saved_city = self._db_factory.persist_city() saved_city = self._db_factory.persist_city(self._user.id)
self.assertEqual(saved_city.name, 'Montréal') self.assertEqual(saved_city.name, 'Montréal')
pickled_city = loads(saved_city.city) pickled_city = loads(saved_city.city)
self.assertEqual(len(pickled_city.buildings), 10) self.assertEqual(len(pickled_city.buildings), 10)
@ -60,27 +64,33 @@ class TestDBFactory(TestCase):
self._db_factory.delete_city(saved_city.id) self._db_factory.delete_city(saved_city.id)
def test_save_same_city_with_same_hub_version(self): def test_save_same_city_with_same_hub_version(self):
first_city = self._db_factory.persist_city() first_city = self._db_factory.persist_city(self._user.id)
second_city = self._db_factory.persist_city() second_city = self._db_factory.persist_city(self._user.id)
self.assertEqual(second_city['message'], f'Same version of {self.city.name} exist') self.assertEqual(second_city['message'], f'Same version of {self.city.name} exist')
self.assertEqual(first_city.name, 'Montréal') self.assertEqual(first_city.name, 'Montréal')
self.assertEqual(first_city.country_code, 'ca') self.assertEqual(first_city.country_code, 'ca')
self._db_factory.delete_city(first_city.id) self._db_factory.delete_city(first_city.id)
def test_get_city_by_name(self): def test_get_city_by_name(self):
city = self._db_factory.persist_city() city = self._db_factory.persist_city(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)
self.assertEqual(retrieved_city[0].lower_corner[0], 610610.7547462888) self.assertEqual(retrieved_city[0].lower_corner[0], 610610.7547462888)
self._db_factory.delete_city(city.id) self._db_factory.delete_city(city.id)
def test_get_city_by_user(self):
city = self._db_factory.persist_city(self._user.id)
retrieved_city = self._export_db_factory.get_city_by_user(self._user.id)
self.assertEqual(retrieved_city[0].user_id, self._user.id)
self._db_factory.delete_city(city.id)
def test_get_city_by_id(self): def test_get_city_by_id(self):
city = self._db_factory.persist_city() city = self._db_factory.persist_city(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.upper_corner[0], 610818.6731258357) self.assertEqual(retrieved_city.upper_corner[0], 610818.6731258357)
self._db_factory.delete_city(city.id) self._db_factory.delete_city(city.id)
def test_get_update_city(self): def test_get_update_city(self):
city = self._db_factory.persist_city() city = self._db_factory.persist_city(self._user.id)
self.city.longitude = 1.43589 self.city.longitude = 1.43589
self.city.latitude = -9.38928339 self.city.latitude = -9.38928339
self._db_factory.update_city(city.id, self.city) self._db_factory.update_city(city.id, self.city)