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

Substitution with merged configs is incorrect #110

Open
macster84 opened this issue Jan 6, 2017 · 5 comments
Open

Substitution with merged configs is incorrect #110

macster84 opened this issue Jan 6, 2017 · 5 comments
Assignees
Labels

Comments

@macster84
Copy link

Hi dear maintainers,

with pyhocon 0.3.35 I get unexpected substitution results with merged configs.

I have following configurations:

base.conf

base {
  baseattr1: 1
  baseattr2: 1
}

derived = ${base} {
  derivedattr: 1
  lost: 1
}

test.conf

include "base.conf"

base {
  baseattr1: 2
  baseattr3: 1
}

derived = ${base} {
  derivedattr1: 2
  derivedattr2: 1
}

test.py:

#!/usr/bin/env python
from pyhocon import ConfigFactory
from pyhocon.tool import HOCONConverter

conf = ConfigFactory.parse_file('test.conf')
print(HOCONConverter.to_json(conf))

I executed the test.py with the configs being in the same directory.
Expected result:

{
  "base": {
    "baseattr1": 2,
    "baseattr2": 1,
    "baseattr3": 1
  },
  "derived": {
    "baseattr1": 2,
    "baseattr2": 1,
    "baseattr3": 1,
    "derivedattr1": 2,
    "lost": 1,
    "derivedattr2": 1
  }
}

Actual result:

{
  "base": {
    "baseattr1": 2,
    "baseattr2": 1,
    "baseattr3": 1
  },
  "derived": {
    "baseattr1": 2,
    "baseattr2": 1,
    "baseattr3": 1,
    "derivedattr1": 2,
    "derivedattr2": 1
  }
}

Notice that the key 'lost' is missing.

In fact, if I adapt the test.conf like this; the output is like expected.

include "base.conf"

base {
  baseattr1: 2
  baseattr3: 1
}

derived {
  derivedattr1: 1
}

Notice that the only change was to remove the object merge for key 'derived'.
I did not dig into the code yet, but I think it is related to merging and not the substitution. If I set 'resolve=False' in test.py, the output looks like this:

{
  "base": {
    "baseattr1": 2,
    "baseattr2": 1,
    "baseattr3": 1
  },
  "derived": [ConfigValues: [ConfigSubstitution: base],ConfigTree([(u'derivedattr1', 2), (u'derivedattr2', 1)])]
}

The key 'lost' is already missing.

I tested this stuff with the JVM implementation com.typesafe.config v1.3.0 which yields the expected results.

Do you have any insights on that?
Many thanks in advance

Greets,
Alexander

@macster84 macster84 changed the title Substitution with merged items is incorrect Substitution with merged configs is incorrect Jan 6, 2017
@darthbear darthbear self-assigned this Jan 10, 2017
@darthbear darthbear added the bug label Jan 10, 2017
@hekaldama
Copy link

Hows this coming?

@aalba6675
Copy link
Contributor

aalba6675 commented Jan 11, 2018

Minimal reproducer. It seems to be an issue with resolve_substitutions() If derived = ${base}... appears twice the first one is lost.

>>> test = ConfigFactory.parse_string('''
... base {}
... 
... derived =  ${base} {
...   lost: 1
... }
... 
... derived = ${base} {
...   found: 2
... }
... ''')
>>> 
>>> test['derived']
ConfigTree([('found', 2)])

Expected answer: ConfigTree([('found', 2), ('lost', 1)])

@aalba6675
Copy link
Contributor

aalba6675 commented Jan 11, 2018

I think what is happening: if a key is ConfigValues, when it is resolved to a ConfigTree we do not walk down the linked list of overriden_value and perform resolution and merging if the overriden values are also ConfigTrees

derived = ${base}
derived = ${base2}

base  {
 a: 1
 b: 2
}

base2 {
 b:3
 c:4
}

The overriden list ConfigTree([('a', 1), ('b', 2)]), ConfigTree([('b', 3), ('c', 4)]) but we do check that the antecedents are ConfigTrees and merge from the bottom up

@aalba6675
Copy link
Contributor

RFC:
Get substitutions from all overriden_values as well

@@ -423,7 +424,17 @@ class ConfigValues(object):
         return len(self.get_substitutions()) > 0
 
     def get_substitutions(self):
-        return [token for token in self.tokens if isinstance(token, ConfigSubstitution)]
+        lst = []
+        node = self
+        while node:
+            lst = [token for token in node.tokens if isinstance(token, ConfigSubstitution)] + lst
+            if hasattr(node, 'overriden_value'):
+                node = node.overriden_value
+                if not isinstance(node, ConfigValues):
+                    break
+            else:
+                break
+        return lst

If we are a ConfigTree, merge from the bottom up

@@ -459,7 +470,28 @@ class ConfigValues(object):
                         col=col(self._loc, self._instring)))
 
         if first_tok_type is ConfigTree:
+            child = []
+            if hasattr(self, 'overriden_value'):
+                node = self.overriden_value
+                while node:
+                    if isinstance(node, ConfigValues):
+                        value = node.transform()
+                        if isinstance(value, ConfigTree):
+                            child.append(value)
+                        else:
+                            break
+                    elif isinstance(node, ConfigTree):
+                        child.append(node)
+                    else:
+                        break
+                    if hasattr(node, 'overriden_value'):
+                        node = node.overriden_value
+                    else:
+                        break
+
             result = ConfigTree()
+            for conf in reversed(child):
+                ConfigTree.merge_configs(result, conf, copy_trees=True)
             for token in tokens:
                 ConfigTree.merge_configs(result, token, copy_trees=True)
             return result

@aalba6675
Copy link
Contributor

PR #144

darthbear added a commit that referenced this issue May 1, 2018
Issue 145: backtrack to handle self-references(includes #110). Thank you @aalba6675!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants