flood-adapt 0.3.0__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 +22 -0
- flood_adapt/adapter/__init__.py +9 -0
- flood_adapt/adapter/fiat_adapter.py +1502 -0
- flood_adapt/adapter/interface/__init__.py +0 -0
- flood_adapt/adapter/interface/hazard_adapter.py +70 -0
- flood_adapt/adapter/interface/impact_adapter.py +36 -0
- flood_adapt/adapter/interface/model_adapter.py +89 -0
- flood_adapt/adapter/interface/offshore.py +19 -0
- flood_adapt/adapter/sfincs_adapter.py +1857 -0
- flood_adapt/adapter/sfincs_offshore.py +193 -0
- flood_adapt/config/__init__.py +0 -0
- flood_adapt/config/config.py +245 -0
- flood_adapt/config/fiat.py +219 -0
- flood_adapt/config/gui.py +224 -0
- flood_adapt/config/sfincs.py +336 -0
- flood_adapt/config/site.py +124 -0
- flood_adapt/database_builder/__init__.py +0 -0
- flood_adapt/database_builder/database_builder.py +2175 -0
- flood_adapt/database_builder/templates/default_units/imperial.toml +9 -0
- flood_adapt/database_builder/templates/default_units/metric.toml +9 -0
- flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +10 -0
- flood_adapt/database_builder/templates/icons/black_down_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_left_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_right_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_up_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_down.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_right.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_up.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_down.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_right.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_up.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_right.png +0 -0
- flood_adapt/database_builder/templates/icons/white_down_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_left_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_right_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_up_48x48.png +0 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +90 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +57 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +121 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +65 -0
- flood_adapt/database_builder/templates/infographics/OSM/styles.css +45 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +126 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +60 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +121 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +65 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +45 -0
- flood_adapt/database_builder/templates/infographics/images/ambulance.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/car.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/cart.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/firetruck.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/hospital.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/house.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/info.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/money.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/person.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/school.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/truck.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/walking_person.png +0 -0
- flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +4 -0
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +143 -0
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +153 -0
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +127 -0
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +57 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +4 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +191 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +153 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +178 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +57 -0
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +9 -0
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +65 -0
- flood_adapt/database_builder/templates/mapbox_layers/bin_colors.toml +5 -0
- flood_adapt/database_builder.py +16 -0
- flood_adapt/dbs_classes/__init__.py +21 -0
- flood_adapt/dbs_classes/database.py +716 -0
- flood_adapt/dbs_classes/dbs_benefit.py +97 -0
- flood_adapt/dbs_classes/dbs_event.py +91 -0
- flood_adapt/dbs_classes/dbs_measure.py +103 -0
- flood_adapt/dbs_classes/dbs_projection.py +52 -0
- flood_adapt/dbs_classes/dbs_scenario.py +150 -0
- flood_adapt/dbs_classes/dbs_static.py +261 -0
- flood_adapt/dbs_classes/dbs_strategy.py +147 -0
- flood_adapt/dbs_classes/dbs_template.py +302 -0
- flood_adapt/dbs_classes/interface/database.py +147 -0
- flood_adapt/dbs_classes/interface/element.py +137 -0
- flood_adapt/dbs_classes/interface/static.py +47 -0
- flood_adapt/flood_adapt.py +1371 -0
- flood_adapt/misc/__init__.py +0 -0
- flood_adapt/misc/database_user.py +16 -0
- flood_adapt/misc/log.py +183 -0
- flood_adapt/misc/path_builder.py +54 -0
- flood_adapt/misc/utils.py +185 -0
- flood_adapt/objects/__init__.py +59 -0
- flood_adapt/objects/benefits/__init__.py +0 -0
- flood_adapt/objects/benefits/benefits.py +61 -0
- flood_adapt/objects/events/__init__.py +0 -0
- flood_adapt/objects/events/event_factory.py +135 -0
- flood_adapt/objects/events/event_set.py +84 -0
- flood_adapt/objects/events/events.py +221 -0
- flood_adapt/objects/events/historical.py +55 -0
- flood_adapt/objects/events/hurricane.py +64 -0
- flood_adapt/objects/events/synthetic.py +48 -0
- flood_adapt/objects/forcing/__init__.py +0 -0
- flood_adapt/objects/forcing/csv.py +68 -0
- flood_adapt/objects/forcing/discharge.py +66 -0
- flood_adapt/objects/forcing/forcing.py +142 -0
- flood_adapt/objects/forcing/forcing_factory.py +182 -0
- flood_adapt/objects/forcing/meteo_handler.py +93 -0
- flood_adapt/objects/forcing/netcdf.py +40 -0
- flood_adapt/objects/forcing/plotting.py +428 -0
- flood_adapt/objects/forcing/rainfall.py +98 -0
- flood_adapt/objects/forcing/tide_gauge.py +191 -0
- flood_adapt/objects/forcing/time_frame.py +77 -0
- flood_adapt/objects/forcing/timeseries.py +552 -0
- flood_adapt/objects/forcing/unit_system.py +580 -0
- flood_adapt/objects/forcing/waterlevels.py +108 -0
- flood_adapt/objects/forcing/wind.py +124 -0
- flood_adapt/objects/measures/__init__.py +0 -0
- flood_adapt/objects/measures/measure_factory.py +92 -0
- flood_adapt/objects/measures/measures.py +506 -0
- flood_adapt/objects/object_model.py +68 -0
- flood_adapt/objects/projections/__init__.py +0 -0
- flood_adapt/objects/projections/projections.py +89 -0
- flood_adapt/objects/scenarios/__init__.py +0 -0
- flood_adapt/objects/scenarios/scenarios.py +22 -0
- flood_adapt/objects/strategies/__init__.py +0 -0
- flood_adapt/objects/strategies/strategies.py +68 -0
- flood_adapt/workflows/__init__.py +0 -0
- flood_adapt/workflows/benefit_runner.py +541 -0
- flood_adapt/workflows/floodmap.py +85 -0
- flood_adapt/workflows/impacts_integrator.py +82 -0
- flood_adapt/workflows/scenario_runner.py +69 -0
- flood_adapt-0.3.0.dist-info/LICENSE +21 -0
- flood_adapt-0.3.0.dist-info/METADATA +183 -0
- flood_adapt-0.3.0.dist-info/RECORD +139 -0
- flood_adapt-0.3.0.dist-info/WHEEL +5 -0
- flood_adapt-0.3.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DatabaseUser(ABC):
|
|
5
|
+
"""Abstract class for FloodAdapt classes that need to use / interact with the Singleton FloodAdapt database through the lazy-loading self.database property."""
|
|
6
|
+
|
|
7
|
+
_database_instance = None
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def database(self):
|
|
11
|
+
"""Return the database for the object."""
|
|
12
|
+
if self._database_instance is None:
|
|
13
|
+
from flood_adapt.dbs_classes.database import Database
|
|
14
|
+
|
|
15
|
+
self._database_instance = Database()
|
|
16
|
+
return self._database_instance
|
flood_adapt/misc/log.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FloodAdaptLogging:
|
|
9
|
+
_DEFAULT_FORMATTER = logging.Formatter(
|
|
10
|
+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
11
|
+
datefmt="%Y-%m-%d %I:%M:%S %p",
|
|
12
|
+
)
|
|
13
|
+
_root_logger = logging.getLogger("FloodAdapt")
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
file_path: Optional[Path] = None,
|
|
18
|
+
loglevel_console: int = logging.WARNING,
|
|
19
|
+
loglevel_root: int = logging.INFO,
|
|
20
|
+
loglevel_files: int = logging.DEBUG,
|
|
21
|
+
formatter: logging.Formatter = _DEFAULT_FORMATTER,
|
|
22
|
+
ignore_warnings: Optional[list[type[Warning]]] = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Initialize the logging system for the FloodAdapt."""
|
|
25
|
+
self._formatter = formatter
|
|
26
|
+
|
|
27
|
+
self._root_logger.setLevel(loglevel_root)
|
|
28
|
+
if self._root_logger.hasHandlers():
|
|
29
|
+
self._root_logger.handlers.clear()
|
|
30
|
+
|
|
31
|
+
# Add file handler if provided
|
|
32
|
+
if file_path is not None:
|
|
33
|
+
self.add_file_handler(file_path, loglevel_files, formatter)
|
|
34
|
+
|
|
35
|
+
# Add console handler
|
|
36
|
+
console_handler = logging.StreamHandler()
|
|
37
|
+
console_handler.setLevel(loglevel_console)
|
|
38
|
+
console_handler.setFormatter(formatter)
|
|
39
|
+
self._root_logger.addHandler(console_handler)
|
|
40
|
+
|
|
41
|
+
if ignore_warnings:
|
|
42
|
+
for warn_type in ignore_warnings:
|
|
43
|
+
self.configure_warnings("ignore", category=warn_type)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def add_file_handler(
|
|
47
|
+
cls,
|
|
48
|
+
file_path: Path,
|
|
49
|
+
loglevel: int = logging.DEBUG,
|
|
50
|
+
formatter: Optional[logging.Formatter] = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Add a file handler to the logger that directs outputs to a the file."""
|
|
53
|
+
if not file_path:
|
|
54
|
+
raise ValueError("file_path must be provided.")
|
|
55
|
+
file_path = Path(file_path)
|
|
56
|
+
|
|
57
|
+
# check if the path is a only a filename
|
|
58
|
+
if not file_path.parents:
|
|
59
|
+
file_path = Path.cwd() / file_path
|
|
60
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
file_handler = logging.FileHandler(filename=file_path, mode="a")
|
|
63
|
+
file_handler.setLevel(loglevel)
|
|
64
|
+
|
|
65
|
+
formatter = formatter or cls._DEFAULT_FORMATTER
|
|
66
|
+
file_handler.setFormatter(formatter)
|
|
67
|
+
|
|
68
|
+
cls.getLogger().addHandler(file_handler)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def remove_file_handler(cls, file_path: Path) -> None:
|
|
72
|
+
"""Remove a file handler from the logger, which stops sending logs to that file and closes it."""
|
|
73
|
+
for handler in cls.getLogger().handlers:
|
|
74
|
+
if isinstance(handler, logging.FileHandler) and handler.baseFilename == str(
|
|
75
|
+
file_path.resolve()
|
|
76
|
+
):
|
|
77
|
+
handler.close()
|
|
78
|
+
cls.getLogger().removeHandler(handler)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def getLogger(
|
|
82
|
+
cls, name: Optional[str] = None, level: int = logging.INFO
|
|
83
|
+
) -> logging.Logger:
|
|
84
|
+
"""Get a logger with the specified name. If no name is provided, return the root logger.
|
|
85
|
+
|
|
86
|
+
If the logger does not exist, it is created with the specified level.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
name : str, optional
|
|
91
|
+
The name of the logger. If not provided, the root logger is returned.
|
|
92
|
+
level : int,
|
|
93
|
+
The level of the logger. The default is logging.INFO.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
logging.Logger
|
|
98
|
+
The logger with the specified name.
|
|
99
|
+
"""
|
|
100
|
+
if name is None:
|
|
101
|
+
logger = cls._root_logger
|
|
102
|
+
else:
|
|
103
|
+
logger = logging.getLogger(f"FloodAdapt.{name}")
|
|
104
|
+
logger.setLevel(level)
|
|
105
|
+
return logger
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def shutdown(cls):
|
|
109
|
+
root_logger = cls.getLogger()
|
|
110
|
+
handlers = root_logger.handlers[:]
|
|
111
|
+
for handler in handlers:
|
|
112
|
+
if isinstance(handler, logging.FileHandler):
|
|
113
|
+
handler.close()
|
|
114
|
+
root_logger.removeHandler(handler)
|
|
115
|
+
logging.shutdown()
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
@contextmanager
|
|
119
|
+
def to_file(
|
|
120
|
+
cls,
|
|
121
|
+
*,
|
|
122
|
+
file_path: Path,
|
|
123
|
+
loglevel: int = logging.DEBUG,
|
|
124
|
+
formatter: logging.Formatter = _DEFAULT_FORMATTER,
|
|
125
|
+
):
|
|
126
|
+
"""Open a file at filepath to write logs to. Does not affect other loggers.
|
|
127
|
+
|
|
128
|
+
When the context manager exits (via regular execution or an exception), the file is closed and the handler is removed.
|
|
129
|
+
"""
|
|
130
|
+
if file_path is None:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
"file_path must be provided as a key value pair: 'file_path=<file_path>'."
|
|
133
|
+
)
|
|
134
|
+
cls.add_file_handler(file_path, loglevel, formatter)
|
|
135
|
+
try:
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
cls.remove_file_handler(file_path)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def _close_file_handlers(cls, logger: logging.Logger, exclude: List[Path]) -> None:
|
|
142
|
+
"""Close and remove file handlers from a logger."""
|
|
143
|
+
for handler in logger.handlers[:]:
|
|
144
|
+
if (
|
|
145
|
+
isinstance(handler, logging.FileHandler)
|
|
146
|
+
and Path(handler.baseFilename) not in exclude
|
|
147
|
+
):
|
|
148
|
+
handler.close()
|
|
149
|
+
logger.removeHandler(handler)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def close_files(cls, exclude: List[Path] = []) -> None:
|
|
153
|
+
"""Close all file handlers except those in the exclude list."""
|
|
154
|
+
cls._close_file_handlers(cls.getLogger(), exclude)
|
|
155
|
+
cls._close_file_handlers(cls.getLogger("hydromt"), exclude)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def deprecation_warning(cls, version: str, reason: str):
|
|
159
|
+
"""Log a deprecation warning with reason and the version that will remove it."""
|
|
160
|
+
warnings.warn(
|
|
161
|
+
f"DeprecationWarning: {reason}. This will be removed in version {version}.",
|
|
162
|
+
DeprecationWarning,
|
|
163
|
+
stacklevel=2,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def configure_warnings(
|
|
168
|
+
cls, action: str = "default", category: Optional[type[Warning]] = None
|
|
169
|
+
):
|
|
170
|
+
"""
|
|
171
|
+
Configure the behavior of Python warnings.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
action : str, optional
|
|
176
|
+
The action to take on warnings. Common actions include 'ignore', 'default', 'error', 'always', etc.
|
|
177
|
+
The default is 'default', which shows warnings once per triggering location.
|
|
178
|
+
category : type[Warning], optional
|
|
179
|
+
The category of warnings to configure. If not provided, all warnings are configured.
|
|
180
|
+
categories include DeprecationWarning, UserWarning, RuntimeWarning, etc.
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
warnings.simplefilter(action=action, category=category)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from flood_adapt.config.config import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TopLevelDir(str, Enum):
|
|
9
|
+
"""Top level directories in the database."""
|
|
10
|
+
|
|
11
|
+
input = "input"
|
|
12
|
+
output = "output"
|
|
13
|
+
static = "static"
|
|
14
|
+
temp = "temp"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ObjectDir(str, Enum):
|
|
18
|
+
"""The names for object directories at the second level of the database."""
|
|
19
|
+
|
|
20
|
+
site = "site"
|
|
21
|
+
|
|
22
|
+
benefit = "benefits"
|
|
23
|
+
event = "events"
|
|
24
|
+
strategy = "strategies"
|
|
25
|
+
measure = "measures"
|
|
26
|
+
projection = "projections"
|
|
27
|
+
scenario = "scenarios"
|
|
28
|
+
config = "config"
|
|
29
|
+
|
|
30
|
+
# buyout = "measures"
|
|
31
|
+
# elevate = "measures"
|
|
32
|
+
# floodproof = "measures"
|
|
33
|
+
# greening = "measures"
|
|
34
|
+
# floodwall = "measures"
|
|
35
|
+
# pump = "measures"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def db_path(
|
|
39
|
+
top_level_dir: TopLevelDir = TopLevelDir.input,
|
|
40
|
+
object_dir: Optional[ObjectDir] = None,
|
|
41
|
+
obj_name: Optional[str] = None,
|
|
42
|
+
) -> Path:
|
|
43
|
+
"""Return an path to a database directory from arguments."""
|
|
44
|
+
rel_path = Path(top_level_dir.value)
|
|
45
|
+
if object_dir is not None:
|
|
46
|
+
if isinstance(object_dir, ObjectDir):
|
|
47
|
+
rel_path = rel_path / object_dir.value
|
|
48
|
+
else:
|
|
49
|
+
rel_path = rel_path / str(object_dir)
|
|
50
|
+
|
|
51
|
+
if obj_name is not None:
|
|
52
|
+
rel_path = rel_path / obj_name
|
|
53
|
+
|
|
54
|
+
return Settings().database_path / rel_path
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from pydantic import BeforeValidator
|
|
8
|
+
|
|
9
|
+
from flood_adapt.misc.path_builder import (
|
|
10
|
+
ObjectDir,
|
|
11
|
+
db_path,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@contextmanager
|
|
16
|
+
def modified_environ(*remove, **update):
|
|
17
|
+
"""
|
|
18
|
+
Temporarily updates the ``os.environ`` dictionary in-place.
|
|
19
|
+
|
|
20
|
+
From https://github.com/laurent-laporte-pro/stackoverflow-q2059482/blob/master/demo/environ_ctx.py
|
|
21
|
+
|
|
22
|
+
The ``os.environ`` dictionary is updated in-place so that the modification
|
|
23
|
+
is sure to work in all situations.
|
|
24
|
+
|
|
25
|
+
:param remove: Environment variables to remove.
|
|
26
|
+
:param update: Dictionary of environment variables and values to add/update.
|
|
27
|
+
"""
|
|
28
|
+
env = os.environ
|
|
29
|
+
update = update or {}
|
|
30
|
+
remove = remove or []
|
|
31
|
+
|
|
32
|
+
# List of environment variables being updated or removed.
|
|
33
|
+
stomped = (set(update.keys()) | set(remove)) & set(env.keys())
|
|
34
|
+
# Environment variables and values to restore on exit.
|
|
35
|
+
update_after = {k: env[k] for k in stomped}
|
|
36
|
+
# Environment variables and values to remove on exit.
|
|
37
|
+
remove_after = frozenset(k for k in update if k not in env)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
env.update(update)
|
|
41
|
+
[env.pop(k, None) for k in remove]
|
|
42
|
+
yield
|
|
43
|
+
finally:
|
|
44
|
+
env.update(update_after)
|
|
45
|
+
[env.pop(k) for k in remove_after]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def cd(newdir: Path):
|
|
50
|
+
prevdir = Path().cwd()
|
|
51
|
+
os.chdir(newdir)
|
|
52
|
+
try:
|
|
53
|
+
yield
|
|
54
|
+
finally:
|
|
55
|
+
os.chdir(prevdir)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def write_finished_file(path: Path):
|
|
59
|
+
if not path.exists():
|
|
60
|
+
path.mkdir(parents=True)
|
|
61
|
+
with open(Path(path) / "finished.txt", "w") as f:
|
|
62
|
+
f.write("run finished")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def finished_file_exists(path: Path):
|
|
66
|
+
return (Path(path) / "finished.txt").exists()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_filepath(
|
|
70
|
+
object_dir: ObjectDir, obj_name: str, path: Path | str | os.PathLike
|
|
71
|
+
) -> Path:
|
|
72
|
+
"""
|
|
73
|
+
Determine whether a given path is an external file or a file in the database.
|
|
74
|
+
|
|
75
|
+
Users can set the path to a file in the database directly, meaning it will be an absolute path.
|
|
76
|
+
Users can also read the path from loading a toml file, meaning it will be a filename relative to the toml file.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
object_dir : ObjectDir
|
|
81
|
+
The directory name of the object in the database.
|
|
82
|
+
obj_name : str
|
|
83
|
+
The name of the object.
|
|
84
|
+
path : Union[Path, str, os.PathLike]
|
|
85
|
+
The path to the file, which can be an absolute path or a relative path.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
Path
|
|
90
|
+
The resolved path to the file.
|
|
91
|
+
|
|
92
|
+
Raises
|
|
93
|
+
------
|
|
94
|
+
FileNotFoundError
|
|
95
|
+
If the file does not exist in either the provided path or the database path.
|
|
96
|
+
"""
|
|
97
|
+
_path = Path(path)
|
|
98
|
+
if str(_path) == _path.name:
|
|
99
|
+
# this is a filename, so it is in the database
|
|
100
|
+
src_path = db_path(object_dir=object_dir, obj_name=obj_name) / path
|
|
101
|
+
else:
|
|
102
|
+
# this is a path, so it is an external file
|
|
103
|
+
src_path = Path(path)
|
|
104
|
+
return src_path
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def save_file_to_database(
|
|
108
|
+
src_file: Path | str | os.PathLike, dst_dir: Path | str | os.PathLike
|
|
109
|
+
) -> Path:
|
|
110
|
+
"""Save a file to the database.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
src_file : Path | str | os.PathLike
|
|
115
|
+
Path to the file to be copied.
|
|
116
|
+
dst_dir : Path | str | os.PathLike
|
|
117
|
+
Path to the destination directory.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
Path
|
|
122
|
+
Path to the copied file
|
|
123
|
+
|
|
124
|
+
Raises
|
|
125
|
+
------
|
|
126
|
+
FileNotFoundError
|
|
127
|
+
If the src_file does not exist at the given path
|
|
128
|
+
"""
|
|
129
|
+
src_file = Path(src_file).resolve()
|
|
130
|
+
dst_file = Path(dst_dir).resolve() / src_file.name
|
|
131
|
+
|
|
132
|
+
if not src_file.exists():
|
|
133
|
+
raise FileNotFoundError(
|
|
134
|
+
f"Failed to find {src_file} when saving external file to the database as it does not exist."
|
|
135
|
+
)
|
|
136
|
+
if src_file == dst_file:
|
|
137
|
+
return dst_file
|
|
138
|
+
elif dst_file.exists():
|
|
139
|
+
if dst_file.suffix == ".shp":
|
|
140
|
+
for file in list(dst_file.parent.glob(f"{dst_file.stem}.*")):
|
|
141
|
+
os.remove(file)
|
|
142
|
+
else:
|
|
143
|
+
os.remove(dst_file)
|
|
144
|
+
|
|
145
|
+
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
if src_file.suffix == ".shp":
|
|
147
|
+
for file in list(src_file.parent.glob(f"{src_file.stem}.*")):
|
|
148
|
+
shutil.copy2(file, dst_file.parent.joinpath(file.name))
|
|
149
|
+
else:
|
|
150
|
+
shutil.copy2(src_file, dst_file)
|
|
151
|
+
|
|
152
|
+
return dst_file
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def copy_file_to_output_dir(file_path: Path, output_dir: Path) -> Path:
|
|
156
|
+
output_dir = output_dir.resolve()
|
|
157
|
+
if file_path == output_dir / file_path.name:
|
|
158
|
+
return file_path
|
|
159
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
shutil.copy2(file_path, output_dir)
|
|
161
|
+
return output_dir / file_path.name
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def validate_file_extension(allowed_extensions: list[str]):
|
|
165
|
+
"""Validate the extension of the given path has one of the given suffixes.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> from pydantic import BaseModel
|
|
170
|
+
>>> from pathlib import Path
|
|
171
|
+
>>> from typing import Annotated
|
|
172
|
+
|
|
173
|
+
>>> class MyClass(BaseModel):
|
|
174
|
+
>>> csv_path: Annotated[Path, validate_file_extension([".csv"])]
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def _validator(value: Union[Path, str, os.PathLike]) -> Path:
|
|
178
|
+
value = Path(value)
|
|
179
|
+
if value.suffix not in allowed_extensions:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Invalid file extension: {value}. Allowed extensions are {', '.join(allowed_extensions)}."
|
|
182
|
+
)
|
|
183
|
+
return value
|
|
184
|
+
|
|
185
|
+
return BeforeValidator(_validator)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from .benefits.benefits import Benefit
|
|
2
|
+
from .events.event_factory import EventFactory
|
|
3
|
+
from .events.event_set import EventSet
|
|
4
|
+
from .events.events import Event
|
|
5
|
+
from .events.historical import HistoricalEvent
|
|
6
|
+
from .events.hurricane import HurricaneEvent
|
|
7
|
+
from .events.synthetic import SyntheticEvent
|
|
8
|
+
from .forcing.forcing_factory import ForcingFactory
|
|
9
|
+
from .measures.measure_factory import MeasureFactory
|
|
10
|
+
from .measures.measures import (
|
|
11
|
+
Buyout,
|
|
12
|
+
Elevate,
|
|
13
|
+
FloodProof,
|
|
14
|
+
FloodWall,
|
|
15
|
+
GreenInfrastructure,
|
|
16
|
+
Measure,
|
|
17
|
+
MeasureType,
|
|
18
|
+
Pump,
|
|
19
|
+
SelectionType,
|
|
20
|
+
)
|
|
21
|
+
from .object_model import Object
|
|
22
|
+
from .projections.projections import Projection
|
|
23
|
+
from .scenarios.scenarios import Scenario
|
|
24
|
+
from .strategies.strategies import Strategy
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Object
|
|
28
|
+
"Object",
|
|
29
|
+
# Measures
|
|
30
|
+
"MeasureFactory",
|
|
31
|
+
"Measure",
|
|
32
|
+
"MeasureType",
|
|
33
|
+
"SelectionType",
|
|
34
|
+
"MeasureType",
|
|
35
|
+
"Buyout",
|
|
36
|
+
"Elevate",
|
|
37
|
+
"FloodProof",
|
|
38
|
+
"FloodWall",
|
|
39
|
+
"GreenInfrastructure",
|
|
40
|
+
"MeasureType",
|
|
41
|
+
"Pump",
|
|
42
|
+
# Events
|
|
43
|
+
"Event",
|
|
44
|
+
"EventFactory",
|
|
45
|
+
"EventSet",
|
|
46
|
+
"SyntheticEvent",
|
|
47
|
+
"HistoricalEvent",
|
|
48
|
+
"HurricaneEvent",
|
|
49
|
+
# Forcing
|
|
50
|
+
"ForcingFactory",
|
|
51
|
+
# Projections
|
|
52
|
+
"Projection",
|
|
53
|
+
# Strategies
|
|
54
|
+
"Strategy",
|
|
55
|
+
# Scenarios
|
|
56
|
+
"Scenario",
|
|
57
|
+
# Benefits
|
|
58
|
+
"Benefit",
|
|
59
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from flood_adapt.objects.object_model import Object
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CurrentSituationModel(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
The accepted input for a current situation in FloodAdapt.
|
|
11
|
+
|
|
12
|
+
Attributes
|
|
13
|
+
----------
|
|
14
|
+
projection : str
|
|
15
|
+
The name of the projection. Should be a projection saved in the database.
|
|
16
|
+
year : int
|
|
17
|
+
The year of the current situation.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
projection: str
|
|
21
|
+
year: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Benefit(Object):
|
|
25
|
+
"""BaseModel describing the expected variables and data types of a Benefit analysis object.
|
|
26
|
+
|
|
27
|
+
Attributes
|
|
28
|
+
----------
|
|
29
|
+
name: str
|
|
30
|
+
The name of the benefit analysis.
|
|
31
|
+
description: str, default=""
|
|
32
|
+
The description of the benefit analysis.
|
|
33
|
+
strategy : str
|
|
34
|
+
The name of the strategy. Should be a strategy saved in the database.
|
|
35
|
+
event_set : str
|
|
36
|
+
The name of the event set. Should be an event set saved in the database.
|
|
37
|
+
projection : str
|
|
38
|
+
The name of the projection. Should be a projection saved in the database.
|
|
39
|
+
future_year : int
|
|
40
|
+
The future year for the analysis.
|
|
41
|
+
current_situation : CurrentSituationModel
|
|
42
|
+
The current situation model.
|
|
43
|
+
baseline_strategy : str
|
|
44
|
+
The name of the baseline strategy.
|
|
45
|
+
discount_rate : float
|
|
46
|
+
The discount rate for the analysis.
|
|
47
|
+
implementation_cost : Optional[float], default = None
|
|
48
|
+
The implementation cost of the strategy.
|
|
49
|
+
annual_maint_cost : Optional[float], default = None
|
|
50
|
+
The annual maintenance cost of the strategy.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
strategy: str
|
|
54
|
+
event_set: str
|
|
55
|
+
projection: str
|
|
56
|
+
future_year: int
|
|
57
|
+
current_situation: CurrentSituationModel
|
|
58
|
+
baseline_strategy: str
|
|
59
|
+
discount_rate: float
|
|
60
|
+
implementation_cost: Optional[float] = None
|
|
61
|
+
annual_maint_cost: Optional[float] = None
|
|
File without changes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, List
|
|
3
|
+
|
|
4
|
+
import tomli
|
|
5
|
+
|
|
6
|
+
from flood_adapt.objects.events.event_set import EventSet
|
|
7
|
+
from flood_adapt.objects.events.events import (
|
|
8
|
+
Event,
|
|
9
|
+
Mode,
|
|
10
|
+
Template,
|
|
11
|
+
)
|
|
12
|
+
from flood_adapt.objects.events.historical import HistoricalEvent
|
|
13
|
+
from flood_adapt.objects.events.hurricane import (
|
|
14
|
+
HurricaneEvent,
|
|
15
|
+
TranslationModel,
|
|
16
|
+
)
|
|
17
|
+
from flood_adapt.objects.events.synthetic import SyntheticEvent
|
|
18
|
+
|
|
19
|
+
__all__ = ["TranslationModel", "EventSet"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EventFactory:
|
|
23
|
+
"""Factory class for creating events.
|
|
24
|
+
|
|
25
|
+
This class is used to create events based on a template.
|
|
26
|
+
|
|
27
|
+
Attributes
|
|
28
|
+
----------
|
|
29
|
+
_EVENT_TEMPLATES : dict[str, (Event, Event)]
|
|
30
|
+
Dictionary mapping event templates to event classes and models
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_EVENT_TEMPLATES = {
|
|
34
|
+
Template.Hurricane: HurricaneEvent,
|
|
35
|
+
Template.Historical: HistoricalEvent,
|
|
36
|
+
Template.Synthetic: SyntheticEvent,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def get_event_from_template(template: Template) -> type[Event]:
|
|
41
|
+
"""Get the event class corresponding to the template.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
template : str
|
|
46
|
+
Name of the event template
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
Type[Event]
|
|
51
|
+
Event template
|
|
52
|
+
"""
|
|
53
|
+
if template not in EventFactory._EVENT_TEMPLATES:
|
|
54
|
+
raise ValueError(f"Invalid event template: {template}")
|
|
55
|
+
return EventFactory._EVENT_TEMPLATES[template]
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def read_template(filepath: Path) -> Template:
|
|
59
|
+
"""Get event template from toml file."""
|
|
60
|
+
if not filepath.exists():
|
|
61
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
62
|
+
with open(filepath, mode="rb") as fp:
|
|
63
|
+
toml = tomli.load(fp)
|
|
64
|
+
if (template := toml.get("template")) is None:
|
|
65
|
+
raise ValueError(f"Event template not found in {filepath}")
|
|
66
|
+
|
|
67
|
+
return Template(template)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def read_mode(filepath: Path) -> Mode:
|
|
71
|
+
"""Get event mode from toml file."""
|
|
72
|
+
if not filepath.exists():
|
|
73
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
74
|
+
with open(filepath, mode="rb") as fp:
|
|
75
|
+
toml = tomli.load(fp)
|
|
76
|
+
if toml.get("mode") is None:
|
|
77
|
+
raise ValueError(f"Event mode not found in {filepath}")
|
|
78
|
+
return Mode(toml.get("mode"))
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def load_file(toml_file: Path) -> Event:
|
|
82
|
+
"""Return event object based on toml file.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
toml_file : str
|
|
87
|
+
Template name
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
Event
|
|
92
|
+
Event object
|
|
93
|
+
"""
|
|
94
|
+
mode = EventFactory.read_mode(toml_file)
|
|
95
|
+
if mode == Mode.risk:
|
|
96
|
+
event_type = EventSet
|
|
97
|
+
elif mode == Mode.single_event:
|
|
98
|
+
template = Template(EventFactory.read_template(toml_file))
|
|
99
|
+
event_type = EventFactory.get_event_from_template(template)
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Invalid event mode: {mode}")
|
|
102
|
+
return event_type.load_file(toml_file)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def load_dict(attrs: dict[str, Any] | Event) -> Event | EventSet:
|
|
106
|
+
"""Return event object based on attrs dict.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
attrs : dict[str, Any]
|
|
111
|
+
Event attributes
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
Event
|
|
116
|
+
Event object based on template
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(attrs, Event):
|
|
119
|
+
mode = attrs.mode
|
|
120
|
+
template = attrs.template
|
|
121
|
+
else:
|
|
122
|
+
mode = Mode(attrs.get("mode"))
|
|
123
|
+
template = Template(attrs.get("template"))
|
|
124
|
+
|
|
125
|
+
if mode == Mode.risk:
|
|
126
|
+
# TODO Load events
|
|
127
|
+
return EventSet(**attrs)
|
|
128
|
+
elif mode == Mode.single_event:
|
|
129
|
+
return EventFactory.get_event_from_template(template)(**attrs)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"Invalid event mode: {mode}")
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def get_allowed_forcings(template) -> dict[str, List[str]]:
|
|
135
|
+
return EventFactory.get_event_from_template(template).get_allowed_forcings()
|