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 7664ed4a6e42b26686d8c9728d54051ce7941231
Author: Greg Stein <[email protected]>
AuthorDate: Thu Sep 25 15:44:04 2025 -0500

    Factor out the Person database.
    
    Persons are independent from Elections, so split their code and
    handling into separate modules.
    
    * steve/persondb.py: new module to interact with the "persons" table.
      Note: remove self.is_editable() from some methods, as the
      editability of an Election is not applicable.
    * steve/election.py: shift code/queries over to new module. Take a PDB
      argument, when we need information from the Persons database.
    * test/check_coverage.py: track API changes.
---
 v3/steve/election.py      | 61 +++++++------------------------------
 v3/steve/persondb.py      | 77 +++++++++++++++++++++++++++++++++++++++++++++++
 v3/test/check_coverage.py | 20 ++++++------
 3 files changed, 99 insertions(+), 59 deletions(-)

diff --git a/v3/steve/election.py b/v3/steve/election.py
index 02c7638..59715e2 100644
--- a/v3/steve/election.py
+++ b/v3/steve/election.py
@@ -35,6 +35,7 @@ class Election:
     S_CLOSED = 'closed'
 
     def __init__(self, db_fname, eid):
+        ### switch to asfpy.db
         self.db = db.DB(db_fname)
         self.eid = eid
 
@@ -55,16 +56,8 @@ class Election:
                  type=excluded.type,
                  kv=excluded.kv
             ''')
-        self.c_add_person = self.db.add_statement(
-            '''INSERT INTO PERSON VALUES (?, ?, ?)
-               ON CONFLICT DO UPDATE SET
-                 name=excluded.name,
-                 email=excluded.email
-            ''')
         self.c_delete_issue = self.db.add_statement(
             'DELETE FROM ISSUES WHERE iid = ?')
-        self.c_delete_person = self.db.add_statement(
-            'DELETE FROM PERSON WHERE pid = ?')
         self.c_add_vote = self.db.add_statement(
             'INSERT INTO VOTES VALUES (NULL, ?, ?)')
         self.c_add_mayvote = self.db.add_statement(
@@ -89,12 +82,8 @@ class Election:
             'SELECT * FROM ELECTIONS WHERE eid = ?')
         self.q_issues = self.db.add_query('issues',
             'SELECT * FROM ISSUES WHERE eid = ? ORDER BY iid')
-        self.q_person = self.db.add_query('person',
-            'SELECT * FROM PERSON ORDER BY pid')
         self.q_get_issue = self.db.add_query('issues',
             'SELECT * FROM ISSUES WHERE iid = ?')
-        self.q_get_person = self.db.add_query('person',
-            'SELECT * FROM PERSON WHERE pid = ?')
         self.q_get_mayvote = self.db.add_query('mayvote',
             'SELECT * FROM MAYVOTE WHERE pid = ? AND iid = ?')
         self.q_tally = self.db.add_query('mayvote',
@@ -148,7 +137,7 @@ class Election:
 
         self.db.conn.execute('COMMIT')
 
-    def open(self):
+    def open(self, pdb):
 
         # Double-check the Election is in the editing state.
         assert self.is_editable()
@@ -157,7 +146,7 @@ class Election:
         # happens before we move to the "opened" state.
         self.add_salts()
 
-        edata = self.gather_election_data()
+        edata = self.gather_election_data(pdb)
         print('EDATA:', edata)
         salt = crypto.gen_salt()
         opened_key = crypto.gen_opened_key(edata, salt)
@@ -166,7 +155,7 @@ class Election:
         print('KEY:', opened_key)
         self.c_open.perform((salt, opened_key, self.eid))
 
-    def gather_election_data(self):
+    def gather_election_data(self, pdb):
         "Gather a definition of this election for keying and anti-tamper."
 
         # NOTE: separators and other zero-entropy constant chars are
@@ -183,9 +172,11 @@ class Election:
         idata = ''.join(f'{i.iid}{i.title}{i.description}{i.type}{i.kv}'
                         for i in self.q_issues.fetchall())
 
-        self.q_person.perform()
-        pdata = ''.join(p.pid + p.email
-                        for p in self.q_person.fetchall())
+        ### we don't want all people. Just those who are allowed to
+        ### vote in this Election. Examine the "mayvote" table.
+        ### list_persons returns 3-tuples of (PID,NAME,EMAIL). We only
+        ### want pid/email in PDATA.
+        pdata = ''.join(p[0] + p[2] for p in pdb.list_persons())
 
         return (mdata + idata + pdata).encode()
 
@@ -263,36 +254,6 @@ class Election:
         self.q_issues.perform((self.eid,))
         return [ extract_issue(row) for row in self.q_issues.fetchall() ]
 
-    def get_person(self, pid):
-        "Return NAME, EMAIL for Person identified by PID."
-
-        # NEVER return person.salt
-        person = self.q_get_person.first_row((pid,))
-        return person.name, person.email
-
-    def add_person(self, pid, name, email):
-        "Add or update a Person designated by PID."
-        assert self.is_editable()
-
-        # If we ADD, then SALT will be NULL. If we UPDATE, then it will not
-        # be touched (it should be NULL).
-        self.c_add_person.perform((pid, name, email,))
-
-    def delete_person(self, pid):
-        "Delete the Person designated by PID."
-
-        # Can only delete Persons before the Election is OPEN.
-        assert self.is_editable()
-
-        self.c_delete_person.perform((pid,))
-
-    def list_persons(self):
-        "Return ordered (PID, NAME, EMAIL) for each Person."
-
-        # NOTE: the SALT column is omitted. It should never be exposed.
-        self.q_person.perform()
-        return [ row[:3] for row in self.q_person.fetchall() ]
-
     def add_voter(self, pid: str, iid: str | None = None) -> None:
         "Add PID (Person) to Issue IID, or to all Issues (None)."
 
@@ -394,7 +355,7 @@ class Election:
 
         return voted_upon
 
-    def is_tampered(self):
+    def is_tampered(self, pdb):
 
         # The Election should be open.
         assert self.is_open()
@@ -402,7 +363,7 @@ class Election:
         md = self.q_metadata.first_row((self.eid,))
 
         # Compute an opened_key based on the current data.
-        edata = self.gather_election_data()
+        edata = self.gather_election_data(pdb)
         opened_key = crypto.gen_opened_key(edata, md.salt)
 
         print('EDATA:', edata)
diff --git a/v3/steve/persondb.py b/v3/steve/persondb.py
new file mode 100644
index 0000000..2caebf2
--- /dev/null
+++ b/v3/steve/persondb.py
@@ -0,0 +1,77 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----
+#
+# ### TBD: DOCCO
+#
+
+from . import db
+
+import asfpy.db
+
+
+class PersonDB:
+
+    def __init__(self, db_fname):
+        ### switch to asfpy.db
+        self.db = db.DB(db_fname)
+
+        self.c_add_person = self.db.add_statement(
+            '''INSERT INTO PERSON VALUES (?, ?, ?)
+               ON CONFLICT DO UPDATE SET
+                 name=excluded.name,
+                 email=excluded.email
+            ''')
+        self.c_delete_person = self.db.add_statement(
+            'DELETE FROM PERSON WHERE pid = ?')
+
+        self.q_person = self.db.add_query('person',
+            'SELECT * FROM PERSON ORDER BY pid')
+        self.q_get_person = self.db.add_query('person',
+            'SELECT * FROM PERSON WHERE pid = ?')
+
+    def get_person(self, pid):
+        "Return NAME, EMAIL for Person identified by PID."
+
+        # NEVER return person.salt
+        person = self.q_get_person.first_row((pid,))
+        return person.name, person.email
+
+    def add_person(self, pid, name, email):
+        "Add or update a Person designated by PID."
+
+        # If we ADD, then SALT will be NULL. If we UPDATE, then it will not
+        # be touched (it should be NULL).
+        self.c_add_person.perform((pid, name, email,))
+
+    def delete_person(self, pid):
+        "Delete the Person designated by PID."
+
+        # NOTE: if this person has ever been involved in a vote
+        #   (ie. the PID exists in a "mayvote" row), then this will
+        #   throw a referential integrity error.
+        #
+        ### maybe we just don't delete a person, ever?
+
+        self.c_delete_person.perform((pid,))
+
+    def list_persons(self):
+        "Return ordered (PID, NAME, EMAIL) for each Person."
+
+        # NOTE: the SALT column is omitted. It should never be exposed.
+        self.q_person.perform()
+        return [ row[:3] for row in self.q_person.fetchall() ]
diff --git a/v3/test/check_coverage.py b/v3/test/check_coverage.py
index 5ea39f5..611ccc7 100755
--- a/v3/test/check_coverage.py
+++ b/v3/test/check_coverage.py
@@ -44,6 +44,7 @@ def touch_every_line():
     # Do the imports *WITHIN* the coverage test.
     import steve.election
     import steve.crypto
+    import steve.persondb
 
     eid = steve.election.new_eid()
 
@@ -63,13 +64,14 @@ def touch_every_line():
 
     _ = e.get_metadata()  # while EDITABLE
 
-    e.add_person('alice', 'Alice', '[email protected]')
-    e.add_person('bob', None, '[email protected]')
-    e.add_person('carlos', 'Carlos', '[email protected]')
-    e.add_person('david', None, '[email protected]')
-    _ = e.list_persons()
-    e.delete_person('david')
-    _ = e.get_person('alice')
+    pdb = steve.persondb.PersonDB(TESTING_DB)
+    pdb.add_person('alice', 'Alice', '[email protected]')
+    pdb.add_person('bob', None, '[email protected]')
+    pdb.add_person('carlos', 'Carlos', '[email protected]')
+    pdb.add_person('david', None, '[email protected]')
+    _ = pdb.list_persons()
+    pdb.delete_person('david')
+    _ = pdb.get_person('alice')
 
     i1 = steve.crypto.create_id()
     i2 = steve.crypto.create_id()
@@ -96,7 +98,7 @@ def touch_every_line():
     e.add_voter('bob')
     e.add_voter('carlos', i1)
 
-    e.open()
+    e.open(pdb)
     _ = e.get_metadata()  # while OPEN
     e.add_vote('alice', i1, 'y')
     e.add_vote('bob', i1, 'n')
@@ -104,7 +106,7 @@ def touch_every_line():
     e.add_vote('alice', i2, 'bc')
     e.add_vote('bob', i2, 'ad')
     _ = e.has_voted_upon('alice')
-    _ = e.is_tampered()
+    _ = e.is_tampered(pdb)
 
     e.close()
     _ = e.get_metadata()  # while CLOSED

Reply via email to