weatherdb 1.1.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.
- docker/Dockerfile +30 -0
- docker/docker-compose.yaml +58 -0
- docker/docker-compose_test.yaml +24 -0
- docker/start-docker-test.sh +6 -0
- docs/requirements.txt +10 -0
- docs/source/Changelog.md +2 -0
- docs/source/License.rst +7 -0
- docs/source/Methode.md +161 -0
- docs/source/_static/custom.css +8 -0
- docs/source/_static/favicon.ico +0 -0
- docs/source/_static/logo.png +0 -0
- docs/source/api/api.rst +15 -0
- docs/source/api/cli.rst +8 -0
- docs/source/api/weatherDB.broker.rst +10 -0
- docs/source/api/weatherDB.config.rst +7 -0
- docs/source/api/weatherDB.db.rst +23 -0
- docs/source/api/weatherDB.rst +22 -0
- docs/source/api/weatherDB.station.rst +56 -0
- docs/source/api/weatherDB.stations.rst +46 -0
- docs/source/api/weatherDB.utils.rst +22 -0
- docs/source/conf.py +137 -0
- docs/source/index.rst +33 -0
- docs/source/setup/Configuration.md +127 -0
- docs/source/setup/Hosting.md +9 -0
- docs/source/setup/Install.md +49 -0
- docs/source/setup/Quickstart.md +183 -0
- docs/source/setup/setup.rst +12 -0
- weatherdb/__init__.py +24 -0
- weatherdb/_version.py +1 -0
- weatherdb/alembic/README.md +8 -0
- weatherdb/alembic/alembic.ini +80 -0
- weatherdb/alembic/config.py +9 -0
- weatherdb/alembic/env.py +100 -0
- weatherdb/alembic/script.py.mako +26 -0
- weatherdb/alembic/versions/V1.0.0_initial_database_creation.py +898 -0
- weatherdb/alembic/versions/V1.0.2_more_charachters_for_settings+term_station_ma_raster.py +88 -0
- weatherdb/alembic/versions/V1.0.5_fix-ma-raster-values.py +152 -0
- weatherdb/alembic/versions/V1.0.6_update-views.py +22 -0
- weatherdb/broker.py +667 -0
- weatherdb/cli.py +214 -0
- weatherdb/config/ConfigParser.py +663 -0
- weatherdb/config/__init__.py +5 -0
- weatherdb/config/config_default.ini +162 -0
- weatherdb/db/__init__.py +3 -0
- weatherdb/db/connections.py +374 -0
- weatherdb/db/fixtures/RichterParameters.json +34 -0
- weatherdb/db/models.py +402 -0
- weatherdb/db/queries/get_quotient.py +155 -0
- weatherdb/db/views.py +165 -0
- weatherdb/station/GroupStation.py +710 -0
- weatherdb/station/StationBases.py +3108 -0
- weatherdb/station/StationET.py +111 -0
- weatherdb/station/StationP.py +807 -0
- weatherdb/station/StationPD.py +98 -0
- weatherdb/station/StationT.py +164 -0
- weatherdb/station/__init__.py +13 -0
- weatherdb/station/constants.py +21 -0
- weatherdb/stations/GroupStations.py +519 -0
- weatherdb/stations/StationsBase.py +1021 -0
- weatherdb/stations/StationsBaseTET.py +30 -0
- weatherdb/stations/StationsET.py +17 -0
- weatherdb/stations/StationsP.py +128 -0
- weatherdb/stations/StationsPD.py +24 -0
- weatherdb/stations/StationsT.py +21 -0
- weatherdb/stations/__init__.py +11 -0
- weatherdb/utils/TimestampPeriod.py +369 -0
- weatherdb/utils/__init__.py +3 -0
- weatherdb/utils/dwd.py +350 -0
- weatherdb/utils/geometry.py +69 -0
- weatherdb/utils/get_data.py +285 -0
- weatherdb/utils/logging.py +126 -0
- weatherdb-1.1.0.dist-info/LICENSE +674 -0
- weatherdb-1.1.0.dist-info/METADATA +765 -0
- weatherdb-1.1.0.dist-info/RECORD +77 -0
- weatherdb-1.1.0.dist-info/WHEEL +5 -0
- weatherdb-1.1.0.dist-info/entry_points.txt +2 -0
- weatherdb-1.1.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
[database]
|
2
|
+
; These are the main database settings
|
3
|
+
; The database is created with the cli command create-db-schema or the weatherdb.Broker().create_db_schema() method
|
4
|
+
; you can define multiple database connections by adding a new section like [database.connection_name], where you can define your connection name
|
5
|
+
; The connection setting defines which connection is used if not specified
|
6
|
+
connection = main
|
7
|
+
|
8
|
+
[database:main]
|
9
|
+
; setup a database connection
|
10
|
+
; The password should not be stored in the config file, as it is a security risk.
|
11
|
+
; the module will ask for the password on first execution and store it in the keyring.
|
12
|
+
HOST = localhost
|
13
|
+
PORT = 5432
|
14
|
+
DATABASE = weatherdb
|
15
|
+
USER = weatherdb_user
|
16
|
+
|
17
|
+
[database:owner]
|
18
|
+
; You can setup multiple database connections
|
19
|
+
HOST = localhost
|
20
|
+
PORT = 5432
|
21
|
+
DATABASE = weatherdb
|
22
|
+
USER = weatherdb_owner
|
23
|
+
|
24
|
+
[logging]
|
25
|
+
; The logging settings
|
26
|
+
; Should the log be written to a file or to the console
|
27
|
+
; Possible values: file, console
|
28
|
+
HANDLERS =
|
29
|
+
|
30
|
+
; The log level
|
31
|
+
; Possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
32
|
+
LEVEL = DEBUG
|
33
|
+
|
34
|
+
; The log directory
|
35
|
+
; The path can contain the module path as basis with ${main:module_path} or the complete path.
|
36
|
+
; if the log should be written to the console, this setting is ignored
|
37
|
+
DIRECTORY = ${main:module_path}/logs
|
38
|
+
|
39
|
+
; The log file template
|
40
|
+
; The log file will be named with the template and rotated daily with the date appended
|
41
|
+
; The template can contain the computer host name with {host} and the current user name with {user}
|
42
|
+
FILE = weatherDB_{host}_{user}.log
|
43
|
+
|
44
|
+
; Should the older rotated log files be compressed or not?
|
45
|
+
; Possible values: True, False
|
46
|
+
; The default is True
|
47
|
+
COMPRESSION = True
|
48
|
+
|
49
|
+
; The log file format
|
50
|
+
; The format can contain the following variables:
|
51
|
+
; {asctime} - the time of the log entry
|
52
|
+
; {name} - the name of the logger
|
53
|
+
; {levelname} - the log level
|
54
|
+
; {message} - the log message
|
55
|
+
FORMAT = %(asctime)s - %(name)s - %(levelname)s - %(message)s
|
56
|
+
|
57
|
+
[data]
|
58
|
+
; The WeatherDB module needs several data files to work properly.
|
59
|
+
; those files are only used by the main managing module, not by the users.
|
60
|
+
|
61
|
+
; The data files like multi annual and dems rasters are normaly stored in the data directory.
|
62
|
+
; The path can contain the module path as basis with ${main:module_path} or the complete path.
|
63
|
+
; if they are stored somewhere else set the path here
|
64
|
+
BASE_DIR = ${main:module_path}/data
|
65
|
+
|
66
|
+
[data:rasters]
|
67
|
+
; Digital elevation models (DEM)
|
68
|
+
; The paths can contain the module path as basis with ${main:module_path} or the DATA.BASE_DIR with ${data:BASE_DIR} or the complete path.
|
69
|
+
; The module needs at least one DEM to calculate the horizon shadowing angle.
|
70
|
+
; as finer DEM are sometimes not covering the whole area of germany+40km, that is needed, you can add additional DEMs, that gets used if the first one
|
71
|
+
; the DEMs are used in the order of appearance in the list
|
72
|
+
DEMS = ${data:BASE_DIR}/DEM/DEM25.tif,
|
73
|
+
${data:BASE_DIR}/DEM/DEM80.tif
|
74
|
+
|
75
|
+
; Regionalisation Rasters
|
76
|
+
; the module works with regionalised multi-annual rasters from the DWD
|
77
|
+
; the module needs at least the HYRAS and DWD raster, as this is the basis for the regionalisation of the station data
|
78
|
+
; The rasters should be in a cartesian coordinate system with meter as unit to be able to calculate distances (e.g. EPSG:3035)
|
79
|
+
|
80
|
+
[data:rasters:hyras]
|
81
|
+
; multi annual HYRAS raster
|
82
|
+
; The original data can be found on: https://opendata.dwd.de/climate_environment/CDC/grids_germany/multi_annual/hyras_de/precipitation/
|
83
|
+
; to get better results, we refined the 1km rasters to 25m raster, based on a DEM25. (this file is not providen, but can be asked for)
|
84
|
+
FILE = ${data:BASE_DIR}/regionalisation/HYRAS_ma_1991_2020_DGM25.tif
|
85
|
+
|
86
|
+
; This raster file should have at least 3 raster bands for summer, winter and yearly means.
|
87
|
+
; Give the rasters names or band numbers
|
88
|
+
BAND_P_WIHY = n_hyras_wihj
|
89
|
+
BAND_P_SUHY = n_hyras_sohj
|
90
|
+
BAND_P_YEAR = n_hyras_year
|
91
|
+
|
92
|
+
; The EPSG code of the rasters if not defined in the raster file itself
|
93
|
+
SRID = 3035
|
94
|
+
|
95
|
+
; factors to apply to raster value to get to mm or °C
|
96
|
+
; the rasters should be in integer format, as the factors are applied to the raster values to get decimal values
|
97
|
+
FACTOR_P_WIHY = 1
|
98
|
+
FACTOR_P_SUHY = 1
|
99
|
+
FACTOR_P_YEAR = 1
|
100
|
+
|
101
|
+
[data:rasters:dwd]
|
102
|
+
; multi annual DWD raster
|
103
|
+
FILE = ${data:BASE_DIR}/regionalisation/DWD-grid_ma_1991_2020_DGM25.tif
|
104
|
+
BAND_P_WIHY = n_wihj
|
105
|
+
BAND_P_SUHY = n_sohj
|
106
|
+
BAND_P_YEAR = n_year
|
107
|
+
BAND_T_YEAR = t_year
|
108
|
+
BAND_ET_YEAR = et_year
|
109
|
+
|
110
|
+
; The EPSG code of the rasters if not defined in the raster file itself
|
111
|
+
SRID = 3035
|
112
|
+
|
113
|
+
; factors to apply to raster value to get to mm or °C
|
114
|
+
; the rasters should be in integer format, as the factors are applied to the raster values to get decimal values
|
115
|
+
FACTOR_P_WIHY = 1
|
116
|
+
FACTOR_P_SUHY = 1
|
117
|
+
FACTOR_P_YEAR = 1
|
118
|
+
FACTOR_T_YEAR = 0.1
|
119
|
+
FACTOR_ET_YEAR = 1
|
120
|
+
|
121
|
+
[weatherdb]
|
122
|
+
; The WeatherDB module settings to setup different parts of the module
|
123
|
+
|
124
|
+
; The radius in meters to use for the horizon shadowing calculation
|
125
|
+
; The default is 75km, this value got defined because the maximum possible height is around 4000m for germany
|
126
|
+
; --> horizon angle = arctan(4/75) ~ 3°, this is the lowest category in the richter class table
|
127
|
+
HORIZON_RADIUS = 75000
|
128
|
+
|
129
|
+
; The projected coordinate reference system (CRS) to use for the calculation of the distance during the horizon shadowing calculation
|
130
|
+
; This can be different than the DEM CRS, but has to be a projected CRS with meter as unit
|
131
|
+
; The value should be an EPSG code, e.g. 25832 for UTM zone 32N
|
132
|
+
; The default is UTM zone 32N (EPSG:25832)
|
133
|
+
HORIZON_CRS = 25832
|
134
|
+
|
135
|
+
; The projected coordinate reference system (CRS) to use for the calculation of the distance during raster sampling
|
136
|
+
; This can be different than the DEM CRS, but has to be a projected CRS with meter as unit
|
137
|
+
; The value should be an EPSG code, e.g. 25832 for UTM zone 32N
|
138
|
+
; The default is the same as the HORISON_CRS
|
139
|
+
RASTER_BUFFER_CRS = ${weatherdb:HORIZON_CRS}
|
140
|
+
|
141
|
+
; The minimum date to download data from the DWD
|
142
|
+
; The earlier you go the less data is available and the further away the stations have to be to fill the timeseries
|
143
|
+
; The default is 1999-01-01
|
144
|
+
MIN_DATE = 1999-01-01
|
145
|
+
|
146
|
+
|
147
|
+
[weatherdb:max_fillup_distance]
|
148
|
+
; The maximum distance in meters to use for the filling of the station data
|
149
|
+
; For each parameter (P, T, ET) the module uses a different distance
|
150
|
+
; Precipitation (P)
|
151
|
+
P = 110000
|
152
|
+
; Temperature (T)
|
153
|
+
T = 150000
|
154
|
+
; Evapotranspiration (ET)
|
155
|
+
ET = 150000
|
156
|
+
|
157
|
+
; for developpers:
|
158
|
+
; ----------------
|
159
|
+
;
|
160
|
+
; [alembic]
|
161
|
+
; The database connection used by the alembic scripts
|
162
|
+
; connection = main
|
weatherdb/db/__init__.py
ADDED
@@ -0,0 +1,374 @@
|
|
1
|
+
# libraries
|
2
|
+
import sqlalchemy
|
3
|
+
from sqlalchemy import text as sqltxt
|
4
|
+
from sqlalchemy import URL
|
5
|
+
import functools
|
6
|
+
|
7
|
+
from ..config import config
|
8
|
+
|
9
|
+
# DB connection
|
10
|
+
###############
|
11
|
+
class DBEngine:
|
12
|
+
def __init__(self):
|
13
|
+
self._reset_engine()
|
14
|
+
|
15
|
+
# add the listeners for configuration changes
|
16
|
+
self._config_listener_connection = (
|
17
|
+
"database",
|
18
|
+
"connection",
|
19
|
+
self._connection_update)
|
20
|
+
self._config_listener_subsection = (
|
21
|
+
f"database:{config.get('database', 'connection')}",
|
22
|
+
None,
|
23
|
+
self._reset_engine)
|
24
|
+
config.add_listener(*self._config_listener_connection)
|
25
|
+
config.add_listener(*self._config_listener_subsection)
|
26
|
+
|
27
|
+
def __del__(self):
|
28
|
+
config.remove_listener(*self._config_listener_connection)
|
29
|
+
config.remove_listener(*self._config_listener_subsection)
|
30
|
+
|
31
|
+
def _connection_update(self):
|
32
|
+
"""Listener if another database subsection is selected."""
|
33
|
+
# first remove the old section listener
|
34
|
+
config.remove_listener(*self._config_listener_subsection)
|
35
|
+
|
36
|
+
# add the new section listener
|
37
|
+
self._config_listener_subsection = (
|
38
|
+
f"database:{config.get('database', 'connection')}",
|
39
|
+
None,
|
40
|
+
self._reset_engine)
|
41
|
+
config.add_listener(*self._config_listener_subsection)
|
42
|
+
|
43
|
+
# create a new engine
|
44
|
+
self.create_engine()
|
45
|
+
|
46
|
+
# Engine and Session
|
47
|
+
# ##################
|
48
|
+
def _reset_engine(self):
|
49
|
+
"""Reset the engine to None to force a recreation on next usage."""
|
50
|
+
self._engine = None
|
51
|
+
self._session = None
|
52
|
+
self._is_superuser = None
|
53
|
+
self._select_privilege = None
|
54
|
+
self._create_privilege = None
|
55
|
+
self._update_privilege = None
|
56
|
+
self._insert_privilege = None
|
57
|
+
self._delete_privilege = None
|
58
|
+
self._priviledge_to_recheck = set()
|
59
|
+
|
60
|
+
def connect(self, *args, **kwargs):
|
61
|
+
return self.get_engine().connect(*args, **kwargs)
|
62
|
+
|
63
|
+
def get_engine(self):
|
64
|
+
"""Get the sqlalchemy database engine.
|
65
|
+
|
66
|
+
Returns the last created engine if possible or creates a new one.
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
sqlalchemy.engine.base.Engine
|
71
|
+
_description_
|
72
|
+
"""
|
73
|
+
if self._engine is None:
|
74
|
+
self.create_engine()
|
75
|
+
|
76
|
+
return self._engine
|
77
|
+
|
78
|
+
def create_engine(self):
|
79
|
+
# create the engine
|
80
|
+
con_key = config.get("database", "connection")
|
81
|
+
con_sect_key = f"database:{con_key}"
|
82
|
+
con_section = config[con_sect_key]
|
83
|
+
user, pwd = config.get_db_credentials()
|
84
|
+
self._engine = sqlalchemy.create_engine(
|
85
|
+
URL.create(
|
86
|
+
drivername="postgresql+psycopg2",
|
87
|
+
username=user,
|
88
|
+
password=pwd,
|
89
|
+
host=con_section["HOST"],
|
90
|
+
database=con_section["DATABASE"],
|
91
|
+
port=con_section["PORT"]),
|
92
|
+
connect_args={
|
93
|
+
"options": "-c timezone=utc",
|
94
|
+
'connect_timeout': 10})
|
95
|
+
self._check_is_superuser()
|
96
|
+
|
97
|
+
return self._engine
|
98
|
+
|
99
|
+
@property
|
100
|
+
def engine(self):
|
101
|
+
return self.get_engine()
|
102
|
+
|
103
|
+
@engine.setter
|
104
|
+
def engine(self, value):
|
105
|
+
raise PermissionError("You are not allowed to change the engine of the database connection.\nPlease change the configuration to change the database connection.")
|
106
|
+
|
107
|
+
def create_session(self):
|
108
|
+
"""Create a new session from the engine.
|
109
|
+
|
110
|
+
Returns
|
111
|
+
-------
|
112
|
+
sqlalchemy.orm.session.Session
|
113
|
+
_description_
|
114
|
+
"""
|
115
|
+
self._session = sqlalchemy.orm.sessionmaker(bind=self.get_engine())
|
116
|
+
return self._session
|
117
|
+
|
118
|
+
def get_session(self):
|
119
|
+
"""Get a session from the engine.
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
sqlalchemy.orm.session.Session
|
124
|
+
_description_
|
125
|
+
"""
|
126
|
+
if self._session is None:
|
127
|
+
return self.create_session()
|
128
|
+
else:
|
129
|
+
return self._session
|
130
|
+
|
131
|
+
@property
|
132
|
+
def session(self):
|
133
|
+
return self.get_session()
|
134
|
+
|
135
|
+
@session.setter
|
136
|
+
def session(self, value):
|
137
|
+
raise PermissionError("You are not allowed to change the session of the database connection.\nPlease change the configuration to change the database connection.")
|
138
|
+
|
139
|
+
def reload_config(self):
|
140
|
+
"""Reload the configuration and create a new engine.
|
141
|
+
"""
|
142
|
+
self.create_engine()
|
143
|
+
|
144
|
+
# Privilege checks
|
145
|
+
##################
|
146
|
+
def _check_is_superuser(self):
|
147
|
+
with self.connect() as con:
|
148
|
+
self._is_superuser = con.execute(sqltxt(
|
149
|
+
"SELECT usesuper FROM pg_user WHERE usename = current_user;"
|
150
|
+
)).first()[0]
|
151
|
+
return self._is_superuser
|
152
|
+
|
153
|
+
@property
|
154
|
+
def is_superuser(self):
|
155
|
+
if self._is_superuser is None:
|
156
|
+
self._check_is_superuser()
|
157
|
+
return self._is_superuser
|
158
|
+
|
159
|
+
@is_superuser.setter
|
160
|
+
def is_superuser(self, value):
|
161
|
+
raise PermissionError("You are not allowed to change the superuser status of the database connection, as this is due to PostGreSQL database privileges of your database user.")
|
162
|
+
|
163
|
+
def _check_privilege(self, privilege):
|
164
|
+
if self._is_superuser:
|
165
|
+
return True
|
166
|
+
else:
|
167
|
+
with self.connect() as con:
|
168
|
+
if privilege == "CREATE":
|
169
|
+
return con.execute(sqltxt(
|
170
|
+
f"SELECT pg_catalog.has_schema_privilege('public', '{privilege}');"
|
171
|
+
)).first()[0]
|
172
|
+
res = con.execute(sqltxt(
|
173
|
+
f"""SELECT bool_and(pg_catalog.has_table_privilege(table_name, '{privilege}'))
|
174
|
+
FROM information_schema.TABLES
|
175
|
+
WHERE table_schema=CURRENT_SCHEMA AND table_type='BASE TABLE';"""
|
176
|
+
)).first()[0]
|
177
|
+
if res is None:
|
178
|
+
if privilege in self._priviledge_to_recheck:
|
179
|
+
self.log.debug(f"Privilege {privilege} not found, probably because database isn't initiated. Setting as True.")
|
180
|
+
else:
|
181
|
+
self._priviledge_to_recheck.add(privilege)
|
182
|
+
return True
|
183
|
+
else:
|
184
|
+
if privilege in self._priviledge_to_recheck:
|
185
|
+
self._priviledge_to_recheck.remove(privilege)
|
186
|
+
return res
|
187
|
+
|
188
|
+
def _check_select_privilege(self):
|
189
|
+
"""Check on the database if the user has the SELECT privilege."""
|
190
|
+
self._select_privilege = self._check_privilege("SELECT")
|
191
|
+
return self._select_privilege
|
192
|
+
|
193
|
+
def _check_update_privilege(self):
|
194
|
+
"""Check on the database if the user has the UPDATE privilege."""
|
195
|
+
self._update_privilege = self._check_privilege("UPDATE")
|
196
|
+
return self._update_privilege
|
197
|
+
|
198
|
+
def _check_insert_privilege(self):
|
199
|
+
"""Check on the database if the user has the INSERT privilege."""
|
200
|
+
self._insert_privilege = self._check_privilege("INSERT")
|
201
|
+
return self._insert_privilege
|
202
|
+
|
203
|
+
def _check_create_privilege(self):
|
204
|
+
"""Check on the database if the user has the CREATE privilege."""
|
205
|
+
self._create_privilege = self._check_privilege("CREATE")
|
206
|
+
return self._create_privilege
|
207
|
+
|
208
|
+
def _check_delete_privilege(self):
|
209
|
+
"""Check on the database if the user has the DELETE privilege."""
|
210
|
+
self._delete_privilege = self._check_privilege("DELETE")
|
211
|
+
return self._delete_privilege
|
212
|
+
|
213
|
+
@property
|
214
|
+
def select_privilege(self):
|
215
|
+
"""Does the user have the PostGreSQL SELECT privilege on the database?"""
|
216
|
+
if self._select_privilege is None or "SELECT" in self._priviledge_to_recheck:
|
217
|
+
self._check_select_privilege()
|
218
|
+
return self._select_privilege
|
219
|
+
|
220
|
+
@property
|
221
|
+
def update_privilege(self):
|
222
|
+
"""Does the user have the PostGreSQL UPDATE privilege on the database?"""
|
223
|
+
if self._update_privilege is None or "UPDATE" in self._priviledge_to_recheck:
|
224
|
+
self._check_update_privilege()
|
225
|
+
return self._update_privilege
|
226
|
+
|
227
|
+
@property
|
228
|
+
def insert_privilege(self):
|
229
|
+
"""Does the user have the PostGreSQL INSERT privilege on the database?"""
|
230
|
+
if self._insert_privilege is None or "INSERT" in self._priviledge_to_recheck:
|
231
|
+
self._check_insert_privilege()
|
232
|
+
return self._insert_privilege
|
233
|
+
|
234
|
+
@property
|
235
|
+
def upsert_privilege(self):
|
236
|
+
"""Does the user have the PostGreSQL INSERT and UPDATE privilege on the database?"""
|
237
|
+
return self.insert_privilege and self.update_privilege
|
238
|
+
|
239
|
+
@property
|
240
|
+
def create_privilege(self):
|
241
|
+
"""Does the user have the PostGreSQL CREATE privilege on the database?"""
|
242
|
+
if self._create_privilege is None or "CREATE" in self._priviledge_to_recheck:
|
243
|
+
self._check_create_privilege()
|
244
|
+
return self._create_privilege
|
245
|
+
|
246
|
+
@property
|
247
|
+
def delete_privilege(self):
|
248
|
+
"""Does the user have the PostGreSQL DELETE privilege on the database?"""
|
249
|
+
if self._delete_privilege is None or "DELETE" in self._priviledge_to_recheck:
|
250
|
+
self._check_delete_privilege()
|
251
|
+
return self._delete_privilege
|
252
|
+
|
253
|
+
@property
|
254
|
+
def all_privileges(self):
|
255
|
+
"""Does the user have all (SELECT, UPDATE, INSERT, DELETE, CREATE) PostGreSQL privileges on the database?"""
|
256
|
+
return self.select_privilege and \
|
257
|
+
self.update_privilege and \
|
258
|
+
self.insert_privilege and \
|
259
|
+
self.create_privilege and \
|
260
|
+
self.delete_privilege
|
261
|
+
|
262
|
+
def _privilege_setters(self, property_name):
|
263
|
+
raise PermissionError(f"You are not allowed to change the value of {property_name} as this is due to PostGreSQL database privileges of your user.")
|
264
|
+
|
265
|
+
@select_privilege.setter
|
266
|
+
def select_privilege(self, value):
|
267
|
+
self._privilege_setters("select_privilege")
|
268
|
+
|
269
|
+
@update_privilege.setter
|
270
|
+
def update_privilege(self, value):
|
271
|
+
self._privilege_setters("update_privilege")
|
272
|
+
|
273
|
+
@insert_privilege.setter
|
274
|
+
def insert_privilege(self, value):
|
275
|
+
self._privilege_setters("insert_privilege")
|
276
|
+
|
277
|
+
@upsert_privilege.setter
|
278
|
+
def upsert_privilege(self, value):
|
279
|
+
self._privilege_setters("upsert_privilege")
|
280
|
+
|
281
|
+
@create_privilege.setter
|
282
|
+
def create_privilege(self, value):
|
283
|
+
self._privilege_setters("create_privilege")
|
284
|
+
|
285
|
+
@delete_privilege.setter
|
286
|
+
def delete_privilege(self, value):
|
287
|
+
self._privilege_setters("delete_privilege")
|
288
|
+
|
289
|
+
@all_privileges.setter
|
290
|
+
def all_privileges(self, value):
|
291
|
+
self._privilege_setters("all_privileges")
|
292
|
+
|
293
|
+
def deco_is_superuser(self, target):
|
294
|
+
"""Decorator to check if the user is a superuser."""
|
295
|
+
@functools.wraps(target)
|
296
|
+
def wrapper(*args, **kwargs):
|
297
|
+
if self.is_superuser:
|
298
|
+
return target(*args, **kwargs)
|
299
|
+
else:
|
300
|
+
raise PermissionError("You are no super user of the Database and therefor this function is not available.")
|
301
|
+
return wrapper
|
302
|
+
|
303
|
+
def deco_select_privilege(self, target):
|
304
|
+
"""Decorator to check if the user has the SELECT privilege."""
|
305
|
+
@functools.wraps(target)
|
306
|
+
def wrapper(*args, **kwargs):
|
307
|
+
if self.select_privilege:
|
308
|
+
return target(*args, **kwargs)
|
309
|
+
else:
|
310
|
+
raise PermissionError("You have no select privilege on the database and therefor this function is not available.")
|
311
|
+
return wrapper
|
312
|
+
|
313
|
+
def deco_update_privilege(self, target):
|
314
|
+
"""Decorator to check if the user has the UPDATE privilege."""
|
315
|
+
@functools.wraps(target)
|
316
|
+
def wrapper(*args, **kwargs):
|
317
|
+
if self.update_privilege:
|
318
|
+
return target(*args, **kwargs)
|
319
|
+
else:
|
320
|
+
raise PermissionError("You have no update privilege on the database and therefor this function is not available.")
|
321
|
+
return wrapper
|
322
|
+
|
323
|
+
def deco_insert_privilege(self, target):
|
324
|
+
"""Decorator to check if the user has the INSERT privilege."""
|
325
|
+
@functools.wraps(target)
|
326
|
+
def wrapper(*args, **kwargs):
|
327
|
+
if self.insert_privilege:
|
328
|
+
return target(*args, **kwargs)
|
329
|
+
else:
|
330
|
+
raise PermissionError("You have no insert privilege on the database and therefor this function is not available.")
|
331
|
+
return wrapper
|
332
|
+
|
333
|
+
def deco_upsert_privilege(self, target):
|
334
|
+
"""Decorator to check if the user has the INSERT and UPDATE privilege."""
|
335
|
+
@functools.wraps(target)
|
336
|
+
def wrapper(*args, **kwargs):
|
337
|
+
if self.upsert_privilege:
|
338
|
+
return target(*args, **kwargs)
|
339
|
+
else:
|
340
|
+
raise PermissionError("You have no upsert privilege on the database and therefor this function is not available.")
|
341
|
+
return wrapper
|
342
|
+
|
343
|
+
def deco_create_privilege(self, target):
|
344
|
+
"""Decorator to check if the user has the CREATE privilege."""
|
345
|
+
@functools.wraps(target)
|
346
|
+
def wrapper(*args, **kwargs):
|
347
|
+
if self.create_privilege:
|
348
|
+
return target(*args, **kwargs)
|
349
|
+
else:
|
350
|
+
raise PermissionError("You are no admin user of the Database and therefor this function is not available.")
|
351
|
+
return wrapper
|
352
|
+
|
353
|
+
def deco_delete_privilege(self, target):
|
354
|
+
"""Decorator to check if the user has the DELETE privilege."""
|
355
|
+
@functools.wraps(target)
|
356
|
+
def wrapper(*args, **kwargs):
|
357
|
+
if self.delete_privilege:
|
358
|
+
return target(*args, **kwargs)
|
359
|
+
else:
|
360
|
+
raise PermissionError("You have no delete privilege on the database and therefor this function is not available.")
|
361
|
+
return wrapper
|
362
|
+
|
363
|
+
def deco_all_privileges(self, target):
|
364
|
+
"""Decorator to check if the user has all (SELECT, UPDATE, INSERT, DELETE, CREATE) privileges."""
|
365
|
+
@functools.wraps(target)
|
366
|
+
def wrapper(*args, **kwargs):
|
367
|
+
if self.all_privileges:
|
368
|
+
return target(*args, **kwargs)
|
369
|
+
else:
|
370
|
+
raise PermissionError("You are no super user of the Database and therefor this function is not available.")
|
371
|
+
return wrapper
|
372
|
+
|
373
|
+
|
374
|
+
db_engine = DBEngine()
|
@@ -0,0 +1,34 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"precipitation_typ": "precip_summer",
|
4
|
+
"e": 0.38,
|
5
|
+
"b_no-protection": 0.345,
|
6
|
+
"b_little-protection": 0.31,
|
7
|
+
"b_protected": 0.28,
|
8
|
+
"b_heavy-protection": 0.245
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"precipitation_typ": "precip_winter",
|
12
|
+
"e": 0.46,
|
13
|
+
"b_no-protection": 0.34,
|
14
|
+
"b_little-protection": 0.28,
|
15
|
+
"b_protected": 0.24,
|
16
|
+
"b_heavy-protection": 0.19
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"precipitation_typ": "mix",
|
20
|
+
"e": 0.55,
|
21
|
+
"b_no-protection": 0.535,
|
22
|
+
"b_little-protection": 0.39,
|
23
|
+
"b_protected": 0.305,
|
24
|
+
"b_heavy-protection": 0.185
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"precipitation_typ": "snow",
|
28
|
+
"e": 0.82,
|
29
|
+
"b_no-protection": 0.72,
|
30
|
+
"b_little-protection": 0.51,
|
31
|
+
"b_protected": 0.33,
|
32
|
+
"b_heavy-protection": 0.21
|
33
|
+
}
|
34
|
+
]
|