new module appconfig.py

This commit is contained in:
niphlod
2014-12-21 20:30:18 +01:00
parent 5ea654ed06
commit ce025a6b8e
2 changed files with 158 additions and 3 deletions
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Read from configuration files easily without hurting performances
USAGE:
During development you can load a config file either in .ini or .json
format (by default app/private/appconfig.ini or app/private/appconfig.json)
The result is a dict holding the configured values. Passing reload=True
is meant only for development: in production, leave reload to False and all
values will be cached
from gluon.contrib.appconfig import AppConfig
myconfig = AppConfig(path_to_configfile, reload=False)
print myconfig['db']['uri']
The returned dict can walk with "dot notation" an arbitrarely nested dict
print myconfig.take('db.uri')
You can even pass a cast function, i.e.
print myconfig.take('auth.expiration', cast=int)
Once the value has been fetched (and casted) it won't change until the process
is restarted (or reload=True is passed).
"""
import thread
import os
from ConfigParser import SafeConfigParser
from gluon import current
from gluon.serializers import json_parser
locker = thread.allocate_lock()
def AppConfig(*args, **vars):
locker.acquire()
reload_ = vars.pop('reload', False)
try:
instance_name = 'AppConfig_' + current.request.application
if reload_ or not hasattr(AppConfig, instance_name):
setattr(AppConfig, instance_name, AppConfigLoader(*args, **vars))
return getattr(AppConfig, instance_name).settings
finally:
locker.release()
class AppConfigDict(dict):
"""
dict that has a .take() method to fetch nested values and puts
them into cache
"""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.int_cache = {}
def take(self, path, cast=None):
parts = path.split('.')
if path in self.int_cache:
return self.int_cache[path]
value = self
walking = []
for part in parts:
if part not in value:
raise BaseException("%s not in config [%s]" %
(part, '-->'.join(walking)))
value = value[part]
walking.append(part)
if cast is None:
self.int_cache[path] = value
else:
try:
value = cast(value)
self.int_cache[path] = value
except (ValueError, TypeError):
raise BaseException("%s can't be converted to %s" %
(value, cast))
return value
class AppConfigLoader(object):
def __init__(self, configfile=None):
if not configfile:
priv_folder = os.path.join(current.request.folder, 'private')
configfile = os.path.join(priv_folder, 'appconfig.ini')
if not os.path.isfile(configfile):
configfile = os.path.join(priv_folder, 'appconfig.json')
if not os.path.isfile(configfile):
configfile = None
if not configfile or not os.path.isfile(configfile):
raise BaseException("Config file not found")
self.file = configfile
self.ctype = os.path.splitext(configfile)[1][1:]
self.settings = None
self.read_config()
def read_config_ini(self):
config = SafeConfigParser()
config.read(self.file)
settings = {}
for section in config.sections():
settings[section] = {}
for option in config.options(section):
settings[section][option] = config.get(section, option)
self.settings = AppConfigDict(settings)
def read_config_json(self):
with open(self.file, 'r') as c:
self.settings = AppConfigDict(json_parser.load(c))
def read_config(self):
if self.settings is None:
try:
getattr(self, 'read_config_' + self.ctype)()
except AttributeError:
raise BaseException("Unsupported config file format")
return self.settings
+35 -3
View File
@@ -4,14 +4,24 @@
""" Unit tests for contribs """
import unittest
import os
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.storage import Storage
import gluon.contrib.fpdf as fpdf
import gluon.contrib.pyfpdf as pyfpdf
from gluon.contrib.appconfig import AppConfig
from utils import md5_hash
import contrib.fpdf as fpdf
import contrib.pyfpdf as pyfpdf
def setUpModule():
pass
def tearDownModule():
if os.path.isfile('appconfig.json'):
os.unlink('appconfig.json')
class TestContribs(unittest.TestCase):
@@ -35,6 +45,28 @@ class TestContribs(unittest.TestCase):
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
self.assertTrue('hello world' in pdf_out, 'sample message')
def test_appconfig(self):
"""
Test for the appconfig module
"""
from gluon import current
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
current.request = s
simple_config = '{"config1" : "abc", "config2" : "bcd", "config3" : { "key1" : 1, "key2" : 2} }'
with open('appconfig.json', 'w') as g:
g.write(simple_config)
myappconfig = AppConfig('appconfig.json')
self.assertEqual(myappconfig['config1'], 'abc')
self.assertEqual(myappconfig['config2'], 'bcd')
self.assertEqual(myappconfig.take('config1'), 'abc')
self.assertEqual(myappconfig.take('config3.key1', cast=str), '1')
# once parsed, can't be casted to other types
self.assertEqual(myappconfig.take('config3.key1', cast=int), '1')
self.assertEqual(myappconfig.take('config3.key2'), 2)
current.request = {}
if __name__ == '__main__':
unittest.main()