Skip to content

Commit ef150fc

Browse files
committed
Refactor database export functionality to support Excel and BW2Package formats, and update UI components accordingly.
1 parent 87ee78a commit ef150fc

10 files changed

Lines changed: 308 additions & 32 deletions

File tree

activity_browser/actions/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
from .calculation_setup.cs_open import CSOpen
2929

3030
from .database.database_open import DatabaseOpen
31-
from .database.database_export import DatabaseExport
31+
from .database.database_export_excel import DatabaseExportExcel
32+
from .database.database_export_bw2package import DatabaseExportBW2Package
3233
from .database.database_new import DatabaseNew
3334
from .database.database_delete import DatabaseDelete
3435
from .database.database_duplicate import DatabaseDuplicate

activity_browser/actions/database/database_export.py

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from logging import getLogger
2+
from typing import List
3+
4+
from qtpy import QtWidgets
5+
6+
from activity_browser import application
7+
from activity_browser.actions.base import ABAction, exception_dialogs
8+
from activity_browser.ui import widgets, threading
9+
from activity_browser.bwutils import exporters
10+
11+
log = getLogger(__name__)
12+
13+
14+
class DatabaseExportBW2Package(ABAction):
15+
"""
16+
ABAction to export database(s) to BW2Package format (.bw2package).
17+
"""
18+
19+
# icon = icons.qicons.export_db
20+
text = "Export to .bw2package"
21+
tool_tip = "Export database(s) to BW2Package format"
22+
23+
@classmethod
24+
@exception_dialogs
25+
def run(cls, db_names: List[str] = None):
26+
if db_names is None:
27+
import bw2data as bd
28+
dialog = widgets.ABDatabaseSelectionDialog(
29+
parent=application.main_window,
30+
databases=sorted(bd.databases),
31+
title="Select databases to export to BW2Package"
32+
)
33+
if dialog.exec_() == QtWidgets.QDialog.Accepted:
34+
db_names = dialog.get_selected_databases()
35+
else:
36+
return
37+
38+
# Get export directory or file from user
39+
if len(db_names) == 1:
40+
# Single database - suggest a filename
41+
suggested_name = f"{db_names[0]}.bw2package"
42+
path, _ = QtWidgets.QFileDialog.getSaveFileName(
43+
parent=application.main_window,
44+
caption=f'Export database "{db_names[0]}" to BW2Package',
45+
directory=suggested_name,
46+
filter='Brightway2 Database Package (*.bw2package);; All files (*.*)'
47+
)
48+
else:
49+
# Multiple databases - ask for directory
50+
path = QtWidgets.QFileDialog.getExistingDirectory(
51+
parent=application.main_window,
52+
caption=f'Select directory to export {len(db_names)} databases',
53+
)
54+
55+
if not path:
56+
return
57+
58+
# Show export dialog
59+
context = {
60+
"db_names": db_names,
61+
"path": path,
62+
}
63+
export_dialog = ExportBW2PackageSetup(
64+
parent=application.main_window,
65+
title="Export to BW2Package",
66+
context=context
67+
)
68+
export_dialog.exec_()
69+
70+
71+
class ExportBW2PackageSetup(widgets.ABWizard):
72+
"""Wizard for exporting databases to BW2Package format."""
73+
74+
class ExportPage(widgets.ABThreadedWizardPage):
75+
"""Wizard page to export the selected database(s) to BW2Package."""
76+
title = "Exporting Database(s)"
77+
subtitle = "Exporting database(s) to .bw2package file(s)"
78+
79+
class Thread(threading.ABThread):
80+
"""Thread to handle the export process."""
81+
82+
def run_safely(self, db_names: List[str], path: str):
83+
"""Export the database(s) to BW2Package."""
84+
for db_name in db_names:
85+
try:
86+
success = exporters.store_database_as_package(db_name, path)
87+
if success:
88+
log.info(f"Successfully exported database '{db_name}' to BW2Package")
89+
else:
90+
log.error(f"Failed to export database '{db_name}'")
91+
raise RuntimeError(f"Database '{db_name}' not found")
92+
except Exception as e:
93+
log.error(f"Failed to export database '{db_name}': {e}")
94+
raise
95+
96+
def initializePage(self, context: dict):
97+
"""Start the export thread."""
98+
self.thread.start(context["db_names"], context["path"])
99+
100+
pages = [ExportPage]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from logging import getLogger
2+
from typing import List
3+
4+
from qtpy import QtWidgets
5+
6+
from activity_browser import application
7+
from activity_browser.actions.base import ABAction, exception_dialogs
8+
from activity_browser.ui import widgets, threading
9+
from activity_browser.bwutils import exporters
10+
11+
12+
log = getLogger(__name__)
13+
14+
15+
class DatabaseExportExcel(ABAction):
16+
"""
17+
ABAction to export database(s) to Excel format (.xlsx).
18+
"""
19+
20+
icon = application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon)
21+
text = "Export to Excel (.xlsx)"
22+
tool_tip = "Export database(s) to Excel format"
23+
24+
@classmethod
25+
@exception_dialogs
26+
def run(cls, db_names: List[str] = None):
27+
if db_names is None:
28+
import bw2data as bd
29+
dialog = widgets.ABDatabaseSelectionDialog(
30+
parent=application.main_window,
31+
databases=sorted(bd.databases),
32+
title="Select databases to export to Excel"
33+
)
34+
if dialog.exec_() == QtWidgets.QDialog.Accepted:
35+
db_names = dialog.get_selected_databases()
36+
else:
37+
return
38+
39+
# Get export directory or file from user
40+
if len(db_names) == 1:
41+
# Single database - suggest a filename
42+
suggested_name = f"lci-{db_names[0]}.xlsx"
43+
path, _ = QtWidgets.QFileDialog.getSaveFileName(
44+
parent=application.main_window,
45+
caption=f'Export database "{db_names[0]}" to Excel',
46+
directory=suggested_name,
47+
filter='Excel spreadsheet (*.xlsx);; All files (*.*)'
48+
)
49+
else:
50+
# Multiple databases - ask for directory
51+
path = QtWidgets.QFileDialog.getExistingDirectory(
52+
parent=application.main_window,
53+
caption=f'Select directory to export {len(db_names)} databases',
54+
)
55+
56+
if not path:
57+
return
58+
59+
# Show export dialog
60+
context = {
61+
"db_names": db_names,
62+
"path": path,
63+
}
64+
export_dialog = ExportExcelSetup(
65+
parent=application.main_window,
66+
title="Export to Excel",
67+
context=context
68+
)
69+
export_dialog.exec_()
70+
71+
72+
class ExportExcelSetup(widgets.ABWizard):
73+
"""Wizard for exporting databases to Excel format."""
74+
75+
class ExportPage(widgets.ABThreadedWizardPage):
76+
"""Wizard page to export the selected database(s) to Excel."""
77+
title = "Exporting Database(s)"
78+
subtitle = "Exporting database(s) to Excel file(s)"
79+
80+
class Thread(threading.ABThread):
81+
"""Thread to handle the export process."""
82+
83+
def run_safely(self, db_names: List[str], path: str):
84+
"""Export the database(s) to Excel."""
85+
for db_name in db_names:
86+
try:
87+
exporters.write_lci_excel(db_name, path)
88+
log.info(f"Successfully exported database '{db_name}' to Excel")
89+
except Exception as e:
90+
log.error(f"Failed to export database '{db_name}': {e}")
91+
raise
92+
93+
def initializePage(self, context: dict):
94+
"""Start the export thread."""
95+
self.thread.start(context["db_names"], context["path"])
96+
97+
pages = [ExportPage]

activity_browser/layouts/panes/databases.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,24 @@ class DatabasesView(widgets.ABTreeView):
108108
"modified": delegates.DateTimeDelegate,
109109
}
110110

111+
class ExportDatabaseContextMenu(widgets.ABMenu):
112+
menuSetup = [
113+
lambda m: m.setTitle("Export database" if len(m.parent().selected_databases) == 1 else "Export databases"),
114+
lambda m, p: m.add(actions.DatabaseExportExcel, p.selected_databases if p.selected_databases else [],
115+
enable=len(p.selected_databases) >= 1,
116+
text="to .xlsx",
117+
),
118+
lambda m, p: m.add(actions.DatabaseExportBW2Package, p.selected_databases if p.selected_databases else [],
119+
enable=len(p.selected_databases) >= 1,
120+
text="to .bw2package",
121+
),
122+
]
123+
111124
class ContextMenu(widgets.ABMenu):
112125
menuSetup = [
113126
lambda m, p: m.add(actions.DatabaseNew),
114127
lambda m: m.addMenu(ImportDatabaseMenu(m)),
128+
lambda m, p: m.addMenu(DatabasesView.ExportDatabaseContextMenu(parent=p)),
115129
lambda m: m.addSeparator(),
116130
lambda m, p: m.add(actions.DatabaseDelete, p.selected_databases if p.selected_databases else [],
117131
enable=len(p.selected_databases) >= 1,

activity_browser/ui/menu_bar.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ def __init__(self, parent=None) -> None:
4646
self.import_proj_action = actions.ProjectImport.get_QAction()
4747
self.export_proj_action = actions.ProjectExport.get_QAction()
4848

49-
self.export_db_action = actions.DatabaseExport.get_QAction()
50-
5149
self.manage_settings_action = actions.SettingsWizardOpen.get_QAction()
5250
self.manage_projects_action = actions.ProjectManagerOpen.get_QAction()
5351

@@ -60,7 +58,7 @@ def __init__(self, parent=None) -> None:
6058
self.addAction(self.export_proj_action)
6159
self.addSeparator()
6260
self.addMenu(ImportDatabaseMenu(self))
63-
self.addAction(self.export_db_action)
61+
self.addMenu(ExportDatabaseMenu(self))
6462
self.addSeparator()
6563
self.addMenu(ImportICMenu(self))
6664
self.addSeparator()
@@ -286,6 +284,21 @@ def __init__(self, parent=None) -> None:
286284
self.addAction(self.import_from_ecoinvent_action)
287285

288286

287+
class ExportDatabaseMenu(QtWidgets.QMenu):
288+
def __init__(self, parent=None) -> None:
289+
super().__init__(parent=parent)
290+
self.setTitle("Export database")
291+
292+
self.export_to_excel_action = actions.DatabaseExportExcel.get_QAction()
293+
self.export_to_bw2package_action = actions.DatabaseExportBW2Package.get_QAction()
294+
295+
self.export_to_excel_action.setText("to .xlsx")
296+
self.export_to_bw2package_action.setText("to .bw2package")
297+
298+
self.addAction(self.export_to_excel_action)
299+
self.addAction(self.export_to_bw2package_action)
300+
301+
289302
class ImportICMenu(QtWidgets.QMenu):
290303
def __init__(self, parent=None) -> None:
291304
super().__init__(parent=parent)

activity_browser/ui/widgets/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from .central import CentralTabWidget
2323
from .menu import ABMenu
2424
from .list_edit_dialog import ABListEditDialog
25+
from .database_selection_dialog import ABDatabaseSelectionDialog
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import List
2+
3+
from qtpy import QtWidgets
4+
5+
6+
class ABDatabaseSelectionDialog(QtWidgets.QDialog):
7+
"""Dialog to select one or more databases for export."""
8+
9+
def __init__(self, parent=None, databases=None, title="Select databases"):
10+
super().__init__(parent=parent)
11+
self.setWindowTitle(title)
12+
self.setModal(True)
13+
self.resize(400, 300)
14+
15+
layout = QtWidgets.QVBoxLayout(self)
16+
17+
self.db_list_widget = QtWidgets.QListWidget(self)
18+
self.db_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
19+
for db_name in databases:
20+
item = QtWidgets.QListWidgetItem(db_name)
21+
self.db_list_widget.addItem(item)
22+
layout.addWidget(self.db_list_widget)
23+
24+
button_box = QtWidgets.QDialogButtonBox(
25+
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
26+
parent=self
27+
)
28+
button_box.accepted.connect(self.accept)
29+
button_box.rejected.connect(self.reject)
30+
layout.addWidget(button_box)
31+
32+
def get_selected_databases(self) -> List[str]:
33+
"""Return the list of selected database names."""
34+
selected_items = self.db_list_widget.selectedItems()
35+
return [item.text() for item in selected_items]

activity_browser/ui/widgets/menu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class ABMenu(QtWidgets.QMenu):
77
menuSetup: list[Callable[["ABMenu", Optional[QtWidgets.QWidget]], None]]
88
title: str = None
99

10-
def __init__(self, pos, parent=None):
10+
def __init__(self, pos=None, parent=None, title: str = None):
1111
super().__init__(parent)
1212

1313
for item in self.menuSetup:

0 commit comments

Comments
 (0)