root/releases/pkgcore/0.1.4/dev-notes/config.rst @ marienz%2540gentoo.org-20060913163754-b5e2ca73c1b17bac

Revision marienz%2540gentoo.org-20060913163754-b5e2ca73c1b17bac, 8.8 KB (checked in by Marien Zwart <marienz@…>, 2 years ago)

Move documentation from comments to a new dev-notes doc and expand it.

Line 
1=====================================
2 Config use and implementation notes
3=====================================
4
5Using the manager
6=================
7
8Normal use
9----------
10
11To get at the user's configuration::
12
13 from pkgcore.config import load_config
14 config = load_config()
15 main_repo = config.get_default('repo')
16 spork_repo = config.repo['spork']
17
18Usually this is everything you need to know about the manager. Some
19things to be aware of:
20
21- Some of the managed sources of configuration data may be slow, so
22  accessing a source is delayed for as long as possible. Some things
23  require accessing all sources though and should therefore be
24  avoided. The easiest one to trigger is config.repo.keys() or the
25  equivalent list(config.sections('repo')). This has to get the
26  "class" setting for every available config section, which might be
27  slow.
28- For the same reason the manager does not know what type names exist
29  (there is no hardcoded list of them, so the only way to get that
30  information would be loading all config sections). This is why you
31  can get this::
32
33   >>> load_config().section('repo') # typo, should be "sections"
34   Traceback (most recent call last):
35     File "<stdin>", line 1, in ?
36   TypeError: '_ConfigMapping' object is not callable
37
38  This constructed a dictlike object for accessing all config sections
39  of the type "section", then tried to call it.
40
41Testcase use
42------------
43
44For testing of high-level scripts it can be useful to construct a
45config manager containing hardcoded values::
46
47 from pkgcore.config import basics, central
48
49 config = central.ConfigManager([{
50     'repo' = basics.HardCodedConfigSection({'class': my_repo,
51                                             'data': ['1', '2']}),
52     'cont' = basics.ConfigSectionFromStringDict({'class': 'pkgcore.my.cont',
53                                                  'ref': 'repo'}),
54     }])
55
56What this does should be fairly obvious. Be careful you do not use the
57same ConfigSection object in more than one place: caching will not
58behave the way you want. See `Adding a config source`_ for details.
59
60Adding a configurable
61=====================
62
63You often do not really *have* to do anything to make something a
64valid "class" value, but it is clearer and it is necessary in certain
65cases.
66
67Adding a class
68--------------
69
70To make a class available, do this::
71
72 from pkgcore.config import ConfigHint, errors
73
74 class MyRepo(object):
75
76     pkgcore_config_type = ConfigHint({'cache': 'section_ref'},
77                                      typename='repo')
78
79     def __init__(self, repo):
80         try:
81             self.initialize(repo)
82         except SomeRandomException:
83             raise errors.InstantiationError('eep!')
84
85The first ConfigHint arg tells the config system what kind of
86arguments you take. Without it it assumes arguments with no default
87are strings and guesses for the other args based on the type of the
88default value. So if you have no default values or they are just None
89you should tell the system about your args.
90
91The second one tells it you fulfill the repo "protocol", meaning your
92instances will show up in load_config().repo.
93
94ConfigHint takes some more arguments, see the api docs for details.
95
96Adding a callable
97-----------------
98
99To make a callable available you can do this::
100
101 from pkgcore.config import configurable, errors
102
103 @configurable({'cache': 'section_ref'}, typename=repo)
104 def my_repo(repo):
105     # do stuff
106
107configurable is just a convenience function that applies a ConfigHint.
108
109Exception handling
110------------------
111
112If you raise an exception when the config system calls you it will
113catch the exception and wrap it in an InstantiationError. This is good
114for calling code since catching and printing those provides the user
115with a readable description of what happened. It is less good for
116developers since the raising of a new exception kills the traceback
117printed in debug mode. You will have a traceback that "ends" in the
118config code handling instantiation.
119
120You can improve this by raising an InstantiationError yourself. If you
121do this the config system will be able to add the extra information
122needed for a user-friendly error message to it without raising a new
123exception, meaning debug mode will give a traceback leading right back
124to your code raising the InstantiationError.
125
126Adding a config source
127======================
128
129Config sources are pretty straightforward: they are mappings from a
130section name to a ConfigSection subclass. The only tricky thing is the
131combination of section references and caching. The general rule is "do
132not expose the same ConfigSection in more than one way". If you do it
133will be collapsed and instantiated once for every way it is exposed,
134which is usually not what you want. An example::
135
136 from pkgcore.config import basics, configurable
137
138 def example():
139     return object()
140
141 @configurable({'ref': 'section_ref'})
142 def nested(ref):
143     return ref
144
145 multi = basics.HardCodedConfigSection({'class': example})
146
147 myconf = {
148     'multi': multi,
149     'bad': basics.HardCodedConfigSection({'class': nested, 'ref': multi})
150     'good': basics.ConfigSectionFromStringDict({'class': 'nested',
151                                                 'ref': 'multi'})
152
153If you feed this to the ConfigManager and instantiate everything
154"multi" and "good" will be identical but "bad" will be a different
155object. For an explanation of why this happens see the implementation
156notes in the next section.
157
158You trigger a similar problem if you create a custom ConfigSection
159subclass that bypasses central's collapse_named_section for named
160section refs. If you somehow get at the referenced ConfigSection and
161hand it to collapse_section you will most likely circumvent caching.
162Only use collapse_section for unnamed sections.
163
164ConfigManager tries not to extract more things from this mapping than
165it has to. Specifically, it will not call __getitem__ before it needs
166to instantiate the section or needs to know its type. However it
167*will* iterate over the keys (section names) immediately to find
168autoloads. If this is a problem (getting those names is slow) then
169make sure the manager knows your config is "remote".
170
171Implementation notes
172====================
173
174This code has evolved quite a bit over time. The current code/design
175tries among other things to:
176
177- Allow sections to contain both named and nameless/inline references
178  to other sections.
179- Allow serialization of the loaded config.
180- Not do unnecessary work (if possibly not recollapse configs,
181  definitely not trigger unnecessary imports, access configs
182  unnecessarily, reinstantiate configs)
183- Provide both end-user error messages that are complete enough to
184  track down a problem in a complex nested config and tracebacks that
185  reach back to actual buggy code for developers.
186
187The support for nameless sections means neither ConfigSection nor
188CollapsedConfig have a name attribute. This makes the error handling
189code a bit tricky as it has to tag in the name at various points, but
190this works better than enforcing names where it does not make sense
191(means lots of unnecessary duplication of names when dealing with
192dicts of HardCoded/StringBasedConfigSections).
193
194The suppport for serialization of the loaded config means section_refs
195cannot be instantiated straight away. The object used for
196serialization is the CollapsedConfig which gives you both the actual
197values and the type they have. If the CollapsedConfig contained
198arbitrary instantiated objects serializing them would be impossible.
199So it contains nested CollapsedConfigs instead.
200
201Not doing unnecessary work is done by caching in two places. The
202simple one is CollapsedConfig caching its instantiated value. This is
203pretty straightforward. The more subtle one is ConfigManager caching
204CollapsedConfigs by name. It is obviously a good idea to cache these
205(if we didn't we would have to cache the instantiated value in the
206ConfigManager). An alternative would be caching them by their
207ConfigSection. This has the minor disadvantage of keeping the
208ConfigSection in memory, and the larger one that it may break caching
209for weird config sources that generate ConfigSections on demand. The
210downside of caching by name is we have to make sure nothing generates
211a CollapsedConfig for a named section in a way other than
212collapse_named_section (handing the ConfigSection to collapse_section
213bypasses caching).
214
215This means a ConfigSection has to return a CollapsedConfig for a
216section_ref get_value call, not a ConfigSection. If it was a
217ConfigSection that central then collapsed and the reference was
218actually to a named section caching is bypassed.
219
220The need for a section name starting with "autoload" is also there to
221avoid unnecessary work. Without this we would have to figure out the
222typename of every section. While we can do that without entirely
223collapsing the config we cannot avoid importing the "class", which
224means load_config() would import most of pkgcore. That should
225definitely be avoided.
Note: See TracBrowser for help on using the browser.