-
Notifications
You must be signed in to change notification settings - Fork 116
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
Introduce new oc.env
and oc.decode
resolvers
#606
Conversation
Marked as draft as I want to change |
Didn't look at the code yet, but a question: |
Oh, yes, that's a good point. As far as I am concerned this is intended, however this is a bit tricky to explain in the documentation because currently the fact that dict/list outputs are converted to DictConfig/ListConfig is documented after the default resolvers. Any objection to moving things around? (i.e, first explain what resolvers are and how they behave, then talk about |
Two points:
|
I requested review but that's for the code only -- doc still needs updating according to ongoing discussion Edit: doc has been updated (just the notebook will need to be synced to the doc once more) |
Sure. I didn't review the code yet either. Will look tomorrow. |
If I'm not mistaken, right now the only way for someone to have an object in the config that isn't compatible with OmegaConf is by setting the class A:
...
OmegaConf.clear_resolvers()
OmegaConf.register_resolver("test", lambda: [A()])
c = OmegaConf.create({"x": "${test:}"})
c._set_flag("allow_objects", True)
assert isinstance(c.x[0], A) It is true that in OmegaConf 2.0 a resolver could output anything which would get wrapped in a
Ok, though technically it's hiding what's really happening in practice: |
allow_objects is not something I want to push everywhere. What I have in mind here is to add a flag when registering the resolver that would qualify it's behavior as converting or not converting the resulting containers to corresponding OmegaConf containers. The distinction you are drawing about an intermediate resolver is something I want some clarification about:
In my view, the second should be controlled by a flag when registering the resolver. |
I gave it a shot in d1229d6 (doc update). The notebook will also need to be updated but I'll wait until we settle on the documentation first. |
Just to be clear, in that case, would the resulting node (wrapping the output) be a
What I mean is that (2) (and actually also (1), but let's ignore it) happens only at the very last step of interpolation resolution: we look at the final result, and if it's a dict/list, then we convert it into an OmegaConf container. But the output of an intermediate resolver is fed unchanged (*) as input to the next resolver in the chain. So in my example (*) actually this is incorrect (mentioning it for completeness, even if it doesn't matter in my example): we call |
Thinking more about it, maybe it's better to change this behavior and systematically convert the ouptut of a resolver from dict/list to DictConfig/ListConfig, even for intermediate computations. The main advantage I see is that it is simpler to understand. In that case having a flag to control whether or not this conversion occurs is even more important. So here is a suggestion, @omry please let me know if you'd prefer something different:
|
Wait with any changes related to it, have some other ideas which might change the direction of this discussion. will comment later. |
I realized that now that we support passing the OmegaConf.register_resolver("dict", lambda: {"a": 10})
OmegaConf.register_resolver("dictconfig", lambda _parent_: DictConfig({"a": 10}, parent=_parent_))
cfg = OmegaConf.create({
"d" : "${dict:}",
"dc" : "${dictconfig:}"
})
assert type(cfg.d) is dict
assert type(cfg.dc) is DictConfig I did not try to use We still need to think what this means for type based validation/conversion:
|
True (I kinda liked the automatic conversion though, it seemed natural and more straightforward -- but maybe explicit is still better) Assuming we go with this option, we still need to decide what type of node should wrap a plain dict/list (or any other non-standard object). Ok with AnyNode with
I don't think we should change the current type based validation/conversion: this mechanism has to happen as the very last step (it doesn't make sense to convert the ouptut of an intermediate resolver based on the node type), and it applies both to resolver interpolations and node interpolations (so I don't think resolver should worry about it). |
We can introduce a flag when registering the resolver that would do it automatically on demand if this turns out to be a common pattern. I think being explicit is better because it reduces surprises.
Why do you need to wrap it at all?
Yes, I tend to agree. |
I guess for compatibility with regular interpolations that are returning a Node. |
Generally speaking, |
AnyNode with allow_objects=True works fine then. |
Ok sounds good. I'm planning to do this in a follow-up PR that will:
|
I think oc.create could make sense. this is a parallel to OmegaConf.create(). |
Sounds like a plan! |
docs/source/usage.rst
Outdated
@@ -6,6 +6,7 @@ | |||
import tempfile | |||
import pickle | |||
os.environ['USER'] = 'omry' | |||
os.environ['USERID'] = '123456' |
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.
I think it's enough to just say that environment variables are always returned as strings without showing an actual example.
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.
Removed in c6a7c86
docs/source/usage.rst
Outdated
>>> cfg = OmegaConf.create({ | ||
... 'database': {'password': '${env:DB_PASSWORD,abc123}'} | ||
... 'database': {'password': '${oc.env:DB_PASSWORD,abc123}'} | ||
... }) |
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.
You can use this to drive the point about quoting:
cfg = OmegaConf.create(
{
"database": {
"password1": "${oc.env:DB_PASSWORD,abc123}", # the string 'abc123'
"password2": "${oc.env:DB_PASSWORD,'12345'}", # the string '12345'
},
}
)
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.
Done in e63e669
tests/test_interpolation.py
Outdated
cfg["env_func"] = env_func # allows choosing which env resolver to use | ||
cfg = _ensure_container(cfg) | ||
|
||
# The legacy env resolver triggers a deprecation warning. |
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.
ditto. add one test for directly testing the deprecation warning and ignore the warnings everywhere else.
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.
side note, we should probably split test_interpolation into something like test_simple_interpolations.py and test_custom_resolvers.py.
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.
ditto. add one test for directly testing the deprecation warning and ignore the warnings everywhere else.
Done in a973d21 (I didn't add a new test since there are a couple of tests specific to the legacy env that still explicitly catch that warning, doesn't seem worth doing more refactoring since they will all go away in 2.2)
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.
side note, we should probably split test_interpolation into something like test_simple_interpolations.py and test_custom_resolvers.py.
Noted, will do in a follow-up PR
docs/source/usage.rst
Outdated
>>> def show(x): | ||
... print(f"type: {type(x).__name__}, value: {x}") |
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.
We can probably add this function to the top of the testsetup or somewhere else early on.
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.
I moved it up and used it in more places in e090055
I didn't put it in testsetup because it doesn't appear in the doc and people may wonder exactly what is this function.
I added a bunch of comments on intermediate diffs. be sure to check them as well. |
request review when ready. |
They appeared in the review as far as I can tell. |
Co-authored-by: Omry Yadan <[email protected]>
Co-authored-by: Omry Yadan <[email protected]>
Co-authored-by: Omry Yadan <[email protected]>
Co-authored-by: Omry Yadan <[email protected]>
8862343
to
4800df9
Compare
Funny, I just rebased on top of master without conflict... Just force-pushed. |
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.
Another one bites the dust!
Some doc updates from omry#606 were not fully ported to the notebook.
Some doc updates from #606 were not fully ported to the notebook.
Restore and deprecate the old
env
resolver for backwardcompatibility with OmegaConf 2.0
The new
oc.env
resolver keeps the string representation ofenvironment variables, and does not use the cache
The new
oc.decode
resolver can be used to parse and evaluate stringsaccording to the OmegaConf grammar
Fixes #383
Fixes #573
Fixes #574
Notes: