oslo.config-1.2.1/0000775000175300017540000000000012221001336015056 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/ChangeLog0000664000175300017540000026653512221001336016651 0ustar jenkinsjenkins00000000000000commit eae8b3108d2c794a373660633042f3c0e3a23546 Merge: 3d59667 bf70519 Author: Jenkins Date: Thu Sep 26 09:05:00 2013 +0000 Merge "Fix subparsers add_parser() regression" commit bf705193595d34a70abefc4b3867ea550c0c15db Author: Mark McLoughlin Date: Wed Sep 25 20:07:03 2013 +0100 Fix subparsers add_parser() regression With SubCommandOpt, the handler() callback gets invoked with a subparsers object which has a add_parser() method. The arguments to this method match the arguments to the ArgumentParser constructor and the arguments are passed to the class for our ArgumentParser instance. However, we added a ArgumentParser subclass (_CachedArgumentParser) so that we could sort the output of --help ... but the subclass doesn't support all the arguments of the parent class. glance-control is the only known example of a SubCommandOpt user which passes unusual arguments to subparsers.add_parser(). It uses the parent arg and blows up at startup with: File "./bin/glance-control", line 276, in add_command_parsers parser = subparsers.add_parser(server, parents=[cmd_parser]) File "/usr/lib64/python2.7/argparse.py", line 1064, in add_parser parser = self._parser_class(**kwargs) TypeError: __init__() got an unexpected keyword argument 'parents' It appears we broke glance-control back in June (commit 2951391), but nobody noticed it until 1.2.0 was released and broke stable/grizzly because we stopped using glance-control in the Havana glance unit tests before this regression. Closes-Bug: #1230416 Change-Id: I8d1b52e5390295726eb49af32eb7cab14c29592c commit 3d59667b781650039f5ba2c454dff39d860f879e Author: Ian Wienand Date: Wed Sep 25 14:31:06 2013 +1000 Expand DeprecatedOpt documentation It is not a priori obvious how deprecated options are chosen, so expand the docstring a bit Change-Id: If10b45fb042b4a4d6063129b693228fd6ca86b82 commit 9dabbd0ff744ae9fde993861aaeae576c2e19597 Author: Mark McLoughlin Date: Mon Sep 23 15:55:30 2013 +0100 Release notes for 1.2.0 Change-Id: Ic9c36f9d2b2ecc2d8d76ca87079e75f83c7f08c1 commit 7ec9a88fc451386c23e472036849385da038f4fa Author: Zhongyue Luo Date: Thu Sep 12 21:23:36 2013 +0800 Raises error if duplicate keys found in DictOpt DictOpt didn't report when the input values had duplicate keys. This would be a problem if the user had put in duplicate keys in the conf file and the service didn't work as expected. (c.f) Neutrons provider network mapping options This patch raises a ConfigFileValueError when duplicate keys are found. Fixes bug #1228995 Change-Id: I91e1bd0eda4ed4fd6c7eb6ea5d4a89d583d1305e commit f2be4e83f04579ee0cd87e8601868f310c951fac Author: Zhongyue Luo Date: Wed Sep 11 13:11:17 2013 +0800 Fix DictOpt to split only the first colon I've encountered a ValueError when adapting DictOpt to neutron's network_vlan_ranges. https://github.com/openstack/neutron/blob/master/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini#L23 This patch makes dictopt to split only the first colon for every value Fixes bug #1223667 Change-Id: Iee4011f2a45be933dad393c72df099d6a8116849 commit 0b915fd91f40c968aa507ba3c90ae43071646a5f Merge: a0905c8 58aa8f1 Author: Jenkins Date: Tue Aug 27 02:53:18 2013 +0000 Merge "Update requirements" commit 58aa8f12a357ca7ef8d78d2b00ae03057103f8d3 Author: Julien Danjou Date: Fri Aug 23 11:06:04 2013 +0200 Update requirements This is the fabulous result of running the update.py script from openstack/requirements with this repository as the target. It therefore updates all dependencies, with nice version numbers that this repository have to use to work. It also fixes case on some dependencies. This update also brings a brand shiny new setup.py automatically handled and generate by this same openstack/requirements repository. Change-Id: I22ec396e5fa802c1617514aa6df3ed82f05271b0 commit a0905c875ba19f52c72822fa2cb85598d7099f95 Author: Mark McLoughlin Date: Fri Aug 23 16:06:23 2013 +0100 Release notes for 1.2.0a4 Change-Id: Iff43254317071b863d1ccd50eef4d71fb85240a7 commit 662cfb09c0ea62e642077f80d74056b31dba94d1 Author: Mark McLoughlin Date: Sun Aug 25 12:46:48 2013 +0100 Remove explicit version from setup.cfg With the explicit "pre version" in setup.cfg, PBR generates our version as 1.2.0.a1.g2748574 and, without it, it generates 1.2.0a4.1.g2748574. which in turn causes nova to complain about: VersionConflict: (oslo.config 1.2.0.a1.g2748574 (/opt/stack/new/oslo.config), Requirement.parse('oslo.config>=1.2.0a3')) when trying to load extensions with stevedore. I'm not sure why this is suddenly an issue after 1.2.0a4 has been tagged, but removing this version is the right thing to do anyway since we want to rely on tags for all versioning info. Change-Id: I9090b8401a43befd73a82f9bb78e0b6165619c7c commit 1cec054637b95fe394bbc57642806d41dd28ed8b Author: Davanum Srinivas Date: Sun Aug 18 19:52:33 2013 -0400 Fix first param in __eq__ impl to be self first parameter of a method should be named 'self'. Added a test case as well Change-Id: I03a993a9f84af354dcf7e358ed0e5e126eda48af commit 111845b3eafc4315f1464ae844caf04a286f4b6a Author: Zhongyue Luo Date: Fri Aug 16 14:29:23 2013 +0800 Bump hacking to 0.7.0 Change-Id: I4414f4b8f82bdb3df3fc9c1e9de675e1994aed05 commit c280f6060c6bbc50e9f5f74232b3c45bccee2ec4 Author: Flaper Fesp Date: Wed Aug 14 09:31:37 2013 +0200 Add auto-create support for OptGroup instances Currently, it is possible to auto-create groups by passing a group name to register_opt or register_opts. However, it is not possible to auto-register groups when a OptGroup instance is passed instead. This patch adds support for that to the _get_group method. There are some advantages introduced by this patch: * It keeps consistency of oslo.config's public API. * It allow users to auto-register a group a group and re-use the instance: group = cfg.OptGroup("test") cfg.register_opts(..., group=group) # do something with the group * It allows users to auto-register groups with descriptions. DocImpact Change-Id: I9b9a74688c906d6bdea9c42044b47dc52e48c19d commit c85c8e6c4d5a921aa36a4774fe6bd8ab07d58fb3 Author: Davanum Srinivas Date: Thu Aug 1 21:43:32 2013 -0400 NoSuchOptError should be used not AttributeError Switch back to previous behavior. Looks like we are missing a test to enforce the old behavior that everyone depends on Fixes LP# 1207541 Change-Id: I79c58cfcf619a461f6a42a4dccfa9141bc1033a0 commit 8470e71e8b0fab7db9a73ddaf4b122c592ed6d60 Author: Zhongyue Luo Date: Sun Jul 28 13:48:29 2013 +0900 Fixes six imports Change-Id: I4ee63d990bf95d74cadd1d65ad75f7fe26cc8fbd commit 115e73801f2e2c80006dc11f61599029794f7541 Merge: 9bc1af3 b7f895f Author: Jenkins Date: Mon Jul 22 21:09:49 2013 +0000 Merge "Allow use of hacking 0.6.0 and fix min version" commit 9bc1af398e888f5981453a3efbff2b913767d21a Author: Doug Hellmann Date: Sun Jul 21 17:00:31 2013 -0400 Fix python 3.3 test configuration It is no longer necessary to install testrepository from bzr, so don't. That means we no longer need a separate py33 environment definition, or the special requirements file. Do not use distribute in the test virtualenvs. Change-Id: I1c0002d3992e25e7e0927eca85ccd5e4d57ee7d1 commit b7f895fad2d5973a2a8515b537bd00740a725d01 Author: Sergey Lukjanov Date: Sat Jul 20 00:47:56 2013 +0400 Allow use of hacking 0.6.0 and fix min version Change-Id: Iaf75b8991c6a7c79a22651ca15867adda4dac2b1 commit d836c1f05ac6909409b1c8763d5926d31fad5e2f Author: Luis A. Garcia Date: Thu Jul 18 22:39:44 2013 +0000 Add eclipse project files to .gitignore Like some of the other projects (nova, heat) ignore eclipse project files, for those people that work with eclipse (yuck :) Change-Id: I803a5a9a4bf30dc053b3f3a260a69ce19f3a6c9b commit 305ecd817836a90a8f5e495cbf6a770dcea1f7e8 Author: Julien Danjou Date: Tue Jul 16 11:49:04 2013 +0200 Add Python 3 support These are the final changes needed to have the full py33 tox environment running and passing all tests. Change-Id: I3ac149345561a5bb99e017022ba2e2be10154584 commit b96ebd3e5d958d3bf52508a0df5fcc77daa4bdf5 Merge: 4f44b97 3302fb0 Author: Jenkins Date: Mon Jul 15 19:16:52 2013 +0000 Merge "Raise an exception when _oparser is not initialized" commit 3302fb05723e62f9b02c2cce74e30ded1edfda51 Author: Davanum Srinivas Date: Tue Jul 2 12:16:38 2013 -0400 Raise an exception when _oparser is not initialized Throw an exception when print_help and print_usage are called before __call__() is invoked on ConfigOpts object Fixes LP# 1196601 Change-Id: I2c3b412c5aa9adef87a92103b9749cc66158a3e4 commit 4f44b97017e7f2b5920065f63df9ebe50027f4fb Author: Mark McLoughlin Date: Wed Jul 10 08:22:13 2013 +0100 Hook docstrings into the published docs Fixes bug #1182842 We've always had pretty decent documentation coverage in the docstrings but have never taken the time to hook them into the published docs. This is a first-cut at doing that. There's plenty of rough edges, but it's a good start. Change-Id: I62a509e7d6e156e9b67e75bef9da68fbfc4601ac commit 2b14d7314e4a760a282688174cfbf2edeb31c402 Author: Mark McLoughlin Date: Wed Jul 10 07:09:37 2013 +0100 Use oslo.sphinx and remove local copy of doc theme Use the new oslo.sphinx version of the OpenStack doc theme instead of copying it into this repo. blueprint oslo.sphinx Change-Id: I0bd91f7bb43f97b99051fed65b75fc05d5149cc8 commit 29bf15d65d54219cd76a7f2b18bab0272bc180fe Author: Mark McLoughlin Date: Tue Jul 2 11:28:13 2013 +0100 Add release notes for 1.2.0a3 These will appear here: http://docs.openstack.org/developer/oslo.config/ Change-Id: I2e9bfcf2358dddc8354456e3afc52b7e11ed3d5c commit 08203f69e6f436be3ba37dfd7e46a75695b5548c Author: Monty Taylor Date: Wed Jun 26 13:38:17 2013 -0400 Ensure namespace package is installed. This is a workaround for bug 1194742. There is actually a bug in upstream d2to1 that has a PR up: https://github.com/iguananaut/d2to1/pull/32 But until that is released, this should fix the immediate problem. Change-Id: I136b8493c8d8d48a0116facf5f23c2a1479c070f Signed-off-by: Doug Hellmann commit 5fa6ea2936fc703078a9869b5301d62947273703 Merge: c774a9d c52d8c0 Author: Jenkins Date: Mon Jun 24 16:49:32 2013 +0000 Merge "Add reload_config_files function" commit c774a9d241f3ff78caf738471c8326d53bdb9abe Merge: f0d4e34 b7f2463 Author: Jenkins Date: Mon Jun 24 16:48:46 2013 +0000 Merge "Use assertEqual() rather than assertEquals()" commit c52d8c0a4ee7050d9c5ed57502f2c0bf833aca5b Author: Fengqian.Gao Date: Sat Jun 8 10:56:11 2013 +0800 Add reload_config_files function Add reload_config_files function for ConfigOpts instance to reload all configuration files. Implements blueprint cfg-reload-config-files Change-Id: Ia4d96be170d1ddfaa70b8e467b9a19ed45dc4060 commit f0d4e34fde8f16324961d15d633a2b57552b097b Author: Fengqian.Gao Date: Wed Jun 19 10:15:32 2013 +0800 Modify __clear_cache function Clears cache after function is called. The reload_config_files() function needs to clear cache after it is called. Partially implements bp cfg-reload-config-files Change-Id: If046719253bca4ca4eabd2a2abdaf09e44500099 commit b7f246389382aa032f441aeb5af6f6a43f5ac9c3 Author: Mark McLoughlin Date: Mon Jun 17 23:04:14 2013 +0100 Use assertEqual() rather than assertEquals() assertEquals() is deprecated: http://docs.python.org/2/library/unittest.html#deprecated-aliases Change-Id: I9e74cb0f989854804f7bf7e89cb022925b47bf16 commit 47f6c60e91321ccdab291a25df47501455929467 Author: Chuck Short Date: Sun Jun 16 13:05:15 2013 -0500 python3: Fix traceback while running tests. Fix the following traceback while runing with python3: NameError: free variable 'section' referenced before assignment in enclosing scope Change-Id: I1792aef16dcc170ce261c4d4079742b9125e5b84 Signed-off-by: Chuck Short commit 742615177400bdc9216db8f190ac3057eaa858ea Merge: 99e22c8 d33c1a7 Author: Jenkins Date: Thu Jun 13 23:45:36 2013 +0000 Merge "Rename tools/(pip|test)-requires to *requirements.txt" commit 99e22c8837d2dbf6421689739b8f84d61b17f742 Merge: fec95ba ad67b3c Author: Jenkins Date: Thu Jun 13 23:44:11 2013 +0000 Merge "Tar up .testr.conf as well" commit fec95ba506806ccb87a6333bb9c7c67db75f4172 Merge: 5b15fc6 de803cd Author: Jenkins Date: Thu Jun 13 11:22:37 2013 +0000 Merge "python3: Fix traceback while running tests." commit 5b15fc6808549c3ab3989724dc0a5a1f0e6b32b2 Merge: 56f78ff d62ee3c Author: Jenkins Date: Thu Jun 13 11:00:19 2013 +0000 Merge "python3: Fix traceback while running tests." commit ad67b3cf3849952ee385a4e8660f59fa3f7cdd52 Author: Dirk Mueller Date: Thu Jun 13 10:39:54 2013 +0200 Tar up .testr.conf as well Allows unit-tests to be run from the tarball release. Change-Id: I35e453ea18611b94f954f067699b5f1823ea7481 commit d33c1a7b3f29a15db3330b6d1596c140156d13ff Author: Dirk Mueller Date: Thu Jun 13 10:20:30 2013 +0200 Rename tools/(pip|test)-requires to *requirements.txt This seems to be more standard accross the various OpenStack modules. Change-Id: I6de7bb457db891150eab8db3603e4a72c7d75962 commit de803cdf73180e3164381b121f1e53716bfe6b8d Author: Chuck Short Date: Mon Jun 10 19:00:41 2013 -0500 python3: Fix traceback while running tests. In python3 you can't sort mixed data, that is, for example you can't have a dict that contains strings and intergers. Otherwise you will get the dreaded: TypeError: unorderable types: DictOpt() < _ConfigFileOpt() Change-Id: If712e1d51c1e1329093acb16ed6a84e9f0baf8c1 Signed-off-by: Chuck Short commit d62ee3c36c2d93f998bf2c8ae99196f8f7795a2f Author: Chuck Short Date: Mon Jun 10 18:43:46 2013 -0500 python3: Fix traceback while running tests. While running the tests with python3, some tests fail while running with python3. This is due to change in strings in Python3. http://docs.python.org/3.1/whatsnew/3.0.html Change-Id: I4375bdda74363757dc7180ff3fd68cf7c8dc9c15 Signed-off-by: Chuck Short commit 56f78ff3b96ddefff54b58d0126f8b6aa51dade7 Author: Chuck Short Date: Fri Jun 7 09:14:19 2013 -0500 python3: More python3 compat updates More python3 compability updates. Change-Id: Id784f0d6075f6bf821e2e46898f3bcaa7ade8e3d Signed-off-by: Chuck Short commit 13bcb0fa6c8bcd1cfbd8891936800771970923fd Merge: 2951391 71954fd Author: Jenkins Date: Thu Jun 6 18:54:30 2013 +0000 Merge "python3: Remove mox support from oslo.config." commit 71954fddb955d536d83ffc332465c4132a9c850d Author: Chuck Short Date: Wed Jun 5 17:06:49 2013 -0500 python3: Remove mox support from oslo.config. Mox is not python3 compliant so remove it in favor of fixtures. Change-Id: I381b3ba0434d7f1261c898992a7b7974c64b1fea Signed-off-by: Chuck Short commit 29513917cb8fd85075768d2d927b6389db671737 Author: Davanum Srinivas Date: Wed Jun 5 12:00:45 2013 -0400 Fix config help order to be alphabetical Currently The output of --help is in random order. The only way using public argparse api to control the order of display of --help is to add the arguments in the sorted order. So added a caching mechanism to collect all the calls to add_argument and ensure they are sorted before calling argparse's add_argument. The only other choice if anyone is interested is documented here which uses the non-public API http://stackoverflow.com/questions/12268602/sort-argparse-help-alphabetically Fixes LP# 1185959 Change-Id: I5f934c85fd516e2d87b3ece40142f16c898b04ce commit fb2798650d27b2cab9015eb1bc4247d82012ef77 Merge: a10208d 68d89a7 Author: Jenkins Date: Mon Jun 3 09:11:30 2013 +0000 Merge "python3: Introduce py33 to tox.ini" commit a10208d6d12bbe4d62ec03b6993fd92aa83ad108 Merge: 500b8a3 e68a01e Author: Jenkins Date: Mon Jun 3 07:38:50 2013 +0000 Merge "Enable pep8 E125 check (w/o any changes)." commit e68a01e16498ff655d00c96426f1c867c8b6cb1a Author: Sergey Lukjanov Date: Sun Jun 2 19:51:32 2013 +0400 Enable pep8 E125 check (w/o any changes). Change-Id: Ibda6c7ec990a72c660e043857723049721c60166 commit 68d89a7ea0a184a58391f0720b8cd26c3196e86c Author: Chuck Short Date: Sat Jun 1 20:08:23 2013 -0500 python3: Introduce py33 to tox.ini Introduce py33 to tox.ini to make testing with python3 easier. Change-Id: I3cbd8f65c626cfbb5df647dece25641040872f36 Signed-off-by: Chuck Short commit 500b8a3d04f102aa4aa5236ad7d64e94fb21d04c Author: Mark McLoughlin Date: Fri May 17 00:31:30 2013 +0100 Fix the priority of CLI args vs config file values Fixes bug #1176817 The API enforces a special requirement for CLI options - they must be registered before the command line is parsed. This means that as we parse the command line - and parse config files specified on the command line - we can fully process CLI options whether we find values for them on the command line or in config files. This allows us to fundamentally change how we reconcile values specified on the CLI with values specified in config files. No longer do we have to first look at the set of values found on the command line and then look at the values found in config files. Instead, we can treat all values for CLI options as if they were found on the command line, even if they were actually found in config files. The long and short of this is that if you do: --config-file foo.conf --blaa --config-file bar.conf then '--blaa' will override any value for the 'blaa' option in foo.conf but we can also override the 'blaa' value on the command line from within bar.conf. Change-Id: I0a584a3c26ddadf24e3f473c773ee34a914667b3 commit f083d7cfcbc222720fd479a1dabe08d7ae7ed044 Author: Mark McLoughlin Date: Fri May 17 00:31:22 2013 +0100 Parse config files in an argparse callback Part of fixing bug #1176817 We want to have the priority order of config file value vs CLI argument to be determined by where on the command line the --config-file argument appears. e.g. --config-file nova.conf --log-file nova.log would mean that a log_file value in nova.conf would be ignored, whereas --log-file nova.log --config-file nova.conf would mean the value in the config file takes priority. As a first step towards that, we move to parsing config files in an argparse callback while processing the CLI args rather than parsing all the config files together after CLI args processing. This allows us to know whether a config file was loaded before or after a given CLI argument. Even this introduces a change in semantics - previously, the files in the directory passed to --config-dir were parsed after any of the files passed to --config-file. With this change, we now have --config-dir processed according to where it appears on the command line e.g. --config-file foo.conf --config-dir blaa.d --config-file bar.conf means that the files in blaa.d take precedence over foo.conf, but bar.conf takes precedence over blaa.d. This is obviously the proper semantics and we were never happy with the current semantics, so this is all good. Change-Id: I421e2d0c2cc41f361832c997c6c2198eb92c07c3 commit d75d42f823de7b0fc8a43cc3ab736d81e9c68a11 Author: Mark McLoughlin Date: Fri May 24 12:48:05 2013 +0100 Revert additions to ConfigParser public APIs In commit 2e1c685, we fixed the MultiConfigParser breakage but we also added to the public APIs of MultiConfigParser and ConfigParser APIs. There's no reason to lock ourselves into supporting these new semantics, so let's add them in a way that they're private to cfg. Change-Id: Ief1e491590f022dc16cca78b2c4aa2d418aec68f commit 380c2b7c88fa5e7bacb56b7a29da6c69e1f09399 Author: Mark McLoughlin Date: Fri May 24 10:43:50 2013 +0100 Refactor type conversion The way each opt subclass has to override _get_from_config_parser() just to perform type conversion is lame. Add an explicit type conversion callable attribute on Opt and pass it to MultiConfigParser when we're looking up a value. This will be useful in a future patch where we'll use MultiConfigParser (renamed to Namespace) to look up CLI values too - since these get converted by argparse, we want to avoid converting those values again. Change-Id: I52003026e788e83c355af55accb73d02150a6b0b commit 14f059ed2ca3d00b442df480cac2e1528e50efa1 Merge: 161bba4 7c0d349 Author: Jenkins Date: Wed May 29 23:11:28 2013 +0000 Merge "Restrict StrOpt to just a few allowed values" commit 161bba45886fa8a444cb21643ad049d6c11cf8ae Author: Zhongyue Luo Date: Wed May 29 00:29:08 2013 +0800 Adds group name normalization tests See https://review.openstack.org/#/c/27887/7/tests/test_cfg.py Added tests to ensure the changes do not break legacy code behavior. Change-Id: Ie113ab868f296a983b149739db415518c9f60f38 commit 742fd6c51686af49094b7712ba6821b24a411bed Merge: d50130f 45d9a83 Author: Jenkins Date: Sun May 26 07:02:07 2013 +0000 Merge "Simplify help string for --config-file" commit 45d9a83090ee40a5b87cc00fa7bd946d4b57170b Author: Mark McLoughlin Date: Thu May 23 21:31:32 2013 +0100 Simplify help string for --config-file As Doug pointed out, we can simplify including the defaults for --config-file in its help string by using argparse's builtin %(default)s substitution. Change-Id: I818101a222a64c12617c89d1f498a0a7c50b2184 commit d50130fd34606606fdb7750019c5cd05c53ada61 Author: Mark McLoughlin Date: Thu May 23 21:04:44 2013 +0100 Add release notes for 1.2.0a2 These will appear here: http://docs.openstack.org/developer/oslo.config/ Change-Id: I9141f34f9ed7d10ae7cdf49ece2226f494055645 commit 3d6a8a106702df86c746cd96ba79e40bf6e5f9ab Author: Mark McLoughlin Date: Wed May 22 12:21:42 2013 +0100 Add release notes for 1.1.1 Will be released from stable/grizzly soon. Pushing the release notes to master so they appear here: http://docs.openstack.org/developer/oslo.config/ Change-Id: Ib4389718ee83b997c8b6c8a77dbf82fb0fd98cf3 commit 5a3df70d76c6f18612c803bca1dd6f7c3d6ff2d3 Merge: c8e58cd 2e1c685 Author: Jenkins Date: Thu May 23 12:10:39 2013 +0000 Merge "Fix MultiConfigParser API breakage" commit 7c0d3491eea4d4edef82e65c4817cf482350e371 Author: Davanum Srinivas Date: Tue May 14 21:24:55 2013 -0400 Restrict StrOpt to just a few allowed values Allow the developers to specify allowed parameter with a list of values. Throw a ValueError when the value specified in the command line or config file is not in the list of allowed values Fixes LP# 1123043 DocImpact Change-Id: I69f5610dd738f808c7f2734e8abead0e26f8dadf commit 2e1c685b57d73fb8e32996b4bfa717cb808c7f6f Author: Mark McLoughlin Date: Thu May 23 11:08:10 2013 +0100 Fix MultiConfigParser API breakage Commit c952491 made an incompatible change to MultiConfigParser's behaviour. The code in Quantum which was relying on the behaviour did the following: p = MultiConfigParser() multi_parser.read(conf_file) for parsed_file in multi_parser.parsed: for section in parsed_file.keys(): if section.startswith("CLUSTER:"): ... However, we made a change such that the keys of the 'parsed' dict were normalized to lowercase. Restore the previous behaviour and add another dict for tracking the values with normalized section names. We also add a normalized=False kwarg to the get() method so that we also restore the previous behaviour of the method by default. This shows the need to aggressively make implementation details as not part of the public API of our libraries. In this case, I considered MultiConfigParser as an implementation detail and that no projects would be using it. But, sure enough, if it's public then *someone* will use it. Also, add a bunch of test cases to actually test the behaviour of this class in isolation. Change-Id: I0344e39222b3aecad5e30f820014f4d9f672e90c commit c8e58cda02d1569c3e04be23e15e3b770c52d63c Author: Mark McLoughlin Date: Wed May 22 12:14:14 2013 +0100 Add release notes for 1.2.0a1 These will appear here: http://docs.openstack.org/developer/oslo.config/ Change-Id: Ib9b9e88f54a5a3c8fb0087d55b394bb0bb54caa6 commit ea4a642587c12852777d5af05e23fa6c0de51f16 Merge: 46382e3 e1f8841 Author: Jenkins Date: Wed May 22 08:42:12 2013 +0000 Merge "Allow Multiple sets of deprecated name/group for options" commit 46382e32469f58f6b40fd986a665e75b076b068d Merge: 4633464 2b05194 Author: Jenkins Date: Wed May 22 05:46:57 2013 +0000 Merge "Add CLI vs config file priority test" commit 463346421bff6f36e871425f185a23bf6a800b87 Merge: a03994d 4cb428a Author: Jenkins Date: Wed May 22 03:26:31 2013 +0000 Merge "Add test cases for default_config_files" commit a03994de40b975cbe0ec8d24f3a5e4b56ef65d6d Merge: d670991 dea3e65 Author: Jenkins Date: Wed May 22 03:25:10 2013 +0000 Merge "Move --config-dir tests into their own test case" commit e1f884174eb65895ade604933b25ed245fb45942 Author: Davanum Srinivas Date: Sun May 19 16:37:24 2013 -0400 Allow Multiple sets of deprecated name/group for options We currently allow just one name/group for deprecation. For adoption of oslo db by multiple projects, we need to be able to specify multiple sets of deprecated name/group. For example connection is defined as follows in various projects and we need to be able specify all the combinations for being able to upgrade from Grizzly to Havana Nova: Section - DEFAULT; Key - sql_connection Cinder: Section - DEFAULT; Key - sql_connection Glance: Section - DEFAULT; Key - sql_connection Quantum: Section - DATABASE; key - sql_connection Keystone: Section - SQL; key - connection Please see discussion on the dev mailing list and on the review - https://review.openstack.org/#/c/27345 for oslo-incubator Change-Id: I8c2097275242a4fdbd4f0f9fb4e293d1ba793d16 commit 2b051948f0e2c10dec3f036dcef559c933b78629 Author: Mark McLoughlin Date: Fri May 17 00:31:19 2013 +0100 Add CLI vs config file priority test Add a test to show the current behaviour of config files overriding CLI values, even where the CLI value comes after the --config-file argument. Change-Id: Iaa6dcee49339c096278cc11945cc244a665e6a0a commit 4cb428abab9205e4858505e82f88e48660878352 Author: Mark McLoughlin Date: Fri May 17 00:31:15 2013 +0100 Add test cases for default_config_files It's pretty important we actually test the behaviour of falling back to a default list of config files. Very surprising that we weren't alrady testing this. Change-Id: I903384247480946cc6aed38528695a4b89e717d2 commit dea3e65520a9e423ed4186363abaf184dee32946 Author: Mark McLoughlin Date: Fri May 17 00:31:11 2013 +0100 Move --config-dir tests into their own test case For some reason these were in TemplateSubstitutionTestCase. Change-Id: Ie2ce1a2a4fd20e5255434c5a5ccf4bc42a5e4beb commit d6709915af01c4d2f375c32f8e683a04188ef342 Author: Mark McLoughlin Date: Thu May 16 11:59:12 2013 +0100 Remove some redundant code for bool opt actions You can see how this code came about here: https://review.openstack.org/#/c/14125/20..21/openstack/common/cfg.py but the only time BoolOpt:_get_argparse_kwargs() gets passed the action kwarg is from _add_inverse_to_argparse() where we pass 'store_false'. Change-Id: Ic6aa123680b5eb9aa220152389f3535b703fdf7b commit c9524919cce9d7a94720cb705d1849367ac8695f Author: Zhongyue Luo Date: Fri May 10 00:44:14 2013 +0800 Normalizes non lowercase option group names in conf files To avoid inconsistency of option group name format used in conf files, this patch normalizes group names to all lowercase names Implements bp cfg-lowercase-groups Change-Id: If8e6c671be776a708a7200e52e4897a8a3b90bdf commit f828f2fc169d24f5bd9bd6923fb810e3f253ca4d Author: Monty Taylor Date: Sat Apr 6 13:46:19 2013 -0400 Switch to flake8/hacking instead of pep8+pyflakes. Change-Id: I26a5b2b1923d6301a5ebdb6474d68a489f9fbe72 commit 2205171cb64dfd3f48bb36866fe59448623fc4f0 Merge: 9aeb9df bd84ae4 Author: Jenkins Date: Tue May 7 12:47:07 2013 +0000 Merge "Re-work test_cli to use testscenarios." commit 9aeb9df43414b2c3e0e70f0962355c141123560f Merge: d47560c ef46c65 Author: Jenkins Date: Tue May 7 12:47:06 2013 +0000 Merge "Upgrade testing to use testr." commit d47560cce25d87212f852891a762e41c40535c7f Merge: 1c1ae83 ffbdc2a Author: Jenkins Date: Tue May 7 12:46:54 2013 +0000 Merge "Update build to use pbr." commit 1c1ae83f894262232dfccfd1d75ce7340ce6edfc Author: Steven Deaton Date: Tue May 7 04:55:44 2013 +0000 Made a couple minor textual documentation / typo fixes. Change-Id: I8473a41cacb0a39aa0880e7b22fdc33e7717f54e commit 9d433809044a3960f1aed56739d9a197f8288314 Merge: 8256fae 5be9b75 Author: Jenkins Date: Tue May 7 02:03:34 2013 +0000 Merge "Use builtin startswith and endswith methods in iniparser" commit 8256fae441a2c4fb53a7f314495c1099606c5f79 Merge: 60511c9 bb995cc Author: Jenkins Date: Tue May 7 02:02:43 2013 +0000 Merge "Fixes OptGroup title set typo" commit bd84ae480912802b6f790f710d17507589ce2ad5 Author: Monty Taylor Date: Sat Apr 6 13:09:36 2013 -0400 Re-work test_cli to use testscenarios. The cli tests were already organized in a manner that lets testscenarios do what it does. Reformat the code a little bit to be a single test plus a list of declarative combinations of scenarios and let testscenarios do the work of turning that into a set of test cases. Change-Id: I4e10d9eaeb313c724f0012babac465e37be4fa3d commit ef46c65837343aa3ef6c89067469ec2ab848d6b5 Author: Monty Taylor Date: Sat Apr 6 12:21:59 2013 -0400 Upgrade testing to use testr. Change-Id: Ib0e73c125c908898735e60e5b435468bc2b08316 commit ffbdc2acb632ab3380389eb3669251a481714614 Author: Monty Taylor Date: Sat Apr 27 11:05:18 2013 -0400 Update build to use pbr. Change-Id: If27004df4c756ebc0b00784cb0445ab46ea43d97 commit 60511c9a4ad09978f3f5165d804d99f6692b5c10 Author: Chuck Short Date: Tue Apr 23 13:08:21 2013 -0500 Add python3 compatability support Add partial python 3.x compatibilty support. Change-Id: I52a3192a50b7f07d95b99c9c1b5abcd403b6dde8 Signed-off-by: Chuck Short commit 5be9b757e42447aeb66bbe4d9bdb2b07f2201867 Author: Zhongyue Luo Date: Thu May 2 14:16:31 2013 +0800 Use builtin startswith and endswith methods in iniparser Change-Id: Icbcd9d9c2f8ebc5f827772a0d8dbfba3f6c72da3 commit bb995ccc1439b86ad68a39fc5f16407cf287061a Author: Zhongyue Luo Date: Wed May 1 17:08:55 2013 +0800 Fixes OptGroup title set typo Fixes bug #1175096 Change-Id: Ie64bd2f7a87af27f5fd202728d03423fd66b8b68 commit 2031b324566214a550dce0b26c80ab4cd04d42b1 Author: Mark McLoughlin Date: Mon Apr 22 17:19:14 2013 +0100 Remove debug print statements from tests Added by me in commit ae93f9f Change-Id: Idfaedd01be77b41f049cff7e40da65dd5831b091 commit fc8ca59c8fec5ce676ab41da5ca737c6e9e0c13c Author: Dirk Mueller Date: Mon Apr 22 03:32:29 2013 +0200 Python 3.x compatibility Some mechanical translation to improve compatibility with Python 3.x Change-Id: I47b930be9f5d76f99001c2a202c689c0c1f91e65 commit 922934c825b81c731e5fda1201f85503cd206a91 Author: Davanum Srinivas Date: Fri Mar 22 21:41:08 2013 -0400 Support for Dictionary Options Allow key/pair options that result in dictionary. Works similar to ListOpt option = key1:value, key2:value Adopted tests from ListOpt to build the set of test cases for DictOpt Change-Id: Id832bfee575a0bf50747f7d6c2d504c1ccf99323 commit 921fd433fb105778610191d442ce490e82aff5a5 Author: Davanum Srinivas Date: Wed Mar 27 16:04:22 2013 -0400 set_defaults does not work for more than one kwarg We used to just pick up the first one and break out of the loop. Removing the break statement is enough to get all kwargs to be picked up. Added a test case Fixes LP# 1160922 Change-Id: I966360fb5af30bc04e9f197c4b6de507636fa25d commit 5439d6bf290ec43f2ecddb1696eaa1a4ba114e07 Author: Zhongyue Luo Date: Thu Mar 21 16:35:26 2013 +0800 Adds pylint section in tox.ini in oslo.config to check for unused imports Fixes bug #1157597 Change-Id: I58057cdde6064dacf6689560ec6439fd6dc7cf4f commit dadd3ea62392e2cf4bb8a65c5464ea5e7e0271b2 Author: Zhongyue Luo Date: Thu Mar 21 16:36:32 2013 +0800 Removes unused imports in the oslo.config.tests module Change-Id: Ibf4d9a697432eb0644f7a0e763ab2f78d9fd8505 commit 766892429f879f35109e3e01d22a07405a4a67e8 Author: Mark McLoughlin Date: Tue Mar 19 22:12:32 2013 +0000 Improve test cases for boolean values In I6a3ef892ac54389d30d2e9e25b9ad8047d4926ec we see some confusion about the order which CLI values vs config files values should be applied. The documented semantics is that values in config files override any values specified on the command line. Improve the "bool value override" tests to more explicitly test the cases of a config file overriding a CLI bool, a config file overriding an inverse CLI bool and a config file overriding a previous config file bool. The proposed patch would have broken these tests. Change-Id: I133c43d2d9121c98407d42d6cab8a1ecb2ff450e commit 255082ed3a3abd5f12a4a8459aa349b778ede08a Author: James E. Blair Date: Thu Mar 14 12:02:35 2013 -0700 Rename oslo-config to oslo.config. Change-Id: Ie9c891e5ec54ee908dead98f5133a4f2aa88da58 commit 91155c8e5fe54b5929b23a39db8c624cf9868006 Author: Davanum Srinivas Date: Wed Mar 13 17:57:22 2013 -0400 Fix copyright - from LLC to Foundation one last spot hopefully! Change-Id: I35b7d199ed7acbca96dd9f1a916736c9cf2f4a94 commit 2a170e431779626fac3ad309a712197aa0f58127 Author: Mark McLoughlin Date: Tue Mar 12 16:01:08 2013 +0000 Add Environment::OpenStack classifier Doug had this registered for us: http://mail.python.org/pipermail/catalog-sig/2012-October/004634.html It makes it easier to find all OpenStack packages. Change-Id: I17ee76616eb11a02a65b7a624f625a207b9e9631 commit e83c9f311f4abf3dcc728ff617bbb70db417d617 Author: Mark McLoughlin Date: Tue Mar 12 15:58:46 2013 +0000 Open 1.2.x development The 1.2.x series will be for havana. Its API will be backwards compatible with the 1.1.x series. Change-Id: I041046bb97bf4072f598c91b8a127f4e6bba9f19 commit 273b5dfa7885f4fb8dfc308309fbc7e77b2d1694 Merge: 837b2df f757ea3 Author: Jenkins Date: Tue Mar 12 15:19:58 2013 +0000 Merge "Copyright rename LLC to Foundation" commit f757ea358e54fcb5495304d9504e73147554afec Author: Davanum Srinivas Date: Mon Mar 11 14:42:36 2013 -0400 Copyright rename LLC to Foundation One code change, rest are in file headers Change-Id: I22c3a271edab63b382eb07cecd8b5ef02834b540 commit 837b2df8909f10a38e6ddf30fc14e5dc3f70cb84 Author: Tim Miller Date: Sat Mar 9 01:22:51 2013 -0800 Correct some typos in docstrings in cfg.py. Change-Id: I3c56596cf0154c342e0a0aa0e60e9111519f3554 commit 6dca6694619a04b21b723adaf20551376ab99acd Author: Mark McLoughlin Date: Tue Mar 5 21:54:38 2013 +0000 Change the name of the project to oslo.config Again discussed here: http://lists.openstack.org/pipermail/openstack-dev/2013-March/thread.html#6283 http://lists.openstack.org/pipermail/openstack-dev/2013-March/thread.html#6138 The rationale is that the use of namespace packages is likely to increase and there is apparently some consensus that the convention should be to use period to separate the namespace package name from the subpackage name - i.e. call it oslo.config. The zope.interface is an example of this convention, but PasteDepoy is a counter-example. Oslo is going to have a family of libraries, so it'd be much nicer if they followed what the future Python convention is likely to be. This is a disruptive change for packagers and the benefit isn't huge, but pairing it with the also disruptive change to the versioning scheme makes it somewhat more acceptable. Change-Id: I63e96a8b48b166b07f1c8e79b6eaffb6234cc751 commit 1e3c302fdb21855e4cf10dab27e7e74f6f3355bf Author: Mark McLoughlin Date: Tue Mar 5 21:49:42 2013 +0000 Switch to semantic versioning Discussed here: http://lists.openstack.org/pipermail/openstack-dev/2013-March/006283.html The date based 2013.1 version is likely to be disallowed by PEP426 when it is ratified and we'll be required to treat the date based version as a "private version" and use 0.2013.1 as the official version. Also, I'm coming around to the idea of using semantic versioning (i.e. x.y.z) and increasing the major number when removing any deprecated APIs. Th is the trend that Python is following according to a discussion on distutils-sig. One consequence of using semantic versioning is that we'll be very loathe to remove any deprecated APIs since it will mean a version bump from 1.y.z to 2.y.z. That's probably not a bad thing. With this versioning scheme, I figure we should start with 1.1.0 (2013.1-2012). We'll increase the micro number when we do release from the stable branch and increase the minor number with every coordinated OpenStack release. Change-Id: Ibe033d37742b82dee5f42d9e3d6a0b51582bd3ce commit ad2e416b9dcc4ceb4a12da215c44ef4194a46d79 Author: Mark McLoughlin Date: Tue Mar 5 09:17:49 2013 +0000 Explicitly set ConfigOpts._args to sys.argv[1:] Fixes bug #1146426 If you don't pass an args list to ConfigOpts.__call__() we pass args=None down to ArgumentParser.parse_args() and it defaults to sys.argv[1:]. However, in log_opt_values() we reference sys._args and you'd expect it to show sys.argv[1:] in this case too but it shows None. Fix this by explicitly setting _conf to sys.argv[1:] rather than relying on argparse's behaviour. Also add a test which would fail without this fix. Change-Id: Id5a769bdda5f2359da18c301cb6305aaf3ed8617 commit 59b672033919bb315f2673ed1f417787ca4c042e Author: Mark McLoughlin Date: Sun Feb 10 21:02:22 2013 -0500 Add deprecated_group Opt kwarg We often wish to take existing configuration options and move them from the DEFAULT group to a more specialized group, while still supporting existing configurations. We can support this by adding a new deprecated_group kwarg which allows an option to be aliased to a name in another group e.g. CONF.register_opt(cfg.StrOpt('foo', deprecated_name='bar', deprecated_group='DEFAULT'), group='blaa') or where the option was simply moved between groups: CONF.register_opt(cfg.StrOpt('foo', deprecated_group='DEFAULT'), group='blaa') Implements blueprint cfg-move-opts-between-groups Change-Id: I4d974c71fab3e3c5c7a3e6514bdd9cb20e7200ff commit df77677698483b58d6026994614cb847d25f9b69 Merge: d53de9c b22784c Author: Jenkins Date: Tue Feb 19 21:40:34 2013 +0000 Merge "Allow MultiConfigParser get from mutliple sections" commit d53de9c3c10c0b234f9df979aac9a7af0f790967 Merge: 136f1ef aad3d62 Author: Jenkins Date: Tue Feb 19 21:40:30 2013 +0000 Merge "Refactor _cparser_get_with_deprecated()" commit 136f1ef9cc19817304e95e47891ce69536bf69a0 Merge: 453f63f c64e72d Author: Jenkins Date: Tue Feb 19 21:36:29 2013 +0000 Merge "Add AUTHORS and ChangeLog to .gitignore." commit c64e72debf734aa3412b71ffc074185a9e894ca6 Author: Dan Prince Date: Tue Feb 19 14:44:27 2013 -0500 Add AUTHORS and ChangeLog to .gitignore. Change-Id: Ic32a01978e2aab4028a5fbcbc70953603e5c6e2c commit 453f63ff100ed31c566e1db8fbfdf9d1362fd369 Author: Dan Prince Date: Tue Feb 19 14:39:00 2013 -0500 Add missing files to the tarball. Fixes LP Bug #1130335. Change-Id: I4b8cddc9319146bb7249d96efc2226c9d5316ddb commit 394876f258bf99132ba23c2e2bd07ee5ac6404cf Author: Jason Kölker Date: Mon Feb 18 17:26:26 2013 -0600 Make sure to install the oslo package as well. Each package under a namespace that should install that namespace package so the __init__.py for the namespace gets copied. Fixes Bug 1129587 Change-Id: I064b05479c8a41d8f793886d2a5895dce166a85a commit 36bbf72d2f32a1ae10c484259cf7f29fbbfcd78e Author: Mark McLoughlin Date: Sun Feb 17 09:25:32 2013 +0000 Add LICENSE file Change-Id: Icfbb242b0b5a1d494ed673d48cea07fdd25fbbd7 commit af4ab489df16f120bef53ddb5ca02a6e7e042676 Author: Mark McLoughlin Date: Sat Feb 16 14:31:38 2013 +0000 Fix setup.py to install the correct package Change-Id: I87e3977ecd9078df0c8aa14ed18dc288fe49c596 commit b22784c95cdb8433155c25a64e1f207d03532871 Author: Mark McLoughlin Date: Sun Feb 10 20:56:25 2013 -0500 Allow MultiConfigParser get from mutliple sections MultiConfigParser.get() takes a section name and a list of option names. In order to support aliases of options which span multiple groups, we need to be able to look up from multiple sections. We can easily do this by changing MultiConfigParser.get() to take a list of (section, name) tuples. Change-Id: I50656c021669ccb947ceb56dfcf37d3fffcba194 commit aad3d6225a0d8205f1250928eea64944fdae4b1f Author: Mark McLoughlin Date: Sun Feb 10 20:53:39 2013 -0500 Refactor _cparser_get_with_deprecated() We have two copies of this method with the only difference being the use of the multi kwarg. Obviously, it makes sense to combine the two. Change-Id: I48fccccf32d839a9d443c638e0058354e26a6af9 commit 16c4ecbefc94941821f3ae7ca253fad7bc377c58 Author: YAMAMOTO Takashi Date: Wed Feb 13 14:01:46 2013 +0900 sort options to make --help output prettier explicitly sort options when adding them to argparse. it's a bit silly to print them in a dict iteration order. Change-Id: Id508331d7ee3b24e76be7fa958d27d29905bd3d2 Signed-off-by: YAMAMOTO Takashi commit b1730fcbc4f0852d9dd32fe6440e147fcd930abe Author: Zhongyue Luo Date: Wed Jan 30 22:27:51 2013 +0800 Implements import_group Created import_group method in cfg.py Added testcases Fixes bug #1107843 Change-Id: I427d4069dacbb6e586687370adbb08b5d50f7b63 commit a118969fb71c95bd816db05b3b1b3c6fc0902bb9 Author: Zhongyue Luo Date: Mon Jan 28 14:35:49 2013 +0800 Fixes "is not", "not in" syntax usage. Replaced "not ... is" to "is not" Replaced "not ... in" to "not in" Removed a redundant parenthesis Change-Id: I9564ab1207ccdcb32d7c2bb9e8f29658b2232ff9 commit 9805067bcf156dac354644b9b97ff8ceb4b5b3c6 Author: Mark McLoughlin Date: Sat Feb 9 15:36:08 2013 -0500 Add sphinx documentation Add basic sphinx config and copy the theming from oslo-incubator which was originally copied from keystone. Change-Id: Ibb3b679ce6e160c157ff63f0943807bd82aa1a67 commit f24575c137abd204d5145ff59ae6e2fafc635a73 Author: Mark McLoughlin Date: Mon Jan 28 09:54:01 2013 +0000 Fix version to 2013.1 commit 7da69211e912a8000c6498e015522edd223edfe7 Author: Mark McLoughlin Date: Mon Jan 28 09:28:30 2013 +0000 Start using fixtures and testtools Sync these changes from oslo-incubator: 60f70b0 Replaced direct usage of stubout with BaseTestCase. 827547a Use testtools as test base class. Note: I've copied MoxStubout for now, but eventually I guess we'll have an oslo-testing library we can depend on. commit c490e3515e8fee8c5743b944f60eeafb1b67864b Author: Mark McLoughlin Date: Fri Jan 25 17:29:00 2013 +0000 Sync latest setup code from oslo-incubator Changes include: 6b3c544 setup: count revs for revno if there are no tags 9c8685a Use revno and git sha for pre-release versioning. 5f5ef7d Add env var version override for packagers. 602aa9c trivial pep whitespace fix commit 45f1f32751f2fb9a841f46255a8108b46ad5cabb Author: Mark McLoughlin Date: Mon Jan 21 14:13:59 2013 +0000 Move logging config options into the log module We learned a lesson in Nova - it's best to declare and use config options within a single module if possible. Globally declared and use config options grow like weeds and it becomes harder to find out if, where and how individual options are used. Strangely, in cfg itself, we randomly declare a bunch of logging options which are only used within the openstack.common.log module - let's move the options there and remove the CommonConfigOpts class before they become part of the API we commit to when oslo-config is released. A minor detail in the patch - the logfile and logdir options are already deprecated in favour of log_file and log_dir, but we never got around to removing all other traces of the deprecated options. Change-Id: I3913ea54465658d93dc56e014dfe5d911b0541d6 commit 2ca3c749592c6b445b1ff27ccc40e0f644cf98a1 Author: Mark McLoughlin Date: Mon Dec 17 23:02:47 2012 +0000 Add setuptools magic commit 8c6a4c7523910e0745ae06194fa9484e45feb190 Author: Mark McLoughlin Date: Mon Jan 21 09:12:55 2013 +0000 Add oslo-config project infrastructure commit 27cc655c8046d5f3d59c72934ed11067e0e1e8f1 Author: Mark McLoughlin Date: Mon Jan 21 09:11:41 2013 +0000 Move files to new locations for oslo-config commit 2b210f7ebb7d29adfb4c12179c55d0167d8eff77 Author: Zhongyue Luo Date: Fri Jan 18 14:10:05 2013 +0800 Fixes import order errors Change-Id: I3e35230dd2d96ab9f5a8c11b9ec1cd8d2d00e347 commit 54d834e485927d704a4c68c69135229ad6ff9826 Author: Mark McLoughlin Date: Mon Jan 14 08:38:17 2013 +0000 Make tox run doctests Use 'nosetests --with-doctests' to run any doctests found. We currently only use doctests in a handful of places, but we may as well run them to ensure they work. Make the cfg doctests avoid using the global CONF since we would need to reset its state between each doctest. Fix the cliutils doctests to actually pass. Use 'nosetests --exclude-dir=tests/testmods' to avoid loading the modules from this dir while discovering doctests. The cfg unit tests rely on these modules not having been previously loaded. Change-Id: I19ad70767fa5c8352b994dc963b5d3a7c9d9eb95 commit 947bb9777bdfdcaa44ed4c22fab83a3d07b0a91f Author: Davanum Srinivas Date: Thu Dec 13 22:42:33 2012 -0500 Verbose should not enable debug level logging Fixes LP #989269 Currently setting --verbose in will still allow DEBUG level message to be logged to python logger object. we need to check for --debug first (set DEBUG level), then --verbose (set INFO level) and if neither is set then set default to WARNING DocImpact Change-Id: Ic9e3cb5979b2d7283552ad3a461870373f45a239 commit 049b3f8218eb8181cb4f3e3bf52af1d1b64c092a Author: Monty Taylor Date: Fri Jan 11 12:01:42 2013 +0100 Fix pep8 E125 errors. Caesar's wife must be above reproach. Change-Id: Iac85a57e71d403360f1567c07c8699057f0772fb commit d3deabba42f3a5e7325392def03d985967d76827 Author: Mark McLoughlin Date: Tue Jan 8 21:48:11 2013 +0000 Revert "Support lookup of value using "group.key"" This reverts commit 525ac47. There are already two ways to reference an option in a group: CONF.group.key CONF[group].key Adding a third variant doesn't seem ideal. Also, for the specific case of LazyPluggable in Nova, we can easily just pass an optional config group name to the constructor. Change-Id: I1a29a18d90e8af3ce2563bd1b3eeb64422140016 commit d666430d5004ea56114a2b75ed4b9907e6a9e393 Author: Davanum Srinivas Date: Wed Dec 26 22:50:35 2012 -0500 Support lookup of value using "group.key" Let us check if the opt_name has a '.', if it does then split it into a group/key and try lookup using that combination. Since LazyPluggable uses "CONF[self.__pivot]" if we just add this capability to cfg, we get "LazyPluggable doesn't support option groups" for free. Fixes LP #1093043 Change-Id: I9cedcf22014038e9fe4ed5e66ca5427aa99b5091 commit 8fa6c6232348754cf9f0586114732992df112dae Author: Mark McLoughlin Date: Fri Dec 7 06:44:13 2012 +0000 Add deprecated --logdir common opt --logfile and --logdir are aliases Nova has for the --log-file and --log-dir. If we're to support --logfile as a deprecated common option, we should do the same for --logdir. Change-Id: I16485a93070d9ad7789a287d5b035c6f270ffead commit eb4e0f00ac927cee3767f5c25c7a5ec004af0917 Author: Dan Prince Date: Thu Dec 6 10:40:36 2012 -0500 Add deprecated --logfile common opt. This adds a deprecated common options for --logfile which is an alias for --log_file. This resolves some backwards compatability issues with the most recent oslo common code where --logfile was no longer a valid opt. Change-Id: I17b1277da94a2d81ae439d650a6d7321420dfe14 commit b749f1d3cfeb6937240c05507db8872c7041fa0b Author: Davanum Srinivas Date: Wed Dec 5 16:11:48 2012 -0500 Allow nova and others to override some logging defaults - In log.py, indicate that logging module allows tweaking of just logging_context_format_string option - In cfg.py, add a method that can alter the default given the options and new default - add testcases for log.set_defaults and cfg.set_defaults Fixes LP #1083218 Change-Id: Iefdbce8505eb7a07f2b59d4ed7564b0146f1b0cd commit 1aba080308ec7e88a13533d0cfed30e640bb2530 Author: Michael Basnight Date: Wed Dec 5 16:00:11 2012 -0600 Fixing the trim for ListOp when reading from config file Fixes Bug 1087018 Change-Id: I1c2d34166ae85add86daab6a7483b63297d00f66 commit 82de68129b2bcc611bb584d262291ec32c249a4c Author: Mark McLoughlin Date: Mon Nov 26 06:47:52 2012 +0000 Fix set_default() with boolean CLI options Porting to argparse broke set_default() with boolean CLI options. The new test case shows this borkage. The issue is that, by default, argparse differs subtly from optparse in its handling of defaults for boolean options. Compare: >>> p = optparse.OptionParser() >>> p.add_option('--foo', action='store_true') >>> p.add_option('--bar', action='store_true', default=False) >>> p.parse_args([]) (, []) to: >>> p = argparse.ArgumentParser() >>> p.add_argument('--foo', action='store_true') >>> p.add_argument('--bar', action='store_true', default=False) >>> p.add_argument('--blaa', action='store_true', default=None) >>> p.parse_args([]) Namespace(bar=False, blaa=None, foo=False) i.e. unless you specify a default for a boolean option, optparse defaults to None whereas argparse defaults to False. To get the same optparse behaviour with argparse, you need default=None. Change-Id: Ifc92a834c4ba59e939d80ac5de24d7051232f5b5 commit fd4cad82e84f8c67644bb9fcbd9bc5fe735ff604 Author: Mark McLoughlin Date: Fri Nov 23 15:50:04 2012 +0000 Improve cfg's argparse sub-parsers support In order for sub-parsers to be useful, you need some way of knowing which sub-parser was chosen during argument parsing. It's pretty obvious from the current sub-parsers test case that we don't have a convenient interface for this. One way of doing it is to use the 'dest' argument when adding sub-parsers: >>> subparsers = parser.add_subparsers(dest='cmd') >>> subparsers.add_parser('a') >>> subparsers.add_parser('b') >>> parser.parse_args(['a']) Namespace(cmd='a') The most sensible way to map this into cfg concepts is to register sub-parsers as an Opt. This way, we can make name and argument values of the sub-parser as an attribute on the ConfigOpts object: >>> def add_parsers(subparsers): ... a = subparsers.add_parser('a') ... a.add_argument('id') ... b = subparsers.add_parser('b') ... >>> CONF.register_cli_opt(SubCommandOpt('cmd', handler=add_parsers)) True >>> CONF(['a', '10']) >>> CONF.cmd.name, CONF.cmd.id ('a', '10') The handler method is a bit awkward, but each time cfg is to parse command line args it takes all the registered opts and creates a new argparse parser. So we need to be able to re-add the sub-parsers each time. Change-Id: I01bfd01bf8853cf57a9248b1663eb3da142366a4 commit 18065f5050b00e19c36781902c218c3703b2e50d Author: Mark McLoughlin Date: Fri Nov 23 08:09:12 2012 +0000 Fix regression with cfg CLI arguments Fixes bug #1082279 Only options registered using register_cli_opt() should be available via the CLI, but since e42276a all options are added to the CLI. Also modify one of the existing unit tests to catch this problem. Change-Id: I742a4ae4e0fc17cd9ae5e4424c2edd38e2bc50a2 commit 3d4257fbd3dbfd7b64de22c2aae08a9e4cce9921 Author: Davanum Srinivas Date: Sat Nov 24 08:54:20 2012 -0500 Fix ListOpt to trim whitespace - throw in an extra strip() in the list parsing code - add a test case to verify that it works! Fixes LP #1079299 Change-Id: I4f0864c72ecd2569d0461c301acda395c87a93e0 commit f1133ad6da81ed1f4f99c163ba3bd49b9ccd3e16 Author: Mark McLoughlin Date: Fri Nov 23 20:25:12 2012 +0000 Add another duplicate opt test case This gets the code coverage of the tests back up to 100%. Change-Id: I737c1cfa52d10b3813237a9cb88b15211e0872c1 commit 877e625ca9fe1d93a924c8df6656fb7c56ccbb72 Author: Mark McLoughlin Date: Fri Nov 23 15:12:12 2012 +0000 Hide the GroupAttr conf and group attributes There's no reason why an option group shouldn't have options called 'group' or 'conf'. Add a test case which would have failed because the 'conf' attribute would have been a ConfigOpts instance and fix it by making those attributes private. Change-Id: Ic3e41a546c0d1b7b6aae04e1dbac2933ac661f57 commit 42376535a91460ea538b7c8c60efee30fcd28d21 Author: Mark McLoughlin Date: Fri Nov 23 11:45:04 2012 +0000 Fix broken --help with CommonConfigOpts Since we switched to argparse, the way help strings are interpolated have changed and broken --help with the options registered by CommonConfigOpts. Fix and add a new test case which would catch the issue. Change-Id: I10e42efe4721e22ff41d0efbf390c805ccb9a6a0 commit 39eb58f6f010d08d00d06c596594b320de1fd8a9 Author: Joe Heck Date: Sun Nov 11 21:00:42 2012 +0000 updating sphinx documentation * adding openstack theming (copied from keystone project theme) * updating .gitignore to ignore generated API docs * updated formatting in index.rst page * updaed openstack/common/processutils.py to match hacking docstring * updated docstrings to resolve sphinx warnings Change-Id: Ie89afe20eeab1efd2daf440fc65ccdf90f723c51 commit 6e3307be97ccba18793282582a6c1dd4509285fa Author: Mark McLoughlin Date: Mon Nov 12 16:26:08 2012 -0500 Don't reference argparse._StoreAction This is a private implementation detail of argparse, so we don't want to rely on it. Just sub-class Action instead. Change-Id: Icfcc782cc334d1bc1d4940bec23af48ead692a9d commit 33b6139fb2660fa230865d974f04156e3fe88760 Author: Mark McLoughlin Date: Mon Nov 12 16:26:02 2012 -0500 Fix minor coding style issue Use the same style of exception handling used everywhere else. Change-Id: I5436de1996f69ea6210f48c11ef231eb950ad21d commit 45613f3aa7259c1192ea58919d144d1ce0c97f38 Author: Mark McLoughlin Date: Mon Nov 12 16:26:00 2012 -0500 Remove ConfigCliParser class This sub-class of ArgumentParser isn't really justified anymore. Change-Id: I705224b6e18e4609a8e2deba283767233b0bd578 commit cbe7cfa46ab56ea98f08489e29cbb3949bcf37d5 Author: Mark McLoughlin Date: Mon Nov 12 16:25:59 2012 -0500 Add support for positional arguments argparse makes it awkward to implement the current cfg API where we simply return the leftover arguments to the caller. Very few cfg users actually rely on this functionality and for those cases it probably makes more sense for them to explicitly register positional arguments or sub-parsers. Add support for positional arguments via a 'required' Opt attribute: opt = StrOpt('foo', positional=True) conf.register_cli_opt(opt) conf(['bar']) conf.foo == 'bar' Change-Id: Iea746d710237e1ea26c1ef4871643941d1df09bd commit b5f84bf854b4ea45ca87ae8cb4124cf317162f77 Author: Mark McLoughlin Date: Mon Nov 12 16:25:58 2012 -0500 Use stock argparse behaviour for optional args optparse would print "Options" but argparse prints "optional arguments". The default argparse behaviour is fine, let's stick with that. Change-Id: Ib53a2581af9d776e9a7c1cd90eebe89b70034e57 commit 6d3ff9cc3c5c1b9b2ef6dcc7689553d19e3f85b3 Author: Mark McLoughlin Date: Mon Nov 12 16:25:57 2012 -0500 Use stock argparse --usage behaviour optparse would substitute the program name for %prog, but argparse requires %(prog)s. Also, optparse would print 'Usage:' whereas argparse prints 'usage:'. Neither optparse behaviour that's worth retaining, let's just use the default argparse behaviour. Change-Id: Ied2acb37c366f1a45aed72b6b76f11e2de23828e commit 17e3addb2b81ae0238d23e59133c2357d409144c Author: Mark McLoughlin Date: Mon Nov 12 16:25:55 2012 -0500 Use stock argparse --version behaviour optparse prints the version to stdout, argparse prints the version to stderr. There's no need to preserve the old optparse behaviour, let's just stick with argparse behaviour. Change-Id: Ie141c72112a63149d098afa9db55a95a309e79d7 commit fb800a211f85b20301a273e39e9288edf23d0de8 Author: Mark McLoughlin Date: Mon Nov 12 16:25:54 2012 -0500 Remove add_option() method argparse just has an add_argument() method, so there's no reason for us to keep add_option() around. Change-Id: I6f4be089ceaf0fd8c4c99565af392b445916172e commit 1ed027d07c150ad0cd1f9b6304e7a99c163243b9 Author: Mark McLoughlin Date: Mon Nov 12 16:25:52 2012 -0500 Completely remove cfg's disable_interspersed_args() The use case for disable_interspersed_args() is that nova-manage needs to be able to parse sub-commands. We now have a add_cli_subparsers() method which better supports this use case. Change-Id: I1fcd15889745fe4ddff0ac4bacf385004f9b61af commit a4fcc4cb5916f0123d47ccdfbf4f1fee1c629401 Author: Laurence Miao Date: Sat Oct 6 21:08:14 2012 +0800 argparse support for cfg * openstack/common/cfg.py Optparse is fading out since python 2.7, this patch will help openstack/common work on more advanded version of python(argparse). Now, disable_interspersed_args() has no effect. Added new method add_cli_subparsers, return argparse subparser, for usages such as subcommand. * tests/unit/test_cfg.py SubcommandTestCase added. Disabled test_disable_interspersed_args test entry for happiness of tox, temporarily. Modified test_help for port of argparse. * tools/pip-requires include argparse module for python 2.6 Change-Id: Ie5cbde1a92a92786d64dea0ddfcfbf288c29f960 commit 12c7d781471566f57c1884ce4c4978565913741b Author: David Ripton Date: Wed Oct 31 13:14:52 2012 -0400 Add a missing comma in a docstring. Change-Id: I1f75c7da1ab1543637198ecbb80a81b39ad35fde commit 85851dbec62a11458204a5b32c60d63637b4aa0e Author: Julien Danjou Date: Fri Oct 26 16:55:17 2012 +0200 cfg: fix required if option has a dash If an option has a dash in it and is required, the check fails because it tries to self._get() on the name (with dash) rather than the dest (with underscore). Change-Id: I6448019f70f98bc2e58a325d0cf9ce88b8bb085b Signed-off-by: Julien Danjou commit f4cf739349ed66d6944fd02b8f1c6c50bc23b4a9 Author: Mark McLoughlin Date: Mon Aug 13 11:35:25 2012 +0100 cfg: clean up None value handling Remove the need for an internal NoneValue class by making the existence of the 'default' or 'override' keys signify whether a default or override is set. Change-Id: Iacf49553df5ba8414307904a3ee334c7b8c55758 commit a88773ac6dcea042dc7e946316de013538969f0f Author: Vishvananda Ishaya Date: Fri Aug 10 14:28:59 2012 -0700 Allow set_default and set_override to use None The current implementation interprets set_default('foo', None) and set_override('foo', None) as 'clear the existing default or override', which makes it impossible to override a value with None. This patch adds support for overriding with a None value by adding a special internal class. set_override('foo', None) will now override the existing value with None. This is a slight change to the existing behavior, so this patch adds two calls for the old functionality of clearing defaults and overrides. Example syntax for the new calls are shown below: conf.clear_default('foo') conf.clear_override('foo') The patch also updates the tests to reflect the change in functionality and adds new tests to verify the new functionality. Fixes bug 1035478 Change-Id: Iee5e20e44da9bef6b86e0483ab0b48b625fe503c commit ae93f9f03b0e0c03d3606c4fdbcfc4e2d4cdd55b Author: Mark McLoughlin Date: Sat Aug 11 12:21:51 2012 +0100 Tilde expansion for --config-file and --config-dir Fixes bug #1012671 Allow a filename starting with ~ or ~user to be passed for --config-file or --config-dir. Change-Id: I67705401ed1c35c0cc2161095e36616552740aba commit bf799b73eabb104804a120f174c5fcaa9ef9d3bb Author: Mark McLoughlin Date: Tue Jul 31 12:16:28 2012 +0100 Add import_opt() method to ConfigOpts Related to blueprint cfg-global-object When using the global config object pattern, you often have modules which define options that are referenced in other options. So, for example if module A defined option 'foo' and module be needed to reference that option, you might do: import A print CONF.foo However, this makes it entirely unclear to the casual reader why module A was imported. Nova has a flags.DECLARE() function that helps with this problem by allowing you to do: flags.DECLARE('foo', 'A') The function simply imports module A and checks that the 'foo' option is now defined in the global config object. This is fine, but it is also implicit that this function applies to the global config object. Instead, let's do the following: CONF.import_opt('foo', 'A') Change-Id: I7b98f5be71068bbde70cc0eab991eaebb577de52 commit c8f7c3c93948a8db22d6d9cfef75241210be3e1a Author: Giampaolo Lauria Date: Fri Jul 20 16:41:45 2012 -0400 Modifies _is_opt_registered fcn to check for duplicate opts This change fixes bug 999307 Currently, the check for duplicate options is done by checking whether they are the same object. The proposed fix is to check whether all the object fields have the same value. Change-Id: I2b72d630a0c8821df1d81e25d316d8d9195be492 commit dcce372a4e8d29cb3a0c0a8e468a777a71de415f Author: Mark McLoughlin Date: Tue Jul 17 05:52:51 2012 +0100 cfg: allow empty config values Fixes bug #1025522 Commit 83044a7 caused this to stop working in Quantum: api_extensions_path = and could only be worked around with: api_extensions_path = "" Change-Id: I8c1a57225a2c135e6baf567b8e71d61e974da4e2 commit b16b92a83abdaaf3e3007e71533f23703e959f86 Author: Vincent Untz Date: Thu Jul 5 14:51:26 2012 +0200 cfg: Fix typo in documentation with with -> with Change-Id: I7a524c024b05639ec7ab4d57b6a52f70a95d2235 commit 0ddee649f3053ba738209920c310315f5d2d38ac Author: Gary Kotton Date: Sun Jun 17 04:05:37 2012 -0400 Update common code to support pep 1.3. bug 1014216 Change-Id: I3f8fa2e11c9d3f3d34fb20f65ce886bb9c94463d commit 9d2119aa388a20f3f758e013382e0e8224178608 Author: Johannes Erdfelt Date: Fri Jun 8 17:08:14 2012 +0000 Use 'is not None' instead of '!= None' Fixes bug 1010570 pep8 suggests the former over the latter Change-Id: Ice3a3b1cc2eea9228fffb4ee40fc360ff79054a3 commit c103186111bfbefb5cf8dd84f79cfb63273d18c4 Author: Russell Bryant Date: Wed Jun 6 21:57:14 2012 -0400 Fix a pep8 error. Change-Id: Iab7e703254a354764a5667425ef887b0afc89115 commit 38105a582eaa954cf306f4d4dfe1ca09df2419db Author: Vishvananda Ishaya Date: Wed Jun 6 11:26:18 2012 -0700 Adds support for bol and eol spaces to ini files * Fixes bug 1009639 * Adds tests Change-Id: Id00563dfcc6f143c3e86ec380d66cffc967b8c48 commit c377d8fafc2001b85152bb3688dd54758a1070ab Author: Joe Gordon Date: Mon May 21 18:17:35 2012 -0700 Add support to include config aliases Implements blueprint config-aliases * Supports loading deprecated aliased options from a config file * Supports using deprecated aliased CLI options * For MultiStrOpt Can use mix of name and alias Change-Id: I04678880bc8ee1f85335f5656367bd1437245c6e commit 072addef6f2740fe46f2aba63dd4f95a0a1299df Author: Kevin L. Mitchell Date: Mon Jun 4 10:27:36 2012 -0500 Fix pep8 errors. Fixes a couple of pep8 errors that appeared due to a pep8 tool update. Change-Id: Ida70b1fb962529d3a157f44dcf2e71af773a4431 commit 5b5a5ab240a4b82ddc128c3763b59e0ba51dd022 Author: Mark McLoughlin Date: Tue May 29 08:27:05 2012 +0100 cfg: add a global CONF object Implements blueprint cfg-global-object Add an instance of the CommonConfigOpts class to the cfg module's global namespace. The usage pattern is: from openstack.common import cfg opts = [ cfg.StrOpt('foo', default='blaa'), cfg.StrOpt('bar', default='blaa'), ] CONF = cfg.CONF CONF.register_opts(opts) def do_something_later(): print CONF.foo, CONF.bar def main(): CONF(project='pulsar') Change-Id: I77e87b1e186c243b2638a4b1c202f865249dafce commit a4b2511dae14ea1cce7017c51d2730c870995cb2 Author: Mark McLoughlin Date: Tue May 29 08:27:05 2012 +0100 cfg: add generators for iterating over all options We have a few places now where we do: for opt in self.opts: foo(opt) for group in self.groups: for opt in group.opts: foo(opt, group) Use generators to turn this into simply: for opt, group in self.all_opts(): foo(opt, group) Change-Id: I7a32779c20caeb1bacb85528d7e36c3c18c6c16a commit 75b581e93b41cec82da982d3278af97be6bc773f Author: Mark McLoughlin Date: Tue May 29 08:27:05 2012 +0100 cfg: move constructor args to __call__() args In order to effectively use a global ConfigOpts object, you need to be able to initialize the global object with none of the information we currently require at construction. By moving those constructor args to the __call__() method, we enable the global object usage model but also make the API generally more flexible. For example, you can now reset the object and re-use it for parsing a different set of config files with the same options. There are a couple of other minor behavior changes as a result: - print_usage() and print_help() no longer works before the object has been called to parse options - registration of duplicate short options are no longer detected until the options are parsed - the --config-file and --config-dir options aren't registered until just before parsing the options since the default set of config files can be specified at that time - find_file() can't be used until after the options have been parsed, again because of the late registration of --config-file and --config-dir Finally, an unregister_opt() method is added to support the re-registeration of the --config-file and --config-dir options. Change-Id: I650d8e299e92cbc5d10da47a7ce1b73ca8066bd0 commit c7e16af82215234f8d8c8f5617202f2e77a3e005 Author: Russell Bryant Date: Wed May 16 11:34:29 2012 -0400 Run pep8 on tests. I noticed that pep8 wasn't running on the tests. This patch fixes that, as well as a couple of pep8 errors in test_cfg. Change-Id: I4429bfe6813a2e9394efb1753cbebbadb9f23833 commit 09327ad97206a9856ced348cdd6a54cc66dcc5a8 Author: Joe Gordon Date: Mon May 14 13:36:42 2012 -0700 Alphabetize imports in openstack/common/cfg.py In preparation for enabling alphabetized import checking in Nova Change-Id: I709fca6a121ba44df193757e5ad838de710c2f15 commit 6c9c437f29de055a600ecace43ef782c16ebf877 Author: Mark McLoughlin Date: Sat May 12 11:52:53 2012 +0100 cfg: make reset() clear defaults and overrides Fixes bug #998396 Both Nova and Keystone need to clear the overrides on their config object between test runs. It's reasonable to expect the reset() method would do this, so let's make it so. Also add a clear() method with the old behaviour. Change-Id: I192c5bb07e81f0fb844fa2fd429dc2e7133800de commit cd5456bc78917751fe511a66b4115844a6e94976 Author: Mark McLoughlin Date: Thu May 10 14:25:19 2012 +0100 cfg: automatically create option groups Implements blueprint cfg-auto-create-groups Remove the restriction that groups must be explicitly created. Often you only need a group to have a name (not e.g. a title or help string) so we can easily just auto-create groups for that case. Change-Id: I150ab3900e3aad0068b93487c8d396d21d26cfea commit 358c9aa23b226d8f4ae8da3add1a7595126aeb32 Author: Mark McLoughlin Date: Thu May 10 14:25:19 2012 +0100 cfg: allow options to be marked as required Implements blueprint cfg-required-options Add a 'required' flag to option schemas: StrOpt('foo', required=True) which causes a RequiredOptError exception to be raised if the user fails to supply a value for the option on the CLI or in a config file. Change-Id: Ied7bb25f0c1582c4991d0f212f4871b9358b73fb commit 4e67d716e9ee64468fbccd32d40f33e805227aac Author: Mark McLoughlin Date: Thu May 10 14:25:19 2012 +0100 cfg: use a list comprehension instead of map() Change-Id: Iaccb71d83d957aae77fa0f6bc71952b899d3a159 commit ecdd78bf99d8f8fcf06c15a9a80ddb8e54364e36 Author: Mark McLoughlin Date: Tue May 1 08:59:18 2012 +0100 New ConfigOpts.find_file() for locating conf files Most services have the need to locate files like api-paste.ini or policy.json. This new method attempts to find these files by looking alongside the config files already parsed by ConfigOpts and, failing that, falls back to a standard set of directories. Change-Id: I95897816485b88f78854df194cab7872d7c5452a commit 59610964dd35b730582cbdb08671ae3bdadf2141 Author: Eoghan Glynn Date: Mon Apr 23 21:06:56 2012 +0100 Support for directory source of config files Implements bp cfg-config-dir Allow multiple config files to be pulled in from a config directory, as opposed to individual config files being explicitly enumerated. This logic is enabled using the --config-dir=/path/to/config CLI option, causing config to be retrived from all matching /path/to/config/*.conf files. Sections may be re-opened across config files, and all config items must reside in an explicitly specified section (i.e. it does not default to [DEFAULT]). This behavior is unchanged. Change-Id: Ia29dffe82dfb4742dcf3e8d36b376d906a2492cf commit 122dc40da12c86de673ea7a7d73f4b35f3748cf1 Author: Brian Waldon Date: Wed Apr 25 16:11:45 2012 -0700 Provide file extension when when looking for files * Allow an extension to be passed to find_config files, defaulting to '.conf' Change-Id: I022a3b28d9067158e9ed0da741a5e72cb73af167 commit cb4acbfa481831c5e3e0494f1ef762c416603b19 Author: Mark McLoughlin Date: Tue Apr 24 14:56:07 2012 +0100 Some refactoring of the cfg cache A fairly misc bunch of changes: - init cache before registering config-file and just let register_cli_opt() clear the empty cache - use @__clear_cache on set_default() and set_override() since these are just used by the unit tests and doing so allows us to kill _remove_from_cache() - use @__clear_cache on reset() too - remove recursion from _get() and the substitute param - just use (group_name, opt_name) as the cache key Change-Id: I66934e748eca9ec03e44d7f80a7e10d96a77d8eb commit ad859c16310bce0ca38b6d57df7000e58792d45e Author: Yuriy Taraday Date: Sat Apr 14 01:16:35 2012 +0400 Add caching to openstack.common.cfg Speedup of 'nova list' benchmark by up to 40%, eliminates 3 lines in top-10 cProfile methods. Change-Id: I2d4636f94d88b4a7e38d1565fdd4d1b8a89e560e commit aff6ff33fdeae4f3f95258c1eb8f5363eaefb9d3 Author: Rick Harris Date: Thu Mar 29 04:51:09 2012 +0000 Typofix, OptionGroup should be OptGroup. Change-Id: I67473bb847759ce719876e08f8a894e000f11bb3 commit fbdf1acb681df10e2723fa2b1dcc8626b311bc78 Author: Rick Harris Date: Wed Mar 28 18:37:16 2012 +0000 Use absolute import for iniparser. Fixes bug 967400 Change-Id: I0c028f6b5285cd641dedbcea3132224e404b004e commit 4b81c673c5f3334753c8bb9ecafa24edbfc0ba90 Author: Johannes Erdfelt Date: Wed Mar 14 22:24:14 2012 +0000 Finish implementing MultiStrOpt Fixes bug 955308 Previously only multiple string options from the CLI were supported. This change adds support for config files too and merges the results from both CLI and config files. Change-Id: I642408c03ed295fac050105fd4380940e876f228 commit 0e4f86ec0998779b1ef4a1ae72a985d823886ff4 Author: Eoghan Glynn Date: Thu Mar 22 16:54:26 2012 +0000 Avoid leaking secrets into config logging. Implements bp cfg-password-options Allow options to be declared secret so that their value is obfuscated before logging. Change-Id: Ie2168d218b029d9c12fa5b48342cd5b17b2cc77a commit dd018d72b9b38992b8ebb8645beb11958e527b47 Author: Joe Gordon Date: Tue Mar 13 17:25:19 2012 -0700 Fix bug 954488 Change-Id: I99b764310c575e70aff4a6790e8ba8f55e43deeb commit ef808088f6e5f3b04b65e0b5b2bd18902f6a235b Author: Doug Hellmann Date: Fri Mar 9 11:11:16 2012 +0000 fix restructuredtext formatting in docstrings blueprint sphinx-doc-cleanup bug 94516 - Correct parameter declarations, list formatting, cross-references, etc. - We don't need "let" in generate_autodoc_index.sh since we aren't doing math. - Change conf.py to not prefix class and function names with full namespace in generated output to save width on the screen. Change-Id: I9adc8681951913fd291d03e7142146e9d46841df commit bdebe01b1dc38cc6b0db598b070d97017eb39779 Author: Mark McLoughlin Date: Wed Feb 22 16:29:59 2012 +0000 Add ConfigOpts.print_help() Keystone uses this optparse method. Change-Id: Ic840b2fb2234a12cd94ca671a8d90cd2affe3a5e commit f8e620d53f00194d6bd137963e7ea3c160566773 Author: Zhongyue Luo Date: Fri Jan 20 01:10:58 2012 -0500 cfg: fix a small comment typo Change-Id: I2646d7e674ef3d1759558e820f051cc5e7f3b4ae commit 8c589d5634c947a07c68bdfe69e6c063bda49528 Author: Zhongyue Luo Date: Sun Feb 12 16:04:21 2012 +0800 cfg: unneeded multiple inheritance Fixed bug #927650 In python=<2.6, collections.Mapping inherits from collections.Sized, collections.Iterable, and collections.Container which are also subclasses of object. Change-Id: I6238c683324127abd9fb637748a10b6bdb2961e0 commit b54d839ba9cdc12778368c5037a8a2f0217f0969 Author: Zhongyue Luo Date: Sat Feb 11 20:18:08 2012 +0800 PEP8 cleanup (openstack-common) Fixes bug #930625 Remove backslash continuations in openstack-common. Fix type checking taboos. Change-Id: I49ddb9ff5fa5af760dcfccb52cb4793b71e02f19 commit 76a2802013b29c2f5313b04325a3e9f075fff09b Author: Zhongyue Luo Date: Fri Feb 10 17:21:32 2012 +0000 Backslash continuations (misc.) Fixes bug #925166 This patch for packages which have few backslash continuations. Follow up patches will be for packages network, scheduler, virt, db/sqlalchemy, tests, and api/openstack. Change-Id: I4200010b47b33fa8b9115b5d379b543200f6668d commit 93e99f7bb50b7b5f1b908cb8f633a63a85fdf50d Author: Mark McLoughlin Date: Fri Feb 10 17:14:52 2012 +0000 Disable ConfigParser interpolation (lp#930270) This breaks e.g. volume_name_template=volume-%08x instance_name_template=instance-%08x and is not part of the API contract anyway. We use $opt based value interpolation. Change-Id: I7ba566ae7c9a77322b52c67c5e1ffbffb760f0fc commit 75b173c0b567ae7e975747b74cd983806027f8ac Author: Anthony Young Date: Thu Feb 2 23:28:24 2012 +0000 Implements blueprint separate-nova-volumeapi [...] ** Removes flag osapi_extension and replaces with osapi_compute_extension and osapi_volume_extension [...] Change-Id: I4c2e57c3cafd4e1a9e2ff3ce201c8cf28326afcd commit 93d043c77c51804f69bb9b6e30359d5713b6e671 Author: Vishvananda Ishaya Date: Thu Feb 2 23:25:53 2012 +0000 Makes common/cfg.py raise AttributeError * fixes bug 915039 * includes test Change-Id: I67b886be3b5af3763f52fffe54085975d61d61eb commit 6f986bdcd603777541e38c8f5e4acb7991f3a2f3 Author: lzyeval Date: Thu Feb 2 23:24:46 2012 +0000 PEP8 type comparison cleanup Fixes bug #910295 The None, True, and False values are singletons. All variable *comparisons* to singletons should use 'is' or 'is not'. All variable *evaluations* to boolean should use 'if' or 'if not'. "== None", "== True", "== False", and "!= None" comparisons in sqlalchemy's where(), or_(), filter(), and_(), and select() functions should not be changed. Incorrect comparisons or evaluations in comments were not changed. Change-Id: I087f0883bf115b5fe714ccfda86a794b9b2a87f7 commit b0a32ca9e4e49103d8b6c615fddb2febfe53c595 Author: Mark McLoughlin Date: Fri Jan 27 20:01:39 2012 +0000 Add the Mapping interface to cfg.ConfigOpts Implements blueprint cfg-mapping interface With cfg, option values are accessed via attributes on ConfigOpts objects e.g. conf = ConfigOpts() conf.register_opt(StrOpt('foo')) conf() print conf.foo One use case that isn't easily supported with option values represented this way is iterating over all the registered options. Standard interfaces for listing attributes on an object aren't suitable because they will list more than just the options. For this use case alone, it's worth having ConfigOpts implement the mapping interface. That way we can do e.g. for opt, value in conf.items(): print "Option %s = %s" % (opt, value) It's interesting to compare argparse's approach to this problem - option values are attributes on a Namespace object which has no attributes or methods to pollute the namespace of option names. This is a nice approach, but would mean that we would be passing around both a ConfigOpts object and a Namespace object. That's a bit too much overhead, and the mapping interface provides a usable workaround where there is a conflict. Change-Id: Ic113919a20291048f962999229c76884ebdd5ad8 commit 23e2286135e1454a7dafdea7ff239abf594c07ce Author: Mark McLoughlin Date: Mon Jan 23 15:21:50 2012 +0000 Add cfg test case for recursive substitution i.e. test that if blaa='blaa', foo='$blaa' and bar='$foo' that the value of bar after substitutions is 'blaa' Change-Id: I01d370832a871603b7cb47bfb3546f6aaad8c34d commit ac1c384499049fedec1bc934697d406f6854f0c4 Author: Mark McLoughlin Date: Mon Jan 23 10:42:07 2012 +0000 Add support to cfg for disabling interspersed args Implements blueprint cfg-disable-interspersed-args Nova currently relies on cfg being implemented with optparse because it uses optparse's disable_interspersed_args() The use case for this is if you do: $> nova-manage --verbose create --project foo --user bar you want invoking ConfigOpts() to return: ['create', '--project', 'foo', '--user', 'bar'] as the "extra" args rather than aborting when it doesn't recognize the --project arg. This is a reasonable use case for cfg to support and it should just have {disable,enable}_interspersed_args() methods. If we ever switch from optparse to argparse, we'll do something like this: parser.add_argument('--verbose') ... parser.add_argument( 'extra_args', nargs=argparse.REMAINDER if disable_interspersed_args else '*') ... ns = parser.parse_args(...) extra_args = ns.extra_args i.e. we will need an 'extra_args' multi-value positional argument in any case and we'll just pass nargs=REMAINDER if we want trailing options to be included in the extra args. Change-Id: I3ecb7dc18230327cf5aaaa7d832224e64aafa40c commit a877ff9d785a4ae8df5f52d8b3bd79670367cd8e Author: Mark McLoughlin Date: Thu Jan 12 07:04:39 2012 +0000 Get cfg test cases to 100% * Test cfg.find_config_files() sys.argv usage * Test boolean values in cfg config files * Finish off incomplete cfg bad value test case * Test register_opts() and register_cli_opts() * Test the quiet ignoring of option/group re-registration * Test cfg print_usage() * Test explicit option group titles Change-Id: Icbe4b7c48d4785551f06873821d1be758adf942c commit 9b4801f236d8613a336b976648c7a6c51c6e411e Author: Mark McLoughlin Date: Thu Jan 12 06:55:25 2012 +0000 Add cfg test case for exceptions' __str__ methods Increases coverage from 93% to 97% Change-Id: I6a41b31e29238831fe2a888d5d64dc0bffd770c0 commit b8ce5777af175bbe7c47896e80ecd378407c0393 Author: Mark McLoughlin Date: Thu Jan 12 06:33:00 2012 +0000 Fix some cfg test case naming conflicts As pointed out by Vish, there are duplicates of: OverridesTestCase::test_default_override OverridesTestCase::test_override SadPathTestCase::test_conf_file_not_found Also, rename the ConfigFileOptsTestCase so it is obvious they don't clash with the tests by the same name in CliOptsTestCase. Change-Id: I1d650d05d32501623cfed8f0b6399858d101ae02 commit ab488789f514fc6584913bf7b81945b59a72dbf5 Author: Mark McLoughlin Date: Tue Jan 10 20:51:49 2012 +0000 Add new cfg module As described here: http://wiki.openstack.org/CommonConfigModule The module implements an API for defining configuration options and reading values for those options that a user may have set in a config file or on the command line. The module is successfully in use in both Nova and Glance. Some work remains in Nova to switch from using it under a gflags compatible shim layer, but Glance is using it fully. There doesn't appear to be any blockers to other projects moving over to it fairly easily. Swift would perhaps be the next project to tackle. Just to go through potential future compatibility concerns: - Nova (the scroundrel) hackily uses the private ConfigOpts::_oparser in order to disable interspersed args. This was just for nova-manage and can probably be resolved some other way. In any case, Nova shouldn't switch to openstack-common's cfg API until it removes this hack. - the CommonConfigOpts subclass set of logging related options is perhaps assuming too much about what configuration options should be common across all the projects. However, it seems a fairly sane set and the worst that can happen is that projects avoid using it. - the parameters to the Opt constructor fairly closely mirror optparse, but they're fairly generic and shouldn't prevent us from switching to e.g. argparse - stuff like %prog expansion in the ConfigOpt's usage ctor param is a similar concern, but it's a very minor concern. - find_config_files() search path is perhaps too much policy for openstack-common; however, it is probably as generic as it could be and projects which need a different policy can just not use the function On the whole, I think we're in good shape wrt future compatibility. Change-Id: I279a9db7806d80aff3b9b085b4a9e4fb193662f9oslo.config-1.2.1/.testr.conf0000664000175300017540000000032212221001311017132 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list oslo.config-1.2.1/test-requirements.txt0000664000175300017540000000050012221001311021303 0ustar jenkinsjenkins00000000000000hacking>=0.5.6,<0.8 discover fixtures>=0.3.14 python-subunit testrepository>=0.0.17 testscenarios>=0.4 testtools>=0.9.32 # when we can require tox>= 1.4, this can go into tox.ini: # [testenv:cover] # deps = {[testenv]deps} coverage coverage>=3.6 # this is required for the docs build jobs sphinx>=1.1.2 oslo.sphinx oslo.config-1.2.1/oslo.config.egg-info/0000775000175300017540000000000012221001336020770 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/oslo.config.egg-info/dependency_links.txt0000664000175300017540000000000112221001336025036 0ustar jenkinsjenkins00000000000000 oslo.config-1.2.1/oslo.config.egg-info/SOURCES.txt0000664000175300017540000000151512221001336022656 0ustar jenkinsjenkins00000000000000.testr.conf AUTHORS CONTRIBUTING.rst ChangeLog LICENSE MANIFEST.in README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/cfg.rst doc/source/conf.py doc/source/configopts.rst doc/source/exceptions.rst doc/source/helpers.rst doc/source/index.rst doc/source/opts.rst doc/source/parser.rst oslo/__init__.py oslo.config.egg-info/PKG-INFO oslo.config.egg-info/SOURCES.txt oslo.config.egg-info/dependency_links.txt oslo.config.egg-info/namespace_packages.txt oslo.config.egg-info/not-zip-safe oslo.config.egg-info/requires.txt oslo.config.egg-info/top_level.txt oslo/config/__init__.py oslo/config/cfg.py oslo/config/iniparser.py tests/__init__.py tests/test_cfg.py tests/test_iniparser.py tests/utils.py tests/testmods/__init__.py tests/testmods/bar_foo_opt.py tests/testmods/baz_qux_opt.py tests/testmods/blaa_opt.pyoslo.config-1.2.1/oslo.config.egg-info/top_level.txt0000664000175300017540000000000512221001336023515 0ustar jenkinsjenkins00000000000000oslo oslo.config-1.2.1/oslo.config.egg-info/not-zip-safe0000664000175300017540000000000112221001314023212 0ustar jenkinsjenkins00000000000000 oslo.config-1.2.1/oslo.config.egg-info/PKG-INFO0000664000175300017540000000156012221001336022067 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: oslo.config Version: 1.2.1 Summary: Oslo Configuration API Home-page: https://launchpad.net/oslo Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Oslo Configuration Library ========================== The Oslo configuration API supports parsing command line arguments and .ini style configuration files. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 oslo.config-1.2.1/oslo.config.egg-info/namespace_packages.txt0000664000175300017540000000000512221001336025316 0ustar jenkinsjenkins00000000000000oslo oslo.config-1.2.1/oslo.config.egg-info/requires.txt0000664000175300017540000000000312221001336023361 0ustar jenkinsjenkins00000000000000sixoslo.config-1.2.1/setup.py0000664000175300017540000000143212221001311016561 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr>=0.5.21,<1.0'], pbr=True) oslo.config-1.2.1/README.rst0000664000175300017540000000023612221001311016537 0ustar jenkinsjenkins00000000000000Oslo Configuration Library ========================== The Oslo configuration API supports parsing command line arguments and .ini style configuration files. oslo.config-1.2.1/requirements.txt0000664000175300017540000000001512221001311020327 0ustar jenkinsjenkins00000000000000argparse six oslo.config-1.2.1/tox.ini0000664000175300017540000000074712221001311016372 0ustar jenkinsjenkins00000000000000[tox] distribute = False envlist = py26,py27,py33,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:cover] setenv = VIRTUAL_ENV={envdir} commands = python setup.py testr --coverage [testenv:venv] commands = {posargs} [flake8] show-source = True exclude = .tox,dist,doc,*.egg,build builtins = _ oslo.config-1.2.1/PKG-INFO0000664000175300017540000000156012221001336016155 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: oslo.config Version: 1.2.1 Summary: Oslo Configuration API Home-page: https://launchpad.net/oslo Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Oslo Configuration Library ========================== The Oslo configuration API supports parsing command line arguments and .ini style configuration files. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 oslo.config-1.2.1/doc/0000775000175300017540000000000012221001336015623 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/doc/source/0000775000175300017540000000000012221001336017123 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/doc/source/helpers.rst0000664000175300017540000000023312221001311021306 0ustar jenkinsjenkins00000000000000---------------- Helper Functions ---------------- .. currentmodule:: oslo.config.cfg .. autofunction:: find_config_files .. autofunction:: set_defaults oslo.config-1.2.1/doc/source/conf.py0000664000175300017540000000407712221001311020423 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'oslo.sphinx'] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. # templates_path = [] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.config' copyright = u'2013, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1" html_last_updated_fmt = os.popen(git_cmd).read() # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, '%s Documentation' % project, 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} oslo.config-1.2.1/doc/source/index.rst0000664000175300017540000000574512221001311020770 0ustar jenkinsjenkins00000000000000oslo.config =========== An OpenStack library for parsing configuration options from the command line and configuration files. Contents ======== .. toctree:: :maxdepth: 2 cfg opts configopts helpers parser exceptions Release Notes ============= 1.2.0 ----- * 1223667_: Fix DictOpt to split only the first colon * 1228995_: Disallow duplicate keys in DictOpt * Explicit version removed from setup.cfg * Dependency version updates .. _1223667: https://bugs.launchpad.net/oslo/+bug/1223667 .. _1228995: https://bugs.launchpad.net/oslo/+bug/1228995 1.2.0a4 ------- * Add auto-create support for OptGroup instances (see review 41865_) * Publish full API docs to docs.openstack.org_ * Finished Python 3 support * 1196601_: Raise an exception if print_help() is called before __call__ * Fix DeprecatedOpt equality test * Use oslo.sphinx .. _41865: https://review.openstack.org/41865 .. _1196601: https://bugs.launchpad.net/oslo/+bug/1196601 .. _docs.openstack.org: http://docs.openstack.org/developer/oslo.config/ 1.2.0a3 ------- * 1176817_: Fix the priority of CLI args vs config file values * 1123043_: New 'choices' param to StrOpt constructor * cfg-reload-config-files_: Add new ConfigOpts.reload_config_files() method * 1194742_: Fix regression which meant we weren't registering our namespace package * 1185959_: Make --help output order alphabetical * More progress on python3 support * Fix obscure cache clearing race condition * Move from tools/pip-requires to requirements.txt * Include missing .testr.conf in dist tarball .. _1176817: https://bugs.launchpad.net/oslo/+bug/1176817 .. _1123043: https://bugs.launchpad.net/oslo/+bug/1123043 .. _cfg-reload-config-files: https://blueprints.launchpad.net/oslo/+spec/cfg-reload-config-files .. _1194742: https://bugs.launchpad.net/oslo/+bug/1194742 .. _1185959: https://bugs.launchpad.net/oslo/+bug/1185959 1.2.0a2 ------- * Fix MultiConfigParser API breakage in 1.2.0a1 1.2.0a1 ------- * Solid progress has been made adding Python 3 support. * cfg-lowercase-groups_: uppercase section names in config files are now normalized to lowercase. * Support has been added for dictionary style options with the ``DictOpt`` class. * Multiple deprecated option names per option are now supported via the ``deprecated_opts`` argument. * The package build process now uses pbr_. * The package tests are now run using testr_. * The package coding style checks are now performed using hacking_. .. _cfg-lowercase-groups: https://blueprints.launchpad.net/oslo/+spec/cfg-lowercase-groups .. _pbr: http://docs.openstack.org/developer/pbr/ .. _testr: https://wiki.openstack.org/wiki/Testr .. _hacking: https://pypi.python.org/pypi/hacking 1.1.1 ----- * 1160922_: Fix set_defaults() to handle multiple arguments * 1175096_: Fix the title argument to ``OptGroup`` .. _1160922: https://bugs.launchpad.net/oslo/+bug/1160922 .. _1175096: https://bugs.launchpad.net/oslo/+bug/1175096 Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` oslo.config-1.2.1/doc/source/opts.rst0000664000175300017540000000054712221001311020641 0ustar jenkinsjenkins00000000000000------------------ Option Definitions ------------------ .. currentmodule:: oslo.config.cfg .. autoclass:: Opt .. autoclass:: StrOpt .. autoclass:: BoolOpt .. autoclass:: IntOpt .. autoclass:: FloatOpt .. autoclass:: ListOpt .. autoclass:: DictOpt .. autoclass:: MultiStrOpt .. autoclass:: DeprecatedOpt .. autoclass:: SubCommandOpt .. autoclass:: OptGroup oslo.config-1.2.1/doc/source/parser.rst0000664000175300017540000000034112221001311021140 0ustar jenkinsjenkins00000000000000------------ File Parsing ------------ .. autoclass:: oslo.config.iniparser.BaseParser .. autoclass:: oslo.config.cfg.ConfigParser :members: parse .. autoclass:: oslo.config.cfg.MultiConfigParser :members: read, get oslo.config-1.2.1/doc/source/configopts.rst0000664000175300017540000000021312221001311022015 0ustar jenkinsjenkins00000000000000-------------------- The ConfigOpts Class -------------------- .. currentmodule:: oslo.config.cfg .. autoclass:: ConfigOpts :members: oslo.config-1.2.1/doc/source/cfg.rst0000664000175300017540000000011612221001311020403 0ustar jenkinsjenkins00000000000000-------------- The cfg Module -------------- .. automodule:: oslo.config.cfg oslo.config-1.2.1/doc/source/exceptions.rst0000664000175300017540000000070112221001311022025 0ustar jenkinsjenkins00000000000000---------- Exceptions ---------- .. currentmodule:: oslo.config.cfg .. autoexception:: Error .. autoexception:: ArgsAlreadyParsedError .. autoexception:: NoSuchOptError .. autoexception:: NoSuchGroupError .. autoexception:: DuplicateOptError .. autoexception:: RequiredOptError .. autoexception:: TemplateSubstitutionError .. autoexception:: ConfigFilesNotFoundError .. autoexception:: ConfigFileParseError .. autoexception:: ConfigFileValueError oslo.config-1.2.1/MANIFEST.in0000664000175300017540000000035212221001311016605 0ustar jenkinsjenkins00000000000000include AUTHORS include ChangeLog include LICENSE include README.rst include tox.ini .testr.conf recursive-include tests * recursive-include tools * recursive-include doc * exclude .gitignore exclude .gitreview global-exclude *.pyc oslo.config-1.2.1/LICENSE0000664000175300017540000002665212221001311016067 0ustar jenkinsjenkins00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. --- License for python-keystoneclient versions prior to 2.1 --- All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. oslo.config-1.2.1/setup.cfg0000664000175300017540000000151612221001336016702 0ustar jenkinsjenkins00000000000000[metadata] name = oslo.config author = OpenStack author-email = openstack-dev@lists.openstack.org summary = Oslo Configuration API description-file = README.rst home-page = https://launchpad.net/oslo classifier = Development Status :: 4 - Beta Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 [files] packages = oslo oslo.config namespace_packages = oslo [global] setup-hooks = pbr.hooks.setup_hook [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 oslo.config-1.2.1/oslo/0000775000175300017540000000000012221001336016032 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/oslo/config/0000775000175300017540000000000012221001336017277 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/oslo/config/__init__.py0000664000175300017540000000121212221001311021375 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. oslo.config-1.2.1/oslo/config/cfg.py0000664000175300017540000023363612221001311020416 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. r""" Configuration options which may be set on the command line or in config files. The schema for each option is defined using the Opt sub-classes, e.g.: :: common_opts = [ cfg.StrOpt('bind_host', default='0.0.0.0', help='IP address to listen on'), cfg.IntOpt('bind_port', default=9292, help='Port number to listen on') ] Options can be strings, integers, floats, booleans, lists, or 'multi strings' or 'key/value pairs' (dictionary) :: enabled_apis_opt = cfg.ListOpt('enabled_apis', default=['ec2', 'osapi_compute'], help='List of APIs to enable by default') DEFAULT_EXTENSIONS = [ 'nova.api.openstack.compute.contrib.standard_extensions' ] osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension', default=DEFAULT_EXTENSIONS) Option schemas are registered with the config manager at runtime, but before the option is referenced:: class ExtensionManager(object): enabled_apis_opt = cfg.ListOpt(...) def __init__(self, conf): self.conf = conf self.conf.register_opt(enabled_apis_opt) ... def _load_extensions(self): for ext_factory in self.conf.osapi_compute_extension: .... A common usage pattern is for each option schema to be defined in the module or class which uses the option:: opts = ... def add_common_opts(conf): conf.register_opts(opts) def get_bind_host(conf): return conf.bind_host def get_bind_port(conf): return conf.bind_port An option may optionally be made available via the command line. Such options must be registered with the config manager before the command line is parsed (for the purposes of --help and CLI arg validation):: cli_opts = [ cfg.BoolOpt('verbose', short='v', default=False, help='Print more verbose output'), cfg.BoolOpt('debug', short='d', default=False, help='Print debugging output'), ] def add_common_opts(conf): conf.register_cli_opts(cli_opts) The config manager has two CLI options defined by default, --config-file and --config-dir:: class ConfigOpts(object): def __call__(self, ...): opts = [ MultiStrOpt('config-file', ...), StrOpt('config-dir', ...), ] self.register_cli_opts(opts) Option values are parsed from any supplied config files using oslo.config.iniparser. If none are specified, a default set is used e.g. glance-api.conf and glance-common.conf:: glance-api.conf: [DEFAULT] bind_port = 9292 glance-common.conf: [DEFAULT] bind_host = 0.0.0.0 Option values in config files override those on the command line. Config files are parsed in order, with values in later files overriding those in earlier files. The parsing of CLI args and config files is initiated by invoking the config manager e.g.:: conf = ConfigOpts() conf.register_opt(BoolOpt('verbose', ...)) conf(sys.argv[1:]) if conf.verbose: ... Options can be registered as belonging to a group:: rabbit_group = cfg.OptGroup(name='rabbit', title='RabbitMQ options') rabbit_host_opt = cfg.StrOpt('host', default='localhost', help='IP/hostname to listen on'), rabbit_port_opt = cfg.IntOpt('port', default=5672, help='Port number to listen on') def register_rabbit_opts(conf): conf.register_group(rabbit_group) # options can be registered under a group in either of these ways: conf.register_opt(rabbit_host_opt, group=rabbit_group) conf.register_opt(rabbit_port_opt, group='rabbit') If no group attributes are required other than the group name, the group need not be explicitly registered e.g. def register_rabbit_opts(conf): # The group will automatically be created, equivalent calling:: # conf.register_group(OptGroup(name='rabbit')) conf.register_opt(rabbit_port_opt, group='rabbit') If no group is specified, options belong to the 'DEFAULT' section of config files:: glance-api.conf: [DEFAULT] bind_port = 9292 ... [rabbit] host = localhost port = 5672 use_ssl = False userid = guest password = guest virtual_host = / Command-line options in a group are automatically prefixed with the group name:: --rabbit-host localhost --rabbit-port 9999 Option values in the default group are referenced as attributes/properties on the config manager; groups are also attributes on the config manager, with attributes for each of the options associated with the group:: server.start(app, conf.bind_port, conf.bind_host, conf) self.connection = kombu.connection.BrokerConnection( hostname=conf.rabbit.host, port=conf.rabbit.port, ...) Option values may reference other values using PEP 292 string substitution:: opts = [ cfg.StrOpt('state_path', default=os.path.join(os.path.dirname(__file__), '../'), help='Top-level directory for maintaining nova state'), cfg.StrOpt('sqlite_db', default='nova.sqlite', help='file name for sqlite'), cfg.StrOpt('sql_connection', default='sqlite:///$state_path/$sqlite_db', help='connection string for sql database'), ] Note that interpolation can be avoided by using '$$'. Options may be declared as required so that an error is raised if the user does not supply a value for the option. Options may be declared as secret so that their values are not leaked into log files:: opts = [ cfg.StrOpt('s3_store_access_key', secret=True), cfg.StrOpt('s3_store_secret_key', secret=True), ... ] This module also contains a global instance of the ConfigOpts class in order to support a common usage pattern in OpenStack:: from oslo.config import cfg opts = [ cfg.StrOpt('bind_host', default='0.0.0.0'), cfg.IntOpt('bind_port', default=9292), ] CONF = cfg.CONF CONF.register_opts(opts) def start(server, app): server.start(app, CONF.bind_port, CONF.bind_host) Positional command line arguments are supported via a 'positional' Opt constructor argument:: >>> conf = ConfigOpts() >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True)) True >>> conf(['a', 'b']) >>> conf.bar ['a', 'b'] It is also possible to use argparse "sub-parsers" to parse additional command line arguments using the SubCommandOpt class: >>> def add_parsers(subparsers): ... list_action = subparsers.add_parser('list') ... list_action.add_argument('id') ... >>> conf = ConfigOpts() >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers)) True >>> conf(args=['list', '10']) >>> conf.action.name, conf.action.id ('list', '10') """ import argparse import collections import copy import functools import glob import itertools import logging import os import string import sys import six from six import moves from oslo.config import iniparser LOG = logging.getLogger(__name__) class Error(Exception): """Base class for cfg exceptions.""" def __init__(self, msg=None): self.msg = msg def __str__(self): return self.msg class NotInitializedError(Error): """Raised if parser is not initialized yet.""" def __str__(self): return "call expression on parser has not been invoked" class ArgsAlreadyParsedError(Error): """Raised if a CLI opt is registered after parsing.""" def __str__(self): ret = "arguments already parsed" if self.msg: ret += ": " + self.msg return ret class NoSuchOptError(Error, AttributeError): """Raised if an opt which doesn't exist is referenced.""" def __init__(self, opt_name, group=None): self.opt_name = opt_name self.group = group def __str__(self): if self.group is None: return "no such option: %s" % self.opt_name else: return "no such option in group %s: %s" % (self.group.name, self.opt_name) class NoSuchGroupError(Error): """Raised if a group which doesn't exist is referenced.""" def __init__(self, group_name): self.group_name = group_name def __str__(self): return "no such group: %s" % self.group_name class DuplicateOptError(Error): """Raised if multiple opts with the same name are registered.""" def __init__(self, opt_name): self.opt_name = opt_name def __str__(self): return "duplicate option: %s" % self.opt_name class RequiredOptError(Error): """Raised if an option is required but no value is supplied by the user.""" def __init__(self, opt_name, group=None): self.opt_name = opt_name self.group = group def __str__(self): if self.group is None: return "value required for option: %s" % self.opt_name else: return "value required for option: %s.%s" % (self.group.name, self.opt_name) class TemplateSubstitutionError(Error): """Raised if an error occurs substituting a variable in an opt value.""" def __str__(self): return "template substitution error: %s" % self.msg class ConfigFilesNotFoundError(Error): """Raised if one or more config files are not found.""" def __init__(self, config_files): self.config_files = config_files def __str__(self): return ('Failed to read some config files: %s' % ",".join(self.config_files)) class ConfigFileParseError(Error): """Raised if there is an error parsing a config file.""" def __init__(self, config_file, msg): self.config_file = config_file self.msg = msg def __str__(self): return 'Failed to parse %s: %s' % (self.config_file, self.msg) class ConfigFileValueError(Error): """Raised if a config file value does not match its opt type.""" pass def _fixpath(p): """Apply tilde expansion and absolutization to a path.""" return os.path.abspath(os.path.expanduser(p)) def _get_config_dirs(project=None): """Return a list of directors where config files may be located. :param project: an optional project name If a project is specified, following directories are returned:: ~/.${project}/ ~/ /etc/${project}/ /etc/ Otherwise, these directories:: ~/ /etc/ """ cfg_dirs = [ _fixpath(os.path.join('~', '.' + project)) if project else None, _fixpath('~'), os.path.join('/etc', project) if project else None, '/etc' ] return list(moves.filter(bool, cfg_dirs)) def _search_dirs(dirs, basename, extension=""): """Search a list of directories for a given filename. Iterator over the supplied directories, returning the first file found with the supplied name and extension. :param dirs: a list of directories :param basename: the filename, e.g. 'glance-api' :param extension: the file extension, e.g. '.conf' :returns: the path to a matching file, or None """ for d in dirs: path = os.path.join(d, '%s%s' % (basename, extension)) if os.path.exists(path): return path def find_config_files(project=None, prog=None, extension='.conf'): """Return a list of default configuration files. :param project: an optional project name :param prog: the program name, defaulting to the basename of sys.argv[0] :param extension: the type of the config file We default to two config files: [${project}.conf, ${prog}.conf] And we look for those config files in the following directories:: ~/.${project}/ ~/ /etc/${project}/ /etc/ We return an absolute path for (at most) one of each the default config files, for the topmost directory it exists in. For example, if project=foo, prog=bar and /etc/foo/foo.conf, /etc/bar.conf and ~/.foo/bar.conf all exist, then we return ['/etc/foo/foo.conf', '~/.foo/bar.conf'] If no project name is supplied, we only look for ${prog.conf}. """ if prog is None: prog = os.path.basename(sys.argv[0]) cfg_dirs = _get_config_dirs(project) config_files = [] if project: config_files.append(_search_dirs(cfg_dirs, project, extension)) config_files.append(_search_dirs(cfg_dirs, prog, extension)) return list(moves.filter(bool, config_files)) def _is_opt_registered(opts, opt): """Check whether an opt with the same name is already registered. The same opt may be registered multiple times, with only the first registration having any effect. However, it is an error to attempt to register a different opt with the same name. :param opts: the set of opts already registered :param opt: the opt to be registered :returns: True if the opt was previously registered, False otherwise :raises: DuplicateOptError if a naming conflict is detected """ if opt.dest in opts: if opts[opt.dest]['opt'] != opt: raise DuplicateOptError(opt.name) return True else: return False def set_defaults(opts, **kwargs): for opt in opts: if opt.dest in kwargs: opt.default = kwargs[opt.dest] def _normalize_group_name(group_name): if group_name == 'DEFAULT': return group_name return group_name.lower() class Opt(object): """Base class for all configuration options. An Opt object has no public methods, but has a number of public string properties: name: the name of the option, which may include hyphens dest: the (hyphen-less) ConfigOpts property which contains the option value short: a single character CLI option name default: the default value of the option positional: True if the option is a positional CLI argument metavar: the name shown as the argument to a CLI option in --help output help: an string explaining how the options value is used """ multi = False _convert_value = None def __init__(self, name, dest=None, short=None, default=None, positional=False, metavar=None, help=None, secret=False, required=False, deprecated_name=None, deprecated_group=None, deprecated_opts=None): """Construct an Opt object. The only required parameter is the option's name. However, it is common to also supply a default and help string for all options. :param name: the option's name :param dest: the name of the corresponding ConfigOpts property :param short: a single character CLI option name :param default: the default value of the option :param positional: True if the option is a positional CLI argument :param metavar: the option argument to show in --help :param help: an explanation of how the option is used :param secret: true iff the value should be obfuscated in log output :param required: true iff a value must be supplied for this option :param deprecated_name: deprecated name option. Acts like an alias :param deprecated_group: the group containing a deprecated alias :param deprecated_opts: array of DeprecatedOpt(s) """ self.name = name if dest is None: self.dest = self.name.replace('-', '_') else: self.dest = dest self.short = short self.default = default self.positional = positional self.metavar = metavar self.help = help self.secret = secret self.required = required if deprecated_name is not None: deprecated_name = deprecated_name.replace('-', '_') self.deprecated_opts = copy.deepcopy(deprecated_opts) or [] self.deprecated_opts.append(DeprecatedOpt(deprecated_name, group=deprecated_group)) def __ne__(self, another): return vars(self) != vars(another) def _get_from_namespace(self, namespace, group_name): """Retrieves the option value from a _Namespace object. :param namespace: a _Namespace object :param group_name: a group name """ names = [(group_name, self.dest)] for opt in self.deprecated_opts: dname, dgroup = opt.name, opt.group if dname or dgroup: names.append((dgroup if dgroup else group_name, dname if dname else self.dest)) return namespace._get_value(names, self.multi, self.positional, self._convert_value) def _add_to_cli(self, parser, group=None): """Makes the option available in the command line interface. This is the method ConfigOpts uses to add the opt to the CLI interface as appropriate for the opt type. Some opt types may extend this method, others may just extend the helper methods it uses. :param parser: the CLI option parser :param group: an optional OptGroup object """ container = self._get_argparse_container(parser, group) kwargs = self._get_argparse_kwargs(group) prefix = self._get_argparse_prefix('', group.name if group else None) for opt in self.deprecated_opts: deprecated_name = self._get_deprecated_cli_name(opt.name, opt.group) self._add_to_argparse(parser, container, self.name, self.short, kwargs, prefix, self.positional, deprecated_name) def _add_to_argparse(self, parser, container, name, short, kwargs, prefix='', positional=False, deprecated_name=None): """Add an option to an argparse parser or group. :param container: an argparse._ArgumentGroup object :param name: the opt name :param short: the short opt name :param kwargs: the keyword arguments for add_argument() :param prefix: an optional prefix to prepend to the opt name :param position: whether the optional is a positional CLI argument """ def hyphen(arg): return arg if not positional else '' args = [hyphen('--') + prefix + name] if short: args.append(hyphen('-') + short) if deprecated_name: args.append(hyphen('--') + deprecated_name) parser.add_parser_argument(container, *args, **kwargs) def _get_argparse_container(self, parser, group): """Returns an argparse._ArgumentGroup. :param parser: an argparse.ArgumentParser :param group: an (optional) OptGroup object :returns: an argparse._ArgumentGroup if group is given, else parser """ if group is not None: return group._get_argparse_group(parser) else: return parser def _get_argparse_kwargs(self, group, **kwargs): """Build a dict of keyword arguments for argparse's add_argument(). Most opt types extend this method to customize the behaviour of the options added to argparse. :param group: an optional group :param kwargs: optional keyword arguments to add to :returns: a dict of keyword arguments """ if not self.positional: dest = self.dest if group is not None: dest = group.name + '_' + dest kwargs['dest'] = dest else: kwargs['nargs'] = '?' kwargs.update({'default': None, 'metavar': self.metavar, 'help': self.help, }) return kwargs def _get_argparse_prefix(self, prefix, group_name): """Build a prefix for the CLI option name, if required. CLI options in a group are prefixed with the group's name in order to avoid conflicts between similarly named options in different groups. :param prefix: an existing prefix to append to (e.g. 'no' or '') :param group_name: an optional group name :returns: a CLI option prefix including the group name, if appropriate """ if group_name is not None: return group_name + '-' + prefix else: return prefix def _get_deprecated_cli_name(self, dname, dgroup, prefix=''): """Build a CLi arg name for deprecated options. Either a deprecated name or a deprecated group or both or neither can be supplied: dname, dgroup -> dgroup + '-' + dname dname -> dname dgroup -> dgroup + '-' + self.name neither -> None :param dname: a deprecated name, which can be None :param dgroup: a deprecated group, which can be None :param prefix: an prefix to append to (e.g. 'no' or '') :returns: a CLI argument name """ if dgroup == 'DEFAULT': dgroup = None if dname is None and dgroup is None: return None if dname is None: dname = self.name return self._get_argparse_prefix(prefix, dgroup) + dname def __lt__(self, another): return hash(self) < hash(another) # NOTE(jd) Not available for py2.6 if six.PY3: Opt = functools.total_ordering(Opt) class DeprecatedOpt(object): """Represents a Deprecated option. Here's how you can use it oldopts = [cfg.DeprecatedOpt('oldfoo', group='oldgroup'), cfg.DeprecatedOpt('oldfoo2', group='oldgroup2')] cfg.CONF.register_group(cfg.OptGroup('blaa')) cfg.CONF.register_opt(cfg.StrOpt('foo', deprecated_opts=oldopts), group='blaa') Multi-value options will return all new and deprecated options. For single options, if the new option is present ("[blaa]/foo" above) it will override any deprecated options present. If the new option is not present and multiple deprecated options are present, the option corresponding to the first element of deprecated_opts will be chosen. """ def __init__(self, name, group=None): """Constructs an DeprecatedOpt object. :param name: the name of the option :param group: the group of the option """ self.name = name self.group = group def __key(self): return (self.name, self.group) def __eq__(self, other): return self.__key() == other.__key() def __hash__(self): return hash(self.__key()) class StrOpt(Opt): """String options. String opts do not have their values transformed and are returned as str objects. In addition to the parameters in the base class Opt, StrOpt has an additional parameter. :param choices: Optional sequence of valid values. """ def __init__(self, name, choices=None, **kwargs): self.choices = choices super(StrOpt, self).__init__(name, **kwargs) def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for choices handling.""" return super(StrOpt, self)._get_argparse_kwargs(group, choices=self.choices, **kwargs) def _convert_value(self, value): """Validate a value, no actual conversion required.""" if self.choices and value not in self.choices: message = ('Invalid value: %r (choose from %s)' % (value, ', '.join(map(repr, self.choices)))) raise ValueError(message) return value class BoolOpt(Opt): """Boolean options. Bool opts are set to True or False on the command line using --optname or --noopttname respectively. In config files, boolean values are case insensitive and can be set using 1/0, yes/no, true/false or on/off. """ _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False} def __init__(self, *args, **kwargs): if 'positional' in kwargs: raise ValueError('positional boolean args not supported') super(BoolOpt, self).__init__(*args, **kwargs) @staticmethod def _convert_value(value): """Convert a string value to a bool.""" ret = BoolOpt._boolean_states.get(value.lower()) if ret is None: raise ValueError('Unexpected boolean value %r' % value) return ret def _add_to_cli(self, parser, group=None): """Extends the base class method to add the --nooptname option.""" super(BoolOpt, self)._add_to_cli(parser, group) self._add_inverse_to_argparse(parser, group) def _add_inverse_to_argparse(self, parser, group): """Add the --nooptname option to the option parser.""" container = self._get_argparse_container(parser, group) kwargs = self._get_argparse_kwargs(group, action='store_false') prefix = self._get_argparse_prefix('no', group.name if group else None) for opt in self.deprecated_opts: deprecated_name = self._get_deprecated_cli_name(opt.name, opt.group, prefix='no') kwargs["help"] = "The inverse of --" + self.name self._add_to_argparse(parser, container, self.name, None, kwargs, prefix, self.positional, deprecated_name) def _get_argparse_kwargs(self, group, action='store_true', **kwargs): """Extends the base argparse keyword dict for boolean options.""" kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs) # metavar has no effect for BoolOpt if 'metavar' in kwargs: del kwargs['metavar'] kwargs['action'] = action return kwargs class IntOpt(Opt): """Int opt values are converted to integers using the int() builtin.""" _convert_value = int def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for integer options.""" return super(IntOpt, self)._get_argparse_kwargs(group, type=int, **kwargs) class FloatOpt(Opt): """Float opt values are converted to floats using the float() builtin.""" _convert_value = float def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for float options.""" return super(FloatOpt, self)._get_argparse_kwargs(group, type=float, **kwargs) class ListOpt(Opt): """List Options. List opt values are simple string values separated by commas. The opt value is a list containing these strings. """ @staticmethod def _convert_value(value): """Convert a string value to a list.""" return [a.strip() for a in value.split(',')] class _StoreListAction(argparse.Action): """An argparse action for parsing an option value into a list.""" def __call__(self, parser, namespace, values, option_string=None): if values is not None: values = ListOpt._convert_value(values) setattr(namespace, self.dest, values) def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for list options.""" return Opt._get_argparse_kwargs(self, group, action=ListOpt._StoreListAction, **kwargs) class DictOpt(Opt): """Dictionary options. Dictionary opt values are key:value pairs separated by commas. The opt value is a dictionary of these key/value pairs """ @staticmethod def _convert_value(value): """Split a line. Split a value into key/value pairs separated by commas, then split the each into key and value using colons as separator and then stuff the key/value (s) into a dictionary :param value: the string value of key/value pairs separated by commas :returns: a dict object :raises: ConfigFileValueError """ res = dict() for v in [a for a in value.split(',')]: try: key, val = [a.strip() for a in v.split(':', 1)] except ValueError: raise ConfigFileValueError("Failed to parse '%s' as a colon " "separated key/value pair" % v) if key in res: raise ConfigFileValueError("Duplicate key error. Key: '%s'" % key) res[key] = val return res class _StoreDictAction(argparse.Action): """An argparse action for parsing an option value into a dictionary.""" def __call__(self, parser, namespace, values, option_string=None): if values is not None: values = DictOpt._convert_value(values) setattr(namespace, self.dest, values) def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for dictionary options.""" return Opt._get_argparse_kwargs(self, group, action=DictOpt._StoreDictAction, **kwargs) class MultiStrOpt(Opt): """Multi-string option. Multistr opt values are string opts which may be specified multiple times. The opt value is a list containing all the string values specified. """ multi = True def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for multi str options.""" kwargs = super(MultiStrOpt, self)._get_argparse_kwargs(group) if not self.positional: kwargs['action'] = 'append' else: kwargs['nargs'] = '*' return kwargs class SubCommandOpt(Opt): """Sub-command options. Sub-command options allow argparse sub-parsers to be used to parse additional command line arguments. The handler argument to the SubCommandOpt contructor is a callable which is supplied an argparse subparsers object. Use this handler callable to add sub-parsers. The opt value is SubCommandAttr object with the name of the chosen sub-parser stored in the 'name' attribute and the values of other sub-parser arguments available as additional attributes. """ def __init__(self, name, dest=None, handler=None, title=None, description=None, help=None): """Construct an sub-command parsing option. This behaves similarly to other Opt sub-classes but adds a 'handler' argument. The handler is a callable which is supplied an subparsers object when invoked. The add_parser() method on this subparsers object can be used to register parsers for sub-commands. :param name: the option's name :param dest: the name of the corresponding ConfigOpts property :param title: title of the sub-commands group in help output :param description: description of the group in help output :param help: a help string giving an overview of available sub-commands """ super(SubCommandOpt, self).__init__(name, dest=dest, help=help) self.handler = handler self.title = title self.description = description def _add_to_cli(self, parser, group=None): """Add argparse sub-parsers and invoke the handler method.""" dest = self.dest if group is not None: dest = group.name + '_' + dest subparsers = parser.add_subparsers(dest=dest, title=self.title, description=self.description, help=self.help) # NOTE(jd) Set explicitely to True for Python 3 # See http://bugs.python.org/issue9253 for context subparsers.required = True if self.handler is not None: self.handler(subparsers) class _ConfigFileOpt(Opt): """The --config-file option. This is an private option type which handles the special processing required for --config-file options. As each --config-file option is encountered on the command line, we parse the file and store the parsed values in the _Namespace object. This allows us to properly handle the precedence of --config-file options over previous command line arguments, but not over subsequent arguments. """ class ConfigFileAction(argparse.Action): """An argparse action for --config-file. As each --config-file option is encountered, this action adds the value to the config_file attribute on the _Namespace object but also parses the configuration file and stores the values found also in the _Namespace object. """ def __call__(self, parser, namespace, values, option_string=None): """Handle a --config-file command line argument. :raises: ConfigFileParseError, ConfigFileValueError """ if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) items = getattr(namespace, self.dest) items.append(values) ConfigParser._parse_file(values, namespace) def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for the config file opt.""" kwargs = super(_ConfigFileOpt, self)._get_argparse_kwargs(group) kwargs['action'] = self.ConfigFileAction return kwargs class _ConfigDirOpt(Opt): """The --config-dir option. This is an private option type which handles the special processing required for --config-dir options. As each --config-dir option is encountered on the command line, we parse the files in that directory and store the parsed values in the _Namespace object. This allows us to properly handle the precedence of --config-dir options over previous command line arguments, but not over subsequent arguments. """ class ConfigDirAction(argparse.Action): """An argparse action for --config-dir. As each --config-dir option is encountered, this action sets the config_dir attribute on the _Namespace object but also parses the configuration files and stores the values found also in the _Namespace object. """ def __call__(self, parser, namespace, values, option_string=None): """Handle a --config-dir command line argument. :raises: ConfigFileParseError, ConfigFileValueError """ setattr(namespace, self.dest, values) config_dir_glob = os.path.join(values, '*.conf') for config_file in sorted(glob.glob(config_dir_glob)): ConfigParser._parse_file(config_file, namespace) def _get_argparse_kwargs(self, group, **kwargs): """Extends the base argparse keyword dict for the config dir option.""" kwargs = super(_ConfigDirOpt, self)._get_argparse_kwargs(group) kwargs['action'] = self.ConfigDirAction return kwargs class OptGroup(object): """Represents a group of opts. CLI opts in the group are automatically prefixed with the group name. Each group corresponds to a section in config files. An OptGroup object has no public methods, but has a number of public string properties: name: the name of the group title: the group title as displayed in --help help: the group description as displayed in --help """ def __init__(self, name, title=None, help=None): """Constructs an OptGroup object. :param name: the group name :param title: the group title for --help :param help: the group description for --help """ self.name = name self.title = "%s options" % name if title is None else title self.help = help self._opts = {} # dict of dicts of (opt:, override:, default:) self._argparse_group = None def _register_opt(self, opt, cli=False): """Add an opt to this group. :param opt: an Opt object :param cli: whether this is a CLI option :returns: False if previously registered, True otherwise :raises: DuplicateOptError if a naming conflict is detected """ if _is_opt_registered(self._opts, opt): return False self._opts[opt.dest] = {'opt': opt, 'cli': cli} return True def _unregister_opt(self, opt): """Remove an opt from this group. :param opt: an Opt object """ if opt.dest in self._opts: del self._opts[opt.dest] def _get_argparse_group(self, parser): if self._argparse_group is None: """Build an argparse._ArgumentGroup for this group.""" self._argparse_group = parser.add_argument_group(self.title, self.help) return self._argparse_group def _clear(self): """Clear this group's option parsing state.""" self._argparse_group = None class ParseError(iniparser.ParseError): def __init__(self, msg, lineno, line, filename): super(ParseError, self).__init__(msg, lineno, line) self.filename = filename def __str__(self): return 'at %s:%d, %s: %r' % (self.filename, self.lineno, self.msg, self.line) class ConfigParser(iniparser.BaseParser): def __init__(self, filename, sections): super(ConfigParser, self).__init__() self.filename = filename self.sections = sections self._normalized = None self.section = None def _add_normalized(self, normalized): self._normalized = normalized def parse(self): with open(self.filename) as f: return super(ConfigParser, self).parse(f) def new_section(self, section): self.section = section self.sections.setdefault(self.section, {}) if self._normalized is not None: self._normalized.setdefault(_normalize_group_name(self.section), {}) def assignment(self, key, value): if not self.section: raise self.error_no_section() value = '\n'.join(value) def append(sections, section): sections[section].setdefault(key, []) sections[section][key].append(value) append(self.sections, self.section) if self._normalized is not None: append(self._normalized, _normalize_group_name(self.section)) def parse_exc(self, msg, lineno, line=None): return ParseError(msg, lineno, line, self.filename) def error_no_section(self): return self.parse_exc('Section must be started before assignment', self.lineno) @classmethod def _parse_file(cls, config_file, namespace): """Parse a config file and store any values in the namespace. :raises: ConfigFileParseError, ConfigFileValueError """ config_file = _fixpath(config_file) sections = {} normalized = {} parser = cls(config_file, sections) parser._add_normalized(normalized) try: parser.parse() except iniparser.ParseError as pe: raise ConfigFileParseError(pe.filename, str(pe)) except IOError: namespace._file_not_found(config_file) return namespace.add_parsed_config_file(sections, normalized) class MultiConfigParser(object): def __init__(self): self.parsed = [] self._normalized = [] def read(self, config_files): read_ok = [] for filename in config_files: sections = {} normalized = {} parser = ConfigParser(filename, sections) parser._add_normalized(normalized) try: parser.parse() except IOError: continue self._add_parsed_config_file(sections, normalized) read_ok.append(filename) return read_ok def _add_parsed_config_file(self, sections, normalized): """Add a parsed config file to the list of parsed files. :param sections: a mapping of section name to dicts of config values :param normalized: sections mapping with section names normalized :raises: ConfigFileValueError """ self.parsed.insert(0, sections) self._normalized.insert(0, normalized) def get(self, names, multi=False): return self._get(names, multi=multi) def _get(self, names, multi=False, normalized=False, convert_value=None): """Fetch a config file value from the parsed files. :param names: a list of (section, name) tuples :param multi: a boolean indicating whether to return multiple values :param normalized: whether to normalize group names to lowercase :param convert_value: callable to convert a string into the proper type """ rvalue = [] def normalize(name): return _normalize_group_name(name) if normalized else name def convert(value): return value if convert_value is None else convert_value(value) names = [(normalize(section), name) for section, name in names] for sections in (self._normalized if normalized else self.parsed): for section, name in names: if section not in sections: continue if name in sections[section]: val = [convert(v) for v in sections[section][name]] if multi: rvalue = val + rvalue else: return val if multi and rvalue != []: return rvalue raise KeyError class _Namespace(argparse.Namespace): """An argparse namespace which also stores config file values. As we parse command line arguments, the values get set as attributes on a namespace object. However, we also want to parse config files as they are specified on the command line and collect the values alongside the option values parsed from the command line. Note, we don't actually assign values from config files as attributes on the namespace because config file options be registered after the command line has been parsed, so we may not know how to properly parse or convert a config file value at this point. """ def __init__(self, conf): self.conf = conf self.parser = MultiConfigParser() self.files_not_found = [] def _parse_cli_opts_from_config_file(self, sections, normalized): """Parse CLI options from a config file. CLI options are special - we require they be registered before the command line is parsed. This means that as we parse config files, we can go ahead and apply the appropriate option-type specific conversion to the values in config files for CLI options. We can't do this for non-CLI options, because the schema describing those options may not be registered until after the config files are parsed. This method relies on that invariant in order to enforce proper priority of option values - i.e. that the order in which an option value is parsed, whether the value comes from the CLI or a config file, determines which value specified for a given option wins. The way we implement this ordering is that as we parse each config file, we look for values in that config file for CLI options only. Any values for CLI options found in the config file are treated like they had appeared on the command line and set as attributes on the namespace objects. Values in later config files or on the command line will override values found in this file. """ namespace = _Namespace(self.conf) namespace.parser._add_parsed_config_file(sections, normalized) for opt, group in sorted(self.conf._all_cli_opts()): group_name = group.name if group is not None else None try: value = opt._get_from_namespace(namespace, group_name) except KeyError: continue except ValueError as ve: raise ConfigFileValueError(str(ve)) if group_name is None: dest = opt.dest else: dest = group_name + '_' + opt.dest if opt.multi: if getattr(self, dest, None) is None: setattr(self, dest, []) values = getattr(self, dest) values.extend(value) else: setattr(self, dest, value) def add_parsed_config_file(self, sections, normalized): """Add a parsed config file to the list of parsed files. :param sections: a mapping of section name to dicts of config values :param normalized: sections mapping with section names normalized :raises: ConfigFileValueError """ self._parse_cli_opts_from_config_file(sections, normalized) self.parser._add_parsed_config_file(sections, normalized) def _file_not_found(self, config_file): """Record that we were unable to open a config file. :param config_file: the path to the failed file """ self.files_not_found.append(config_file) def _get_cli_value(self, names, positional): """Fetch a CLI option value. Look up the value of a CLI option. The value itself may have come from parsing the command line or parsing config files specified on the command line. Type conversion have already been performed for CLI options at this point. :param names: a list of (section, name) tuples :param multi: a boolean indicating whether to return multiple values :param convert_value: callable to convert a string into the proper type """ for group_name, name in names: name = name if group_name is None else group_name + '_' + name try: value = getattr(self, name) if value is not None: # argparse ignores default=None for nargs='*' if positional and not value: value = self.default return value except AttributeError: pass raise KeyError def _get_value(self, names, multi, positional, convert_value): """Fetch a value from config files. Multiple names for a given configuration option may be supplied so that we can transparently handle files containing deprecated option names or groups. :param names: a list of (section, name) tuples :param multi: a boolean indicating whether to return multiple values :param positional: whether this is a positional option :param convert_value: callable to convert a string into the proper type """ try: return self._get_cli_value(names, positional) except KeyError: pass names = [(g if g is not None else 'DEFAULT', n) for g, n in names] values = self.parser._get(names, multi=multi, normalized=True, convert_value=convert_value) return values if multi else values[-1] class _CachedArgumentParser(argparse.ArgumentParser): """class for caching/collecting command line arguments. It also sorts the arguments before intializing the ArgumentParser. We need to do this since ArgumentParser by default does not sort the argument options and the only way to influence the order of arguments in '--help' is to ensure they are added in the sorted order. """ def __init__(self, prog=None, usage=None, **kwargs): super(_CachedArgumentParser, self).__init__(prog, usage, **kwargs) self._args_cache = {} def add_parser_argument(self, container, *args, **kwargs): values = [] if container in self._args_cache: values = self._args_cache[container] values.append({'args': args, 'kwargs': kwargs}) self._args_cache[container] = values def initialize_parser_arguments(self): for container, values in six.iteritems(self._args_cache): values.sort(key=lambda x: x['args']) for argument in values: try: container.add_argument(*argument['args'], **argument['kwargs']) except argparse.ArgumentError as e: raise DuplicateOptError(e) self._args_cache = {} def parse_args(self, args=None, namespace=None): self.initialize_parser_arguments() return super(_CachedArgumentParser, self).parse_args(args, namespace) def print_help(self, file=None): self.initialize_parser_arguments() super(_CachedArgumentParser, self).print_help(file) def print_usage(self, file=None): self.initialize_parser_arguments() super(_CachedArgumentParser, self).print_usage(file) class ConfigOpts(collections.Mapping): """Config options which may be set on the command line or in config files. ConfigOpts is a configuration option manager with APIs for registering option schemas, grouping options, parsing option values and retrieving the values of options. """ def __init__(self): """Construct a ConfigOpts object.""" self._opts = {} # dict of dicts of (opt:, override:, default:) self._groups = {} self._args = None self._oparser = None self._namespace = None self.__cache = {} self._config_opts = [] def _pre_setup(self, project, prog, version, usage, default_config_files): """Initialize a ConfigCliParser object for option parsing.""" if prog is None: prog = os.path.basename(sys.argv[0]) if default_config_files is None: default_config_files = find_config_files(project, prog) self._oparser = _CachedArgumentParser(prog=prog, usage=usage) self._oparser.add_parser_argument(self._oparser, '--version', action='version', version=version) return prog, default_config_files def _setup(self, project, prog, version, usage, default_config_files): """Initialize a ConfigOpts object for option parsing.""" self._config_opts = [ _ConfigFileOpt('config-file', default=default_config_files, metavar='PATH', help=('Path to a config file to use. Multiple ' 'config files can be specified, with values ' 'in later files taking precedence. The ' 'default files used are: %(default)s')), _ConfigDirOpt('config-dir', metavar='DIR', help='Path to a config directory to pull *.conf ' 'files from. This file set is sorted, so as to ' 'provide a predictable parse order if ' 'individual options are over-ridden. The set ' 'is parsed after the file(s) specified via ' 'previous --config-file, arguments hence ' 'over-ridden options in the directory take ' 'precedence.'), ] self.register_cli_opts(self._config_opts) self.project = project self.prog = prog self.version = version self.usage = usage self.default_config_files = default_config_files def __clear_cache(f): @functools.wraps(f) def __inner(self, *args, **kwargs): if kwargs.pop('clear_cache', True): result = f(self, *args, **kwargs) self.__cache.clear() return result else: return f(self, *args, **kwargs) return __inner def __call__(self, args=None, project=None, prog=None, version=None, usage=None, default_config_files=None): """Parse command line arguments and config files. Calling a ConfigOpts object causes the supplied command line arguments and config files to be parsed, causing opt values to be made available as attributes of the object. The object may be called multiple times, each time causing the previous set of values to be overwritten. Automatically registers the --config-file option with either a supplied list of default config files, or a list from find_config_files(). If the --config-dir option is set, any *.conf files from this directory are pulled in, after all the file(s) specified by the --config-file option. :param args: command line arguments (defaults to sys.argv[1:]) :param project: the toplevel project name, used to locate config files :param prog: the name of the program (defaults to sys.argv[0] basename) :param version: the program version (for --version) :param usage: a usage string (%prog will be expanded) :param default_config_files: config files to use by default :returns: the list of arguments left over after parsing options :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError, RequiredOptError, DuplicateOptError """ self.clear() prog, default_config_files = self._pre_setup(project, prog, version, usage, default_config_files) self._setup(project, prog, version, usage, default_config_files) self._namespace = self._parse_cli_opts(args if args is not None else sys.argv[1:]) if self._namespace.files_not_found: raise ConfigFilesNotFoundError(self._namespace.files_not_found) self._check_required_opts() def __getattr__(self, name): """Look up an option value and perform string substitution. :param name: the opt name (or 'dest', more precisely) :returns: the option value (after string subsititution) or a GroupAttr :raises: NoSuchOptError """ try: return self._get(name) except Exception: raise NoSuchOptError(name) def __getitem__(self, key): """Look up an option value and perform string substitution.""" return self.__getattr__(key) def __contains__(self, key): """Return True if key is the name of a registered opt or group.""" return key in self._opts or key in self._groups def __iter__(self): """Iterate over all registered opt and group names.""" for key in itertools.chain(self._opts.keys(), self._groups.keys()): yield key def __len__(self): """Return the number of options and option groups.""" return len(self._opts) + len(self._groups) def reset(self): """Clear the object state and unset overrides and defaults.""" self._unset_defaults_and_overrides() self.clear() @__clear_cache def clear(self): """Clear the state of the object to before it was called. Any subparsers added using the add_cli_subparsers() will also be removed as a side-effect of this method. """ self._args = None self._oparser = None self._namespace = None self.unregister_opts(self._config_opts) for group in self._groups.values(): group._clear() @__clear_cache def register_opt(self, opt, group=None, cli=False): """Register an option schema. Registering an option schema makes any option value which is previously or subsequently parsed from the command line or config files available as an attribute of this object. :param opt: an instance of an Opt sub-class :param cli: whether this is a CLI option :param group: an optional OptGroup object or group name :return: False if the opt was already registered, True otherwise :raises: DuplicateOptError """ if group is not None: group = self._get_group(group, autocreate=True) return group._register_opt(opt, cli) if _is_opt_registered(self._opts, opt): return False self._opts[opt.dest] = {'opt': opt, 'cli': cli} return True @__clear_cache def register_opts(self, opts, group=None): """Register multiple option schemas at once.""" for opt in opts: self.register_opt(opt, group, clear_cache=False) @__clear_cache def register_cli_opt(self, opt, group=None): """Register a CLI option schema. CLI option schemas must be registered before the command line and config files are parsed. This is to ensure that all CLI options are shown in --help and option validation works as expected. :param opt: an instance of an Opt sub-class :param group: an optional OptGroup object or group name :return: False if the opt was already registered, True otherwise :raises: DuplicateOptError, ArgsAlreadyParsedError """ if self._args is not None: raise ArgsAlreadyParsedError("cannot register CLI option") return self.register_opt(opt, group, cli=True, clear_cache=False) @__clear_cache def register_cli_opts(self, opts, group=None): """Register multiple CLI option schemas at once.""" for opt in opts: self.register_cli_opt(opt, group, clear_cache=False) def register_group(self, group): """Register an option group. An option group must be registered before options can be registered with the group. :param group: an OptGroup object """ if group.name in self._groups: return self._groups[group.name] = copy.copy(group) @__clear_cache def unregister_opt(self, opt, group=None): """Unregister an option. :param opt: an Opt object :param group: an optional OptGroup object or group name :raises: ArgsAlreadyParsedError, NoSuchGroupError """ if self._args is not None: raise ArgsAlreadyParsedError("reset before unregistering options") if group is not None: self._get_group(group)._unregister_opt(opt) elif opt.dest in self._opts: del self._opts[opt.dest] @__clear_cache def unregister_opts(self, opts, group=None): """Unregister multiple CLI option schemas at once.""" for opt in opts: self.unregister_opt(opt, group, clear_cache=False) def import_opt(self, name, module_str, group=None): """Import an option definition from a module. Import a module and check that a given option is registered. This is intended for use with global configuration objects like cfg.CONF where modules commonly register options with CONF at module load time. If one module requires an option defined by another module it can use this method to explicitly declare the dependency. :param name: the name/dest of the opt :param module_str: the name of a module to import :param group: an option OptGroup object or group name :raises: NoSuchOptError, NoSuchGroupError """ __import__(module_str) self._get_opt_info(name, group) def import_group(self, group, module_str): """Import an option group from a module. Import a module and check that a given option group is registered. This is intended for use with global configuration objects like cfg.CONF where modules commonly register options with CONF at module load time. If one module requires an option group defined by another module it can use this method to explicitly declare the dependency. :param group: an option OptGroup object or group name :param module_str: the name of a module to import :raises: ImportError, NoSuchGroupError """ __import__(module_str) self._get_group(group) @__clear_cache def set_override(self, name, override, group=None): """Override an opt value. Override the command line, config file and default values of a given option. :param name: the name/dest of the opt :param override: the override value :param group: an option OptGroup object or group name :raises: NoSuchOptError, NoSuchGroupError """ opt_info = self._get_opt_info(name, group) opt_info['override'] = override @__clear_cache def set_default(self, name, default, group=None): """Override an opt's default value. Override the default value of given option. A command line or config file value will still take precedence over this default. :param name: the name/dest of the opt :param default: the default value :param group: an option OptGroup object or group name :raises: NoSuchOptError, NoSuchGroupError """ opt_info = self._get_opt_info(name, group) opt_info['default'] = default @__clear_cache def clear_override(self, name, group=None): """Clear an override an opt value. Clear a previously set override of the command line, config file and default values of a given option. :param name: the name/dest of the opt :param group: an option OptGroup object or group name :raises: NoSuchOptError, NoSuchGroupError """ opt_info = self._get_opt_info(name, group) opt_info.pop('override', None) @__clear_cache def clear_default(self, name, group=None): """Clear an override an opt's default value. Clear a previously set override of the default value of given option. :param name: the name/dest of the opt :param group: an option OptGroup object or group name :raises: NoSuchOptError, NoSuchGroupError """ opt_info = self._get_opt_info(name, group) opt_info.pop('default', None) def _all_opt_infos(self): """A generator function for iteration opt infos.""" for info in self._opts.values(): yield info, None for group in self._groups.values(): for info in group._opts.values(): yield info, group def _all_cli_opts(self): """A generator function for iterating CLI opts.""" for info, group in self._all_opt_infos(): if info['cli']: yield info['opt'], group def _unset_defaults_and_overrides(self): """Unset any default or override on all options.""" for info, group in self._all_opt_infos(): info.pop('default', None) info.pop('override', None) def find_file(self, name): """Locate a file located alongside the config files. Search for a file with the supplied basename in the directories which we have already loaded config files from and other known configuration directories. The directory, if any, supplied by the config_dir option is searched first. Then the config_file option is iterated over and each of the base directories of the config_files values are searched. Failing both of these, the standard directories searched by the module level find_config_files() function is used. The first matching file is returned. :param basename: the filename, e.g. 'policy.json' :returns: the path to a matching file, or None """ dirs = [] if self.config_dir: dirs.append(_fixpath(self.config_dir)) for cf in reversed(self.config_file): dirs.append(os.path.dirname(_fixpath(cf))) dirs.extend(_get_config_dirs(self.project)) return _search_dirs(dirs, name) def log_opt_values(self, logger, lvl): """Log the value of all registered opts. It's often useful for an app to log its configuration to a log file at startup for debugging. This method dumps to the entire config state to the supplied logger at a given log level. :param logger: a logging.Logger object :param lvl: the log level (e.g. logging.DEBUG) arg to logger.log() """ logger.log(lvl, "*" * 80) logger.log(lvl, "Configuration options gathered from:") logger.log(lvl, "command line args: %s", self._args) logger.log(lvl, "config files: %s", self.config_file) logger.log(lvl, "=" * 80) def _sanitize(opt, value): """Obfuscate values of options declared secret.""" return value if not opt.secret else '*' * len(str(value)) for opt_name in sorted(self._opts): opt = self._get_opt_info(opt_name)['opt'] logger.log(lvl, "%-30s = %s", opt_name, _sanitize(opt, getattr(self, opt_name))) for group_name in self._groups: group_attr = self.GroupAttr(self, self._get_group(group_name)) for opt_name in sorted(self._groups[group_name]._opts): opt = self._get_opt_info(opt_name, group_name)['opt'] logger.log(lvl, "%-30s = %s", "%s.%s" % (group_name, opt_name), _sanitize(opt, getattr(group_attr, opt_name))) logger.log(lvl, "*" * 80) def print_usage(self, file=None): """Print the usage message for the current program. This method is for use after all CLI options are known registered using __call__() method. If this method is called before the __call__() is invoked, it throws NotInitializedError :param file: the File object (if None, output is on sys.stdout) :raises: NotInitializedError """ if not self._oparser: raise NotInitializedError() self._oparser.print_usage(file) def print_help(self, file=None): """Print the help message for the current program. This method is for use after all CLI options are known registered using __call__() method. If this method is called before the __call__() is invoked, it throws NotInitializedError :param file: the File object (if None, output is on sys.stdout) :raises: NotInitializedError """ if not self._oparser: raise NotInitializedError() self._oparser.print_help(file) def _get(self, name, group=None, namespace=None): if isinstance(group, OptGroup): key = (group.name, name) else: key = (group, name) try: if namespace is not None: raise KeyError return self.__cache[key] except KeyError: value = self._substitute(self._do_get(name, group, namespace)) self.__cache[key] = value return value def _do_get(self, name, group=None, namespace=None): """Look up an option value. :param name: the opt name (or 'dest', more precisely) :param group: an OptGroup :param namespace: the namespace object that retrieves the option value from :returns: the option value, or a GroupAttr object :raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError, TemplateSubstitutionError """ if group is None and name in self._groups: return self.GroupAttr(self, self._get_group(name)) info = self._get_opt_info(name, group) opt = info['opt'] if isinstance(opt, SubCommandOpt): return self.SubCommandAttr(self, group, opt.dest) if 'override' in info: return info['override'] if namespace is None: namespace = self._namespace if namespace is not None: group_name = group.name if group is not None else None try: return opt._get_from_namespace(namespace, group_name) except KeyError: pass except ValueError as ve: raise ConfigFileValueError(str(ve)) if 'default' in info: return info['default'] return opt.default def _substitute(self, value): """Perform string template substitution. Substitute any template variables (e.g. $foo, ${bar}) in the supplied string value(s) with opt values. :param value: the string value, or list of string values :returns: the substituted string(s) """ if isinstance(value, list): return [self._substitute(i) for i in value] elif isinstance(value, str): tmpl = string.Template(value) return tmpl.safe_substitute(self.StrSubWrapper(self)) else: return value def _get_group(self, group_or_name, autocreate=False): """Looks up a OptGroup object. Helper function to return an OptGroup given a parameter which can either be the group's name or an OptGroup object. The OptGroup object returned is from the internal dict of OptGroup objects, which will be a copy of any OptGroup object that users of the API have access to. If autocreate is True, the group will be created if it's not found. If group is an instance of OptGroup, that same instance will be registered, otherwise a new instance of OptGroup will be created. :param group_or_name: the group's name or the OptGroup object itself :param autocreate: whether to auto-create the group if it's not found :raises: NoSuchGroupError """ group = group_or_name if isinstance(group_or_name, OptGroup) else None group_name = group.name if group else group_or_name if group_name not in self._groups: if not autocreate: raise NoSuchGroupError(group_name) self.register_group(group or OptGroup(name=group_name)) return self._groups[group_name] def _get_opt_info(self, opt_name, group=None): """Return the (opt, override, default) dict for an opt. :param opt_name: an opt name/dest :param group: an optional group name or OptGroup object :raises: NoSuchOptError, NoSuchGroupError """ if group is None: opts = self._opts else: group = self._get_group(group) opts = group._opts if opt_name not in opts: raise NoSuchOptError(opt_name, group) return opts[opt_name] def _check_required_opts(self, namespace=None): """Check that all opts marked as required have values specified. :param namespace: the namespace object be checked the required options :raises: RequiredOptError """ for info, group in self._all_opt_infos(): opt = info['opt'] if opt.required: if 'default' in info or 'override' in info: continue if self._get(opt.dest, group, namespace) is None: raise RequiredOptError(opt.name, group) def _parse_cli_opts(self, args): """Parse command line options. Initializes the command line option parser and parses the supplied command line arguments. :param args: the command line arguments :returns: a _Namespace object containing the parsed option values :raises: SystemExit, DuplicateOptError ConfigFileParseError, ConfigFileValueError """ self._args = args for opt, group in sorted(self._all_cli_opts(), key=lambda x: x[0].name): opt._add_to_cli(self._oparser, group) return self._parse_config_files() def _parse_config_files(self): """Parse configure files options. :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError, RequiredOptError, DuplicateOptError """ namespace = _Namespace(self) for arg in self._args: if arg == '--config-file' or arg.startswith('--config-file='): break else: for config_file in self.default_config_files: ConfigParser._parse_file(config_file, namespace) return self._oparser.parse_args(self._args, namespace) @__clear_cache def reload_config_files(self): """Reload configure files and parse all options :return False if reload configure files failed or else return True """ try: namespace = self._parse_config_files() if namespace.files_not_found: raise ConfigFilesNotFoundError(namespace.files_not_found) self._check_required_opts(namespace) except SystemExit as exc: LOG.warn("Caught SystemExit while reloading configure files \ with exit code: %d" % exc.code) return False except Error as err: LOG.warn("Caught Error while reloading configure files: %s" % err.__str__()) return False else: self._namespace = namespace return True class GroupAttr(collections.Mapping): """Helper class. Represents the option values of a group as a mapping and attributes. """ def __init__(self, conf, group): """Construct a GroupAttr object. :param conf: a ConfigOpts object :param group: an OptGroup object """ self._conf = conf self._group = group def __getattr__(self, name): """Look up an option value and perform template substitution.""" return self._conf._get(name, self._group) def __getitem__(self, key): """Look up an option value and perform string substitution.""" return self.__getattr__(key) def __contains__(self, key): """Return True if key is the name of a registered opt or group.""" return key in self._group._opts def __iter__(self): """Iterate over all registered opt and group names.""" for key in self._group._opts.keys(): yield key def __len__(self): """Return the number of options and option groups.""" return len(self._group._opts) class SubCommandAttr(object): """Helper class. Represents the name and arguments of an argparse sub-parser. """ def __init__(self, conf, group, dest): """Construct a SubCommandAttr object. :param conf: a ConfigOpts object :param group: an OptGroup object :param dest: the name of the sub-parser """ self._conf = conf self._group = group self._dest = dest def __getattr__(self, name): """Look up a sub-parser name or argument value.""" if name == 'name': name = self._dest if self._group is not None: name = self._group.name + '_' + name return getattr(self._conf._namespace, name) if name in self._conf: raise DuplicateOptError(name) try: return getattr(self._conf._namespace, name) except AttributeError: raise NoSuchOptError(name) class StrSubWrapper(object): """Helper class. Exposes opt values as a dict for string substitution. """ def __init__(self, conf): """Construct a StrSubWrapper object. :param conf: a ConfigOpts object """ self.conf = conf def __getitem__(self, key): """Look up an opt value from the ConfigOpts object. :param key: an opt name :returns: an opt value :raises: TemplateSubstitutionError if attribute is a group """ value = getattr(self.conf, key) if isinstance(value, self.conf.GroupAttr): raise TemplateSubstitutionError( 'substituting group %s not supported' % key) return value CONF = ConfigOpts() oslo.config-1.2.1/oslo/config/iniparser.py0000664000175300017540000001023412221001311021636 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class ParseError(Exception): def __init__(self, message, lineno, line): self.msg = message self.line = line self.lineno = lineno def __str__(self): return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line) class BaseParser(object): lineno = 0 parse_exc = ParseError def _assignment(self, key, value): self.assignment(key, value) return None, [] def _get_section(self, line): if not line.endswith(']'): return self.error_no_section_end_bracket(line) if len(line) <= 2: return self.error_no_section_name(line) return line[1:-1] def _split_key_value(self, line): colon = line.find(':') equal = line.find('=') if colon < 0 and equal < 0: return self.error_invalid_assignment(line) if colon < 0 or (equal >= 0 and equal < colon): key, value = line[:equal], line[equal + 1:] else: key, value = line[:colon], line[colon + 1:] value = value.strip() if value and value[0] == value[-1] and value.startswith(("\"", "'")): value = value[1:-1] return key.strip(), [value] def parse(self, lineiter): key = None value = [] for line in lineiter: self.lineno += 1 line = line.rstrip() if not line: # Blank line, ends multi-line values if key: key, value = self._assignment(key, value) continue elif line.startswith((' ', '\t')): # Continuation of previous assignment if key is None: self.error_unexpected_continuation(line) else: value.append(line.lstrip()) continue if key: # Flush previous assignment, if any key, value = self._assignment(key, value) if line.startswith('['): # Section start section = self._get_section(line) if section: self.new_section(section) elif line.startswith(('#', ';')): self.comment(line[1:].lstrip()) else: key, value = self._split_key_value(line) if not key: return self.error_empty_key(line) if key: # Flush previous assignment, if any self._assignment(key, value) def assignment(self, key, value): """Called when a full assignment is parsed.""" raise NotImplementedError() def new_section(self, section): """Called when a new section is started.""" raise NotImplementedError() def comment(self, comment): """Called when a comment is parsed.""" pass def error_invalid_assignment(self, line): raise self.parse_exc("No ':' or '=' found in assignment", self.lineno, line) def error_empty_key(self, line): raise self.parse_exc('Key cannot be empty', self.lineno, line) def error_unexpected_continuation(self, line): raise self.parse_exc('Unexpected continuation line', self.lineno, line) def error_no_section_end_bracket(self, line): raise self.parse_exc('Invalid section (must end with ])', self.lineno, line) def error_no_section_name(self, line): raise self.parse_exc('Empty section name', self.lineno, line) oslo.config-1.2.1/oslo/__init__.py0000664000175300017540000000130312221001311020131 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __import__('pkg_resources').declare_namespace(__name__) oslo.config-1.2.1/CONTRIBUTING.rst0000664000175300017540000000103212221001311017504 0ustar jenkinsjenkins00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in the "If you're a developer, start here" section of this page: http://wiki.openstack.org/HowToContribute Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://wiki.openstack.org/GerritWorkflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/oslo oslo.config-1.2.1/tests/0000775000175300017540000000000012221001336016220 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/tests/test_cfg.py0000664000175300017540000033506112221001311020371 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import os import shutil import sys import tempfile import fixtures import six from six import moves import testscenarios from oslo.config import cfg from tests import utils load_tests = testscenarios.load_tests_apply_scenarios class ExceptionsTestCase(utils.BaseTestCase): def test_error(self): msg = str(cfg.Error('foobar')) self.assertEqual(msg, 'foobar') def test_args_already_parsed_error(self): msg = str(cfg.ArgsAlreadyParsedError('foobar')) self.assertEqual(msg, 'arguments already parsed: foobar') def test_no_such_opt_error(self): msg = str(cfg.NoSuchOptError('foo')) self.assertEqual(msg, 'no such option: foo') def test_no_such_opt_error_with_group(self): msg = str(cfg.NoSuchOptError('foo', cfg.OptGroup('bar'))) self.assertEqual(msg, 'no such option in group bar: foo') def test_no_such_group_error(self): msg = str(cfg.NoSuchGroupError('bar')) self.assertEqual(msg, 'no such group: bar') def test_duplicate_opt_error(self): msg = str(cfg.DuplicateOptError('foo')) self.assertEqual(msg, 'duplicate option: foo') def test_required_opt_error(self): msg = str(cfg.RequiredOptError('foo')) self.assertEqual(msg, 'value required for option: foo') def test_required_opt_error_with_group(self): msg = str(cfg.RequiredOptError('foo', cfg.OptGroup('bar'))) self.assertEqual(msg, 'value required for option: bar.foo') def test_template_substitution_error(self): msg = str(cfg.TemplateSubstitutionError('foobar')) self.assertEqual(msg, 'template substitution error: foobar') def test_config_files_not_found_error(self): msg = str(cfg.ConfigFilesNotFoundError(['foo', 'bar'])) self.assertEqual(msg, 'Failed to read some config files: foo,bar') def test_config_file_parse_error(self): msg = str(cfg.ConfigFileParseError('foo', 'foobar')) self.assertEqual(msg, 'Failed to parse foo: foobar') class BaseTestCase(utils.BaseTestCase): class TestConfigOpts(cfg.ConfigOpts): def __call__(self, args=None, default_config_files=[]): return cfg.ConfigOpts.__call__( self, args=args, prog='test', version='1.0', usage='%(prog)s FOO BAR', default_config_files=default_config_files) def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.NestedTempfile()) self.conf = self.TestConfigOpts() self.tempdirs = [] def create_tempfiles(self, files, ext='.conf'): tempfiles = [] for (basename, contents) in files: if not os.path.isabs(basename): (fd, path) = tempfile.mkstemp(prefix=basename, suffix=ext) else: path = basename + ext fd = os.open(path, os.O_CREAT | os.O_WRONLY) tempfiles.append(path) try: os.write(fd, contents.encode('utf-8')) finally: os.close(fd) return tempfiles class UsageTestCase(BaseTestCase): def test_print_usage(self): f = moves.StringIO() self.conf([]) self.conf.print_usage(file=f) self.assertTrue('usage: test FOO BAR' in f.getvalue()) self.assertTrue('optional:' not in f.getvalue()) class HelpTestCase(BaseTestCase): def test_print_help(self): f = moves.StringIO() self.conf([]) self.conf.print_help(file=f) self.assertTrue('usage: test FOO BAR' in f.getvalue()) self.assertTrue('optional' in f.getvalue()) self.assertTrue('-h, --help' in f.getvalue()) def test_print_sorted_help(self): f = moves.StringIO() self.conf.register_cli_opt(cfg.StrOpt('zba')) self.conf.register_cli_opt(cfg.StrOpt('abc')) self.conf.register_cli_opt(cfg.StrOpt('ghi')) self.conf.register_cli_opt(cfg.StrOpt('deb')) self.conf([]) self.conf.print_help(file=f) zba = f.getvalue().find('--zba') abc = f.getvalue().find('--abc') ghi = f.getvalue().find('--ghi') deb = f.getvalue().find('--deb') list = [abc, deb, ghi, zba] self.assertEqual(sorted(list), list) class FindConfigFilesTestCase(BaseTestCase): def test_find_config_files(self): config_files = [os.path.expanduser('~/.blaa/blaa.conf'), '/etc/foo.conf'] self.useFixture(fixtures.MonkeyPatch('sys.argv', ['foo'])) self.useFixture(fixtures.MonkeyPatch('os.path.exists', lambda p: p in config_files)) self.assertEqual(cfg.find_config_files(project='blaa'), config_files) def test_find_config_files_with_extension(self): config_files = ['/etc/foo.json'] self.useFixture(fixtures.MonkeyPatch('sys.argv', ['foo'])) self.useFixture(fixtures.MonkeyPatch('os.path.exists', lambda p: p in config_files)) self.assertEqual(cfg.find_config_files(project='blaa'), []) self.assertEqual(cfg.find_config_files(project='blaa', extension='.json'), config_files) class DefaultConfigFilesTestCase(BaseTestCase): def test_use_default(self): self.conf.register_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('foo-', '[DEFAULT]\n''foo = bar\n')]) self.conf.register_cli_opt(cfg.StrOpt('config-file-foo')) self.conf(args=['--config-file-foo', 'foo.conf'], default_config_files=[paths[0]]) self.assertEqual(self.conf.config_file, [paths[0]]) self.assertEqual(self.conf.foo, 'bar') def test_do_not_use_default_multi_arg(self): self.conf.register_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('foo-', '[DEFAULT]\n''foo = bar\n')]) self.conf(args=['--config-file', paths[0]], default_config_files=['bar.conf']) self.assertEqual(self.conf.config_file, [paths[0]]) self.assertEqual(self.conf.foo, 'bar') def test_do_not_use_default_single_arg(self): self.conf.register_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('foo-', '[DEFAULT]\n''foo = bar\n')]) self.conf(args=['--config-file=' + paths[0]], default_config_files=['bar.conf']) self.assertEqual(self.conf.config_file, [paths[0]]) self.assertEqual(self.conf.foo, 'bar') def test_no_default_config_file(self): self.conf(args=[]) self.assertEqual(self.conf.config_file, []) def test_find_default_config_file(self): paths = self.create_tempfiles([('def', '[DEFAULT]')]) self.useFixture(fixtures.MonkeyPatch( 'oslo.config.cfg.find_config_files', lambda project, prog: paths)) self.conf(args=[], default_config_files=None) self.assertEqual(self.conf.config_file, paths) def test_default_config_file(self): paths = self.create_tempfiles([('def', '[DEFAULT]')]) self.conf(args=[], default_config_files=paths) self.assertEqual(self.conf.config_file, paths) def test_default_config_file_with_value(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('def', '[DEFAULT]\n''foo = bar\n')]) self.conf(args=[], default_config_files=paths) self.assertEqual(self.conf.config_file, paths) self.assertEqual(self.conf.foo, 'bar') def test_default_config_file_priority(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('def', '[DEFAULT]\n''foo = bar\n')]) self.conf(args=['--foo=blaa'], default_config_files=paths) self.assertEqual(self.conf.config_file, paths) self.assertEqual(self.conf.foo, 'blaa') class CliOptsTestCase(BaseTestCase): """Test CLI Options. Each test scenario takes a name for the scenarios, as well as a dict: opt_class - class of the type of option that should be tested default - a default value for the option cli_args - a list containing a representation of an input command line value - the result value that is expected to be found deps - a tuple of deprecated name/group """ scenarios = [ ('str_default', dict(opt_class=cfg.StrOpt, default=None, cli_args=[], value=None, deps=(None, None))), ('str_arg', dict(opt_class=cfg.StrOpt, default=None, cli_args=['--foo', 'bar'], value='bar', deps=(None, None))), ('str_arg_deprecated_name', dict(opt_class=cfg.StrOpt, default=None, cli_args=['--oldfoo', 'bar'], value='bar', deps=('oldfoo', None))), ('str_arg_deprecated_group', dict(opt_class=cfg.StrOpt, default=None, cli_args=['--old-foo', 'bar'], value='bar', deps=(None, 'old'))), ('str_arg_deprecated_group_default', dict(opt_class=cfg.StrOpt, default=None, cli_args=['--foo', 'bar'], value='bar', deps=(None, 'DEFAULT'))), ('str_arg_deprecated_group_and_name', dict(opt_class=cfg.StrOpt, default=None, cli_args=['--old-oof', 'bar'], value='bar', deps=('oof', 'old'))), ('bool_default', dict(opt_class=cfg.BoolOpt, default=False, cli_args=[], value=False, deps=(None, None))), ('bool_arg', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--foo'], value=True, deps=(None, None))), ('bool_arg_deprecated_name', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--oldfoo'], value=True, deps=('oldfoo', None))), ('bool_arg_deprecated_group', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--old-foo'], value=True, deps=(None, 'old'))), ('bool_arg_deprecated_group_default', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--foo'], value=True, deps=(None, 'DEFAULT'))), ('bool_arg_deprecated_group_and_name', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--old-oof'], value=True, deps=('oof', 'old'))), ('bool_arg_inverse', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--foo', '--nofoo'], value=False, deps=(None, None))), ('bool_arg_inverse_deprecated_name', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--oldfoo', '--nooldfoo'], value=False, deps=('oldfoo', None))), ('bool_arg_inverse_deprecated_group', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--old-foo', '--old-nofoo'], value=False, deps=(None, 'old'))), ('bool_arg_inverse_deprecated_group_default', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--foo', '--nofoo'], value=False, deps=(None, 'DEFAULT'))), ('bool_arg_inverse_deprecated_group_and_name', dict(opt_class=cfg.BoolOpt, default=None, cli_args=['--old-oof', '--old-nooof'], value=False, deps=('oof', 'old'))), ('int_default', dict(opt_class=cfg.IntOpt, default=10, cli_args=[], value=10, deps=(None, None))), ('int_arg', dict(opt_class=cfg.IntOpt, default=None, cli_args=['--foo=20'], value=20, deps=(None, None))), ('int_arg_deprecated_name', dict(opt_class=cfg.IntOpt, default=None, cli_args=['--oldfoo=20'], value=20, deps=('oldfoo', None))), ('int_arg_deprecated_group', dict(opt_class=cfg.IntOpt, default=None, cli_args=['--old-foo=20'], value=20, deps=(None, 'old'))), ('int_arg_deprecated_group_default', dict(opt_class=cfg.IntOpt, default=None, cli_args=['--foo=20'], value=20, deps=(None, 'DEFAULT'))), ('int_arg_deprecated_group_and_name', dict(opt_class=cfg.IntOpt, default=None, cli_args=['--old-oof=20'], value=20, deps=('oof', 'old'))), ('float_default', dict(opt_class=cfg.FloatOpt, default=1.0, cli_args=[], value=1.0, deps=(None, None))), ('float_arg', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--foo', '2.0'], value=2.0, deps=(None, None))), ('float_arg_deprecated_name', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--oldfoo', '2.0'], value=2.0, deps=('oldfoo', None))), ('float_arg_deprecated_group', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--old-foo', '2.0'], value=2.0, deps=(None, 'old'))), ('float_arg_deprecated_group_default', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--foo', '2.0'], value=2.0, deps=(None, 'DEFAULT'))), ('float_arg_deprecated_group_and_name', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--old-oof', '2.0'], value=2.0, deps=('oof', 'old'))), ('list_default', dict(opt_class=cfg.ListOpt, default=['bar'], cli_args=[], value=['bar'], deps=(None, None))), ('list_arg', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--foo', 'blaa,bar'], value=['blaa', 'bar'], deps=(None, None))), ('list_arg_with_spaces', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--foo', 'blaa ,bar'], value=['blaa', 'bar'], deps=(None, None))), ('list_arg_deprecated_name', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--oldfoo', 'blaa,bar'], value=['blaa', 'bar'], deps=('oldfoo', None))), ('list_arg_deprecated_group', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--old-foo', 'blaa,bar'], value=['blaa', 'bar'], deps=(None, 'old'))), ('list_arg_deprecated_group_default', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--foo', 'blaa,bar'], value=['blaa', 'bar'], deps=(None, 'DEFAULT'))), ('list_arg_deprecated_group_and_name', dict(opt_class=cfg.ListOpt, default=None, cli_args=['--old-oof', 'blaa,bar'], value=['blaa', 'bar'], deps=('oof', 'old'))), ('dict_default', dict(opt_class=cfg.DictOpt, default={'foo': 'bar'}, cli_args=[], value={'foo': 'bar'}, deps=(None, None))), ('dict_arg', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--foo', 'key1:blaa,key2:bar'], value={'key1': 'blaa', 'key2': 'bar'}, deps=(None, None))), ('dict_arg_multiple_keys_last_wins', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--foo', 'key1:blaa', '--foo', 'key2:bar'], value={'key2': 'bar'}, deps=(None, None))), ('dict_arg_with_spaces', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--foo', 'key1:blaa ,key2:bar'], value={'key1': 'blaa', 'key2': 'bar'}, deps=(None, None))), ('dict_arg_deprecated_name', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--oldfoo', 'key1:blaa', '--oldfoo', 'key2:bar'], value={'key2': 'bar'}, deps=('oldfoo', None))), ('dict_arg_deprecated_group', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--old-foo', 'key1:blaa,key2:bar'], value={'key1': 'blaa', 'key2': 'bar'}, deps=(None, 'old'))), ('dict_arg_deprecated_group2', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--old-foo', 'key1:blaa', '--old-foo', 'key2:bar'], value={'key2': 'bar'}, deps=(None, 'old'))), ('dict_arg_deprecated_group_default', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--foo', 'key1:blaa', '--foo', 'key2:bar'], value={'key2': 'bar'}, deps=(None, 'DEFAULT'))), ('dict_arg_deprecated_group_and_name', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--old-oof', 'key1:blaa,key2:bar'], value={'key1': 'blaa', 'key2': 'bar'}, deps=('oof', 'old'))), ('dict_arg_deprecated_group_and_name2', dict(opt_class=cfg.DictOpt, default=None, cli_args=['--old-oof', 'key1:blaa', '--old-oof', 'key2:bar'], value={'key2': 'bar'}, deps=('oof', 'old'))), ('multistr_default', dict(opt_class=cfg.MultiStrOpt, default=['bar'], cli_args=[], value=['bar'], deps=(None, None))), ('multistr_arg', dict(opt_class=cfg.MultiStrOpt, default=None, cli_args=['--foo', 'blaa', '--foo', 'bar'], value=['blaa', 'bar'], deps=(None, None))), ('multistr_arg_deprecated_name', dict(opt_class=cfg.MultiStrOpt, default=None, cli_args=['--oldfoo', 'blaa', '--oldfoo', 'bar'], value=['blaa', 'bar'], deps=('oldfoo', None))), ('multistr_arg_deprecated_group', dict(opt_class=cfg.MultiStrOpt, default=None, cli_args=['--old-foo', 'blaa', '--old-foo', 'bar'], value=['blaa', 'bar'], deps=(None, 'old'))), ('multistr_arg_deprecated_group_default', dict(opt_class=cfg.MultiStrOpt, default=None, cli_args=['--foo', 'blaa', '--foo', 'bar'], value=['blaa', 'bar'], deps=(None, 'DEFAULT'))), ('multistr_arg_deprecated_group_and_name', dict(opt_class=cfg.MultiStrOpt, default=None, cli_args=['--old-oof', 'blaa', '--old-oof', 'bar'], value=['blaa', 'bar'], deps=('oof', 'old'))), ] def test_cli(self): self.conf.register_cli_opt( self.opt_class('foo', default=self.default, deprecated_name=self.deps[0], deprecated_group=self.deps[1])) self.conf(self.cli_args) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, self.value) class CliSpecialOptsTestCase(BaseTestCase): def test_help(self): self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO())) self.assertRaises(SystemExit, self.conf, ['--help']) self.assertTrue('FOO BAR' in sys.stdout.getvalue()) self.assertTrue('--version' in sys.stdout.getvalue()) self.assertTrue('--help' in sys.stdout.getvalue()) self.assertTrue('--config-file' in sys.stdout.getvalue()) def test_version(self): self.useFixture(fixtures.MonkeyPatch('sys.stderr', moves.StringIO())) self.assertRaises(SystemExit, self.conf, ['--version']) self.assertTrue('1.0' in sys.stderr.getvalue()) def test_config_file(self): paths = self.create_tempfiles([('1', '[DEFAULT]'), ('2', '[DEFAULT]')]) self.conf(['--config-file', paths[0], '--config-file', paths[1]]) self.assertEqual(self.conf.config_file, paths) class PositionalTestCase(BaseTestCase): def _do_pos_test(self, opt_class, default, cli_args, value): self.conf.register_cli_opt(opt_class('foo', default=default, positional=True)) self.conf(cli_args) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, value) def test_positional_str_default(self): self._do_pos_test(cfg.StrOpt, None, [], None) def test_positional_str_arg(self): self._do_pos_test(cfg.StrOpt, None, ['bar'], 'bar') def test_positional_int_default(self): self._do_pos_test(cfg.IntOpt, 10, [], 10) def test_positional_int_arg(self): self._do_pos_test(cfg.IntOpt, None, ['20'], 20) def test_positional_float_default(self): self._do_pos_test(cfg.FloatOpt, 1.0, [], 1.0) def test_positional_float_arg(self): self._do_pos_test(cfg.FloatOpt, None, ['2.0'], 2.0) def test_positional_list_default(self): self._do_pos_test(cfg.ListOpt, ['bar'], [], ['bar']) def test_positional_list_arg(self): self._do_pos_test(cfg.ListOpt, None, ['blaa,bar'], ['blaa', 'bar']) def test_positional_dict_default(self): self._do_pos_test(cfg.DictOpt, {'key1': 'bar'}, [], {'key1': 'bar'}) def test_positional_dict_arg(self): self._do_pos_test(cfg.DictOpt, None, ['key1:blaa,key2:bar'], {'key1': 'blaa', 'key2': 'bar'}) def test_positional_multistr_default(self): self._do_pos_test(cfg.MultiStrOpt, ['bar'], [], ['bar']) def test_positional_multistr_arg(self): self._do_pos_test(cfg.MultiStrOpt, None, ['blaa', 'bar'], ['blaa', 'bar']) def test_positional_bool(self): self.assertRaises(ValueError, cfg.BoolOpt, 'foo', positional=True) def test_required_positional_opt(self): self.conf.register_cli_opt( cfg.StrOpt('foo', required=True, positional=True)) self.conf(['bar']) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_missing_required_cli_opt(self): self.conf.register_cli_opt( cfg.StrOpt('foo', required=True, positional=True)) self.assertRaises(cfg.RequiredOptError, self.conf, []) class ConfigFileOptsTestCase(BaseTestCase): def _do_deprecated_test(self, opt_class, value, result, key, section='DEFAULT', dname=None, dgroup=None): self.conf.register_opt(opt_class('newfoo', deprecated_name=dname, deprecated_group=dgroup)) paths = self.create_tempfiles([('test', '[' + section + ']\n' + key + ' = ' + value + '\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'newfoo')) self.assertEqual(self.conf.newfoo, result) def _do_dname_test_use(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'oldfoo', dname='oldfoo') def _do_dgroup_test_use(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'newfoo', section='old', dgroup='old') def _do_default_dgroup_test_use(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'newfoo', section='DEFAULT', dgroup='DEFAULT') def _do_dgroup_and_dname_test_use(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'oof', section='old', dgroup='old', dname='oof') def _do_dname_test_ignore(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'newfoo', dname='oldfoo') def _do_dgroup_test_ignore(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'newfoo', section='DEFAULT', dgroup='old') def _do_dgroup_and_dname_test_ignore(self, opt_class, value, result): self._do_deprecated_test(opt_class, value, result, 'oof', section='old', dgroup='old', dname='oof') def test_conf_file_str_default(self): self.conf.register_opt(cfg.StrOpt('foo', default='bar')) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_conf_file_str_value(self): self.conf.register_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n''foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_conf_file_str_value_override(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = baar\n'), ('2', '[DEFAULT]\n' 'foo = baaar\n')]) self.conf(['--foo', 'bar', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'baaar') def test_conf_file_str_value_override_use_deprecated(self): """last option should always win, even if last uses deprecated.""" self.conf.register_cli_opt( cfg.StrOpt('newfoo', deprecated_name='oldfoo')) paths = self.create_tempfiles([('0', '[DEFAULT]\n' 'newfoo = middle\n'), ('1', '[DEFAULT]\n' 'oldfoo = last\n')]) self.conf(['--newfoo', 'first', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'newfoo')) self.assertFalse(hasattr(self.conf, 'oldfoo')) self.assertEqual(self.conf.newfoo, 'last') def test_conf_file_str_use_dname(self): self._do_dname_test_use(cfg.StrOpt, 'value1', 'value1') def test_conf_file_str_use_dgroup(self): self._do_dgroup_test_use(cfg.StrOpt, 'value1', 'value1') def test_conf_file_str_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.StrOpt, 'value1', 'value1') def test_conf_file_str_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.StrOpt, 'value1', 'value1') def test_conf_file_str_ignore_dname(self): self._do_dname_test_ignore(cfg.StrOpt, 'value2', 'value2') def test_conf_file_str_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.StrOpt, 'value2', 'value2') def test_conf_file_str_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.StrOpt, 'value2', 'value2') def test_conf_file_bool_default(self): self.conf.register_opt(cfg.BoolOpt('foo', default=False)) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, False) def test_conf_file_bool_value(self): self.conf.register_opt(cfg.BoolOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = true\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, True) def test_conf_file_bool_cli_value_override(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = 0\n')]) self.conf(['--foo', '--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, False) def test_conf_file_bool_cli_inverse_override(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = true\n')]) self.conf(['--nofoo', '--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, True) def test_conf_file_bool_cli_order_override(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = false\n')]) self.conf(['--config-file', paths[0], '--foo']) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, True) def test_conf_file_bool_file_value_override(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = 0\n'), ('2', '[DEFAULT]\n' 'foo = yes\n')]) self.conf(['--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, True) def test_conf_file_bool_use_dname(self): self._do_dname_test_use(cfg.BoolOpt, 'yes', True) def test_conf_file_bool_use_dgroup(self): self._do_dgroup_test_use(cfg.BoolOpt, 'yes', True) def test_conf_file_bool_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.BoolOpt, 'yes', True) def test_conf_file_bool_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.BoolOpt, 'yes', True) def test_conf_file_bool_ignore_dname(self): self._do_dname_test_ignore(cfg.BoolOpt, 'no', False) def test_conf_file_bool_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.BoolOpt, 'no', False) def test_conf_file_bool_ignore_group_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.BoolOpt, 'no', False) def test_conf_file_int_default(self): self.conf.register_opt(cfg.IntOpt('foo', default=666)) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 666) def test_conf_file_int_value(self): self.conf.register_opt(cfg.IntOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = 666\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 666) def test_conf_file_int_value_override(self): self.conf.register_cli_opt(cfg.IntOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = 66\n'), ('2', '[DEFAULT]\n' 'foo = 666\n')]) self.conf(['--foo', '6', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 666) def test_conf_file_int_use_dname(self): self._do_dname_test_use(cfg.IntOpt, '66', 66) def test_conf_file_int_use_dgroup(self): self._do_dgroup_test_use(cfg.IntOpt, '66', 66) def test_conf_file_int_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.IntOpt, '66', 66) def test_conf_file_int_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.IntOpt, '66', 66) def test_conf_file_int_ignore_dname(self): self._do_dname_test_ignore(cfg.IntOpt, '64', 64) def test_conf_file_int_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.IntOpt, '64', 64) def test_conf_file_int_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.IntOpt, '64', 64) def test_conf_file_float_default(self): self.conf.register_opt(cfg.FloatOpt('foo', default=6.66)) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 6.66) def test_conf_file_float_value(self): self.conf.register_opt(cfg.FloatOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = 6.66\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 6.66) def test_conf_file_float_value_override(self): self.conf.register_cli_opt(cfg.FloatOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = 6.6\n'), ('2', '[DEFAULT]\n' 'foo = 6.66\n')]) self.conf(['--foo', '6', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 6.66) def test_conf_file_float_use_dname(self): self._do_dname_test_use(cfg.FloatOpt, '66.54', 66.54) def test_conf_file_float_use_dgroup(self): self._do_dgroup_test_use(cfg.FloatOpt, '66.54', 66.54) def test_conf_file_float_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.FloatOpt, '66.54', 66.54) def test_conf_file_float_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.FloatOpt, '66.54', 66.54) def test_conf_file_float_ignore_dname(self): self._do_dname_test_ignore(cfg.FloatOpt, '64.54', 64.54) def test_conf_file_float_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.FloatOpt, '64.54', 64.54) def test_conf_file_float_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.FloatOpt, '64.54', 64.54) def test_conf_file_list_default(self): self.conf.register_opt(cfg.ListOpt('foo', default=['bar'])) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar']) def test_conf_file_list_value(self): self.conf.register_opt(cfg.ListOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar']) def test_conf_file_list_value_override(self): self.conf.register_cli_opt(cfg.ListOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = bar,bar\n'), ('2', '[DEFAULT]\n' 'foo = b,a,r\n')]) self.conf(['--foo', 'bar', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['b', 'a', 'r']) def test_conf_file_list_use_dname(self): self._do_dname_test_use(cfg.ListOpt, 'a,b,c', ['a', 'b', 'c']) def test_conf_file_list_use_dgroup(self): self._do_dgroup_test_use(cfg.ListOpt, 'a,b,c', ['a', 'b', 'c']) def test_conf_file_list_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.ListOpt, 'a,b,c', ['a', 'b', 'c']) def test_conf_file_list_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.ListOpt, 'a,b,c', ['a', 'b', 'c']) def test_conf_file_list_ignore_dname(self): self._do_dname_test_ignore(cfg.ListOpt, 'd,e,f', ['d', 'e', 'f']) def test_conf_file_list_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.ListOpt, 'd,e,f', ['d', 'e', 'f']) def test_conf_file_list_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore( cfg.ListOpt, 'd,e,f', ['d', 'e', 'f']) def test_conf_file_list_spaces_use_dname(self): self._do_dname_test_use(cfg.ListOpt, 'a, b, c', ['a', 'b', 'c']) def test_conf_file_list_spaces_use_dgroup(self): self._do_dgroup_test_use(cfg.ListOpt, 'a, b, c', ['a', 'b', 'c']) def test_conf_file_list_spaces_use_default_dgroup(self): self._do_default_dgroup_test_use( cfg.ListOpt, 'a, b, c', ['a', 'b', 'c']) def test_conf_file_list_spaces_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use( cfg.ListOpt, 'a, b, c', ['a', 'b', 'c']) def test_conf_file_list_spaces_ignore_dname(self): self._do_dname_test_ignore(cfg.ListOpt, 'd, e, f', ['d', 'e', 'f']) def test_conf_file_list_spaces_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.ListOpt, 'd, e, f', ['d', 'e', 'f']) def test_conf_file_list_spaces_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.ListOpt, 'd, e, f', ['d', 'e', 'f']) def test_conf_file_dict_default(self): self.conf.register_opt(cfg.DictOpt('foo', default={'key': 'bar'})) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, {'key': 'bar'}) def test_conf_file_dict_value(self): self.conf.register_opt(cfg.DictOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = key:bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, {'key': 'bar'}) def test_conf_file_dict_colon_in_value(self): self.conf.register_opt(cfg.DictOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = key:bar:baz\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, {'key': 'bar:baz'}) def test_conf_file_dict_value_no_colon(self): self.conf.register_opt(cfg.DictOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = key:bar,baz\n')]) self.conf(['--config-file', paths[0]]) self.assertRaises(cfg.ConfigFileValueError, self.conf._get, 'foo') self.assertRaises(AttributeError, getattr, self.conf, 'foo') def test_conf_file_dict_value_duplicate_key(self): self.conf.register_opt(cfg.DictOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = key:bar,key:baz\n')]) self.conf(['--config-file', paths[0]]) self.assertRaises(cfg.ConfigFileValueError, self.conf._get, 'foo') self.assertRaises(AttributeError, getattr, self.conf, 'foo') def test_conf_file_dict_values_override_deprecated(self): self.conf.register_cli_opt(cfg.DictOpt('foo', deprecated_name='oldfoo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = key1:bar1\n'), ('2', '[DEFAULT]\n' 'oldfoo = key2:bar2\n' 'oldfoo = key3:bar3\n')]) self.conf(['--foo', 'key0:bar0', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, {'key3': 'bar3'}) def test_conf_file_dict_deprecated(self): self.conf.register_opt(cfg.DictOpt('newfoo', deprecated_name='oldfoo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'oldfoo= key1:bar1\n' 'oldfoo = key2:bar2\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'newfoo')) self.assertEqual(self.conf.newfoo, {'key2': 'bar2'}) def test_conf_file_dict_value_override(self): self.conf.register_cli_opt(cfg.DictOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = key:bar,key2:bar\n'), ('2', '[DEFAULT]\n' 'foo = k1:v1,k2:v2\n')]) self.conf(['--foo', 'x:y,x2:y2', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, {'k1': 'v1', 'k2': 'v2'}) def test_conf_file_dict_use_dname(self): self._do_dname_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_use_dgroup(self): self._do_dgroup_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_ignore_dname(self): self._do_dname_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_dict_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_dict_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_dict_spaces_use_dname(self): self._do_dname_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_spaces_use_dgroup(self): self._do_dgroup_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_spaces_use_default_dgroup(self): self._do_default_dgroup_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_spaces_use_dgroup_and_dname(self): self._do_dgroup_and_dname_test_use(cfg.DictOpt, 'k1:a,k2:b,k3:c', {'k1': 'a', 'k2': 'b', 'k3': 'c'}) def test_conf_file_dict_spaces_ignore_dname(self): self._do_dname_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_dict_spaces_ignore_dgroup(self): self._do_dgroup_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_dict_spaces_ignore_dgroup_and_dname(self): self._do_dgroup_and_dname_test_ignore(cfg.DictOpt, 'k1:d,k2:e,k3:f', {'k1': 'd', 'k2': 'e', 'k3': 'f'}) def test_conf_file_multistr_default(self): self.conf.register_opt(cfg.MultiStrOpt('foo', default=['bar'])) paths = self.create_tempfiles([('test', '[DEFAULT]\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar']) def test_conf_file_multistr_value(self): self.conf.register_opt(cfg.MultiStrOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar']) def test_conf_file_multistr_values_append_deprecated(self): self.conf.register_cli_opt(cfg.MultiStrOpt('foo', deprecated_name='oldfoo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = bar1\n'), ('2', '[DEFAULT]\n' 'oldfoo = bar2\n' 'oldfoo = bar3\n')]) self.conf(['--foo', 'bar0', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar0', 'bar1', 'bar2', 'bar3']) def test_conf_file_multistr_values_append(self): self.conf.register_cli_opt(cfg.MultiStrOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = bar1\n'), ('2', '[DEFAULT]\n' 'foo = bar2\n' 'foo = bar3\n')]) self.conf(['--foo', 'bar0', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, ['bar0', 'bar1', 'bar2', 'bar3']) def test_conf_file_multistr_deprecated(self): self.conf.register_opt( cfg.MultiStrOpt('newfoo', deprecated_name='oldfoo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'oldfoo= bar1\n' 'oldfoo = bar2\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'newfoo')) self.assertEqual(self.conf.newfoo, ['bar1', 'bar2']) def test_conf_file_multiple_opts(self): self.conf.register_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')]) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n' 'bar = foo\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') self.assertTrue(hasattr(self.conf, 'bar')) self.assertEqual(self.conf.bar, 'foo') def test_conf_file_raw_value(self): self.conf.register_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar-%08x\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar-%08x') class ConfigFileReloadTestCase(BaseTestCase): def test_conf_files_reload(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = baar\n'), ('2', '[DEFAULT]\n' 'foo = baaar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'baar') shutil.copy(paths[1], paths[0]) self.conf.reload_config_files() self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'baaar') def test_conf_files_reload_default(self): self.conf.register_cli_opt(cfg.StrOpt('foo1')) self.conf.register_cli_opt(cfg.StrOpt('foo2')) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo1 = default1\n'), ('2', '[DEFAULT]\n' 'foo2 = default2\n')]) paths_change = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo1 = change_default1\n'), ('2', '[DEFAULT]\n' 'foo2 = change_default2\n')]) self.conf(args=[], default_config_files=paths) self.assertTrue(hasattr(self.conf, 'foo1')) self.assertEquals(self.conf.foo1, 'default1') self.assertTrue(hasattr(self.conf, 'foo2')) self.assertEquals(self.conf.foo2, 'default2') shutil.copy(paths_change[0], paths[0]) shutil.copy(paths_change[1], paths[1]) self.conf.reload_config_files() self.assertTrue(hasattr(self.conf, 'foo1')) self.assertEquals(self.conf.foo1, 'change_default1') self.assertTrue(hasattr(self.conf, 'foo2')) self.assertEquals(self.conf.foo2, 'change_default2') def test_conf_files_reload_file_not_found(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = baar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'baar') os.remove(paths[0]) self.conf.reload_config_files() self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'baar') def test_conf_files_reload_error(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) self.conf.register_cli_opt(cfg.StrOpt('foo1', required=True)) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = test1\n' 'foo1 = test11\n'), ('2', '[DEFAULT]\n' 'foo2 = test2\n' 'foo3 = test22\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'test1') self.assertTrue(hasattr(self.conf, 'foo1')) self.assertEquals(self.conf.foo1, 'test11') shutil.copy(paths[1], paths[0]) self.conf.reload_config_files() self.assertTrue(hasattr(self.conf, 'foo')) self.assertEquals(self.conf.foo, 'test1') self.assertTrue(hasattr(self.conf, 'foo1')) self.assertEquals(self.conf.foo1, 'test11') class OptGroupsTestCase(BaseTestCase): def test_arg_group(self): blaa_group = cfg.OptGroup('blaa', 'blaa options') self.conf.register_group(blaa_group) self.conf.register_cli_opt(cfg.StrOpt('foo'), group=blaa_group) self.conf(['--blaa-foo', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_autocreate_group_by_name(self): self.conf.register_cli_opt(cfg.StrOpt('foo'), group='blaa') self.conf(['--blaa-foo', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_autocreate_group_by_group(self): group = cfg.OptGroup(name='blaa', title='Blaa options') self.conf.register_cli_opt(cfg.StrOpt('foo'), group=group) self.conf(['--blaa-foo', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_autocreate_title(self): blaa_group = cfg.OptGroup('blaa') self.assertEqual(blaa_group.title, 'blaa options') def test_arg_group_by_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt(cfg.StrOpt('foo'), group='blaa') self.conf(['--blaa-foo', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_with_default(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt( cfg.StrOpt('foo', default='bar'), group='blaa') self.conf([]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_with_conf_and_group_opts(self): self.conf.register_cli_opt(cfg.StrOpt('conf'), group='blaa') self.conf.register_cli_opt(cfg.StrOpt('group'), group='blaa') self.conf(['--blaa-conf', 'foo', '--blaa-group', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'conf')) self.assertEqual(self.conf.blaa.conf, 'foo') self.assertTrue(hasattr(self.conf.blaa, 'group')) self.assertEqual(self.conf.blaa.group, 'bar') def test_arg_group_in_config_file(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo'), group='blaa') paths = self.create_tempfiles([('test', '[blaa]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_with_deprecated_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', deprecated_name='oldfoo'), group='blaa') paths = self.create_tempfiles([('test', '[blaa]\n' 'oldfoo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_with_deprecated_group(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', deprecated_group='DEFAULT'), group='blaa') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_with_deprecated_group_and_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt( cfg.StrOpt('foo', deprecated_group='DEFAULT', deprecated_name='oldfoo'), group='blaa') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'oldfoo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_override_deprecated_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', deprecated_name='oldfoo'), group='blaa') paths = self.create_tempfiles([('test', '[blaa]\n' 'foo = bar\n' 'oldfoo = blabla\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_override_deprecated_group(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', deprecated_group='DEFAULT'), group='blaa') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = blabla\n' '[blaa]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_override_deprecated_group_and_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt( cfg.StrOpt('foo', deprecated_group='DEFAULT', deprecated_name='oldfoo'), group='blaa') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'oldfoo = blabla\n' '[blaa]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_with_capital_name(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo'), group='blaa') paths = self.create_tempfiles([('test', '[BLAA]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertFalse(hasattr(self.conf, 'BLAA')) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_arg_group_in_config_file_with_capital_name_on_legacy_code(self): self.conf.register_group(cfg.OptGroup('BLAA')) self.conf.register_opt(cfg.StrOpt('foo'), group='BLAA') paths = self.create_tempfiles([('test', '[BLAA]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertFalse(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf, 'BLAA')) self.assertTrue(hasattr(self.conf.BLAA, 'foo')) self.assertEqual(self.conf.BLAA.foo, 'bar') class MappingInterfaceTestCase(BaseTestCase): def test_mapping_interface(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf(['--foo', 'bar']) self.assertTrue('foo' in self.conf) self.assertTrue('config_file' in self.conf) self.assertEqual(len(self.conf), 3) self.assertEqual(self.conf['foo'], 'bar') self.assertEqual(self.conf.get('foo'), 'bar') self.assertTrue('bar' in list(self.conf.values())) def test_mapping_interface_with_group(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt(cfg.StrOpt('foo'), group='blaa') self.conf(['--blaa-foo', 'bar']) self.assertTrue('blaa' in self.conf) self.assertTrue('foo' in list(self.conf['blaa'])) self.assertEqual(len(self.conf['blaa']), 1) self.assertEqual(self.conf['blaa']['foo'], 'bar') self.assertEqual(self.conf['blaa'].get('foo'), 'bar') self.assertTrue('bar' in self.conf['blaa'].values()) self.assertEqual(self.conf.blaa, self.conf['blaa']) class ReRegisterOptTestCase(BaseTestCase): def test_conf_file_re_register_opt(self): opt = cfg.StrOpt('foo') self.assertTrue(self.conf.register_opt(opt)) self.assertFalse(self.conf.register_opt(opt)) def test_conf_file_re_register_opt_in_group(self): group = cfg.OptGroup('blaa') self.conf.register_group(group) self.conf.register_group(group) # not an error opt = cfg.StrOpt('foo') self.assertTrue(self.conf.register_opt(opt, group=group)) self.assertFalse(self.conf.register_opt(opt, group='blaa')) class TemplateSubstitutionTestCase(BaseTestCase): def _prep_test_str_sub(self, foo_default=None, bar_default=None): self.conf.register_cli_opt(cfg.StrOpt('foo', default=foo_default)) self.conf.register_cli_opt(cfg.StrOpt('bar', default=bar_default)) def _assert_str_sub(self): self.assertTrue(hasattr(self.conf, 'bar')) self.assertEqual(self.conf.bar, 'blaa') def test_str_sub_default_from_default(self): self._prep_test_str_sub(foo_default='blaa', bar_default='$foo') self.conf([]) self._assert_str_sub() def test_str_sub_default_from_default_recurse(self): self.conf.register_cli_opt(cfg.StrOpt('blaa', default='blaa')) self._prep_test_str_sub(foo_default='$blaa', bar_default='$foo') self.conf([]) self._assert_str_sub() def test_str_sub_default_from_arg(self): self._prep_test_str_sub(bar_default='$foo') self.conf(['--foo', 'blaa']) self._assert_str_sub() def test_str_sub_default_from_config_file(self): self._prep_test_str_sub(bar_default='$foo') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = blaa\n')]) self.conf(['--config-file', paths[0]]) self._assert_str_sub() def test_str_sub_arg_from_default(self): self._prep_test_str_sub(foo_default='blaa') self.conf(['--bar', '$foo']) self._assert_str_sub() def test_str_sub_arg_from_arg(self): self._prep_test_str_sub() self.conf(['--foo', 'blaa', '--bar', '$foo']) self._assert_str_sub() def test_str_sub_arg_from_config_file(self): self._prep_test_str_sub() paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = blaa\n')]) self.conf(['--config-file', paths[0], '--bar=$foo']) self._assert_str_sub() def test_str_sub_config_file_from_default(self): self._prep_test_str_sub(foo_default='blaa') paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'bar = $foo\n')]) self.conf(['--config-file', paths[0]]) self._assert_str_sub() def test_str_sub_config_file_from_arg(self): self._prep_test_str_sub() paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'bar = $foo\n')]) self.conf(['--config-file', paths[0], '--foo=blaa']) self._assert_str_sub() def test_str_sub_config_file_from_config_file(self): self._prep_test_str_sub() paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'bar = $foo\n' 'foo = blaa\n')]) self.conf(['--config-file', paths[0]]) self._assert_str_sub() def test_str_sub_group_from_default(self): self.conf.register_cli_opt(cfg.StrOpt('foo', default='blaa')) self.conf.register_group(cfg.OptGroup('ba')) self.conf.register_cli_opt(cfg.StrOpt('r', default='$foo'), group='ba') self.conf([]) self.assertTrue(hasattr(self.conf, 'ba')) self.assertTrue(hasattr(self.conf.ba, 'r')) self.assertEqual(self.conf.ba.r, 'blaa') class ConfigDirTestCase(BaseTestCase): def test_config_dir(self): snafu_group = cfg.OptGroup('snafu') self.conf.register_group(snafu_group) self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('bell'), group=snafu_group) dir = tempfile.mkdtemp() self.tempdirs.append(dir) paths = self.create_tempfiles([(os.path.join(dir, '00-test'), '[DEFAULT]\n' 'foo = bar-00\n' '[snafu]\n' 'bell = whistle-00\n'), (os.path.join(dir, '02-test'), '[snafu]\n' 'bell = whistle-02\n' '[DEFAULT]\n' 'foo = bar-02\n'), (os.path.join(dir, '01-test'), '[DEFAULT]\n' 'foo = bar-01\n')]) self.conf(['--foo', 'bar', '--config-dir', os.path.dirname(paths[0])]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar-02') self.assertTrue(hasattr(self.conf, 'snafu')) self.assertTrue(hasattr(self.conf.snafu, 'bell')) self.assertEqual(self.conf.snafu.bell, 'whistle-02') def test_config_dir_file_precedence(self): snafu_group = cfg.OptGroup('snafu') self.conf.register_group(snafu_group) self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('bell'), group=snafu_group) dir = tempfile.mkdtemp() self.tempdirs.append(dir) paths = self.create_tempfiles([(os.path.join(dir, '00-test'), '[DEFAULT]\n' 'foo = bar-00\n'), ('01-test', '[snafu]\n' 'bell = whistle-01\n' '[DEFAULT]\n' 'foo = bar-01\n'), ('03-test', '[snafu]\n' 'bell = whistle-03\n' '[DEFAULT]\n' 'foo = bar-03\n'), (os.path.join(dir, '02-test'), '[DEFAULT]\n' 'foo = bar-02\n')]) self.conf(['--foo', 'bar', '--config-file', paths[1], '--config-dir', os.path.dirname(paths[0]), '--config-file', paths[2], ]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar-03') self.assertTrue(hasattr(self.conf, 'snafu')) self.assertTrue(hasattr(self.conf.snafu, 'bell')) self.assertEqual(self.conf.snafu.bell, 'whistle-03') def test_config_dir_default_file_precedence(self): snafu_group = cfg.OptGroup('snafu') self.conf.register_group(snafu_group) self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('bell'), group=snafu_group) dir = tempfile.mkdtemp() self.tempdirs.append(dir) paths = self.create_tempfiles([(os.path.join(dir, '00-test'), '[DEFAULT]\n' 'foo = bar-00\n' '[snafu]\n' 'bell = whistle-11\n'), ('01-test', '[snafu]\n' 'bell = whistle-01\n' '[DEFAULT]\n' 'foo = bar-01\n'), ('03-test', '[snafu]\n' 'bell = whistle-03\n' '[DEFAULT]\n' 'foo = bar-03\n'), (os.path.join(dir, '02-test'), '[DEFAULT]\n' 'foo = bar-02\n')]) self.conf(['--foo', 'bar', '--config-dir', os.path.dirname(paths[0])], default_config_files=[paths[1], paths[2]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar-02') self.assertTrue(hasattr(self.conf, 'snafu')) self.assertTrue(hasattr(self.conf.snafu, 'bell')) self.assertEqual(self.conf.snafu.bell, 'whistle-11') class ReparseTestCase(BaseTestCase): def test_reparse(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt( cfg.StrOpt('foo', default='r'), group='blaa') paths = self.create_tempfiles([('test', '[blaa]\n' 'foo = b\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'b') self.conf(['--blaa-foo', 'a']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'a') self.conf([]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'r') class OverridesTestCase(BaseTestCase): def test_default_none(self): self.conf.register_opt(cfg.StrOpt('foo', default='foo')) self.conf([]) self.assertEqual(self.conf.foo, 'foo') self.conf.set_default('foo', None) self.assertEqual(self.conf.foo, None) self.conf.clear_default('foo') self.assertEqual(self.conf.foo, 'foo') def test_override_none(self): self.conf.register_opt(cfg.StrOpt('foo', default='foo')) self.conf([]) self.assertEqual(self.conf.foo, 'foo') self.conf.set_override('foo', None) self.assertEqual(self.conf.foo, None) self.conf.clear_override('foo') self.assertEqual(self.conf.foo, 'foo') def test_no_default_override(self): self.conf.register_opt(cfg.StrOpt('foo')) self.conf([]) self.assertEqual(self.conf.foo, None) self.conf.set_default('foo', 'bar') self.assertEqual(self.conf.foo, 'bar') self.conf.clear_default('foo') self.assertEqual(self.conf.foo, None) def test_default_override(self): self.conf.register_opt(cfg.StrOpt('foo', default='foo')) self.conf([]) self.assertEqual(self.conf.foo, 'foo') self.conf.set_default('foo', 'bar') self.assertEqual(self.conf.foo, 'bar') self.conf.clear_default('foo') self.assertEqual(self.conf.foo, 'foo') def test_override(self): self.conf.register_opt(cfg.StrOpt('foo')) self.conf.set_override('foo', 'bar') self.conf([]) self.assertEqual(self.conf.foo, 'bar') self.conf.clear_override('foo') self.assertEqual(self.conf.foo, None) def test_group_no_default_override(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo'), group='blaa') self.conf([]) self.assertEqual(self.conf.blaa.foo, None) self.conf.set_default('foo', 'bar', group='blaa') self.assertEqual(self.conf.blaa.foo, 'bar') self.conf.clear_default('foo', group='blaa') self.assertEqual(self.conf.blaa.foo, None) def test_group_default_override(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', default='foo'), group='blaa') self.conf([]) self.assertEqual(self.conf.blaa.foo, 'foo') self.conf.set_default('foo', 'bar', group='blaa') self.assertEqual(self.conf.blaa.foo, 'bar') self.conf.clear_default('foo', group='blaa') self.assertEqual(self.conf.blaa.foo, 'foo') def test_group_override(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo'), group='blaa') self.assertEqual(self.conf.blaa.foo, None) self.conf.set_override('foo', 'bar', group='blaa') self.conf([]) self.assertEqual(self.conf.blaa.foo, 'bar') self.conf.clear_override('foo', group='blaa') self.assertEqual(self.conf.blaa.foo, None) def test_cli_bool_default(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) self.conf.set_default('foo', True) self.assertTrue(self.conf.foo) self.conf([]) self.assertTrue(self.conf.foo) self.conf.set_default('foo', False) self.assertFalse(self.conf.foo) self.conf.clear_default('foo') self.assertTrue(self.conf.foo is None) def test_cli_bool_override(self): self.conf.register_cli_opt(cfg.BoolOpt('foo')) self.conf.set_override('foo', True) self.assertTrue(self.conf.foo) self.conf([]) self.assertTrue(self.conf.foo) self.conf.set_override('foo', False) self.assertFalse(self.conf.foo) self.conf.clear_override('foo') self.assertTrue(self.conf.foo is None) class ResetAndClearTestCase(BaseTestCase): def test_clear(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('bar'), group='blaa') self.assertEqual(self.conf.foo, None) self.assertEqual(self.conf.blaa.bar, None) self.conf(['--foo', 'foo', '--blaa-bar', 'bar']) self.assertEqual(self.conf.foo, 'foo') self.assertEqual(self.conf.blaa.bar, 'bar') self.conf.clear() self.assertEqual(self.conf.foo, None) self.assertEqual(self.conf.blaa.bar, None) def test_reset_and_clear_with_defaults_and_overrides(self): self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('bar'), group='blaa') self.conf.set_default('foo', 'foo') self.conf.set_override('bar', 'bar', group='blaa') self.conf(['--foo', 'foofoo']) self.assertEqual(self.conf.foo, 'foofoo') self.assertEqual(self.conf.blaa.bar, 'bar') self.conf.clear() self.assertEqual(self.conf.foo, 'foo') self.assertEqual(self.conf.blaa.bar, 'bar') self.conf.reset() self.assertEqual(self.conf.foo, None) self.assertEqual(self.conf.blaa.bar, None) class UnregisterOptTestCase(BaseTestCase): def test_unregister_opt(self): opts = [cfg.StrOpt('foo'), cfg.StrOpt('bar')] self.conf.register_opts(opts) self.assertTrue(hasattr(self.conf, 'foo')) self.assertTrue(hasattr(self.conf, 'bar')) self.conf.unregister_opt(opts[0]) self.assertFalse(hasattr(self.conf, 'foo')) self.assertTrue(hasattr(self.conf, 'bar')) self.conf([]) self.assertRaises(cfg.ArgsAlreadyParsedError, self.conf.unregister_opt, opts[1]) self.conf.clear() self.assertTrue(hasattr(self.conf, 'bar')) self.conf.unregister_opts(opts) def test_unregister_opt_from_group(self): opt = cfg.StrOpt('foo') self.conf.register_opt(opt, group='blaa') self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.conf.unregister_opt(opt, group='blaa') self.assertFalse(hasattr(self.conf.blaa, 'foo')) class ImportOptTestCase(BaseTestCase): def test_import_opt(self): self.assertFalse(hasattr(cfg.CONF, 'blaa')) cfg.CONF.import_opt('blaa', 'tests.testmods.blaa_opt') self.assertTrue(hasattr(cfg.CONF, 'blaa')) def test_import_opt_in_group(self): self.assertFalse(hasattr(cfg.CONF, 'bar')) cfg.CONF.import_opt('foo', 'tests.testmods.bar_foo_opt', group='bar') self.assertTrue(hasattr(cfg.CONF, 'bar')) self.assertTrue(hasattr(cfg.CONF.bar, 'foo')) def test_import_opt_import_errror(self): self.assertRaises(ImportError, cfg.CONF.import_opt, 'blaa', 'tests.testmods.blaablaa_opt') def test_import_opt_no_such_opt(self): self.assertRaises(cfg.NoSuchOptError, cfg.CONF.import_opt, 'blaablaa', 'tests.testmods.blaa_opt') def test_import_opt_no_such_group(self): self.assertRaises(cfg.NoSuchGroupError, cfg.CONF.import_opt, 'blaa', 'tests.testmods.blaa_opt', group='blaa') class ImportGroupTestCase(BaseTestCase): def test_import_group(self): self.assertFalse(hasattr(cfg.CONF, 'qux')) cfg.CONF.import_group('qux', 'tests.testmods.baz_qux_opt') self.assertTrue(hasattr(cfg.CONF, 'qux')) self.assertTrue(hasattr(cfg.CONF.qux, 'baz')) def test_import_group_import_error(self): self.assertRaises(ImportError, cfg.CONF.import_group, 'qux', 'tests.testmods.bazzz_quxxx_opt') def test_import_group_no_such_group(self): self.assertRaises(cfg.NoSuchGroupError, cfg.CONF.import_group, 'quxxx', 'tests.testmods.baz_qux_opt') class RequiredOptsTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) self.conf.register_opt(cfg.StrOpt('boo', required=False)) def test_required_opt(self): self.conf.register_opt(cfg.StrOpt('foo', required=True)) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_required_cli_opt(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) self.conf(['--foo', 'bar']) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_required_cli_opt_with_dash(self): self.conf.register_cli_opt(cfg.StrOpt('foo-bar', required=True)) self.conf(['--foo-bar', 'baz']) self.assertTrue(hasattr(self.conf, 'foo_bar')) self.assertEqual(self.conf.foo_bar, 'baz') def test_missing_required_opt(self): self.conf.register_opt(cfg.StrOpt('foo', required=True)) self.assertRaises(cfg.RequiredOptError, self.conf, []) def test_missing_required_cli_opt(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) self.assertRaises(cfg.RequiredOptError, self.conf, []) def test_required_group_opt(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', required=True), group='blaa') paths = self.create_tempfiles([('test', '[blaa]\n' 'foo = bar')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_required_cli_group_opt(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt( cfg.StrOpt('foo', required=True), group='blaa') self.conf(['--blaa-foo', 'bar']) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'foo')) self.assertEqual(self.conf.blaa.foo, 'bar') def test_missing_required_group_opt(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', required=True), group='blaa') self.assertRaises(cfg.RequiredOptError, self.conf, []) def test_missing_required_cli_group_opt(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt( cfg.StrOpt('foo', required=True), group='blaa') self.assertRaises(cfg.RequiredOptError, self.conf, []) def test_required_opt_with_default(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) self.conf.set_default('foo', 'bar') self.conf([]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') def test_required_opt_with_override(self): self.conf.register_cli_opt(cfg.StrOpt('foo', required=True)) self.conf.set_override('foo', 'bar') self.conf([]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') class SadPathTestCase(BaseTestCase): def test_unknown_attr(self): self.conf([]) self.assertFalse(hasattr(self.conf, 'foo')) self.assertRaises(AttributeError, getattr, self.conf, 'foo') self.assertRaises(cfg.NoSuchOptError, self.conf._get, 'foo') self.assertRaises(cfg.NoSuchOptError, self.conf.__getattr__, 'foo') def test_unknown_attr_is_attr_error(self): self.conf([]) self.assertFalse(hasattr(self.conf, 'foo')) self.assertRaises(AttributeError, getattr, self.conf, 'foo') def test_unknown_group_attr(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf([]) self.assertTrue(hasattr(self.conf, 'blaa')) self.assertFalse(hasattr(self.conf.blaa, 'foo')) self.assertRaises(cfg.NoSuchOptError, getattr, self.conf.blaa, 'foo') def test_ok_duplicate(self): opt = cfg.StrOpt('foo') self.conf.register_cli_opt(opt) opt2 = cfg.StrOpt('foo') self.conf.register_cli_opt(opt2) self.conf([]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, None) def test_error_duplicate(self): self.conf.register_cli_opt(cfg.StrOpt('foo', help='bar')) self.assertRaises(cfg.DuplicateOptError, self.conf.register_cli_opt, cfg.StrOpt('foo')) def test_error_duplicate_with_different_dest(self): self.conf.register_cli_opt(cfg.StrOpt('foo', dest='f')) self.conf.register_cli_opt(cfg.StrOpt('foo')) self.assertRaises(cfg.DuplicateOptError, self.conf, []) def test_error_duplicate_short(self): self.conf.register_cli_opt(cfg.StrOpt('foo', short='f')) self.conf.register_cli_opt(cfg.StrOpt('bar', short='f')) self.assertRaises(cfg.DuplicateOptError, self.conf, []) def test_already_parsed(self): self.conf([]) self.assertRaises(cfg.ArgsAlreadyParsedError, self.conf.register_cli_opt, cfg.StrOpt('foo')) def test_bad_cli_arg(self): self.conf.register_opt(cfg.BoolOpt('foo')) self.useFixture(fixtures.MonkeyPatch('sys.stderr', moves.StringIO())) self.assertRaises(SystemExit, self.conf, ['--foo']) self.assertTrue('error' in sys.stderr.getvalue()) self.assertTrue('--foo' in sys.stderr.getvalue()) def _do_test_bad_cli_value(self, opt_class): self.conf.register_cli_opt(opt_class('foo')) self.useFixture(fixtures.MonkeyPatch('sys.stderr', moves.StringIO())) self.assertRaises(SystemExit, self.conf, ['--foo', 'bar']) self.assertTrue('foo' in sys.stderr.getvalue()) self.assertTrue('bar' in sys.stderr.getvalue()) def test_bad_int_arg(self): self._do_test_bad_cli_value(cfg.IntOpt) def test_bad_float_arg(self): self._do_test_bad_cli_value(cfg.FloatOpt) def test_conf_file_not_found(self): (fd, path) = tempfile.mkstemp() os.remove(path) self.assertRaises(cfg.ConfigFilesNotFoundError, self.conf, ['--config-file', path]) def test_conf_file_broken(self): paths = self.create_tempfiles([('test', 'foo')]) self.assertRaises(cfg.ConfigFileParseError, self.conf, ['--config-file', paths[0]]) def _do_test_conf_file_bad_value(self, opt_class): self.conf.register_opt(opt_class('foo')) paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertRaises(AttributeError, getattr, self.conf, 'foo') self.assertRaises(cfg.ConfigFileValueError, self.conf._get, 'foo') def test_conf_file_bad_bool(self): self._do_test_conf_file_bad_value(cfg.BoolOpt) def test_conf_file_bad_int(self): self._do_test_conf_file_bad_value(cfg.IntOpt) def test_conf_file_bad_float(self): self._do_test_conf_file_bad_value(cfg.FloatOpt) def test_str_sub_from_group(self): self.conf.register_group(cfg.OptGroup('f')) self.conf.register_cli_opt(cfg.StrOpt('oo', default='blaa'), group='f') self.conf.register_cli_opt(cfg.StrOpt('bar', default='$f.oo')) self.conf([]) self.assertFalse(hasattr(self.conf, 'bar')) self.assertRaises( AttributeError, getattr, self.conf, 'bar') self.assertRaises( cfg.TemplateSubstitutionError, self.conf._get, 'bar') def test_set_default_unknown_attr(self): self.conf([]) self.assertRaises( cfg.NoSuchOptError, self.conf.set_default, 'foo', 'bar') def test_set_default_unknown_group(self): self.conf([]) self.assertRaises(cfg.NoSuchGroupError, self.conf.set_default, 'foo', 'bar', group='blaa') def test_set_override_unknown_attr(self): self.conf([]) self.assertRaises( cfg.NoSuchOptError, self.conf.set_override, 'foo', 'bar') def test_set_override_unknown_group(self): self.conf([]) self.assertRaises(cfg.NoSuchGroupError, self.conf.set_override, 'foo', 'bar', group='blaa') class FindFileTestCase(BaseTestCase): def test_find_policy_file(self): policy_file = '/etc/policy.json' self.useFixture(fixtures.MonkeyPatch( 'os.path.exists', lambda p: p == policy_file)) self.conf([]) self.assertEqual(self.conf.find_file('foo.json'), None) self.assertEqual(self.conf.find_file('policy.json'), policy_file) def test_find_policy_file_with_config_file(self): dir = tempfile.mkdtemp() self.tempdirs.append(dir) paths = self.create_tempfiles([(os.path.join(dir, 'test.conf'), '[DEFAULT]'), (os.path.join(dir, 'policy.json'), '{}')], ext='') self.conf(['--config-file', paths[0]]) self.assertEqual(self.conf.find_file('policy.json'), paths[1]) def test_find_policy_file_with_config_dir(self): dir = tempfile.mkdtemp() self.tempdirs.append(dir) path = self.create_tempfiles([(os.path.join(dir, 'policy.json'), '{}')], ext='')[0] self.conf(['--config-dir', dir]) self.assertEqual(self.conf.find_file('policy.json'), path) class OptDumpingTestCase(BaseTestCase): class FakeLogger: def __init__(self, test_case, expected_lvl): self.test_case = test_case self.expected_lvl = expected_lvl self.logged = [] def log(self, lvl, fmt, *args): self.test_case.assertEqual(lvl, self.expected_lvl) self.logged.append(fmt % args) def setUp(self): super(OptDumpingTestCase, self).setUp() self._args = ['--foo', 'this', '--blaa-bar', 'that', '--blaa-key', 'admin', '--passwd', 'hush'] def _do_test_log_opt_values(self, args): self.conf.register_cli_opt(cfg.StrOpt('foo')) self.conf.register_cli_opt(cfg.StrOpt('passwd', secret=True)) self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_cli_opt(cfg.StrOpt('bar'), 'blaa') self.conf.register_cli_opt(cfg.StrOpt('key', secret=True), 'blaa') self.conf(args) logger = self.FakeLogger(self, 666) self.conf.log_opt_values(logger, 666) self.assertEqual(logger.logged, [ "*" * 80, "Configuration options gathered from:", "command line args: ['--foo', 'this', '--blaa-bar', " "'that', '--blaa-key', 'admin', '--passwd', 'hush']", "config files: []", "=" * 80, "config_dir = None", "config_file = []", "foo = this", "passwd = ****", "blaa.bar = that", "blaa.key = *****", "*" * 80, ]) def test_log_opt_values(self): self._do_test_log_opt_values(self._args) def test_log_opt_values_from_sys_argv(self): self.useFixture(fixtures.MonkeyPatch('sys.argv', ['foo'] + self._args)) self._do_test_log_opt_values(None) class ConfigParserTestCase(BaseTestCase): def test_parse_file(self): paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n' '[BLAA]\n' 'bar = foo\n')]) sections = {} parser = cfg.ConfigParser(paths[0], sections) parser.parse() self.assertTrue('DEFAULT' in sections) self.assertTrue('BLAA' in sections) self.assertEqual(sections['DEFAULT']['foo'], ['bar']) self.assertEqual(sections['BLAA']['bar'], ['foo']) def test_parse_file_with_normalized(self): paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n' '[BLAA]\n' 'bar = foo\n')]) sections = {} normalized = {} parser = cfg.ConfigParser(paths[0], sections) parser._add_normalized(normalized) parser.parse() self.assertTrue('DEFAULT' in sections) self.assertTrue('DEFAULT' in normalized) self.assertTrue('BLAA' in sections) self.assertTrue('blaa' in normalized) self.assertEqual(sections['DEFAULT']['foo'], ['bar']) self.assertEqual(normalized['DEFAULT']['foo'], ['bar']) self.assertEqual(sections['BLAA']['bar'], ['foo']) self.assertEqual(normalized['blaa']['bar'], ['foo']) def test_no_section(self): with tempfile.NamedTemporaryFile() as tmpfile: tmpfile.write(six.b('foo = bar')) tmpfile.flush() parser = cfg.ConfigParser(tmpfile.name, {}) self.assertRaises(cfg.ParseError, parser.parse) class MultiConfigParserTestCase(BaseTestCase): def test_parse_single_file(self): paths = self.create_tempfiles([('test', '[DEFAULT]\n' 'foo = bar\n' '[BLAA]\n' 'bar = foo\n')]) parser = cfg.MultiConfigParser() read_ok = parser.read(paths) self.assertEqual(read_ok, paths) self.assertTrue('DEFAULT' in parser.parsed[0]) self.assertEqual(parser.parsed[0]['DEFAULT']['foo'], ['bar']) self.assertEqual(parser.get([('DEFAULT', 'foo')]), ['bar']) self.assertEqual(parser.get([('DEFAULT', 'foo')], multi=True), ['bar']) self.assertEqual(parser.get([('DEFAULT', 'foo')], multi=True), ['bar']) self.assertEqual(parser._get([('DEFAULT', 'foo')], multi=True, normalized=True), ['bar']) self.assertTrue('BLAA' in parser.parsed[0]) self.assertEqual(parser.parsed[0]['BLAA']['bar'], ['foo']) self.assertEqual(parser.get([('BLAA', 'bar')]), ['foo']) self.assertEqual(parser.get([('BLAA', 'bar')], multi=True), ['foo']) self.assertEqual(parser._get([('blaa', 'bar')], multi=True, normalized=True), ['foo']) def test_parse_multiple_files(self): paths = self.create_tempfiles([('test1', '[DEFAULT]\n' 'foo = bar\n' '[BLAA]\n' 'bar = foo'), ('test2', '[DEFAULT]\n' 'foo = barbar\n' '[BLAA]\n' 'bar = foofoo\n' '[bLAa]\n' 'bar = foofoofoo\n')]) parser = cfg.MultiConfigParser() read_ok = parser.read(paths) self.assertEqual(read_ok, paths) self.assertTrue('DEFAULT' in parser.parsed[0]) self.assertEqual(parser.parsed[0]['DEFAULT']['foo'], ['barbar']) self.assertTrue('DEFAULT' in parser.parsed[1]) self.assertEqual(parser.parsed[1]['DEFAULT']['foo'], ['bar']) self.assertEqual(parser.get([('DEFAULT', 'foo')]), ['barbar']) self.assertEqual(parser.get([('DEFAULT', 'foo')], multi=True), ['bar', 'barbar']) self.assertTrue('BLAA' in parser.parsed[0]) self.assertTrue('bLAa' in parser.parsed[0]) self.assertEqual(parser.parsed[0]['BLAA']['bar'], ['foofoo']) self.assertEqual(parser.parsed[0]['bLAa']['bar'], ['foofoofoo']) self.assertTrue('BLAA' in parser.parsed[1]) self.assertEqual(parser.parsed[1]['BLAA']['bar'], ['foo']) self.assertEqual(parser.get([('BLAA', 'bar')]), ['foofoo']) self.assertEqual(parser.get([('bLAa', 'bar')]), ['foofoofoo']) self.assertEqual(parser.get([('BLAA', 'bar')], multi=True), ['foo', 'foofoo']) self.assertEqual(parser._get([('BLAA', 'bar')], multi=True, normalized=True), ['foo', 'foofoo', 'foofoofoo']) class TildeExpansionTestCase(BaseTestCase): def test_config_file_tilde(self): homedir = os.path.expanduser('~') tmpfile = tempfile.mktemp(dir=homedir, prefix='cfg-', suffix='.conf') tmpbase = os.path.basename(tmpfile) try: self.conf(['--config-file', os.path.join('~', tmpbase)]) except cfg.ConfigFilesNotFoundError as cfnfe: self.assertTrue(homedir in str(cfnfe)) self.useFixture(fixtures.MonkeyPatch( 'os.path.exists', lambda p: p == tmpfile)) self.assertEqual(self.conf.find_file(tmpbase), tmpfile) def test_config_dir_tilde(self): homedir = os.path.expanduser('~') tmpdir = tempfile.mktemp(dir=homedir, prefix='cfg-', suffix='.d') tmpfile = os.path.join(tmpdir, 'foo.conf') tmpbase = os.path.basename(tmpfile) self.useFixture(fixtures.MonkeyPatch( 'glob.glob', lambda p: [tmpfile])) try: self.conf(['--config-dir', os.path.join('~', os.path.basename(tmpdir))]) except cfg.ConfigFilesNotFoundError as cfnfe: self.assertTrue(os.path.expanduser('~') in str(cfnfe)) self.useFixture(fixtures.MonkeyPatch( 'os.path.exists', lambda p: p == tmpfile)) self.assertEqual(self.conf.find_file(tmpbase), tmpfile) class SubCommandTestCase(BaseTestCase): def test_sub_command(self): def add_parsers(subparsers): sub = subparsers.add_parser('a') sub.add_argument('bar', type=int) self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', handler=add_parsers)) self.assertTrue(hasattr(self.conf, 'cmd')) self.conf(['a', '10']) self.assertTrue(hasattr(self.conf.cmd, 'name')) self.assertTrue(hasattr(self.conf.cmd, 'bar')) self.assertEqual(self.conf.cmd.name, 'a') self.assertEqual(self.conf.cmd.bar, 10) def test_sub_command_with_parent(self): def add_parsers(subparsers): parent = argparse.ArgumentParser(add_help=False) parent.add_argument('bar', type=int) subparsers.add_parser('a', parents=[parent]) self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', handler=add_parsers)) self.assertTrue(hasattr(self.conf, 'cmd')) self.conf(['a', '10']) self.assertTrue(hasattr(self.conf.cmd, 'name')) self.assertTrue(hasattr(self.conf.cmd, 'bar')) self.assertEqual(self.conf.cmd.name, 'a') self.assertEqual(self.conf.cmd.bar, 10) def test_sub_command_with_dest(self): def add_parsers(subparsers): subparsers.add_parser('a') self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', dest='command', handler=add_parsers)) self.assertTrue(hasattr(self.conf, 'command')) self.conf(['a']) self.assertEqual(self.conf.command.name, 'a') def test_sub_command_with_group(self): def add_parsers(subparsers): sub = subparsers.add_parser('a') sub.add_argument('--bar', choices='XYZ') self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', handler=add_parsers), group='blaa') self.assertTrue(hasattr(self.conf, 'blaa')) self.assertTrue(hasattr(self.conf.blaa, 'cmd')) self.conf(['a', '--bar', 'Z']) self.assertTrue(hasattr(self.conf.blaa.cmd, 'name')) self.assertTrue(hasattr(self.conf.blaa.cmd, 'bar')) self.assertEqual(self.conf.blaa.cmd.name, 'a') self.assertEqual(self.conf.blaa.cmd.bar, 'Z') def test_sub_command_not_cli(self): self.conf.register_opt(cfg.SubCommandOpt('cmd')) self.conf([]) def test_sub_command_resparse(self): def add_parsers(subparsers): subparsers.add_parser('a') self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', handler=add_parsers)) foo_opt = cfg.StrOpt('foo') self.conf.register_cli_opt(foo_opt) self.conf(['--foo=bar', 'a']) self.assertTrue(hasattr(self.conf.cmd, 'name')) self.assertEqual(self.conf.cmd.name, 'a') self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar') self.conf.clear() self.conf.unregister_opt(foo_opt) self.conf(['a']) self.assertTrue(hasattr(self.conf.cmd, 'name')) self.assertEqual(self.conf.cmd.name, 'a') self.assertFalse(hasattr(self.conf, 'foo')) def test_sub_command_no_handler(self): self.conf.register_cli_opt(cfg.SubCommandOpt('cmd')) self.useFixture(fixtures.MonkeyPatch('sys.stderr', moves.StringIO())) self.assertRaises(SystemExit, self.conf, []) self.assertTrue('error' in sys.stderr.getvalue()) def test_sub_command_with_help(self): def add_parsers(subparsers): subparsers.add_parser('a') self.conf.register_cli_opt(cfg.SubCommandOpt('cmd', title='foo foo', description='bar bar', help='blaa blaa', handler=add_parsers)) self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO())) self.assertRaises(SystemExit, self.conf, ['--help']) self.assertTrue('foo foo' in sys.stdout.getvalue()) self.assertTrue('bar bar' in sys.stdout.getvalue()) self.assertTrue('blaa blaa' in sys.stdout.getvalue()) def test_sub_command_errors(self): def add_parsers(subparsers): sub = subparsers.add_parser('a') sub.add_argument('--bar') self.conf.register_cli_opt(cfg.BoolOpt('bar')) self.conf.register_cli_opt( cfg.SubCommandOpt('cmd', handler=add_parsers)) self.conf(['a']) self.assertRaises(cfg.DuplicateOptError, getattr, self.conf.cmd, 'bar') self.assertRaises(cfg.NoSuchOptError, getattr, self.conf.cmd, 'foo') def test_sub_command_multiple(self): self.conf.register_cli_opt(cfg.SubCommandOpt('cmd1')) self.conf.register_cli_opt(cfg.SubCommandOpt('cmd2')) self.useFixture(fixtures.MonkeyPatch('sys.stderr', moves.StringIO())) self.assertRaises(SystemExit, self.conf, []) self.assertTrue('multiple' in sys.stderr.getvalue()) class SetDefaultsTestCase(BaseTestCase): def test_default_to_none(self): opts = [cfg.StrOpt('foo', default='foo')] self.conf.register_opts(opts) cfg.set_defaults(opts, foo=None) self.conf([]) self.assertEqual(self.conf.foo, None) def test_default_from_none(self): opts = [cfg.StrOpt('foo')] self.conf.register_opts(opts) cfg.set_defaults(opts, foo='bar') self.conf([]) self.assertEqual(self.conf.foo, 'bar') def test_change_default(self): opts = [cfg.StrOpt('foo', default='foo')] self.conf.register_opts(opts) cfg.set_defaults(opts, foo='bar') self.conf([]) self.assertEqual(self.conf.foo, 'bar') def test_change_default_many(self): opts = [cfg.StrOpt('foo', default='foo'), cfg.StrOpt('foo2', default='foo2')] self.conf.register_opts(opts) cfg.set_defaults(opts, foo='bar', foo2='bar2') self.conf([]) self.assertEqual(self.conf.foo, 'bar') self.assertEqual(self.conf.foo2, 'bar2') def test_group_default_to_none(self): opts = [cfg.StrOpt('foo', default='foo')] self.conf.register_opts(opts, group='blaa') cfg.set_defaults(opts, foo=None) self.conf([]) self.assertEqual(self.conf.blaa.foo, None) def test_group_default_from_none(self): opts = [cfg.StrOpt('foo')] self.conf.register_opts(opts, group='blaa') cfg.set_defaults(opts, foo='bar') self.conf([]) self.assertEqual(self.conf.blaa.foo, 'bar') def test_group_change_default(self): opts = [cfg.StrOpt('foo', default='foo')] self.conf.register_opts(opts, group='blaa') cfg.set_defaults(opts, foo='bar') self.conf([]) self.assertEqual(self.conf.blaa.foo, 'bar') class DeprecatedOptionsTestCase(BaseTestCase): def test_deprecated_opts_equal(self): d1 = cfg.DeprecatedOpt('oldfoo', group='oldgroup') d2 = cfg.DeprecatedOpt('oldfoo', group='oldgroup') self.assertEqual(d1, d2) def test_deprecated_opts_not_equal(self): d1 = cfg.DeprecatedOpt('oldfoo', group='oldgroup') d2 = cfg.DeprecatedOpt('oldfoo2', group='oldgroup') self.assertNotEqual(d1, d2) class MultipleDeprecatedOptionsTestCase(BaseTestCase): def test_conf_file_override_use_deprecated_name_and_group(self): self.conf.register_group(cfg.OptGroup('blaa')) self.conf.register_opt(cfg.StrOpt('foo', deprecated_name='oldfoo', deprecated_group='oldgroup'), group='blaa') paths = self.create_tempfiles([('test', '[oldgroup]\n' 'oldfoo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertEqual(self.conf.blaa.foo, 'bar') def test_conf_file_override_use_deprecated_opts(self): self.conf.register_group(cfg.OptGroup('blaa')) oldopts = [cfg.DeprecatedOpt('oldfoo', group='oldgroup')] self.conf.register_opt(cfg.StrOpt('foo', deprecated_opts=oldopts), group='blaa') paths = self.create_tempfiles([('test', '[oldgroup]\n' 'oldfoo = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertEqual(self.conf.blaa.foo, 'bar') def test_conf_file_override_use_deprecated_multi_opts(self): self.conf.register_group(cfg.OptGroup('blaa')) oldopts = [cfg.DeprecatedOpt('oldfoo', group='oldgroup'), cfg.DeprecatedOpt('oldfoo2', group='oldgroup2')] self.conf.register_opt(cfg.StrOpt('foo', deprecated_opts=oldopts), group='blaa') paths = self.create_tempfiles([('test', '[oldgroup2]\n' 'oldfoo2 = bar\n')]) self.conf(['--config-file', paths[0]]) self.assertEqual(self.conf.blaa.foo, 'bar') class ChoicesTestCase(BaseTestCase): def test_choice_default(self): self.conf.register_cli_opt(cfg.StrOpt('protocol', default='http', choices=['http', 'https', 'ftp'])) self.conf([]) self.assertEqual(self.conf.protocol, 'http') def test_choice_good(self): self.conf.register_cli_opt(cfg.StrOpt('foo', choices=['bar1', 'bar2'])) self.conf(['--foo', 'bar1']) self.assertEqual(self.conf.foo, 'bar1') def test_choice_bad(self): self.conf.register_cli_opt(cfg.StrOpt('foo', choices=['bar1', 'bar2'])) self.assertRaises(SystemExit, self.conf, ['--foo', 'bar3']) def test_conf_file_choice_value(self): self.conf.register_opt(cfg.StrOpt('foo', choices=['bar1', 'bar2'])) paths = self.create_tempfiles([('test', '[DEFAULT]\n''foo = bar1\n')]) self.conf(['--config-file', paths[0]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'bar1') def test_conf_file_bad_choice_value(self): self.conf.register_opt(cfg.StrOpt('foo', choices=['bar1', 'bar2'])) paths = self.create_tempfiles([('test', '[DEFAULT]\n''foo = bar3\n')]) self.conf(['--config-file', paths[0]]) self.assertRaises(cfg.ConfigFileValueError, self.conf._get, 'foo') self.assertRaises(AttributeError, getattr, self.conf, 'foo') def test_conf_file_choice_value_override(self): self.conf.register_cli_opt(cfg.StrOpt('foo', choices=['baar', 'baaar'])) paths = self.create_tempfiles([('1', '[DEFAULT]\n' 'foo = baar\n'), ('2', '[DEFAULT]\n' 'foo = baaar\n')]) self.conf(['--foo', 'baar', '--config-file', paths[0], '--config-file', paths[1]]) self.assertTrue(hasattr(self.conf, 'foo')) self.assertEqual(self.conf.foo, 'baaar') class PrintHelpTestCase(utils.BaseTestCase): def test_print_help_without_init(self): conf = cfg.ConfigOpts() conf.register_opts([]) self.assertRaises(cfg.NotInitializedError, conf.print_help) def test_print_help_with_clear(self): conf = cfg.ConfigOpts() conf.register_opts([]) conf([]) conf.clear() self.assertRaises(cfg.NotInitializedError, conf.print_help) oslo.config-1.2.1/tests/__init__.py0000664000175300017540000000121212221001311020316 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. oslo.config-1.2.1/tests/test_iniparser.py0000664000175300017540000000750612221001311021626 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import unittest from oslo.config import iniparser class TestParser(iniparser.BaseParser): comment_called = False values = None section = '' def __init__(self): self.values = {} def assignment(self, key, value): self.values.setdefault(self.section, {}) self.values[self.section][key] = value def new_section(self, section): self.section = section def comment(self, section): self.comment_called = True class BaseParserTestCase(unittest.TestCase): def setUp(self): self.parser = iniparser.BaseParser() def _assertParseError(self, *lines): self.assertRaises(iniparser.ParseError, self.parser.parse, lines) def test_invalid_assignment(self): self._assertParseError("foo - bar") def test_empty_key(self): self._assertParseError(": bar") def test_unexpected_continuation(self): self._assertParseError(" baz") def test_invalid_section(self): self._assertParseError("[section") def test_no_section_name(self): self._assertParseError("[]") class ParserTestCase(unittest.TestCase): def setUp(self): self.parser = TestParser() def test_blank_line(self): lines = [""] self.parser.parse(lines) self.assertEqual(self.parser.values, {}) def test_assignment_equal(self): lines = ["foo = bar"] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': ['bar']}}) def test_assignment_colon(self): lines = ["foo: bar"] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': ['bar']}}) def test_assignment_multiline(self): lines = ["foo = bar0", " bar1"] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': ['bar0', 'bar1']}}) def test_assignment_multline_empty(self): lines = ["foo = bar0", "", " bar1"] self.assertRaises(iniparser.ParseError, self.parser.parse, lines) def test_section_assignment(self): lines = ["[test]", "foo = bar"] self.parser.parse(lines) self.assertEqual(self.parser.values, {'test': {'foo': ['bar']}}) def test_new_section(self): lines = ["[foo]"] self.parser.parse(lines) self.assertEqual(self.parser.section, 'foo') def test_comment(self): lines = ["# foobar"] self.parser.parse(lines) self.assertTrue(self.parser.comment_called) def test_empty_assignment(self): lines = ["foo = "] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': ['']}}) def test_assignment_space_single_quote(self): lines = ["foo = ' bar '"] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': [' bar ']}}) def test_assignment_space_double_quote(self): lines = ["foo = \" bar \""] self.parser.parse(lines) self.assertEqual(self.parser.values, {'': {'foo': [' bar ']}}) class ExceptionTestCase(unittest.TestCase): def test_parseerror(self): exc = iniparser.ParseError('test', 42, 'example') self.assertEqual(str(exc), "at line 42, test: 'example'") oslo.config-1.2.1/tests/utils.py0000664000175300017540000000375112221001311017731 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010-2011 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Common utilities used in testing""" import os import fixtures import testtools TRUE_VALUES = ('true', '1', 'yes') class BaseTestCase(testtools.TestCase): def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.FakeLogger('oslo.config')) test_timeout = os.environ.get('OS_TEST_TIMEOUT', 30) try: test_timeout = int(test_timeout) except ValueError: # If timeout value is invalid, fail hard. print("OS_TEST_TIMEOUT set to invalid value" " defaulting to no timeout") test_timeout = 0 if test_timeout > 0: self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) if os.environ.get('OS_STDOUT_CAPTURE') in TRUE_VALUES: stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if os.environ.get('OS_STDERR_CAPTURE') in TRUE_VALUES: stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) oslo.config-1.2.1/tests/testmods/0000775000175300017540000000000012221001336020062 5ustar jenkinsjenkins00000000000000oslo.config-1.2.1/tests/testmods/bar_foo_opt.py0000664000175300017540000000135312221001311022720 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo.config import cfg CONF = cfg.CONF CONF.register_opt(cfg.StrOpt('foo'), group='bar') oslo.config-1.2.1/tests/testmods/blaa_opt.py0000664000175300017540000000133712221001311022212 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo.config import cfg CONF = cfg.CONF CONF.register_opt(cfg.StrOpt('blaa')) oslo.config-1.2.1/tests/testmods/__init__.py0000664000175300017540000000121212221001311022160 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. oslo.config-1.2.1/tests/testmods/baz_qux_opt.py0000664000175300017540000000136412221001311022764 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2013 Intel Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo.config import cfg CONF = cfg.CONF CONF.register_opt(cfg.StrOpt('baz'), group='qux') oslo.config-1.2.1/AUTHORS0000664000175300017540000000263712221001336016136 0ustar jenkinsjenkins00000000000000Anthony Young Brian Waldon Chuck Short Dan Prince Davanum Srinivas Davanum Srinivas David Ripton Dirk Mueller Doug Hellmann Eoghan Glynn Fengqian.Gao Flaper Fesp Gary Kotton Giampaolo Lauria Ian Wienand James E. Blair Jason Kölker Joe Gordon Joe Heck Johannes Erdfelt Julien Danjou Kevin L. Mitchell Laurence Miao Luis A. Garcia Mark McLoughlin Michael Basnight Monty Taylor Rick Harris Russell Bryant Sergey Lukjanov Steven Deaton Tim Miller Vincent Untz Vishvananda Ishaya YAMAMOTO Takashi Yuriy Taraday Zhongyue Luo Zhongyue Luo lzyeval