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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
27 changes: 18 additions & 9 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ module.exports = {
globals: {
__PATH_PREFIX__: true,
},
env: {
browser: true,
es6: true,
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
"indent": ["error", 2],
"no-console": "off",
"strict": ["error", "global"],
"curly": "warn",
"semi": ["error", "never"],
"space-in-parens": ["error", "never"],
"space-before-function-paren": ["error", "always"],
"space-before-blocks": ["error", "always"]
}
'indent': ['error', 2],
'no-console': 'off',
'strict': ['error', 'global'],
'curly': 'warn',
'semi': ['error', 'never'],
'space-in-parens': ['error', 'never'],
'space-before-function-paren': ['error', 'always'],
'space-before-blocks': ['error', 'always'],
},
}
54 changes: 21 additions & 33 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,50 +1,38 @@
# Extend the base Python image
# See https://hub.docker.com/_/python for version options
# N.b., there are many options for Python images. We used the plain
# version number in the pilot. YMMV. See this post for a discussion of
# some options and their pros and cons:
# https://pythonspeed.com/articles/base-image-python-docker-images/
FROM python:3.8

# Add the NodeSource PPA
# (see: https://github.com/nodesource/distributions/blob/master/README.md)
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -

# Install any additional OS-level packages you need via apt-get. RUN statements
# add additional layers to your image, increasing its final size. Keep your
# image small by combining related commands into one RUN statement, e.g.,
#
# RUN apt-get update && \
# apt-get install -y python-pip
#
# 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

# Inside the container, create an app directory and switch into it
# Add the NodeSource PPA and install Node.js and npm
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get update && \
apt-get install -y --no-install-recommends postgresql-client nodejs && \
node -v && npm -v && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*


# Install ESLint globally and check its version
RUN npm install -g eslint && eslint -v

# Create an app directory and set it as the working directory
RUN mkdir /app
WORKDIR /app

# Copy the requirements file into the app directory, and install them. Copy
# only the requirements file, so Docker can cache this build step. Otherwise,
# the requirements must be reinstalled every time you build the image after
# the app code changes. See this post for further discussion of strategies
# for building lean and efficient containers:
# https://blog.realkinetic.com/building-minimal-docker-containers-for-python-applications-37d0272c52f3
# Copy the requirements file and install Python dependencies
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Install Node requirements
COPY ./package.json /app/package.json
# Copy package.json and package-lock.json and install Node.js dependencies
COPY ./package.json ./package-lock.json /app/
RUN npm install

# Copy the contents of the current host directory (i.e., our app code) into
# the container.
# Copy the rest of the application code
COPY . /app

# Add a bogus env var for the Django secret key in order to allow us to run
# the 'collectstatic' management command
# Add a bogus env var for the Django secret key
ENV DJANGO_SECRET_KEY 'foobar'

# Build static files into the container
RUN python manage.py collectstatic --noinput

# Command to run the application
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "parserator_web.wsgi:application"]
136 changes: 54 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,111 +1,83 @@
# DataMade Code Challenge: Parserator
# DataMade Parserator Challenge Solution

Welcome to the DataMade code challenge! 👋
Hi DataMade Team,

Your task is to recreate the **address parsing form** in DataMade's
[Parserator](https://parserator.datamade.us) web service. Parserator can take
input strings that represent addresses (like `123 main st chicago il`)
and split them up into their component parts:
**My name is Ahmad, and I work at the New York City Council**. Welcome to my code challenge solution! 👋

![Example of Parserator parsing the string "123 main st chicago il"](images/usaddress.gif)
At the City Council, I frequently engage in civic projects, often involving the display of data on interactive Leaflet maps. This challenge was a delightful experience, and I thoroughly enjoyed working on it.

In this repo, we've provided the basic scaffolding of the templates, views, and
routes that comprise the app. You'll need to flesh out certain code blocks in
the frontend and backend code in order to send API requests, process them on
the server, and display the results to the user.
## Overview

You can use vanilla JavaScript or jQuery to complete the JavaScript portions of
this assessment.
The challenge was both fun and educational. Below is a summary of what I accomplished during this project:

To get started, fork this repo and follow the instructions below.

## Installation
## Demo: Handling Various Address Entry Scenarios
!["Address Entry Scenarios](images/parserator-solved-demo.gif)

Development requires a local installation of [Docker](https://docs.docker.com/install/)
and [Docker Compose](https://docs.docker.com/compose/install/). These are the
only two system-level dependencies you should need.
## Accomplishments

Once you have Docker and Docker Compose installed, build the application containers:

```
docker-compose build
```

Next, run the app:

```
docker-compose up
```

The app will log to the console, and you should be able to visit it at http://localhost:8000.

## Completing the challenge

Once you have the app up and running on your computer, you'll need to flesh out
certain code blocks to complete the parsing interface.
### Backend Development

**Note:** You can use the following address strings for testing during implementation:
**Implemented the `parse` Method:**
- Used `usaddress` to parse address strings into components.
- Handled various edge cases including repeated address labels and invalid addresses.

- ✅ Valid: `123 main st chicago il`
- ❌ Invalid: `123 main st chicago il 123 main st`
**Enhanced Error Handling:**
- Implemented specific error responses for unparseable addresses and repeated labels.
- Ensured the API returns meaningful error messages to guide the user.

### Step 1: Implement the `parse` method
### Frontend Development

In `parserator_web/views.py`, use [`usaddress`](https://github.com/datamade/usaddress)
to implement the `AddressParse.parse()` method. It should return two pieces of
data:
**Wired Up the Form:**
- Connected the form to the backend API using JavaScript.
- Ensured the form sends address data to the API and receives the parsed components.

- `address_components`: The parsed address
- `address_type`: The type of address provided
**Displayed Results and Errors:**
- Implemented dynamic display of parsed address components.
- Handled and displayed error messages directly on the frontend to enhance user experience.

### Step 2: Complete the API endpoint
### Docker File

In `parserator_web/views.py`, complete the `AddressParse.get()` method to return
three pieces of data:
**Docker Setup:**
- Used Docker and Docker Compose for containerized development and testing.
- Ensured smooth setup and teardown of the application environment.
- Integrated ESLint for JavaScript linting.
- Added npm installation and ESLint setup in the Dockerfile.

- `input_string`: The string that the user sent
- `address_components`: A dictionary of parsed components that comprise the address,
in the format `{address_part: tag}` (returned by `AddressParse.parse()`)
- `address_type`: A string representing type of the parsed address (returned by `AddressParse.parse()`)
### Testing

Don't forget to handle strings that cannot be parsed and return errors!
**Added Unit Tests:**
- Created unit tests to ensure the API returns correct responses for valid and invalid addresses.
- Verified that error messages are returned appropriately for different failure cases.

### Step 3: Wire up the form to send requests to the API
**Passed All Tests:**
- Successfully passed all unit tests and ESLint checks.
- Python linting passed! 👍
- JavaScript linting passed! 👍
- `tests/test_views.py::test_api_parse_succeeds` PASSED
- `tests/test_views.py::test_api_parse_raises_error` PASSED

In `parserator_web/templates/parserator_web/index.html`, fill out the `<script>`
tag in the `extra_js` block, adding JavaScript code that will use the form
to send form data to the API endpoint fleshed out in Step 2.
## Installation Instructions

### Step 4: Display results from the API
Development requires a local installation of [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/). These are the only two system-level dependencies you should need.

In `parserator_web/templates/parserator_web/index.html`, extend the `<script>`
tag in the `extra_js` block to display results from the API endpoint in the
hidden element `<div id="address-results">`.

Make sure that if the API raises an error, it displays this error to the user.

### Step 5: Add unit tests
Once you have Docker and Docker Compose installed, build the application containers:
```
docker-compose build
```
Next, run the app:
```
docker-compose up
```

The `tests/` directory contains two stubbed tests. Complete each test by making
a request to the API endpoint and verifying that it passes or fails, and
returns the expected output.
The app will log to the console, and you should be able to visit it at http://localhost:8000.

You can run the tests using Docker:

```bash
```
docker-compose -f docker-compose.yml -f tests/docker-compose.yml run --rm app
```

Note that, in addition to running the unit tests, the testing script will lint
your JavaScript and Python code. Don't forget to fix any linting errors these
commands surface!

Not familiar with `pytest`? Consult [our testing guidelines](https://github.com/datamade/testing-guidelines)
for quick start instructions, plus tips and tricks for testing Django
applications.

### Step 6: Submit your work
This project involved recreating the address parsing functionality of DataMade's Parserator service. Key tasks included implementing the parsing logic, enhancing error handling, wiring up the frontend form to the backend API, and ensuring comprehensive testing with unit tests.

To submit your work, create a feature branch for your code, commit your changes,
push your commits up to your fork, and open up a pull request against `master`.
Finally, drop a link to your pull request in your application.
So excited and looking forward to your feedback!
Thank you!
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ services:
- parserator-node-modules:/app/node_modules
environment:
DJANGO_SECRET_KEY: reallysupersecret
DJANGO_MANAGEPY_MIGRATE: "on"
DJANGO_ALLOWED_HOSTS: 172.17.0.1,localhost,127.0.0.1
DJANGO_MANAGEPY_MIGRATE: 'on'
DJANGO_ALLOWED_HOSTS: 0.0.0.0,172.17.0.1,localhost,127.0.0.1
entrypoint: /app/docker-entrypoint.sh
command: python manage.py runserver 0.0.0.0:8000

postgres:
container_name: parserator-postgres
image: postgres:11
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 10s
timeout: 5s
retries: 5
Expand Down
Binary file added images/parserator-solved-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions node_modules/.bin/acorn

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/eslint

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/esparse

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/esvalidate

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/js-yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/node-which

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/rimraf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/semver

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading