You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by gs...@apache.org on 2025/09/25 21:45:04 UTC
(steve) 02/02: Continued work on the Election class.
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 <gs...@gmail.com>
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()