Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proper config module #128

Closed
wants to merge 13 commits into from
Closed

Conversation

Holzhaus
Copy link
Member

Rationale

Jasper really needs a proper config module.

I saw multiple errors caused by modules/classes/functions trying to access the yaml-configuration looking for values that were not set (e.g. gmail_address). This means that we'll end up writing checking functions all over the place or using a general try: ... except: ...-block wrapped around Conversation.handleForever() (which currently prevents us from killing Jasper with CRTL-C by the way).

So the obvious solution is to create a new module (named jasperconf in this case), that does this kind of stuff for us. It offers us a config singleton, which relieves us from the obligation to pass around the profile-variable or open the config file every time we need a value.

It also adds proper separation between main and plugin namespace in the config file (all plugins access the config via plugin_get/plugin_set methods, which retrieves and sets value in pluginsplugin_name → x.
If that namespace should ever be changed - no problem, just change the jasperconf module and probably add a conversion for old config files.

Last (and probably least), it also decouples the access to config values from the actual backend (YAML) and thus makes a transition - if neccessary - (e.g. to ConfigParser) a lot easier.

Usage

Main program

from jasperconf import Config as conf
firstname = conf.get("firstname")
# Alternative:
# firstname = conf.get(["firstname"])
conf.set(["keys","GOOGLE_SPEECH"], value="ABCDEF" )
google_api_key = conf.get(["keys", "GOOGLE_SPEECH"])

Plugins (in client/modules)

PLUGIN_NAME = "foo"

from ..jasperconf import Config as conf
bar = conf.plugin_get(PLUGIN_NAME, "bar")
# Alternative:
# bar = conf.plugin_get(PLUGIN_NAME, ["bar"])
conf.plugin_set(PLUGIN_NAME, ['example','baz'], value="Doh!")
doh = conf.plugin_get(PLUGIN_NAME, ['example','baz'])

@Holzhaus
Copy link
Member Author

Just wanted to add that I rewrote 'populate.py' to use the new module and tidied it up while I was at it.
It now also support output to a different file (specified via command line argument --file) and editing an config profile file. To start fresh, delete the existing config file or use --new.

It did not change any other modules because I'm not sure this get's accepted because of the ConfigParser pull request, in which you stated that you did not want to break compatability with third party plugins (if I recall that correctly). Although this is the case here, too, I hope you see the advantages of this approach.

So if you're willing to accept this PR, I will update all other modules to use jasperconf.

@Holzhaus
Copy link
Member Author

So....What do you think?

@charliermarsh
Copy link

I like the idea of moving away from passing profile everywhere... Do you have an example of a module that currently needs the plugin_get/plugin_set functionality?

@Holzhaus
Copy link
Member Author

Well, any plugin module that needs its own config value (Gmail.py for example).
The idea is to stop pollution of the central namespace, because every module has it's own. This would of course be archievable by the normal get/set methods, too, but this adds an addition layer of abstraction for plugins.
The main advantages are:

  • It's easier for the plugin developers (no need to think about the absolute namespace path, just input the plugin name and go)
  • we could move the "plugins" namespace later on (to another location inside the config file, to a separate plugins.conf file or even to separate files/folders for every plugin) without changing the plugin modules at all
  • Perhaps we could add code to restrict plugins from chaning the main namespace at all (actually I don't know if that's possible...probably via the traceback module or something like that)

@charliermarsh
Copy link

Yeah, that makes sense. (Although in the Gmail.py example: you actually do need the Gmail address elsewhere in some cases (e.g., if you want Jasper to email you a list of links to news articles); but in general, the principle of polluting the global namespace does need to end.)

I'm fine with merging this in after code review and testing, but the docs would need some major updates. (At this point, it also makes sense to start discussing some sort of Jasper v2 (CC @shbhrsaha). We can continue discussion offline.)


class AbstractConfig(object):
"""
Base class which acts as an interface for custom Config classes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove these indentations in the docstrings? Don't think we do that elsewhere.

@charliermarsh
Copy link

Pending these changes, I don't think we're too far off here (although the modules need to be modified, of course).

@Holzhaus Holzhaus added enhancement and removed v2.0 labels Sep 11, 2014
@Holzhaus Holzhaus modified the milestone: v2.0 Sep 11, 2014
plugin_path = self.sanitize_path(path)
path = ["plugins", plugin]
path.extend(plugin_path)
return self.set(path, value=value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic to create the plugin path is the same in plugin_get and plugin_set, can this be abstracted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess.
I'm not completely sure if they'll useful anyway (probably they should be left out and then made methods of the plugin base class (config_set() and config_get).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the plugin's config_get should call the config's plugin_get, same thing for set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could just call config.set(['plugins', self.SLUG, 'foo'], value='bar')?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... but I like the idea of just saying config_set('foo', 'bar'). Simple, less duplication, less errors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, my comment referred to the inner workings of a method that belongs to the base plugin class (i.e. the class that all Plugins inherit from). Therefore, every Plugin will be able to do self.config_set(foo, bar) by default.

@Holzhaus
Copy link
Member Author

I'd like to suggest that I remove the plugin_set/plugin_get method and add a to_dict() method for now, so that we can merge this PR before we reach v2.0.

This way, we can simplify the core jasper code and use it right now. Modules get the dict generated by the to_dict() method as profile argument (which will look just like the dict generated by yaml.safe_load()), so there'll be no changes neccessary in client modules.

@Holzhaus
Copy link
Member Author

What do you think?

@alexsiri7
Copy link
Contributor

I think it's a good idea to allow v1 modules to be compatible. This sounds quite reasonable. We should also check whether the method requires the parameter, and if not then not send it (that will allow modules to stop receiving the parameter and start using the new interface).

@Holzhaus
Copy link
Member Author

I removed plugin_get/plugin_set, added the to_dict() method, added logging and rebased to the latest master version.

@Holzhaus
Copy link
Member Author

Going to start integration after merge of #199, #176 and #155 to avoid ending up with a lot of merge conflicts.

@Holzhaus Holzhaus removed this from the v2.0 milestone Sep 25, 2014
@Holzhaus Holzhaus mentioned this pull request Oct 1, 2014
@coveralls
Copy link

Coverage Status

Coverage remained the same when pulling 9ff4d39 on Holzhaus:proper-config into 3ad1cee on jasperproject:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same when pulling 3245550 on Holzhaus:proper-config into 56f65ff on jasperproject:master.

@Holzhaus Holzhaus closed this Nov 27, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants