Skip to content
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

Add functionality for overriding dictionary merges. #61

Merged
merged 2 commits into from
Jan 3, 2017

Conversation

Rtzq0
Copy link
Collaborator

@Rtzq0 Rtzq0 commented Jan 2, 2017

Dictionary merges can now be overridden at any level by usage
of a special character. The tilde is chosen as base default.

This PR is a resolution to #60

Copy link
Collaborator

@lottspot lottspot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had a chance to actually test this change out yet, but a couple of overarching things from my read through of the patch:

  1. I'm trying to understand the role of the initmerge parameter, but probably need help getting it
  2. We should probably provide some way for the user to escape the special meaning of PARAMETER_DICT_KEY_OVERRIDE_PREFIX

Overall though, I think this is a great feature being done with the right implementation strategy

cur (dict): Current dictionary
new (dict): Dictionary to be merged
path (string): Merging path from recursion
initmerge (bool): True if called as part of entity init
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is an example of a case in which a Parameters object performs a merge as part of Entity initialization? I don't see any merges that occur in Entity.__init__

Copy link
Collaborator Author

@Rtzq0 Rtzq0 Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merge is actually performed by Parameters.__init__ which happens to be called by Entity.__init__. Lines 47-50 of Parameters.__init__...

        if mapping is not None:
            # we initialise by merging, otherwise the list of references might
            # not be updated
            self.merge(mapping)

In actuality this happens, as far as I can tell, every time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, ok make total sense. The note could be a bit less confusing if edited to state "True if called as part of Parameters init" rather than "entity init".

path.new_subpath(key))
if key.startswith(ovrprfx) and not initmerge:
ret[key.lstrip(ovrprfx)] = newvalue
else:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the case if key.startswith(overprfx) and initmerge should be explicitly handled. I don't think we want to allow processed parameter keys to contain special characters without those characters being explicitly escaped.

Copy link
Collaborator Author

@Rtzq0 Rtzq0 Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this in principle but in practice it's a mite hairier... in reality processed parameter keys never actually reach the user because merge is called outside init in core. I've tested this by creating a node with no classes and "tilde-params". Furthermore if we strip them in the __init__ merge they do not exist as a signalling character during the "proper" merge, in which case it merges as normal (contrary to expectations).

I'm not averse to reformulating this conditional but I'd maybe like to see something that you think would work. I futzed with this a lot and there's some delicate interplay going between Parameters.__init__ and Core._nodeinfo

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I think I see what you're saying here. This certainly does present a practical implementation challenge. I think I need to get my hands dirty a bit with this feature to see how it all plays out.

@@ -188,6 +188,18 @@ def test_merge_dicts_overwrite(self):
goal.update(mergee)
self.assertDictEqual(p.as_dict(), dict(dict=goal))

def test_merge_dicts_override(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm yes unit tests.... A Very Good Idea

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was WAAAAY too tired when I wrote those. Replicated the functionality of other tests AND missed validating that we override properly on list extensions as well!

Much better and cleaner now.

@Rtzq0
Copy link
Collaborator Author

Rtzq0 commented Jan 2, 2017

RE 1. I'm trying to understand the role of the initmerge parameter, but probably need help getting it:

The only time a Parameters() instance is merged with a dict is during __init__. At every other time it's merged with a Parameters() instance. initmerge is just a signalling argument that indicates that we've detected that this is an __init__ merge based on the fact that we're being merged with a dict object.

RE 2. We should probably provide some way for the user to escape the special meaning of PARAMETER_DICT_KEY_OVERRIDE_PREFIX

I'd like to open a separate non-blocking issue for this, the reason being that it'll be more consistent for users if the "special behavior character" is consistent between this and the applications negations (which are currently hardcoded with no escaping) and for the escaping system to also be consistent between those sections.

Dictionary merges can now be overridden at any level by usage
of a special character. The tilde is chosen as base default.
@lottspot
Copy link
Collaborator

lottspot commented Jan 2, 2017

The only time a Parameters() instance is merged with a dict is during init. At every other time it's merged with a Parameters() instance. initmerge is just a signalling argument that indicates that we've detected that this is an init merge based on the fact that we're being merged with a dict object.

Makes total sense now, thanks! I might suggest revising this approach to go one of two ways though:

  1. Fully explicit (probably better): Add the init parameter to Parameters.merge directly and set it explicitly within Parameters.__init__. Don't try to guess whether it is an init merge at all.
  2. Fully implicit (probably less good): Don't add it as a parameter anywhere. Guess whether it is an initial merge based on the (lack of) contents of self._base.

I think going at it one of these two ways will make the intent less confusing to read and would make the code itself more robust to changes

I'd like to open a separate non-blocking issue for this, the reason being that it'll be more consistent for users if the "special behavior character" is consistent between this and the applications negations (which are currently hardcoded with no escaping) and for the escaping system to also be consistent between those sections.

I certainly agree-- We have this problem elsewhere in the codebase right now, and we should solve it in a unified way, which probably requires its own dedicated effort. You're probably right in that it shouldn't block this merge, but since this merge does represent a breaking change with user data formats, and until that problem is solved the breakage will have no remediation, it's definitely a problem we'll need to address before another stable release

@Rtzq0
Copy link
Collaborator Author

Rtzq0 commented Jan 3, 2017

I have pushed another commit that takes your preferred approach to the initmerge argument. I did not initially do it that way intentionally but I have been convinced that explicit is probably better.

I did not rebase this one because I thought it would be good to keep the extra reversion point, JIC.

@Rtzq0 Rtzq0 added this to the 1.4.2 milestone Jan 3, 2017
@lottspot
Copy link
Collaborator

lottspot commented Jan 3, 2017

Kickass job on implementing this feature! I'd say it's ready for a merge and some real-world testing

@lottspot lottspot merged commit 75dbf1e into madduck:master Jan 3, 2017
@Rtzq0 Rtzq0 deleted the issue_60 branch January 3, 2017 19:25
@bbinet
Copy link

bbinet commented Apr 5, 2017

It would be great if the same feature could also be implemented for the lists: overwriting a list instead of extending when the special ~ character is used.

@Rtzq0
Copy link
Collaborator Author

Rtzq0 commented Apr 5, 2017

@bbinet that is definitely how I /wanted/ to do it, but I hit some surprising complexity. If this represents a use case/need for you, please open and issue so that I have something to track it so I don't forget to work on this problem :-)

@bbinet
Copy link

bbinet commented Apr 5, 2017

@Rtzq0 I've just created #70
I'm curious about the surprising complexity, but let's discuss it in the #70 issue.

@bbinet
Copy link

bbinet commented May 3, 2017

@Rtzq0 I meant #75

AndrewPickford pushed a commit to AndrewPickford/reclass that referenced this pull request Sep 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants