Skip to content

Commit e8df269

Browse files
committed
Migrating projects updates databases
1 parent feb0e17 commit e8df269

2 files changed

Lines changed: 98 additions & 7 deletions

File tree

activity_browser/actions/project/project_migrate25.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
from tqdm import tqdm
2+
from logging import getLogger
13
from qtpy import QtWidgets, QtGui, QtCore
24

3-
from bw2data.project import projects
5+
import bw2data as bd
6+
import pandas as pd
47

5-
from activity_browser import application
8+
from activity_browser import application, signals
69
from activity_browser.actions.base import ABAction, exception_dialogs
10+
from activity_browser.bwutils import AB_metadata
711
from activity_browser.ui.icons import qicons
812
from activity_browser.ui.threading import ABThread
913

14+
log = getLogger(__name__)
15+
1016

1117
class ProjectMigrate25(ABAction):
1218
"""
@@ -23,16 +29,16 @@ class ProjectMigrate25(ABAction):
2329
@exception_dialogs
2430
def run(name: str = None):
2531
if name is None:
26-
name = projects.current
32+
name = bd.projects.current
2733

2834
dialog = MigrateDialog(name, application.main_window)
2935
dialog.exec_()
3036

3137
if dialog.result() == dialog.DialogCode.Rejected:
3238
return
3339

34-
if name != projects.current:
35-
projects.set_current(name, update=False)
40+
if name != bd.projects.current:
41+
bd.projects.set_current(name, update=False)
3642

3743
# setup dialog
3844
progress = QtWidgets.QProgressDialog(
@@ -51,6 +57,7 @@ def run(name: str = None):
5157
thread = MigrateThread(application)
5258
thread.finished.connect(lambda: progress.deleteLater())
5359
thread.start()
60+
thread.connect_progress_dialog(progress)
5461

5562

5663
class MigrateDialog(QtWidgets.QDialog):
@@ -85,6 +92,74 @@ def __init__(self, project_name: str, parent=None):
8592

8693
class MigrateThread(ABThread):
8794
def run_safely(self):
88-
projects.migrate_project_25()
89-
projects.set_current(projects.current)
95+
self.pre_process_methods()
96+
97+
log.info("Updating and processing all datasets in the project")
98+
bd.projects.set_current(bd.projects.current)
99+
100+
for db_name in bd.databases:
101+
self.update_database_activity_types(db_name)
102+
103+
# set the bw25 flag in the project dataset
104+
bd.projects.dataset.data["25"] = True
105+
bd.projects.dataset.save()
106+
107+
# reloading project to ensure all changes are applied
108+
bd.projects.set_current(bd.projects.current)
109+
110+
@classmethod
111+
def pre_process_methods(cls):
112+
log.info("Pre-processing methods for migration to bw25")
113+
data = {m: bd.Method(m).load() for m in bd.methods}
114+
df = pd.DataFrame([(k, v[0][0], v[0][1], v[1])
115+
for k, values in data.items() for v in values
116+
if isinstance(v[0], tuple) and len(v) == 2 and len(v[0]) == 2],
117+
columns=["method", "database", "code", "value"])
118+
119+
df = df.merge(AB_metadata.dataframe["id"], left_on=["database", "code"], right_index=True)
120+
121+
signals.method.blockSignals(True)
122+
signals.meta.blockSignals(True)
123+
124+
for name in tqdm(df["method"].unique(), desc="Pre-processing methods", unit="method", total=len(df["method"].unique())):
125+
method_df = df[df["method"] == name][["id", "value"]]
126+
method_list = list(method_df.itertuples(index=False, name=None))
127+
bd.Method(name).write(method_list, process=False)
128+
129+
signals.method.blockSignals(False)
130+
signals.meta.blockSignals(False)
131+
132+
return
133+
134+
@classmethod
135+
def update_database_activity_types(cls, db_name: str):
136+
database = bd.Database(db_name)
137+
write = False
138+
139+
if not isinstance(database, bd.backends.SQLiteBackend):
140+
return
141+
142+
log.info(f"Updating activity types in {db_name}")
143+
raw = database.load()
144+
145+
for key, ds in tqdm(raw.items(), desc=f"Updating activity types in {db_name}", unit="activity", total=len(raw)):
146+
if cls.activity_is_processwithreferenceproduct(ds):
147+
write = True
148+
ds["type"] = "processwithreferenceproduct"
149+
150+
if write:
151+
database.write(raw)
152+
153+
@staticmethod
154+
def activity_is_processwithreferenceproduct(ds: dict) -> bool:
155+
production = [exc for exc in ds.get("exchanges", []) if exc.get("type") == "production"]
156+
return (
157+
ds.get("type") in ["process", "processwithreferenceproduct"] and
158+
(
159+
len(production) == 0 or
160+
production[0].get("input") == (ds["database"], ds["code"])
161+
)
162+
)
163+
164+
90165

activity_browser/ui/threading.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from activity_browser.mod.tqdm.std import qt_tqdm
66

77
from qtpy.QtCore import QThread, SignalInstance, Signal
8+
from qtpy import QtWidgets
89

910

1011
class ABThread(QThread):
@@ -53,6 +54,21 @@ def _emit_status(self, progress: int, message: str):
5354
def run_safely(self, *args, **kwargs):
5455
raise NotImplementedError
5556

57+
def connect_progress_dialog(self, progress_dialog: QtWidgets.QProgressDialog):
58+
"""
59+
Connects the status signal to a progress dialog.
60+
"""
61+
def slot(progress, message):
62+
if progress == -1:
63+
progress_dialog.setLabelText(message)
64+
progress_dialog.setRange(0, 0)
65+
else:
66+
progress_dialog.setRange(0, 100)
67+
progress_dialog.setValue(progress)
68+
progress_dialog.setLabelText(message or "Working...")
69+
70+
self.status.connect(slot)
71+
5672

5773
class SafeBWConnection:
5874
def __enter__(self):

0 commit comments

Comments
 (0)