Skip to content

Commit

Permalink
Merge pull request #35 from BritishGeologicalSurvey/gui
Browse files Browse the repository at this point in the history
GUI Update
  • Loading branch information
ximenesuk authored Aug 26, 2021
2 parents 6163592 + 233b608 commit 30b95bd
Show file tree
Hide file tree
Showing 13 changed files with 684 additions and 108 deletions.
33 changes: 28 additions & 5 deletions app/ags.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,25 @@
import python_ags4
from python_ags4 import AGS4


from app.response_templates import PLAIN_TEXT_TEMPLATE, RESPONSE_TEMPLATE

logger = logging.getLogger(__name__)

# Collect full paths of dictionaries installed alongside python_ags4
_dictionary_files = list(Path(python_ags4.__file__).parent.glob('Standard_dictionary*.ags'))
STANDARD_DICTIONARIES = {f.name: f.absolute() for f in _dictionary_files}

logger = logging.getLogger(__name__)


def validate(filename: Path, standard_AGS4_dictionary: Optional[str] = None) -> dict:
"""
Validate filename (against optional dictionary) and respond in
dictionary suitable for converting to JSON.
def validate(filename: Path) -> dict:
"""Validate filename and respond in dictionary."""
:raises ValueError: Raised if dictionary provided is not available.
"""
logger.info("Validate called for %", filename.name)

# Prepare response with metadata
Expand All @@ -25,9 +37,20 @@ def validate(filename: Path) -> dict:
'checker': f'python_ags4 v{python_ags4.__version__}',
'time': dt.datetime.now(tz=dt.timezone.utc)}

# Select dictionary file if exists
if standard_AGS4_dictionary:
try:
dictionary_file = STANDARD_DICTIONARIES[standard_AGS4_dictionary]
except KeyError:
msg = (f"{standard_AGS4_dictionary} not available. "
f"Installed dictionaries: {STANDARD_DICTIONARIES.keys()}")
raise ValueError(msg)
else:
dictionary_file = None

# Get error information from file
try:
errors = AGS4.check_file(filename)
errors = AGS4.check_file(filename, standard_AGS4_dictionary=dictionary_file)
try:
metadata = errors.pop('Metadata') # This also removes it from returned errors
dictionary = [d['desc'] for d in metadata
Expand Down Expand Up @@ -106,11 +129,11 @@ def convert(filename: Path, results_dir: Path) -> Tuple[Optional[Path], str]:
return (converted_file, log)


def is_valid(filename: Path) -> bool:
def is_valid(filename: Path, standard_AGS4_dictionary: Optional[str] = None) -> bool:
"""
Validate filename and parse returned log to determine if file is valid.
"""
return validate(filename)['valid']
return validate(filename, standard_AGS4_dictionary=standard_AGS4_dictionary)['valid']


def get_unicode_message(stderr: str, filename: str) -> str:
Expand Down
4 changes: 2 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def setup_logging(logging_level=logging.INFO):


@app.get("/", response_class=HTMLResponse, include_in_schema=False)
async def homepage(request: Request):
return templates.TemplateResponse('index.html', {'request': request})
async def landing_page(request: Request):
return templates.TemplateResponse('landing_page.html', {'request': request})


def custom_openapi():
Expand Down
77 changes: 57 additions & 20 deletions app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from pathlib import Path
from typing import List

from fastapi import APIRouter, BackgroundTasks, File, Query, Request, UploadFile
from fastapi.responses import FileResponse, StreamingResponse
from fastapi import APIRouter, BackgroundTasks, File, Form, Request, UploadFile
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse

from app import ags
from app.errors import error_responses, InvalidPayloadError
Expand All @@ -16,8 +16,8 @@

log_responses = dict(error_responses)
log_responses['200'] = {
"content": {"text/plain": {}},
"description": "Return a log file"}
"content": {"application/json": {}, "text/plain": {}},
"description": "Return a log in json or text"}

zip_responses = dict(error_responses)
zip_responses['200'] = {
Expand All @@ -31,27 +31,56 @@ class Format(str, Enum):
JSON = "json"


format_query = Query(
# Enum for search logic
class Dictionary(str, Enum):
V4_0_3 = "v4_0_3"
V4_0_4 = "v4_0_4"
V4_1 = "v4_1"


format_form = Form(
default=Format.JSON,
title='Format',
description='Response format, text or json',
title='Response Format',
description='Response format: json or text',
)

dictionary_form = Form(
default=None,
title='Validation Dictionary',
description='Version of AGS dictionary to validate against',
)

validation_file = File(
...,
title='File to validate',
description='An AGS file ending in .ags',
)

conversion_file = File(
...,
title='File to convert',
description='An AGS or XLSX file',
)


@router.post("/isvalid/",
response_model=ValidationResponse,
responses=log_responses)
async def is_valid(background_tasks: BackgroundTasks,
file: UploadFile = File(...),
file: UploadFile = validation_file,
std_dictionary: Dictionary = dictionary_form,
request: Request = None):
if not file.filename:
raise InvalidPayloadError(request)
tmp_dir = Path(tempfile.mkdtemp())
background_tasks.add_task(shutil.rmtree, tmp_dir)
dictionary = None
if std_dictionary:
dictionary = f'Standard_dictionary_{std_dictionary}.ags'
contents = await file.read()
local_ags_file = tmp_dir / file.filename
local_ags_file.write_bytes(contents)
valid = ags.is_valid(local_ags_file)
valid = ags.is_valid(local_ags_file, standard_AGS4_dictionary=dictionary)
data = [valid]
response = prepare_validation_response(request, data)
return response
Expand All @@ -61,17 +90,21 @@ async def is_valid(background_tasks: BackgroundTasks,
response_model=ValidationResponse,
responses=log_responses)
async def validate(background_tasks: BackgroundTasks,
file: UploadFile = File(...),
fmt: Format = format_query,
file: UploadFile = validation_file,
std_dictionary: Dictionary = dictionary_form,
fmt: Format = format_form,
request: Request = None):
if not file.filename:
raise InvalidPayloadError(request)
tmp_dir = Path(tempfile.mkdtemp())
background_tasks.add_task(shutil.rmtree, tmp_dir)
dictionary = None
if std_dictionary:
dictionary = f'Standard_dictionary_{std_dictionary}.ags'
contents = await file.read()
local_ags_file = tmp_dir / file.filename
local_ags_file.write_bytes(contents)
result = ags.validate(local_ags_file)
result = ags.validate(local_ags_file, standard_AGS4_dictionary=dictionary)
if fmt == Format.TEXT:
log = ags.to_plain_text(result)
logfile = tmp_dir / 'results.log'
Expand All @@ -87,21 +120,26 @@ async def validate(background_tasks: BackgroundTasks,
response_model=ValidationResponse,
responses=log_responses)
async def validate_many(background_tasks: BackgroundTasks,
files: List[UploadFile] = File(...),
fmt: Format = format_query,
files: List[UploadFile] = validation_file,
std_dictionary: Dictionary = dictionary_form,
fmt: Format = format_form,
request: Request = None):
if not files[0].filename:
raise InvalidPayloadError(request)
tmp_dir = Path(tempfile.mkdtemp())
background_tasks.add_task(shutil.rmtree, tmp_dir)
dictionary = None
if std_dictionary:
dictionary = f'Standard_dictionary_{std_dictionary}.ags'
if fmt == Format.TEXT:
full_logfile = tmp_dir / 'results.log'
with full_logfile.open('wt') as f:
for file in files:
contents = await file.read()
local_ags_file = tmp_dir / file.filename
local_ags_file.write_bytes(contents)
log = ags.to_plain_text(ags.validate(local_ags_file))
result = ags.validate(local_ags_file, standard_AGS4_dictionary=dictionary)
log = ags.to_plain_text(result)
f.write(log)
f.write('=' * 80 + '\n')
response = FileResponse(full_logfile, media_type="text/plain")
Expand All @@ -111,8 +149,8 @@ async def validate_many(background_tasks: BackgroundTasks,
contents = await file.read()
local_ags_file = tmp_dir / file.filename
local_ags_file.write_bytes(contents)
log = ags.validate(local_ags_file)
data.append(log)
result = ags.validate(local_ags_file, standard_AGS4_dictionary=dictionary)
data.append(result)
response = prepare_validation_response(request, data)
return response

Expand All @@ -121,8 +159,7 @@ async def validate_many(background_tasks: BackgroundTasks,
response_class=StreamingResponse,
responses=zip_responses)
async def convert_many(background_tasks: BackgroundTasks,
files: List[UploadFile] = File(...),
fmt: Format = format_query,
files: List[UploadFile] = conversion_file,
request: Request = None):
if not files[0].filename:
raise InvalidPayloadError(request)
Expand Down Expand Up @@ -159,4 +196,4 @@ def prepare_validation_response(request, data):
'self': str(request.url),
'data': data,
}
return ValidationResponse(**response_data)
return ValidationResponse(**response_data, media_type="application/json")
128 changes: 121 additions & 7 deletions app/static/css/styles.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
main {
min-height: 100%;
margin-bottom: -100px;
}

main:after {
content: "";
display: block;
height: 100px;
}

body {
height: 100%;
color: #002E40;
text-align: left;
font-family: Arial, Helvetica, sans-serif;
}


h1 {
color: #002E40;
text-align: center;
Expand All @@ -17,9 +28,112 @@ h2 {
font-family: Arial, Helvetica, sans-serif;
}

img {
display: block;
margin-left: auto;
margin-right: auto;
width: 20%;
}
/* Popup text style start */

.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}

.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
top: -5px;
left: 110%;
}

.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: 50%;
right: 100%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent black transparent transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}

/* Popup text style start */

/* Header style start */

.header-wrapper
{
background-color: #002E40;
color: white;
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
padding: 20px 32px;
}

.header-wrapper img {
height:48px;
vertical-align: middle;
}

@media only screen and (max-width: 600px) {
.header-wrapper {
flex-direction: column;
text-align: center;
padding: 24px 16px;
}
}

.header-wrapper a {
color: white;
}

.header-wrapper a:hover {
background: rgba(255, 255, 255, 0.3);
}

/* Header style end */

/* Footer style start */

.footer-wrapper
{
background-color: #002E40;
color: white;
display: flex;
gap: 32px;
align-items: center;
justify-content: space-evenly;
padding: 48px 32px;
}

.footer-wrapper img {
height:48px;
vertical-align: middle;
}

@media only screen and (max-width: 600px) {
.footer-wrapper {
flex-direction: column;
text-align: center;
padding: 24px 16px;
}
}

.footer-wrapper a {
color: white;
}

.footer-wrapper a:hover {
background: rgba(255, 255, 255, 0.3);
}

/* Footer style end */
Loading

0 comments on commit 30b95bd

Please sign in to comment.