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