Module gatenlp.pam.pampac.actions
Module for PAMPAC action classes.
Expand source code
"""
Module for PAMPAC action classes.
"""
from abc import ABC, abstractmethod
from gatenlp import Annotation
from gatenlp.features import Features
class Getter(ABC):
"""
Common base class of all Getter helper classes.
"""
@abstractmethod
def __call__(self, succ, context=None, location=None):
pass
def _get_match(succ, name, resultidx=0, matchidx=0, silent_fail=False):
"""
Helper method to return the match info for the given result index and name, or None.
Args:
succ: success instance
name: name of the match info
resultidx: index of the result in success
matchidx: if there is more than one matching match info with that name, which one to return
silent_fail: if True, return None, if False, raise an exception if the match info is not present
Returns:
the match info or None
"""
if resultidx >= len(succ):
if not silent_fail:
raise Exception(f"No resultidx {resultidx}, only {len(succ)} results")
return None
res = succ[resultidx]
matches = res.matches4name(name)
if not matches:
if not silent_fail:
raise Exception(f"No match info with name {name} in result")
return None
if matchidx >= len(matches):
if not silent_fail:
raise Exception(f"No match info with index {matchidx}, length is {len(matches)}")
return None
return matches[matchidx]
# pylint: disable=R0912
def _get_span(succ, name, resultidx=0, matchidx=0, silent_fail=False):
"""
Helper method to return the span for the given result index and name, or None.
Args:
succ: success instance
name: name of the match info, if None, uses the entire span of the result
resultidx: index of the result in success
matchidx: if there is more than one match info with that name, which one to return, if no name, ignored
silent_fail: if True, return None, if False, raise an exception if the match info is not present
Returns:
the span or None if no Span exists
"""
if resultidx >= len(succ):
if not silent_fail:
raise Exception(f"No resultidx {resultidx}, only {len(succ)} results")
return None
res = succ[resultidx]
if name:
matches = res.matches4name(name)
if not matches:
if not silent_fail:
raise Exception(f"No match info with name {name} in result")
return None
if matchidx >= len(matches):
if not silent_fail:
raise Exception(f"No match info with index {matchidx}, length is {len(matches)}")
return None
ret = matches[matchidx].get("span")
else:
ret = res.span
if ret is None:
if silent_fail:
return None
else:
raise Exception("No span found")
return ret
class Actions:
"""
A container to run several actions for a rule.
"""
def __init__(self,
*actions,
):
"""
Wrap several actions for use in a rule.
Args:
*actions: any number of actions to run.
"""
self.actions = list(actions)
def __call__(self, succ, context=None, location=None):
"""
Invokes the actions defined for this wrapper in sequence and
returns one of the following: for no wrapped actions, no action is invoked and None is returned;
for exactly one action the return value of that action is returned, for 2 or more actions
a list with the return values of each of those actions is returned.
Args:
succ: the success object
context: the context
location: the location
Returns: None, action return value or list of action return values
"""
if len(self.actions) == 1:
return self.actions[0](succ, context=context, location=location)
elif len(self.actions) == 0:
return None
else:
ret = []
for action in self.actions:
ret.append(action(succ, context=context, location=location))
return ret
def add(self, action, tofront=False):
"""
Add an action to the list of existing actions.
Args:
action: the action to add
tofront: if True, add as first instead of last action
"""
if tofront:
self.actions.insert(0, action)
else:
self.actions.append(action)
class AddAnn:
"""
Action for adding an annotation.
"""
def __init__(
self,
name=None,
ann=None, # create a copy of this ann retrieved with GetAnn
type=None, # or create a new annotation with this type
annset=None, # if not none, create in this set instead of the one used for matching
features=None,
span=None, # use literal span, GetSpan, if none, span from match
resultidx=0,
matchidx=0,
silent_fail=False,
): # pylint: disable=W0622
"""
Create an action for adding a new annotation to the outset.
Args:
name: the name of the match to use for getting the annotation span, if None, use the
whole span of each match
ann: either an Annotation which will be (deep) copied to create the new annotation, or
a GetAnn helper for copying the annoation the helper returns. If this is specified the
other parameters for creating a new annotation are ignored.
type: the type of a new annotation to create
annset: if not None, create the new annotation in this set instead of the one used for matching
features: the features of a new annotation to create. This can be a GetFeatures helper for copying
the features from another annotation in the results
span: the span of the annotation, this can be a GetSpan helper for copying the span from another
annotation in the results
resultidx: the index of the result to use if more than one result is in the Success. If None,
the AddAnn action is performed for all results
matchidx: the index of the match info to use if more than one item matches the given name. If None,
the AddAnn action is performed for all match info items with that name.
silent_fail: if True and the annotation can not be created for some reason, just do silently nothing,
otherwise raises an Exception.
"""
# span is either a span, the index of a match info to take the span from, or a callable that will return the
# span at firing time
assert type is not None or ann is not None
self.name = name
self.anntype = type
self.ann = ann
self.features = features
self.span = span
self.resultidx = resultidx
self.matchidx = matchidx
self.silent_fail = silent_fail
self.annset = annset
# pylint: disable=R0912
def _add4span(self, span, succ, context, location):
if span is None:
return
if self.annset is not None:
outset = self.annset
else:
outset = context.outset
if self.ann:
if isinstance(self.ann, Annotation):
outset.add_ann(self.ann.deepcopy())
else:
ann = self.ann(succ)
if ann is None:
if self.silent_fail:
return
else:
raise Exception("No matching annotation found")
outset.add_ann(ann)
else:
if self.span:
if callable(self.span):
span = self.span(succ, context=context, location=location)
else:
span = self.span
if callable(self.anntype):
anntype = self.anntype(succ, context=context, location=location)
else:
anntype = self.anntype
if self.features:
if callable(self.features):
features = self.features(succ, context=context, location=location)
else:
# NOTE: if we got a dictionary where some values are helpers, we need to run the helper
# and replace the value with the result. However, this would change the original dictionary
# just the first time if there are several matches, so we always shallow copy the features
# first!
features = self.features.copy()
for k, v in features.items():
if isinstance(v, Getter):
features[k] = v(succ, context=context, location=location)
else:
features = None
outset.add(span.start, span.end, anntype, features=features)
def _add4result(self, succ, resultidx, context, location):
if self.matchidx is None:
for matchidx in range(len(succ[resultidx].matches)):
span = _get_span(succ, self.name, resultidx, matchidx, self.silent_fail)
# print(f"DEBUG: midx=None, running for {matchidx}, span={span}")
self._add4span(span, succ, context, location)
else:
span = _get_span(succ, self.name, resultidx, self.matchidx, self.silent_fail)
# print(f"DEBUG: running for {self.matchidx}, span={span}")
self._add4span(span, succ, context, location)
def __call__(self, succ, context=None, location=None):
if self.resultidx is None:
for resultidx in range(len(succ)):
# print(f"DEBUG: ridx=None, running for {resultidx}")
self._add4result(succ, resultidx, context, location)
else:
# print(f"DEBUG: running for {self.resultidx}")
self._add4result(succ, self.resultidx, context, location)
class UpdateAnnFeatures:
"""
Action for updating the features of an annotation.
"""
def __init__(
self,
name=None,
updateann=None,
fromann=None,
features=None,
replace=False, # replace existing features rather than updating
resultidx=0,
matchidx=0,
silent_fail=False,
deepcopy=False
):
"""
Create an UpdateAnnFeatures action. The features to use for updating can either come from
an existing annotation, an annotation fetched with a GetAnn annotation getter, or from a
a features instance, a feature getter or a dictionary.
Args:
name: the name of the match to use for getting the annotation to modify (if updateann is not
specified). This must be None if updateann is specified.
updateann: if specified, update the features of this annotation. This can be either a literal
annotation or a GetAnn help to access another annotation from the result.
fromann: if specified use the features from this annotation. This can be either a literal annotation
or a GetAnn helper to access another annotation from the result.
features: the features to use for updating, either literal features or dictionary,
or a GetFeatures helper.
replace: if True, replace the existing features with the new ones, otherwise update the existing features.
resultidx: the index of the result to use, if there is more than one (default: 0)
matchidx: the index of a matching info element to use, if more than one matches exist
with the given name (default: 0)
silent_fail: if True, do not raise an exception if the features cannot be updated
deepcopy: if True, existing features are deep-copied, otherwise a shallow copy or new instance
is created.
"""
# check parameters for getting the features:
if fromann is None and features is None:
raise Exception("Either fromann or features must be specified")
if fromann is not None and features is not None:
raise Exception("Parameters fromann and features must not be both specified at the same time")
# check parameters for setting features:
if name is None and updateann is None:
raise Exception("Either name or updateann must be specified")
if name is not None and updateann is not None:
raise Exception("Parameters name and updateann must not be both specified at the same time")
self.name = name
self.updateann = updateann
self.fromann = fromann
self.replace = replace
self.features = features
self.resultidx = resultidx
self.matchidx = matchidx
self.silent_fail = silent_fail
self.deepcopy = deepcopy
# pylint: disable=R0912
def __call__(self, succ, context=None, location=None):
# determine the annotation to modify
if self.updateann is not None:
if isinstance(self.updateann, Annotation):
updateann = self.updateann
else:
updateann = self.updateann(succ, context=context, location=location)
else:
match = _get_match(
succ, self.name, self.resultidx, self.matchidx, self.silent_fail
)
if not match:
if self.silent_fail:
return
else:
raise Exception(f"Could not find the name {self.name}")
updateann = match.get("ann")
if updateann is None:
if self.silent_fail:
return
else:
raise Exception(
f"Could not find an annotation for the name {self.name}"
)
updatefeats = updateann.features
# determine the features to use: either from an annotation/annotation getter or from
# features or a features getter
if self.fromann is not None:
if isinstance(self.fromann, Annotation):
fromfeats = self.fromann.features
else:
ann = self.fromann(succ)
if ann is None:
if self.silent_fail:
return
else:
raise Exception("No matching source annotation found")
fromfeats = ann.features
else: # get it from self.features
if callable(self.features):
fromfeats = self.features(succ, context=context, location=location)
else:
fromfeats = self.features
# make sure we have features and optionally make sure we have a deep copy
fromfeats = Features(fromfeats, deepcopy=self.deepcopy)
if self.replace:
updatefeats.clear()
updatefeats.update(fromfeats)
class RemoveAnn:
"""
Action for removing an anntoation.
"""
def __init__(self, name=None,
annset=None,
resultidx=0, matchidx=0,
silent_fail=True):
"""
Create a remove annoation action.
Args:
name: the name of a match from which to get the annotation to remove
annset: the annotation set to remove the annotation from. This must be a mutable set and
usually should be an attached set and has to be a set which contains the annotation
to be removed. Note that with complex patterns this may remove annotations which are
still being matched from the copy in the pampac context at a later time!
resultidx: index of the result to use, if several (default: 0)
matchidx: index of the match to use, if several (default: 0)
silent_fail: if True, silently ignore the error of no annotation to get removed
"""
assert name is not None
assert annset is not None
self.name = name
self.annset = annset
self.resultidx = resultidx
self.matchidx = matchidx
self.silent_fail = silent_fail
def __call__(self, succ, context=None, location=None):
match = _get_match(
succ, self.name, self.resultidx, self.matchidx, self.silent_fail
)
if not match:
if self.silent_fail:
return
else:
raise Exception(f"Could not find the name {self.name}")
theann = match.get("ann")
if theann is None:
if self.silent_fail:
return
else:
raise Exception(
f"Could not find an annotation for the name {self.name}"
)
self.annset.remove(theann)
Classes
class Actions (*actions)
-
A container to run several actions for a rule.
Wrap several actions for use in a rule.
Args
*actions
- any number of actions to run.
Expand source code
class Actions: """ A container to run several actions for a rule. """ def __init__(self, *actions, ): """ Wrap several actions for use in a rule. Args: *actions: any number of actions to run. """ self.actions = list(actions) def __call__(self, succ, context=None, location=None): """ Invokes the actions defined for this wrapper in sequence and returns one of the following: for no wrapped actions, no action is invoked and None is returned; for exactly one action the return value of that action is returned, for 2 or more actions a list with the return values of each of those actions is returned. Args: succ: the success object context: the context location: the location Returns: None, action return value or list of action return values """ if len(self.actions) == 1: return self.actions[0](succ, context=context, location=location) elif len(self.actions) == 0: return None else: ret = [] for action in self.actions: ret.append(action(succ, context=context, location=location)) return ret def add(self, action, tofront=False): """ Add an action to the list of existing actions. Args: action: the action to add tofront: if True, add as first instead of last action """ if tofront: self.actions.insert(0, action) else: self.actions.append(action)
Methods
def add(self, action, tofront=False)
-
Add an action to the list of existing actions.
Args
action
- the action to add
tofront
- if True, add as first instead of last action
Expand source code
def add(self, action, tofront=False): """ Add an action to the list of existing actions. Args: action: the action to add tofront: if True, add as first instead of last action """ if tofront: self.actions.insert(0, action) else: self.actions.append(action)
class AddAnn (name=None, ann=None, type=None, annset=None, features=None, span=None, resultidx=0, matchidx=0, silent_fail=False)
-
Action for adding an annotation.
Create an action for adding a new annotation to the outset.
Args
name
- the name of the match to use for getting the annotation span, if None, use the whole span of each match
ann
- either an Annotation which will be (deep) copied to create the new annotation, or a GetAnn helper for copying the annoation the helper returns. If this is specified the other parameters for creating a new annotation are ignored.
type
- the type of a new annotation to create
annset
- if not None, create the new annotation in this set instead of the one used for matching
features
- the features of a new annotation to create. This can be a GetFeatures helper for copying the features from another annotation in the results
span
- the span of the annotation, this can be a GetSpan helper for copying the span from another annotation in the results
resultidx
- the index of the result to use if more than one result is in the Success. If None, the AddAnn action is performed for all results
matchidx
- the index of the match info to use if more than one item matches the given name. If None, the AddAnn action is performed for all match info items with that name.
silent_fail
- if True and the annotation can not be created for some reason, just do silently nothing, otherwise raises an Exception.
Expand source code
class AddAnn: """ Action for adding an annotation. """ def __init__( self, name=None, ann=None, # create a copy of this ann retrieved with GetAnn type=None, # or create a new annotation with this type annset=None, # if not none, create in this set instead of the one used for matching features=None, span=None, # use literal span, GetSpan, if none, span from match resultidx=0, matchidx=0, silent_fail=False, ): # pylint: disable=W0622 """ Create an action for adding a new annotation to the outset. Args: name: the name of the match to use for getting the annotation span, if None, use the whole span of each match ann: either an Annotation which will be (deep) copied to create the new annotation, or a GetAnn helper for copying the annoation the helper returns. If this is specified the other parameters for creating a new annotation are ignored. type: the type of a new annotation to create annset: if not None, create the new annotation in this set instead of the one used for matching features: the features of a new annotation to create. This can be a GetFeatures helper for copying the features from another annotation in the results span: the span of the annotation, this can be a GetSpan helper for copying the span from another annotation in the results resultidx: the index of the result to use if more than one result is in the Success. If None, the AddAnn action is performed for all results matchidx: the index of the match info to use if more than one item matches the given name. If None, the AddAnn action is performed for all match info items with that name. silent_fail: if True and the annotation can not be created for some reason, just do silently nothing, otherwise raises an Exception. """ # span is either a span, the index of a match info to take the span from, or a callable that will return the # span at firing time assert type is not None or ann is not None self.name = name self.anntype = type self.ann = ann self.features = features self.span = span self.resultidx = resultidx self.matchidx = matchidx self.silent_fail = silent_fail self.annset = annset # pylint: disable=R0912 def _add4span(self, span, succ, context, location): if span is None: return if self.annset is not None: outset = self.annset else: outset = context.outset if self.ann: if isinstance(self.ann, Annotation): outset.add_ann(self.ann.deepcopy()) else: ann = self.ann(succ) if ann is None: if self.silent_fail: return else: raise Exception("No matching annotation found") outset.add_ann(ann) else: if self.span: if callable(self.span): span = self.span(succ, context=context, location=location) else: span = self.span if callable(self.anntype): anntype = self.anntype(succ, context=context, location=location) else: anntype = self.anntype if self.features: if callable(self.features): features = self.features(succ, context=context, location=location) else: # NOTE: if we got a dictionary where some values are helpers, we need to run the helper # and replace the value with the result. However, this would change the original dictionary # just the first time if there are several matches, so we always shallow copy the features # first! features = self.features.copy() for k, v in features.items(): if isinstance(v, Getter): features[k] = v(succ, context=context, location=location) else: features = None outset.add(span.start, span.end, anntype, features=features) def _add4result(self, succ, resultidx, context, location): if self.matchidx is None: for matchidx in range(len(succ[resultidx].matches)): span = _get_span(succ, self.name, resultidx, matchidx, self.silent_fail) # print(f"DEBUG: midx=None, running for {matchidx}, span={span}") self._add4span(span, succ, context, location) else: span = _get_span(succ, self.name, resultidx, self.matchidx, self.silent_fail) # print(f"DEBUG: running for {self.matchidx}, span={span}") self._add4span(span, succ, context, location) def __call__(self, succ, context=None, location=None): if self.resultidx is None: for resultidx in range(len(succ)): # print(f"DEBUG: ridx=None, running for {resultidx}") self._add4result(succ, resultidx, context, location) else: # print(f"DEBUG: running for {self.resultidx}") self._add4result(succ, self.resultidx, context, location)
class Getter
-
Common base class of all Getter helper classes.
Expand source code
class Getter(ABC): """ Common base class of all Getter helper classes. """ @abstractmethod def __call__(self, succ, context=None, location=None): pass
Ancestors
- abc.ABC
Subclasses
class RemoveAnn (name=None, annset=None, resultidx=0, matchidx=0, silent_fail=True)
-
Action for removing an anntoation.
Create a remove annoation action.
Args
name
- the name of a match from which to get the annotation to remove
annset
- the annotation set to remove the annotation from. This must be a mutable set and usually should be an attached set and has to be a set which contains the annotation to be removed. Note that with complex patterns this may remove annotations which are still being matched from the copy in the pampac context at a later time!
resultidx
- index of the result to use, if several (default: 0)
matchidx
- index of the match to use, if several (default: 0)
silent_fail
- if True, silently ignore the error of no annotation to get removed
Expand source code
class RemoveAnn: """ Action for removing an anntoation. """ def __init__(self, name=None, annset=None, resultidx=0, matchidx=0, silent_fail=True): """ Create a remove annoation action. Args: name: the name of a match from which to get the annotation to remove annset: the annotation set to remove the annotation from. This must be a mutable set and usually should be an attached set and has to be a set which contains the annotation to be removed. Note that with complex patterns this may remove annotations which are still being matched from the copy in the pampac context at a later time! resultidx: index of the result to use, if several (default: 0) matchidx: index of the match to use, if several (default: 0) silent_fail: if True, silently ignore the error of no annotation to get removed """ assert name is not None assert annset is not None self.name = name self.annset = annset self.resultidx = resultidx self.matchidx = matchidx self.silent_fail = silent_fail def __call__(self, succ, context=None, location=None): match = _get_match( succ, self.name, self.resultidx, self.matchidx, self.silent_fail ) if not match: if self.silent_fail: return else: raise Exception(f"Could not find the name {self.name}") theann = match.get("ann") if theann is None: if self.silent_fail: return else: raise Exception( f"Could not find an annotation for the name {self.name}" ) self.annset.remove(theann)
class UpdateAnnFeatures (name=None, updateann=None, fromann=None, features=None, replace=False, resultidx=0, matchidx=0, silent_fail=False, deepcopy=False)
-
Action for updating the features of an annotation.
Create an UpdateAnnFeatures action. The features to use for updating can either come from an existing annotation, an annotation fetched with a GetAnn annotation getter, or from a a features instance, a feature getter or a dictionary.
Args
name
- the name of the match to use for getting the annotation to modify (if updateann is not specified). This must be None if updateann is specified.
updateann
- if specified, update the features of this annotation. This can be either a literal annotation or a GetAnn help to access another annotation from the result.
fromann
- if specified use the features from this annotation. This can be either a literal annotation or a GetAnn helper to access another annotation from the result.
features
- the features to use for updating, either literal features or dictionary, or a GetFeatures helper.
replace
- if True, replace the existing features with the new ones, otherwise update the existing features.
resultidx
- the index of the result to use, if there is more than one (default: 0)
matchidx
- the index of a matching info element to use, if more than one matches exist with the given name (default: 0)
silent_fail
- if True, do not raise an exception if the features cannot be updated
deepcopy
- if True, existing features are deep-copied, otherwise a shallow copy or new instance is created.
Expand source code
class UpdateAnnFeatures: """ Action for updating the features of an annotation. """ def __init__( self, name=None, updateann=None, fromann=None, features=None, replace=False, # replace existing features rather than updating resultidx=0, matchidx=0, silent_fail=False, deepcopy=False ): """ Create an UpdateAnnFeatures action. The features to use for updating can either come from an existing annotation, an annotation fetched with a GetAnn annotation getter, or from a a features instance, a feature getter or a dictionary. Args: name: the name of the match to use for getting the annotation to modify (if updateann is not specified). This must be None if updateann is specified. updateann: if specified, update the features of this annotation. This can be either a literal annotation or a GetAnn help to access another annotation from the result. fromann: if specified use the features from this annotation. This can be either a literal annotation or a GetAnn helper to access another annotation from the result. features: the features to use for updating, either literal features or dictionary, or a GetFeatures helper. replace: if True, replace the existing features with the new ones, otherwise update the existing features. resultidx: the index of the result to use, if there is more than one (default: 0) matchidx: the index of a matching info element to use, if more than one matches exist with the given name (default: 0) silent_fail: if True, do not raise an exception if the features cannot be updated deepcopy: if True, existing features are deep-copied, otherwise a shallow copy or new instance is created. """ # check parameters for getting the features: if fromann is None and features is None: raise Exception("Either fromann or features must be specified") if fromann is not None and features is not None: raise Exception("Parameters fromann and features must not be both specified at the same time") # check parameters for setting features: if name is None and updateann is None: raise Exception("Either name or updateann must be specified") if name is not None and updateann is not None: raise Exception("Parameters name and updateann must not be both specified at the same time") self.name = name self.updateann = updateann self.fromann = fromann self.replace = replace self.features = features self.resultidx = resultidx self.matchidx = matchidx self.silent_fail = silent_fail self.deepcopy = deepcopy # pylint: disable=R0912 def __call__(self, succ, context=None, location=None): # determine the annotation to modify if self.updateann is not None: if isinstance(self.updateann, Annotation): updateann = self.updateann else: updateann = self.updateann(succ, context=context, location=location) else: match = _get_match( succ, self.name, self.resultidx, self.matchidx, self.silent_fail ) if not match: if self.silent_fail: return else: raise Exception(f"Could not find the name {self.name}") updateann = match.get("ann") if updateann is None: if self.silent_fail: return else: raise Exception( f"Could not find an annotation for the name {self.name}" ) updatefeats = updateann.features # determine the features to use: either from an annotation/annotation getter or from # features or a features getter if self.fromann is not None: if isinstance(self.fromann, Annotation): fromfeats = self.fromann.features else: ann = self.fromann(succ) if ann is None: if self.silent_fail: return else: raise Exception("No matching source annotation found") fromfeats = ann.features else: # get it from self.features if callable(self.features): fromfeats = self.features(succ, context=context, location=location) else: fromfeats = self.features # make sure we have features and optionally make sure we have a deep copy fromfeats = Features(fromfeats, deepcopy=self.deepcopy) if self.replace: updatefeats.clear() updatefeats.update(fromfeats)