|
1 # Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net> |
|
2 # |
|
3 # This file is part of Iro. |
|
4 # |
|
5 # Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
6 # this software and associated documentation files (the "Software"), to deal in |
|
7 # the Software without restriction, including without limitation the rights to use, |
|
8 # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
|
9 # #Software, and to permit persons to whom the Software is furnished to do so, |
|
10 # subject to the following conditions: |
|
11 # |
|
12 # The above copyright notice and this permission notice shall be included in |
|
13 # all copies or substantial portions of the Software. |
|
14 # |
|
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
16 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
|
17 # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
20 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
21 |
|
22 from twisted.python import log |
|
23 |
|
24 from ConfigParser import ConfigParser |
|
25 import signal |
|
26 from functools import partial |
|
27 try: |
|
28 from collections import OrderedDict |
|
29 except ImportError: |
|
30 from ordereddict import OrderedDict |
|
31 |
|
32 from validate import vInteger |
|
33 from error import NeededOption |
|
34 |
|
35 class MyConfigParser(ConfigParser): |
|
36 """Configparser that also validate configfile. |
|
37 |
|
38 It is possile to restiger function, that are called, when config file is reloaded |
|
39 """ |
|
40 def __init__(self): |
|
41 ConfigParser.__init__(self) |
|
42 self.reloadList=[] |
|
43 |
|
44 def read(self,files): |
|
45 """reads an validate configuration file""" |
|
46 from offer import getProvider |
|
47 r = ConfigParser.read(self, files) |
|
48 for s in self.sections(): |
|
49 if s == "main": |
|
50 main.validate(self.items(s)) |
|
51 else: |
|
52 getProvider("tmp", self.get(s,'typ'), self.items(s)) |
|
53 return r |
|
54 |
|
55 def reload_(self): |
|
56 """run all registered function.""" |
|
57 for f in self.reloadList: |
|
58 f() |
|
59 |
|
60 def registerReload(self, func): |
|
61 """adds **func** to reloadList. |
|
62 |
|
63 func ist called with no arguments. |
|
64 """ |
|
65 self.reloadList.append(func) |
|
66 |
|
67 class Option(): |
|
68 """One Option in the configuration file""" |
|
69 def __init__(self, validate, long="", help="", must=False, default=None): |
|
70 """ |
|
71 :param func validate: a validate function, it has to return the value, if valid and raise an error if not. |
|
72 :param string long: long description |
|
73 :param string help: the help text |
|
74 :param boolean must: Is this option nessasary |
|
75 :param default: default value |
|
76 """ |
|
77 self.validate = validate |
|
78 self.long=long |
|
79 self.help = help |
|
80 self.must = must |
|
81 self.default = default |
|
82 |
|
83 class Config: |
|
84 """Base class for all classes, that uses option from configfile. |
|
85 |
|
86 If one option is valid, the attribute is created with the value of the validate function. |
|
87 """ |
|
88 def __init__(self, name, options=None): |
|
89 """ |
|
90 :param string name: section name. |
|
91 :param `collections.OrderedDict` options: Orderd Dict of the configuration options (see :attr:`options`) |
|
92 """ |
|
93 self.name = name |
|
94 |
|
95 self.options = options |
|
96 """Options :class:`collections.OrderedDict` for Options used in configuration file (see :class:`iro.config.Option`). Ordering of configuration fields are done by :attr:`order`. |
|
97 |
|
98 Sample:: |
|
99 |
|
100 OrderedDict([ |
|
101 ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), |
|
102 ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), |
|
103 ]) |
|
104 |
|
105 A child class typically use update to add more options. |
|
106 """ |
|
107 |
|
108 self._init = True |
|
109 """indecates, if the config is loaded with config values.""" |
|
110 |
|
111 |
|
112 def _read(self, cfg, write=False): |
|
113 """Test or set configuration options. |
|
114 |
|
115 :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. |
|
116 :param boolean write: test or set the option to actual object. |
|
117 |
|
118 :raises: :exc:`iro.error.NeededOption` |
|
119 """ |
|
120 c = dict(cfg) |
|
121 |
|
122 for o in self.options: |
|
123 option = self.options[o] |
|
124 try: |
|
125 value = option.validate(c[o],o) |
|
126 if write: |
|
127 self._init = False |
|
128 setattr(self,o,value) |
|
129 except KeyError: |
|
130 if option.must: |
|
131 raise NeededOption(self.name, o) |
|
132 elif write and option.default is not None: |
|
133 setattr(self, o, option.default) |
|
134 |
|
135 def validate(self, cfg): |
|
136 """Validate configuration. |
|
137 |
|
138 :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. |
|
139 """ |
|
140 self._read(cfg, False) |
|
141 |
|
142 def load(self, cfg): |
|
143 """Loads configuration into object. |
|
144 |
|
145 :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. |
|
146 """ |
|
147 self._read(cfg, True) |
|
148 |
|
149 def same(self, other): |
|
150 """returns ``True``, if the options of other object are the same""" |
|
151 for o in self.options: |
|
152 if getattr(self,o) != getattr(other,o): |
|
153 return False |
|
154 else: |
|
155 return True |
|
156 |
|
157 def sampleConf(self): |
|
158 """returns a sample Configuration section. |
|
159 |
|
160 This function also adds the long help text to the sample section. |
|
161 |
|
162 :return: a list of lines |
|
163 """ |
|
164 ret=[] |
|
165 for o in self.options: |
|
166 opt=self.options[o] |
|
167 if opt.long: |
|
168 ret.append("# "+opt.long) |
|
169 if opt.must: |
|
170 s= "%s = "%o |
|
171 if opt.default is not None: |
|
172 s += str(opt.default) |
|
173 ret.append(s) |
|
174 else: |
|
175 ret.append("# %s = %s"%(o,opt.default)) |
|
176 ret.append("") |
|
177 |
|
178 return ["[%s]"%self.name,]+ret |
|
179 |
|
180 def readConfig(): |
|
181 """Read the configuration and update all registered object (see :meth:`MyConfigParser.reload_`).""" |
|
182 log.msg("Reading configs.") |
|
183 configParser.read(confFiles) |
|
184 configParser.reload_() |
|
185 if main._init: |
|
186 main.load(configParser.items("main")) |
|
187 else: |
|
188 m = Config("main", main_options) |
|
189 m.load(configParser.items("main")) |
|
190 if not main.same(m): |
|
191 raise Exception("Main options can't be reloaded, please restart your Application.") |
|
192 |
|
193 def init(): |
|
194 """Load the main options.""" |
|
195 configParser.read(confFiles) |
|
196 main.load(configParser.items("main")) |
|
197 |
|
198 def registerSignal(): |
|
199 '''register readConfig to SIGUSR2''' |
|
200 def rC(signal, frame): |
|
201 readConfig() |
|
202 |
|
203 signal.signal(signal.SIGUSR2,rC) |
|
204 |
|
205 configParser = MyConfigParser() |
|
206 """configParser to get configuration.""" |
|
207 |
|
208 confFiles=["~/iro.conf","/etc/iro/iro.conf"] |
|
209 """Configfile list """ |
|
210 |
|
211 main_options = OrderedDict([ |
|
212 ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), |
|
213 ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), |
|
214 ]) |
|
215 |
|
216 "options for main section" |
|
217 |
|
218 main = Config("main", main_options) |
|
219 """Main config options""" |