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
Propagate environment during recursive import/export/validation #359
Conversation
env = ConfigObject() | ||
elif not isinstance(env, ConfigObject): | ||
env = ConfigObject(env) | ||
env._setdefault('partial', partial) |
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 makes more sense to have values passed as arguments overwrite those in env
.
Having two ways to specify options is confusing. But I understand you don't want to break backward compatibility.
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.
Hmm. There may be some misunderstanding here. For the most part, options are supposed to be specified just like before:
foo.import_data({...}, strict=True, partial=True)
This PR does not intend to change that.
An application may also pass an env
object, but there are only two reasons to do so:
- you want to put some proprietary data in it
- your custom type or
Model
class wants to change the parameters in the middle of the recursive import/export process and propagate the new settings from there on.
What this code does is it sets up the env
container on the first invocation, and then the options will be frozen thereafter. Letting arguments overwrite the prior state on every call is precisely the problem this PR aims to fix.
Here's an example of how a call stack might look like before:
Model.__init__({...}, strict=True)
Model.convert({...}, strict=True)
convert({...}, strict=True)
import_loop({...}, strict=True)
ModelType.to_native(...)
Model.import_data(...)
Model.convert(...)
convert(...)
import_loop(...)
Okay, back at import_loop
. What's the value of strict
? It's False
, set by convert()
at step 7. But obviously it should be True
, since that's what was originally specified.
There were two ways to fix this:
- make sure every function throughout the chain propagates every possible parameter as an argument, or
- encapsulate the settings in a single object in the beginning and pass that around instead.
I opted for the latter approach because it's way cleaner and you can now add new options without touching everything.
If there's something that was easy to do before but becomes awkward due to this change, I'm sure we can address it somehow.
Okay, I think I get it now about Fair enough, but in that case "native" is not a well-defined concept. Alternatively, Resolving what "native" means is out of scope for this PR, so I'm going to shelve the fifth commit. |
I like the cleanup idea proposed and the patch as is seems good. Do you have an idea how this could affect execution performance? What's the tradeoff in overhead/ease of use of passing ConfigObjects instead of dicts or maybe namedtuples? (this is just a question, don't block this patch acceptance based on it - I think performance should be a next goal for schematics as a whole). |
Great idea about Aside from being faster, (Whenever However, using In general I totally agree about the performance concerns. It would be great if someone put together a testcase for trying out some optimization ideas. |
I see no problem in re-purposing the |
Now using >>> from schematics.transforms import ExportContext
>>> ExportContext(role='public')
ExportContext(role='public', raise_error_on_role=None, print_none=None, app_data=None) Added In a hypothetical params = {}
if self.strict is not None:
params['strict'] = self.strict
if self.partial is not None:
params['partial'] = self.partial
branched_env = env._replace(**params) if params else env
return model.import_data(value, env=branched_env) can now be expressed as params = {
'strict': self.strict,
'partial': self.partial
}
return model.import_data(value, env=env._branch(**params)) |
c647fb7
to
98ae7e9
Compare
Although the diff is all over the place, this is a rather straightforward PR, so I'm preparing to merge. Last call regarding nomenclature: (1) After @lkraider alleviated my concerns about reusing the retired
(2) The old Thoughts? If we make the switch from
|
Can you list the flow of options/context now? For example, how will something like |
297d6e7
to
5427dde
Compare
Taken literally, that form (a user-provided dict as (EDIT: Actually no, it can't be removed because the same methods are also called from Other than that, This now makes things very simple: up until the first invocation of The only case where a context object needs to be created from scratch is when writing tests for a method that needs one: mls = MultilingualStringType(default_locale='en_US')
assert mls.to_primitive(
{'en_US': 'snake', 'fr_FR': 'serpent'},
context=ExportContext(app_data={'locale': 'fr_FR'})) == 'serpent'
^^^^^^^^^^^^^ |
Thanks for clearing it up, sounds great for me. |
This patch introduces a metadata object that is propagated through every level of a recursive import/validation process. It is used to carry options and other data that need to be available throughout the entire process, not just during the outermost loop.
* Add BaseType.parent_field * Remove BaseType.validate_required() (see MultiType.__init__()) * Remove ModelType.strict * Move ModelType.validate_model()
To simplify the creation of dummy env objects, use a modified namedtuple whose constructor sets missing fields to None.
I believe this PR is now mature enough to be merged. It resolves #316 and facilitates the addition of new conversion options in the future without causing disruption to existing methods. |
Propagate environment during recursive import/export/validation
@bintoro hi, when is this change going to be released on PyPI? |
@chekunkov I'm not sure. The patch is technically backward-incompatible, since all If we want to be really strict about semantic versioning, this would have to wait until v2.0-alpha. If we accept mildly breaking changes in a minor release, this could be shipped pretty quickly in v1.2.0. I haven't decided. |
Just FYI, I've decided to include this patch in v1.2.0 that will be released shortly. I'm going to alleviate upgrading pains by making the |
I'm curious what the state of the 1.2 release is? |
I know that I'm using a 2.0 pre-release personally. You might wish to try it yourself, to see if this feature is in that, and if it works for your cases. |
A rewrite of #266.
This is a huge PR, but it's split into meaningful commits, each of which represents a working state with all tests passing.
env
is now always passed to allto_primitive
,to_native
, andexport_loop
methods on types. Consequently, the standardfield_converter
interface also hasenv
as a mandatory component:field_converter(field, value, env)
.The
env
object will be set up by eitherimport_loop
orexport_loop
during the first iteration if the application hasn't supplied it by then.context
The overloading of "context" with a number of uses has been resolved as follows:
context
inimport_loop()
andvalidate()
renamed totrusted_data
context
as an argument toto_native()
,to_primitive()
,validate()
, andmock()
superseded byenv
context
as an argument toexpand
intransforms.py
renamed toexpanded_data
context
when accessing the private context replaced byenv.context
.Basically,
env.context
is just a recommendation for establishing a private namespace. Apparently the only thing in the library that accessesenv.context
isMultiLingualString
that looks for a locale there.export_loop
inspection gone[DELETED]
Miscellaneous changes
ModelType.strict
option removed for now. To support something like this, we would need to differentiate between at least two flavors of settings:ModelType
definitions), where later (as in deeper level of recursion) overrides earlierI suspect
ModelType.strict
may have been a band-aid to provide some degree of control where the runtimestrict
setting hasn't been doing anything.BaseType.validate_required()
removed.import_loop
enforcesrequired=True
for fields directly on a model, so this was only needed for nested fields. They now get this validator inMultiType.__init__()
as needed.Validators
env
is now passed to all validators, including model-level validators. Although the object is absolutely needed only in compound type validators, it's probably best to make the change everywhere at once, since appendingenv
to the argument list is all that is needed to adapt existing code.