Repository: getpelican/pelican

Issue:
Using `execfile` for settings is non-ideal
`execfile` does not set nice things like `__file__` when executing a module. This can lead to tracebacks like:

```
Traceback (most recent call last):
  File "/Users/Julian/.virtualenvs/julian/bin/pelican", line 3, in <module>
    main()
  File "/Users/Julian/.virtualenvs/julian/lib/python2.7/site-packages/pelican/__init__.py", line 127, in main
    settings = read_settings(args.settings)
  File "/Users/Julian/.virtualenvs/julian/lib/python2.7/site-packages/pelican/settings.py", line 55, in read_settings
    execfile(filename, tempdict)
  File "settings.py", line 10, in <module>
    THEME = os.path.abspath(os.path.dirname(__file__))
NameError: name '__file__' is not defined
```

I'm not a huge fan of using Python as a configuration format, but if it's going to be used, it'd be nice to have the settings module imported instead of `execfile`d. All the globals in the settings file is going to mean that to do that `import *` is going to be necessary, so I'd love to see a `Settings` object (or a simple `settings = some_dict`) instead, but I'll take what I can get :).

Of course, if these are things that you're open to, I'd be willing to contribute patches.

Cheers.



Discussion:
This opens some issues:

First, it would break compatibility, and I think that's a bad news for the users.
Second, I don't get what this brings for the users? What we could do is to import the module and use the module as the settings. I'm not sure how we should do that in python, but importlib is probably an option to look at.

If you're willing to work on that, then please do so, but keep in mind that we don't want any compat. breakage :)

Well, as for compatibility, other than the last thing I mentioned, you'd have full backwards compatibility, just by switching from `execfile` to an `import *`. You wouldn't need `importlib` really I guess. As for what the benefit is, the benefit is that as a user I expect things like `__file__` to work whenever I'm writing a module :) -- I encountered that traceback as I was writing my own personal `settings.py`.

I'll throw something together to give you a more concrete idea.

Cheers.


Resolving patch:
diff --git a/pelican/settings.py b/pelican/settings.py
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -1,4 +1,6 @@
 # -*- coding: utf-8 -*-
+import imp
+import inspect
 import os
 import locale
 import logging
@@ -84,21 +86,31 @@ def read_settings(filename=None):
     return configured_settings
 
 
-def get_settings_from_file(filename, default_settings=None):
-    """Load a Python file into a dictionary.
+def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
     """
-    if default_settings == None:
-        default_settings = _DEFAULT_CONFIG
+    Load settings from a module, returning a dict.
+
+    """
+
     context = default_settings.copy()
-    if filename:
-        tempdict = {}
-        execfile(filename, tempdict)
-        for key in tempdict:
-            if key.isupper():
-                context[key] = tempdict[key]
+    if module is not None:
+         context.update(
+             (k, v) for k, v in inspect.getmembers(module) if k.isupper()
+         )
     return context
 
 
+def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
+    """
+    Load settings from a file path, returning a dict.
+
+    """
+
+    name = os.path.basename(filename).rpartition(".")[0]
+    module = imp.load_source(name, filename)
+    return get_settings_from_module(module, default_settings=default_settings)
+
+
 def configure_settings(settings, default_settings=None, filename=None):
     """Provide optimizations, error checking, and warnings for loaded settings"""
     if default_settings is None:


Test patch: