~launchpad-pqm/lazr.config/devel

« back to all changes in this revision

Viewing changes to src/lazr/config/README.txt

  • Committer: Launchpad Patch Queue Manager
  • Date: 2009-01-06 04:55:57 UTC
  • mfrom: (1.2.11 megamerge)
  • Revision ID: launchpad@pqm.canonical.com-20090106045557-pdqc979lqoa7b8e3
[r=sinzui] Fixes for bugs 310619 309859 310782 and 309988. Also
        integrate Gary's licensing updates and bump the version number to 1.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
..
 
2
    This file is part of lazr.config.
 
3
 
 
4
    lazr.config is free software: you can redistribute it and/or modify it
 
5
    under the terms of the GNU Lesser General Public License as published by
 
6
    the Free Software Foundation, either version 3 of the License, or (at your
 
7
    option) any later version.
 
8
 
 
9
    lazr.config is distributed in the hope that it will be useful, but WITHOUT
 
10
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
11
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 
12
    License for more details.
 
13
 
 
14
    You should have received a copy of the GNU Lesser General Public License
 
15
    along with lazr.config.  If not, see <http://www.gnu.org/licenses/>.
 
16
 
1
17
LAZR config
2
18
***********
3
19
 
9
25
The config file supports inheritance to minimize duplication of
10
26
information across files. The format supports schema validation.
11
27
 
 
28
 
12
29
============
13
30
ConfigSchema
14
31
============
79
96
    >>> schema.filename
80
97
    '...lazr/config/testdata/base.conf'
81
98
 
 
99
If you provide an optional file-like object as a second argument to the
 
100
constructor, that is used instead of opening the named file implicitly.
 
101
 
 
102
    >>> file_object = open(base_conf)
 
103
    >>> other_schema = ConfigSchema('/does/not/exist.conf', file_object)
 
104
    >>> verifyObject(IConfigSchema, other_schema)
 
105
    True
 
106
 
 
107
    >>> print other_schema.name
 
108
    exist.conf
 
109
    >>> print other_schema.filename
 
110
    /does/not/exist.conf
 
111
 
 
112
    >>> file_object.close()
 
113
 
82
114
A schema is made up of multiple SchemaSections. They can be iterated
83
115
over in a loop as needed.
84
116
 
92
124
    section_3.app_b
93
125
    section_33
94
126
 
 
127
    >>> for section_schema in sorted(other_schema, key=attrgetter('name')):
 
128
    ...     print section_schema.name
 
129
    section-2.app-b
 
130
    section-5
 
131
    section_1
 
132
    section_3.app_a
 
133
    section_3.app_b
 
134
    section_33
 
135
 
95
136
You can check if the schema contains a section name, and that can be
96
137
used to access the SchemaSection as a subscript.
97
138
 
139
180
      ...
140
181
    NoCategoryError: ...
141
182
 
 
183
You can pass a default argument to getByCategory() to avoid the exception.
 
184
 
 
185
    >>> missing = object()
 
186
    >>> schema.getByCategory('non-section', missing) is missing
 
187
    True
 
188
 
 
189
 
142
190
=============
143
191
SchemaSection
144
192
=============
224
272
    key2 : changed
225
273
    key3 : unique
226
274
 
 
275
 
227
276
=======================
228
277
ConfigSchema validation
229
278
=======================
281
330
      ...
282
331
    InvalidSectionNameError: [$category.name_part.optional] ...
283
332
 
 
333
 
284
334
=============
285
335
IConfigLoader
286
336
=============
330
380
 
331
381
The bad_config example will be used for validation tests.
332
382
 
 
383
 
333
384
======
334
385
Config
335
386
======
368
419
    # Accept the default values for the optional section-5.
369
420
    [section-5]
370
421
 
 
422
The .master section allows admins to define configurations for an arbitrary
 
423
number of processes.  If the schema defines .master sections, then the conf
 
424
file can contain sections that extend the .master section.  These are like
 
425
categories with templates except that the section names extending .master need
 
426
not be named in the schema file.
 
427
 
 
428
    >>> master_schema_conf = path.join(testfiles_dir,  'master.conf')
 
429
    >>> master_local_conf = path.join(testfiles_dir,  'master-local.conf')
 
430
    >>> master_schema = ConfigSchema(master_schema_conf)
 
431
    >>> sections = master_schema.getByCategory('thing')
 
432
    >>> sorted(section.name for section in sections)
 
433
    ['thing.master']
 
434
    >>> master_conf = master_schema.load(master_local_conf)
 
435
    >>> sections = master_conf.getByCategory('thing')
 
436
    >>> sorted(section.name for section in sections)
 
437
    ['thing.one', 'thing.two']
 
438
    >>> sorted(section.foo for section in sections)
 
439
    ['1', '2']
 
440
    >>> print master_conf.thing.one.name
 
441
    thing.one
 
442
 
371
443
The shared.conf file derives the keys and default values from the
372
444
schema. This config was loaded before local.conf because its sections
373
445
and values are required to be in place before local.conf applies its
481
553
      ...
482
554
    NoCategoryError: ...
483
555
 
 
556
As with schemas, you can pass a default argument to getByCategory() to avoid
 
557
the exception.
 
558
 
 
559
    >>> missing = object()
 
560
    >>> config.getByCategory('non-section', missing) is missing
 
561
    True
 
562
 
 
563
 
484
564
=======
485
565
Section
486
566
=======
580
660
     ...
581
661
    AttributeError: Config options cannot be set directly.
582
662
 
 
663
 
583
664
==================
584
665
Validating configs
585
666
==================
608
689
    UnknownKeyError: The meta section does not have a metakey key.
609
690
    UnknownSectionError: base.conf does not have a unknown-section section.
610
691
 
 
692
 
611
693
===============
612
694
Config overlays
613
695
===============
656
738
    ...     print config_data.name
657
739
    base.conf
658
740
 
 
741
 
659
742
push()
660
743
======
661
744
 
774
857
    key4 : F&#028c;k yeah!
775
858
    key5 :
776
859
 
 
860
push() can also be used to extend master sections.
 
861
 
 
862
    >>> sections = sorted(master_conf.getByCategory('bar'),
 
863
    ...                   key=attrgetter('name'))
 
864
    >>> for section in sections:
 
865
    ...     print section.name, section.baz
 
866
    bar.master badger
 
867
    bar.soup cougar
 
868
 
 
869
    >>> master_conf.push('override', """
 
870
    ... [bar.two]
 
871
    ... baz: dolphin
 
872
    ... """)
 
873
    >>> sections = sorted(master_conf.getByCategory('bar'),
 
874
    ...                   key=attrgetter('name'))
 
875
    >>> for section in sections:
 
876
    ...     print section.name, section.baz
 
877
    bar.soup cougar
 
878
    bar.two dolphin
 
879
 
 
880
    >>> master_conf.push('overlord', """
 
881
    ... [bar.three]
 
882
    ... baz: emu
 
883
    ... """)
 
884
    >>> sections = sorted(master_conf.getByCategory('bar'),
 
885
    ...                   key=attrgetter('name'))
 
886
    >>> for section in sections:
 
887
    ...     print section.name, section.baz
 
888
    bar.soup cougar
 
889
    bar.three emu
 
890
    bar.two dolphin
 
891
 
 
892
push() works with master sections too.
 
893
 
 
894
    >>> schema_file = StringIO.StringIO("""\
 
895
    ... [thing.master]
 
896
    ... foo: 0
 
897
    ... bar: 0
 
898
    ... """)
 
899
    >>> push_schema = ConfigSchema('schema.cfg', schema_file)
 
900
 
 
901
    >>> config_file = StringIO.StringIO("""\
 
902
    ... [thing.one]
 
903
    ... foo: 1
 
904
    ... """)
 
905
    >>> push_config = push_schema.loadFile(config_file, 'config.cfg')
 
906
    >>> print push_config.thing.one.foo
 
907
    1
 
908
    >>> print push_config.thing.one.bar
 
909
    0
 
910
 
 
911
    >>> push_config.push('test.cfg', """\
 
912
    ... [thing.one]
 
913
    ... bar: 2
 
914
    ... """)
 
915
    >>> print push_config.thing.one.foo
 
916
    1
 
917
    >>> print push_config.thing.one.bar
 
918
    2
 
919
 
777
920
 
778
921
pop()
779
922
=====
839
982
    >>> print config.extends
840
983
    None
841
984
 
 
985
 
842
986
===============================
843
987
Attribute access to config data
844
988
===============================
896
1040
      ...
897
1041
    AttributeError: No section key named non_key.
898
1042
 
 
1043
 
899
1044
====================
900
1045
Implicit data typing
901
1046
====================
1040
1185
    >>> implicit_config['section_33'].key2
1041
1186
    'multiline value 1\nmultiline value 2'
1042
1187
 
 
1188
 
1043
1189
=======================
1044
1190
Type conversion helpers
1045
1191
=======================
1048
1194
functions have to be imported and called explicitly on the configuration
1049
1195
variable values.
1050
1196
 
 
1197
 
 
1198
Booleans
 
1199
========
 
1200
 
 
1201
There is a helper for turning various strings into the boolean values True and
 
1202
False.
 
1203
 
 
1204
    >>> from lazr.config import as_boolean
 
1205
 
 
1206
True values include (case-insensitively): true, yes, 1, on, enabled, and
 
1207
enable.
 
1208
 
 
1209
    >>> for value in ('true', 'yes', 'on', 'enable', 'enabled', '1'):
 
1210
    ...     print value, '->', as_boolean(value)
 
1211
    ...     print value.upper(), '->', as_boolean(value.upper())
 
1212
    true -> True
 
1213
    TRUE -> True
 
1214
    yes -> True
 
1215
    YES -> True
 
1216
    on -> True
 
1217
    ON -> True
 
1218
    enable -> True
 
1219
    ENABLE -> True
 
1220
    enabled -> True
 
1221
    ENABLED -> True
 
1222
    1 -> True
 
1223
    1 -> True
 
1224
 
 
1225
False values include (case-insensitively): false, no, 0, off, disabled, and
 
1226
disable.
 
1227
 
 
1228
    >>> for value in ('false', 'no', 'off', 'disable', 'disabled', '0'):
 
1229
    ...     print value, '->', as_boolean(value)
 
1230
    ...     print value.upper(), '->', as_boolean(value.upper())
 
1231
    false -> False
 
1232
    FALSE -> False
 
1233
    no -> False
 
1234
    NO -> False
 
1235
    off -> False
 
1236
    OFF -> False
 
1237
    disable -> False
 
1238
    DISABLE -> False
 
1239
    disabled -> False
 
1240
    DISABLED -> False
 
1241
    0 -> False
 
1242
    0 -> False
 
1243
 
 
1244
Anything else is a error.
 
1245
 
 
1246
    >>> as_boolean('cheese')
 
1247
    Traceback (most recent call last):
 
1248
    ...
 
1249
    ValueError: Invalid boolean value: cheese
 
1250
 
 
1251
 
1051
1252
Host and port
1052
1253
=============
1053
1254
 
1096
1297
    >>> as_host_port(':foo')
1097
1298
    Traceback (most recent call last):
1098
1299
    ...
1099
 
    ValueError: invalid literal for int(): foo
 
1300
    ValueError: invalid literal for int...foo...
 
1301
 
1100
1302
 
1101
1303
User and group
1102
1304
==============
1134
1336
    >>> group == grp.getgrgid(os.getgid()).gr_name
1135
1337
    True
1136
1338
 
 
1339
 
1137
1340
Time intervals
1138
1341
==============
1139
1342
 
1209
1412
    Traceback (most recent call last):
1210
1413
    ...
1211
1414
    ValueError
 
1415
 
 
1416
 
 
1417
Log levels
 
1418
==========
 
1419
 
 
1420
It's convenient to be able to use symbolic log level names when using
 
1421
lazr.config to configure the Python logger.
 
1422
 
 
1423
    >>> from lazr.config import as_log_level
 
1424
 
 
1425
Any symbolic log level value is valid to use, case insensitively.
 
1426
 
 
1427
    >>> for value in ('critical', 'error', 'warning', 'info',
 
1428
    ...               'debug', 'notset'):
 
1429
    ...     print value, '->', as_log_level(value)
 
1430
    ...     print value.upper(), '->', as_log_level(value.upper())
 
1431
    critical -> 50
 
1432
    CRITICAL -> 50
 
1433
    error -> 40
 
1434
    ERROR -> 40
 
1435
    warning -> 30
 
1436
    WARNING -> 30
 
1437
    info -> 20
 
1438
    INFO -> 20
 
1439
    debug -> 10
 
1440
    DEBUG -> 10
 
1441
    notset -> 0
 
1442
    NOTSET -> 0
 
1443
 
 
1444
Non-log levels cannot be used here.
 
1445
 
 
1446
    >>> as_log_level('cheese')
 
1447
    Traceback (most recent call last):
 
1448
    ...
 
1449
    AttributeError: 'module' object has no attribute 'CHEESE'