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

Reply via email to