conservancy_beancount/conservancy_beancount/errors.py
Brett Smith 7a4e1c52f3 errors: InvalidMetadataError stores its key and value.
This makes it easier to introspect than trying to parse the message,
which is meant primarily for human readers.
2020-03-31 11:50:27 -04:00

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