weatherdb 1.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
weatherdb/broker.py
ADDED
@@ -0,0 +1,667 @@
|
|
1
|
+
"""
|
2
|
+
This submodule has only one class Broker. This one is used to do actions on all the stations together. Mainly only used for updating the DB.
|
3
|
+
"""
|
4
|
+
# libraries
|
5
|
+
import logging
|
6
|
+
from sqlalchemy import text as sqltxt
|
7
|
+
from packaging import version as pv
|
8
|
+
from pathlib import Path
|
9
|
+
import textwrap
|
10
|
+
from contextlib import contextmanager
|
11
|
+
import atexit
|
12
|
+
import sys
|
13
|
+
|
14
|
+
from .db.connections import db_engine
|
15
|
+
from .stations import StationsP, StationsPD, StationsT, StationsET
|
16
|
+
from . import __version__
|
17
|
+
|
18
|
+
__all__ = ["Broker"]
|
19
|
+
|
20
|
+
log = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
class Broker(object):
|
23
|
+
"""A class to manage and update the database.
|
24
|
+
|
25
|
+
Can get used to update all the stations and parameters at once.
|
26
|
+
|
27
|
+
This class is only working with SELECT and UPDATE user privileges. Even Better is to also have DELETE and INSERT privileges.
|
28
|
+
"""
|
29
|
+
def __init__(self):
|
30
|
+
if not db_engine.update_privilege & db_engine.select_privilege:
|
31
|
+
raise PermissionError("You don't have enough privileges to use the Broker. The database user must have SELECT and UPDATE privileges.")
|
32
|
+
|
33
|
+
self.stations_pd = StationsPD()
|
34
|
+
self.stations_t = StationsT()
|
35
|
+
self.stations_et = StationsET()
|
36
|
+
self.stations_p = StationsP()
|
37
|
+
self.stations = [
|
38
|
+
self.stations_pd,
|
39
|
+
self.stations_t,
|
40
|
+
self.stations_et,
|
41
|
+
self.stations_p]
|
42
|
+
|
43
|
+
self._is_active = False
|
44
|
+
|
45
|
+
self._db_schema_valid = None
|
46
|
+
|
47
|
+
def _check_paras(self, paras, valid_paras=["p_d", "p", "t", "et"]):
|
48
|
+
valid_paras = ["p_d", "p", "t", "et"]
|
49
|
+
for para in paras:
|
50
|
+
if para not in valid_paras:
|
51
|
+
raise ValueError(
|
52
|
+
"The given parameter {para} is not valid.".format(
|
53
|
+
para=para))
|
54
|
+
|
55
|
+
@property
|
56
|
+
def _alembic_config(self):
|
57
|
+
from .alembic.config import config as alembic_config
|
58
|
+
return alembic_config
|
59
|
+
|
60
|
+
@db_engine.deco_is_superuser
|
61
|
+
def create_db_schema(self, if_exists=None, silent=False, owner=None):
|
62
|
+
"""Create the database schema.
|
63
|
+
|
64
|
+
Parameters
|
65
|
+
----------
|
66
|
+
if_exists : str, optional
|
67
|
+
What to do if the tables already exist.
|
68
|
+
If None the user gets asked.
|
69
|
+
If "D" or "drop" the tables get dropped and recreated.
|
70
|
+
If "I" or "ignore" the existing tables get ignored and the creation of the schema continues for the other.
|
71
|
+
If "E" er "exit" the creation of the schema gets exited.
|
72
|
+
The default is None.
|
73
|
+
silent : bool, optional
|
74
|
+
If True the user gets not asked if the tables already exist.
|
75
|
+
If True, if_exists must not be None.
|
76
|
+
The default is False.
|
77
|
+
owner : str, optional
|
78
|
+
The user that should get the ownership of the tables and schemas.
|
79
|
+
If None the current database user will be the owner.
|
80
|
+
The default is None.
|
81
|
+
"""
|
82
|
+
# check silent
|
83
|
+
if silent and if_exists is None:
|
84
|
+
raise ValueError("silent can only be True if if_exists is not None.")
|
85
|
+
|
86
|
+
# add POSTGIS extension
|
87
|
+
with db_engine.connect() as con:
|
88
|
+
con.execute(sqltxt("CREATE EXTENSION IF NOT EXISTS postgis;"))
|
89
|
+
con.commit()
|
90
|
+
|
91
|
+
# check for existing tables
|
92
|
+
from .db.models import ModelBase
|
93
|
+
with db_engine.connect() as con:
|
94
|
+
res = con.execute(sqltxt("""
|
95
|
+
SELECT table_name
|
96
|
+
FROM information_schema.tables
|
97
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';""")
|
98
|
+
).fetchall()
|
99
|
+
existing_tables = [table[0] for table in res]
|
100
|
+
problem_tables = [table.name
|
101
|
+
for table in ModelBase.metadata.tables.values()
|
102
|
+
if table.name in existing_tables]
|
103
|
+
if len(problem_tables)>0 and silent:
|
104
|
+
log.info("The following tables already exist on the database:\n - " +
|
105
|
+
"\n - ".join([table for table in problem_tables]))
|
106
|
+
|
107
|
+
# ask the user what to do if if_exists is None
|
108
|
+
if if_exists is None:
|
109
|
+
print(textwrap.dedent(
|
110
|
+
"""What do you want to do?
|
111
|
+
- [D] : Drop all the tables and recreate them again.
|
112
|
+
- [I] : Ignore those tables and continue with the creation of the schema.
|
113
|
+
- [E] : Exit the creation of the schema."""))
|
114
|
+
while True:
|
115
|
+
answer = input("Your choice: ").upper()
|
116
|
+
if answer in ["D", "E", "I"]:
|
117
|
+
if_exists = answer
|
118
|
+
break
|
119
|
+
else:
|
120
|
+
print("Please enter a valid answer.")
|
121
|
+
|
122
|
+
# execute the choice
|
123
|
+
if if_exists.upper() == "D":
|
124
|
+
log.info("Dropping the tables.")
|
125
|
+
with db_engine.connect() as con:
|
126
|
+
for table in problem_tables:
|
127
|
+
con.execute(sqltxt(f"DROP TABLE {table} CASCADE;"))
|
128
|
+
con.commit()
|
129
|
+
elif if_exists.upper() == "E":
|
130
|
+
log.info("Exiting the creation of the schema.")
|
131
|
+
return
|
132
|
+
|
133
|
+
# create the tables
|
134
|
+
log.info("Creating the tables and views.")
|
135
|
+
with db_engine.connect() as con:
|
136
|
+
ModelBase.metadata.create_all(con, checkfirst=True)
|
137
|
+
con.commit()
|
138
|
+
|
139
|
+
# create the schema for the timeseries
|
140
|
+
with db_engine.connect() as con:
|
141
|
+
con.execute(sqltxt("CREATE SCHEMA IF NOT EXISTS timeseries;"))
|
142
|
+
con.commit()
|
143
|
+
|
144
|
+
# set the owner of the tables
|
145
|
+
if owner is not None:
|
146
|
+
log.info(f"Setting the owner of the tables to {owner}.")
|
147
|
+
with db_engine.connect() as con:
|
148
|
+
for table in ModelBase.metadata.tables.values():
|
149
|
+
con.execute(sqltxt(f"ALTER TABLE {table.name} OWNER TO {owner};"))
|
150
|
+
con.execute(sqltxt(f"ALTER SCHEMA timeseries OWNER TO {owner};"))
|
151
|
+
con.commit()
|
152
|
+
|
153
|
+
# tell alembic that the actual database schema is up-to-date
|
154
|
+
from alembic import command
|
155
|
+
command.stamp(self._alembic_config, "head")
|
156
|
+
|
157
|
+
def upgrade_db_schema(self, revision="head"):
|
158
|
+
"""Upgrade the database schema to a specific revision.
|
159
|
+
|
160
|
+
Parameters
|
161
|
+
----------
|
162
|
+
revision : str, optional
|
163
|
+
The revision to upgrade to.
|
164
|
+
If "head" the database gets upgraded to the latest revision.
|
165
|
+
The default is "head".
|
166
|
+
"""
|
167
|
+
from alembic import command
|
168
|
+
from alembic.runtime.environment import EnvironmentContext
|
169
|
+
from alembic.script import ScriptDirectory
|
170
|
+
|
171
|
+
from .db.models import ModelBase
|
172
|
+
|
173
|
+
# get alembic context
|
174
|
+
context = EnvironmentContext(
|
175
|
+
self._alembic_config,
|
176
|
+
ScriptDirectory(self._alembic_config.get_main_option("script_location")))
|
177
|
+
with db_engine.connect() as con:
|
178
|
+
context.configure(connection=con)
|
179
|
+
migration_context = context.get_context()
|
180
|
+
pre_version = pv.parse(migration_context.get_current_revision())
|
181
|
+
head_version = pv.parse(context.get_head_revision())
|
182
|
+
|
183
|
+
# check revision
|
184
|
+
if revision == "head":
|
185
|
+
version = head_version
|
186
|
+
else:
|
187
|
+
version = pv.parse(revision)
|
188
|
+
|
189
|
+
# remove all views
|
190
|
+
for view in ModelBase.metadata.views:
|
191
|
+
view.drop_view(None, con)
|
192
|
+
|
193
|
+
# apply the migrations
|
194
|
+
if version > pre_version:
|
195
|
+
command.upgrade(self._alembic_config, revision)
|
196
|
+
elif version < pre_version:
|
197
|
+
command.downgrade(self._alembic_config, revision)
|
198
|
+
|
199
|
+
# check if revision is the same as head
|
200
|
+
if "head" in revision or version == head_version:
|
201
|
+
# create the views
|
202
|
+
for view in ModelBase.metadata.views:
|
203
|
+
view.create_view(None, con)
|
204
|
+
else:
|
205
|
+
log.info("The views are not created because the revision is not head. This can resolve in errors during module execution.")
|
206
|
+
|
207
|
+
def _check_db_schema(self):
|
208
|
+
"""Check the database schema for differences to the models.
|
209
|
+
|
210
|
+
Returns
|
211
|
+
-------
|
212
|
+
bool
|
213
|
+
Whether the database schema is up-to-date.
|
214
|
+
|
215
|
+
Raises
|
216
|
+
------
|
217
|
+
Exception
|
218
|
+
If the database schema is not up-to-date.
|
219
|
+
"""
|
220
|
+
if self._db_schema_valid:
|
221
|
+
return self._db_schema_valid
|
222
|
+
else:
|
223
|
+
from alembic import command
|
224
|
+
try:
|
225
|
+
command.check(self._alembic_config)
|
226
|
+
self._db_schema_valid = True
|
227
|
+
except Exception as e:
|
228
|
+
self._db_schema_valid = False
|
229
|
+
if "Target database is not up to date" in str(e):
|
230
|
+
note = "You may need to run `weatherdb upgrade-db-schema` in CLI or use `weatherdb.Broker().upgrade_db_schema()` as python script to upgrade the database schema first."
|
231
|
+
if sys.version_info >= (3, 11):
|
232
|
+
e.add_note(note)
|
233
|
+
else:
|
234
|
+
raise Exception(str(e) + "\n" + note) from e
|
235
|
+
|
236
|
+
raise e
|
237
|
+
|
238
|
+
@db_engine.deco_all_privileges
|
239
|
+
def initiate_db(self, **kwargs):
|
240
|
+
"""Initiate the Database.
|
241
|
+
|
242
|
+
Downloads all the data from the CDC server for the first time.
|
243
|
+
Updates the multi-annual data and the richter-class for all the stations.
|
244
|
+
Quality checks and fills up the timeseries.
|
245
|
+
|
246
|
+
Parameters
|
247
|
+
----------
|
248
|
+
**kwargs : dict
|
249
|
+
The keyword arguments to pass to the called methods of the stations
|
250
|
+
"""
|
251
|
+
log.info("Broker initiate_db starts")
|
252
|
+
self._check_db_schema()
|
253
|
+
|
254
|
+
with self.activate():
|
255
|
+
self.update_meta(
|
256
|
+
paras=["p_d", "p", "t", "et"], **kwargs)
|
257
|
+
self.update_raw(
|
258
|
+
paras=["p_d", "p", "t", "et"],
|
259
|
+
only_new=False,
|
260
|
+
**kwargs)
|
261
|
+
self.update_ma_raster(
|
262
|
+
paras=["p_d", "p", "t", "et"],
|
263
|
+
**kwargs)
|
264
|
+
self.stations_p.update_richter_class(**kwargs)
|
265
|
+
self.quality_check(paras=["p", "t", "et"], **kwargs)
|
266
|
+
self.fillup(paras=["p", "t", "et"], **kwargs)
|
267
|
+
self.richter_correct(**kwargs)
|
268
|
+
|
269
|
+
self.set_db_version()
|
270
|
+
|
271
|
+
self.vacuum()
|
272
|
+
|
273
|
+
def update_raw(self, only_new=True, paras=["p_d", "p", "t", "et"], **kwargs):
|
274
|
+
"""Update the raw data from the DWD-CDC server to the database.
|
275
|
+
|
276
|
+
Parameters
|
277
|
+
----------
|
278
|
+
only_new : bool, optional
|
279
|
+
Get only the files that are not yet in the database?
|
280
|
+
If False all the available files are loaded again.
|
281
|
+
The default is True.
|
282
|
+
paras : list of str, optional
|
283
|
+
The parameters for which to do the actions.
|
284
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
285
|
+
The default is ["p_d", "p", "t", "et"].
|
286
|
+
**kwargs : dict
|
287
|
+
The keyword arguments to pass to the update_raw method of the stations
|
288
|
+
"""
|
289
|
+
self._check_paras(paras)
|
290
|
+
log.info("Broker update_raw starts")
|
291
|
+
self._check_db_schema()
|
292
|
+
|
293
|
+
with self.activate():
|
294
|
+
for stations in self.stations:
|
295
|
+
if stations._para in paras:
|
296
|
+
stations.update_raw(only_new=only_new, **kwargs)
|
297
|
+
|
298
|
+
def update_meta(self, paras=["p_d", "p", "t", "et"], **kwargs):
|
299
|
+
"""Update the meta file from the CDC Server to the Database.
|
300
|
+
|
301
|
+
Parameters
|
302
|
+
----------
|
303
|
+
paras : list of str, optional
|
304
|
+
The parameters for which to do the actions.
|
305
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
306
|
+
The default is ["p_d", "p", "t", "et"].
|
307
|
+
**kwargs : dict
|
308
|
+
The keyword arguments to pass to update_meta method of the stations
|
309
|
+
"""
|
310
|
+
self._check_paras(paras)
|
311
|
+
log.info("Broker update_meta starts")
|
312
|
+
self._check_db_schema()
|
313
|
+
|
314
|
+
with self.activate():
|
315
|
+
for stations in self.stations:
|
316
|
+
if stations._para in paras:
|
317
|
+
stations.update_meta(**kwargs)
|
318
|
+
|
319
|
+
def update_ma_raster(self, paras=["p_d", "p", "t", "et"], **kwargs):
|
320
|
+
"""Update the multi-annual data from raster to table.
|
321
|
+
|
322
|
+
Parameters
|
323
|
+
----------
|
324
|
+
paras : list of str, optional
|
325
|
+
The parameters for which to do the actions.
|
326
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
327
|
+
The default is ["p_d", "p", "t", "et"].
|
328
|
+
**kwargs : dict
|
329
|
+
The keyword arguments to pass to update_ma_raster method of the stations
|
330
|
+
"""
|
331
|
+
self._check_paras(paras)
|
332
|
+
log.info("Broker update_ma_raster starts")
|
333
|
+
self._check_db_schema()
|
334
|
+
|
335
|
+
with self.activate():
|
336
|
+
for stations in self.stations:
|
337
|
+
if stations._para in paras:
|
338
|
+
stations.update_ma_raster(**kwargs)
|
339
|
+
|
340
|
+
def update_ma_timeseries(self, paras=["p_d", "p", "t", "et"], **kwargs):
|
341
|
+
"""Update the multi-annual values from timeseries in the database.
|
342
|
+
|
343
|
+
Parameters
|
344
|
+
----------
|
345
|
+
paras : list of str, optional
|
346
|
+
The parameters for which to do the actions.
|
347
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
348
|
+
The default is ["p_d", "p", "t", "et"].
|
349
|
+
**kwargs : dict
|
350
|
+
The keyword arguments to pass to update_ma_timeseries method of the stations
|
351
|
+
"""
|
352
|
+
self._check_paras(paras)
|
353
|
+
log.info("Broker update_ma_timeseries starts")
|
354
|
+
self._check_db_schema()
|
355
|
+
|
356
|
+
with self.activate():
|
357
|
+
for stations in self.stations:
|
358
|
+
if stations._para in paras:
|
359
|
+
stations.update_ma_timeseries(**kwargs)
|
360
|
+
|
361
|
+
def update_period_meta(self, paras=["p_d", "p", "t", "et"], **kwargs):
|
362
|
+
"""Update the periods in the meta table.
|
363
|
+
|
364
|
+
Parameters
|
365
|
+
----------
|
366
|
+
paras : list of str, optional
|
367
|
+
The parameters for which to do the actions.
|
368
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
369
|
+
The default is ["p_d", "p", "t", "et"].
|
370
|
+
**kwargs : dict
|
371
|
+
The keyword arguments to pass to update_period_meta method of the stations
|
372
|
+
"""
|
373
|
+
self._check_paras(paras=paras,
|
374
|
+
valid_paras=["p_d", "p", "t", "et"])
|
375
|
+
log.info("Broker update_period_meta starts")
|
376
|
+
self._check_db_schema()
|
377
|
+
|
378
|
+
with self.activate():
|
379
|
+
for stations in self.stations:
|
380
|
+
if stations._para in paras:
|
381
|
+
stations.update_period_meta(**kwargs)
|
382
|
+
|
383
|
+
def quality_check(self, paras=["p", "t", "et"], with_fillup_nd=True, **kwargs):
|
384
|
+
"""Do the quality check on the stations raw data.
|
385
|
+
|
386
|
+
Parameters
|
387
|
+
----------
|
388
|
+
paras : list of str, optional
|
389
|
+
The parameters for which to do the actions.
|
390
|
+
Can be one, some or all of ["p", "t", "et"].
|
391
|
+
The default is ["p", "t", "et"].
|
392
|
+
with_fillup_nd : bool, optional
|
393
|
+
Should the daily precipitation data get filled up if the 10 minute precipitation data gets quality checked.
|
394
|
+
The default is True.
|
395
|
+
**kwargs : dict
|
396
|
+
The keyword arguments to pass to quality_check method of the stations
|
397
|
+
"""
|
398
|
+
self._check_paras(
|
399
|
+
paras=paras,
|
400
|
+
valid_paras=["p", "t", "et"])
|
401
|
+
log.info("Broker quality_check starts")
|
402
|
+
self._check_db_schema()
|
403
|
+
|
404
|
+
with self.activate():
|
405
|
+
if with_fillup_nd and "p" in paras:
|
406
|
+
self.stations_pd.fillup(**kwargs)
|
407
|
+
|
408
|
+
for stations in self.stations:
|
409
|
+
if stations._para in paras:
|
410
|
+
stations.quality_check(**kwargs)
|
411
|
+
|
412
|
+
def last_imp_quality_check(self, paras=["p", "t", "et"], with_fillup_nd=True, **kwargs):
|
413
|
+
"""Quality check the last imported data.
|
414
|
+
|
415
|
+
Also fills up the daily precipitation data if the 10 minute precipitation data should get quality checked.
|
416
|
+
|
417
|
+
Parameters
|
418
|
+
----------
|
419
|
+
paras : list of str, optional
|
420
|
+
The parameters for which to do the actions.
|
421
|
+
Can be one, some or all of ["p", "t", "et"].
|
422
|
+
The default is ["p", "t", "et"].
|
423
|
+
with_fillup_nd : bool, optional
|
424
|
+
Should the daily precipitation data get filled up if the 10 minute precipitation data gets quality checked.
|
425
|
+
The default is True.
|
426
|
+
**kwargs : dict
|
427
|
+
The keyword arguments to pass to last_imp_quality_check method of the stations.
|
428
|
+
If with_fillup_nd is True, the keyword arguments are also passed to the last_imp_fillup method of the stations_pd.
|
429
|
+
"""
|
430
|
+
self._check_paras(
|
431
|
+
paras=paras,
|
432
|
+
valid_paras=["p", "t", "et"])
|
433
|
+
log.info("Broker last_imp_quality_check starts")
|
434
|
+
self._check_db_schema()
|
435
|
+
|
436
|
+
with self.activate():
|
437
|
+
if with_fillup_nd and "p" in paras:
|
438
|
+
self.stations_pd.last_imp_fillup(**kwargs)
|
439
|
+
|
440
|
+
for stations in self.stations:
|
441
|
+
if stations._para in paras:
|
442
|
+
stations.last_imp_quality_check(**kwargs)
|
443
|
+
|
444
|
+
def fillup(self, paras=["p", "t", "et"], **kwargs):
|
445
|
+
"""Fillup the timeseries.
|
446
|
+
|
447
|
+
Parameters
|
448
|
+
----------
|
449
|
+
paras : list of str, optional
|
450
|
+
The parameters for which to do the actions.
|
451
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
452
|
+
The default is ["p_d", "p", "t", "et"].
|
453
|
+
**kwargs : dict
|
454
|
+
The keyword arguments to pass to fillup method of the stations
|
455
|
+
"""
|
456
|
+
log.info("Broker fillup starts")
|
457
|
+
self._check_db_schema()
|
458
|
+
|
459
|
+
with self.activate():
|
460
|
+
self._check_paras(paras)
|
461
|
+
for stations in self.stations:
|
462
|
+
if stations._para in paras:
|
463
|
+
stations.fillup(**kwargs)
|
464
|
+
|
465
|
+
def last_imp_fillup(self, paras=["p", "t", "et"], **kwargs):
|
466
|
+
"""Fillup the last imported data.
|
467
|
+
|
468
|
+
Parameters
|
469
|
+
----------
|
470
|
+
paras : list of str, optional
|
471
|
+
The parameters for which to do the actions.
|
472
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
473
|
+
The default is ["p_d", "p", "t", "et"].
|
474
|
+
**kwargs : dict
|
475
|
+
The keyword arguments to pass to last_imp_fillup method of the stations
|
476
|
+
"""
|
477
|
+
log.info("Broker last_imp_fillup starts")
|
478
|
+
self._check_db_schema()
|
479
|
+
|
480
|
+
with self.activate():
|
481
|
+
self._check_paras(paras)
|
482
|
+
for stations in self.stations:
|
483
|
+
if stations._para in paras:
|
484
|
+
stations.last_imp_fillup(**kwargs)
|
485
|
+
|
486
|
+
def richter_correct(self, **kwargs):
|
487
|
+
"""Richter correct all of the precipitation data.
|
488
|
+
|
489
|
+
Parameters
|
490
|
+
----------
|
491
|
+
**kwargs : dict
|
492
|
+
The keyword arguments to pass to richter_correct method of the stations_p
|
493
|
+
"""
|
494
|
+
log.info("Broker: last_imp_corr starts")
|
495
|
+
self._check_db_schema()
|
496
|
+
|
497
|
+
with self.activate():
|
498
|
+
self.stations_p.richter_correct(**kwargs)
|
499
|
+
|
500
|
+
def last_imp_corr(self, **kwargs):
|
501
|
+
"""Richter correct the last imported precipitation data.
|
502
|
+
|
503
|
+
Parameters
|
504
|
+
----------
|
505
|
+
**kwargs : dict
|
506
|
+
The keyword arguments to pass to last_imp_corr method of the stations
|
507
|
+
"""
|
508
|
+
log.info("Broker: last_imp_corr starts")
|
509
|
+
self._check_db_schema()
|
510
|
+
|
511
|
+
with self.activate():
|
512
|
+
self.stations_p.last_imp_corr(**kwargs)
|
513
|
+
|
514
|
+
def update_db(self, paras=["p_d", "p", "t", "et"], **kwargs):
|
515
|
+
"""The regular Update of the database.
|
516
|
+
|
517
|
+
Downloads new data.
|
518
|
+
Quality checks the newly imported data.
|
519
|
+
Fills up the newly imported data.
|
520
|
+
|
521
|
+
Parameters
|
522
|
+
----------
|
523
|
+
paras : list of str, optional
|
524
|
+
The parameters for which to do the actions.
|
525
|
+
Can be one, some or all of ["p_d", "p", "t", "et"].
|
526
|
+
The default is ["p_d", "p", "t", "et"].
|
527
|
+
**kwargs : dict
|
528
|
+
The keyword arguments to pass to the called methods of the stations
|
529
|
+
"""
|
530
|
+
log.info("Broker update_db starts")
|
531
|
+
self._check_paras(paras)
|
532
|
+
self._check_db_schema()
|
533
|
+
|
534
|
+
with self.activate():
|
535
|
+
if self.get_db_version() is None or pv.parse(__version__) > self.get_db_version():
|
536
|
+
log.info("--> There is a new version of the python script. Therefor the database is recalculated completly")
|
537
|
+
self.initiate_db()
|
538
|
+
else:
|
539
|
+
self.update_meta(paras=paras, **kwargs)
|
540
|
+
self.update_raw(paras=paras, **kwargs)
|
541
|
+
if "p_d" in paras:
|
542
|
+
paras.remove("p_d")
|
543
|
+
self.last_imp_quality_check(paras=paras, **kwargs)
|
544
|
+
self.last_imp_fillup(paras=paras, **kwargs)
|
545
|
+
self.last_imp_corr(**kwargs)
|
546
|
+
|
547
|
+
def vacuum(self, do_analyze=True, **kwargs):
|
548
|
+
sql = "VACUUM {analyze};".format(
|
549
|
+
analyze="ANALYZE" if do_analyze else "")
|
550
|
+
with db_engine.connect().execution_options(isolation_level="AUTOCOMMIT") as con:
|
551
|
+
con.execute(sqltxt(sql))
|
552
|
+
|
553
|
+
def get_setting(self, key):
|
554
|
+
"""Get a specific settings value from the database.
|
555
|
+
|
556
|
+
Parameters
|
557
|
+
----------
|
558
|
+
key : str
|
559
|
+
The key of the setting.
|
560
|
+
|
561
|
+
Returns
|
562
|
+
-------
|
563
|
+
value: str or None
|
564
|
+
The database settings value.
|
565
|
+
"""
|
566
|
+
with db_engine.connect() as con:
|
567
|
+
res = con.execute(
|
568
|
+
sqltxt(f"SELECT value FROM settings WHERE key='{key}';")
|
569
|
+
).fetchone()
|
570
|
+
if res is None:
|
571
|
+
return None
|
572
|
+
else:
|
573
|
+
return res[0]
|
574
|
+
|
575
|
+
def set_setting(self, key:str, value:str):
|
576
|
+
"""Set a specific setting.
|
577
|
+
|
578
|
+
Parameters
|
579
|
+
----------
|
580
|
+
key : str
|
581
|
+
The key of the setting.
|
582
|
+
value : str
|
583
|
+
The value of the setting.
|
584
|
+
"""
|
585
|
+
with db_engine.connect() as con:
|
586
|
+
con.execute(sqltxt(
|
587
|
+
f"""INSERT INTO settings
|
588
|
+
VALUES ('{key}', '{value}')
|
589
|
+
ON CONFLICT (key)
|
590
|
+
DO UPDATE SET value=EXCLUDED.value;"""))
|
591
|
+
con.commit()
|
592
|
+
|
593
|
+
def get_db_version(self):
|
594
|
+
"""Get the package version that the databases state is at.
|
595
|
+
|
596
|
+
Returns
|
597
|
+
-------
|
598
|
+
version
|
599
|
+
The version of the database.
|
600
|
+
"""
|
601
|
+
res = self.get_setting("version")
|
602
|
+
if res is not None:
|
603
|
+
res = pv.parse(res)
|
604
|
+
return res
|
605
|
+
|
606
|
+
def set_db_version(self, version=pv.parse(__version__)):
|
607
|
+
"""Set the package version that the databases state is at.
|
608
|
+
|
609
|
+
Parameters
|
610
|
+
----------
|
611
|
+
version: pv.Version, optional
|
612
|
+
The Version of the python package
|
613
|
+
The default is the version of this package.
|
614
|
+
"""
|
615
|
+
if not isinstance(version, pv.Version):
|
616
|
+
raise TypeError("version must be of type pv.Version")
|
617
|
+
self.set_setting("version", str(version))
|
618
|
+
|
619
|
+
@property
|
620
|
+
def is_any_active(self):
|
621
|
+
"""Get the state of the broker.
|
622
|
+
|
623
|
+
Returns
|
624
|
+
-------
|
625
|
+
bool
|
626
|
+
Whether the broker is active.
|
627
|
+
"""
|
628
|
+
return self.get_setting("broker_active") == "True"
|
629
|
+
|
630
|
+
@property
|
631
|
+
def is_active(self):
|
632
|
+
"""Get the state of the broker.
|
633
|
+
|
634
|
+
Returns
|
635
|
+
-------
|
636
|
+
bool
|
637
|
+
Whether the broker is active.
|
638
|
+
"""
|
639
|
+
return self._is_active
|
640
|
+
|
641
|
+
@is_active.setter
|
642
|
+
def is_active(self, is_active:bool):
|
643
|
+
"""Set the state of the broker.
|
644
|
+
|
645
|
+
Parameters
|
646
|
+
----------
|
647
|
+
is_active : bool
|
648
|
+
Whether the broker is active.
|
649
|
+
"""
|
650
|
+
self._is_active = is_active
|
651
|
+
self.set_setting("broker_active", str(is_active))
|
652
|
+
|
653
|
+
def _deactivate(self):
|
654
|
+
self.is_active = False
|
655
|
+
|
656
|
+
@contextmanager
|
657
|
+
def activate(self):
|
658
|
+
"""Activate the broker in a context manager."""
|
659
|
+
try:
|
660
|
+
if self.is_any_active and not self.is_active:
|
661
|
+
raise RuntimeError("Another Broker is active and therefor this broker is not allowed to run.")
|
662
|
+
self.is_active = True
|
663
|
+
atexit.register(self._deactivate)
|
664
|
+
yield self
|
665
|
+
finally:
|
666
|
+
self._deactivate()
|
667
|
+
atexit.unregister(self._deactivate)
|