Skip to content

Commit 4ce4044

Browse files
committed
Clarified confusion between model identity (UUID) versus model name for database operations. store uses UUID for replacement, load by name fetches the first match. Creating FairModel('New Name') generates a new UUID and thus a new, distinct model.
1 parent 9488c40 commit 4ce4044

File tree

1 file changed

+57
-26
lines changed

1 file changed

+57
-26
lines changed

pyfair/utility/database.py

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,33 @@ class FairDatabase(object):
3535
>>> query_output_string = db2.query('SELECT uuid, json FROM model')
3636
3737
"""
38+
3839
def __init__(self, path):
3940
self._path = pathlib.Path(path)
4041
self._initialize()
4142

4243
def _initialize(self):
4344
"""Initialize database with tables if necessary."""
4445
with sqlite3.connect(self._path) as conn:
45-
conn.execute("""CREATE TABLE IF NOT EXISTS models (
46+
conn.execute(
47+
"""CREATE TABLE IF NOT EXISTS models (
4648
uuid string,
4749
name string,
4850
creation_date text NOT NULL,
4951
json string NOT NULL,
5052
CONSTRAINT model_pk PRIMARY KEY (uuid));
51-
""")
52-
conn.execute("""CREATE TABLE IF NOT EXISTS results (
53+
"""
54+
)
55+
conn.execute(
56+
"""CREATE TABLE IF NOT EXISTS results (
5357
uuid string,
5458
mean real NOT NULL,
5559
stdev real NOT NULL,
5660
min real NOT NULL,
5761
max real NOT NULL,
5862
CONSTRAINT results_pk PRIMARY KEY (uuid));
59-
""")
63+
"""
64+
)
6065

6166
def _dict_factory(self, cursor, row):
6267
"""Convenience function for sqlite queries"""
@@ -69,17 +74,24 @@ def _dict_factory(self, cursor, row):
6974
def load(self, name_or_uuid):
7075
"""Loads a model from the database
7176
72-
This takes a name or UUID and looks up the model using a UUID
73-
function using self._load_uuid(). If that fails, it attempts to
74-
look of the funciton by name using self._load_name().
77+
This takes a name or UUID. It first attempts to interpret the input
78+
as a UUID and load directly. If that fails (e.g., the input is not
79+
in UUID format), it attempts to load by model name.
80+
81+
If loading by name, the method retrieves the *first* model found
82+
matching that name (based on internal database ordering). If multiple
83+
distinct models (with different UUIDs) share the same name, this
84+
may not be the most recent or a specific version unless names are
85+
managed uniquely. For precise loading, using the model's UUID is
86+
recommended.
7587
7688
Parameters
7789
----------
7890
name_or_uuid : str
7991
The name model or its UUID string
8092
8193
Returns
82-
-------
94+
------
8395
FairModel or FairMetaModel
8496
The model or metamodel corresponding with the input UUID string
8597
or input name string.
@@ -89,6 +101,9 @@ def load(self, name_or_uuid):
89101
FairException
90102
When the UUID or name does not exist in the database
91103
104+
See Also
105+
--------
106+
store : Method for storing models. Note its behavior regarding UUIDs.
92107
"""
93108
# If it is a valid UUID
94109
try:
@@ -110,9 +125,9 @@ def _load_name(self, name):
110125
cursor.execute("SELECT uuid FROM models WHERE name = ?", (name,))
111126
result = cursor.fetchone()
112127
if not result:
113-
raise FairException('Name for model not found.')
128+
raise FairException("Name for model not found.")
114129
# Use model UUID query to load via _load_uuid function
115-
model = self._load_uuid(result['uuid'])
130+
model = self._load_uuid(result["uuid"])
116131
return model
117132

118133
def _load_uuid(self, uuid):
@@ -125,17 +140,17 @@ def _load_uuid(self, uuid):
125140
cursor.execute("SELECT * FROM models WHERE uuid = ?", (uuid,))
126141
model_data = cursor.fetchone()
127142
if not model_data:
128-
raise FairException('UUID for model not found.')
143+
raise FairException("UUID for model not found.")
129144
# Load model type based on json
130-
json_data = model_data['json']
145+
json_data = model_data["json"]
131146
model_param_data = json.loads(json_data)
132-
model_type = model_param_data['type']
133-
if model_type == 'FairMetaModel':
147+
model_type = model_param_data["type"]
148+
if model_type == "FairMetaModel":
134149
model = FairMetaModel.read_json(json_data)
135-
elif model_type == 'FairModel':
150+
elif model_type == "FairModel":
136151
model = FairModel.read_json(json_data)
137152
else:
138-
raise FairException('Unrecognized model type.')
153+
raise FairException("Unrecognized model type.")
139154
return model
140155

141156
def store(self, model_or_metamodel):
@@ -147,10 +162,31 @@ def store(self, model_or_metamodel):
147162
statistics about the risk are stored in the 'results' table, and 2)
148163
the model data is stored in the 'models' table.
149164
165+
The model's UUID (obtained via `model_or_metamodel.get_uuid()`) is used
166+
as the primary key in the database.
167+
168+
If a record with the same UUID already exists, `INSERT OR REPLACE`
169+
semantics are used, meaning the existing record for that UUID will be
170+
overwritten with the data from the model being stored. This is how
171+
updates to an existing model (identified by its UUID) should be performed:
172+
load the model, modify the loaded instance, then store that same instance.
173+
174+
Creating a new `FairModel()` instance results in a new, unique UUID.
175+
Storing such a new instance will always create a new record or replace
176+
an existing record *only if that new UUID happened to match an old one*
177+
(which is astronomically unlikely for standard UUIDs).
178+
It does not replace based on model name.
179+
180+
Parameters
181+
----------
182+
model_or_metamodel : FairModel or FairMetaModel
183+
The model instance to store.
184+
150185
Raises
151186
------
152187
FairException
153188
If model or metamodel is not yet calculated
189+
and thus not ready for storage.
154190
155191
"""
156192
m = model_or_metamodel
@@ -160,30 +196,25 @@ def store(self, model_or_metamodel):
160196
# Export from model
161197
meta = json.loads(m.to_json())
162198
json_data = m.to_json()
163-
results = m.export_results()['Risk']
199+
results = m.export_results()["Risk"]
164200
# Write to database
165201
with sqlite3.connect(self._path) as conn:
166202
cursor = conn.cursor()
167203
# Write model data
168204
cursor.execute(
169205
"""INSERT OR REPLACE INTO models VALUES(?, ?, ?, ?)""",
170-
(
171-
meta['model_uuid'],
172-
meta['name'],
173-
meta['creation_date'],
174-
json_data
175-
)
206+
(meta["model_uuid"], meta["name"], meta["creation_date"], json_data),
176207
)
177208
# Write cached results
178209
cursor.execute(
179210
"""INSERT OR REPLACE INTO results VALUES(?, ?, ?, ?, ?)""",
180211
(
181-
meta['model_uuid'],
212+
meta["model_uuid"],
182213
results.mean(axis=0),
183214
results.std(axis=0),
184215
results.min(axis=0),
185-
results.max(axis=0)
186-
)
216+
results.max(axis=0),
217+
),
187218
)
188219
# Vacuum database
189220
conn = sqlite3.connect(self._path)

0 commit comments

Comments
 (0)