Close

23rd July 2022

Vehicle Enquiry Service API Lookup Demo

Vehicle Enquiry Service API Lookup Demo

The Goal

To build a web based application that allows the retrieval of vehicle information from the UK DVLA Vehicle Enquiry Service API and package that in a container for simplified distribution.  A Vehicle Enquiry Service API Lookup Demo if you like.  I’m building this because I find it interesting and also because I wanted something that was more applicable for organisations when demonstrating and  discussing modern applications, infrastructure packaging and delivery.

Building the application

To build a functioning MVP from the API the application needs a mechanism to query the API, process the returned data and display the returning information.  Further the application should also take inputs from a user, that specify vehicle registrations to be looked up.

I’ve been building a few prototypes recently using data from various sources and types using Python and I’ve found the Streamlit library to be an incredible resource for building and rapidly sharing data-based applications.  Streamlit will provide the layout, text inputs, buttons and the community developed streamlit_tags library will provide the capability to capture a list of vehicle registrations.

The Pandas library will provide ready to use data structures and data analysis tools. The Requests library is the de facto standard for making HTTP requests in Python, so the application will use this to interact with the UK DVLA Vehicle Enquiry Service API.

Using Anaconda I can create a Python environment that contains only the libraries that the application needs, and avoids creating issues with my local copies of python used by the OS for my device.  With anaconda available I can either use Anaconda Navigator to import the required libraries to the virtual python environment or I can open the virtual environment from the command line and interact directly using pip.  For example, to install streamlit, I could run the following from the virtual environment command line.

pip install streamlit

With the libraries available the python script that streamlit will call can be created and we can launch streamlit from the virtual environment command line.

streamlit run app.py

The Python Script

Rather than go through line by line what the script is doing in this post I’ve commented the code and also made the project available on github for review.

import requests
from requests.structures import CaseInsensitiveDict
from pandas.io.json import json_normalize
import pandas as pd
import streamlit as st
from streamlit_tags import st_tags

# multiple vehicle lookup function
def car_regs_lookup():
# build empty dataframe
    multicar_reg = pd.DataFrame()

# for loop to iterate through the list of registration numbers
    for x in regs: 
        try:
# base url for the API
            url = "https://driver-vehicle-licensing.api.gov.uk/vehicle-enquiry/v1/vehicles"

# URL headers
            headers = CaseInsensitiveDict()
            headers["x-api-key"] = "Your API KEY"
            headers["Content-Type"] = "application/json"

# API data request the format requires the passing of {} and "" as strings hence the formatting options
            data = str({"registrationNumber": str(x)}).replace("'", '"')

# API request
            resp = requests.post(url, headers=headers, data=data)

# Write out the API status code
            st.markdown(f'<h1 style="color:#6ac94f;font-size:12px;">returned API Status Code = {resp.status_code}</h1>', unsafe_allow_html=True)

# If the API status code is not 200 then raise an exception   
            if resp.status_code != 200:
                raise Exception("Error: {}".format(resp.status_code))
            else:
# Write out the API response
                st.write(resp.json())

# Convert the API response to a pandas dataframe
                json = resp.json()
                car_reg = json_normalize(json)

# Append the response dataframe to the empty dataframe
                multicar_reg = pd.concat([multicar_reg, car_reg])
        except:
# If an exception is raised then write out the error message
            st.warning("No data found for provided registration number, please try again")

# Write out the merged dataframe
    st.dataframe(multicar_reg)

# Convert the dataframe to a CSV
    def convert_df(multicar_reg):
        return multicar_reg.to_csv().encode('utf-8')
    csv = convert_df(multicar_reg)

# Create a download button for the CSV
    st.download_button(
        label="Download Output as CSV",
        data=csv,
        file_name=(f'CarDetails.csv'),
        mime='text/csv',
        )

# Single vehicle lookup function  
def car_reg_lookup():
    try:
# Write out the caputred registration number
        st.write(st.session_state.reg)

# base url for the API
        url = "https://driver-vehicle-licensing.api.gov.uk/vehicle-enquiry/v1/vehicles"

# URL headers
        headers = CaseInsensitiveDict()
        headers["x-api-key"] = "Your API Key"
        headers["Content-Type"] = "application/json"

# API data request the format requires the passing of {} and "" as strings hence the formatting options
        data = str({"registrationNumber": str(st.session_state.reg)}).replace("'", '"')

# API request
        resp = requests.post(url, headers=headers, data=data)

# Write out the API status code
        st.markdown(f'<h1 style="color:#6ac94f;font-size:12px;">returned API Status Code = {resp.status_code}</h1>', unsafe_allow_html=True)

# If the API status code is not 200 then raise an exception
        if resp.status_code != 200:
            raise Exception("Error: {}".format(resp.status_code))
        else:

# Write out the API response
            st.write(resp.json())

# Convert the API response to a pandas dataframe
            json = resp.json()
            car_reg = json_normalize(json)

# Write out the dataframe
            st.dataframe(car_reg)

# Convert the dataframe to a CSV
            def convert_df(car_reg):
                return car_reg.to_csv().encode('utf-8')
            csv = convert_df(car_reg)

# Create a download button for the CSV
            st.download_button(
                label="Download Output as CSV",
                data=csv,
                file_name=(f'CarDetails{st.session_state.reg}.csv'),
                mime='text/csv',
                )
    except:
# If an exception is raised then write out the error message
        st.warning("No data found for provided registration number, please try again")

# This section creates managed the look and feel of the interface displays reference information
st.set_page_config(
   layout="wide", page_title="UK DVLA API - Car Registration Lookup")

st.title("UK DVLA API - Car Registration Lookup")
st.markdown("This tool can be used to lookup a vehicle registration number and return the details of the vehicle.")
st.markdown("Information about the API is available from this [DVLA website](https://developer-portal.driver-vehicle-licensing.api.gov.uk/apis/vehicle-enquiry-service/vehicle-enquiry-service-description.html#vehicle-enquiry-service-api)")

# This section is the text input for the single registration lookup
reg = st.text_input("Vehicle Registration to Lookup", "EXAMPLE", key="reg")

# This section is the text input for the multiple registration lookup
regs = st_tags(label="Input Multiple Vehicle Registrations",
               text="Press enter to add more",
               key="regs",)     

# This section is the button for the single registration lookup
if st.button('Perform single vehicle lookup'):
    car_reg_lookup() 

# This section is the button for the multiple registration lookup
if st.button('Perform multiple vehicle lookup'):
    car_regs_lookup()

Packaging the application

To package the application as a container I need to build a dockerfile and a requirements.txt file.  The requirements.txt file is used by pip to install the libraries that are required and the dockerfile dictates how the image will be built and execute when run.

Broadly speaking the requirements file will need to mirror the libraries being imported.  To remove any guess work you can use pip freeze

pip3 freeze > requirements.txt  # Python3
pip freeze > requirements.txt  # Python2

For the above Python the requirements.txt file looks like this

requests
pandas
streamlit
streamlit_tags

it’s good practice to version control the libraries, so to add specific versions to the requirements file, follow the below example syntax

streamlit==1.11.0

The dockerfile dictates how the image will be built and what will be executed when the image is run.  The commented dockerfile for the project above is included here for reference.

# dockerfile using Python image
FROM python:3.6-slim

# Create working directory
RUN mkdir /application
WORKDIR "/application"

# upgrade pip
RUN pip install --upgrade pip

# update image
RUN apt-get update \
    && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

# Copy requirements and script to image working directory
COPY . .

# install requirements
RUN pip install -r /application/requirements.txt

# execute script
ENTRYPOINT ["streamlit", "run"]

CMD ["/application/app.py"]

Making it available

With the prerequisites met the image can be built using docker.  The simplest way to accomplish this is to create a clean directory, with only the application files within, the dockerfile, requirements.txt and the app.py.  After changing directory to the clean host directory, the following is the command to build the image.

docker image build . -t reg_api

In the example above “.” represents build from the current directory, and “-t” is the image tag, which in the above example is “reg_api”.  Example output below.

After the image has been built, I like to check that it’s listed as I expect, a simple grep works.

Running the Application

In Docker the image can be run via the command line with docker run, as we’re serving content on non standard ports that mapping needs to be made using -p.

docker run -p8501:8501 reg_api:latest

Now this is an image, it can be tagged and pushed to any image repository that might be required.

docker tag SOURCE_IMAGE[:TAG] IMAGE_REPO/USER/reg_api[:TAG]

docker push IMAGE_REPO/USER/reg_api[:TAG]

If that image repository happens to be accessible to a Kubernetes cluster, then the location could be referenced in YAML to deploy to Kubernetes.

kubectl apply -f reg_api.yaml -n reg_api

Example of a simple YAML file to deploy a pod of the created image and a loadbalancer

apiVersion: v1
kind: Pod
metadata:
  name: reg_api
  labels:
    role: reg_api
    app: reg_api
spec:
  containers:
  - name: reg_api
    image: IMAGE_REPO/USER/reg_api:latest
    ports:
     - containerPort: 8501
---
kind: Service
apiVersion: v1
metadata:
  name: reg_api-lb
  namespace: reg_api
spec:
  type: LoadBalancer
  selector:
    app: reg_api
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8501

Summary

The goal of building a web based application that allows the retrieval of vehicle information from the UK DVLA Vehicle Enquiry Service API and package that in a container for simplified distribution has been met.

A repository with all the files required to build the image locally can be accessed from github.  All that will be required is your own API key from the DVLA.

No support offered or liability accepted, use is entirely at your own risk.

Hopefully this is interesting or useful to someone else!

Simon