Select Git revision
views_logic.py
-
Timofey Arkhangelskiy authoredTimofey Arkhangelskiy authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
views_logic.py 14.75 KiB
# Contains functions called by the top-level view functions
# that process the user's request and return a rendered XML
# template
from typing import Optional
from fastapi import Request, Query, Response
from .enums import *
from .diagnostics import Diagnostic
from .config import ResourceConfig
def initial_validation(operation, version, queryType, searchOptions, query):
"""
Validate and convert values of the main request parameters.
Return converted values and a list of fatal diagnostics, if anything is wrong.
"""
failDiagnoctics = []
if version == '1.2':
version = SRUVersion.v1_2
elif version == '2.0':
version = SRUVersion.v2_0
else:
version = SRUVersion.v2_0
failDiagnoctics.append(Diagnostic(DiagnosticTypes.sru, 5, details='2.0'))
if operation == '':
if len(query) > 0:
operation = Operation.searchRetrieve
else:
operation = Operation.explain
elif operation == 'explain':
operation = Operation.explain
elif operation == 'searchRetrieve':
operation = Operation.searchRetrieve
elif operation == 'scan':
operation = Operation.scan
else:
operation = Operation.explain
failDiagnoctics.append(Diagnostic(DiagnosticTypes.sru, 4, version=version))
if queryType == 'fcs':
queryType = QueryType.fcs
elif queryType == 'cql':
queryType = QueryType.cql
else:
queryType = QueryType.cql
failDiagnoctics.append(Diagnostic(DiagnosticTypes.sru, 6, message='Supported query types: fcs and cql.',
version=version))
try:
searchOptions['startRecord'] = int(searchOptions['startRecord'])
except ValueError:
searchOptions['startRecord'] = 1
failDiagnoctics.append(Diagnostic(DiagnosticTypes.sru, 6, message='startRecord should be a positive integer.',
version=version))
if searchOptions['startRecord'] < 1:
failDiagnoctics.append(Diagnostic(DiagnosticTypes.sru, 6, message='startRecord should be a positive integer.',
version=version))
try:
searchOptions['maximumRecords'] = int(searchOptions['maximumRecords'])
except ValueError:
searchOptions['maximumRecords'] = 0
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 6, message='maximumRecords should be a non-negative integer.',
version=version))
if searchOptions['maximumRecords'] < 0:
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 6, message='maximumRecords should be a non-negative integer.',
version=version))
# recordPacking has entirely different semantics in SRU 1.2 and SRU 2.0
if version == SRUVersion.v1_2:
if searchOptions['recordPacking'] == '':
searchOptions['recordPacking'] = 'xml'
if searchOptions['recordPacking'] not in ('xml', 'string'):
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 71, message='recordPacking should equal "xml" or "string".',
version=version))
else:
if searchOptions['recordXMLEscaping'] == '':
searchOptions['recordXMLEscaping'] = 'xml'
if searchOptions['recordPacking'] == '':
searchOptions['recordPacking'] = 'packed'
if searchOptions['recordXMLEscaping'] not in ('xml', 'string'):
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 71, message='recordXMLEscaping should equal "xml" or "string".',
version=version))
if searchOptions['recordPacking'] not in ('packed', 'unpacked'):
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 6, message='recordPacking should equal "packed" or "unpacked".',
version=version))
try:
searchOptions['resultSetTTL'] = int(searchOptions['resultSetTTL'])
except ValueError:
searchOptions['resultSetTTL'] = 0
failDiagnoctics.append(
Diagnostic(DiagnosticTypes.sru, 6, message='resultSetTTL should be a positive integer.',
version=version))
if searchOptions['resultSetTTL'] < 0:
# This does not look good, but we don't care because this
# value is not used anyway
pass
# failDiagnoctics.append(
# Diagnostic(DiagnosticTypes.sru, 6, message='resultSetTTL should be a positive integer.',
# version=version))
return operation, version, queryType, searchOptions, failDiagnoctics
def fatal_response(operation: Operation,
version: SRUVersion,
config: Optional[ResourceConfig],
diagnostics: list[Diagnostic],
request, templates):
"""
Return a response with the fatal diagnostics
and no other payload.
"""
if config is None:
configStr = ''
else:
configStr = config.as_dict()
if version == SRUVersion.v1_2:
templateVersion = 1
else:
templateVersion = 2
for diag in diagnostics:
diag.version = version
diagStr = [str(d) for d in diagnostics]
if operation in (Operation.explain, Operation.scan):
return templates.TemplateResponse('explain_response.xml',
{
'request': request,
'diagnostics': diagStr,
'config': configStr,
'version': templateVersion
},
media_type='application/xml')
elif operation == Operation.searchRetrieve:
return templates.TemplateResponse('search_retrieve_response.xml',
{
'request': request,
'diagnostics': diagStr,
'n_hits': 0,
'version': templateVersion
},
media_type='application/xml')
def process_explain(version: SRUVersion,
searchOptions: dict[str, str],
config: Optional[ResourceConfig],
diagnostics: list[Diagnostic],
request, templates):
"""
Process an explain request.
Return a rendered XML response.
"""
if version == SRUVersion.v1_2:
templateVersion = 1
else:
templateVersion = 2
for diag in diagnostics:
diag.version = version
endpointDescNeeded = False
if 'x-fcs-endpoint-description' in searchOptions and searchOptions['x-fcs-endpoint-description'] == 'true':
endpointDescNeeded = True
diagStr = [str(d) for d in diagnostics]
return templates.TemplateResponse('explain_response.xml',
{
'request': request,
'diagnostics': diagStr,
'config': config.as_dict(),
'version': templateVersion,
'endpoint_desc_needed': endpointDescNeeded
},
media_type='application/xml')
def process_search_retrieve(version: SRUVersion,
queryType: QueryType,
query: str,
searchOptions: dict,
config: Optional[ResourceConfig],
diagnostics: list[Diagnostic],
app, request, templates):
"""
Process a searchRetrieve request.
Return a rendered XML response.
"""
if version == SRUVersion.v1_2:
templateVersion = 1
else:
templateVersion = 2
for diag in diagnostics:
diag.version = version
if config.platform == CorpPlatform.annis:
try:
if queryType == QueryType.cql:
query = app.qp_annis.translate_simple(query, config, searchOptions)
else:
query = app.qp_annis.translate_advanced(query, config, searchOptions)
print(query)
res = app.qp_annis.send_query(query, config)
except Diagnostic as diag:
return fatal_response(Operation.searchRetrieve, version, config, diagnostics + [diag], request, templates)
# return query['query']
return res
# records, nHits, diagnostics = app.rp_annis.parse(res, config, searchOptions['x-fcs-dataviews'])
# if any(diag.is_fatal() for diag in diagnostics):
# return fatal_response(Operation.searchRetrieve, version, config, diagnostics, request, templates)
# records = [r.as_dict() for r in records]
# diagnostics = [str(d) for d in diagnostics]
# return templates.TemplateResponse('search_retrieve_response.xml',
# {
# 'request': request,
# 'n_hits': nHits,
# 'records': records,
# 'version': templateVersion,
# 'diagnostics': diagnostics
# },
# media_type='application/xml')
if config.platform == CorpPlatform.tsakorpus:
try:
if queryType == QueryType.cql:
strGetParams = app.qp_tsakorpus.translate_simple(query, config, searchOptions)
else:
strGetParams = app.qp_tsakorpus.translate_advanced(query, config, searchOptions)
print(strGetParams)
res = app.qp_tsakorpus.send_query(strGetParams, config)
except Diagnostic as diag:
return fatal_response(Operation.searchRetrieve, version, config, diagnostics + [diag], request, templates)
records, nHits, diagnostics = app.rp_tsakorpus.parse(res, config, searchOptions)
if any(diag.is_fatal() for diag in diagnostics):
return fatal_response(Operation.searchRetrieve, version, config, diagnostics, request, templates)
records = [r.as_dict() for r in records]
diagnostics = [str(d) for d in diagnostics]
return templates.TemplateResponse('search_retrieve_response.xml',
{
'request': request,
'n_hits': nHits,
'records': records,
'version': templateVersion,
'diagnostics': diagnostics
},
media_type='application/xml')
elif config.platform == CorpPlatform.litterae:
try:
if queryType == QueryType.cql:
strGetParams = app.qp_litterae.translate_simple(query, config, searchOptions)
else:
# No advanced search for Litterae
strGetParams = app.qp_litterae.translate_simple(query, config, searchOptions)
# print(strGetParams)
res = app.qp_litterae.send_query(strGetParams, config)
print(res)
except Diagnostic as diag:
return fatal_response(Operation.searchRetrieve, version, config, diagnostics + [diag], request, templates)
for dv in searchOptions['x-fcs-dataviews'].split(','):
dv = dv.strip()
if dv != 'hits' and version == SRUVersion.v2_0:
# Litterae does not provide any additional annotation, so only Generic Hits
# are available as a data view.
# If SRU 1.2 is used, such a diagnostic has already been added
# at a previous step.
diagnostics.append(Diagnostic(DiagnosticTypes.fcs, 4, details=dv, version=version))
records, nHits, diagnostics = app.rp_litterae.parse(res, config, searchOptions)
if any (diag.is_fatal() for diag in diagnostics):
return fatal_response(Operation.searchRetrieve, version, config, diagnostics, request, templates)
records = [r.as_dict() for r in records]
diagnostics = [str(d) for d in diagnostics]
return templates.TemplateResponse('search_retrieve_response.xml',
{
'request': request,
'n_hits': nHits,
'records': records,
'version': templateVersion,
'diagnostics': diagnostics
},
media_type='application/xml')
def process_request(operation: Operation,
version: SRUVersion,
queryType: QueryType,
query: str,
searchOptions: dict[str, str],
config: Optional[ResourceConfig],
diagnostics: list[Diagnostic],
app, request, templates):
"""
Process validated user request that came in through the endpoint()
function in main.py.
Return a rendered template.
:param diagnostics: List of diagnostics produced by the validation
function.
"""
print(query)
# If something is clearly wrong with the query, return
# a response with the list of diagnostics
if config is None or any(d.is_fatal() for d in diagnostics):
return fatal_response(operation, version, config, diagnostics, request, templates)
# If everything looks good, proceed to query parsing
if operation == Operation.searchRetrieve:
return process_search_retrieve(version, queryType, query, searchOptions, config, diagnostics, app, request, templates)
elif operation == Operation.explain:
return process_explain(version, searchOptions, config, diagnostics, request, templates)
# We should not end up here, but if we did, something went wrong and
# no fatal diagnostic describes the problem. Add a generic fatal diagnostic
# and return a fatal response.
diagnostics.append(Diagnostic(DiagnosticTypes.sru, 1, version=version))
return fatal_response(operation, version, config, diagnostics, request, templates)
if __name__ == '__main__':
pass