r"""
crate_anon/nlp_webserver/server_processor.py
===============================================================================
Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CRATE.
CRATE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CRATE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CRATE. If not, see <https://www.gnu.org/licenses/>.
===============================================================================
ServerProcessor class.
"""
from typing import Dict, Optional, Any
from crate_anon.nlp_manager.base_nlp_parser import BaseNlpParser
from crate_anon.nlp_manager.all_processors import make_nlp_parser_unconfigured
from crate_anon.nlprp.api import JsonObjectType, NlprpServerProcessor
from crate_anon.nlprp.constants import NlprpKeys, NlprpValues
from crate_anon.nlprp.errors import BAD_REQUEST, mkerror, no_such_proc_error
from crate_anon.nlp_webserver.constants import PROCTYPE_GATE, GATE_BASE_URL
[docs]class ServerProcessor(NlprpServerProcessor):
"""
Adds extra information to
:class:`crate_anon.nlprp.api.NlprpServerProcessor`.
- For ease of finding processor info based on name and version
(alternative would be a dictionary in which the keys were name_version
and the values were another dictionary with the rest of the info).
- Also used as the client-side representation.
"""
# Master list of all instances (processors)
processors = {} # type: Dict[str, "ServerProcessor"]
[docs] def __init__(
self,
name: str,
title: str,
version: str,
is_default_version: bool,
description: str,
schema_type: str = NlprpValues.UNKNOWN,
sql_dialect: Optional[str] = None,
tabular_schema: Optional[Dict[str, Any]] = None,
proctype: Optional[str] = None,
) -> None:
super().__init__(
name=name,
title=title,
version=version,
is_default_version=is_default_version,
description=description,
schema_type=schema_type,
sql_dialect=sql_dialect,
tabular_schema=tabular_schema,
)
if len(self.processor_id) > 100:
raise ValueError(
f"Processor id {self.processor_id} is too "
"long for database field"
)
self.base_url = None
if proctype == PROCTYPE_GATE:
self.base_url = GATE_BASE_URL
self.parser = None # type: Optional[BaseNlpParser]
if not proctype:
self.proctype = name
else:
self.proctype = proctype
# Add instance to list of processors
ServerProcessor.processors[self.processor_id] = self
@property
def processor_id(self) -> str:
return f"{self.name}_{self.version}"
[docs] @classmethod
def get_processor(cls, name: str, version: str = "") -> "ServerProcessor":
"""
Fetch a processor by name and (optionally) version.
Args:
name: requested processor name
version: (optional) requested processor version
Returns:
a :class:`Processor`
Raises:
:exc:`crate_anon.nlprp.errors.NlprpError` if no processor
matches.
"""
for candidate in cls.processors.values():
if name == candidate.name:
# Initially coded as case-insensitive (as someone might put
# e.g. 'CRP' instead of 'Crp'), but has to be case-sensitive
# because some of the GATE processors have the same name as the
# Python ones only different case.
if version:
# Specific version requested.
if version == candidate.version:
return candidate
else:
# No specific version requested.
if candidate.is_default_version:
return candidate
raise no_such_proc_error(name, version)
[docs] @classmethod
def get_processor_nlprp(
cls, requested_processor_dict: JsonObjectType
) -> "ServerProcessor":
"""
Fetch a processor, from an NLPRP dictionary specifying it.
Args:
requested_processor_dict: part of an NLPRP request
Returns:
a :class:`Processor`
Raises:
:exc:`crate_anon.nlprp.errors.NlprpError` if the
``NlprpKeys.NAME`` key is missing or no processor matches.
"""
version = requested_processor_dict.get(NlprpKeys.VERSION) # optional
try:
name = requested_processor_dict[NlprpKeys.NAME] # may raise
except KeyError:
raise mkerror(
BAD_REQUEST, f"Processor request has no {NlprpKeys.NAME!r} key"
)
return cls.get_processor(name=name, version=version)
[docs] @classmethod
def get_processor_from_id(cls, processor_id: str) -> "ServerProcessor":
"""
Fetch a processor, from a processor ID (a string representing name and
versio).
Args:
processor_id: string in the format ``name_version``. The version
part can't contain an underscore, but the name can.
Returns:
a :class:`Processor`
Raises:
:exc:`crate_anon.nlprp.errors.NlprpError` if no processor
matches.
"""
# Split on the last occurrence of '_'
name, _, version = processor_id.rpartition("_")
return cls.get_processor(name, version)
[docs] def set_parser(self) -> None:
"""
Sets 'self.parser' to an instance of a subclass of 'BaseNlpParser'
not bound to any nlpdef or cfgsection, unless self.proctype is GATE
(in which case, do nothing).
"""
if self.proctype != PROCTYPE_GATE:
# We do not have to supply a NLP definition here.
self.parser = make_nlp_parser_unconfigured(self.proctype)
# else: do nothing