Skip to content

Commit

Permalink
Merge pull request #1314 from mrvisscher/legacy_biosphere_setup
Browse files Browse the repository at this point in the history
Add options for legacy biosphere versions to project setup wizard
  • Loading branch information
mrvisscher authored Jun 27, 2024
2 parents ab9cdaa + 91936c0 commit 5a6b623
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 113 deletions.
37 changes: 35 additions & 2 deletions activity_browser/mod/bw2io/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
from bw2io import *

from activity_browser import log
from activity_browser.info import __ei_versions__
from activity_browser.utils import sort_semantic_versions


def ab_bw2setup():
def ab_bw2setup(version):
import bw2io as bi
from activity_browser.mod.bw2io.importers.ecospold2_biosphere import ABEcospold2BiosphereImporter
from .migrations import ab_create_core_migrations

ab_create_core_migrations()

bio_import = ABEcospold2BiosphereImporter()
version = version[:3]

if version == sort_semantic_versions(__ei_versions__)[0][:3]:
log.info(f"Installing biosphere version >{version}<")
# most recent version
bio_import = ABEcospold2BiosphereImporter()
else:
log.info(f"Installing legacy biosphere version >{version}<")
# not most recent version, import legacy biosphere from AB
bio_import = ABEcospold2BiosphereImporter(version=version)
bio_import.apply_strategies()
log.info("Writing biosphere database")
bio_import.write_database()

log.info("Writing LCIA methods")
create_default_lcia_methods()

# patching biosphere
sorted_versions = sort_semantic_versions(
__ei_versions__, highest_to_lowest=False
)
ei_versions = sorted_versions[: sorted_versions.index(version) + 1]

patches = [
patch
for patch in dir(bi.data)
if patch.startswith("add_ecoinvent")
and patch.endswith("biosphere_flows")
and any(version.replace(".", "") in patch for version in ei_versions)
]

for patch in patches:
log.info(f"Applying biosphere patch: {patch}")
update_bio = getattr(bi.data, patch)
update_bio()

48 changes: 39 additions & 9 deletions activity_browser/mod/bw2io/importers/ecospold2_biosphere.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from zipfile import ZipFile

from bw2io.importers.ecospold2_biosphere import *
import pyprind
import logging
import os

from activity_browser.info import __ei_versions__
from activity_browser.utils import sort_semantic_versions


class ABEcospold2BiosphereImporter(Ecospold2BiosphereImporter):
Expand Down Expand Up @@ -47,16 +53,40 @@ def extract_flow_data(o):
)
return ds

if not filepath:
import bw2io.importers.ecospold2_biosphere as mod
filepath = (
Path(mod.__file__).parent.parent.resolve()
/ "data"
/ "lci"
/ f"ecoinvent elementary flows {version}.xml"
)
if version != '3.9' and not filepath:
import activity_browser.bwutils as mod
lci_dirpath = os.path.join(os.path.dirname(mod.__file__), "ecoinvent_biosphere_versions", "legacy_biosphere")

# find the most recent legacy biosphere that is equal to or older than chosen version
for ei_version in sort_semantic_versions(__ei_versions__):
use_version = ei_version
zip_fp = os.path.join(
lci_dirpath, f"ecoinvent elementary flows {use_version}.xml.zip"
)
if sort_semantic_versions([version, ei_version])[
0
] == version and os.path.isfile(zip_fp):
# this version is equal/lower and available
break

# extract the xml from the zip
with ZipFile(zip_fp) as zipped_file:
with zipped_file.open(
f"ecoinvent elementary flows {use_version}.xml"
) as file:
root = objectify.parse(file).getroot()
else:
if not filepath:
import bw2io.importers.ecospold2_biosphere as mod
filepath = (
Path(mod.__file__).parent.parent.resolve()
/ "data"
/ "lci"
/ f"ecoinvent elementary flows {version}.xml"
)

root = objectify.parse(open(filepath, encoding="utf-8")).getroot()

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

# AB implementation: added prog_bar here
Expand Down
3 changes: 2 additions & 1 deletion activity_browser/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import appdirs
from PySide2.QtWidgets import QMessageBox

from activity_browser import log, signals
from activity_browser import log
from activity_browser.signals import signals
from activity_browser.mod import bw2data as bd


Expand Down
163 changes: 66 additions & 97 deletions activity_browser/ui/wizards/db_import_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path

import bw2data.errors
import ecoinvent_interface
import ecoinvent_interface as ei
import requests
from bw2io import BW2Package, SingleOutputEcospold2Importer
from bw2io.extractors import Ecospold2DataExtractor
Expand Down Expand Up @@ -97,7 +97,7 @@ def system_model(self):

@property
def release_type(self):
return ecoinvent_interface.ReleaseType.ecospold
return ei.ReleaseType.ecospold

def update_downloader(self):
self.downloader.version = self.version
Expand Down Expand Up @@ -239,20 +239,10 @@ def __init__(self, parent=None):
layout.addWidget(box)
self.setLayout(layout)

def validatePage(self):
if (
self.wizard.has_existing_remote_credentials()
and self.radio_buttons[0].isChecked()
):
self.has_valid_remote_creds, _ = self.wizard.downloader.login()
return True

def nextId(self):
option_id = [b.isChecked() for b in self.radio_buttons].index(True)
self.wizard.import_type = self.OPTIONS[option_id][1]
next_id = self.OPTIONS[option_id][2]
if next_id == DatabaseImportWizard.EI_LOGIN and self.has_valid_remote_creds:
return DatabaseImportWizard.EI_VERSION
return next_id


Expand Down Expand Up @@ -369,7 +359,7 @@ def __init__(self, parent=None):
self.setLayout(layout)

def initializePage(self):
self.stored_dbs = ecoinvent_interface.CachedStorage()
self.stored_dbs = ei.CachedStorage()
self.stored_combobox.clear()
self.stored_combobox.addItems(
sorted(
Expand Down Expand Up @@ -1013,90 +1003,68 @@ def delete_canceled_db(self):


class EcoinventLoginPage(QtWidgets.QWizardPage):

def __init__(self, parent=None):
super().__init__(parent)
self.wizard = parent
self.complete = False
eco_settings = ecoinvent_interface.Settings()
self.username_edit = QtWidgets.QLineEdit()
if eco_settings.username:
self.username_edit.setText(eco_settings.username)
else:
self.username_edit.setPlaceholderText("ecoinvent username")
self.password_edit = QtWidgets.QLineEdit()
self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password)
if eco_settings.password:
self.password_edit.setText(eco_settings.password)
else:
self.password_edit.setPlaceholderText("ecoinvent password")
self.save_creds = QtWidgets.QPushButton("Save Credentials")
self.save_creds.clicked.connect(self.save_credentials)
self.login_button = QtWidgets.QPushButton("login")
self.login_button.clicked.connect(self.login)
self.password_edit.returnPressed.connect(self.login_button.click)
self.success_label = QtWidgets.QLabel()

self.valid_un = None
self.valid_pw = None

box = QtWidgets.QGroupBox("Login to the ecoinvent homepage:")
box_layout = QtWidgets.QVBoxLayout()
box_layout.addWidget(self.username_edit)
box_layout.addWidget(self.password_edit)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(self.login_button)
hlay.addWidget(self.save_creds)
hlay.addStretch(1)
box_layout.addLayout(hlay)
box_layout.addWidget(self.success_label)
box.setLayout(box_layout)
box.setStyleSheet(style_group_box.border_title)

self.setTitle("Login")
self.setSubTitle("Login with your ecoinvent credentials to authorize the download")

# create username field
self.username = QtWidgets.QLineEdit()
self.username.setPlaceholderText('ecoinvent username')
self.registerField("username*", self.username)

# create password field and set hidden
self.password = QtWidgets.QLineEdit()
self.password.setPlaceholderText('ecoinvent password'),
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
self.registerField("password*", self.password)

# empty message for now, will be used in case of wrong password or other error
self.message = QtWidgets.QLabel()

# set layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(box)
layout.addWidget(self.username)
layout.addWidget(self.password)
layout.addWidget(self.message)

self.setLayout(layout)

self.login_thread = LoginThread(self.wizard.downloader)
import_signals.login_success.connect(self.login_response)
def initializePage(self):
# on initialization set stored username & password
settings = ei.Settings()
self.username.setText(settings.username)
self.password.setText(settings.password)

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

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

def isComplete(self):
return self.complete
# logon was unsuccesful
except requests.exceptions.HTTPError as e:
QtWidgets.QApplication.restoreOverrideCursor()

@Slot(name="EidlLogin")
def login(self) -> None:
self.success_label.setText("Trying to login ...")
self.login_thread.update(self.username, self.password)
self.login_thread.start()

@Slot(name="SaveEiCredentials")
def save_credentials(self):
self.success_label.setText("Saving Credentials")
ecoinvent_interface.permanent_setting("username", self.username)
ecoinvent_interface.permanent_setting("password", self.password)
self.success_label.setText("Saved Credentials")

@Slot(bool, name="handleLoginResponse")
def login_response(self, success: bool) -> None:
if not success:
self.success_label.setText("Login failed!")
self.complete = False
else:
self.username_edit.setEnabled(False)
self.password_edit.setEnabled(False)
self.login_button.setEnabled(False)
self.valid_un = self.username
self.valid_pw = self.password
self.success_label.setText("Login successful!")
self.login_thread.exit()
self.complete = True
self.completeChanged.emit()
# in case of 401: Unauthorized, we prompt for a retry of logon
if e.response.status_code == 401:
self.message.setText("Invalid username and/or password, please try again.")
return False
# else, other HTTPError, try again later maybe? Raise exception for logging
else:
self.message.setText("Unknown connection error, try again later.")
raise e

# in case of success, set the settings for permanent use
ei.permanent_setting("username", self.username.text())
ei.permanent_setting("password", self.password.text())
return True

def nextId(self):
return DatabaseImportWizard.EI_VERSION
Expand All @@ -1119,7 +1087,7 @@ def run(self):
log.error(str(e), exc_info=True)
import_signals.login_success.emit(False)
msg = str(e)
cs = ecoinvent_interface.CachedStorage()
cs = ei.CachedStorage()
if len(cs.catalogue) > 0:
msg += (
"\n\nIf you work offline you can use your previously downloaded databases"
Expand Down Expand Up @@ -1158,6 +1126,7 @@ def __init__(self, parent=None):

def initializePage(self):
available_versions = self.wizard.downloader.list_versions()
QtWidgets.QApplication.restoreOverrideCursor()
shown_versions = {version for version in available_versions}
# Catch for incorrect 'universal' key presence
# (introduced in version 3.6 of ecoinvent)
Expand Down Expand Up @@ -1404,22 +1373,22 @@ def __init__(
self,
version: typing.Optional[str] = None,
system_model: typing.Optional[str] = None,
release_type: typing.Optional[ecoinvent_interface.ReleaseType] = None,
release_type: typing.Optional[ei.ReleaseType] = None,
):
self.version = version
self.system_model = system_model
self._release_type = release_type
self._settings = ecoinvent_interface.Settings()
self._settings = ei.Settings()
self.update_ecoinvent_release()

def update_ecoinvent_release(self):
try:
self._release = ecoinvent_interface.EcoinventRelease(self._settings)
self._release = ei.EcoinventRelease(self._settings)
except ValueError:
self._release = None

@property
def release(self) -> ecoinvent_interface.EcoinventRelease:
def release(self) -> ei.EcoinventRelease:
if self._release is None:
raise ValueError("ecoinvent release has not been initialized properly")
return self._release
Expand Down Expand Up @@ -1447,19 +1416,19 @@ def release_type(self):
return self._release_type

@release_type.setter
def release_type(self, value: typing.Union[str, ecoinvent_interface.ReleaseType]):
if isinstance(value, ecoinvent_interface.ReleaseType):
def release_type(self, value: typing.Union[str, ei.ReleaseType]):
if isinstance(value, ei.ReleaseType):
self._release_type = value
return

if isinstance(value, str):
self._release_type = ecoinvent_interface.ReleaseType[value]
self._release_type = ei.ReleaseType[value]
return

raise ValueError("invalid value provided for release_type")

def login(self) -> (bool, typing.Optional[typing.Tuple[str, str]]):
release = ecoinvent_interface.EcoinventRelease(self._settings)
release = ei.EcoinventRelease(self._settings)
error_message = None
try:
release.login()
Expand Down
Loading

0 comments on commit 5a6b623

Please sign in to comment.