flood-adapt 1.0.4__py3-none-any.whl → 1.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flood_adapt/__init__.py CHANGED
@@ -1,11 +1,12 @@
1
1
  # has to be here at the start to avoid circular imports
2
- __version__ = "1.0.4"
2
+
3
+ __version__ = "1.0.6"
3
4
 
4
5
  from flood_adapt import adapter, database_builder, dbs_classes, objects
5
6
  from flood_adapt.config.config import Settings
6
7
  from flood_adapt.config.site import Site
7
8
  from flood_adapt.flood_adapt import FloodAdapt
8
- from flood_adapt.misc.exceptions import ComponentError, DatabaseError, FloodAdaptError
9
+ from flood_adapt.misc.exceptions import DatabaseError, FloodAdaptError
9
10
  from flood_adapt.misc.log import FloodAdaptLogging
10
11
  from flood_adapt.objects.forcing import unit_system
11
12
 
@@ -21,7 +22,6 @@ __all__ = [
21
22
  "database_builder",
22
23
  "FloodAdaptError",
23
24
  "DatabaseError",
24
- "ComponentError",
25
25
  ]
26
26
 
27
27
  FloodAdaptLogging() # Initialize logging once for the entire package
@@ -205,7 +205,7 @@ class FiatAdapter(IImpactAdapter):
205
205
  self.close_files()
206
206
  if self.model_root.exists():
207
207
  logger.info(f"Deleting {self.model_root}")
208
- shutil.rmtree(self.model_root)
208
+ shutil.rmtree(self.model_root, ignore_errors=True)
209
209
 
210
210
  def fiat_completed(self) -> bool:
211
211
  """Check if fiat has run as expected.
@@ -353,7 +353,7 @@ class FiatAdapter(IImpactAdapter):
353
353
  os.remove(os.path.join(subdir, file))
354
354
 
355
355
  if not os.listdir(subdir):
356
- os.rmdir(subdir)
356
+ shutil.rmtree(subdir, ignore_errors=True)
357
357
 
358
358
  if strict:
359
359
  raise RuntimeError(f"FIAT model failed to run in {path}.")
@@ -1574,5 +1574,5 @@ class FiatAdapter(IImpactAdapter):
1574
1574
  )
1575
1575
  if simulation_path.exists():
1576
1576
  self.close_files()
1577
- shutil.rmtree(simulation_path)
1577
+ shutil.rmtree(simulation_path, ignore_errors=True)
1578
1578
  logger.info(f"Deleted Delft-FIAT simulation folder: {simulation_path}")
@@ -212,7 +212,7 @@ class SfincsAdapter(IHazardAdapter):
212
212
  os.remove(os.path.join(subdir, file))
213
213
 
214
214
  if not os.listdir(subdir):
215
- os.rmdir(subdir)
215
+ shutil.rmtree(subdir, ignore_errors=True)
216
216
 
217
217
  if strict:
218
218
  raise RuntimeError(f"SFINCS model failed to run in {path}.")
@@ -828,7 +828,7 @@ class SfincsAdapter(IHazardAdapter):
828
828
 
829
829
  if sim_path.parent.exists() and not any(sim_path.parent.iterdir()):
830
830
  # Remove the parent directory `simulations` if it is empty
831
- sim_path.parent.rmdir()
831
+ shutil.rmtree(sim_path.parent, ignore_errors=True)
832
832
 
833
833
  def _run_risk_scenario(self, scenario: Scenario):
834
834
  """Run the whole workflow for a risk scenario.
@@ -1304,10 +1304,14 @@ class SfincsAdapter(IHazardAdapter):
1304
1304
  )
1305
1305
  return
1306
1306
 
1307
+ model_rivers = self._read_river_locations()
1308
+ if model_rivers.empty:
1309
+ logger.warning(
1310
+ "Cannot add discharge forcing: No rivers defined in the sfincs model."
1311
+ )
1312
+ return
1307
1313
  logger.info(f"Setting discharge forcing for river: {discharge.river.name}")
1308
-
1309
1314
  time_frame = self.get_model_time()
1310
- model_rivers = self._read_river_locations()
1311
1315
 
1312
1316
  # Check that the river is defined in the model and that the coordinates match
1313
1317
  river_loc = shapely.Point(
@@ -1705,17 +1709,22 @@ class SfincsAdapter(IHazardAdapter):
1705
1709
 
1706
1710
  def _read_river_locations(self) -> gpd.GeoDataFrame:
1707
1711
  path = self.get_model_root() / "sfincs.src"
1708
-
1709
- with open(path) as f:
1710
- lines = f.readlines()
1712
+ lines = []
1713
+ if path.exists():
1714
+ with open(path) as f:
1715
+ lines = f.readlines()
1711
1716
  coords = [(float(line.split()[0]), float(line.split()[1])) for line in lines]
1712
1717
  points = [shapely.Point(coord) for coord in coords]
1713
1718
 
1714
1719
  return gpd.GeoDataFrame({"geometry": points}, crs=self._model.crs)
1715
1720
 
1716
1721
  def _read_waterlevel_boundary_locations(self) -> gpd.GeoDataFrame:
1717
- with open(self.get_model_root() / "sfincs.bnd") as f:
1718
- lines = f.readlines()
1722
+ path = self.get_model_root() / "sfincs.bnd"
1723
+ lines = []
1724
+ if path.exists():
1725
+ with open(path) as f:
1726
+ lines = f.readlines()
1727
+
1719
1728
  coords = [(float(line.split()[0]), float(line.split()[1])) for line in lines]
1720
1729
  points = [shapely.Point(coord) for coord in coords]
1721
1730
 
@@ -97,7 +97,7 @@ class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
97
97
  sim_path = self._get_simulation_path()
98
98
  # SfincsAdapter.write() doesnt write the bca file apparently so we need to copy the template
99
99
  if sim_path.exists():
100
- shutil.rmtree(sim_path)
100
+ shutil.rmtree(sim_path, ignore_errors=True)
101
101
 
102
102
  with SfincsAdapter(model_root=self.template_path) as _offshore_model:
103
103
  # Load objects, set root & write template model
@@ -1,7 +1,6 @@
1
1
  import gc
2
2
  import os
3
3
  import shutil
4
- import time
5
4
  from pathlib import Path
6
5
  from typing import Any, Literal, Optional, Union
7
6
 
@@ -11,6 +10,8 @@ import pandas as pd
11
10
  import xarray as xr
12
11
  from geopandas import GeoDataFrame
13
12
 
13
+ from flood_adapt.adapter.fiat_adapter import FiatAdapter
14
+ from flood_adapt.adapter.sfincs_adapter import SfincsAdapter
14
15
  from flood_adapt.config.hazard import SlrScenariosModel
15
16
  from flood_adapt.config.impacts import FloodmapType
16
17
  from flood_adapt.config.site import Site
@@ -22,7 +23,7 @@ from flood_adapt.dbs_classes.dbs_scenario import DbsScenario
22
23
  from flood_adapt.dbs_classes.dbs_static import DbsStatic
23
24
  from flood_adapt.dbs_classes.dbs_strategy import DbsStrategy
24
25
  from flood_adapt.dbs_classes.interface.database import IDatabase
25
- from flood_adapt.misc.exceptions import DatabaseError
26
+ from flood_adapt.misc.exceptions import ConfigError, DatabaseError
26
27
  from flood_adapt.misc.log import FloodAdaptLogging
27
28
  from flood_adapt.misc.path_builder import (
28
29
  TopLevelDir,
@@ -140,8 +141,6 @@ class Database(IDatabase):
140
141
 
141
142
  def shutdown(self):
142
143
  """Explicitly shut down the singleton and clear all references."""
143
- import gc
144
-
145
144
  self._instance = None
146
145
  self._init_done = False
147
146
 
@@ -191,7 +190,7 @@ class Database(IDatabase):
191
190
  SLR scenarios configuration model with the file path set to the static path.
192
191
  """
193
192
  if self.site.sfincs.slr_scenarios is None:
194
- raise DatabaseError("No SLR scenarios defined in the site configuration.")
193
+ raise ConfigError("No SLR scenarios defined in the site configuration.")
195
194
  slr = self.site.sfincs.slr_scenarios
196
195
  slr.file = str(self.static_path / slr.file)
197
196
  return slr
@@ -234,7 +233,7 @@ class Database(IDatabase):
234
233
  if _type == FloodmapType.water_level:
235
234
  paths = [base_dir / "max_water_level_map.nc"]
236
235
  elif _type == FloodmapType.water_depth:
237
- paths = [base_dir / f"FloodMap_{self.name}.tif"]
236
+ paths = [base_dir / f"FloodMap_{scenario_name}.tif"]
238
237
  elif mode == Mode.risk:
239
238
  if _type == FloodmapType.water_level:
240
239
  paths = list(base_dir.glob("RP_*_maps.nc"))
@@ -577,20 +576,13 @@ class Database(IDatabase):
577
576
  for dir in os.listdir(self.scenarios.output_path)
578
577
  ]
579
578
 
580
- def _call_garbage_collector(func, path, exc_info, retries=5, delay=0.1):
581
- """Retry deletion up to 5 times if the file is locked."""
582
- for attempt in range(retries):
583
- gc.collect()
584
- time.sleep(delay)
585
- try:
586
- func(path) # Retry deletion
587
- return # Exit if successful
588
- except Exception as e:
589
- print(
590
- f"Attempt {attempt + 1}/{retries} failed to delete {path}: {e}"
591
- )
592
-
593
- print(f"Giving up on deleting {path} after {retries} attempts.")
579
+ # Init model instances outside the loop
580
+ overland = self.static.get_overland_sfincs_model()
581
+ fiat = self.static.get_fiat_model()
582
+ if self.site.sfincs.config.offshore_model is not None:
583
+ offshore = self.static.get_offshore_sfincs_model()
584
+ else:
585
+ offshore = None
594
586
 
595
587
  for _dir in output_scenarios:
596
588
  # Delete if: input was deleted or corrupted output due to unfinished run
@@ -598,12 +590,18 @@ class Database(IDatabase):
598
590
  path.name for path in input_scenarios
599
591
  ] or not finished_file_exists(_dir):
600
592
  logger.info(f"Cleaning up corrupted outputs of scenario: {_dir.name}.")
601
- shutil.rmtree(_dir, onerror=_call_garbage_collector)
593
+ shutil.rmtree(_dir, ignore_errors=True)
602
594
  # If the scenario is finished, delete the simulation folders depending on `save_simulation`
603
595
  elif finished_file_exists(_dir):
604
- self._delete_simulations(_dir.name)
596
+ self._delete_simulations(_dir.name, overland, fiat, offshore)
605
597
 
606
- def _delete_simulations(self, scenario_name: str) -> None:
598
+ def _delete_simulations(
599
+ self,
600
+ scenario_name: str,
601
+ overland: SfincsAdapter,
602
+ fiat: FiatAdapter,
603
+ offshore: Optional[SfincsAdapter],
604
+ ) -> None:
607
605
  """Delete all simulation folders for a given scenario.
608
606
 
609
607
  Parameters
@@ -617,7 +615,6 @@ class Database(IDatabase):
617
615
 
618
616
  if not self.site.sfincs.config.save_simulation:
619
617
  # Delete SFINCS overland
620
- overland = self.static.get_overland_sfincs_model()
621
618
  if sub_events:
622
619
  for sub_event in sub_events:
623
620
  overland._delete_simulation_folder(scn, sub_event=sub_event)
@@ -625,8 +622,7 @@ class Database(IDatabase):
625
622
  overland._delete_simulation_folder(scn)
626
623
 
627
624
  # Delete SFINCS offshore
628
- if self.site.sfincs.config.offshore_model:
629
- offshore = self.static.get_offshore_sfincs_model()
625
+ if self.site.sfincs.config.offshore_model and offshore is not None:
630
626
  if sub_events:
631
627
  for sub_event in sub_events:
632
628
  sim_path = offshore._get_simulation_path_offshore(
@@ -639,7 +635,7 @@ class Database(IDatabase):
639
635
  sim_path.parent.iterdir()
640
636
  ):
641
637
  # Remove the parent directory `simulations` if it is empty
642
- sim_path.parent.rmdir()
638
+ shutil.rmtree(sim_path.parent, ignore_errors=True)
643
639
  else:
644
640
  sim_path = offshore._get_simulation_path_offshore(scn)
645
641
  if sim_path.exists():
@@ -648,9 +644,8 @@ class Database(IDatabase):
648
644
 
649
645
  if sim_path.parent.exists() and not any(sim_path.parent.iterdir()):
650
646
  # Remove the parent directory `simulations` if it is empty
651
- sim_path.parent.rmdir()
647
+ shutil.rmtree(sim_path.parent, ignore_errors=True)
652
648
 
653
649
  if not self.site.fiat.config.save_simulation:
654
650
  # Delete FIAT
655
- fiat = self.static.get_fiat_model()
656
651
  fiat._delete_simulation_folder(scn)
@@ -1,5 +1,3 @@
1
- import shutil
2
-
3
1
  from flood_adapt.dbs_classes.dbs_template import DbsTemplate
4
2
  from flood_adapt.misc.exceptions import DatabaseError
5
3
  from flood_adapt.workflows.benefit_runner import Benefit, BenefitRunner
@@ -37,30 +35,6 @@ class DbsBenefit(DbsTemplate[Benefit]):
37
35
  # Save the benefit
38
36
  super().save(object_model, overwrite=overwrite)
39
37
 
40
- def delete(self, name: str, toml_only: bool = False):
41
- """Delete an already existing benefit in the database.
42
-
43
- Parameters
44
- ----------
45
- name : str
46
- name of the benefit
47
- toml_only : bool, optional
48
- whether to only delete the toml file or the entire folder. If the folder is empty after deleting the toml,
49
- it will always be deleted. By default False
50
-
51
- Raises
52
- ------
53
- DatabaseError
54
- Raise error if benefit has already model output
55
- """
56
- # First delete the benefit
57
- super().delete(name, toml_only=toml_only)
58
-
59
- # Delete output if edited
60
- output_path = self.output_path / name
61
- if output_path.exists():
62
- shutil.rmtree(output_path, ignore_errors=True)
63
-
64
38
  def get_runner(self, name: str) -> BenefitRunner:
65
39
  return BenefitRunner(self._database, self.get(name))
66
40
 
@@ -1,7 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from flood_adapt.dbs_classes.dbs_template import DbsTemplate
4
- from flood_adapt.misc.exceptions import DatabaseError
4
+ from flood_adapt.misc.exceptions import DoesNotExistError
5
5
  from flood_adapt.objects.events.event_factory import EventFactory
6
6
  from flood_adapt.objects.events.event_set import EventSet
7
7
  from flood_adapt.objects.events.events import Event
@@ -31,7 +31,7 @@ class DbsEvent(DbsTemplate[Event]):
31
31
 
32
32
  # Check if the object exists
33
33
  if not Path(event_path).is_file():
34
- raise DatabaseError(f"{self.display_name} '{name}' does not exist.")
34
+ raise DoesNotExistError(name, self.display_name)
35
35
 
36
36
  # Load event
37
37
  return EventFactory.load_file(event_path, load_all=load_all)
@@ -4,7 +4,7 @@ from typing import Any
4
4
  import geopandas as gpd
5
5
 
6
6
  from flood_adapt.dbs_classes.dbs_template import DbsTemplate
7
- from flood_adapt.misc.exceptions import DatabaseError
7
+ from flood_adapt.misc.exceptions import DatabaseError, DoesNotExistError
8
8
  from flood_adapt.misc.utils import resolve_filepath
9
9
  from flood_adapt.objects.measures.measure_factory import MeasureFactory
10
10
  from flood_adapt.objects.measures.measures import Measure
@@ -34,7 +34,7 @@ class DbsMeasure(DbsTemplate[Measure]):
34
34
 
35
35
  # Check if the object exists
36
36
  if not Path(full_path).is_file():
37
- raise DatabaseError(f"{self.display_name}: '{name}' does not exist.")
37
+ raise DoesNotExistError(name, self.display_name)
38
38
 
39
39
  # Load and return the object
40
40
  return MeasureFactory.get_measure_object(full_path)
@@ -1,4 +1,3 @@
1
- import shutil
2
1
  from typing import Any
3
2
 
4
3
  from flood_adapt.dbs_classes.dbs_template import DbsTemplate
@@ -36,29 +35,6 @@ class DbsScenario(DbsTemplate[Scenario]):
36
35
 
37
36
  return scenarios
38
37
 
39
- def delete(self, name: str, toml_only: bool = False):
40
- """Delete an already existing scenario in the database.
41
-
42
- Parameters
43
- ----------
44
- name : str
45
- name of the scenario to be deleted
46
- toml_only : bool, optional
47
- whether to only delete the toml file or the entire folder. If the folder is empty after deleting the toml,
48
- it will always be deleted. By default False
49
-
50
- Raises
51
- ------
52
- DatabaseError
53
- Raise error if scenario to be deleted is already in use.
54
- """
55
- # First delete the scenario
56
- super().delete(name, toml_only)
57
-
58
- # Then delete the results
59
- if (self.output_path / name).exists():
60
- shutil.rmtree(self.output_path / name, ignore_errors=False)
61
-
62
38
  def check_higher_level_usage(self, name: str) -> list[str]:
63
39
  """Check if a scenario is used in a benefit.
64
40
 
@@ -12,7 +12,7 @@ from flood_adapt.adapter.sfincs_adapter import SfincsAdapter
12
12
  from flood_adapt.config.config import Settings
13
13
  from flood_adapt.dbs_classes.interface.database import IDatabase
14
14
  from flood_adapt.dbs_classes.interface.static import IDbsStatic
15
- from flood_adapt.misc.exceptions import DatabaseError
15
+ from flood_adapt.misc.exceptions import ConfigError, DatabaseError
16
16
 
17
17
 
18
18
  def cache_method_wrapper(func: Callable) -> Callable:
@@ -260,7 +260,7 @@ class DbsStatic(IDbsStatic):
260
260
  def get_offshore_sfincs_model(self) -> SfincsAdapter:
261
261
  """Get the template overland Sfincs model."""
262
262
  if self._database.site.sfincs.config.offshore_model is None:
263
- raise DatabaseError("No offshore model defined in the site configuration.")
263
+ raise ConfigError("No offshore model defined in the site configuration.")
264
264
 
265
265
  offshore_path = (
266
266
  self._database.static_path
@@ -273,7 +273,7 @@ class DbsStatic(IDbsStatic):
273
273
  def get_fiat_model(self) -> FiatAdapter:
274
274
  """Get the path to the FIAT model."""
275
275
  if self._database.site.fiat is None:
276
- raise DatabaseError("No FIAT model defined in the site configuration.")
276
+ raise ConfigError("No FIAT model defined in the site configuration.")
277
277
  template_path = self._database.static_path / "templates" / "fiat"
278
278
  with FiatAdapter(
279
279
  model_root=template_path,
@@ -287,7 +287,7 @@ class DbsStatic(IDbsStatic):
287
287
  @cache_method_wrapper
288
288
  def get_cyclone_track_database(self) -> CycloneTrackDatabase:
289
289
  if self._database.site.sfincs.cyclone_track_database is None:
290
- raise DatabaseError(
290
+ raise ConfigError(
291
291
  "No cyclone track database defined in the site configuration."
292
292
  )
293
293
  database_file = str(
@@ -1,7 +1,7 @@
1
1
  from itertools import combinations
2
2
 
3
3
  from flood_adapt.dbs_classes.dbs_template import DbsTemplate
4
- from flood_adapt.misc.exceptions import DatabaseError
4
+ from flood_adapt.misc.exceptions import AlreadyExistsError, DatabaseError
5
5
  from flood_adapt.objects.measures.measures import MeasureType
6
6
  from flood_adapt.objects.strategies.strategies import Strategy
7
7
 
@@ -49,9 +49,7 @@ class DbsStrategy(DbsTemplate[Strategy]):
49
49
  if overwrite and object_exists:
50
50
  self.delete(object_model.name, toml_only=True)
51
51
  elif not overwrite and object_exists:
52
- raise DatabaseError(
53
- f"'{object_model.name}' name is already used by another {self.display_name}. Choose a different name"
54
- )
52
+ raise AlreadyExistsError(object_model.name, self.display_name)
55
53
 
56
54
  # Check if any measures overlap
57
55
  self._check_overlapping_measures(object_model.measures)
@@ -8,7 +8,13 @@ import tomli_w
8
8
 
9
9
  from flood_adapt.dbs_classes.interface.database import IDatabase
10
10
  from flood_adapt.dbs_classes.interface.element import AbstractDatabaseElement
11
- from flood_adapt.misc.exceptions import DatabaseError
11
+ from flood_adapt.misc.exceptions import (
12
+ AlreadyExistsError,
13
+ DatabaseError,
14
+ DoesNotExistError,
15
+ IsStandardObjectError,
16
+ IsUsedInError,
17
+ )
12
18
  from flood_adapt.objects.object_model import Object
13
19
 
14
20
  T_OBJECTMODEL = TypeVar("T_OBJECTMODEL", bound=Object)
@@ -41,13 +47,18 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
41
47
  -------
42
48
  Object
43
49
  object of the type of the specified object model
50
+
51
+ Raises
52
+ ------
53
+ DoesNotExistError
54
+ Raise error if the object does not exist.
44
55
  """
45
56
  # Make the full path to the object
46
57
  full_path = self.input_path / name / f"{name}.toml"
47
58
 
48
59
  # Check if the object exists
49
60
  if not Path(full_path).is_file():
50
- raise DatabaseError(f"{self.display_name}: '{name}' does not exist.")
61
+ raise DoesNotExistError(name, self.display_name)
51
62
 
52
63
  # Load and return the object
53
64
  return self._object_class.load_file(full_path)
@@ -74,6 +85,15 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
74
85
  name of the new measure
75
86
  new_description : str
76
87
  description of the new measure
88
+
89
+ Raises
90
+ ------
91
+ AlreadyExistsError
92
+ Raise error if an object with the new name already exists.
93
+ IsStandardObjectError
94
+ Raise error if an object with the new name is a standard object.
95
+ DatabaseError
96
+ Raise error if the saving of the object fails.
77
97
  """
78
98
  copy_object = self.get(old_name)
79
99
  copy_object.name = new_name
@@ -123,8 +143,14 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
123
143
 
124
144
  Raises
125
145
  ------
146
+ AlreadyExistsError
147
+ Raise error if object to be saved already exists.
148
+ IsStandardObjectError
149
+ Raise error if object to be overwritten is a standard object.
150
+ IsUsedInError
151
+ Raise error if object to be overwritten is already in use.
126
152
  DatabaseError
127
- Raise error if name is already in use.
153
+ Raise error if the overwriting of the object fails.
128
154
  """
129
155
  self._validate_to_save(object_model, overwrite=overwrite)
130
156
 
@@ -138,7 +164,7 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
138
164
  )
139
165
 
140
166
  def delete(self, name: str, toml_only: bool = False):
141
- """Delete an already existing object in the database.
167
+ """Delete an already existing object as well as its outputs from the database.
142
168
 
143
169
  Parameters
144
170
  ----------
@@ -150,34 +176,50 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
150
176
 
151
177
  Raises
152
178
  ------
153
- DatabaseError
179
+ IsStandardObjectError
180
+ Raise error if object to be deleted is a standard object.
181
+ IsUsedInError
154
182
  Raise error if object to be deleted is already in use.
183
+ DoesNotExistError
184
+ Raise error if object to be deleted does not exist.
185
+ DatabaseError
186
+ Raise error if the deletion of the object fails.
155
187
  """
156
188
  # Check if the object is a standard object. If it is, raise an error
157
189
  if self._check_standard_objects(name):
158
- raise DatabaseError(
159
- f"'{name}' cannot be deleted/modified since it is a standard {self.display_name}."
160
- )
190
+ raise IsStandardObjectError(name, self.display_name)
161
191
 
162
192
  # Check if object is used in a higher level object. If it is, raise an error
163
193
  if used_in := self.check_higher_level_usage(name):
164
- raise DatabaseError(
165
- f"{self.display_name}: '{name}' cannot be deleted/modified since it is already used in the {self._higher_lvl_object.capitalize()}(s): {', '.join(used_in)}"
194
+ raise IsUsedInError(
195
+ name, self.display_name, self._higher_lvl_object, used_in
166
196
  )
167
197
 
168
- # Once all checks are passed, delete the object
198
+ # Check if the object exists
169
199
  toml_path = self.input_path / name / f"{name}.toml"
170
- if toml_only:
171
- # Only delete the toml file
172
- toml_path.unlink(missing_ok=True)
173
- # If the folder is empty, delete the folder
174
- if not list(toml_path.parent.iterdir()):
175
- toml_path.parent.rmdir()
176
- else:
177
- # Delete the entire folder
178
- shutil.rmtree(toml_path.parent, ignore_errors=True)
179
- if (self.output_path / name).exists():
180
- shutil.rmtree(self.output_path / name, ignore_errors=True)
200
+ if not toml_path.exists():
201
+ raise DoesNotExistError(name, self.display_name)
202
+
203
+ # Once all checks are passed, delete the object
204
+ toml_path.unlink(missing_ok=True)
205
+
206
+ # Delete the entire folder
207
+ if not list(toml_path.parent.iterdir()):
208
+ shutil.rmtree(toml_path.parent)
209
+ elif not toml_only:
210
+ try:
211
+ shutil.rmtree(toml_path.parent)
212
+ except OSError as e:
213
+ raise DatabaseError(f"Failed to delete `{name}` due to: {e}") from e
214
+
215
+ # Delete output
216
+ if (self.output_path / name).exists():
217
+ try:
218
+ shutil.rmtree(self.output_path / name)
219
+ except OSError as e:
220
+ raise DatabaseError(
221
+ f"Failed to delete output of `{name}` due to: {e}"
222
+ ) from e
181
223
 
182
224
  def _check_standard_objects(self, name: str) -> bool:
183
225
  """Check if an object is a standard object.
@@ -275,6 +317,4 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
275
317
  if overwrite and object_exists:
276
318
  self.delete(object_model.name, toml_only=True)
277
319
  elif not overwrite and object_exists:
278
- raise DatabaseError(
279
- f"'{object_model.name}' name is already used by another {self.display_name.lower()}. Choose a different name"
280
- )
320
+ raise AlreadyExistsError(object_model.name, self.display_name)
@@ -10,13 +10,50 @@ class DatabaseError(FloodAdaptError):
10
10
  pass
11
11
 
12
12
 
13
- class ComponentError(FloodAdaptError):
14
- """Base class for exceptions raised in any component/object related files."""
13
+ class IsStandardObjectError(DatabaseError):
14
+ """Raised when an operation is attempted on a standard object."""
15
15
 
16
- pass
16
+ def __init__(
17
+ self,
18
+ name: str,
19
+ object_type: str,
20
+ ):
21
+ msg = f"The {object_type} '{name}' is a standard object and cannot be modified or deleted."
22
+ super().__init__(msg)
17
23
 
18
24
 
19
- class WorkflowError(FloodAdaptError):
20
- """Base class for exceptions raised in any workflow related files."""
25
+ class AlreadyExistsError(DatabaseError):
26
+ """Raised when an attempt is made to create an object that already exists."""
21
27
 
22
- pass
28
+ def __init__(
29
+ self,
30
+ name: str,
31
+ object_type: str,
32
+ ):
33
+ msg = f"The {object_type} '{name}' already exists."
34
+ super().__init__(msg)
35
+
36
+
37
+ class DoesNotExistError(DatabaseError):
38
+ """Raised when an attempt is made to access an object that does not exist."""
39
+
40
+ def __init__(self, name: str, object_type: str):
41
+ msg = f"The {object_type} '{name}' does not exist."
42
+ super().__init__(msg)
43
+
44
+
45
+ class IsUsedInError(DatabaseError):
46
+ """Raised when an attempt is made to delete or modify an object that is in use / referenced by another object."""
47
+
48
+ def __init__(
49
+ self, name: str, object_type: str, used_in_type: str, used_in: list[str]
50
+ ):
51
+ msg = f"The {object_type} '{name}' cannot be deleted/modified since it is already used in the {used_in_type}(s): {', '.join(used_in)}"
52
+ super().__init__(msg)
53
+
54
+
55
+ class ConfigError(DatabaseError):
56
+ """Raised when optional configuration, usually in the site, is missing or invalid."""
57
+
58
+ def __init__(self, message: str):
59
+ super().__init__(message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flood-adapt
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A software package support system which can be used to assess the benefits and costs of flood resilience measures
5
5
  Author-email: Gundula Winter <Gundula.Winter@deltares.nl>, Panos Athanasiou <Panos.Athanasiou@deltares.nl>, Frederique de Groen <Frederique.deGroen@deltares.nl>, Tim de Wilde <Tim.deWilde@deltares.nl>, Julian Hofer <Julian.Hofer@deltares.nl>, Daley Adrichem <Daley.Adrichem@deltares.nl>, Luuk Blom <Luuk.Blom@deltares.nl>
6
6
  License: ====================================================
@@ -712,18 +712,16 @@ Requires-Dist: cht-cyclones==1.0.3
712
712
  Requires-Dist: cht-meteo==0.3.1
713
713
  Requires-Dist: cht-observations==0.2.1
714
714
  Requires-Dist: cht-tide==0.1.1
715
- Requires-Dist: dask==2024.11.2
716
- Requires-Dist: numba_celltree==0.2.2
717
715
  Requires-Dist: fiat-toolbox==0.1.20
718
716
  Requires-Dist: fiona<2.0,>=1.0
719
717
  Requires-Dist: geojson<4.0,>=3.0
720
718
  Requires-Dist: geopandas<2.0,>=1.0
721
- Requires-Dist: hydromt-fiat==0.5.5
722
- Requires-Dist: hydromt-sfincs==1.2.0
719
+ Requires-Dist: hydromt-fiat<1.0,>=0.5.5
720
+ Requires-Dist: hydromt-sfincs[quadtree]<2.0,>=1.2.2
723
721
  Requires-Dist: numpy<2.0,>=1.0
724
722
  Requires-Dist: numpy-financial<2.0,>=1.0
725
723
  Requires-Dist: pandas<3.0,>=2.0
726
- Requires-Dist: plotly<7.0,>=6.0
724
+ Requires-Dist: plotly<6.3,>=6.0
727
725
  Requires-Dist: pydantic<3.0,>=2.0
728
726
  Requires-Dist: pydantic-settings<3.0,>=2.0
729
727
  Requires-Dist: pyogrio<1.0
@@ -1,9 +1,9 @@
1
- flood_adapt/__init__.py,sha256=ckRctoVnY8-iqM2cbEkPXTPdOKjZJNDKeBolOYFa81s,818
1
+ flood_adapt/__init__.py,sha256=th2A3Ub_atS7QHg1y2SxGCiECW7BKlWrXZX7fuHd4M8,781
2
2
  flood_adapt/flood_adapt.py,sha256=g-OpS0spvi-puwQFhvsfu_vvduozTPXk9DU2ay9ypgs,38698
3
3
  flood_adapt/adapter/__init__.py,sha256=vnF8NCkEVX-N-gtGS-J_A1H1YYAjihWjJZFyYGwcp8Q,180
4
- flood_adapt/adapter/fiat_adapter.py,sha256=kqZsAEo4YToLtaKTwATAGgmp5-FWQffZLbAItuLx104,60010
5
- flood_adapt/adapter/sfincs_adapter.py,sha256=mvW1cW8NNr2fnhZW0Ffzg9gDuheSrhTvYI0Y0AkLCNA,78499
6
- flood_adapt/adapter/sfincs_offshore.py,sha256=MjSPTrFPuOI2VqHeaBobS_OCozgnJST05nR49lq7cPs,7639
4
+ flood_adapt/adapter/fiat_adapter.py,sha256=s6M-_bwr4NUWLZvlc619BUmnA_z-DNMggVcnlG-jrqM,60075
5
+ flood_adapt/adapter/sfincs_adapter.py,sha256=QIfD_7UnciN4MdminT5BRDEKCyubJBGf8RsSOd2hs9s,78864
6
+ flood_adapt/adapter/sfincs_offshore.py,sha256=DkqGwx0Fx4dojY1YH8tW3MUS4Omgd5DC6QINEsTP0Uk,7659
7
7
  flood_adapt/adapter/interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  flood_adapt/adapter/interface/hazard_adapter.py,sha256=S2NIUAMSRgxC_E-tZRJ2qIP06U1zEVdn-MnvMTrn86s,2828
9
9
  flood_adapt/adapter/interface/impact_adapter.py,sha256=WK_r0KBBR-Z6Yiys0dqtOVbFIsqEjr3aWBuZkb9zN5Y,1428
@@ -76,22 +76,22 @@ flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographi
76
76
  flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml,sha256=VLO7NRWTPzYRj3jYDb_mImPTppSm8Lh1ajCdNPuyc4U,2045
77
77
  flood_adapt/database_builder/templates/output_layers/bin_colors.toml,sha256=yN3_h2IimOyjtfhZ-ZoWyNa-2cAeFRNlbvaNTLhEMfA,417
78
78
  flood_adapt/dbs_classes/__init__.py,sha256=J-a6BEkjDhuUzzRKuAn_AtTg_D9wNIsmY3BnVTiC2JA,731
79
- flood_adapt/dbs_classes/database.py,sha256=xtdV-tqfJ26lTtp-K7fC1PhaSUFY_qfcfyIYh0gIp9Q,23583
80
- flood_adapt/dbs_classes/dbs_benefit.py,sha256=BbEdx3_sNzqPu4T3AUwiDdf9OnbEtIb-sQui-dYpgLU,2671
81
- flood_adapt/dbs_classes/dbs_event.py,sha256=7fxlJFih5qrKuqpBlcQ1mYOHCfWyLJytEuu5JkBDNrc,1885
82
- flood_adapt/dbs_classes/dbs_measure.py,sha256=nEmq1kinc4l5d4LIxguzuXvCVYrWvFxX8maak8wIggw,4087
79
+ flood_adapt/dbs_classes/database.py,sha256=oR0aC2HRsGGe0pwFrPtPAzXxfv_lqtTdjY9UrS9wwtU,23409
80
+ flood_adapt/dbs_classes/dbs_benefit.py,sha256=ayEYz8ga49HLdYuUsDWZOuZnpRnBpTuyhvfe2IyWAKI,1825
81
+ flood_adapt/dbs_classes/dbs_event.py,sha256=ak3kHan6L1EfC8agDLKiCe8gaY5leOmj_qUBsI61q9A,1869
82
+ flood_adapt/dbs_classes/dbs_measure.py,sha256=vVs-LtnHJN7eSGIFUglJdpbtfq_QI_Ftkv4lh5mfnNM,4085
83
83
  flood_adapt/dbs_classes/dbs_projection.py,sha256=lyiU_ctP2ixK28RKnBN6mVJbOuaDsWCj1y6-MHlyi_k,1078
84
- flood_adapt/dbs_classes/dbs_scenario.py,sha256=hUvfGHw2LKmTFz2QeRijmTaalFrYap0gPK5QkNzuZfQ,4833
85
- flood_adapt/dbs_classes/dbs_static.py,sha256=0q72yCe03L4ibPVikQjuOjSdwgP66DQMbio3zj8UJlw,10550
86
- flood_adapt/dbs_classes/dbs_strategy.py,sha256=Pin0SXukdIdNwDBuM0XtqNsWUpM-gMoYy-ZfYNIs9Hw,4928
87
- flood_adapt/dbs_classes/dbs_template.py,sha256=CTjDijAWjbs3u9wwFKigAfVlDZnduTtYDTFD_b78m4w,10384
84
+ flood_adapt/dbs_classes/dbs_scenario.py,sha256=LHWx3Dr1XR47bPyPRkR70h3VcT0f0MVgB-R8V_G_O04,3993
85
+ flood_adapt/dbs_classes/dbs_static.py,sha256=KPKjz7xyzjn6yqVvfEHlzW7Wnqi_DmSkFhC9l4hEAr4,10557
86
+ flood_adapt/dbs_classes/dbs_strategy.py,sha256=qiEObHZeYL93GmdjSiGQls1ZmxdMZPkRkwzHgmoYwyE,4856
87
+ flood_adapt/dbs_classes/dbs_template.py,sha256=b2x2sWNYTnaWU8Plgp51PFPrZGEv2kRRn9JBAgYhLbI,11578
88
88
  flood_adapt/dbs_classes/interface/database.py,sha256=kPHsmreB-vHaFD_FRXbP06tVs7pGxt4Rucim6aEqiKg,2550
89
89
  flood_adapt/dbs_classes/interface/element.py,sha256=XN3SjfEiAa4oZ61XJNnHkfKQu5Da42EH8PEkfosIJ9w,3528
90
90
  flood_adapt/dbs_classes/interface/static.py,sha256=amChHlParELA4vFMUn_kL1fx6z7fCFW-hJXBo7PGtbY,1588
91
91
  flood_adapt/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  flood_adapt/misc/database_user.py,sha256=4PnXMNA0poQWWGc9FbmYAfJh5PhTMI3xzlCaIuP5wn4,546
93
93
  flood_adapt/misc/debug_timer.py,sha256=TUXsJSX62P66AQrf7rqpmd95EZECJVWGT7pHpuxNG0I,743
94
- flood_adapt/misc/exceptions.py,sha256=xLT1QnbjClftzYaxX5XdvVAkoI0q1CAsWJX901GB9JM,513
94
+ flood_adapt/misc/exceptions.py,sha256=66lD9OlAfGadhbMN2t2C03KofzU_zOoVHnuEeDtSc0w,1827
95
95
  flood_adapt/misc/log.py,sha256=aK5uJch8p3a4Js4f79tO5AM9yZvNlGPjHYPsY5EuWbc,6898
96
96
  flood_adapt/misc/path_builder.py,sha256=sLhvk3tq-QzI3fFjdzckpBYYZeuGyHBbuI0R98Tqud8,1396
97
97
  flood_adapt/misc/utils.py,sha256=-wOWgbDqswwGVYJARKfq-3L7DIs4OIHfrze5g3-qapU,5588
@@ -134,8 +134,8 @@ flood_adapt/objects/strategies/strategies.py,sha256=Jw-WJDCamL9p_7VEir3AdmYPMVAi
134
134
  flood_adapt/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
135
  flood_adapt/workflows/benefit_runner.py,sha256=VtYt0sHFymNyErpzOtuN55cKJGVm5hT2a_Qzprg6T88,21786
136
136
  flood_adapt/workflows/scenario_runner.py,sha256=9_Y6GmMYhYoTRkBUIlju0eBy6DosGf4Zl2tgu1QEubI,4119
137
- flood_adapt-1.0.4.dist-info/LICENSE,sha256=Ui5E03pQ0EVKxvKA54lTPA1xrtgA2HMGLQai95eOzoE,36321
138
- flood_adapt-1.0.4.dist-info/METADATA,sha256=Eb02xNPlDolOPVGRx6g8RvqI8CTiH2YS2KCbUK3_qvg,53259
139
- flood_adapt-1.0.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
140
- flood_adapt-1.0.4.dist-info/top_level.txt,sha256=JvzMi6cTcQPEThCfpgMEeVny3ghI1urSH0CCgVIqSzw,12
141
- flood_adapt-1.0.4.dist-info/RECORD,,
137
+ flood_adapt-1.0.6.dist-info/LICENSE,sha256=Ui5E03pQ0EVKxvKA54lTPA1xrtgA2HMGLQai95eOzoE,36321
138
+ flood_adapt-1.0.6.dist-info/METADATA,sha256=FvbH5BpNMV_cXv23oWzPC2APQvSzPmGBwQKZjyBIFGY,53209
139
+ flood_adapt-1.0.6.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
140
+ flood_adapt-1.0.6.dist-info/top_level.txt,sha256=JvzMi6cTcQPEThCfpgMEeVny3ghI1urSH0CCgVIqSzw,12
141
+ flood_adapt-1.0.6.dist-info/RECORD,,