Skip to content

Commit

Permalink
support python config file
Browse files Browse the repository at this point in the history
  • Loading branch information
Microndgt committed Apr 9, 2018
1 parent 5e123e9 commit 6ac0863
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 277 deletions.
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@ pip install git+https://github.com/Microndgt/pyconf.git

Usage
=====
Load a config file

ini config file
---

```python
import pyconf
c = pyconf.load('tests/sample.conf', config_class=pyconf.IniConfig)
print(c['path'])
# some_path
```

python config file
---

```python
import pyconf
c = pyconf.load('sample.conf')
print(c['general'])
# {'foo': 'baz'}
c = pyconf.load('tests/sample.py', config_class=pyconf.PyConfig)
print(c['path'])
# some_path
```

Tests
Expand All @@ -30,5 +42,21 @@ Tests
Run the tests with

```bash
python test_configs.py
python -m tests.test_ini_configs
python -m tests.test_py_configs
```

History
===

0.1.0
---

1. change the Architecture
2. support the python config file

0.0.1
---

1. init project
2. ini config parser done
6 changes: 4 additions & 2 deletions pyconf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
Load a config file::
>>> import pyconf
>>> c = pyconf.load('sample.conf')
>>> c = pyconf.load('tests/sample.conf')
>>> c['general']
{'foo': 'baz'}
"""

__title__ = 'pyconf'
__version__ = '0.0.1'
__version__ = '0.1.0'
__author__ = 'Kevin Du'
__license__ = 'MIT'

from .ini_config import IniConfig
from .py_config import PyConfig
from .base_config import BaseConfig, Section
from .api import load
20 changes: 10 additions & 10 deletions pyconf/api.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from .ini_config import IniConfig
from .py_config import PyConfig


config_parsers = {
'ini': IniConfig
}
support_configs = (IniConfig, PyConfig)


def load(config_file, ext="ini"):
def load(config_file, config_class=IniConfig):
"""Constructs and returns a :class:`Config <Config>` instance.
:param config_file: configuration file to be parsed
Expand All @@ -16,23 +15,24 @@ def load(config_file, ext="ini"):
>>> import pyconf
>>> fc = pyconf.load('sample.conf')
>>> fc = pyconf.load('tests/sample.conf')
>>> fc['general']['spam']
"""
# ini extensions
Config = config_parsers.get(ext, 'ini')
if config_class not in support_configs:
raise Exception("config class: {} not supported".format(config_class))
configs = {}
try:
configs = Config(config_file)
configs = config_class(config_file)
except FileNotFoundError:
raise
except Exception as e:
for extension, config_class in config_parsers.items():
if Config == config_class:
for extra_config_class in support_configs:
if config_class == extra_config_class:
continue
try:
configs = config_class(config_file)
configs = extra_config_class(config_file)
except FileNotFoundError:
raise
else:
Expand Down
216 changes: 216 additions & 0 deletions pyconf/base_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
class BaseConfig:
def __init__(self, config_file):
self.sections = {}
self._add_section('root')

self.load(config_file)

def load(self, config_file):
raise NotImplementedError

def _add_section(self, name, last_section=None):
"""Adds an empty section with the given name.
:param name: new section name.
"""
if last_section is None:
last_section = self.sections
last_section[name] = Section()
return last_section[name]

def get_config(self):
"""Gets all section items.
:returns: dictionary with section name as key and section values and value.
"""

return {section: self.sections[section].get_values() for section in self.sections}

def get(self, key):
"""Tries to get a value from the ``root`` section dict_props by the given key.
:param key: lookup key.
:returns: value (str, bool, int, or float) if key exists, None otherwise.
"""

if key in self.sections:
return self.sections[key]

return self['root'].get(key)

def _add_dict_prop_to_section(self, key, value, section=None, pure=False):
"""Adds a key-value item to the given section.
:param key: new item key.
:param value: new item value.
:param section: (optional) section name (``root`` by default).
"""
if section is None:
section = self.sections['root']
section._add_dict_prop(key, value, pure=pure)

def _add_list_prop_to_section(self, value, section=None):
"""Adds a flag value to the given section.
:param value: new item value.
:param section: (optional) section name (``root`` by default).
"""
if section is None:
section = self.sections['root']
section._add_list_prop(value)

def __repr__(self):
return str(self.get_config())

def __str__(self):
return str(self.get_config())

def __getitem__(self, key):
if key in self.sections:
return self.sections[key]
else:
try:
return self.sections['root'][key]
except KeyError:
pass

raise KeyError(key)

def __setitem__(self, key, value):
try:
self.sections['root'][key] = value
return None
except KeyError as e:
raise e

def __eq__(self, other):
return self.sections == other.sections


class Section:
"""INI configuration section.
A Section instance stores both key-value and flag items, in ``dict_props`` and ``list_props`` attributes respectively.
It is possible to iterate over a section; flag values are listed first, then key-value items.
"""

def __init__(self):
self.dict_props = {}
self.list_props = []

def get_values(self):
"""Gets section values.
If section contains only flag values, a list is returned.
If section contains only key-value items, a dictionary is returned.
If section contains both flag and key-value items, a tuple of both is returned.
"""

if self.list_props and self.dict_props:
return self.list_props, self.dict_props

return self.list_props or self.dict_props or None

def get(self, key):
"""Tries to get a value from the dict_props by the given key.
:param key: lookup key.
:returns: value if key exists (str, bool, int, or float), None otherwise.
"""

return self.dict_props.get(key)

def _get_value_type(self, value):
"""Checks if the given value is boolean, float, int, of str.
Returns converted value if conversion is possible (str, bool, int, or float).
:param value: value to check.
"""

value = value.strip()

if value == 'True':
return True
elif value == 'False':
return False
else:
try:
return_value = int(value)
except ValueError:
try:
return_value = float(value)
except ValueError:
return value

return return_value

def _add_dict_prop(self, key, value, pure=False):
"""Adds a key-value item to section."""
if pure:
self.dict_props[key] = value
return
typed_value_map = map(self._get_value_type, value.split(','))

typed_value_tuple = tuple(typed_value_map)

if len(typed_value_tuple) == 1:
self.dict_props[key] = typed_value_tuple[0]
else:
self.dict_props[key] = typed_value_tuple

def _add_list_prop(self, value):
"""Adds a flag value to section."""
typed_value_map = map(self._get_value_type, value.split(','))

typed_value_tuple = tuple(typed_value_map)

if len(typed_value_tuple) == 1:
self.list_props.append(typed_value_tuple[0])
else:
self.list_props.append(typed_value_tuple)

def __repr__(self):
return str(self.get_values())

def __str__(self):
return str(self.get_values())

def __iter__(self):
for list_prop in self.list_props:
yield list_prop

for dict_prop in self.dict_props:
yield dict_prop

def __getitem__(self, key):
try:
return self.dict_props[key]
except KeyError:
pass

try:
return self.list_props[key]
except (KeyError, TypeError):
pass

raise KeyError(key)

def __setitem__(self, key, value):
try:
self.dict_props[key] = value
return None
except KeyError:
pass

try:
self.list_props[key] = value
return None
except (KeyError, TypeError) as e:
raise e

def __eq__(self, other):
return self.dict_props == other.dict_props and self.list_props == other.list_props
Loading

0 comments on commit 6ac0863

Please sign in to comment.