Skip to content

Commit 5a6b623

Browse files
authored
Merge pull request #1314 from mrvisscher/legacy_biosphere_setup
Add options for legacy biosphere versions to project setup wizard
2 parents ab9cdaa + 91936c0 commit 5a6b623

6 files changed

Lines changed: 180 additions & 113 deletions

File tree

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,50 @@
11
from bw2io import *
22

33
from activity_browser import log
4+
from activity_browser.info import __ei_versions__
5+
from activity_browser.utils import sort_semantic_versions
46

57

6-
def ab_bw2setup():
8+
def ab_bw2setup(version):
9+
import bw2io as bi
710
from activity_browser.mod.bw2io.importers.ecospold2_biosphere import ABEcospold2BiosphereImporter
811
from .migrations import ab_create_core_migrations
912

1013
ab_create_core_migrations()
1114

12-
bio_import = ABEcospold2BiosphereImporter()
15+
version = version[:3]
16+
17+
if version == sort_semantic_versions(__ei_versions__)[0][:3]:
18+
log.info(f"Installing biosphere version >{version}<")
19+
# most recent version
20+
bio_import = ABEcospold2BiosphereImporter()
21+
else:
22+
log.info(f"Installing legacy biosphere version >{version}<")
23+
# not most recent version, import legacy biosphere from AB
24+
bio_import = ABEcospold2BiosphereImporter(version=version)
1325
bio_import.apply_strategies()
1426
log.info("Writing biosphere database")
1527
bio_import.write_database()
28+
1629
log.info("Writing LCIA methods")
1730
create_default_lcia_methods()
31+
32+
# patching biosphere
33+
sorted_versions = sort_semantic_versions(
34+
__ei_versions__, highest_to_lowest=False
35+
)
36+
ei_versions = sorted_versions[: sorted_versions.index(version) + 1]
37+
38+
patches = [
39+
patch
40+
for patch in dir(bi.data)
41+
if patch.startswith("add_ecoinvent")
42+
and patch.endswith("biosphere_flows")
43+
and any(version.replace(".", "") in patch for version in ei_versions)
44+
]
45+
46+
for patch in patches:
47+
log.info(f"Applying biosphere patch: {patch}")
48+
update_bio = getattr(bi.data, patch)
49+
update_bio()
50+

activity_browser/mod/bw2io/importers/ecospold2_biosphere.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
from zipfile import ZipFile
2+
13
from bw2io.importers.ecospold2_biosphere import *
24
import pyprind
35
import logging
6+
import os
7+
8+
from activity_browser.info import __ei_versions__
9+
from activity_browser.utils import sort_semantic_versions
410

511

612
class ABEcospold2BiosphereImporter(Ecospold2BiosphereImporter):
@@ -47,16 +53,40 @@ def extract_flow_data(o):
4753
)
4854
return ds
4955

50-
if not filepath:
51-
import bw2io.importers.ecospold2_biosphere as mod
52-
filepath = (
53-
Path(mod.__file__).parent.parent.resolve()
54-
/ "data"
55-
/ "lci"
56-
/ f"ecoinvent elementary flows {version}.xml"
57-
)
56+
if version != '3.9' and not filepath:
57+
import activity_browser.bwutils as mod
58+
lci_dirpath = os.path.join(os.path.dirname(mod.__file__), "ecoinvent_biosphere_versions", "legacy_biosphere")
59+
60+
# find the most recent legacy biosphere that is equal to or older than chosen version
61+
for ei_version in sort_semantic_versions(__ei_versions__):
62+
use_version = ei_version
63+
zip_fp = os.path.join(
64+
lci_dirpath, f"ecoinvent elementary flows {use_version}.xml.zip"
65+
)
66+
if sort_semantic_versions([version, ei_version])[
67+
0
68+
] == version and os.path.isfile(zip_fp):
69+
# this version is equal/lower and available
70+
break
71+
72+
# extract the xml from the zip
73+
with ZipFile(zip_fp) as zipped_file:
74+
with zipped_file.open(
75+
f"ecoinvent elementary flows {use_version}.xml"
76+
) as file:
77+
root = objectify.parse(file).getroot()
78+
else:
79+
if not filepath:
80+
import bw2io.importers.ecospold2_biosphere as mod
81+
filepath = (
82+
Path(mod.__file__).parent.parent.resolve()
83+
/ "data"
84+
/ "lci"
85+
/ f"ecoinvent elementary flows {version}.xml"
86+
)
87+
88+
root = objectify.parse(open(filepath, encoding="utf-8")).getroot()
5889

59-
root = objectify.parse(open(filepath, encoding="utf-8")).getroot()
6090
flow_data = []
6191

6292
# AB implementation: added prog_bar here

activity_browser/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import appdirs
99
from PySide2.QtWidgets import QMessageBox
1010

11-
from activity_browser import log, signals
11+
from activity_browser import log
12+
from activity_browser.signals import signals
1213
from activity_browser.mod import bw2data as bd
1314

1415

activity_browser/ui/wizards/db_import_wizard.py

Lines changed: 66 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pathlib import Path
1010

1111
import bw2data.errors
12-
import ecoinvent_interface
12+
import ecoinvent_interface as ei
1313
import requests
1414
from bw2io import BW2Package, SingleOutputEcospold2Importer
1515
from bw2io.extractors import Ecospold2DataExtractor
@@ -97,7 +97,7 @@ def system_model(self):
9797

9898
@property
9999
def release_type(self):
100-
return ecoinvent_interface.ReleaseType.ecospold
100+
return ei.ReleaseType.ecospold
101101

102102
def update_downloader(self):
103103
self.downloader.version = self.version
@@ -239,20 +239,10 @@ def __init__(self, parent=None):
239239
layout.addWidget(box)
240240
self.setLayout(layout)
241241

242-
def validatePage(self):
243-
if (
244-
self.wizard.has_existing_remote_credentials()
245-
and self.radio_buttons[0].isChecked()
246-
):
247-
self.has_valid_remote_creds, _ = self.wizard.downloader.login()
248-
return True
249-
250242
def nextId(self):
251243
option_id = [b.isChecked() for b in self.radio_buttons].index(True)
252244
self.wizard.import_type = self.OPTIONS[option_id][1]
253245
next_id = self.OPTIONS[option_id][2]
254-
if next_id == DatabaseImportWizard.EI_LOGIN and self.has_valid_remote_creds:
255-
return DatabaseImportWizard.EI_VERSION
256246
return next_id
257247

258248

@@ -369,7 +359,7 @@ def __init__(self, parent=None):
369359
self.setLayout(layout)
370360

371361
def initializePage(self):
372-
self.stored_dbs = ecoinvent_interface.CachedStorage()
362+
self.stored_dbs = ei.CachedStorage()
373363
self.stored_combobox.clear()
374364
self.stored_combobox.addItems(
375365
sorted(
@@ -1013,90 +1003,68 @@ def delete_canceled_db(self):
10131003

10141004

10151005
class EcoinventLoginPage(QtWidgets.QWizardPage):
1006+
10161007
def __init__(self, parent=None):
10171008
super().__init__(parent)
1018-
self.wizard = parent
1019-
self.complete = False
1020-
eco_settings = ecoinvent_interface.Settings()
1021-
self.username_edit = QtWidgets.QLineEdit()
1022-
if eco_settings.username:
1023-
self.username_edit.setText(eco_settings.username)
1024-
else:
1025-
self.username_edit.setPlaceholderText("ecoinvent username")
1026-
self.password_edit = QtWidgets.QLineEdit()
1027-
self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password)
1028-
if eco_settings.password:
1029-
self.password_edit.setText(eco_settings.password)
1030-
else:
1031-
self.password_edit.setPlaceholderText("ecoinvent password")
1032-
self.save_creds = QtWidgets.QPushButton("Save Credentials")
1033-
self.save_creds.clicked.connect(self.save_credentials)
1034-
self.login_button = QtWidgets.QPushButton("login")
1035-
self.login_button.clicked.connect(self.login)
1036-
self.password_edit.returnPressed.connect(self.login_button.click)
1037-
self.success_label = QtWidgets.QLabel()
1038-
1039-
self.valid_un = None
1040-
self.valid_pw = None
1041-
1042-
box = QtWidgets.QGroupBox("Login to the ecoinvent homepage:")
1043-
box_layout = QtWidgets.QVBoxLayout()
1044-
box_layout.addWidget(self.username_edit)
1045-
box_layout.addWidget(self.password_edit)
1046-
hlay = QtWidgets.QHBoxLayout()
1047-
hlay.addWidget(self.login_button)
1048-
hlay.addWidget(self.save_creds)
1049-
hlay.addStretch(1)
1050-
box_layout.addLayout(hlay)
1051-
box_layout.addWidget(self.success_label)
1052-
box.setLayout(box_layout)
1053-
box.setStyleSheet(style_group_box.border_title)
1009+
1010+
self.setTitle("Login")
1011+
self.setSubTitle("Login with your ecoinvent credentials to authorize the download")
1012+
1013+
# create username field
1014+
self.username = QtWidgets.QLineEdit()
1015+
self.username.setPlaceholderText('ecoinvent username')
1016+
self.registerField("username*", self.username)
1017+
1018+
# create password field and set hidden
1019+
self.password = QtWidgets.QLineEdit()
1020+
self.password.setPlaceholderText('ecoinvent password'),
1021+
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
1022+
self.registerField("password*", self.password)
1023+
1024+
# empty message for now, will be used in case of wrong password or other error
1025+
self.message = QtWidgets.QLabel()
1026+
1027+
# set layout
10541028
layout = QtWidgets.QVBoxLayout()
1055-
layout.addWidget(box)
1029+
layout.addWidget(self.username)
1030+
layout.addWidget(self.password)
1031+
layout.addWidget(self.message)
1032+
10561033
self.setLayout(layout)
10571034

1058-
self.login_thread = LoginThread(self.wizard.downloader)
1059-
import_signals.login_success.connect(self.login_response)
1035+
def initializePage(self):
1036+
# on initialization set stored username & password
1037+
settings = ei.Settings()
1038+
self.username.setText(settings.username)
1039+
self.password.setText(settings.password)
10601040

1061-
@property
1062-
def username(self) -> str:
1063-
return self.valid_un or self.username_edit.text()
1041+
def validatePage(self):
1042+
# set waitcursor because we're making http requests which take long
1043+
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
10641044

1065-
@property
1066-
def password(self) -> str:
1067-
return self.valid_pw or self.password_edit.text()
1045+
# set the provided settings and check if we can get a version list (i.e. logon was succesful)
1046+
try:
1047+
settings = ei.Settings(username=self.username.text(), password=self.password.text())
1048+
release = ei.EcoinventRelease(settings)
1049+
release.list_versions()
10681050

1069-
def isComplete(self):
1070-
return self.complete
1051+
# logon was unsuccesful
1052+
except requests.exceptions.HTTPError as e:
1053+
QtWidgets.QApplication.restoreOverrideCursor()
10711054

1072-
@Slot(name="EidlLogin")
1073-
def login(self) -> None:
1074-
self.success_label.setText("Trying to login ...")
1075-
self.login_thread.update(self.username, self.password)
1076-
self.login_thread.start()
1077-
1078-
@Slot(name="SaveEiCredentials")
1079-
def save_credentials(self):
1080-
self.success_label.setText("Saving Credentials")
1081-
ecoinvent_interface.permanent_setting("username", self.username)
1082-
ecoinvent_interface.permanent_setting("password", self.password)
1083-
self.success_label.setText("Saved Credentials")
1084-
1085-
@Slot(bool, name="handleLoginResponse")
1086-
def login_response(self, success: bool) -> None:
1087-
if not success:
1088-
self.success_label.setText("Login failed!")
1089-
self.complete = False
1090-
else:
1091-
self.username_edit.setEnabled(False)
1092-
self.password_edit.setEnabled(False)
1093-
self.login_button.setEnabled(False)
1094-
self.valid_un = self.username
1095-
self.valid_pw = self.password
1096-
self.success_label.setText("Login successful!")
1097-
self.login_thread.exit()
1098-
self.complete = True
1099-
self.completeChanged.emit()
1055+
# in case of 401: Unauthorized, we prompt for a retry of logon
1056+
if e.response.status_code == 401:
1057+
self.message.setText("Invalid username and/or password, please try again.")
1058+
return False
1059+
# else, other HTTPError, try again later maybe? Raise exception for logging
1060+
else:
1061+
self.message.setText("Unknown connection error, try again later.")
1062+
raise e
1063+
1064+
# in case of success, set the settings for permanent use
1065+
ei.permanent_setting("username", self.username.text())
1066+
ei.permanent_setting("password", self.password.text())
1067+
return True
11001068

11011069
def nextId(self):
11021070
return DatabaseImportWizard.EI_VERSION
@@ -1119,7 +1087,7 @@ def run(self):
11191087
log.error(str(e), exc_info=True)
11201088
import_signals.login_success.emit(False)
11211089
msg = str(e)
1122-
cs = ecoinvent_interface.CachedStorage()
1090+
cs = ei.CachedStorage()
11231091
if len(cs.catalogue) > 0:
11241092
msg += (
11251093
"\n\nIf you work offline you can use your previously downloaded databases"
@@ -1158,6 +1126,7 @@ def __init__(self, parent=None):
11581126

11591127
def initializePage(self):
11601128
available_versions = self.wizard.downloader.list_versions()
1129+
QtWidgets.QApplication.restoreOverrideCursor()
11611130
shown_versions = {version for version in available_versions}
11621131
# Catch for incorrect 'universal' key presence
11631132
# (introduced in version 3.6 of ecoinvent)
@@ -1404,22 +1373,22 @@ def __init__(
14041373
self,
14051374
version: typing.Optional[str] = None,
14061375
system_model: typing.Optional[str] = None,
1407-
release_type: typing.Optional[ecoinvent_interface.ReleaseType] = None,
1376+
release_type: typing.Optional[ei.ReleaseType] = None,
14081377
):
14091378
self.version = version
14101379
self.system_model = system_model
14111380
self._release_type = release_type
1412-
self._settings = ecoinvent_interface.Settings()
1381+
self._settings = ei.Settings()
14131382
self.update_ecoinvent_release()
14141383

14151384
def update_ecoinvent_release(self):
14161385
try:
1417-
self._release = ecoinvent_interface.EcoinventRelease(self._settings)
1386+
self._release = ei.EcoinventRelease(self._settings)
14181387
except ValueError:
14191388
self._release = None
14201389

14211390
@property
1422-
def release(self) -> ecoinvent_interface.EcoinventRelease:
1391+
def release(self) -> ei.EcoinventRelease:
14231392
if self._release is None:
14241393
raise ValueError("ecoinvent release has not been initialized properly")
14251394
return self._release
@@ -1447,19 +1416,19 @@ def release_type(self):
14471416
return self._release_type
14481417

14491418
@release_type.setter
1450-
def release_type(self, value: typing.Union[str, ecoinvent_interface.ReleaseType]):
1451-
if isinstance(value, ecoinvent_interface.ReleaseType):
1419+
def release_type(self, value: typing.Union[str, ei.ReleaseType]):
1420+
if isinstance(value, ei.ReleaseType):
14521421
self._release_type = value
14531422
return
14541423

14551424
if isinstance(value, str):
1456-
self._release_type = ecoinvent_interface.ReleaseType[value]
1425+
self._release_type = ei.ReleaseType[value]
14571426
return
14581427

14591428
raise ValueError("invalid value provided for release_type")
14601429

14611430
def login(self) -> (bool, typing.Optional[typing.Tuple[str, str]]):
1462-
release = ecoinvent_interface.EcoinventRelease(self._settings)
1431+
release = ei.EcoinventRelease(self._settings)
14631432
error_message = None
14641433
try:
14651434
release.login()

0 commit comments

Comments
 (0)