Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow database relinking attempt during import #428

Merged
merged 10 commits into from
Jul 22, 2020
14 changes: 13 additions & 1 deletion activity_browser/app/bwutils/commontasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from datetime import datetime as dt
import hashlib
import os
import textwrap
Expand Down Expand Up @@ -129,8 +130,19 @@ def store_database_as_package(db_name: str, directory: str = None) -> bool:
"""
if db_name not in bw.databases:
return False
metadata = bw.databases[db_name]
db = bw.Database(db_name)
output_dir = directory or "export"
bw.BW2Package.export_obj(bw.Database(db_name), db_name, output_dir)
# First, ensure the metadata on the database is up-to-date.
modified = dt.strptime(metadata["modified"], "%Y-%m-%dT%H:%M:%S.%f")
if "processed" in metadata:
processed = dt.strptime(metadata["processed"], "%Y-%m-%dT%H:%M:%S.%f")
if processed < modified:
db.process()
else:
db.process()
# Now that processing is done, perform the export.
bw.BW2Package.export_obj(db, db_name, output_dir)
return True


Expand Down
39 changes: 39 additions & 0 deletions activity_browser/app/bwutils/strategies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from typing import Collection

import brightway2 as bw
from bw2data.backends.peewee import ActivityDataset


def relink_exchanges_dbs(data: Collection, relink: dict) -> Collection:
"""Use this to relink exchanges during an actual import."""
for act in data:
for exc in act.get("exchanges", []):
input_key = exc.get("input", ("", ""))
if input_key[0] in relink:
new_key = (relink[input_key[0]], input_key[1])
try:
# try and find the new key
_ = bw.get_activity(new_key)
exc["input"] = new_key
except ActivityDataset.DoesNotExist as e:
raise ValueError("Cannot relink exchange '{}', key '{}' not found.".format(exc, new_key)
).with_traceback(e.__traceback__)
return data


def relink_exchanges_bw2package(data: dict, relink: dict) -> dict:
"""Use this to relink exchanges during an BW2Package import."""
for key, value in data.items():
for exc in value.get("exchanges", []):
input_key = exc.get("input", ("", ""))
if input_key[0] in relink:
new_key = (relink[input_key[0]], input_key[1])
try:
# try and find the new key
_ = bw.get_activity(new_key)
exc["input"] = new_key
except ActivityDataset.DoesNotExist as e:
raise ValueError("Cannot relink exchange '{}', key '{}' not found.".format(exc, new_key)
).with_traceback(e.__traceback__)
return data
46 changes: 28 additions & 18 deletions activity_browser/app/controller.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
import os
import uuid
from typing import Iterable
from typing import Iterable, Optional

import brightway2 as bw
from bw2data.parameters import ActivityParameter
from PySide2 import QtWidgets
from PySide2.QtCore import Slot
from PySide2.QtCore import QObject, Slot
from bw2data.backends.peewee import sqlite3_lci_db
from bw2data.parameters import ParameterBase
from bw2data.project import ProjectDataset, SubstitutableDatabase
from bw2data.proxies import ExchangeProxyBase

from .bwutils import commontasks as bc, AB_metadata
Expand Down Expand Up @@ -37,7 +35,7 @@ class Controller(object):
(https://stackoverflow.com/questions/26698628/mvc-design-with-qt-designer-and-pyqt-pyside)
"""
def __init__(self):
self.db_wizard = None
self.db_wizard: Optional[QtWidgets.QWizard] = None
self.copy_progress = None
self.connect_signals()
signals.project_selected.emit()
Expand Down Expand Up @@ -110,12 +108,13 @@ def clear_database_wizard(self):
self.db_wizard.deleteLater()
self.db_wizard = None

def import_database_wizard(self):
@Slot(QObject, name="openImportWizard")
def import_database_wizard(self, parent):
""" Create a database import wizard, if it already exists, set the
previous one to delete and recreate it.
"""
self.clear_database_wizard()
self.db_wizard = DatabaseImportWizard()
self.db_wizard = DatabaseImportWizard(parent)

def switch_brightway2_dir_path(self, dirpath):
if bc.switch_brightway2_dir(dirpath):
Expand Down Expand Up @@ -329,9 +328,11 @@ def new_activity(self, database_name):
type="process",
)
new_act.save()
production_exchange = new_act.new_exchange(amount=1, type="production")
production_exchange.input = new_act
production_exchange = new_act.new_exchange(
input=new_act, amount=1, type="production"
)
production_exchange.save()
bw.databases.set_modified(database_name)
signals.open_activity_tab.emit(new_act.key)
signals.metadata_changed.emit(new_act.key)
signals.database_changed.emit(database_name)
Expand All @@ -358,8 +359,9 @@ def delete_activity(self, key):
# Remove all activity parameters
Controller.delete_activity_parameter(act.key)
act.delete()
bw.databases.set_modified(act["database"])
signals.metadata_changed.emit(act.key)
signals.database_changed.emit(act['database'])
signals.database_changed.emit(act["database"])
signals.databases_changed.emit()
signals.calculation_setup_changed.emit()

Expand Down Expand Up @@ -395,6 +397,7 @@ def duplicate_activity(self, key):
if product.get('input') == key:
product['input'] = new_act.key
new_act.save()
bw.databases.set_modified(act['database'])
signals.metadata_changed.emit(new_act.key)
signals.database_changed.emit(act['database'])
signals.databases_changed.emit()
Expand Down Expand Up @@ -435,6 +438,7 @@ def duplicate_activity_to_db(self, target_db, activity):
if bc.count_database_records(target_db) < 50:
bw.databases.clean()

bw.databases.set_modified(target_db)
signals.metadata_changed.emit(new_act_key)
signals.database_changed.emit(target_db)
signals.open_activity_tab.emit(new_act_key)
Expand All @@ -444,6 +448,7 @@ def modify_activity(self, key, field, value):
activity = bw.get_activity(key)
activity[field] = value
activity.save()
bw.databases.set_modified(key[0])
signals.metadata_changed.emit(key)
signals.database_changed.emit(key[0])

Expand Down Expand Up @@ -477,24 +482,26 @@ def add_exchanges(self, from_keys: Iterable, to_key: tuple) -> None:
else:
exc['type'] = 'unknown'
exc.save()
bw.databases.set_modified(to_key[0])
signals.metadata_changed.emit(to_key)
signals.database_changed.emit(to_key[0])


def delete_exchanges(self, exchanges):
db_changed = set()
for exc in exchanges:
db_changed.add(exc['output'][0])
exc._document.delete_instance()
db_changed.add(exc["output"][0])
exc.delete()
for db in db_changed:
bw.databases.set_modified(db)
# signals.metadata_changed.emit(to_key)
signals.database_changed.emit(db)

def modify_exchange_amount(self, exchange, value):
exchange['amount'] = value
exchange["amount"] = value
exchange.save()

signals.database_changed.emit(exchange['output'][0])
bw.databases.set_modified(exchange["output"][0])
signals.database_changed.emit(exchange["output"][0])

@staticmethod
@Slot(object, str, object)
Expand All @@ -516,10 +523,11 @@ def modify_exchange(exchange, field, value):
else:
exchange[field] = value
exchange.save()
bw.databases.set_modified(exchange["output"][0])
if field == "formula":
# If a formula was set, removed or changed, recalculate exchanges
signals.exchange_formula_changed.emit(exchange["output"])
signals.database_changed.emit(exchange['output'][0])
signals.database_changed.emit(exchange["output"][0])

@staticmethod
@Slot(object, object, name="modifyExchangeUncertainty")
Expand All @@ -531,14 +539,16 @@ def modify_exchange_uncertainty(exc: ExchangeProxyBase, unc_dict: dict) -> None:
v = float("nan") if not v else float(v)
exc[k] = v
exc.save()
signals.database_changed.emit(exc['output'][0])
bw.databases.set_modified(exc["output"][0])
signals.database_changed.emit(exc["output"][0])

@staticmethod
@Slot(object, object, name="modifyExchangePedigree")
def modify_exchange_pedigree(exc: ExchangeProxyBase, pedigree: dict) -> None:
exc["pedigree"] = pedigree
exc.save()
signals.database_changed.emit(exc['output'][0])
bw.databases.set_modified(exc["output"][0])
signals.database_changed.emit(exc["output"][0])

# PARAMETERS
@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion activity_browser/app/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Signals(QObject):
delete_database = Signal(str)
copy_database = Signal(str)
install_default_data = Signal()
import_database = Signal()
import_database = Signal(QObject)

database_selected = Signal(str)
databases_changed = Signal()
Expand Down
2 changes: 1 addition & 1 deletion activity_browser/app/ui/menu_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def connect_signals(self):
signals.project_selected.connect(self.biosphere_exists)
signals.databases_changed.connect(self.biosphere_exists)
self.update_biosphere_action.triggered.connect(self.update_biosphere)
self.import_db_action.triggered.connect(signals.import_database.emit)
self.import_db_action.triggered.connect(lambda: signals.import_database.emit(self))

# FILE
def setup_file_menu(self):
Expand Down
2 changes: 1 addition & 1 deletion activity_browser/app/ui/tabs/project_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def __init__(self, parent):

def _connect_signals(self):
self.add_default_data_button.clicked.connect(signals.install_default_data.emit)
self.import_database_button.clicked.connect(signals.import_database.emit)
self.import_database_button.clicked.connect(lambda: signals.import_database.emit(self))
self.new_database_button.clicked.connect(signals.add_database.emit)

def _construct_layout(self):
Expand Down
1 change: 1 addition & 0 deletions activity_browser/app/ui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .database_copy import CopyDatabaseDialog
from .dialog import (
ForceInputDialog, TupleNameDialog, ExcelReadDialog, ChoiceSelectionDialog,
DatabaseRelinkDialog,
)
from .line_edit import (SignalledPlainTextEdit, SignalledComboEdit,
SignalledLineEdit)
Expand Down
42 changes: 42 additions & 0 deletions activity_browser/app/ui/widgets/dialog.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from pathlib import Path
from typing import List

from PySide2 import QtGui, QtWidgets
from PySide2.QtCore import QRegExp, Slot
Expand Down Expand Up @@ -232,3 +233,44 @@ def changed(self) -> None:
if self.complete:
self.update_combobox(self.path)
self.buttons.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(self.complete)


class DatabaseRelinkDialog(QtWidgets.QDialog):
LABEL_TEXT = (
"A database could not be found in project, attempt to relink the"
" exchanges to a different database?"
"\n\nReplace database '{}' with:"
)

def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Database relinking")
self.label = QtWidgets.QLabel("")
self.choice = QtWidgets.QComboBox()
self.choice.addItems(["-----"])
self.choice.setDisabled(True)

self.buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)

layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.choice)
layout.addWidget(self.buttons)
self.setLayout(layout)

@property
def new_db(self) -> str:
return self.choice.currentText()

@classmethod
def start_relink(cls, parent: QtWidgets.QWidget, db: str, options: List[str]) -> 'DatabaseRelinkDialog':
obj = cls(parent)
obj.label.setText(cls.LABEL_TEXT.format(db))
obj.choice.clear()
obj.choice.addItems(options)
obj.choice.setEnabled(True)
return obj
Loading