-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PythonMutator: support omitempty in PyDABs #1513
Changes from 1 commit
b1d8b59
7a3e961
370b286
838c116
214577b
f067029
d58f374
0ffa1b6
f3b352e
dd4583e
03994b6
e575c97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -241,8 +241,12 @@ func createLoadOverrideVisitor(ctx context.Context) merge.OverrideVisitor { | |
jobsPath := dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs")) | ||
|
||
return merge.OverrideVisitor{ | ||
VisitDelete: func(valuePath dyn.Path, left dyn.Value) error { | ||
return fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
VisitDelete: func(valuePath dyn.Path, left dyn.Value) (dyn.Value, error) { | ||
if isOmitemptyDelete(left) { | ||
return left, nil | ||
} | ||
|
||
return dyn.InvalidValue, fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
}, | ||
VisitInsert: func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) { | ||
if !valuePath.HasPrefix(jobsPath) { | ||
|
@@ -274,21 +278,25 @@ func createInitOverrideVisitor(ctx context.Context) merge.OverrideVisitor { | |
jobsPath := dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs")) | ||
|
||
return merge.OverrideVisitor{ | ||
VisitDelete: func(valuePath dyn.Path, left dyn.Value) error { | ||
VisitDelete: func(valuePath dyn.Path, left dyn.Value) (dyn.Value, error) { | ||
if isOmitemptyDelete(left) { | ||
return left, nil | ||
} | ||
|
||
if !valuePath.HasPrefix(jobsPath) { | ||
return fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
return dyn.InvalidValue, fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
} | ||
|
||
deleteResource := len(valuePath) == len(jobsPath)+1 | ||
|
||
if deleteResource { | ||
return fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
return dyn.InvalidValue, fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) | ||
} | ||
|
||
// deleting properties is allowed because it only changes an existing resource | ||
log.Debugf(ctx, "Delete value at %q", valuePath.String()) | ||
|
||
return nil | ||
return dyn.NilValue, nil | ||
}, | ||
VisitInsert: func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) { | ||
if !valuePath.HasPrefix(jobsPath) { | ||
|
@@ -311,6 +319,21 @@ func createInitOverrideVisitor(ctx context.Context) merge.OverrideVisitor { | |
} | ||
} | ||
|
||
func isOmitemptyDelete(left dyn.Value) bool { | ||
// PyDABs output can omit empty sequences/mappings, because we don't track them as optional, | ||
// there is no semantic difference between empty and missing, so we keep them as they were before. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly with this PR you are trying to fix the case where a value in the original bundle config is an empty (but not nil) sequence or mapping, but pydabs skips emitting the key (with an empty value) in its serialized output. This causes an error ( Is my understanding of the problem you are trying to solve correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. We don't distinguish between empty and unset lists, so when the list is empty, we omit it in the output. |
||
|
||
if left.Kind() == dyn.KindMap && left.MustMap().Len() == 0 { | ||
return true | ||
} | ||
|
||
if left.Kind() == dyn.KindSequence && len(left.MustSequence()) == 0 { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
// interpreterPath returns platform-specific path to Python interpreter in the virtual environment. | ||
func interpreterPath(venvPath string) string { | ||
if runtime.GOOS == "windows" { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,11 +13,17 @@ import ( | |
// For instance, it can disallow changes outside the specific path(s), or update | ||
// the location of the effective value. | ||
// | ||
// Values returned by 'VisitDelete', 'VisitInsert' and 'VisitUpdate' are used as | ||
// the final value of the node. 'VisitDelete' should return dyn.NilValue to | ||
// do the delete, or can return 'left' to undo it. | ||
pietern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// | ||
// TODO 'VisitDelete' and 'VisitInsert' should support dyn.NilValue as well | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would the semantics be? I think you mean |
||
// | ||
// 'VisitDelete' is called when a value is removed from mapping or sequence | ||
// 'VisitInsert' is called when a new value is added to mapping or sequence | ||
// 'VisitUpdate' is called when a leaf value is updated | ||
type OverrideVisitor struct { | ||
VisitDelete func(valuePath dyn.Path, left dyn.Value) error | ||
VisitDelete func(valuePath dyn.Path, left dyn.Value) (dyn.Value, error) | ||
VisitInsert func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) | ||
VisitUpdate func(valuePath dyn.Path, left dyn.Value, right dyn.Value) (dyn.Value, error) | ||
} | ||
|
@@ -111,11 +117,16 @@ func overrideMapping(basePath dyn.Path, leftMapping dyn.Mapping, rightMapping dy | |
if _, ok := rightMapping.GetPair(leftPair.Key); !ok { | ||
path := basePath.Append(dyn.Key(leftPair.Key.MustString())) | ||
|
||
err := visitor.VisitDelete(path, leftPair.Value) | ||
deleteOut, err := visitor.VisitDelete(path, leftPair.Value) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
|
||
// if 'delete' was undone, add it back | ||
if deleteOut != dyn.NilValue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to check the |
||
out.Set(leftPair.Key, deleteOut) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -186,11 +197,16 @@ func overrideSequence(basePath dyn.Path, left []dyn.Value, right []dyn.Value, vi | |
} else if len(left) > len(right) { | ||
for i := minLen; i < len(left); i++ { | ||
path := basePath.Append(dyn.Index(i)) | ||
err := visitor.VisitDelete(path, left[i]) | ||
out, err := visitor.VisitDelete(path, left[i]) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// if 'delete' was undone, add it back | ||
if out != dyn.NilValue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above. |
||
values = append(values, out) | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
omitempty
is specific to the JSON serializer. Here we check if the incoming value is an empty collection.Maybe
isEmptyCollection
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intentional because we want to reserve this method to specific behavior with omit empty and add a comment explaining that. For instance, we intentionally don't handle objects where all fields are empty.