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