yahp icon indicating copy to clipboard operation
yahp copied to clipboard

JSON Merges Style Inherits by Default

Open Averylamp opened this issue 3 years ago • 3 comments

Inherits Spec

Want to move from our custom inherits scheme to a scheme such as JSON merge to allow for more flexibility when merging different parameter types. Hoping to use a operator schema based merge, but inline in our yamls. Here's the spec:


Inherits Keyword Options

The special yaml field that triggers inherits has the following form

# Generic form with Optional `<Operator>` and `<Scope>` delimited with `$` and `|` respectively
inherits$<Operator>|<Scope>:

# simple inherits with default options (Operator:overwrite)
inherits:

# Operator specified
inherits$concat:

# Scope specified
inherits|root:

# Operator and Scope specified
inherits$replace|match:


Inherits field file options

There are two formats of how the inherits field works. The inherits: field can take in either a list of yamls to inherit from, or a singular yaml file to inherit. If there are a list of files, the latter files take precedence over earlier files (if overwriting), but essentially each file should be applied with the inherits operator sequentially.

# List of inherits, applied sequentially
inherits:
  - inherit1.yaml
  - inherit2.yaml

# singular value inherits only values underneath the field
inherits: inherit_field.yaml

By default, relative filepaths to the file that calls inherits are used. If a absolute path is give, starting with /, absolute filepaths are used.

Each inherited file can recursively inherit other files (using a relative or absolute path).

Do not worry about circular inherits (maybe throw an error if possible).


Inherits Scope

When inheriting from other yamls, there are two options that decide where in the other yaml to start inheriting from.

  • match (default)
  • root All future examples are done with the updates operator (default operator)

Match example

1.yaml

produce:
  tomatoes:
    inherits: 2.yaml
  potatoes: almost ripe

2.yaml

produce:
  tomatoes: ripe

out.yaml

produce:
  tomatoes: ripe
  potatoes: almost ripe

Root

Example 1

1.yaml

produce:
  tomatoes:
    inherits: 2.yaml
  potatoes: almost ripe

2.yaml

tomatoes: ripe

out.yaml

produce:
  tomatoes: 
    tomatoes: ripe
  potatoes: almost ripe

Note, in this example, the keys are duplicated, because the root yaml is loaded as the value of tomatoes:

Example 2

1.yaml

produce:
  tomatoes:
    inherits: 2.yaml
  potatoes: almost ripe

2.yaml

ripe

out.yaml

produce:
  tomatoes: ripe
  potatoes: almost ripe

This produces the expected, as 2.yaml is loaded directly from the root and update is applied to the tomatoes value


The inherits field can be placed anywhere within a dictionary, and will only inherit/operate within its current scope from that point in the dictionary to the leaves. For example:

1.yaml

tomatoes:
  inherits|match: 2.yaml
potatoes: almost ripe

2.yaml

tomatoes: ripe
potatoes: planted

out.yaml

tomatoes: ripe
potatoes: almost ripe

Operators

There are a couple of operators we want to support

  • update (default)
  • concat (much like update, but concats list fields)
  • replace (replaces the value directly without updates, removes non existing values)

A lot of what differentiates operators is how they deal with lists or leaf values.


Update Operator

The update operator is the default operator. It will update a dictionary of values

1.yaml

produce:
  inherits$update|root:
    - 2.yaml
  tomatoes: 
    number: 12
    type: cherry
    status: ripe
    tags:
     - organic
     - fertilized
  potatoes: 
    type: russell

2.yaml

tomatoes: 
  number: 13
  tags:
    - gmo
potatoes:
  status: dying

out.yaml

produce:
  tomatoes: 
    number: 13
    type: cherry
    status: ripe
    tags:
     - gmo
  potatoes: 
    type: russell
    status: dying

Concat Operator

The concat operator differs from the update operator in leaf list values. Note that instead of replacing the entire list, the lists are concatenated together. Also note, this only applies to list items, but not other + implementing items such as int or str

1.yaml

produce:
  inherits$update|root:
    - 2.yaml
  tomatoes: 
    number: 12
    type: cherry
    status: ripe
    tags:
     - organic
     - fertilized
  potatoes: 
    type: russell

2.yaml

tomatoes: 
  number: 2
  tags:
    - gmo
potatoes:
  status: dying

out.yaml

produce:
  tomatoes: 
    number: 2
    type: cherry
    status: ripe
    tags:
     - organic
     - fertilized
     - gmo
  potatoes: 
    type: russell
    status: dying

Replace Operator

The replace operator replaces the entire value with the inherited values.

Note: it does not make sense to use inherits$replace with a list of yamls as only the last will be the value

Example 1

1.yaml

produce:
  inherits$replace|root:
    - 2.yaml
  tomatoes: 
    number: 12
    type: cherry
    status: ripe
    tags:
     - organic
     - fertilized
  potatoes: 
    type: russell

2.yaml

tomatoes: 
  number: 2
  tags:
    - gmo

out.yaml

produce:
  tomatoes: 
    number: 2
    tags:
      - gmo
Example 2

1.yaml

produce:
  inherits$replace|root:
    - 2.yaml
    - 3.yaml
  tomatoes: 
    number: 12
    type: cherry
    status: ripe
    tags:
     - organic
     - fertilized
  potatoes: 
    type: russell

2.yaml

tomatoes: 
  number: 2
  tags:
    - gmo

3.yaml

None

out.yaml

produce: None

Other Gotchas


Example 1 (No match found)

1.yaml

produce:
  inherits:
    - 2.yaml
  tomatoes: 
    number: 12
    type: cherry
  potatoes: 
    type: russell

2.yaml

tomatoes: 
  number: 2
  tags:
    - gmo

out.yaml

produce:
  tomatoes: 
    number: 12
    type: cherry
  potatoes: 
    type: russell

Nothing is updated as the produce: key is not matched.

Averylamp avatar Jan 03 '22 22:01 Averylamp