This is an automated email from the ASF dual-hosted git repository. gstein pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/steve.git
commit a1368b36f15db5f1fae838b1c40f0effb9a21d69 Author: Greg Stein <[email protected]> AuthorDate: Thu Sep 25 16:38:00 2025 -0500 Continued work on the Election class. * election.py: - add _LOGGER - in delete() use .perform() on the (custom) cursor rather than the bare execute() method - self-destroy the instance when its Election has been deleted - in add_salts() use the connection rather than a cursor to manage the transaction. The cursor will bung up the interal prepared statement is the whole purpose of the cursor. - remove EID from the .add_issue() method. We already have that. - finish the class create() method, along with retry on the EID - add .delete_by_eid() as a class method - remove back-compat new_id() function * check_coverage.py: - switch/use pathlib - remove use of EID. that is internal to the Election. - switch to Election.create() rather than poking at the SQL table - track EID elimination change to .add_issue() - add Election deletion calls for coverage testing - initialize logging. - put the covreport/ subdir next to this-script (not CWD) --- v3/steve/election.py | 48 ++++++++++++++++++++++++++++++++++------------ v3/test/check_coverage.py | 49 +++++++++++++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/v3/steve/election.py b/v3/steve/election.py index 59715e2..2f76c31 100644 --- a/v3/steve/election.py +++ b/v3/steve/election.py @@ -20,12 +20,16 @@ # # +import logging import json +import sqlite3 from . import crypto from . import db from . import vtypes +_LOGGER = logging.getLogger(__name__) + class Election: @@ -35,6 +39,8 @@ class Election: S_CLOSED = 'closed' def __init__(self, db_fname, eid): + _LOGGER.debug(f'Opening election ID "{eid}"') + ### switch to asfpy.db self.db = db.DB(db_fname) self.eid = eid @@ -127,16 +133,20 @@ class Election: # Order these things because of referential integrity. # Delete all rows that refer to Issues within this Election. - self.c_delete_mayvote.execute((self.eid,)) + self.c_delete_mayvote.perform((self.eid,)) # Now, delete all the Issues that are part of this Election. - self.c_delete_issues.execute((self.eid,)) + self.c_delete_issues.perform((self.eid,)) # Finally, remove the Election itself. - self.c_delete_election.execute((self.eid,)) + self.c_delete_election.perform((self.eid,)) self.db.conn.execute('COMMIT') + # Disable this instance. + self.db.conn.close() + self.db = None + def open(self, pdb): # Double-check the Election is in the editing state. @@ -197,7 +207,7 @@ class Election: # Use M_ALL_ISSUES to iterate over all Person/Issue mappings # in this Election (specified by EID). - self.m_all_issues.execute('BEGIN TRANSACTION') + self.db.conn.execute('BEGIN TRANSACTION') self.m_all_issues.perform((self.eid,)) for mayvote in self.m_all_issues.fetchall(): # MAYVOTE is a 1-tuple: _ROWID_ @@ -206,7 +216,7 @@ class Election: salt = crypto.gen_salt() self.c_salt_mayvote.perform((salt, mayvote[0])) - self.m_all_issues.execute('COMMIT') + self.db.conn.execute('COMMIT') def get_metadata(self): "Return basic metadata about this Election." @@ -226,14 +236,14 @@ class Election: return (issue.title, issue.description, issue.type, self.json2kv(issue.kv)) - def add_issue(self, iid, eid, title, description, vtype, kv): + def add_issue(self, iid, title, description, vtype, kv): "Add or update an issue designated by IID." assert self.is_editable() assert vtype in vtypes.TYPES # If we ADD, then SALT will be NULL. If we UPDATE, then it will not # be touched (it should be NULL). - self.c_add_issue.perform((iid, eid, title, description, vtype, + self.c_add_issue.perform((iid, self.eid, title, description, vtype, self.kv2json(kv))) def delete_issue(self, iid): @@ -413,9 +423,23 @@ class Election: return j and json.loads(j) @classmethod - def create(cls, title, owner_pid, authz=None): - pass - + def create(cls, db_fname, title, owner_pid, authz=None): + # Open in autocommit + conn = sqlite3.connect(db_fname, isolation_level=None) + while True: + eid = crypto.create_id() + try: + conn.execute('INSERT INTO elections (eid, title, owner_pid)' + ' VALUES (?, ?, ?)', + (eid, title, owner_pid,)) + break + except sqlite3.IntegrityError: + _LOGGER.debug('EID conflict(!!) ... trying again.') + conn.close() + + return cls(db_fname, eid) -### compat: -new_eid = crypto.create_id + @classmethod + def delete_by_eid(cls, db_fname, eid): + "Delete the specified Election." + cls(db_fname, eid).delete() diff --git a/v3/test/check_coverage.py b/v3/test/check_coverage.py index 611ccc7..b7ddc9a 100755 --- a/v3/test/check_coverage.py +++ b/v3/test/check_coverage.py @@ -26,16 +26,18 @@ import sys import os.path import sqlite3 +import logging +import pathlib import coverage # pip3 install coverage # Ensure that we can import the "steve" package. -THIS_DIR = os.path.realpath(os.path.dirname(__file__)) -PARENT_DIR = os.path.dirname(THIS_DIR) -sys.path.insert(0, PARENT_DIR) +THIS_DIR = pathlib.Path(__file__).resolve().parent +PARENT_DIR = THIS_DIR.parent +sys.path.insert(0, str(PARENT_DIR)) -TESTING_DB = os.path.join(THIS_DIR, 'covtest.db') -SCHEMA_FILE = os.path.join(PARENT_DIR, 'schema.sql') +TESTING_DB = THIS_DIR / 'covtest.db' +SCHEMA_FILE = PARENT_DIR / 'schema.sql' def touch_every_line(): @@ -46,21 +48,17 @@ def touch_every_line(): import steve.crypto import steve.persondb - eid = steve.election.new_eid() - # Start the election, and open it. try: os.remove(TESTING_DB) except OSError: pass - conn = sqlite3.connect(TESTING_DB) + conn = sqlite3.connect(TESTING_DB, isolation_level=None) conn.executescript(open(SCHEMA_FILE).read()) - conn.execute('INSERT INTO ELECTIONS VALUES' - f' ("{eid}", "title", "alice", NULL, NULL, NULL, NULL)') - conn.commit() + conn.close() # Ready to load up the Election and exercise it. - e = steve.election.Election(TESTING_DB, eid) + e = steve.election.Election.create(TESTING_DB, 'coverage', 'alice') _ = e.get_metadata() # while EDITABLE @@ -77,8 +75,8 @@ def touch_every_line(): i2 = steve.crypto.create_id() i3 = steve.crypto.create_id() - e.add_issue(i1, eid, 'issue A', None, 'yna', None) - e.add_issue(i2, eid, 'issue B', None, 'stv', { + e.add_issue(i1, 'issue A', None, 'yna', None) + e.add_issue(i2, 'issue B', None, 'stv', { 'seats': 3, 'labelmap': { 'a': 'Alice', @@ -89,7 +87,7 @@ def touch_every_line(): }, }) _ = e.list_issues() - e.add_issue(i3, eid, 'issue C', None, 'yna', None) + e.add_issue(i3, 'issue C', None, 'yna', None) e.delete_issue(i3) _ = e.get_issue(i1) @@ -113,6 +111,19 @@ def touch_every_line(): _ = e.tally_issue(i1) _ = e.tally_issue(i2) + # Complete coverage: delete an election. + e2 = steve.election.Election.create(TESTING_DB, 'E2', 'alice') + # Provide some data that should get deleted. + ### note: the referential integrity should to into a test suite. + e2i1 = steve.crypto.create_id() + e2.add_issue(e2i1, 'issue E2.A', None, 'yna', None) + e2.add_voter('alice') + e2.delete() + + # Use the class method this time. + eid = steve.election.Election.create(TESTING_DB, 'E3', 'alice').eid + steve.election.Election.delete_by_eid(TESTING_DB, eid) + def main(): cov = coverage.Coverage( @@ -127,8 +138,14 @@ def main(): cov.stop() cov.report(file=sys.stdout) - cov.html_report(directory='covreport') + cov.html_report(directory=str(THIS_DIR / 'covreport')) if __name__ == '__main__': + DATE_FORMAT = '%m/%d %H:%M' + logging.basicConfig(level=logging.DEBUG, + style='{', + format='[{asctime}|{levelname}|{module}] {message}', + datefmt=DATE_FORMAT, + ) main()
