7a4e1c52f3
This makes it easier to introspect than trying to parse the message, which is meant primarily for human readers.
138 lines
3.9 KiB
Python
138 lines
3.9 KiB
Python
"""Error classes for plugins to report problems in the books"""
|
|
# Copyright © 2020 Brett Smith
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import beancount.core.data as bc_data
|
|
|
|
from typing import (
|
|
Any,
|
|
Iterable,
|
|
Optional,
|
|
)
|
|
|
|
from .beancount_types import (
|
|
Directive,
|
|
MetaKey,
|
|
MetaValue,
|
|
Posting,
|
|
Transaction,
|
|
Type,
|
|
)
|
|
|
|
Meta = Optional[bc_data.Meta]
|
|
|
|
class Error(Exception):
|
|
def __init__(self,
|
|
message: str,
|
|
entry: Optional[Directive],
|
|
source: Meta=None,
|
|
) -> None:
|
|
self.message = message
|
|
self.entry = entry
|
|
if source:
|
|
self.source = source
|
|
elif entry is not None:
|
|
self.source = entry.meta
|
|
else:
|
|
self.source = {}
|
|
self._fill_source(self.source, '<unknown>')
|
|
|
|
def __repr__(self) -> str:
|
|
return "{clsname}<{source[filename]}:{source[lineno]}: {message}>".format(
|
|
clsname=type(self).__name__,
|
|
message=self.message,
|
|
source=self.source,
|
|
)
|
|
|
|
def _fill_source(self,
|
|
source: bc_data.Meta,
|
|
filename: str='conservancy_beancount',
|
|
lineno: int=0,
|
|
) -> None:
|
|
source.setdefault('filename', filename)
|
|
source.setdefault('lineno', lineno)
|
|
|
|
|
|
Iter = Iterable[Error]
|
|
|
|
class BrokenLinkError(Error):
|
|
def __init__(self,
|
|
txn: Transaction,
|
|
key: MetaKey,
|
|
link: str,
|
|
source: Meta=None,
|
|
) -> None:
|
|
super().__init__(
|
|
"{} not found in repository: {}".format(key, link),
|
|
txn,
|
|
source,
|
|
)
|
|
|
|
|
|
class BrokenRTLinkError(Error):
|
|
def __init__(self,
|
|
txn: Transaction,
|
|
key: MetaKey,
|
|
link: str,
|
|
parsed: Any=True,
|
|
source: Meta=None,
|
|
) -> None:
|
|
if parsed:
|
|
msg_fmt = "{} not found in RT: {}"
|
|
else:
|
|
msg_fmt = "{} link is malformed: {}"
|
|
super().__init__(
|
|
msg_fmt.format(key, link),
|
|
txn,
|
|
source,
|
|
)
|
|
|
|
|
|
class ConfigurationError(Error):
|
|
def __init__(self,
|
|
message: str,
|
|
entry: Optional[Directive]=None,
|
|
source: Meta=None,
|
|
) -> None:
|
|
if source is None:
|
|
source = {}
|
|
self._fill_source(source)
|
|
super().__init__(message, entry, source)
|
|
|
|
|
|
class InvalidMetadataError(Error):
|
|
def __init__(self,
|
|
txn: Transaction,
|
|
key: MetaKey,
|
|
value: Optional[MetaValue]=None,
|
|
post: Optional[bc_data.Posting]=None,
|
|
need_type: Type[object]=str,
|
|
source: Meta=None,
|
|
) -> None:
|
|
if post is None:
|
|
srcname = 'transaction'
|
|
else:
|
|
srcname = post.account
|
|
if value is None:
|
|
msg = "{} missing {}".format(srcname, key)
|
|
elif isinstance(value, need_type):
|
|
msg = "{} has invalid {}: {}".format(srcname, key, value)
|
|
else:
|
|
msg = "{} has wrong type of {}: expected {} but is a {}".format(
|
|
srcname, key, need_type.__name__, type(value).__name__,
|
|
)
|
|
super().__init__(msg, txn, source)
|
|
self.key = key
|
|
self.value = value
|