kadet icon indicating copy to clipboard operation
kadet copied to clipboard

Add yaml serializer for addict dictionary

Open jkrzemin opened this issue 4 years ago • 4 comments

Using kwargs to update root on kadet input right now can lead to quite a frustrating and a bit misleading error

yaml.representer.RepresenterError: ('cannot represent an object', {'name': 'http-api', 'port': 8080, 'targetPort': 8080, 'protocol': 'TCP'})

It's because a dictionary is of Dict type which is unknown to yaml serializer, which misleadingly (in context of this error message) has the same text representation as regular python dictionary.

jkrzemin avatar Mar 11 '21 16:03 jkrzemin

@jkrzemin thanks for reporting. Can you paste a snippet that is causing this? Just to make sure I understand it correctly

ramaro avatar Mar 11 '21 17:03 ramaro

Ok, so here is a class definition

(venv) ➜  kapitan_issue_example cacat inventory/classes/my_component.yml
parameters:
  kapitan:
    compile:
      - output_path: kadet_output
        input_type: kadet
        output_type: yaml
        input_paths:
          - components/my_component

Here is python source for component

(venv) ➜  kapitan_issue_example cacat components/my_component/__init__.py
from kapitan.inputs import kadet

inv = kadet.inventory()

class SomeResource(kadet.BaseObj):
    def body(self) -> None:
        self.root.update({
            'key_for_dictionary_parameter': {
                'inner_key': self.kwargs.dict_param
                }
        })


def main():
    output = kadet.BaseObj()
    output.root['my_component'] = SomeResource(dict_param=inv.parameters.dict_parameter)
    return output

Which causes

(venv) ➜  kapitan_issue_example   kapitan compile
Unknown (Non-Kapitan) Error occurred
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/Users/jkrzemin/.pyenv/versions/3.8.1/lib/python3.8/multiprocessing/pool.py", line 125, in worker
    result = (True, func(*args, **kwds))
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/targets.py", line 465, in compile_target
    input_compiler.compile_obj(comp_obj, ext_vars, **kwargs)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/inputs/base.py", line 54, in compile_obj
    self.compile_input_path(input_path, comp_obj, ext_vars, **kwargs)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/inputs/base.py", line 69, in compile_input_path
    self.compile_file(
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/inputs/kadet.py", line 156, in compile_file
    fp.write_yaml(item_value)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/inputs/base.py", line 125, in write_yaml
    yaml.dump(obj, stream=self.fp, indent=indent, Dumper=PrettyDumper, default_flow_style=False)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/__init__.py", line 290, in dump
    return dump_all([data], stream, Dumper=Dumper, **kwds)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/__init__.py", line 278, in dump_all
    dumper.represent(data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 27, in represent
    node = self.represent_data(data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 58, in represent_data
    node = self.yaml_representers[None](self, data)
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/yaml/representer.py", line 231, in represent_undefined
    raise RepresenterError("cannot represent an object", data)
yaml.representer.RepresenterError: ('cannot represent an object', {'foo': {'bar': 'car'}})
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/targets.py", line 136, in compile_targets
    [p.get() for p in pool.imap_unordered(worker, target_objs) if p]
  File "/private/tmp/kapitan_issue_example/venv/lib/python3.8/site-packages/kapitan/targets.py", line 136, in <listcomp>
    [p.get() for p in pool.imap_unordered(worker, target_objs) if p]
  File "/Users/jkrzemin/.pyenv/versions/3.8.1/lib/python3.8/multiprocessing/pool.py", line 865, in next
    raise value
yaml.representer.RepresenterError: ('cannot represent an object', {'foo': {'bar': 'car'}})


('cannot represent an object', {'foo': {'bar': 'car'}})

An interesting thing I've found during preparing this snippet is that if I'd assign self.kwargs.dict_param to key_for_dictionary_parameter instead of key_for_dictionary_parameter['inner_key'] it works properly. I didn't go through the source, but it smells a bit like a shallow check during compile step or YAML serialization.

Edit: I've forgotten about target spec, here it is.

(venv) ➜  kapitan_issue_example cacat inventory/targets/my_target.yml
classes:
  - common
  - my_component

parameters:
  target_name: my_target
  dict_parameter:
    foo:
      bar: car

jkrzemin avatar Mar 15 '21 13:03 jkrzemin

@ramaro does this snipped provide information you need?

jkrzemin avatar Mar 17 '21 11:03 jkrzemin

@jkrzemin thanks, this makes it clearer! It is indeed a case of assigning self.kwargs.dict_param to key_for_dictionary_parameter, that's by design. However I do agree that requiring assigning from self.kwargs might not always be obvious or desired, so maybe we can relax that somehow.

ramaro avatar Mar 17 '21 18:03 ramaro