''' Configure file support for irfpy library.
.. codeauthor:: Yoshifumi Futaana
.. versionchanged:: 4.4.0
The priority order of the setup file in :class:`Rc` has been changed.
Configure files are used to make irfpy library portable.
For example, consider cases you want to access a local file,
for example, a data file.
The location of the data file may differ user by user, or even computer by computer.
Thus, the location is *not* recommended to be hard-coded into any library code.
Instead, a configuration file should be used.
See more detailed example in :ref:`recipe_irfpyrc`.
.. autosummary::
Rc
'''
import os
import configparser
import warnings
import irfpy.util.exception as ex
import logging
_logger = logging.getLogger(__name__)
class _SingleRcFile:
''' Class treating individual configure file.
Class treating individual configure file.
From v1.8, the use of RcFile explicitly is **not** recommended, instead,
:class:`Rc` class is recommended.
:class:`Rc` handles multiple configure file at once.
'''
def __init__(self, name=None):
''' Open a configure file.
:param name: Name of the RC file. Absolute path.
'''
if name is None:
name = os.path.join(os.getenv('HOME'), '.pyanarc')
_logger.debug('Config file name = %s' % name)
self.config = configparser.ConfigParser()
self.name = os.path.abspath(name)
if os.path.exists(name):
_logger.info('Config file %s loaded.' % name)
self.config.read(name)
else:
_logger.warning('Config file %s not found. Using default values.' % name)
def get(self, subproject, parameter):
''' Get a value of the specified parameter of particular subproject.
'''
try:
val = self.config.get(subproject, parameter)
except (configparser.NoOptionError, configparser.NoSectionError) as e:
val = None
return val
def __str__(self):
return self.name
def __eq__(self, other):
return self.name == other.name
_template = '\n'.join(['',
'=' * 80,
'irfpyrc: No entry ``{parameter}`` in ``{subsection}`` section is found.',
'Create a file .irfpyrc at the current directory or home directory with following entry:',
'-' * 80,
'[{subsection}]',
'{parameter} = {default_value}',
'-' * 80,
'See https://irfpy.irf.se/projects/util/cookbook/recipe_irfpyrc.html#recipe-irfpyrc',
'for configure file.',
'=' * 80,
])
''' A template string for warning or exceptioin '''
[docs]class Rc:
''' Rc class represents the configuration files.
Library developer may instance this class to use the configuration system
for ``irfpy``.
By default, the following files are loaded in the priority order.
.. versionchanged:: 4.4.0
The priority order is changed.
At the current directory:
- ``./.irfpyrc`` -- Recommended for Mac/Linux users
- ``./irfpy.ini`` -- Recommended for Windows/Mac/Linux users
- ``./irfpy.rc``
- ``./.pyanarc`` -- Not recommended. Will be deprecated.
At the home directory (for Mac/Linux):
- ``${HOME}/.irfpyrc`` -- Recommended for Mac/Linux users
- ``${HOME}/irfpy.ini`` -- Recommended for Mac/Linux users
- ``${HOME}/irfpy.rc``
- ``${HOME}/.pyanarc`` -- Not recommended. Will be deprecated.
At the home directory (for Windows):
- ``%USERPROFILE%/.irfpyrc``
- ``%USERPROFILE%/irfpy.ini`` -- Recommended for Windows users
- ``%USERPROFILE%/irfpy.rc``
- ``%USERPROFILE%/.pyanarc`` -- Not recommended. Will be deprecated.
Here ``${HOME}`` represents the home folder for Mac/Linux
(e.g. ``/home/username`` or ``/Users/username``)
and ``%USERPROFILE%`` is the home directory for Windows
(e.g. ``/Users/username``).
The usual exercise of using the configure files are:
>>> rc = Rc()
>>> rc.get('subproject', 'option')
If no entry is found for all the configure files, None is returned.
Instead, ou can specify the ``default`` keyword; then the returned will be
the default if there is no relevant entry.
>>> prm = rc.get('subproject', 'option', default="https://irfpy.irf.se/")
>>> print(prm)
https://irfpy.irf.se/
If you want to add a file into the configure list, you can use
:meth:`append` method. In this case, the specified file is added
as the most prioritized configure file.
>>> if os.path.exists('rcfile/pyanarc.special'):
... rc.append('rcfile/pyanarc.special')
If you do not want to load the configure file, you may use
:meth:`erase` method. This will clear the loaded configure files.
>>> rc.erase()
>>> print(len(rc))
0
'''
def __init__(self, load_default=True, raises=False):
"""
:keyword raises: Raises exception if the entry is not found.
:keyword load_default: Load default files. If *False*,
no files are loaded. You may use :meth:`append` method
to load manually.
"""
self.rclist = []
# Initialize default file names in a priority order.
default_names = ['.irfpyrc',
'irfpy.ini',
'irfpy.rc',
'.pyanarc'
]
default_paths = ['./']
home = os.getenv('HOME')
if home is not None:
default_paths.append(home)
home = os.getenv('USERPROFILE')
if home is not None:
default_paths.append(home)
defaults = []
for dp in default_paths:
for dn in default_names:
defaults.append(os.path.join(dp, dn))
for rcfilename in defaults:
if os.path.exists(rcfilename):
rc = self.append(rcfilename, first=False) # Append the RC file in the end
self._raises = raises
[docs] def append(self, filename, first=True):
''' Append the specified file name to the configure file list
Append the specified file name to the configure file list.
:param filename: Configure file name.
:keyword first: If *True* (default), the configure file is added to the top of the list.
If *False*, the configure file is added to the end of the list.
Usually, user's configure file should be added to the top.
:returns: A configure file object.
:raises IOError: When the configure file is not found.
'''
fname = os.path.expandvars(filename)
if os.path.exists(fname):
rc = _SingleRcFile(fname)
# If _SingleRcFile object is in the list
if rc in self.rclist:
self.rclist.remove(rc)
# Append the given file.
if first:
self.rclist.insert(0, rc)
else:
self.rclist.append(rc)
# Append the files in "include" section of the given file.
self._include(rc)
else:
raise IOError('!!!! File %s not found.' % fname)
def _include(self, rc):
""" Append files in ``include`` entry to the configure file list.
:param rc: RC file object.
:type rc: :class:`_SingleRcFile`
:return: None
"""
include_files = rc.get('DEFAULT', 'include')
if include_files is None:
return
include_files = include_files.split()
for include_file in include_files:
include_file = include_file.strip()
self.append(include_file, first=False)
[docs] def erase(self):
''' Erase the existing configure file list.
'''
self.rclist = []
[docs] def get(self, subproject, parameter, error_sample="<relevant_value>", default=None):
''' Get the value from the configuration files.
:param subproject: Subsection of RC file.
:param parameter: Parameter in the subsection of RC file.
:keyword error_sample: Sample string for displaying in the warning.
:keyword default: The default return value, in case the ``subproject`` and ``parameter`` is not in the list.
'''
for rc in self.rclist:
val = rc.get(subproject, parameter)
if val is not None:
return val
if default:
return default
if self._raises:
raise NoEntryFoundError(subproject, parameter)
else:
_logger.warning(_template.format(subsection=subproject, parameter=parameter, default_value=error_sample))
return None
def __str__(self):
s = '<Rc with %d RcFile instance(s):\n' % len(self.rclist)
for f in self.rclist:
s += " " + str(f) + "\n"
s += ">\n"
return s
def __len__(self):
return len(self.rclist)
[docs]class NoEntryFoundError(ex.IrfpyError):
def __init__(self, subproject, parameter, error_sample='<relevant_value>'):
warnings.warn(_template.format(subsection=subproject, parameter=parameter, default_value=error_sample))
self.value = "No entry ``{parameter}`` in the ``{subsection}`` section.".format(parameter=parameter, subsection=subproject)