Files @ 11552123765b
Branch filter:

Location: NPO-Accounting/conservancy_beancount/conservancy_beancount/config.py

Brett Smith
config: Add Config.cache_dir_path method.
"""User configuration for Conservancy bookkeeping tools"""
# 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 functools
import os
import urllib.parse as urlparse

import requests.auth
import rt

from pathlib import Path
from typing import (
    NamedTuple,
    Optional,
    Type,
)

from . import rtutil

class RTCredentials(NamedTuple):
    server: Optional[str] = None
    user: Optional[str] = None
    passwd: Optional[str] = None
    auth: Optional[str] = None

    @classmethod
    def from_env(cls) -> 'RTCredentials':
        values = dict(cls._field_defaults)
        for key in values:
            env_key = 'RT{}'.format(key.upper())
            try:
                values[key] = os.environ[env_key]
            except KeyError:
                pass
        return cls(**values)

    @classmethod
    def from_rtrc(cls) -> 'RTCredentials':
        values = dict(cls._field_defaults)
        rtrc_path = Path.home() / '.rtrc'
        try:
            with rtrc_path.open() as rtrc_file:
                for line in rtrc_file:
                    try:
                        key, value = line.split(None, 1)
                    except ValueError:
                        pass
                    else:
                        if key in values:
                            values[key] = value.rstrip('\n')
        except OSError:
            return cls()
        else:
            return cls(**values)


class Config:
    def _dir_or_none(self, path: Path) -> Optional[Path]:
        try:
            path.mkdir(exist_ok=True)
        except OSError:
            return None
        else:
            return path

    def cache_dir_path(self, name: str='conservancy_beancount') -> Optional[Path]:
        try:
            cache_root = Path(os.environ['XDG_CACHE_DIR'])
        except (KeyError, ValueError):
            cache_root = Path.home() / '.cache'
        return (
            self._dir_or_none(cache_root)
            and self._dir_or_none(cache_root / name)
        )

    def repository_path(self) -> Optional[Path]:
        try:
            return Path(os.environ['CONSERVANCY_REPOSITORY'])
        except (KeyError, ValueError):
            return None

    def rt_credentials(self) -> RTCredentials:
        all_creds = zip(
            RTCredentials.from_env(),
            RTCredentials.from_rtrc(),
            RTCredentials(auth='rt'),
        )
        return RTCredentials._make(v0 or v1 or v2 for v0, v1, v2 in all_creds)

    def rt_client(self,
                  credentials: RTCredentials=None,
                  client: Type[rt.Rt]=rt.Rt,
    ) -> Optional[rt.Rt]:
        if credentials is None:
            credentials = self.rt_credentials()
        if credentials.server is None:
            return None
        urlparts = urlparse.urlparse(credentials.server)
        rest_path = urlparts.path.rstrip('/') + '/REST/1.0/'
        url = urlparse.urlunparse(urlparts._replace(path=rest_path))
        if credentials.auth == 'basic':
            auth = requests.auth.HTTPBasicAuth(credentials.user, credentials.passwd)
            retval = client(url, http_auth=auth)
        else:
            retval = client(url, credentials.user, credentials.passwd)
        if retval.login():
            return retval
        else:
            return None

    @functools.lru_cache(4)
    def _rt_wrapper(self, credentials: RTCredentials, client: Type[rt.Rt]) -> Optional[rtutil.RT]:
        wrapper_client = self.rt_client(credentials, client)
        if wrapper_client is None:
            return None
        else:
            return rtutil.RT(wrapper_client)

    def rt_wrapper(self,
                  credentials: RTCredentials=None,
                  client: Type[rt.Rt]=rt.Rt,
    ) -> Optional[rtutil.RT]:
        if credentials is None:
            credentials = self.rt_credentials()
        return self._rt_wrapper(credentials, client)