Skip to content

Commit 4adb1e9

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/main' into project-setup-wizard
# Conflicts: # .github/workflows/main.yaml
2 parents 760fc19 + 5c523ee commit 4adb1e9

24 files changed

Lines changed: 2022 additions & 201 deletions
Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,59 @@
1-
from .activity.activity_delete import ActivityDelete
1+
from .activity.activity_relink import ActivityRelink
2+
from .activity.activity_new import ActivityNew
23
from .activity.activity_duplicate import ActivityDuplicate
3-
from .activity.activity_duplicate_to_db import ActivityDuplicateToDB
4-
from .activity.activity_duplicate_to_loc import ActivityDuplicateToLoc
4+
from .activity.activity_open import ActivityOpen
55
from .activity.activity_graph import ActivityGraph
6+
from .activity.activity_duplicate_to_loc import ActivityDuplicateToLoc
7+
from .activity.activity_delete import ActivityDelete
8+
from .activity.activity_duplicate_to_db import ActivityDuplicateToDB
69
from .activity.activity_modify import ActivityModify
7-
from .activity.activity_new import ActivityNew
8-
from .activity.activity_open import ActivityOpen
9-
from .activity.activity_relink import ActivityRelink
10-
from .biosphere_update import BiosphereUpdate
10+
11+
from .calculation_setup.cs_new import CSNew
1112
from .calculation_setup.cs_delete import CSDelete
1213
from .calculation_setup.cs_duplicate import CSDuplicate
13-
from .calculation_setup.cs_new import CSNew
1414
from .calculation_setup.cs_rename import CSRename
15-
from .database.database_delete import DatabaseDelete
16-
from .database.database_duplicate import DatabaseDuplicate
17-
from .database.database_export import DatabaseExport
15+
1816
from .database.database_import import DatabaseImport
17+
from .database.database_export import DatabaseExport
1918
from .database.database_new import DatabaseNew
19+
from .database.database_delete import DatabaseDelete
20+
from .database.database_duplicate import DatabaseDuplicate
2021
from .database.database_relink import DatabaseRelink
21-
from .default_install import DefaultInstall
22-
from .exchange.exchange_copy_sdf import ExchangeCopySDF
22+
23+
from .exchange.exchange_new import ExchangeNew
2324
from .exchange.exchange_delete import ExchangeDelete
24-
from .exchange.exchange_formula_remove import ExchangeFormulaRemove
2525
from .exchange.exchange_modify import ExchangeModify
26-
from .exchange.exchange_new import ExchangeNew
26+
from .exchange.exchange_formula_remove import ExchangeFormulaRemove
2727
from .exchange.exchange_uncertainty_modify import ExchangeUncertaintyModify
2828
from .exchange.exchange_uncertainty_remove import ExchangeUncertaintyRemove
29+
from .exchange.exchange_copy_sdf import ExchangeCopySDF
30+
31+
from .method.method_duplicate import MethodDuplicate
32+
from .method.method_delete import MethodDelete
33+
34+
from .method.cf_uncertainty_modify import CFUncertaintyModify
2935
from .method.cf_amount_modify import CFAmountModify
30-
from .method.cf_new import CFNew
3136
from .method.cf_remove import CFRemove
32-
from .method.cf_uncertainty_modify import CFUncertaintyModify
37+
from .method.cf_new import CFNew
3338
from .method.cf_uncertainty_remove import CFUncertaintyRemove
34-
from .method.method_delete import MethodDelete
35-
from .method.method_duplicate import MethodDuplicate
36-
from .parameter.parameter_clear_broken import ParameterClearBroken
37-
from .parameter.parameter_delete import ParameterDelete
38-
from .parameter.parameter_modify import ParameterModify
39+
3940
from .parameter.parameter_new import ParameterNew
4041
from .parameter.parameter_new_automatic import ParameterNewAutomatic
4142
from .parameter.parameter_rename import ParameterRename
42-
from .parameter.parameter_uncertainty_modify import ParameterUncertaintyModify
43+
from .parameter.parameter_delete import ParameterDelete
44+
from .parameter.parameter_modify import ParameterModify
4345
from .parameter.parameter_uncertainty_remove import ParameterUncertaintyRemove
44-
from .plugin_wizard_open import PluginWizardOpen
45-
from .project.project_delete import ProjectDelete
46-
from .project.project_duplicate import ProjectDuplicate
46+
from .parameter.parameter_uncertainty_modify import ParameterUncertaintyModify
47+
from .parameter.parameter_clear_broken import ParameterClearBroken
48+
4749
from .project.project_new import ProjectNew
50+
from .project.project_duplicate import ProjectDuplicate
51+
from .project.project_delete import ProjectDelete
4852
from .project.project_switch import ProjectSwitch
53+
from .project.project_export import ProjectExport
54+
from .project.project_import import ProjectImport
55+
56+
from .default_install import DefaultInstall
57+
from .biosphere_update import BiosphereUpdate
58+
from .plugin_wizard_open import PluginWizardOpen
4959
from .settings_wizard_open import SettingsWizardOpen

activity_browser/actions/activity/activity_duplicate_to_db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ActivityDuplicateToDB(ABAction):
2424
@classmethod
2525
@exception_dialogs
2626
def run(cls, activity_keys: List[tuple], to_db: str = None):
27-
# get bw activity objects from keys
27+
# from activity_browser.bwutils.metadata import AB_metadata
2828
activities = [bd.get_activity(key) for key in activity_keys]
2929

3030
if to_db and not cls.confirm_db(to_db):

activity_browser/actions/base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ def get_QAction(cls, *args, **kwargs) -> QtWidgets.QAction:
3232
@classmethod
3333
def get_QButton(cls, *args, **kwargs):
3434
"""Convenience function to return a button that has this ABAction as default action."""
35-
button = QtWidgets.QToolButton(None)
36-
action = cls.get_QAction(*args, **kwargs)
37-
38-
button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
39-
button.setDefaultAction(action)
35+
button = QtWidgets.QPushButton(
36+
cls.icon,
37+
cls.text
38+
)
39+
button.clicked.connect(lambda x: cls.run(*args, **kwargs))
4040
return button
4141

4242

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import os
2+
import json
3+
import tarfile
4+
5+
from PySide2 import QtWidgets, QtCore
6+
7+
from activity_browser import application, log
8+
from activity_browser.mod import bw2data as bd
9+
from activity_browser.actions.base import ABAction, exception_dialogs
10+
from activity_browser.ui.threading import ABThread
11+
12+
13+
class ProjectExport(ABAction):
14+
"""
15+
ABAction to export the current project. Prompts the user to return a save-file location. And then start a thread to
16+
package the project and save it there. Saving code copied from bw2data.backup.
17+
"""
18+
icon = application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon)
19+
text = "&Export this project..."
20+
tool_tip = "Export project to file"
21+
22+
@staticmethod
23+
@exception_dialogs
24+
def run():
25+
"""Export the current project to a folder chosen by the user."""
26+
# get target path from the user
27+
save_path, save_type = QtWidgets.QFileDialog.getSaveFileName(
28+
parent=application.main_window,
29+
caption="Choose where",
30+
dir=os.path.expanduser(f"~/{bd.projects.current}.tar.gz"),
31+
filter="Tar-file (*.tar.gz)"
32+
)
33+
34+
if not save_path: return
35+
36+
# setup dialog
37+
progress = QtWidgets.QProgressDialog(
38+
parent=application.main_window,
39+
labelText="Exporting project",
40+
maximum=0
41+
)
42+
progress.setCancelButton(None)
43+
progress.setWindowTitle("Exporting project")
44+
progress.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)
45+
progress.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False)
46+
progress.findChild(QtWidgets.QProgressBar).setTextVisible(False)
47+
progress.resize(400, 100)
48+
progress.show()
49+
50+
thread = ExportThread(application)
51+
setattr(thread, "save_path", save_path)
52+
thread.finished.connect(lambda: progress.deleteLater())
53+
thread.start()
54+
55+
56+
class ExportThread(ABThread):
57+
58+
def run_safely(self):
59+
project_name = bd.projects.current
60+
project_dir = os.path.join(bd.projects._base_data_dir, bd.utils.safe_filename(project_name))
61+
62+
with open(os.path.join(project_dir, ".project-name.json"), "w") as f:
63+
json.dump({"name": project_name}, f)
64+
65+
log.info("Creating project backup archive - this could take a few minutes...")
66+
with tarfile.open(self.save_path, "w:gz") as tar:
67+
tar.add(project_dir, arcname=bd.utils.safe_filename(project_name))
68+
69+
log.info(f"Project `{project_name}` exported.")
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import codecs
3+
import json
4+
import tarfile
5+
6+
from PySide2 import QtWidgets, QtCore
7+
from bw2io import backup
8+
9+
from activity_browser import application, log
10+
from activity_browser.mod import bw2data as bd
11+
from activity_browser.actions.base import ABAction, exception_dialogs
12+
from activity_browser.ui.icons import qicons
13+
from activity_browser.ui.threading import ABThread
14+
15+
16+
class ProjectImport(ABAction):
17+
"""
18+
ABAction to import a new project. Prompts the user to select a file. Imports the project name from the file as a
19+
suggestion. Prompts user to either accept the name or change it. If the name already exists, try again. Else,
20+
perform the import in a separate thread and show a progress dialog until it is finished. Finally, move to the newly
21+
imported project.
22+
"""
23+
icon = qicons.import_db
24+
text = "&Import a project..."
25+
tool_tip = "Import project from a file"
26+
27+
@classmethod
28+
@exception_dialogs
29+
def run(cls):
30+
"""Import a project into AB based on file chosen by user."""
31+
32+
# get the path from the user
33+
path, _ = QtWidgets.QFileDialog.getOpenFileName(
34+
parent=application.main_window,
35+
caption='Choose project file to import',
36+
filter='Tar archive (*.tar.gz);; All files (*.*)'
37+
)
38+
if not path: return
39+
40+
# create a name suggestion based on the file name
41+
suggestion = cls.get_project_name(path)
42+
43+
# get a new project name from the user:
44+
while True:
45+
project_name, _ = QtWidgets.QInputDialog.getText(
46+
application.main_window,
47+
'Choose project name',
48+
'Choose a name for your project',
49+
text=suggestion
50+
)
51+
52+
if not project_name: return
53+
54+
if project_name in bd.projects:
55+
# this name already exists, inform user and ask again.
56+
QtWidgets.QMessageBox.information(
57+
application.main_window,
58+
"Not possible.",
59+
"A project with this name already exists."
60+
)
61+
else: break
62+
63+
# setup dialog
64+
progress = QtWidgets.QProgressDialog(
65+
parent=application.main_window,
66+
labelText="Importing project",
67+
maximum=0
68+
)
69+
progress.setCancelButton(None)
70+
progress.setWindowTitle("Importing project")
71+
progress.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)
72+
progress.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False)
73+
progress.findChild(QtWidgets.QProgressBar).setTextVisible(False)
74+
progress.resize(400, 100)
75+
progress.show()
76+
77+
# setup the import
78+
thread = ImportThread(application)
79+
setattr(thread, "path", path)
80+
setattr(thread, "project_name", project_name)
81+
82+
thread.finished.connect(lambda: progress.deleteLater())
83+
thread.finished.connect(lambda: bd.projects.set_current(project_name))
84+
85+
# start the import
86+
thread.start()
87+
88+
@staticmethod
89+
def get_project_name(fp):
90+
reader = codecs.getreader("utf-8")
91+
# See https://stackoverflow.com/questions/68997850/python-readlines-with-tar-file-gives-streamerror-seeking-backwards-is-not-al/68998071#68998071
92+
with tarfile.open(fp, "r:gz") as tar:
93+
for member in tar:
94+
if member.name[-17:] == "project-name.json":
95+
return json.load(reader(tar.extractfile(member)))["name"]
96+
raise ValueError("Couldn't find project name file in archive")
97+
98+
99+
class ImportThread(ABThread):
100+
101+
def run_safely(self):
102+
log.debug('Starting project import:'
103+
f'\nPATH: {self.path}'
104+
f'\nNAME: {self.project_name}')
105+
backup.restore_project_directory(fp=self.path, project_name=self.project_name)
106+
log.info(f"Project `{self.project_name}` imported.")
107+

activity_browser/bwutils/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
99
1010
"""
11+
from bw2data.errors import *
12+
from bw2io.errors import *
1113

1214

1315
class ABError(Exception):

0 commit comments

Comments
 (0)