Skip to content

Commit 7806c97

Browse files
Implement database classes
1 parent a9b375e commit 7806c97

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

dbcsep/database.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright (C) 2024:
4+
# Swiss Seismological Service, ETH Zurich, Zurich, Switzerland.
5+
# Helmholtz Centre Potsdam GFZ German Research Centre for Geosciences, Potsdam, Germany.
6+
#
7+
# This program is free software: you can redistribute it and/or modify it
8+
# under the terms of the GNU Affero General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or (at
10+
# your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful, but
13+
# WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15+
# General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Affero General Public License
18+
# along with this program. If not, see http://www.gnu.org/licenses/.
19+
20+
import abc
21+
import logging
22+
import sqlite3
23+
24+
logger = logging.getLogger()
25+
26+
27+
class AbstractDatabase(abc.ABC):
28+
"""
29+
The AbstractDatabase class represents any database. It manages the database connection and
30+
cursor.
31+
32+
Attributes:
33+
connection:
34+
Database connection.
35+
cursor:
36+
Database cursor.
37+
"""
38+
39+
def __init__(self):
40+
self.connection = None
41+
self.cursor = None
42+
43+
@abc.abstractmethod
44+
def connect(self):
45+
"""
46+
Abstract connection to the database.
47+
"""
48+
49+
pass
50+
51+
def commit_and_close(self):
52+
"""
53+
Commits the executed statements and closes the database connection.
54+
"""
55+
56+
self.connection.commit()
57+
self.close()
58+
59+
def close(self):
60+
"""
61+
Closes the database connection.
62+
"""
63+
64+
self.connection.close()
65+
66+
67+
class SpatiaLiteDatabase(AbstractDatabase):
68+
"""
69+
The `SpatiaLiteDatabase` class represents a SpatiaLite database. It manages the database
70+
connection and cursor. Upon connection, the SpatiaLite extension is loaded.
71+
72+
Args:
73+
database_filepath (str):
74+
Filepath for the SpatiaLite database file.
75+
spatialite_filepath (str, default: 'mod_spatialite'):
76+
Filepath of the SpatiaLite extension.
77+
78+
Attributes:
79+
database_filepath (str):
80+
SpatiaLite database filepath.
81+
spatialite_filepath (str):
82+
Filepath to the SpatiaLite extension.
83+
connection (sqlite3.Connection):
84+
Database connection.
85+
cursor (sqlite3.Cursor):
86+
Database cursor.
87+
"""
88+
89+
def __init__(self, database_filepath, spatialite_filepath="mod_spatialite"):
90+
super().__init__()
91+
self.database_filepath = database_filepath
92+
self.spatialite_filepath = spatialite_filepath
93+
94+
def connect(self, init_spatial_metadata=True, **kwargs):
95+
"""
96+
Connects to a database, loads the SpatiaLite extension and initializes it if requested.
97+
If the database file does not exist, it will be automatically created.
98+
99+
Args:
100+
init_spatial_metadata (Bool):
101+
Defines if the spatial metadata should be initialized. This is not necessary if
102+
an existing SpatiaLite database is opened.
103+
"""
104+
105+
# Connect (if exists) or create SQLite database.
106+
logger.debug(f"Connecting to database at {self.database_filepath}.")
107+
try:
108+
self.connection = sqlite3.connect(self.database_filepath, **kwargs)
109+
except Exception:
110+
logger.exception("Error connecting to the database file.")
111+
raise
112+
logger.debug("Connection to database established.")
113+
114+
# Load and initialize the SpatiaLite extension.
115+
self.connection.enable_load_extension(True)
116+
sql_statement = f"SELECT load_extension('{self.spatialite_filepath}');"
117+
try:
118+
self.connection.execute(sql_statement)
119+
except sqlite3.OperationalError:
120+
logger.exception("Error loading spatial extension.")
121+
raise
122+
logger.debug("SpatiaLite extension loaded.")
123+
124+
if init_spatial_metadata:
125+
try:
126+
self.connection.execute("SELECT InitSpatialMetaData(1);")
127+
except Exception:
128+
logger.exception("Error initializing spatial metadata.")
129+
raise
130+
logger.debug("Spatial metadata initialized.")
131+
132+
# Debug output.
133+
try:
134+
versions = self.connection.execute("SELECT sqlite_version(), spatialite_version()")
135+
except Exception:
136+
logger.exception("Error querying database versions.")
137+
raise
138+
for row in versions:
139+
logger.debug(f"SQLite version: {row[0]}.")
140+
logger.debug(f"SpatiaLite version: {row[1]}.")
141+
142+
try:
143+
self.connection.execute("PRAGMA foreign_keys = ON")
144+
except Exception:
145+
logger.exception("Error with enabling foreign keys.")
146+
raise
147+
148+
self.cursor = self.connection.cursor()

0 commit comments

Comments
 (0)