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

Refactored extra types to use a single enum #352

Merged
merged 28 commits into from Feb 4, 2019

Conversation

liiight
Copy link
Contributor

@liiight liiight commented Jan 7, 2019

Change Summary

Use a single value to describe allowing extra attributes or ignoring them, by using an enum:

class ExtraAttributes(Enum):
    DISALLOW_MUTATION = auto()
    ALWAYS_DISALLOW = auto()
    MUTATION_ONLY = auto()
    ALWAYS_ALLOW = auto()

    def allow_mutation(self):
        return self in (self.MUTATION_ONLY, self.ALWAYS_ALLOW)

    def should_ignore(self):
        return self in (self.DISALLOW_MUTATION, self.MUTATION_ONLY)

Related issue number

#351

Checklist

  • Unit tests for the changes exist
  • Tests pass on CI and coverage remains at 100%
  • Documentation reflects the changes where applicable
  • HISTORY.rst has been updated
    • if this is the first change since a release, please add a new section
    • include the issue number or this pull request number #<number>
    • include your github username @<whomever>

@liiight
Copy link
Contributor Author

liiight commented Jan 7, 2019

Still need to add new unit tests, docs and deprecation warning but I wanted to get early feedback.

@codecov
Copy link

codecov bot commented Jan 7, 2019

Codecov Report

Merging #352 into master will not change coverage.
The diff coverage is 100%.

@@          Coverage Diff          @@
##           master   #352   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          14     14           
  Lines        1910   1934   +24     
  Branches      370    375    +5     
=====================================
+ Hits         1910   1934   +24

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

@samuelcolvin about the failing test:

def test_allow_extra_deprecated():
    """Will be removed in a future release"""

    class Model(BaseModel):
        a: float = ...

        class Config:
            allow_extra = True

    assert Model(a='10.2', b=12).dict() == {'a': 10.2, 'b': 12}

I'm not sure how this passed before since the default of ignore_extra is True. Shouldn't it have just ignored b?

@samuelcolvin
Copy link
Member

allow_extra is True, not ignore_extra...

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

ignore_extra: whether to ignore any extra values in input data (default: True)

If the default of ignore_extra is True and allow_extra is True, doesn't that mean that extra attributes will be ignored on init but allowed on mutation?

@samuelcolvin
Copy link
Member

No, see #345 which is merged but not yet deployed.

They both apply on init.

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

Ah, I see. So to better reflect this in my logic:

ignore_extra = True
allow_extra = False

Ignore on init, fail on mutate

ignore_extra = False
allow_extra = False

Always fail on extra

ignore_extra = False
allow_extra = True

Always allow

ignore_extra = True
allow_extra = True

Also always allow.

Correct?

If so, that's kinda weird. Are you sure you want to keep this behaviour like this?

pydantic/main.py Outdated
@@ -19,14 +20,26 @@
from .validators import dict_validator


class ExtraAttributes(Enum):
DISALLOW_MUTATION = auto()
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should have different behaviour for mutation and init, Just ignore and allow should suffice.

Copy link
Member

Choose a reason for hiding this comment

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

also, please use lower case.

Copy link
Member

Choose a reason for hiding this comment

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

sorry, ignore, allow and disallow.

But since we're making a change, maybe ignored, allowed and forbidden would be clearer.

pydantic/main.py Outdated
@@ -19,14 +20,26 @@
from .validators import dict_validator


class ExtraAttributes(Enum):
Copy link
Member

Choose a reason for hiding this comment

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

IntEnum please and no need for auto. 1 and 2 should be fine.

That way people aren't forced to import to use this.

Copy link
Member

Choose a reason for hiding this comment

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

On reflection, maybe better to use (str, Enum) and have values of allowed, ignored and forbidden - should be clearer for people to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean like this?

class Extra(str, Enum):
    allowed = 'allowed'
    ignored = 'ignored'
    forbidden = 'forbidden'

Copy link
Member

Choose a reason for hiding this comment

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

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, how is that exactly used?

pydantic/main.py Outdated
@@ -402,28 +415,54 @@ def validate_model(model, input_data: dict, raise_exc=True): # noqa: C901 (igno
"""
validate data against a model.
"""
def _deprecated_values(config) -> bool:
"""
Copy link
Member

Choose a reason for hiding this comment

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

No need for a separate method, also move the depeciation logic to the __new__ method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure where you mean

@@ -179,7 +179,9 @@ class Model(BaseModel):
assert Model().c == 0


def test_allow_extra():
def test_allow_extra_deprecated():
"""Will be removed in a future release"""
Copy link
Member

Choose a reason for hiding this comment

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

please capture warnings and check check they're right.

@samuelcolvin
Copy link
Member

Mutation and init should have the same (or equivalent) behaviour:

  • allowed - extra fields can be included (or set) without any validation
  • ignored - all extra values/fields/attributes are ignored on init and on set
  • forbidden - any extra values/fields/attributes on init or set raises an error

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

Mutation and init should have the same (or equivalent) behaviour:

  • allowed - extra fields can be included (or set) without any validation
  • ignored - all extra values/fields/attributes are ignored on init and on set
  • forbidden - any extra values/fields/attributes on init or set raises an error

I like that, but this changes the default behaviour. You want it to be ignore or forbidden?

@samuelcolvin
Copy link
Member

Default should be ignore, I believe that's roughly the same as currently.

If there are changes to current behaviour, just include "breaking change" in history.

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

If we break behaviour then let's get rid of allow_extra and ignore_extra all together instead of trying to be backwards compatible, no?

I vote for this option BTW, the old behaviour was kinda confusing, which is the motivation of this. This new behaviour is way more clearer and explicit.

@samuelcolvin
Copy link
Member

I don't think we do break current behaviour with the new setting as ignore. Can you give an example of a change in behaviour?

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

ignore_extra = True
allow_extra = False

This condition which would ignore on init but fail on mutate would not be supported anymore.

@samuelcolvin
Copy link
Member

Ok, but that's a more subtle change than just removing ignore_extra and allow_extra completed.

Please keep those settings but with a deprecation warning, and add extra.

@liiight
Copy link
Contributor Author

liiight commented Jan 8, 2019

So what's the expected behaviour in that scenario? Should it correlate to ignore or forbidden?

@samuelcolvin
Copy link
Member

You don't need to change the current behaviour for allow_extra, just add the new setting.

If extra is ignored or forbidden __setattr__ should raise an error if you try to sett a missing attribute.

@samuelcolvin
Copy link
Member

Probably easier at this stage to commit to your PR than try to explain what I mean in comments.

@liiight
Copy link
Contributor Author

liiight commented Jan 9, 2019

sorry about the formatting

@liiight
Copy link
Contributor Author

liiight commented Jan 9, 2019

guess you took it over completely...

docs/index.rst Outdated
:allow_extra: whether or not to allow (and include on the model) any extra values from input data;
when ``allow_extra`` option is set to ``True`` it overrides ``ignore_extra`` (default: ``False``)
:extra: whether to ignore, allow or forbid extra attributes in model. Can use either string values of ``ignore``,
``allow`` or ``forbidden``, or use ``Extra`` enum (default is ``Extra.ignore``)
Copy link
Member

Choose a reason for hiding this comment

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

Currently, the values are ignored, allowed or forbidden.

However looking at it again ignore, allow and forbid might make more sense and would be closer to the previous usage. Choice is yours.

@samuelcolvin
Copy link
Member

It was my intention from the start to do history and docs so I'll do that.

Thanks a lot, much appreciated.

This approach on your part left me with a bitter taste and I doubt I will contribute anything else to this project.

I quite understand that and I'm very sorry. That wasn't my intention and I agree with you that welcoming first-time contributors is key to the success of open source projects, I hope you can see that from #308, #341, #320 or #179.

This is not a criticism but rather an explanation for why I rather took over: very open-ended questions like "Not sure where you mean" and "Hmm, how is that exactly used?" frustrated me slightly and made me think that explaining what I meant was going to take a very long time, I assumed (perhaps wrongly) that you either didn't have very much experience or hadn't taken long to look into it yourself. I then decided to move the core logic into __new__ since that was going to take a lot of explaining, by the time I had then fixed tests I agree it looked rather like I'd done all the fun bits.

I also think that contributing to pydantic could be made easier with some documentation, for example, you might not have been aware of make format to fix formatting and simple make to run all tests and linting, which would have saved a few commits.

Again, sorry not to be more welcoming and thank you very much for your contribution.

@samuelcolvin
Copy link
Member

You've missed renaming in a few places, also linting is failing.

If it's a pain to fix, I'll do it in a couple of days.

@liiight
Copy link
Contributor Author

liiight commented Jan 11, 2019

I plan to fix it, not feeling too hot these days. If it can wait a few days, I'll do it then. Also, I plan to respond to your last comment, but like I said, I'm too sick now...

@samuelcolvin
Copy link
Member

great.

@liiight
Copy link
Contributor Author

liiight commented Jan 15, 2019

About your reply: I can understand now that my questions were too open ended like you said, and perhaps were better suited in an ongoing discussion in chat room instead of comments to a PR. I am by no means a new python developer (you can browse my Github profile) but I admit that I was relying more on you answering my question than really trying to dig in the code, and I also take responsibility about that.

I can also suggest that other than the suggestion you already made to improve first time contributions, that you also add docstrings, at the very least in crucial parts of the logic, like the metaclass and other non trivial sections of the code.

Anyway, I accept your apology and take responsibility in my part of the "problem".

@samuelcolvin
Copy link
Member

Hi @liiight, any change you could fix this? It'd be great to get it merged.

If it's a pain, I'll happily do it in a day or two.

@liiight
Copy link
Contributor Author

liiight commented Jan 26, 2019 via email

@samuelcolvin
Copy link
Member

To avoid this PR having loads of conflicts, I'm waiting to merge it before merging type hints #373. Unfortunately, that means this is delaying a big improvement to pydantic.

Normally in this situation, I'd take over and get the PR finished. However given ⬆️, I'm a little 😨 😨 😨 👻 about doing that.

@liiight therefore please finish this or let me know if you'd like me to finished it.

Again thank you for your contribution and appologies for nagging. 🙏

@liiight
Copy link
Contributor Author

liiight commented Feb 3, 2019

I apologize for the delays and I oblige to finish it within the next 24 hours

@samuelcolvin
Copy link
Member

thanks.

@liiight
Copy link
Contributor Author

liiight commented Feb 4, 2019

Looks done to me @samuelcolvin

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

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

Tiny change you can just accept, then I'll merge.

pydantic/main.py Outdated Show resolved Hide resolved
Co-Authored-By: liiight <4374581+liiight@users.noreply.github.com>
@samuelcolvin samuelcolvin merged commit 9900c7f into pydantic:master Feb 4, 2019
@samuelcolvin
Copy link
Member

awesome. Thank you very much.

@liiight
Copy link
Contributor Author

liiight commented Feb 4, 2019

Sure. Let's hope the next PR wont be this difficult 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants