appdaemon icon indicating copy to clipboard operation
appdaemon copied to clipboard

Throw an error / refuse to load if apps.yaml has a duplicate top-level key

Open markfickett opened this issue 1 year ago • 2 comments
trafficstars

Is there an existing feature request for this?

  • [X] I have searched the existing issues

Your feature request

A couple times I've accidentally repeated the same apps.yaml top-level key. AppDaemon picks one and ignores the other (maybe this is just YAML load behavior). This has led to confusing errors for me, such as a new app working fine but an old one no longer working somewhat mysteriously until I figure out what I did. I would rather AppDaemon treat this as an error and refuse to load the new apps.yaml file.

Example:

my_app:
  module: my_app
  class: MyApp
  argument: value_one
# ... many lines ...
my_app:
  module: my_app
  class: MyApp
  argument: a_different_value

In the above case, I would like an error, but AppDaemon loads the file with only one my_app.

PyYAML has issues open for this since 2016 so I doubt we'll see a library solution, but there are a number of workarounds mentioned like creating a customized loader.

markfickett avatar Dec 14 '23 20:12 markfickett

As you guessed, this is a limitation of YAML. By the time this situation gets through to AD code, the YAML libraries have already ignored one of the entries as a duplicate and we never get to see it.

acockburn avatar Dec 15 '23 13:12 acockburn

Thanks for taking a look!

What do you think of the approach of customizing the yaml Loader? Here's an example from a gist discussion thread:

class UniqueKeyLoader(yaml.SafeLoader):
    def construct_mapping(self, node, deep=False):
        mapping = set()
        for key_node, value_node in node.value:
            if ':merge' in key_node.tag:
                continue 
            key = self.construct_object(key_node, deep=deep)
            if key in mapping:
                raise ValueError(f"Duplicate {key!r} key found in YAML.")
            mapping.add(key)
        return super().construct_mapping(node, deep)

# other code

yaml_dic=yaml.load(yaml_file,Loader=UniqueKeyLoader)

Poking around AppDaemon's code, would that fit into utils.read_yaml_config, as a possible replacement for Loader=yaml.SafeLoader (which gets called from app_management.py) ?

By the way I'm really enjoying AppDaemon, very easy to get started, helpful examples, and flexible enough to handle as much complexity as I've come up with so far.

markfickett avatar Dec 16 '23 02:12 markfickett