new module appconfig.py
This commit is contained in:
@@ -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
|
||||||
@@ -4,14 +4,24 @@
|
|||||||
""" Unit tests for contribs """
|
""" Unit tests for contribs """
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import os
|
||||||
from fix_path import fix_sys_path
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
fix_sys_path(__file__)
|
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
|
def setUpModule():
|
||||||
import contrib.pyfpdf as pyfpdf
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule():
|
||||||
|
if os.path.isfile('appconfig.json'):
|
||||||
|
os.unlink('appconfig.json')
|
||||||
|
|
||||||
|
|
||||||
class TestContribs(unittest.TestCase):
|
class TestContribs(unittest.TestCase):
|
||||||
@@ -35,6 +45,28 @@ class TestContribs(unittest.TestCase):
|
|||||||
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
|
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
|
||||||
self.assertTrue('hello world' in pdf_out, 'sample message')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user