From eb9ca0572a4c7c9a93f0739cac22bafd18aab515 Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Wed, 10 Jul 2024 11:02:25 -0500
Subject: [PATCH 1/6] Added npm
Usage:
npm install install all the dependencies in your project
npm install add the dependency to your project
npm test run this project's tests
npm run run the script named
npm -h quick help on
npm -l display usage info for all commands
npm help search for help on
npm help npm more involved overview
All commands:
access, adduser, audit, bugs, cache, ci, completion,
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
edit, exec, explain, explore, find-dupes, fund, get, help,
help-search, hook, init, install, install-ci-test,
install-test, link, ll, login, logout, ls, org, outdated,
owner, pack, ping, pkg, prefix, profile, prune, publish,
query, rebuild, repo, restart, root, run-script, search,
set, shrinkwrap, star, stars, start, stop, team, test,
token, uninstall, unpublish, unstar, update, version, view,
whoami
Specify configs in the ini-formatted file:
/Users/noahappelbaum/.npmrc
or on the command line via: npm --key=value
More configuration info: npm help config
Configuration fields: npm help 7 config
npm@9.7.2 /opt/homebrew/lib/node_modules/npm to apt-get command explicitly, to fix Unknown command: "not"
To see a list of supported npm commands, run:
npm help error on Docker build
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 4c464fdf..d250560a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,7 +19,7 @@ RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
#
# Read more on Dockerfile best practices at the source:
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices
-RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client nodejs
+RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client nodejs npm
# Inside the container, create an app directory and switch into it
RUN mkdir /app
From a8d83b5d80f57685fb184e285aaecb294b6fc2a5 Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Thu, 11 Jul 2024 22:23:24 -0500
Subject: [PATCH 2/6] signing off
---
parserator_web/templates/parserator_web/base.html | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/parserator_web/templates/parserator_web/base.html b/parserator_web/templates/parserator_web/base.html
index 8de4f844..0d532a4b 100644
--- a/parserator_web/templates/parserator_web/base.html
+++ b/parserator_web/templates/parserator_web/base.html
@@ -81,7 +81,15 @@
From 7ce2ef95c5c719cb6f4130b86ed0dd16e536edd3 Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Thu, 11 Jul 2024 22:25:44 -0500
Subject: [PATCH 3/6] removed inline styling in favor of utility class for
consistency
---
parserator_web/templates/parserator_web/index.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/parserator_web/templates/parserator_web/index.html b/parserator_web/templates/parserator_web/index.html
index a72d9c80..b70c738a 100644
--- a/parserator_web/templates/parserator_web/index.html
+++ b/parserator_web/templates/parserator_web/index.html
@@ -17,8 +17,7 @@ U.S. addres
-
-
+
Parsing results
Address type:
From ce63127aedf1551958e63db5ff7f826676f82569 Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Thu, 11 Jul 2024 22:30:04 -0500
Subject: [PATCH 4/6] functional frontend UI -- fetches and renders parsed
address data
---
parserator_web/static/js/index.js | 99 ++++++++++++++++++++++++++++++-
1 file changed, 97 insertions(+), 2 deletions(-)
diff --git a/parserator_web/static/js/index.js b/parserator_web/static/js/index.js
index 492674cc..07bacd96 100644
--- a/parserator_web/static/js/index.js
+++ b/parserator_web/static/js/index.js
@@ -1,2 +1,97 @@
-/* TODO: Flesh this out to connect the form to the API and render results
- in the #address-results div. */
+const $form = $(".form");
+const $addressInput = $("#address");
+const $addressResults = $("#address-results");
+const $parseType = $("#parse-type");
+const $resultsTableBody = $("tbody");
+const $errorMessage = $("")
+ .attr("id", "error-message")
+ .addClass("text-danger d-inline-block mx-4");
+const DEFAULT_ERROR_MESSAGE = "Could not parse address";
+const API_URL = "/api/parse/"
+
+$form.on("submit", (evt) => {
+ evt.preventDefault();
+ handleSubmit();
+});
+
+/** handleSubmit:
+ * Handles user form input;
+ * calls functions to fetch parsed data and display results/errors
+ */
+async function handleSubmit() {
+ const input = $addressInput.val();
+
+ try {
+ const parsedAddressData = await fetchParsedAddressData(input);
+ renderResults(
+ parsedAddressData.address_components,
+ parsedAddressData.address_type
+ );
+ } catch (e) {
+ renderErrorMessage(e.message);
+ }
+}
+
+/** fetchParsedAddressData:
+ * fetches parsed address data from API
+ * @param {String} address
+ * @returns {Object} parsed address data, from JSON response
+ * @throws Error on bad request
+ */
+async function fetchParsedAddressData(address) {
+ const params = new URLSearchParams({ address });
+ const response = await fetch(`${API_URL}?${params}`);
+ const parsedAddressData = await response.json();
+
+ if (response.status != 200) {
+ throw new Error(parsedAddressData.error || DEFAULT_ERROR_MESSAGE);
+ }
+
+ return parsedAddressData;
+}
+
+/** renderResults:
+ * Displays parsed address components and information in UI
+ * @param {Object} addressComponents tagged address components
+ * @param {String} addressType
+ */
+function renderResults(addressComponents, addressType) {
+ clearResultsDisplay();
+
+ $parseType.text(addressType);
+ fillResultsTableBody(addressComponents);
+}
+
+/** renderErrorMessage:
+ * Displays an error message in UI
+ * @param {String} message error message
+ */
+function renderErrorMessage(message) {
+ $errorMessage.text(message);
+ $form.append($errorMessage);
+}
+
+/** fillResultsTableBody:
+ * builds displayed table of address components in UI
+ * @param {Object} addressComponents like {component: tag, ...}
+ */
+function fillResultsTableBody(addressComponents) {
+ for (const addressPart in addressComponents) {
+ const $row = $("
");
+ const tag = addressComponents[addressPart];
+ $row.append(
+ $("| ").text(addressPart),
+ $(" | ").text(tag)
+ );
+ $resultsTableBody.append($row);
+ }
+}
+
+/** clearResultsDisplay:
+ * resets UI, in order to display new results
+ */
+function clearResultsDisplay() {
+ $errorMessage.remove();
+ $resultsTableBody.empty();
+ $addressResults.removeClass("d-none");
+}
From d1a8791f02ba441478858e22bb0ab7f6c6b28a77 Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Thu, 11 Jul 2024 22:32:53 -0500
Subject: [PATCH 5/6] implemented AddressParse APIView get endpoint and tests
---
parserator_web/views.py | 45 ++++++++++++++++++++++++++++++++++++-----
tests/test_views.py | 38 ++++++++++++++++++++++++++++------
2 files changed, 72 insertions(+), 11 deletions(-)
diff --git a/parserator_web/views.py b/parserator_web/views.py
index 0be3f4a9..01bf7454 100644
--- a/parserator_web/views.py
+++ b/parserator_web/views.py
@@ -1,9 +1,11 @@
import usaddress
+from usaddress import RepeatedLabelError
from django.views.generic import TemplateView
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from rest_framework.exceptions import ParseError
+from rest_framework import status
class Home(TemplateView):
@@ -14,11 +16,44 @@ class AddressParse(APIView):
renderer_classes = [JSONRenderer]
def get(self, request):
- # TODO: Flesh out this method to parse an address string using the
- # parse() method and return the parsed components to the frontend.
- return Response({})
+ """
+ API endpoint to parse a U.S. address
+ expects a string "address" query
+ responds with JSON, like:
+ {
+ "input_string": "123 main street"
+ "address_components": {"AddressNumber": "123", ...},
+ "addres_type": "Street Address"
+ }
+ """
+ form_data = request.query_params
+ if ("address" not in form_data):
+ raise ParseError
+
+ input_string = form_data["address"]
+
+ try:
+ (address_components, address_type) = self.parse(input_string)
+ response_object = {}
+ response_object["input_string"] = input_string
+ response_object["address_components"] = address_components
+ response_object["address_type"] = address_type
+
+ return Response(response_object)
+
+ except RepeatedLabelError:
+ error_message = "Repeated Label Error; check that address is correct"
+ return Response(
+ {"error": error_message}, status=status.HTTP_400_BAD_REQUEST
+ )
+ # TODO: Add except clauses for any other errors raised by usaddress
def parse(self, address):
- # TODO: Implement this method to return the parsed components of a
- # given address using usaddress: https://github.com/datamade/usaddress
+ """
+ Parses a United States address string into its component parts,
+ using usaddress: https://github.com/datamade/usaddress.
+
+ -> address_components: OrderedDict, address_type: str
+ """
+ (address_components, address_type) = usaddress.tag(address)
return address_components, address_type
diff --git a/tests/test_views.py b/tests/test_views.py
index bfd5d0b7..3bc573ad 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,15 +1,41 @@
import pytest
+import json
+
+parse_url = "/api/parse/"
def test_api_parse_succeeds(client):
- # TODO: Finish this test. Send a request to the API and confirm that the
- # data comes back in the appropriate format.
address_string = '123 main st chicago il'
- pytest.fail()
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 200
+ parsed_data = json.loads(response.content)
+ assert parsed_data == {
+ "input_string": "123 main st chicago il",
+ "address_components": {
+ "AddressNumber": "123",
+ "StreetName": "main",
+ "StreetNamePostType": "st",
+ "PlaceName": "chicago",
+ "StateName": "il"
+ },
+ "address_type": "Street Address"
+ }
def test_api_parse_raises_error(client):
- # TODO: Finish this test. The address_string below will raise a
- # RepeatedLabelError, so ParseAddress.parse() will not be able to parse it.
address_string = '123 main st chicago il 123 main st'
- pytest.fail()
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 400
+
+def test_api_parse_raises_error_on_missing_address_string(client):
+ response = client.get(parse_url, {})
+
+ assert response.status_code == 400
+
+def test_api_parse_succeeds_on_empty_string(client):
+ address_string = ""
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 200
From e0c06b74f6d00d666084106610025939fd29832b Mon Sep 17 00:00:00 2001
From: Noah Appelbaum
Date: Fri, 12 Jul 2024 10:46:00 -0500
Subject: [PATCH 6/6] formatting and import fixes to satisfy test alerts
---
tests/test_views.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/test_views.py b/tests/test_views.py
index 3bc573ad..e2476dba 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,4 +1,3 @@
-import pytest
import json
parse_url = "/api/parse/"
@@ -23,17 +22,20 @@ def test_api_parse_succeeds(client):
"address_type": "Street Address"
}
+
def test_api_parse_raises_error(client):
address_string = '123 main st chicago il 123 main st'
response = client.get(parse_url, {"address": address_string})
assert response.status_code == 400
+
def test_api_parse_raises_error_on_missing_address_string(client):
response = client.get(parse_url, {})
assert response.status_code == 400
+
def test_api_parse_succeeds_on_empty_string(client):
address_string = ""
response = client.get(parse_url, {"address": address_string})
|