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
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)
|