Skip to content

Commit fba9870

Browse files
authored
Merge pull request #34 from DavidCEllis/cleanup
Fix an internal bug in _sqlclasses, some cleanup
2 parents 67616a2 + 5dcb521 commit fba9870

File tree

3 files changed

+613
-21
lines changed

3 files changed

+613
-21
lines changed

src/ducktools/env/__main__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,7 @@ def delete_env_command(manager, args):
434434
def main_command() -> int:
435435
executable_name = os.path.splitext(os.path.basename(sys.executable))[0]
436436

437-
if zipapp_path := globals().get("zipapp_path"):
438-
command = f"{executable_name} {zipapp_path}"
439-
elif __name__ == "__main__":
437+
if __name__ == "__main__":
440438
command = f"{executable_name} -m ducktools.env"
441439
else:
442440
command = os.path.basename(sys.argv[0])

src/ducktools/env/_sqlclasses.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
# This is a minimal object/database wrapper for ducktools.classbuilder
2525
# Execute the class to see examples of the methods that will be generated
26+
# There are a lot of features that would be needed for a *general* version of this
27+
# This only implements the required features for ducktools-env's use case
2628

2729
import itertools
2830

@@ -132,7 +134,7 @@ class SQLMeta(SlotMakerMeta):
132134
TABLE_NAME: str
133135
VALID_FIELDS: dict[str, SQLAttribute]
134136
COMPUTED_FIELDS: set[str]
135-
PRIMARY_KEY: str
137+
PK_NAME: str
136138
STR_LIST_COLUMNS: set[str]
137139
BOOL_COLUMNS: set[str]
138140

@@ -181,20 +183,25 @@ def __init_subclass__(
181183
primary_key = None
182184
for name, field in fields.items():
183185
if field.primary_key:
186+
if primary_key is not None:
187+
raise AttributeError("sqlclass *must* have **only** one primary key")
184188
primary_key = name
185-
break
186189

187190
if primary_key is None:
188191
raise AttributeError("sqlclass *must* have one primary key")
189192

190-
if sum(1 for f in fields.values() if f.primary_key) > 1:
191-
raise AttributeError("sqlclass *must* have **only** one primary key")
192-
193-
cls.PRIMARY_KEY = primary_key
193+
cls.PK_NAME = primary_key
194194
cls.TABLE_NAME = caps_to_snake(cls.__name__)
195195

196196
super().__init_subclass__(**kwargs)
197197

198+
@property
199+
def primary_key(self):
200+
"""
201+
Get the actual value of the primary key on an instance.
202+
"""
203+
return getattr(self, self.PK_NAME)
204+
198205
@classmethod
199206
def create_table(cls, con):
200207
sql_field_list = []
@@ -256,7 +263,7 @@ def _select_query(cls, cursor, filters: dict[str, MAPPED_TYPES] | None = None):
256263
search_condition = ""
257264

258265
cursor.row_factory = cls.row_factory
259-
result = cursor.execute(f"SELECT * FROM {cls.TABLE_NAME} {search_condition}", filters)
266+
result = cursor.execute(f"SELECT * FROM {cls.TABLE_NAME}{search_condition}", filters)
260267
return result
261268

262269
@classmethod
@@ -302,7 +309,7 @@ def select_like(cls, con, filters: dict[str, MAPPED_TYPES] | None = None):
302309
try:
303310
cursor.row_factory = cls.row_factory
304311
result = cursor.execute(
305-
f"SELECT * FROM {cls.TABLE_NAME} {search_condition}",
312+
f"SELECT * FROM {cls.TABLE_NAME}{search_condition}",
306313
filters
307314
)
308315
rows = result.fetchall()
@@ -313,13 +320,13 @@ def select_like(cls, con, filters: dict[str, MAPPED_TYPES] | None = None):
313320

314321
@classmethod
315322
def max_pk(cls, con):
316-
statement = f"SELECT MAX({cls.PRIMARY_KEY}) from {cls.TABLE_NAME}"
323+
statement = f"SELECT MAX({cls.PK_NAME}) FROM {cls.TABLE_NAME}"
317324
result = con.execute(statement)
318325
return result.fetchone()[0]
319326

320327
@classmethod
321328
def row_from_pk(cls, con, pk_value):
322-
return cls.select_row(con, filters={cls.PRIMARY_KEY: pk_value})
329+
return cls.select_row(con, filters={cls.PK_NAME: pk_value})
323330

324331
def insert_row(self, con):
325332
columns = ", ".join(
@@ -338,16 +345,22 @@ def insert_row(self, con):
338345
with con:
339346
result = con.execute(sql_statement, processed_values)
340347

341-
if getattr(self, self.PRIMARY_KEY) is None:
342-
setattr(self, self.PRIMARY_KEY, result.lastrowid)
348+
if getattr(self, self.PK_NAME) is None:
349+
setattr(self, self.PK_NAME, result.lastrowid)
343350

344351
if self.COMPUTED_FIELDS:
345352
row = self.row_from_pk(con, result.lastrowid)
346353
for field in self.COMPUTED_FIELDS:
347354
setattr(self, field, getattr(row, field))
348355

349356
def update_row(self, con, columns: list[str]):
350-
if self.PRIMARY_KEY is None:
357+
"""
358+
Update the values in the database for this 'row'
359+
360+
:param con: SQLContext
361+
:param columns: list of the columns to update from this class.
362+
"""
363+
if self.primary_key is None:
351364
raise AttributeError("Primary key has not yet been set")
352365

353366
if invalid_columns := (set(columns) - self.VALID_FIELDS.keys()):
@@ -360,22 +373,28 @@ def update_row(self, con, columns: list[str]):
360373
}
361374

362375
set_columns = ", ".join(f"{name} = :{name}" for name in columns)
363-
search_condition = f"{self.PRIMARY_KEY} = :{self.PRIMARY_KEY}"
376+
search_condition = f"{self.PK_NAME} = :{self.PK_NAME}"
364377

365378
with con:
366-
con.execute(
379+
result = con.execute(
367380
f"UPDATE {self.TABLE_NAME} SET {set_columns} WHERE {search_condition}",
368381
processed_values,
369382
)
370383

384+
# Computed rows may need to be updated
385+
if self.COMPUTED_FIELDS:
386+
row = self.row_from_pk(con, self.primary_key)
387+
for field in self.COMPUTED_FIELDS:
388+
setattr(self, field, getattr(row, field))
389+
371390
def delete_row(self, con):
372-
if self.PRIMARY_KEY is None:
391+
if self.primary_key is None:
373392
raise AttributeError("Primary key has not yet been set")
374393

375-
pk_filter = {self.PRIMARY_KEY: getattr(self, self.PRIMARY_KEY)}
394+
pk_filter = {self.PK_NAME: self.primary_key}
376395

377396
with con:
378397
con.execute(
379-
f"DELETE FROM {self.TABLE_NAME} WHERE {self.PRIMARY_KEY} = :{self.PRIMARY_KEY}",
398+
f"DELETE FROM {self.TABLE_NAME} WHERE {self.PK_NAME} = :{self.PK_NAME}",
380399
pk_filter,
381400
)

0 commit comments

Comments
 (0)