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.
Files changed (77) hide show
  1. docker/Dockerfile +30 -0
  2. docker/docker-compose.yaml +58 -0
  3. docker/docker-compose_test.yaml +24 -0
  4. docker/start-docker-test.sh +6 -0
  5. docs/requirements.txt +10 -0
  6. docs/source/Changelog.md +2 -0
  7. docs/source/License.rst +7 -0
  8. docs/source/Methode.md +161 -0
  9. docs/source/_static/custom.css +8 -0
  10. docs/source/_static/favicon.ico +0 -0
  11. docs/source/_static/logo.png +0 -0
  12. docs/source/api/api.rst +15 -0
  13. docs/source/api/cli.rst +8 -0
  14. docs/source/api/weatherDB.broker.rst +10 -0
  15. docs/source/api/weatherDB.config.rst +7 -0
  16. docs/source/api/weatherDB.db.rst +23 -0
  17. docs/source/api/weatherDB.rst +22 -0
  18. docs/source/api/weatherDB.station.rst +56 -0
  19. docs/source/api/weatherDB.stations.rst +46 -0
  20. docs/source/api/weatherDB.utils.rst +22 -0
  21. docs/source/conf.py +137 -0
  22. docs/source/index.rst +33 -0
  23. docs/source/setup/Configuration.md +127 -0
  24. docs/source/setup/Hosting.md +9 -0
  25. docs/source/setup/Install.md +49 -0
  26. docs/source/setup/Quickstart.md +183 -0
  27. docs/source/setup/setup.rst +12 -0
  28. weatherdb/__init__.py +24 -0
  29. weatherdb/_version.py +1 -0
  30. weatherdb/alembic/README.md +8 -0
  31. weatherdb/alembic/alembic.ini +80 -0
  32. weatherdb/alembic/config.py +9 -0
  33. weatherdb/alembic/env.py +100 -0
  34. weatherdb/alembic/script.py.mako +26 -0
  35. weatherdb/alembic/versions/V1.0.0_initial_database_creation.py +898 -0
  36. weatherdb/alembic/versions/V1.0.2_more_charachters_for_settings+term_station_ma_raster.py +88 -0
  37. weatherdb/alembic/versions/V1.0.5_fix-ma-raster-values.py +152 -0
  38. weatherdb/alembic/versions/V1.0.6_update-views.py +22 -0
  39. weatherdb/broker.py +667 -0
  40. weatherdb/cli.py +214 -0
  41. weatherdb/config/ConfigParser.py +663 -0
  42. weatherdb/config/__init__.py +5 -0
  43. weatherdb/config/config_default.ini +162 -0
  44. weatherdb/db/__init__.py +3 -0
  45. weatherdb/db/connections.py +374 -0
  46. weatherdb/db/fixtures/RichterParameters.json +34 -0
  47. weatherdb/db/models.py +402 -0
  48. weatherdb/db/queries/get_quotient.py +155 -0
  49. weatherdb/db/views.py +165 -0
  50. weatherdb/station/GroupStation.py +710 -0
  51. weatherdb/station/StationBases.py +3108 -0
  52. weatherdb/station/StationET.py +111 -0
  53. weatherdb/station/StationP.py +807 -0
  54. weatherdb/station/StationPD.py +98 -0
  55. weatherdb/station/StationT.py +164 -0
  56. weatherdb/station/__init__.py +13 -0
  57. weatherdb/station/constants.py +21 -0
  58. weatherdb/stations/GroupStations.py +519 -0
  59. weatherdb/stations/StationsBase.py +1021 -0
  60. weatherdb/stations/StationsBaseTET.py +30 -0
  61. weatherdb/stations/StationsET.py +17 -0
  62. weatherdb/stations/StationsP.py +128 -0
  63. weatherdb/stations/StationsPD.py +24 -0
  64. weatherdb/stations/StationsT.py +21 -0
  65. weatherdb/stations/__init__.py +11 -0
  66. weatherdb/utils/TimestampPeriod.py +369 -0
  67. weatherdb/utils/__init__.py +3 -0
  68. weatherdb/utils/dwd.py +350 -0
  69. weatherdb/utils/geometry.py +69 -0
  70. weatherdb/utils/get_data.py +285 -0
  71. weatherdb/utils/logging.py +126 -0
  72. weatherdb-1.1.0.dist-info/LICENSE +674 -0
  73. weatherdb-1.1.0.dist-info/METADATA +765 -0
  74. weatherdb-1.1.0.dist-info/RECORD +77 -0
  75. weatherdb-1.1.0.dist-info/WHEEL +5 -0
  76. weatherdb-1.1.0.dist-info/entry_points.txt +2 -0
  77. 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)