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
schema_obj: refactor _search() #1222
Conversation
There's no reason to store everything as a string - it seems simpler to keep everything as their native Python type. The only type that changes when converting to/from JSON are tuples, which get converted to lists. We can keep them as lists in the schema for the sake of normalization.
Basic idea: make _search() a low-level traversal function, and encode the distinct logic of each accessor (set/get/add etc) within those functions themselves. Adds a few helper functions to factor out other shared functionality.
Eventually these warnings should be pushed back down into the schema_obj messages once we have a static logger.
There were a couple bugs in the test: - Attempt to set list to None - Incorrect keypath But this also revealed a bug in the schema refactor: - Shouldn't allow None for lists
These values are set by the processes spawned by each node in _runtask(), and get merged into the main manifest with clobber=False. Therefore, we have to clear the set flag so they aren't dropped during the merge.
When dumping a manifest, some paths will not be able to be resolved, and find_files() will return lists with "None" entries. This seemed to work okay with the old set() method, but violates our new typechecks. In these cases the behavior was for the paths to not get included in the manifest, so it seems okay to write an empty list instead.
This matches the behavior prior to #1147
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.
Changes look good, I like the simplification of the type checking. If we keep set
you will need to update the schema version.
@gadfort Thanks for the review! I'll fix up the various messages per your suggestions. RE: >>> chip.set('metric', 'syn', '0', 'errors', None)
>>> chip.set('metric', 'syn', '0', 'errors', 0, clobber=False)
# Fails! However, in retrospect this is probably counterintuitive. I'd be open to making I'm also thinking it might be reasonable to fall back to the default value in the case when a user sets >>> chip.get('option', 'quiet')
False
>>> chip.set('option', 'quiet', True)
>>> chip.set('option', 'quiet', None)
False # (not None!) Then that just leaves the question of whether empty list should be a similar sentinel for list-types, or if empty lists should be considered a value distinct from a |
Not sure I fully grasp the set vs sentinel vs clobber tradeoffs here, especially as it relates to bool. Setting a parameter that we have specified as type bool to None per your example seems confusing to me. Same thing goes for lists. Is the idea to intercept a special "None" value entered through set and take some action. How do we distinguish from someone who actually wants to set a value to None? print(type(None)) <class 'NoneType'> |
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.
Nice simplification! As a general comment, should we consider using SiliconCompilerError
in any of the places where TypeError
/ ValueError
is used?
Pro: Easy to catch in build scripts.
Con: Could accidentally swallow errors, and the Schema object is sort of logically separate from runtime SC errors.
if value == 'true': return True | ||
if value == 'false': return False |
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.
if value in ('true', 'True')
? Ditto for false?
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'm thinking of keeping as-is to maintain parity with our current API, which only supports lower-case "true"/"false" for strings. However, I'd be open to changing this if others think it's a good idea.
@WRansohoff good points RE: exceptions. I think tweaking how they work in a future PR could be reasonable. Some of my thinking about how things should work is captured here: #1085. I think ultimately we should have |
@gadfort I think I've addressed all the feedback! At a high level:
|
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.
just down to the debug vs. warning. If it's debug else where maybe we should leave it and make a note to look at them later.
# TODO: this message should be pushed down into Schema.set() | ||
# once we have a static logger. | ||
if clobber: | ||
self.logger.debug(f'Failed to set value for {keypath}: ' |
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.
should this be a warning or error instead of debug?
self.logger.debug(f'Failed to set value for {keypath}: ' | ||
'parameter is locked') | ||
else: | ||
self.logger.debug(f'Failed to set value for {keypath}: ' |
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.
same
self.logger.debug(f'Clearing {keypath}') | ||
|
||
if not self.schema.clear(*keypath): | ||
self.logger.debug(f'Failed to clear value for {keypath}: parameter is locked') |
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.
same
if not self.schema.add(*args, field=field): | ||
# TODO: this message should be pushed down into Schema.add() | ||
# once we have a static logger. | ||
self.logger.debug(f'Failed to add value for {keypath}: ' |
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.
same
Seems to be a new Yosys setup issue that it flags, possibly because the test setup doesn't align well with tool/task split changes.
This PR refactors our monolithic
_search()
function to make the code clearer, fix some latent bugs, improve error messages, and, most relevantly, facilitate adding special handling for global values with step/index overrides.This PR is meant to be (mostly) a clean refactor with no change to user-facing functionality. However, there are some differences in behavior described below.
First, a couple key design decisions made in this PR:
cfg
dictionary now stores native Python types, rather than just strings. As far as I can tell, there's no reason to store values as strings -- all the Python types used are serializable to/from JSON (with the exception of tuples, which get serialized as lists). Using native Python types makes the code much clearer._search()
is no longer a monolithic function. I kept the name around, but the new_search()
is a simple traversal function. The actualset()
/get()
/add()
, etc. functionality is now written in those functions. They make use of a variety of new internal helper functions for shared functionality. I think this approach is much clearer.Differences in behavior
get()
no longer creates keypathsI noticed that with the old
_search()
,get()
-ing a keypath with unset free keys actually adds keys to the cfg dictionary:It seemed a little odd to me that
get()
could have this side effect. I've changed the behavior in this PR to allow it to keep descending through the defaults (so you can recover a default value), but the dictionary doesn't actually get created. New behavior:Can no longer set lists to
None
I think
set()
previously allowed you to provideNone
for a list value. This PR changes that behavior to cause it to fail the type check, since we've generally been using[]
as the equivalent null value for list types. However, I'd be open to changing this behavior to acceptNone
for list types, and have it get normalized as[]
in the schema.Add 'set' flag for clobber
This PR adds another flag, 'set', to parameters to indicate whether their value has been set by a user yet. Previously, clobber operated based on whether the value was of an "empty" type (generally equivalent to False-y), but this caused unintuitive behavior if a value was explicitly set to an "empty" value (#1146). Adding the "set" flag allows us to determine whether a value has actually been exlicitly set by user code before
Closes #1146, #1188