Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
- restore_cache:
keys:
- v9-dependencies-{{ checksum "uv.lock" }}
- setup_remote_docker
- run: printenv DJANGO_SETTINGS_MODULE SAM_CONFIG_FILE SAM_LAMBDA_CONFIG_ENV SAM_PUBLIC_CONFIG_ENV
- run: printenv SECRET_KEY | md5sum
- run: printenv AWS_ACCESS_KEY_ID | md5sum
Expand Down
34 changes: 33 additions & 1 deletion api/endpoints/v1/voting_information/address.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from dc_logging_client import DCWidePostcodeLoggingClient
from elections_api_client import WdivWcivfApiClient
from sentry_sdk import logger as sentry_logger
from starlette.requests import Request
from starlette.responses import JSONResponse
from stitcher import Stitcher
Expand All @@ -8,10 +10,40 @@

def get_address(request: Request):
uprn: str = request.path_params["uprn"]
logger: DCWidePostcodeLoggingClient = request.app.state.POSTCODE_LOGGER
client = WdivWcivfApiClient(query_params=request.query_params)
try:
wdiv, wcivf = client.get_data_for_address(uprn)
except UpstreamApiError as error:
return JSONResponse(error.message, status_code=error.status)
stitcher = Stitcher(wdiv, wcivf, request)
return JSONResponse(stitcher.make_result_known_response())
result = stitcher.make_result_known_response()

has_ballot = any(date.get("ballots") for date in result.get("dates", []))

# Try and get the postcode from the postcode location
postcode_location = result.get("postcode_location", {})
properties = postcode_location.get("properties", {})
postcode = properties.get("postcode", None)
Comment on lines +25 to +27
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels very defensive, but I figure we don't want to fall over here because there wasn't a postcode_location or properties object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment from the EC API. We don't want to log None postcodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I wasn't sure which way to go. I think we shouldn't end up logging None postcodes because we should always have a postcode from WDIV. If this broke then I was thinking that logging them was a good way to be able to look back and see how long the problem was. I guess it does just pollute the logs table though. Happy to put a check in and not log if postcode is None

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think it would be better to not log here, but to raise an error in Sentry. We still get to see when things are going wrong, but don't persist this to the logs


if postcode:
logger.log(
logger.entry_class(
postcode=str(postcode),
dc_product=logger.dc_product.aggregator_api,
api_key=request.scope["api_user"].api_key,
calls_devs_dc_api=False,
had_election=has_ballot,
**request.scope["utm_dict"],
)
)
if not postcode:
sentry_logger.error(
f"WDIV didn't return a postcode for uprn:{uprn}",
attributes={
"uprn": uprn,
"api_key": request.scope["api_user"].api_key,
},
)

return JSONResponse(result)
23 changes: 15 additions & 8 deletions api/endpoints/v1/voting_information/postcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
async def get_postcode(request: Request):
postcode = request.path_params["postcode"].upper()
logger: DCWidePostcodeLoggingClient = request.app.state.POSTCODE_LOGGER
entry = logger.entry_class(
postcode=str(postcode),
dc_product=logger.dc_product.aggregator_api,
api_key=request.scope["api_user"].api_key,
calls_devs_dc_api=False,
**request.scope["utm_dict"],
)
logger.log(entry)

client = WdivWcivfApiClient(query_params=request.query_params)
try:
Expand All @@ -31,4 +23,19 @@ async def get_postcode(request: Request):
else:
result = stitcher.make_result_known_response()

has_ballot = any(
date.get("ballots") for date in result.get("dates", [])
)

logger.log(
logger.entry_class(
postcode=str(postcode),
dc_product=logger.dc_product.aggregator_api,
api_key=request.scope["api_user"].api_key,
calls_devs_dc_api=False,
had_election=has_ballot,
**request.scope["utm_dict"],
)
)

return JSONResponse(result)
2 changes: 1 addition & 1 deletion common/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [

# note: PEP508 syntax here instead of tool.uv.sources
# so we can pip install the package
"dc-logging-utils @ git+https://github.com/DemocracyClub/dc_logging.git@1.0.2"
"dc-logging-utils @ git+https://github.com/DemocracyClub/dc_logging.git@3.0.0"
]

[tool.uv]
Expand Down
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These wdiv_address.json files are included because we were using the same fixures to mock responses from the WDIV postcode endpoint and the WDIV address endpoint. The only difference is that the address endpoint always includes a single address object in the addresses array. This was how I was going to get the postcode, before it was added to the postcode_location geojson. However I left these files here since they make it a bit more explicit that the endpoints aren't exactly the same.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"polling_station_known": false,
"postcode_location": {
"type": "Feature",
"properties": {
"postcode": "AA1 4AA"
},
"geometry": {
"type": "Point",
"coordinates": [
0.008211636363636364,
51.46623861363635
]
}
},
"council": {
"url": "http://127.0.0.1:8001/api/beta/councils/LEW/",
"council_id": "LEW",
"name": "London Borough of Lewisham",
"nation": "England",
"email": "electoral.services@lewisham.gov.uk",
"phone": "020 8314 6086",
"website": "http://www.lewisham.gov.uk/",
"postcode": "SE6 4RU",
"address": "Electoral Registration Officer\nLewisham Town Hall\nCatford Road\nCatford",
"identifiers": [
"E09000023"
]
},
"polling_station": null,
"advance_voting_station": null,
"addresses": [
{
"url": "http://127.0.0.1:8000/api/beta/address/1-foo-street-bar-town/",
"address": "1 Foo St., Bar Town",
"postcode": "AA1 4AA",
"council": "London Borough of Lewisham",
"polling_station_id": "",
"uprn": "1-foo-street-bar-town"
}
],
"report_problem_url": null,
"metadata": null,
"ballots": [
{
"ballot_paper_id": "mayor.lewisham.2018-05-03",
"ballot_title": "Mayor of Lewisham election",
"poll_open_date": "2018-05-03",
"elected_role": "Mayor of Lewisham",
"metadata": null,
"cancelled": false,
"cancellation_reason": null,
"replaced_by": null,
"replaces": null,
"requires_voter_id": "EA-2022"
},
{
"ballot_paper_id": "local.lewisham.blackheath.2018-05-03",
"ballot_title": "Lewisham local election Blackheath",
"poll_open_date": "2018-05-03",
"elected_role": "Local Councillor",
"metadata": null,
"cancelled": true,
"cancellation_reason": "UNDER_CONTESTED",
"replaced_by": "local.lewisham.blackheath.2018-05-10",
"replaces": null,
"requires_voter_id": "EA-2022"
},
{
"ballot_paper_id": "local.lewisham.blackheath.2018-05-10",
"ballot_title": "Lewisham local election Blackheath",
"poll_open_date": "2018-05-10",
"elected_role": "Local Councillor",
"metadata": null,
"cancelled": false,
"cancellation_reason": null,
"replaced_by": null,
"replaces": "local.lewisham.blackheath.2018-05-03",
"requires_voter_id": "EA-2022"
},
{
"ballot_paper_id": "parl.lewisham-east.by.2018-06-14",
"ballot_title": "Lewisham East by-election",
"poll_open_date": "2018-06-14",
"elected_role": "Member of Parliament",
"metadata": null,
"cancelled": false,
"cancellation_reason": null,
"replaced_by": null,
"replaces": null,
"requires_voter_id": "EA-2022"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"polling_station_known": false,
"postcode_location": {
"type": "Feature",
"properties": {
"postcode": "AA1 1AA"
},
"geometry": {
"type": "Point",
"coordinates": [
-0.13447605,
51.489488200000004
]
}
},
"council": {
"url": "https://wheredoivote.co.uk/api/beta/councils/WSM/",
"council_id": "WSM",
"name": "City of Westminster",
"nation": "England",
"email": "electoralservices@westminster.gov.uk",
"phone": "020 7641 2730",
"website": "http://www.westminster.gov.uk/",
"postcode": "WC2N 5HR",
"address": "Electoral Registration Officer\nWestminster City Council\n2nd Floor, City Hall\n5 Strand",
"identifiers": [
"E09000033"
]
},
"polling_station": null,
"advance_voting_station": null,
"addresses": [
{
"url": "http://127.0.0.1:8000/api/beta/address/1-foo-street-bar-town/",
"address": "1 Foo St., Bar Town",
"postcode": "AA1 1AA",
"council": "City of Westminster",
"polling_station_id": "",
"uprn": "1-foo-street-bar-town"
}
],
"report_problem_url": null,
"metadata": null,
"ballots": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"polling_station_known": true,
"postcode_location": {
"type": "Feature",
"properties": {
"postcode": "AA1 2AA"
},
"geometry": {
"type": "Point",
"coordinates": [
-0.1804843,
51.5113775
]
}
},
"council": {
"url": "https://wheredoivote.co.uk/api/beta/councils/WSM/",
"council_id": "WSM",
"name": "City of Westminster",
"nation": "England",
"email": "electoralservices@westminster.gov.uk",
"phone": "020 7641 2730",
"website": "http://www.westminster.gov.uk/",
"postcode": "WC2N 5HR",
"address": "Electoral Registration Officer\nWestminster City Council\n2nd Floor, City Hall\n5 Strand",
"identifiers": [
"E09000033"
]
},
"polling_station": {
"id": "E09000033.39-york-room-lancaster-hall-hotel",
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-0.1785134375,
51.512779025
]
},
"properties": {
"urls": {
"detail": "http://127.0.0.1:8001/api/beta/pollingstations/?council_id=E09000033&station_id=39-york-room-lancaster-hall-hotel",
"geo": "http://127.0.0.1:8001/api/beta/pollingstations/geo/?council_id=E09000033&station_id=39-york-room-lancaster-hall-hotel"
},
"council": "http://127.0.0.1:8001/api/beta/councils/E09000033/",
"station_id": "39-york-room-lancaster-hall-hotel",
"postcode": "W2 3EL",
"address": "York Room, Lancaster Hall Hotel\n35 Craven Terrace\nLondon"
}
},
"advance_voting_station": {
"name": "Exeter Guildhall",
"address": "Exeter City Council\nCivic Centre\nParis Street\nExeter\nDevon",
"postcode": "EX1 1JN",
"location": {
"type": "Point",
"coordinates": [
-3.524551005678706,
50.72486002944331
]
},
"opening_times": [
[
"2018-11-20",
"10:00:00",
"16:00:00"
],
[
"2018-11-21",
"10:00:00",
"16:00:00"
]
]
},
"addresses": [
{
"url": "http://127.0.0.1:8000/api/beta/address/1-foo-street-bar-town/",
"address": "1 Foo St., Bar Town",
"postcode": "AA1 2AA",
"council": "City of Westminster",
"polling_station_id": "",
"uprn": "1-foo-street-bar-town"
}
],
"report_problem_url": "https://wheredoivote.co.uk/report_problem/?source=testing&source_url=testing",
"metadata": null,
"ballots": [
{
"ballot_paper_id": "local.westminster.lancaster-gate.by.2018-11-22",
"ballot_title": "Westminster local election Lancaster Gate by-election",
"poll_open_date": "2018-11-22",
"elected_role": "Local Councillor",
"metadata": null,
"cancelled": false,
"cancellation_reason": null,
"replaced_by": null,
"replaces": null,
"requires_voter_id": "EA-2022"
}
]
}
Loading