Skip to content

Commit 6eb2115

Browse files
authored
Merge pull request #1595 from mrvisscher/major-database-deletion
Database action reworks
2 parents d425ece + 9eb2499 commit 6eb2115

34 files changed

Lines changed: 433 additions & 2461 deletions

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
Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import List
2+
13
from qtpy import QtCore, QtWidgets
24

35
import bw2data as bd
@@ -12,72 +14,87 @@
1214

1315
class DatabaseDelete(ABAction):
1416
"""
15-
Deletes a specified database from the project after user confirmation.
17+
Deletes one or more databases from the project after user confirmation.
1618
1719
This method performs the following steps:
18-
- Displays a confirmation dialog to the user with the database name and record count.
19-
- If the user confirms, deletes the database, its upstream exchanges, and associated parameters.
20-
- Removes the database from the project settings.
20+
- Displays a confirmation dialog to the user with the database name(s) and total record count.
21+
- If the user confirms, deletes the database(s), their upstream exchanges, and associated parameters.
22+
- Removes the database(s) from the project settings.
2123
2224
Args:
23-
db_name (str): The name of the database to be deleted.
25+
db_names (List[str]): The name(s) of the database(s) to be deleted.
2426
2527
Steps:
2628
- Set the cursor to a waiting state while gathering data for large databases.
27-
- Retrieve the record count for the specified database.
28-
- Construct a warning message with the database name and record count.
29+
- Retrieve the record count for the specified database(s).
30+
- Construct a warning message with the database name(s) and record count.
2931
- Display a confirmation dialog to the user.
3032
- If the user cancels, exit the method.
3133
- Set the cursor to a waiting state while performing the deletion.
32-
- Delete upstream exchanges associated with the database.
33-
- Remove the database from the Brightway2 project.
34+
- Delete upstream exchanges associated with the database(s).
35+
- Remove the database(s) from the Brightway2 project.
3436
- Delete database parameters.
35-
- Remove the database from the project settings.
37+
- Remove the database(s) from the project settings.
3638
- Restore the cursor to its default state.
3739
"""
3840

3941
icon = qicons.delete
40-
text = "Delete database"
41-
tool_tip = "Delete this database from the project"
42+
text = "Delete databases"
43+
tool_tip = "Delete database(s) from the project"
4244

4345
@staticmethod
4446
@exception_dialogs
45-
def run(db_name: str):
47+
def run(db_names: List[str]):
4648
# gathering data will take time for large databases
4749
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
4850

49-
# get the record count from the database controller
50-
db_name = db_name
51-
n_records = AB_metadata.dataframe[AB_metadata.dataframe["database"] == db_name].shape[0]
51+
# get the total record count from all databases
52+
total_records = 0
53+
for db_name in db_names:
54+
n_records = AB_metadata.dataframe[AB_metadata.dataframe["database"] == db_name].shape[0]
55+
total_records += n_records
5256

5357
# construct warning text
54-
text = f"Are you sure you want to delete database '{db_name}'?"
55-
if n_records:
56-
text += f" It contains {n_records} activities"
58+
if len(db_names) == 1:
59+
text = f"Are you sure you want to delete database <b>'{db_names[0]}'</b>?"
60+
if total_records:
61+
text += f" It contains {total_records} activities."
62+
else:
63+
text = f"Are you sure you want to delete {len(db_names)} databases?"
64+
if total_records:
65+
text += f" They contain {total_records} activities in total."
5766

5867
# ask the user for confirmation
5968
QtWidgets.QApplication.restoreOverrideCursor()
6069
response = QtWidgets.QMessageBox.question(
61-
application.main_window, "Delete database?", text
70+
application.main_window, build_title(db_names), text
6271
)
6372

6473
# return if the user cancels
65-
if response != response.Yes:
74+
if response != QtWidgets.QMessageBox.Yes:
6675
return
6776

6877
# deleting data will take time for large databases
6978
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
7079

71-
# delete upstream exchanges
72-
ExchangeDataset.delete().where(ExchangeDataset.input_database == db_name).execute()
80+
for db_name in db_names:
81+
# delete upstream exchanges
82+
ExchangeDataset.delete().where(ExchangeDataset.input_database == db_name).execute()
7383

74-
# instruct the DatabaseController to delete the database from the project.
75-
del bd.databases[db_name]
84+
# instruct the DatabaseController to delete the database from the project.
85+
del bd.databases[db_name]
7686

77-
# delete database parameters
78-
Group.delete().where(Group.name == db_name).execute()
87+
# delete database parameters
88+
Group.delete().where(Group.name == db_name).execute()
7989

80-
# remove database from project settings
81-
settings.project_settings.remove_db(db_name)
90+
# remove database from project settings
91+
settings.project_settings.remove_db(db_name)
8292

8393
QtWidgets.QApplication.restoreOverrideCursor()
94+
95+
96+
def build_title(db_names: List[str]) -> str:
97+
"""Build an appropriate title for the confirmation dialog."""
98+
if len(db_names) == 1:
99+
return "Delete database?"
100+
return "Delete databases?"

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.show()
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.show()
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]

0 commit comments

Comments
 (0)