Mercurial > p > roundup > code
comparison roundup/configuration.py @ 2646:fd7b2fc1eb28
Config is now base class for all configurations...
...main Roundup configuration class is CoreConfig.
Builtin TRACKER_HOME renamed to HOME in Config and Options.
In CoreConfig, TRACKER_HOME is an alias for HOME.
Option.value2str(): added parameter 'current' -
stringify current Option value.
Option RDBMS_PORT defaults to None.
Config: added methods add_section, update_option, getopt (not implemented).
Config.save(): added warning about unset options at the top of the file.
new class UserConfig for inifile-driven configs.
CoreConfig: new attributes .ext and .detectors holding UserConfigs
for extensions and detectors, respectively.
| author | Alexander Smishlajev <a1s@users.sourceforge.net> |
|---|---|
| date | Tue, 27 Jul 2004 11:26:20 +0000 |
| parents | 11811b313459 |
| children | 1df7d4a41da4 |
comparison
equal
deleted
inserted
replaced
| 2645:8250c63c3963 | 2646:fd7b2fc1eb28 |
|---|---|
| 1 # Roundup Issue Tracker configuration support | 1 # Roundup Issue Tracker configuration support |
| 2 # | 2 # |
| 3 # $Id: configuration.py,v 1.13 2004-07-27 01:59:28 richard Exp $ | 3 # $Id: configuration.py,v 1.14 2004-07-27 11:26:20 a1s Exp $ |
| 4 # | 4 # |
| 5 __docformat__ = "restructuredtext" | 5 __docformat__ = "restructuredtext" |
| 6 | 6 |
| 7 import imp | 7 import imp |
| 8 import os | 8 import os |
| 21 | 21 |
| 22 class NoConfigError(ConfigurationError): | 22 class NoConfigError(ConfigurationError): |
| 23 | 23 |
| 24 """Raised when configuration loading fails | 24 """Raised when configuration loading fails |
| 25 | 25 |
| 26 Constructor parameters: path to the directory that was used as TRACKER_HOME | 26 Constructor parameters: path to the directory that was used as HOME |
| 27 | 27 |
| 28 """ | 28 """ |
| 29 | 29 |
| 30 def __str__(self): | 30 def __str__(self): |
| 31 return "No valid configuration files found in directory %s" \ | 31 return "No valid configuration files found in directory %s" \ |
| 156 override this method, not the public .value2str(). | 156 override this method, not the public .value2str(). |
| 157 | 157 |
| 158 """ | 158 """ |
| 159 return str(value) | 159 return str(value) |
| 160 | 160 |
| 161 def value2str(self, value): | 161 def value2str(self, value=NODEFAULT, current=0): |
| 162 """Return 'value' argument converted to external representation""" | 162 """Return 'value' argument converted to external representation |
| 163 | |
| 164 If 'current' is True, use current option value. | |
| 165 | |
| 166 """ | |
| 167 if current: | |
| 168 value = self._value | |
| 163 if value is NODEFAULT: | 169 if value is NODEFAULT: |
| 164 return str(value) | 170 return str(value) |
| 165 else: | 171 else: |
| 166 return self._value2str(value) | 172 return self._value2str(value) |
| 167 | 173 |
| 290 | 296 |
| 291 class FilePathOption(Option): | 297 class FilePathOption(Option): |
| 292 | 298 |
| 293 """File or directory path name | 299 """File or directory path name |
| 294 | 300 |
| 295 Paths may be either absolute or relative to the TRACKER_HOME. | 301 Paths may be either absolute or relative to the HOME. |
| 296 | 302 |
| 297 """ | 303 """ |
| 298 | 304 |
| 299 class_description = "The path may be either absolute" \ | 305 class_description = "The path may be either absolute or relative\n" \ |
| 300 " or relative to the tracker home." | 306 " to the directory containig this config file." |
| 301 | 307 |
| 302 def get(self): | 308 def get(self): |
| 303 _val = Option.get(self) | 309 _val = Option.get(self) |
| 304 if _val and not os.path.isabs(_val): | 310 if _val and not os.path.isabs(_val): |
| 305 _val = os.path.join(self.config["TRACKER_HOME"], _val) | 311 _val = os.path.join(self.config["HOME"], _val) |
| 306 return _val | 312 return _val |
| 307 | 313 |
| 308 class FloatNumberOption(Option): | 314 class FloatNumberOption(Option): |
| 309 | 315 |
| 310 """Floating point numbers""" | 316 """Floating point numbers""" |
| 449 "Name of the Postgresql or MySQL database to use.", | 455 "Name of the Postgresql or MySQL database to use.", |
| 450 ['MYSQL_DBNAME']), | 456 ['MYSQL_DBNAME']), |
| 451 (NullableOption, 'host', 'localhost', | 457 (NullableOption, 'host', 'localhost', |
| 452 "Hostname that the Postgresql or MySQL database resides on.", | 458 "Hostname that the Postgresql or MySQL database resides on.", |
| 453 ['MYSQL_DBHOST']), | 459 ['MYSQL_DBHOST']), |
| 454 (NullableOption, 'port', '5432', | 460 (NullableOption, 'port', '', |
| 455 "Port number that the Postgresql or MySQL database resides on."), | 461 "Port number that the Postgresql or MySQL database resides on."), |
| 456 (NullableOption, 'user', 'roundup', | 462 (NullableOption, 'user', 'roundup', |
| 457 "Postgresql or MySQL database user that Roundup should use.", | 463 "Postgresql or MySQL database user that Roundup should use.", |
| 458 ['MYSQL_DBUSER']), | 464 ['MYSQL_DBUSER']), |
| 459 (NullableOption, 'password', 'roundup', | 465 (NullableOption, 'password', 'roundup', |
| 546 "If 'no', they're never added to the nosy.\n", | 552 "If 'no', they're never added to the nosy.\n", |
| 547 ["ADD_RECIPIENTS_TO_NOSY"]), | 553 ["ADD_RECIPIENTS_TO_NOSY"]), |
| 548 )), | 554 )), |
| 549 ) | 555 ) |
| 550 | 556 |
| 551 ### Main class | 557 ### Configuration classes |
| 552 | 558 |
| 553 class Config: | 559 class Config: |
| 554 | 560 |
| 555 """Roundup instance configuration. | 561 """Base class for configuration objects. |
| 556 | 562 |
| 557 Configuration options may be accessed as attributes or items | 563 Configuration options may be accessed as attributes or items |
| 558 of instances of this class. All option names are uppercased. | 564 of instances of this class. All option names are uppercased. |
| 559 | 565 |
| 560 """ | 566 """ |
| 561 | 567 |
| 562 # Config file names (in the TRACKER_HOME directory): | 568 # Config file name |
| 563 INI_FILE = "config.ini" # new style config file name | 569 INI_FILE = "config.ini" |
| 564 PYCONFIG = "config" # module name for old style configuration | |
| 565 | 570 |
| 566 # Object attributes that should not be taken as common configuration | 571 # Object attributes that should not be taken as common configuration |
| 567 # options in __setattr__ (most of them are initialized in constructor): | 572 # options in __setattr__ (most of them are initialized in constructor): |
| 568 # builtin pseudo-option - tracker home directory | 573 # builtin pseudo-option - package home directory |
| 569 TRACKER_HOME = "." | 574 HOME = "." |
| 570 # names of .ini file sections, in order | 575 # names of .ini file sections, in order |
| 571 sections = None | 576 sections = None |
| 577 # section comments | |
| 578 section_descriptions = None | |
| 572 # lists of option names for each section, in order | 579 # lists of option names for each section, in order |
| 573 section_options = None | 580 section_options = None |
| 574 # mapping from option names and aliases to Option instances | 581 # mapping from option names and aliases to Option instances |
| 575 options = None | 582 options = None |
| 576 # logging engine | 583 |
| 577 logging = rlog.BasicLogging() | 584 def __init__(self, home_dir=None, layout=None): |
| 578 | 585 """Initialize confing instance |
| 579 def __init__(self, tracker_home=None): | 586 |
| 587 Parameters: | |
| 588 home_dir: | |
| 589 optional configuration directory. | |
| 590 If passed, load the config from that directory | |
| 591 after processing config layout (if any). | |
| 592 layout: | |
| 593 optional configuration layout, a sequence of | |
| 594 section definitions suitable for .add_section() | |
| 595 | |
| 596 """ | |
| 580 # initialize option containers: | 597 # initialize option containers: |
| 581 self.sections = [] | 598 self.sections = [] |
| 599 self.section_descriptions = {} | |
| 582 self.section_options = {} | 600 self.section_options = {} |
| 583 self.options = {} | 601 self.options = {} |
| 584 # add options from the SETTINGS structure | 602 # add options from the layout structure |
| 585 for (_section, _options) in SETTINGS: | 603 if layout: |
| 586 for _option_def in _options: | 604 for section in layout: |
| 587 _class = _option_def[0] | 605 self.add_section(*section) |
| 588 _args = _option_def[1:] | 606 if home_dir is not None: |
| 589 _option = _class(self, _section, *_args) | 607 self.load(home_dir) |
| 590 self.add_option(_option) | 608 |
| 591 # load the config if tracker_home given | 609 def add_section(self, section, options, description=None): |
| 592 if tracker_home is None: | 610 """Define new config section |
| 593 self.init_logging() | 611 |
| 594 else: | 612 Parameters: |
| 595 self.load(tracker_home) | 613 section - name of the config.ini section |
| 614 options - a sequence of Option definitions. | |
| 615 Each Option definition is a sequence | |
| 616 containing class object and constructor | |
| 617 parameters, starting from the setting name: | |
| 618 setting, default, [description, [aliases]] | |
| 619 description - optional section comment | |
| 620 | |
| 621 Note: aliases should only exist in historical options | |
| 622 for backwards compatibility - new options should | |
| 623 *not* have aliases! | |
| 624 | |
| 625 """ | |
| 626 if description or not self.section_descriptions.has_key(section): | |
| 627 self.section_descriptions[section] = description | |
| 628 for option_def in options: | |
| 629 klass = option_def[0] | |
| 630 args = option_def[1:] | |
| 631 option = klass(self, section, *args) | |
| 632 self.add_option(option) | |
| 596 | 633 |
| 597 def add_option(self, option): | 634 def add_option(self, option): |
| 598 """Adopt a new Option object""" | 635 """Adopt a new Option object""" |
| 599 _section = option.section | 636 _section = option.section |
| 600 _name = option.setting | 637 _name = option.setting |
| 607 self.options[(_section, _name)] = option | 644 self.options[(_section, _name)] = option |
| 608 # make the option known under all of it's A.K.A.s | 645 # make the option known under all of it's A.K.A.s |
| 609 for _name in option.aliases: | 646 for _name in option.aliases: |
| 610 self.options[_name] = option | 647 self.options[_name] = option |
| 611 | 648 |
| 649 def update_option(self, name, klass, | |
| 650 default=NODEFAULT, description=None | |
| 651 ): | |
| 652 """Override behaviour of early created option. | |
| 653 | |
| 654 Parameters: | |
| 655 name: | |
| 656 option name | |
| 657 klass: | |
| 658 one of the Option classes | |
| 659 default: | |
| 660 optional default value for the option | |
| 661 description: | |
| 662 optional new description for the option | |
| 663 | |
| 664 Conversion from current option value to new class value | |
| 665 is done via string representation. | |
| 666 | |
| 667 This method may be used to attach some brains | |
| 668 to options autocreated by UserConfig. | |
| 669 | |
| 670 """ | |
| 671 # fetch current option | |
| 672 option = self._get_option(name) | |
| 673 # compute constructor parameters | |
| 674 if default is NODEFAULT: | |
| 675 default = option.default | |
| 676 if description is None: | |
| 677 description = option.description | |
| 678 value = option.value2str(current=1) | |
| 679 # resurrect the option | |
| 680 option = klass(self, option.section, option.setting, | |
| 681 default=default, description=description) | |
| 682 # apply the value | |
| 683 option.set(value) | |
| 684 # incorporate new option | |
| 685 del self[name] | |
| 686 self.add_option(option) | |
| 687 | |
| 612 def reset(self): | 688 def reset(self): |
| 613 """Set all options to their default values""" | 689 """Set all options to their default values""" |
| 614 for _option in self.items(): | 690 for _option in self.items(): |
| 615 _option.reset() | 691 _option.reset() |
| 692 | |
| 693 # This is a placeholder. TBD. | |
| 694 # Meant for commandline tools. | |
| 695 # Allows automatic creation of configuration files like this: | |
| 696 # roundup-server -p 8017 -u roundup --save-config | |
| 697 def getopt(self, args, **options): | |
| 698 """Apply options specified in command line arguments. | |
| 699 | |
| 700 Parameters: | |
| 701 args: | |
| 702 command line to parse (sys.argv[1:]) | |
| 703 options: | |
| 704 mapping from option names to command line option specs. | |
| 705 e.g. server_port="p:", server_user="u:" | |
| 706 Names are forced to lower case for commandline parsing | |
| 707 (long options) and to upper case to find config options. | |
| 708 Command line options accepting no value are assumed | |
| 709 to be binary and receive value 'yes'. | |
| 710 | |
| 711 Return value: same as for python standard getopt(), except that | |
| 712 processed options are removed from returned option list. | |
| 713 | |
| 714 """ | |
| 715 | |
| 716 # option and section locators (used in option access methods) | |
| 717 | |
| 718 def _get_option(self, name): | |
| 719 try: | |
| 720 return self.options[name] | |
| 721 except KeyError: | |
| 722 raise InvalidOptionError(name) | |
| 723 | |
| 724 def _get_section_options(self, name): | |
| 725 return self.section_options.setdefault(name, []) | |
| 726 | |
| 727 def _get_unset_options(self): | |
| 728 """Return options that need manual adjustments | |
| 729 | |
| 730 Return value is a dictionary where keys are section | |
| 731 names and values are lists of option names as they | |
| 732 appear in the config file. | |
| 733 | |
| 734 """ | |
| 735 need_set = {} | |
| 736 for option in self.items(): | |
| 737 if not option.isset(): | |
| 738 need_set.setdefault(option.section, []).append(option.setting) | |
| 739 return need_set | |
| 740 | |
| 741 # file operations | |
| 742 | |
| 743 def load_ini(self, home_dir, defaults=None): | |
| 744 """Set options from config.ini file in given home_dir | |
| 745 | |
| 746 Parameters: | |
| 747 home_dir: | |
| 748 config home directory | |
| 749 defaults: | |
| 750 optional dictionary of defaults for ConfigParser | |
| 751 | |
| 752 Note: if home_dir does not contain config.ini file, | |
| 753 no error is raised. Config will be reset to defaults. | |
| 754 | |
| 755 """ | |
| 756 # parse the file | |
| 757 config_defaults = {"HOME": home_dir} | |
| 758 if defaults: | |
| 759 config_defaults.update(defaults) | |
| 760 _config = ConfigParser.ConfigParser(defaults) | |
| 761 _config.read([os.path.join(home_dir, self.INI_FILE)]) | |
| 762 # .ini file loaded ok. set the options, starting from HOME | |
| 763 self.reset() | |
| 764 self.HOME = home_dir | |
| 765 for _option in self.items(): | |
| 766 _option.load_ini(_config) | |
| 767 | |
| 768 def load(self, home_dir): | |
| 769 """Load configuration settings from home_dir""" | |
| 770 self.load_ini(home_dir) | |
| 771 | |
| 772 def save(self, ini_file=None): | |
| 773 """Write current configuration to .ini file | |
| 774 | |
| 775 'ini_file' argument, if passed, must be valid full path | |
| 776 to the file to write. If omitted, default file in current | |
| 777 HOME is created. | |
| 778 | |
| 779 If the file to write already exists, it is saved with '.bak' | |
| 780 extension. | |
| 781 | |
| 782 """ | |
| 783 if ini_file is None: | |
| 784 ini_file = os.path.join(self.HOME, self.INI_FILE) | |
| 785 _tmp_file = os.path.splitext(ini_file)[0] | |
| 786 _bak_file = _tmp_file + ".bak" | |
| 787 _tmp_file = _tmp_file + ".tmp" | |
| 788 _fp = file(_tmp_file, "wt") | |
| 789 _fp.write("# %s configuration file\n" % self["TRACKER_NAME"]) | |
| 790 _fp.write("# Autogenerated at %s\n" % time.asctime()) | |
| 791 need_set = self._get_unset_options() | |
| 792 if need_set: | |
| 793 _fp.write("\n# WARNING! Following options need adjustments:\n") | |
| 794 for section, options in need_set.items(): | |
| 795 _fp.write("# [%s]: %s\n" % (section, ", ".join(options))) | |
| 796 for _section in self.sections: | |
| 797 _fp.write("\n[%s]\n" % _section) | |
| 798 for _option in self._get_section_options(_section): | |
| 799 _fp.write("\n" + self.options[(_section, _option)].format()) | |
| 800 _fp.close() | |
| 801 if os.access(ini_file, os.F_OK): | |
| 802 if os.access(_bak_file, os.F_OK): | |
| 803 os.remove(_bak_file) | |
| 804 os.rename(ini_file, _bak_file) | |
| 805 os.rename(_tmp_file, ini_file) | |
| 806 | |
| 807 # container emulation | |
| 808 | |
| 809 def __len__(self): | |
| 810 return len(self.items()) | |
| 811 | |
| 812 def __getitem__(self, name): | |
| 813 if name == "HOME": | |
| 814 return self.HOME | |
| 815 else: | |
| 816 return self._get_option(name).get() | |
| 817 | |
| 818 def __setitem__(self, name, value): | |
| 819 if name == "HOME": | |
| 820 self.HOME = value | |
| 821 else: | |
| 822 self._get_option(name).set(value) | |
| 823 | |
| 824 def __delitem__(self, name): | |
| 825 _option = self._get_option(name) | |
| 826 _section = _option.section | |
| 827 _name = _option.setting | |
| 828 self._get_section_options(_section).remove(_name) | |
| 829 del self.options[(_section, _name)] | |
| 830 for _alias in _option.aliases: | |
| 831 del self.options[_alias] | |
| 832 | |
| 833 def items(self): | |
| 834 """Return the list of Option objects, in .ini file order | |
| 835 | |
| 836 Note that HOME is not included in this list | |
| 837 because it is builtin pseudo-option, not a real Option | |
| 838 object loaded from or saved to .ini file. | |
| 839 | |
| 840 """ | |
| 841 return [self.options[(_section, _name)] | |
| 842 for _section in self.sections | |
| 843 for _name in self._get_section_options(_section) | |
| 844 ] | |
| 845 | |
| 846 def keys(self): | |
| 847 """Return the list of "canonical" names of the options | |
| 848 | |
| 849 Unlike .items(), this list also includes HOME | |
| 850 | |
| 851 """ | |
| 852 return ["HOME"] + [_option.name for _option in self.items()] | |
| 853 | |
| 854 # .values() is not implemented because i am not sure what should be | |
| 855 # the values returned from this method: Option instances or config values? | |
| 856 | |
| 857 # attribute emulation | |
| 858 | |
| 859 def __setattr__(self, name, value): | |
| 860 if self.__dict__.has_key(name) or hasattr(self.__class__, name): | |
| 861 self.__dict__[name] = value | |
| 862 else: | |
| 863 self._get_option(name).set(value) | |
| 864 | |
| 865 # Note: __getattr__ is not symmetric to __setattr__: | |
| 866 # self.__dict__ lookup is done before calling this method | |
| 867 def __getattr__(self, name): | |
| 868 return self[name] | |
| 869 | |
| 870 class UserConfig(Config): | |
| 871 | |
| 872 """Configuration for user extensions. | |
| 873 | |
| 874 Instances of this class have no predefined configuration layout. | |
| 875 Options are created on the fly for each setting present in the | |
| 876 config file. | |
| 877 | |
| 878 """ | |
| 879 | |
| 880 def load_ini(self, home_dir, defaults=None): | |
| 881 """Load options from config.ini file in given home_dir | |
| 882 | |
| 883 Parameters: | |
| 884 home_dir: | |
| 885 config home directory | |
| 886 defaults: | |
| 887 optional dictionary of defaults for ConfigParser | |
| 888 | |
| 889 Options are automatically created as they are read | |
| 890 from the config file. | |
| 891 | |
| 892 Note: if home_dir does not contain config.ini file, | |
| 893 no error is raised. Config will be reset to defaults. | |
| 894 | |
| 895 """ | |
| 896 # parse the file | |
| 897 config_defaults = {"HOME": home_dir} | |
| 898 if defaults: | |
| 899 config_defaults.update(defaults) | |
| 900 config = ConfigParser.ConfigParser(defaults) | |
| 901 config.read([os.path.join(home_dir, self.INI_FILE)]) | |
| 902 # .ini file loaded ok. | |
| 903 self.HOME = home_dir | |
| 904 # see what options are already defined and add missing ones | |
| 905 preset = [(option.section, option.setting) for option in self.items()] | |
| 906 for section in config.sections(): | |
| 907 for name in config.options(section): | |
| 908 if (section, name) not in preset: | |
| 909 self.add_option(Option(self, section, name)) | |
| 910 # set the options | |
| 911 self.reset() | |
| 912 for option in self.items(): | |
| 913 option.load_ini(config) | |
| 914 | |
| 915 class CoreConfig(Config): | |
| 916 | |
| 917 """Roundup instance configuration. | |
| 918 | |
| 919 Core config has a predefined layout (see the SETTINGS structure), | |
| 920 support loading of old-style pythonic configurations and hold | |
| 921 three additional attributes: | |
| 922 logging: | |
| 923 instance logging engine, from standard python logging module | |
| 924 or minimalistic logger implemented in Roundup | |
| 925 detectors: | |
| 926 user-defined configuration for detectors | |
| 927 ext: | |
| 928 user-defined configuration for extensions | |
| 929 | |
| 930 """ | |
| 931 | |
| 932 # module name for old style configuration | |
| 933 PYCONFIG = "config" | |
| 934 # logging engine | |
| 935 logging = rlog.BasicLogging() | |
| 936 # user configs | |
| 937 ext = None | |
| 938 detectors = None | |
| 939 | |
| 940 def __init__(self, home_dir=None): | |
| 941 Config.__init__(self, home_dir, SETTINGS) | |
| 942 # load the config if home_dir given | |
| 943 if home_dir is None: | |
| 944 self.init_logging() | |
| 945 | |
| 946 # TODO: remove MAIL_PASSWORD if MAIL_USER is empty | |
| 947 #def _get_unset_options(self): | |
| 948 | |
| 949 def reset(self): | |
| 950 Config.reset(self) | |
| 951 if self.ext: | |
| 952 self.ext.reset() | |
| 953 if self.detectors: | |
| 954 self.detectors.reset() | |
| 616 self.init_logging() | 955 self.init_logging() |
| 617 | 956 |
| 618 def init_logging(self): | 957 def init_logging(self): |
| 619 _file = self["LOGGING_CONFIG"] | 958 _file = self["LOGGING_CONFIG"] |
| 620 if _file and os.path.isfile(_file): | 959 if _file and os.path.isfile(_file): |
| 632 if _file: | 971 if _file: |
| 633 _logging.setFile(_file) | 972 _logging.setFile(_file) |
| 634 _logging.setLevel(self["LOGGING_LEVEL"] or "ERROR") | 973 _logging.setLevel(self["LOGGING_LEVEL"] or "ERROR") |
| 635 self.logging = _logging | 974 self.logging = _logging |
| 636 | 975 |
| 637 # option and section locators (used in option access methods) | 976 def load(self, home_dir): |
| 638 | 977 """Load configuration from path designated by home_dir argument""" |
| 639 def _get_option(self, name): | 978 if os.path.isfile(os.path.join(home_dir, self.INI_FILE)): |
| 640 try: | 979 self.load_ini(home_dir) |
| 641 return self.options[name] | 980 else: |
| 642 except KeyError: | 981 self.load_pyconfig(home_dir) |
| 643 raise InvalidOptionError(name) | |
| 644 | |
| 645 def _get_section_options(self, name): | |
| 646 return self.section_options.setdefault(name, []) | |
| 647 | |
| 648 # file operations | |
| 649 | |
| 650 def load(self, tracker_home): | |
| 651 """Load configuration from path designated by tracker_home argument""" | |
| 652 if os.path.isfile(os.path.join(tracker_home, self.INI_FILE)): | |
| 653 self.load_ini(tracker_home) | |
| 654 else: | |
| 655 self.load_pyconfig(tracker_home) | |
| 656 | |
| 657 def load_ini(self, tracker_home): | |
| 658 """Set options from config.ini file in given tracker_home directory""" | |
| 659 # parse the file | |
| 660 _config = ConfigParser.ConfigParser({"TRACKER_HOME": tracker_home}) | |
| 661 _config.read([os.path.join(tracker_home, self.INI_FILE)]) | |
| 662 # .ini file loaded ok. set the options, starting from TRACKER_HOME | |
| 663 self.reset() | |
| 664 self.TRACKER_HOME = tracker_home | |
| 665 for _option in self.items(): | |
| 666 _option.load_ini(_config) | |
| 667 self.init_logging() | 982 self.init_logging() |
| 668 | 983 self.ext = UserConfig(os.path.join(home_dir, "extensions")) |
| 669 def load_pyconfig(self, tracker_home): | 984 self.detectors = UserConfig(os.path.join(home_dir, "detectors")) |
| 670 """Set options from config.py file in given tracker_home directory""" | 985 |
| 986 def load_ini(self, home_dir, defaults=None): | |
| 987 """Set options from config.ini file in given home_dir directory""" | |
| 988 config_defaults = {"TRACKER_HOME": home_dir} | |
| 989 if defaults: | |
| 990 config_defaults.update(defaults) | |
| 991 Config.load_ini(self, home_dir, config_defaults) | |
| 992 | |
| 993 def load_pyconfig(self, home_dir): | |
| 994 """Set options from config.py file in given home_dir directory""" | |
| 671 # try to locate and import the module | 995 # try to locate and import the module |
| 672 _mod_fp = None | 996 _mod_fp = None |
| 673 try: | 997 try: |
| 674 try: | 998 try: |
| 675 _module = imp.find_module(self.PYCONFIG, [tracker_home]) | 999 _module = imp.find_module(self.PYCONFIG, [home_dir]) |
| 676 _mod_fp = _module[0] | 1000 _mod_fp = _module[0] |
| 677 _config = imp.load_module(self.PYCONFIG, *_module) | 1001 _config = imp.load_module(self.PYCONFIG, *_module) |
| 678 except ImportError: | 1002 except ImportError: |
| 679 raise NoConfigError(tracker_home) | 1003 raise NoConfigError(home_dir) |
| 680 finally: | 1004 finally: |
| 681 if _mod_fp is not None: | 1005 if _mod_fp is not None: |
| 682 _mod_fp.close() | 1006 _mod_fp.close() |
| 683 # module loaded ok. set the options, starting from TRACKER_HOME | 1007 # module loaded ok. set the options, starting from HOME |
| 684 self.reset() | 1008 self.reset() |
| 685 self.TRACKER_HOME = tracker_home | 1009 self.HOME = home_dir |
| 686 for _option in self.items(): | 1010 for _option in self.items(): |
| 687 _option.load_pyconfig(_config) | 1011 _option.load_pyconfig(_config) |
| 688 self.init_logging() | |
| 689 # backward compatibility: | 1012 # backward compatibility: |
| 690 # SMTP login parameters were specified as a tuple in old style configs | 1013 # SMTP login parameters were specified as a tuple in old style configs |
| 691 # convert them to new plain string options | 1014 # convert them to new plain string options |
| 692 _mailuser = getattr(_config, "MAILUSER", ()) | 1015 _mailuser = getattr(_config, "MAILUSER", ()) |
| 693 if len(_mailuser) > 0: | 1016 if len(_mailuser) > 0: |
| 694 self.MAIL_USERNAME = _mailuser[0] | 1017 self.MAIL_USERNAME = _mailuser[0] |
| 695 if len(_mailuser) > 1: | 1018 if len(_mailuser) > 1: |
| 696 self.MAIL_PASSWORD = _mailuser[1] | 1019 self.MAIL_PASSWORD = _mailuser[1] |
| 697 | 1020 |
| 698 def save(self, ini_file=None): | 1021 # in this config, HOME is also known as TRACKER_HOME |
| 699 """Write current configuration to .ini file | |
| 700 | |
| 701 'ini_file' argument, if passed, must be valid full path | |
| 702 to the file to write. If omitted, default file in current | |
| 703 TRACKER_HOME is created. | |
| 704 | |
| 705 If the file to write already exists, it is saved with '.bak' | |
| 706 extension. | |
| 707 | |
| 708 """ | |
| 709 if ini_file is None: | |
| 710 ini_file = os.path.join(self.TRACKER_HOME, self.INI_FILE) | |
| 711 _tmp_file = os.path.splitext(ini_file)[0] | |
| 712 _bak_file = _tmp_file + ".bak" | |
| 713 _tmp_file = _tmp_file + ".tmp" | |
| 714 _fp = file(_tmp_file, "wt") | |
| 715 _fp.write("# %s configuration file\n" % self["TRACKER_NAME"]) | |
| 716 _fp.write("# Autogenerated at %s\n" % time.asctime()) | |
| 717 for _section in self.sections: | |
| 718 _fp.write("\n[%s]\n" % _section) | |
| 719 for _option in self._get_section_options(_section): | |
| 720 _fp.write("\n" + self.options[(_section, _option)].format()) | |
| 721 _fp.close() | |
| 722 if os.access(ini_file, os.F_OK): | |
| 723 if os.access(_bak_file, os.F_OK): | |
| 724 os.remove(_bak_file) | |
| 725 os.rename(ini_file, _bak_file) | |
| 726 os.rename(_tmp_file, ini_file) | |
| 727 | |
| 728 # container emulation | |
| 729 | |
| 730 def __len__(self): | |
| 731 return len(self.items()) | |
| 732 | |
| 733 def __getitem__(self, name): | 1022 def __getitem__(self, name): |
| 734 if name == "TRACKER_HOME": | 1023 if name == "TRACKER_HOME": |
| 735 return self.TRACKER_HOME | 1024 return self.HOME |
| 736 else: | 1025 else: |
| 737 return self._get_option(name).get() | 1026 return Config.__getitem__(self, name) |
| 738 | 1027 |
| 739 def __setitem__(self, name, value): | 1028 def __setitem__(self, name, value): |
| 740 if name == "TRACKER_HOME": | 1029 if name == "TRACKER_HOME": |
| 741 self.TRACKER_HOME = value | 1030 self.HOME = value |
| 742 else: | 1031 else: |
| 743 self._get_option(name).set(value) | 1032 self._get_option(name).set(value) |
| 744 | 1033 |
| 745 def __delitem__(self, name): | |
| 746 _option = self._get_option(name) | |
| 747 _section = _option.section | |
| 748 _name = _option.setting | |
| 749 self._get_section_options(_section).remove(_name) | |
| 750 del self.options[(_section, _name)] | |
| 751 for _alias in _option.aliases: | |
| 752 del self.options[_alias] | |
| 753 | |
| 754 def items(self): | |
| 755 """Return the list of Option objects, in .ini file order | |
| 756 | |
| 757 Note that TRACKER_HOME is not included in this list | |
| 758 because it is builtin pseudo-option, not a real Option | |
| 759 object loaded from or saved to .ini file. | |
| 760 | |
| 761 """ | |
| 762 return [self.options[(_section, _name)] | |
| 763 for _section in self.sections | |
| 764 for _name in self._get_section_options(_section) | |
| 765 ] | |
| 766 | |
| 767 def keys(self): | |
| 768 """Return the list of "canonical" names of the options | |
| 769 | |
| 770 Unlike .items(), this list also includes TRACKER_HOME | |
| 771 | |
| 772 """ | |
| 773 return ["TRACKER_HOME"] + [_option.name for _option in self.items()] | |
| 774 | |
| 775 # .values() is not implemented because i am not sure what should be | |
| 776 # the values returned from this method: Option instances or config values? | |
| 777 | |
| 778 # attribute emulation | |
| 779 | |
| 780 def __setattr__(self, name, value): | 1034 def __setattr__(self, name, value): |
| 781 if self.__dict__.has_key(name) \ | 1035 if name == "TRACKER_HOME": |
| 782 or self.__class__.__dict__.has_key(name): | 1036 self.__dict__["HOME"] = value |
| 783 self.__dict__[name] = value | 1037 else: |
| 784 else: | 1038 Config.__setattr__(self, name, value) |
| 785 self._get_option(name).set(value) | |
| 786 | |
| 787 # Note: __getattr__ is not symmetric to __setattr__: | |
| 788 # self.__dict__ lookup is done before calling this method | |
| 789 __getattr__ = __getitem__ | |
| 790 | 1039 |
| 791 # vim: set et sts=4 sw=4 : | 1040 # vim: set et sts=4 sw=4 : |
