def test_write_skip_aggregations(self): required_config = Namespace() required_config.add_option( 'minimal_version_for_understanding_refusal', doc='ignore the Thottleable protocol', default={'Firefox': '3.5.4'}, ) required_config.add_aggregation('an_agg', lambda x, y, z: 'I refuse') cm = ConfigurationManager( required_config, values_source_list=[], ) config = cm.get_config() s = StringIO() @contextlib.contextmanager def s_opener(): yield s cm.write_conf('py', s_opener) generated_python_module_text = s.getvalue() expected = """# generated Python configman file # ignore the Thottleable protocol minimal_version_for_understanding_refusal = { "Firefox": "3.5.4" } """ self.assertEqual(generated_python_module_text, expected)
def define_config(): definition = Namespace() # here we're setting up the minimal parameters required for connecting # to a database. definition.add_option( name='host', default='localhost', doc='the hostname of the database', short_form='h' ) definition.add_option( name='dbname', default='', doc='the name of the database', short_form='d' ) definition.add_option( name='user', default='', doc='the name of the user within the database', short_form='u' ) definition.add_option( name='password', default='', doc='the name of the database', short_form='p' ) # This final aggregation object is the most interesting one. Its final # value depends on the final values of options within the same Namespace. # After configman is done doing all its value overlays, there is # one final pass through the option definitions with the sole purpose of # expanding the Aggregations. To do so, the Aggregations' aggregation_fn # is called passing the whole config set to the function. That function # can then use any values within the config values to come up with its # own value. In this case, the function returns a factory function that # return functions that return database connections wrapped in # contextmanagers. definition.add_aggregation( name='db_transaction', function=transaction_context_factory ) return definition
def test_write_skip_aggregations(self): required_config = Namespace() required_config.add_option( 'minimal_version_for_understanding_refusal', doc='ignore the Thottleable protocol', default={'Firefox': '3.5.4'}, ) required_config.add_aggregation( 'an_agg', lambda x, y, z: 'I refuse' ) cm = ConfigurationManager( required_config, values_source_list=[], ) config = cm.get_config() s = StringIO() @contextlib.contextmanager def s_opener(): yield s cm.write_conf('py', s_opener) generated_python_module_text = s.getvalue() expected = """# generated Python configman file # ignore the Thottleable protocol minimal_version_for_understanding_refusal = { "Firefox": "3.5.4" } """ self.assertEqual(generated_python_module_text, expected)
def _do_main( initial_app, values_source_list=None, config_path=None, config_manager_cls=ConfigurationManager ): if values_source_list is None: values_source_list = [ ConfigFileFutureProxy, environment, command_line ] global restart restart = False if isinstance(initial_app, basestring): initial_app = class_converter(initial_app) if config_path is None: default = './config' config_path = os.environ.get( 'DEFAULT_SOCORRO_CONFIG_PATH', default ) if config_path != default: # you tried to set it, then it must be a valid directory if not os.path.isdir(config_path): raise IOError('%s is not a valid directory' % config_path) # the only config parameter is a special one that refers to a class or # module that defines an application. In order to qualify, a class must # have a constructor that accepts a DotDict derivative as the sole # input parameter. It must also have a 'main' function that accepts no # parameters. For a module to be acceptable, it must have a main # function that accepts a DotDict derivative as its input parameter. app_definition = Namespace() app_definition.add_option( 'application', doc='the fully qualified module or class of the application', default=initial_app, from_string_converter=class_converter ) try: app_name = initial_app.app_name # this will be used as the default # b app_version = initial_app.app_version app_description = initial_app.app_description except AttributeError as x: raise AppDetailMissingError(x) app_definition.add_aggregation( 'logger', functools.partial(setup_logger, app_name) ) definitions = ( app_definition, logging_required_config(app_name) ) config_manager = config_manager_cls( definitions, app_name=app_name, app_version=app_version, app_description=app_description, values_source_list=values_source_list, config_pathname=config_path ) def fix_exit_code(code): # some apps don't return a code so you might get None # which isn't good enough to send to sys.exit() if code is None: return 0 return code with config_manager.context() as config: #config.logger.config = config config.executor_identity = lambda: threading.currentThread().getName() config_manager.log_config(config.logger) # install the signal handler for SIGHUP to be the action defined in # 'respond_to_SIGHUP' respond_to_SIGHUP_with_logging = functools.partial( respond_to_SIGHUP, logger=config.logger ) signal.signal(signal.SIGHUP, respond_to_SIGHUP_with_logging) # get the app class from configman. Why bother since we have it aleady # with the 'initial_app' name? In most cases initial_app == app, # it might not always be that way. The user always has the ability # to specify on the command line a new app class that will override # 'initial_app'. app = config.application if isinstance(app, type): # invocation of the app if the app_object was a class instance = app(config) instance.config_manager = config_manager return_code = fix_exit_code(instance.main()) elif inspect.ismodule(app): # invocation of the app if the app_object was a module return_code = fix_exit_code(app.main(config)) elif inspect.isfunction(app): # invocation of the app if the app_object was a function return_code = fix_exit_code(app(config)) config.logger.info('done.') return return_code raise NotImplementedError("The app did not have a callable main function")
if __name__ == "__main__": definition_source = Namespace() # setup the option that will specify which database connection/transaction # factory will be used. Condfig man will query the class for additional # config options for the database connection parameters. definition_source.add_option('database', default=PGTransaction, doc='the database connection source', short_form='d') # this Aggregation will actually instatiate the class in the preceding # option called 'database'. Once instantiated, it will be available as # 'db_transaction'. It will then be used as a source of database # connections cloaked as a context. definition_source.add_aggregation(name='db_transaction', function=transaction_factory) c = ConfigurationManager(definition_source, app_name='demo4', app_description=__doc__) with c.context() as config: print "\n**** First query will succeed" with config.db_transaction() as trans: trans.query('select * from life') trans.commit() print "\n**** Second query will fail" with config.db_transaction() as trans: trans.query('select * from life') print "\n**** about to leave the config context"
def _do_main(initial_app, values_source_list=None, config_path=None, config_manager_cls=ConfigurationManager): if values_source_list is None: values_source_list = [ConfigFileFutureProxy, environment, command_line] global restart restart = False if isinstance(initial_app, basestring): initial_app = class_converter(initial_app) if config_path is None: default = './config' config_path = os.environ.get('DEFAULT_SOCORRO_CONFIG_PATH', default) if config_path != default: # you tried to set it, then it must be a valid directory if not os.path.isdir(config_path): raise IOError('%s is not a valid directory' % config_path) # the only config parameter is a special one that refers to a class or # module that defines an application. In order to qualify, a class must # have a constructor that accepts a DotDict derivative as the sole # input parameter. It must also have a 'main' function that accepts no # parameters. For a module to be acceptable, it must have a main # function that accepts a DotDict derivative as its input parameter. app_definition = Namespace() app_definition.add_option( 'application', doc='the fully qualified module or class of the application', default=initial_app, from_string_converter=class_converter) try: app_name = initial_app.app_name # this will be used as the default # b app_version = initial_app.app_version app_description = initial_app.app_description except AttributeError as x: raise AppDetailMissingError(x) app_definition.add_aggregation('logger', functools.partial(setup_logger, app_name)) definitions = (app_definition, logging_required_config(app_name)) config_manager = config_manager_cls(definitions, app_name=app_name, app_version=app_version, app_description=app_description, values_source_list=values_source_list, config_pathname=config_path) def fix_exit_code(code): # some apps don't return a code so you might get None # which isn't good enough to send to sys.exit() if code is None: return 0 return code with config_manager.context() as config: #config.logger.config = config config.executor_identity = lambda: threading.currentThread().getName() config_manager.log_config(config.logger) # install the signal handler for SIGHUP to be the action defined in # 'respond_to_SIGHUP' respond_to_SIGHUP_with_logging = functools.partial( respond_to_SIGHUP, logger=config.logger) signal.signal(signal.SIGHUP, respond_to_SIGHUP_with_logging) # get the app class from configman. Why bother since we have it aleady # with the 'initial_app' name? In most cases initial_app == app, # it might not always be that way. The user always has the ability # to specify on the command line a new app class that will override # 'initial_app'. app = config.application if isinstance(app, type): # invocation of the app if the app_object was a class instance = app(config) instance.config_manager = config_manager return_code = fix_exit_code(instance.main()) elif inspect.ismodule(app): # invocation of the app if the app_object was a module return_code = fix_exit_code(app.main(config)) elif inspect.isfunction(app): # invocation of the app if the app_object was a function return_code = fix_exit_code(app(config)) config.logger.info('done.') return return_code raise NotImplementedError("The app did not have a callable main function")
required_config.add_option( 'weather_underground_api_key', doc='the api key to access Weather Underground data', short_form="K", default="WEATHER UNDERGROUND ACCESS KEY") required_config.add_option( 'state_code', doc='the two letter state code', default="OR", ) required_config.add_option( 'city_name', doc='the name of the city', default="Waldport", ) required_config.add_aggregation('target_url', function=create_url) required_config.add_option( 'seconds_for_timeout', doc='the number of seconds to allow for fetching weather data', default=10) required_config.add_option( 'things_gateway_auth_key', doc='the api key to access the Things Gateway', short_form="G", default='THINGS GATEWAY AUTH KEY', ) required_config.add_option('thing_id', doc='the id of the color bulb to control', default="TIDE LIGHT THING ID") required_config.update(logging_config)
if __name__ == "__main__": definition_source = Namespace() # setup the option that will specify which database connection/transaction # factory will be used. Condfig man will query the class for additional # config options for the database connection parameters. definition_source.add_option('database', default=PGTransaction, doc='the database connection source', short_form='d') # this Aggregation will actually instatiate the class in the preceding # option called 'database'. Once instantiated, it will be available as # 'db_transaction'. It will then be used as a source of database # connections cloaked as a context. definition_source.add_aggregation( name='db_transaction', function=transaction_factory ) c = ConfigurationManager(definition_source, app_name='demo4', app_description=__doc__) with c.context() as config: print "\n**** First query will succeed" with config.db_transaction() as trans: trans.query('select * from life') trans.commit() print "\n**** Second query will fail" with config.db_transaction() as trans: trans.query('select * from life')
) tide_light_config.add_option('thing_id', doc='the id of the color bulb to control', default="TIDE LIGHT THING ID") # this defines a constant based solely on configuration data def create_url(config, local_namespace, args): """generate a URL to fetch local weather data from Weather Underground using configuration data""" return "http://api.wunderground.com/api/{}/tide/q/{}/{}.json".format( local_namespace.weather_underground_api_key, local_namespace.state_code, local_namespace.city_name) tide_light_config.add_aggregation('target_url', function=create_url) # these are the common configuration parameters that will be used for all tide # lights. base_required_config = Namespace() base_required_config.add_option( 'weather_underground_api_key', doc='the api key to access Weather Underground data', short_form="K", default="WEATHER UNDERGROUND ACCESS KEY") base_required_config.add_option( 'things_gateway_auth_key', doc='the api key to access the Things Gateway', short_form="G", default='THINGS GATEWAY AUTH KEY', )