0

1. Summary

I can't find, how I can add variables from YAML files to Python files without duplicates.


2. Purpose

I use Pelican — static sites generator. It use .py files for configuration. Problems:

  1. I can't reuse variables from .py for JavaScript
  2. import * antipattern, that use even in official Pelican blog

I try move configuration to YAML files → I get problem of this question.


3. MCVE

3.1. Files

"""First Python file."""
# [INFO] Using ruamel.yaml — superset of PyYAML:
# https://stackoverflow.com/a/38922434/5951529
import ruamel.yaml as yaml

SETTINGS_FILES = ["kira.yaml", "kristina.yaml"]

for setting_file in SETTINGS_FILES:
    VARIABLES = yaml.safe_load(open(setting_file))
    # [INFO] Convert Python dictionary to variables:
    # https://stackoverflow.com/a/36059129/5951529
    locals().update(VARIABLES)

# [INFO] View all variables:
# https://stackoverflow.com/a/633134/5951529
print(dir())
  • publishconf.py:
"""Second Python file."""
import ruamel.yaml as yaml

# [NOTE] Another value in list
SETTINGS_FILES = ["kira.yaml", "katya.yaml"]

for setting_file in SETTINGS_FILES:
    VARIABLES = yaml.load(open(setting_file))
    locals().update(VARIABLES)


print(dir())
  • kira.yaml:
DECISION: Saint Petersburg
  • kristina.yaml:
SPAIN: Marbella
  • katya.yaml:
BURIED: Novoshakhtinsk

3.2. Expected behavior

  • DECISION and SPAIN variables in main.py:
$ python main.py
['DECISION', 'SETTINGS_FILES', 'SPAIN', 'VARIABLES', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__warningregistry__', 'setting_file', 'yaml']
  • DECISION and BURIED variables in publishconf.py:
$ python publishconf.py
['BURIED', 'DECISION', 'SETTINGS_FILES', 'VARIABLES', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__warningregistry__', 'setting_file', 'yaml']

3.3. Problem

Duplicate loop in main.py and publishconf.py:

for setting_file in SETTINGS_FILES:
    VARIABLES = yaml.load(open(setting_file))
    locals().update(VARIABLES)

Can I not use duplicates?


4. Not helped

4.1. Configuration file

"""Config Python file."""
# [INFO] Using ruamel.yaml — superset of PyYAML:
# https://stackoverflow.com/a/38922434/5951529
import ruamel.yaml as yaml

MAIN_CONFIG = ["kira.yaml", "kristina.yaml"]

PUBLISHCONF_CONFIG = ["kira.yaml", "katya.yaml"]


def kirafunction(pelicanplugins):
    """Function for both Python files."""
    for setting_file in pelicanplugins:
        # [INFO] Convert Python dictionary to variables:
        # https://stackoverflow.com/a/36059129/5951529
        variables = yaml.safe_load(open(setting_file))
        globals().update(variables)


def main_function():
    """For main.py."""
    kirafunction(MAIN_CONFIG)


def publishconf_function():
    """For publishconf.py."""
    kirafunction(PUBLISHCONF_CONFIG)
  • main.py:
"""First Python file."""
import sys

from config import main_function

sys.path.append(".")


main_function()

# [INFO] View all variables:
# https://stackoverflow.com/a/633134/5951529
print(dir())
  • publishconf.py:
"""Second Python file."""
import sys

from config import publishconf_function

sys.path.append(".")


publishconf_function()

print(dir())

Variables from main_function and publishconf_function doesn't share across files:

$ python main.py
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'main_function', 'signal', 'sys']

4.2. Another attempts

  1. Wrap loop to function as in this example:

    def kirafunction():
        """Docstring."""
        for setting_file in SETTINGS_FILES:
            VARIABLES = yaml.safe_load(open(setting_file))
            locals().update(VARIABLES)
    
    
    kirafunction()
    
  2. Using global keyword

  3. I think editing locals() like that is generally a bad idea. If you think globals() is a better alternative, think it twice!

  4. Search in Stack Overflow questions

Саша Черных
  • 2,561
  • 4
  • 25
  • 71
  • In general, to avoid duplicates in python we use sets, so maybe you could try to implement some in your code – Nenri Mar 11 '19 at 09:52
  • Why are you using `locals()` or `globals()` at all? Save everything to variables. – Adrian Krupa Mar 11 '19 at 10:07
  • `locals().update(VARIABLES)` is totally wrong. That is not going to work in a function, and is explicitly warned against in the documentation. Simply use the dictionary. You don't need variables, *use the container*. – juanpa.arrivillaga Mar 11 '19 at 10:08
  • @AdrianKrupa, `type: answer`. I try [**as in this answer**](https://stackoverflow.com/a/36059129/5951529). Thanks. – Саша Черных Mar 11 '19 at 14:17
  • @juanpa.arrivillaga, `type: reply`. I've update my question. Look at 2 item. Thanks. – Саша Черных Mar 11 '19 at 14:19
  • What is your point? You don't need variables, just use the dictionary you get back from the config. – juanpa.arrivillaga Mar 11 '19 at 15:35
  • @juanpa.arrivillaga, `type: reply`. `You don't need variables, just use the dictionary you get back from the config.` — look at item 2 of my question and [**Pelican configuration example**](http://docs.getpelican.com/en/latest/settings.html#example-settings). If I really don't need variables, can you write example, what can I do? Thanks. – Саша Черных Mar 11 '19 at 15:46

1 Answers1

1

I would avoid any update to what locals returns, because documentation explicitely states:

Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

The globals function is a dictionnary simply containing the attributes of a module, and the mapping returned by globals is indeed writable.

So if this exists in one Python source:

def kirafunction(map,settings):
  # [NOTE] Another value in list
  for setting_file in settings:
    VARIABLES = yaml.load(open(setting_file))
    map.update(VARIABLES)

This can be used from any other Python source after importing the above function:

kirafunction(globals(), settings)

and will import the variables in the globals dictionnary of the calling module. And will be highly non-pythonic...

A slightly more Pythonic way, would be to dedicate one Python module to hold both the code loading the yaml files and the new variables:

loader.py:

import ruamel.yaml as yaml

SETTINGS_FILES = ["kira.yaml", "kristina.yaml"]

for setting_file in SETTINGS_FILES:
    VARIABLES = yaml.safe_load(open(setting_file))
    # [INFO] Convert Python dictionary to variables:
    # https://stackoverflow.com/a/36059129/5951529
    globals().update(VARIABLES)

Then from any other Python module you can use:

...
import loader      # assuming that it is in sys.path...
...
print(loader.DECISION)
print(dir(loader))

But it is still uncommon and would require comments to explain the rationale for it.


After reading the Pelican config example from you comment, I assume that what you need is a way to import in different scripts a bunch of variables declared in yaml files. In that case I would put the code loading the variables in one module, and update the globals() dictionnary in the other modules:

loader.py:

import ruamel.yaml as yaml

MAIN_CONFIG = ["kira.yaml", "kristina.yaml"]

PUBLISHCONF_CONFIG = ["kira.yaml", "katya.yaml"]


def kirafunction(pelicanplugins):
    """Function for both Python files."""
    variables = {}
    for setting_file in pelicanplugins:
        # [INFO] Convert Python dictionary to variables:
        # https://stackoverflow.com/a/36059129/5951529
        variables.update(yaml.safe_load(open(setting_file)))
    return variables

Then for example in publishconf.py you would use:

from loader import kirafunction, PUBLISHCONF_CONFIG as pelican_config

# other Python code...

# import variables from the yaml files defined in PUBLISHCONF_CONFIG
#  because Pelican expects them as plain Python module variables
globals().update(kirafunction(pelican_config))

Again, updating globals() is probably appropriate in this use case, but is generally frowned upon, hence the comment.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • `But it is still uncommon and would require comments to explain the rationale for it.` — look at [**my comment**](https://stackoverflow.com/questions/55099110#comment96942881_55099529) for another answer. Thanks. – Саша Черных Mar 11 '19 at 10:39
  • Serge Ballesta, `type: need clarification`. **1.** Can you show full another Python module code? Look at 4.1. item of my question for details. // **2.** Is `globals()` from your answer a good practice? Thanks. – Саша Черных Mar 11 '19 at 14:28
  • @СашаЧерных: 1. I am sorry, by I cannot really understand what you are trying to achieve... 2. Definitely not, except when strictly required. What I mean here is that dynamic creation of variables is generally deemed as a code smell, and you should prefere a simple mapping (a dict or dict sub class) if you can use it. But I do not use or know Pelican, so I cannot know whether that kind of solution is appropriate, or whether you are experimenting a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Serge Ballesta Mar 11 '19 at 14:43
  • Serge Ballesta, // **1.** `type: clarification`. Can you add to your example full `main.py` or `publishconf.py`? // **2.** `type: reply`. [**Example Pelican configuration**](http://docs.getpelican.com/en/latest/settings.html#example-settings). I try to move this configuration to YAML files by reasons from item 2 of my question. // Please, tell me, if I need to say some more information. Thanks. – Саша Черных Mar 11 '19 at 15:54