Skip to content

Commit

Permalink
Merge pull request #125 from ropable/master
Browse files Browse the repository at this point in the history
Use Alpine base image, update JavaScript library versions
  • Loading branch information
ropable authored Sep 16, 2024
2 parents 5442cb3 + b556a2b commit e70c5b0
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 64 deletions.
48 changes: 30 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
# syntax=docker/dockerfile:1
# Prepare the base environment.
FROM python:3.12.4-slim AS builder_base_caddy
FROM python:3.12.6-alpine AS builder_base
LABEL [email protected]
LABEL org.opencontainers.image.source=https://github.com/dbca-wa/caddy

RUN apt-get update -y \
&& apt-get upgrade -y \
&& apt-get install -y libmagic-dev gcc binutils gdal-bin proj-bin python3-dev libpq-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --root-user-action=ignore --upgrade pip
# Install system requirements to build Python packages.
RUN apk add --no-cache \
gcc \
libressl-dev \
musl-dev \
libffi-dev
# Create a non-root user to run the application.
ARG UID=10001
ARG GID=10001
RUN addgroup -g ${GID} appuser \
&& adduser -H -D -u ${UID} -G appuser appuser

# Install Python libs using poetry.
FROM builder_base_caddy AS python_libs_caddy
# Install Python libs using Poetry.
FROM builder_base AS python_libs_caddy
# Add system dependencies required to use GDAL
RUN apk add --no-cache \
gdal \
geos \
proj \
binutils
WORKDIR /app
ARG POETRY_VERSION=1.8.3
RUN pip install --no-cache-dir --root-user-action=ignore poetry==${POETRY_VERSION}
COPY poetry.lock pyproject.toml ./
RUN poetry config virtualenvs.create false \
ARG POETRY_VERSION=1.8.3
RUN pip install --no-cache-dir --root-user-action=ignore poetry==${POETRY_VERSION} \
&& poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi --only main

# Create a non-root user.
ARG UID=10001
ARG GID=10001
RUN groupadd -g ${GID} appuser \
&& useradd --no-create-home --no-log-init --uid ${UID} --gid ${GID} appuser
# Remove system libraries, no longer required.
RUN apk del \
gcc \
libressl-dev \
musl-dev \
libffi-dev

# Install the project.
FROM python_libs_caddy AS project_caddy
COPY geocoder.py gunicorn.py manage.py ./
COPY caddy ./caddy
COPY shack ./shack

USER ${UID}
EXPOSE 8080
CMD ["gunicorn", "caddy.wsgi", "--config", "gunicorn.py"]
35 changes: 35 additions & 0 deletions Dockerfile.debian
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# syntax=docker/dockerfile:1
# Prepare the base environment.
FROM python:3.12.4-slim AS builder_base_caddy
LABEL [email protected]
LABEL org.opencontainers.image.source=https://github.com/dbca-wa/caddy

RUN apt-get update -y \
&& apt-get upgrade -y \
&& apt-get install -y libmagic-dev gcc binutils gdal-bin proj-bin python3-dev libpq-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --root-user-action=ignore --upgrade pip

# Install Python libs using poetry.
FROM builder_base_caddy AS python_libs_caddy
WORKDIR /app
ARG POETRY_VERSION=1.8.3
RUN pip install --no-cache-dir --root-user-action=ignore poetry==${POETRY_VERSION}
COPY poetry.lock pyproject.toml ./
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi --only main

# Create a non-root user.
ARG UID=10001
ARG GID=10001
RUN groupadd -g ${GID} appuser \
&& useradd --no-create-home --no-log-init --uid ${UID} --gid ${GID} appuser

# Install the project.
COPY geocoder.py gunicorn.py manage.py ./
COPY caddy ./caddy
COPY shack ./shack

USER ${UID}
EXPOSE 8080
CMD ["gunicorn", "caddy.wsgi", "--config", "gunicorn.py"]
42 changes: 32 additions & 10 deletions caddy/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Caddy Geocoder</title>
Expand All @@ -7,7 +7,11 @@
<meta name="description" content="Cadastre spatial dataset geocoder">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/1.0.1/pure-min.css">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/pure/3.0.0/pure-min.css"
integrity="sha512-X2yGIVwg8zeG8N4xfsidr9MqIfIE8Yz1It+w2rhUJMqxUwvbVqC5OPcyRlPKYOw/bsdJut91//NO9rSbQZIPRQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer" />
<style>
html {
padding: 1em 1em;
Expand All @@ -27,21 +31,39 @@
<h1>Caddy Geocoder</h1>
<p>API endpoints:</p>
<ul>
<li><pre>/api/geocode?q=&lt;ADDRESS&gt;&amp;limit=10</pre> (returns up to 10 results, default reponse is 5 results).</li>
<li><pre>/api/geocode?q=&lt;OWNER NAME&gt;</pre></li>
<li><pre>/api/geocode?q=&lt;RESERVE NUMBER&gt;</pre></li>
<li><pre>/api/geocode?q=&lt;PIN&gt;</pre></li>
<li><pre>/api/geocode?point=&lt;LON&gt;,&lt;LAT&gt;</pre> (returns details for a single property intersected by the supplied point).</li>
<li><pre>/api/&lt;PIN&gt;</pre> (returns details for a single property, queried by cadastral PIN).</li>
<li>
<pre>/api/geocode?q=&lt;ADDRESS&gt;&amp;limit=10</pre> (returns up to 10 results, default reponse is 5 results).
</li>
<li>
<pre>/api/geocode?q=&lt;OWNER NAME&gt;</pre>
</li>
<li>
<pre>/api/geocode?q=&lt;RESERVE NUMBER&gt;</pre>
</li>
<li>
<pre>/api/geocode?q=&lt;PIN&gt;</pre>
</li>
<li>
<pre>/api/geocode?point=&lt;LON&gt;,&lt;LAT&gt;</pre> (returns details for a single property intersected by the supplied point).
</li>
<li>
<pre>/api/&lt;PIN&gt;</pre> (returns details for a single property, queried by cadastral PIN).
</li>
</ul>
<form id="address-search-form" class="pure-form">
<legend>Search for an address:</legend>
<input id="address" type="text" class="pure-input-1-2" placeholder="E.g. 17 dick perry avenue kensington">
<input id="address"
type="text"
class="pure-input-1-2"
placeholder="E.g. 17 dick perry avenue kensington">
</form>
<p>API response:</p>
<div id="api-response" class="preformatted">[]</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script>
$(function() {
$('#address-search-form').on("submit",function(e) {
Expand Down
69 changes: 39 additions & 30 deletions geocoder.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
#!/usr/bin/python
from bottle import Bottle, static_file, request, response
from caddy.utils import env
import os
import re

import ujson
from bottle import Bottle, request, response, static_file
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.sql import text

from caddy.utils import env

dot_env = os.path.join(os.getcwd(), ".env")
if os.path.exists(dot_env):
from dotenv import load_dotenv

load_dotenv()
app = application = Bottle()

Expand Down Expand Up @@ -60,22 +62,24 @@ def detail(object_id):
return "Bad request"

response.content_type = "application/json"
sql = text(f"""SELECT object_id, address_nice, owner, ST_AsText(centroid), ST_AsText(envelope), ST_AsText(boundary), data
sql = text("""SELECT object_id, address_nice, owner, ST_AsText(centroid), ST_AsText(envelope), ST_AsText(boundary), data
FROM shack_address
WHERE object_id = :object_id""")
sql = sql.bindparams(object_id=object_id)
result = session.execute(sql).fetchone()

if result:
return ujson.dumps({
"object_id": result[0],
"address": result[1],
"owner": result[2],
"centroid": result[3],
"envelope": result[4],
"boundary": result[5],
"data": result[6],
})
return ujson.dumps(
{
"object_id": result[0],
"address": result[1],
"owner": result[2],
"centroid": result[3],
"envelope": result[4],
"boundary": result[5],
"data": result[6],
}
)
else:
return "{}"

Expand Down Expand Up @@ -108,15 +112,17 @@ def geocode():
# Serialise and return any query result.
response.content_type = "application/json"
if result:
return ujson.dumps({
"object_id": result[0],
"address": result[1],
"owner": result[2],
"centroid": result[3],
"envelope": result[4],
"boundary": result[5],
"data": result[6],
})
return ujson.dumps(
{
"object_id": result[0],
"address": result[1],
"owner": result[2],
"centroid": result[3],
"envelope": result[4],
"boundary": result[5],
"data": result[6],
}
)
else:
return "{}"
else:
Expand All @@ -139,7 +145,7 @@ def geocode():
else:
limit = 5

sql = text(f"""SELECT address_nice, owner, ST_X(centroid), ST_Y(centroid), object_id
sql = text("""SELECT address_nice, owner, ST_X(centroid), ST_Y(centroid), object_id
FROM shack_address
WHERE tsv @@ to_tsquery(:tsquery)
LIMIT :limit""")
Expand All @@ -151,18 +157,21 @@ def geocode():
if result:
j = []
for i in result:
j.append({
"address": i[0],
"owner": i[1],
"lon": i[2],
"lat": i[3],
"pin": i[4],
})
j.append(
{
"address": i[0],
"owner": i[1],
"lon": i[2],
"lat": i[3],
"pin": i[4],
}
)
return ujson.dumps(j)
else:
return "[]"


if __name__ == "__main__":
from bottle import run

run(application, host="0.0.0.0", port=env("PORT", 8080))
2 changes: 1 addition & 1 deletion kustomize/overlays/prod/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ patches:
- path: service_patch.yaml
images:
- name: ghcr.io/dbca-wa/caddy
newTag: 2.3.9
newTag: 2.3.10
6 changes: 3 additions & 3 deletions manage.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys

# These lines are required for interoperability between local and container environments.
dot_env = os.path.join(os.getcwd(), '.env')
dot_env = os.path.join(os.getcwd(), ".env")
if os.path.exists(dot_env):
from dotenv import load_dotenv

load_dotenv()


Expand All @@ -24,5 +24,5 @@ def main():
execute_from_command_line(sys.argv)


if __name__ == '__main__':
if __name__ == "__main__":
main()
18 changes: 16 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "caddy"
version = "2.3.9"
version = "2.3.10"
description = "Lightweight application service to harvest and index the cadastre dataset, and expose an API endpoint to allow full-text searching of addresses."
authors = ["Ashley Felton <[email protected]>"]
license = "Apache License 2.0"
Expand All @@ -9,7 +9,7 @@ package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
django = "4.2.15"
psycopg = {version = "3.2.1", extras = ["binary", "pool"]}
psycopg = { version = "3.2.1", extras = ["binary", "pool"] }
python-dotenv = "1.0.1"
dj-database-url = "2.2.0"
django-extensions = "3.2.3"
Expand All @@ -29,6 +29,20 @@ ipython = "^8.27.0"
ipdb = "^0.13.13"
pre-commit = "^3.8.0"

# Reference: https://docs.astral.sh/ruff/configuration/
[tool.ruff]
line-length = 120

[tool.ruff.lint]
ignore = [
"E501", # Line too long
"E722", # Bare except
]

# Reference: https://www.djlint.com/docs/configuration/
[tool.djlint]
profile = "django"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.masonry.api"

0 comments on commit e70c5b0

Please sign in to comment.