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 """
|
||||
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user