Skip to content

Commit

Permalink
Feature/post pr 17 comments (#20)
Browse files Browse the repository at this point in the history
* DELETE/REBASE this out before PR - smushing dev envs

* initial thoughts/debugging on integrating with backend

* the requirements.txt file - ???

* Revert "DELETE/REBASE this out before PR - smushing dev envs"

This reverts commit 821db9f.

* after backing out env hack, redoing poetry install

* separating running poetry export from pylint

* first 3 endpoints have some initial functionality

* initial stub at some json data models

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update Makefile

Co-authored-by: Ion Y <[email protected]>

* Update Makefile

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* add missing PHONY target

* changing ContestDict to AnyContest

* checkpointing: post PR and 2023/04/11 meeting

* checkpoint during refactoring

* initial pass on some thoughts regarding all 5 endpoints

* checkpointing endpoint #4

* initial pass on endpoint #5

* wordsmithing

* copying blank ballot, cast ballot, ballot check to here

* refactoring backend.py - cleaning it up somewhat

* another checkpoint at the tailend of the initial refactoring

* adding mock verify-ballot, tally-election, and show-contest json docs

* show-contest is now returning a dictionary

* since the endpoint can support multiple digests, is seems easier to return (from the backend ops) a list than a dict

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/json_data_models.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* PR - removing /resuse/ endpoint

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* PR - cleaning up testing comments

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* tweaking a comment re: verbosity when tallying

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/main.py

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/mock-data/blank-ballot.json

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/mock-data/blank-ballot.json

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/mock-data/cast-ballot.json

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/mock-data/cast-ballot.json

Co-authored-by: Ion Y <[email protected]>

* Update src/vtp/web/api/mock-data/cast-ballot.json

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* Update tests/test_main.py

Co-authored-by: Ion Y <[email protected]>

* PR - tweaking class documentation

* more PR work - clean up error testing idiom

---------

Co-authored-by: Ion Y <[email protected]>
  • Loading branch information
windoverwater and ion-oset authored May 1, 2023
1 parent b27828f commit de9d579
Show file tree
Hide file tree
Showing 16 changed files with 1,613 additions and 403 deletions.
60 changes: 60 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Ancient Makefile implicit rule disabler
(%): %
%:: %,v
%:: RCS/%,v
%:: s.%
%:: SCCS/s.%
%.out: %
%.c: %.w %.ch
%.tex: %.w %.ch
%.mk:

# Variables
DOC_DIR := docs
SRC_DIR := src/vtp/web/api
TEST_DIR := tests

# Use colors for errors and warnings when in an interactive terminal
INTERACTIVE := $(shell test -t 0 && echo 1)
ifdef INTERACTIVE
RED := \033[0;31m
END := \033[0m
else
RED :=
END :=
endif

# Let there be no default target
.PHONY: default
default:
@echo "${RED}There is no default make target.${END} Specify one of:"
@echo "pylint - runs pylint"
@echo "pytest - runs pytest"
@echo "reqs - generates a new requirements.txt file"
@echo "etags - constructs an emacs tags table"
@echo ""
@echo "See ${BUILD_DIR}/README.md for more details and info"

# Run pylint
.PHONY: pylint
pylint:
@echo "${RED}NOTE - isort and black disagree on 3 files${END} - let black win"
isort ${SRC_DIR} ${TEST_DIR}
black ${SRC_DIR} ${TEST_DIR}
pylint --recursive y ${SRC_DIR} ${TEST_DIR}

# Run tests
.PHONY: pytest
pytest:
pytest ${TEST_DIR}

# emacs tags
ETAG_SRCS := $(shell find * -type f -name '*.py' -o -name '*.md' | grep -v defunct)
.PHONY: etags
etags: ${ETAG_SRCS}
etags ${ETAG_SRCS}

# Generate a requirements.txt for dependabot (ignoring the symlinks)
.PHONY: reqs
reqs requirements.txt: pyproject.toml poetry.lock
poetry export --with dev -f requirements.txt --output requirements.txt
477 changes: 244 additions & 233 deletions poetry.lock

Large diffs are not rendered by default.

363 changes: 363 additions & 0 deletions requirements.txt

Large diffs are not rendered by default.

223 changes: 223 additions & 0 deletions src/vtp/web/api/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"""
Backend support for the web-api. One important aspect of this file is
to support web-api testing in the absense of a live ElectionData
deployment. An ElectionData deployment is when a
setup-vtp-demo-operation operation has been run, nominally creating a
/opt/VoteTrackerPlus/demo.01 ElectionData folder and the required
subfolders.
With an ElectionData deployment the VTP git commands can be executed
and VoteTracker+ can function as designed.
Without an ElectionData deployment VTP git commands cannot be
executed. Currently this state is configured by the _MOCK_MODE
variable below. When set, and when this repo is part of the
VTP-dev-env parent repo (or when the VoteTrackerPlus and
VTP-mock-election.US.xx repos are simply sibling repos of this one),
the commands here do not call into the VoteTrackerPlus repo and
instead stub out the effective IO operations with static, non-varying
mock data. That mock data is currently nominally stored in (checked
into) the VTP-mock-election.US.xx repo as the mock data is a direct
function of the live ElectionData election configuration and CVR data
found in that repo. And that is due to two things: 1) the
VTP-mock-election.US.xx holds the configuration of an election such as
the blank ballot definition and 2) it also holds several hundred
pre-cast random ballots so to fill the ballot cache so that
ballot-checks can be immediately produced upon casting a ballot.
Regardless, for the time being the VTP-mock-election.US.xx also holds
checkedin mock values for the data that the web-api and above layers
need when running in mock mode.
"""

import csv
import json

from vtp.core.common import Common
from vtp.ops.accept_ballot_operation import AcceptBallotOperation
from vtp.ops.cast_ballot_operation import CastBallotOperation
from vtp.ops.setup_vtp_demo_operation import SetupVtpDemoOperation
from vtp.ops.show_contests_operation import ShowContestsOperation
from vtp.ops.tally_contests_operation import TallyContestsOperation
from vtp.ops.verify_ballot_receipt_operation import VerifyBallotReceiptOperation


class VtpBackend:
"""
Class to keep the namespace separate and allow the creation of a
shim layer in the VTP-web-api repo so that this repo can easily
talk with the VoteTrackerPlus backend repo.
"""

########
# backend demo constants
########
# set mock mode
_MOCK_MODE = False
# where the blank ballot is stored for the spring demo
_MOCK_BLANK_BALLOT = "mock-data/blank-ballot.json"
# where the cast-ballot.json file is stored for the spring demo
_MOCK_CAST_BALLOT = "mock-data/cast-ballot.json"
# where the ballot-check is stored for the spring demo
_MOCK_BALLOT_CHECK = "mock-data/receipt.26.csv"
_MOCK_VOTER_INDEX = 26
# a mock contest content
_MOCK_CONTEST_CONTENT = "mock-data/mock_contest.json"
# default guid - making one up
_MOCK_GUID = "01d963fd74100ee3f36428740a8efd8afd781839"
# default mock receipt log
_MOCK_VERIFY_BALLOT_LOG = "mock-data/verify-ballot-doc.json"
# default mock tally log
_MOCK_TALLY_CONTESTS_LOG = "mock-data/tally-election-doc.json"
# default mock show contest log
_MOCK_SHOW_CONTEST_LOG = "mock-data/show-contest-doc.json"
# backend default address
_ADDRESS = "123, Main Street, Concord, Massachusetts"

@staticmethod
def get_vote_store_id() -> str:
"""
Endpoint #1: will return a vote_store_id, a.k.a. a guid
"""
if VtpBackend._MOCK_MODE:
# in mock mode there is no guid - make one up
return VtpBackend._MOCK_GUID
operation = SetupVtpDemoOperation(
election_data_dir=Common.get_generic_ro_edf_dir(),
)
return operation.run(guid_client_store=True)

@staticmethod
def get_empty_ballot(vote_store_id: str) -> dict:
"""
Endpoint #2: given an existing guid, will return the blank
ballot
"""
if VtpBackend._MOCK_MODE:
# in mock mode there is no guid - make one up
with open(VtpBackend._MOCK_BLANK_BALLOT, "r", encoding="utf8") as infile:
json_doc = json.load(infile)
# import pdb; pdb.set_trace()
return json_doc
# Cet a (the) blank ballot from the backend
operation = CastBallotOperation(
election_data_dir=Common.get_guid_based_edf_dir(vote_store_id),
)
return operation.run(
an_address = VtpBackend._ADDRESS,
return_bb=True,
)

@staticmethod
def get_all_guid_workspaces() -> list:
"""
Will return a list of all the existing guid workspaces
"""
return SetupVtpDemoOperation.get_all_guid_workspaces()

@staticmethod
def mock_get_cast_ballot() -> dict:
"""Mock only - return a static cast ballot"""
with open(VtpBackend._MOCK_CAST_BALLOT, "r", encoding="utf8") as infile:
json_doc = json.load(infile)
return json_doc

@staticmethod
def mock_get_ballot_check() -> tuple[list, int]:
"""Mock only - return a static cast ballot"""
with open(VtpBackend._MOCK_BALLOT_CHECK, "r", encoding="utf8") as infile:
csv_doc = list(csv.reader(infile))
return csv_doc, VtpBackend._MOCK_VOTER_INDEX

@staticmethod
def cast_ballot(vote_store_id: str, cast_ballot: dict) -> dict:
"""
Endpoint #3: will cast (upload) a cast ballot and return the
ballot-check and voter-index
"""
if VtpBackend._MOCK_MODE:
# Just return a mock ballot-check and voter-index
return VtpBackend.mock_get_ballot_check()
# handle the incoming ballot and return the ballot-check and voter-index
operation = AcceptBallotOperation(
election_data_dir=Common.get_guid_based_edf_dir(vote_store_id),
)
return operation.run(
cast_ballot_json=cast_ballot,
merge_contests=True,
)

@staticmethod
def verify_ballot_check(
vote_store_id: str,
ballot_check: list,
vote_index: int,
cvr: bool = False,
) -> str:
"""
Endpoint #4: will verify a ballot-check and vote-inded, returning an
undefined string at this time.
"""
if VtpBackend._MOCK_MODE:
# Just return a mock verify ballot string
with open(VtpBackend._MOCK_VERIFY_BALLOT_LOG, "r", encoding="utf8") as infile:
json_doc = json.load(infile)
return json_doc
# handle the incoming ballot and return the ballot-check and voter-index
operation = VerifyBallotReceiptOperation(
election_data_dir=Common.get_guid_based_edf_dir(vote_store_id),
stdout_printing=False,
)
return operation.run(
receipt_data=ballot_check,
row=str(vote_index),
cvr=cvr,
)

@staticmethod
def tally_election_check(
vote_store_id: str,
contests: str,
digests: str,
) -> str:
"""
Endpoint #5: will tally an election and print stuff
"""
if VtpBackend._MOCK_MODE:
# Just return a mock tally string
with open(VtpBackend._MOCK_TALLY_CONTESTS_LOG, "r", encoding="utf8") as infile:
json_doc = json.load(infile)
return json_doc
# handle the incoming ballot and return the ballot-check and voter-index
operation = TallyContestsOperation(
election_data_dir=Common.get_guid_based_edf_dir(vote_store_id),
stdout_printing=False,
)
if digests == "None":
digests = ""
if contests == "None":
contests = ""
return operation.run(
contest_uid=contests,
track_contests=digests,
)

@staticmethod
def show_contest(
vote_store_id: str,
contests: str,
) -> dict:
"""
Endpoint #6: display the contents of one or more contests
"""
if VtpBackend._MOCK_MODE:
# Just return a mock contest
with open(VtpBackend._MOCK_SHOW_CONTEST_LOG, "r", encoding="utf8") as infile:
json_doc = json.load(infile)
return json_doc
# handle the show_contest
operation = ShowContestsOperation(
election_data_dir=Common.get_guid_based_edf_dir(vote_store_id),
stdout_printing=False,
)
return {"contents": operation.run(contest_check=contests)}
Loading

0 comments on commit de9d579

Please sign in to comment.