Files @ e6894c2b4693
Branch filter:

Location: NPO-Accounting/conservancy_beancount/conservancy_beancount/plugin/meta_project.py

Brett Smith
setup: Enable stricter type checking.

This caught the "return instead of raise" bug in meta_project.
"""meta_project - Validate project metadata"""
# 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/>.

from pathlib import Path

import yaml
import yaml.error

from . import core
from .. import config as configmod
from .. import data
from .. import errors as errormod
from ..beancount_types import (
    MetaValueEnum,
    Transaction,
)

from typing import (
    Any,
    Dict,
    Optional,
    Set,
)

class MetaProject(core._NormalizePostingMetadataHook):
    DEFAULT_PROJECT = 'Conservancy'
    PROJECT_DATA_PATH = Path('Projects', 'project-data.yml')
    VALUES_ENUM = core.MetadataEnum('project', {DEFAULT_PROJECT})

    def __init__(self, config: configmod.Config, source_path: Path=PROJECT_DATA_PATH) -> None:
        repo_path = config.repository_path()
        if repo_path is None:
            raise self._config_error("no repository configured")
        project_data_path = repo_path / source_path
        source = {'filename': str(project_data_path)}
        try:
            with project_data_path.open() as yaml_file:
                project_data: Dict[str, Dict[str, Any]] = yaml.safe_load(yaml_file)
            names: Set[MetaValueEnum] = {self.DEFAULT_PROJECT}
            aliases: Dict[MetaValueEnum, MetaValueEnum] = {}
            for key, params in project_data.items():
                name = params.get('accountName', key)
                names.add(name)
                human_name = params.get('humanName', name)
                if name != human_name:
                    aliases[human_name] = name
                if name != key:
                    aliases[key] = name
        except AttributeError:
            self._config_error("loaded YAML data not in project-data format", project_data_path)
        except OSError as error:
            self._config_error(error.strerror, project_data_path)
        except yaml.error.YAMLError as error:
            self._config_error(error.args[0] or "YAML load error", project_data_path)
        else:
            self.VALUES_ENUM = core.MetadataEnum(self.METADATA_KEY, names, aliases)

    def _config_error(self, msg: str, filename: Optional[Path]=None):
        source = {}
        if filename is not None:
            source['filename'] = str(filename)
        raise errormod.ConfigurationError(
            "cannot load project data: " + msg,
            source=source,
        )

    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
        return post.account.is_under('Assets', 'Liabilities') is None

    def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
        if post.account.is_under(
                'Accrued:VacationPayable',
                'Expenses:Payroll',
        ):
            return self.DEFAULT_PROJECT
        else:
            raise errormod.InvalidMetadataError(txn, self.METADATA_KEY, None, post)