Source code for config

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Maintain config file for cmiputil package.

Config file format for this package is so called 'INI file', handled
by python standard `configparser module`_, so you can use
`interpolation of values`_.

Default name of config file is ``cmiputil.conf`` (set as
:attr:`conf_name`), and is searched in the order below:

1. specified by the argument of :class:`Conf()`
2. specified by the environment variable `CMIPUTIL_CONFFILE`
3. ``$CMIPUTIL_CONFDIR/cmiputil.conf``
4. ``$cwd/cmiputil.conf``
5. ``$HOME/cmiputil.conf``

Once found, the rest is skipped.

If `file` given to the :class:`Conf()` is ``None``, no config file is
read and *blank* instance is created.  If you want to read the default
config file only, leave ``file=""`` as a default.

The name of "Common" section, which any module in this package may
access, is :attr:`common_sect_name` and the key=value pair is
:attr:`common_config`.

You can create sample(default) config file by :mod:`createSampleConf`
program, that collects default configuration of each module by
``getDefaultConf()`` method defined in each module(class) in this
package, and write them to the file by `Conf.read_dict()`_ and
:meth:`Conf.writeConf`.

.. _configparser module:
   https://docs.python.org/3/library/configparser.html

.. _interpolation of values:
   https://docs.python.org/3/library/configparser.html#interpolation-of-values

.. _configparser.ConfigParser:
   https://docs.python.org/3/library/configparser.html#configparser-objects

.. _Conf.read_dict():
   https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.read_dict

"""
__author__ = 'T.Inoue'
__credits__ = 'Copyright (c) 2019 RIST'
__version__ = 'v20190905'
__date__ = '2019/09/05'

import os
import configparser
from pathlib import Path
# from pprint import pprint

conf_dir = ['~/', './', ]
"""Directory list for searching a config file.

Overriden by an environment variable `CMIPUTIL_CONFDIR` if set.
The order is important since the latter searched first.
"""
if os.getenv('CMIPUTIL_CONFDIR'):
    conf_dir = os.getenv('CMIPUTIL_CONFDIR').split(':')

#: Name of the config file.
conf_name = 'cmiputil.conf'

#: Name of the 'common' section.
common_sect_name = 'cmiputil'

#: Default configuration of the 'common' section.
common_config = {'cmip6_data_dir': '/data'}


[docs]class Conf(configparser.ConfigParser): """ Config parser for this package. If `file` is given, this must be a string or a path-like object, specifying config file to be read first (See above). See `configparser.ConfigParser`_ for other methods and details. Attributes: file (Path): config file had read in. commonSection (SectionProxy): "Common" section for this package. """ _debug = False @classmethod def _enable_debug(cls): cls._debug = True @classmethod def _disable_debug(cls): cls._debug = True # @property # def debug(cls): # return cls._debug def __init__(self, file=""): super().__init__() self.file = file if file is None: files = "" self.read(files) if (self._debug): print("dbg:no config file read") else: files = [Path(d).expanduser()/Path(conf_name) for d in conf_dir] if os.getenv('CMIPUTIL_CONFFILE'): files.append(Path(os.getenv('CMIPUTIL_CONFFILE'))) if file: files.append(Path(file)) for f in files[::-1]: try: fp = open(f) except FileNotFoundError: continue self.read_file(fp) self.file = f fp.close() break if (self._debug): print(f"dbg:read config file: {self.file}") if self.has_section(common_sect_name): self.commonSection = self[common_sect_name]
[docs] def setCommonSection(self): """ Set default "common" section. Warning: Do not call this after reading real config file. """ self[common_sect_name] = common_config
[docs] def writeConf(self, file, overwrite=False): """ Write current attributes to the `file`, this must be a string or a path-like. If given `file` exists, :exc:`FileExistsError` is raised unless `overwrite` is ``True``. Do not forget to call :meth:`.setCommonSection()` to include common section to write. Examples: >>> from cmiputil import config, esgfsearch >>> conf = config.Conf(None) >>> conf.setCommonSection() >>> d = esgfsearch.getDefaultConf() >>> conf.read_dict(d) >>> conf.writeConf('/tmp/cmiputil.conf', overwrite=True) After above example, ``/tmp/cmiputil.conf`` is as below:: [cmiputil] cmip6_data_dir = /data [ESGFSearch] search_service = http://esgf-node.llnl.gov/esg-search/ aggregate = True [ESGFSearch.keywords] replica = false latest = true [ESGFSearch.facets] table_id = Amon """ if ((not Path(file).is_file()) or overwrite): with open(file, 'w') as f: self.write(f) else: raise FileExistsError(f'file already exists: "{file}"')
def __str__(self): res = '' for sec in self.sections(): res += f'[{sec}]\n' for op in self.options(sec): res += f'{op} = {self[sec][op]}\n' res += '\n' return res
if __name__ == '__main__': import doctest doctest.testmod()