"""
Common code for DaCHS's base package.
"""
#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL. See the
#c COPYING file in the source distribution.
import enum
import warnings
from gavo import utils
from gavo.utils import NotGiven #noflake: backwards compatiblity import
from gavo.utils.excs import ( #noflake: exceptions are historically in base
Error,
BadCode, DataError, DBTableError, EmptyData, ExecutiveAction,
LiteralParseError,
MultiplicityError, NotFoundError, ParseException, RDNotFound,
ReportableError, RestrictedElement, SkipThis, StructureError,
SourceParseError, ValidationError)
from gavo.utils.dachstypes import (
Any, NoReturn, Optional,
Self, Structure, StructParseContext, StructParserValue, Type, Union)
[docs]class Ignore(ExecutiveAction):
"""An executive action causing an element to be not adopted by its
parent.
Raise this in -- typically -- onElementComplete if the element just
built goes somewhere else but into its parent structure (or is
somehow benignly unusable). Classic use case: Active Tags.
"""
[docs]class Replace(ExecutiveAction):
"""An executive action replacing the current child with the Exception's
argument.
Use this sparingly. I'd like to get rid of it.
"""
def __init__(self,
newOb: "Structure",
newName: Optional[str] = None) -> None:
self.newOb, self.newName = newOb, newName
# the tree event types for our struct parsing
ParserEventType = enum.StrEnum("ParserEventName", ["start", "end", "value"])
[docs]class Parser:
"""A mixin for a structure you can feed events to.
The class mixing thing in must define methods ``start_``, ``end_``,
and ``value_``, each accepting a parse context (a base.RDParseContext
instance), the name of an element or attribute, and a value
(which is an attribute of dicts in the case of start, and a
string in the case of value.
They all must return either None or the next parser; start_ may also
return a string, which then names the attribute the next value is for.
"""
def __init__(self) -> NoReturn:
raise StructureError("Parser is now a pure mixin")
[docs] def feedEvent(self,
ctx: StructParseContext,
type: ParserEventType,
name: str,
value: StructParserValue) -> Optional[Union["Parser", str]]:
if type=="start":
return self.start_(ctx, name, value)
elif type=="value":
return self.value_(ctx, name, value)
elif type=="end":
return self.end_(ctx, name, value)
else:
raise StructureError(
"Illegal event type while building: '%s'"%type)
[docs] def start_(self,
ctx: StructParseContext,
name: str,
value: StructParserValue) -> Optional[Union["Parser", str]]:
pass
[docs] def end_(self,
ctx: StructParseContext,
name: str,
value: StructParserValue) -> Optional["Parser"]:
pass
[docs] def value_(self,
ctx: StructParseContext,
name: str,
value: StructParserValue) -> Optional["Parser"]:
pass
[docs]class ParserEvent(tuple):
"""a tuple consists of an event type (a ParserEventType)
the name and value from common.Parser, and the position object.
We probably should use a namedtuple here one day and actually
check we get what we need, but for now, this is only to acquiesce
the type checker.
"""
def __new__(cls, *args):
return super().__new__(cls, args)
[docs]class StructParseDebugMixin:
"""put this before Parser in the parent class list of a struct,
and you'll see the events coming in to your parser.
"""
[docs] def feedEvent(self,
ctx: StructParseContext,
type: ParserEventType,
name: str,
value: StructParserValue) -> Optional[Self]:
print(type, name, value, self)
return super().feedEvent( # type: ignore
self, ctx, type, name, value)
[docs]class StructCallbacks:
"""A class terminating super() calls for the structure callbacks.
Structs, when finished, call completeElement(ctx), validate(),
and onElementComplete() in sequence. All these need to up-call.
This mixin provides non-upcalling implementations of these methods,
and it needs to be present in mixins for structure.
This will only work properly if self actually is a struct, as we rely
on the presence of name_ attributes in case of prematurely terminated
method resolution.
"""
name_: Union[str, Type[utils.Undefined]] = "(StructCallbacks terminator)"
def __warnTermination(self, methname: str) -> None:
if hasattr(super(), methname):
mro = " ".join(c.__name__ for c in self.__class__.__mro__)
warnings.warn(f"Discarding {methname} method while calling up"
f" from {self.name_}, mro {mro} ")
[docs] def validate(self) -> None:
self.__warnTermination("validate")
[docs] def completeElement(self, ctx: StructParseContext) -> None:
self.__warnTermination("completeElement")
[docs] def onElementComplete(self) -> None:
self.__warnTermination("onElementComplete")
[docs] def setParent(self, parent: Any) -> None:
self.__warnTermination("setParent")