Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/post pr 17 comments #20

Merged
merged 61 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
821db9f
DELETE/REBASE this out before PR - smushing dev envs
windoverwater Apr 5, 2023
05204ac
initial thoughts/debugging on integrating with backend
windoverwater Apr 5, 2023
dcd3bf2
the requirements.txt file - ???
windoverwater Apr 5, 2023
94f8c48
Revert "DELETE/REBASE this out before PR - smushing dev envs"
windoverwater Apr 6, 2023
dac39ee
after backing out env hack, redoing poetry install
windoverwater Apr 6, 2023
858f4f5
separating running poetry export from pylint
windoverwater Apr 6, 2023
7b56073
first 3 endpoints have some initial functionality
windoverwater Apr 6, 2023
659d46d
initial stub at some json data models
windoverwater Apr 7, 2023
590515b
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 10, 2023
7442e95
Update Makefile
windoverwater Apr 10, 2023
f9038ee
Update Makefile
windoverwater Apr 10, 2023
c1678e9
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 10, 2023
1a532bb
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 10, 2023
4b1eb70
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 10, 2023
acf1662
add missing PHONY target
windoverwater Apr 10, 2023
41ca7e5
changing ContestDict to AnyContest
windoverwater Apr 11, 2023
802c03b
checkpointing: post PR and 2023/04/11 meeting
windoverwater Apr 11, 2023
96b6b4a
checkpoint during refactoring
windoverwater Apr 12, 2023
d36f8cf
initial pass on some thoughts regarding all 5 endpoints
windoverwater Apr 12, 2023
4dde597
checkpointing endpoint #4
windoverwater Apr 12, 2023
2f9affd
initial pass on endpoint #5
windoverwater Apr 12, 2023
d284483
wordsmithing
windoverwater Apr 12, 2023
491d5ff
copying blank ballot, cast ballot, ballot check to here
windoverwater Apr 12, 2023
438ffcb
refactoring backend.py - cleaning it up somewhat
windoverwater Apr 23, 2023
03dc390
another checkpoint at the tailend of the initial refactoring
windoverwater Apr 24, 2023
9ede0c0
adding mock verify-ballot, tally-election, and show-contest json docs
windoverwater Apr 25, 2023
518b7ab
show-contest is now returning a dictionary
windoverwater Apr 25, 2023
8d6014b
since the endpoint can support multiple digests, is seems easier to r…
windoverwater Apr 28, 2023
1433b3e
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 30, 2023
ac581e6
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 30, 2023
3a106ff
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 30, 2023
cbc71e0
Update src/vtp/web/api/json_data_models.py
windoverwater Apr 30, 2023
6bae52b
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
8b43373
PR - removing /resuse/ endpoint
windoverwater Apr 30, 2023
957fc47
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
30556fc
PR - cleaning up testing comments
windoverwater Apr 30, 2023
c060b91
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
bc4afe8
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
c90c380
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
b745d83
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
d355d57
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
ce7ba81
tweaking a comment re: verbosity when tallying
windoverwater Apr 30, 2023
31108ce
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
8b5dce7
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
8203807
Update src/vtp/web/api/main.py
windoverwater Apr 30, 2023
95183bc
Update src/vtp/web/api/mock-data/blank-ballot.json
windoverwater Apr 30, 2023
5d811b1
Update src/vtp/web/api/mock-data/blank-ballot.json
windoverwater Apr 30, 2023
be129bf
Update src/vtp/web/api/mock-data/cast-ballot.json
windoverwater Apr 30, 2023
e2b87fe
Update src/vtp/web/api/mock-data/cast-ballot.json
windoverwater Apr 30, 2023
f9b3413
Update src/vtp/web/api/mock-data/cast-ballot.json
windoverwater Apr 30, 2023
6eb6f94
Update tests/test_main.py
windoverwater Apr 30, 2023
1941fd4
Update tests/test_main.py
windoverwater Apr 30, 2023
c214332
Update tests/test_main.py
windoverwater Apr 30, 2023
d3aedc2
Update tests/test_main.py
windoverwater Apr 30, 2023
65b5f39
Update tests/test_main.py
windoverwater Apr 30, 2023
c5fe9d8
Update tests/test_main.py
windoverwater Apr 30, 2023
780697b
Update tests/test_main.py
windoverwater Apr 30, 2023
022be13
Update tests/test_main.py
windoverwater Apr 30, 2023
46f4ce3
Update tests/test_main.py
windoverwater Apr 30, 2023
7e74074
PR - tweaking class documentation
windoverwater Apr 30, 2023
dc8882d
more PR work - clean up error testing idiom
windoverwater Apr 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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":
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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:
windoverwater marked this conversation as resolved.
Show resolved Hide resolved
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