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

allow for shallow copies #4093

Merged
merged 13 commits into from Aug 10, 2022
Merged

allow for shallow copies #4093

merged 13 commits into from Aug 10, 2022

Conversation

timkpaine
Copy link
Contributor

@timkpaine timkpaine commented May 19, 2022

Change Summary

Related issue number

Fix #4092.

Checklist

  • Unit tests for the changes exist
  • Tests pass on CI and coverage remains at 100%
  • Documentation reflects the changes where applicable
  • changes/<pull request or issue id>-<github username>.md file added describing change
    (see changes/README.md for details)
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

@timkpaine
Copy link
Contributor Author

please review

@samuelcolvin
Copy link
Member

Thanks so much. I'm just moving house at the moment, but hopefully will find time to review this in the next few days.

@timkpaine
Copy link
Contributor Author

@samuelcolvin no worries! pinning 1.9.0 is a workaround for now (or setting Config.copy_on_model_validation = False, though the behavior is somewhat different).

pydantic/config.py Outdated Show resolved Hide resolved
@samuelcolvin
Copy link
Member

You need to change the base for this PR to 1.9.X-fixes, not master.

@timkpaine timkpaine changed the base branch from master to 1.9.X-fixes May 20, 2022 13:33
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.

Also need to add something to the documentation.

pydantic/config.py Outdated Show resolved Hide resolved
pydantic/main.py Outdated Show resolved Hide resolved
pydantic/main.py Outdated Show resolved Hide resolved
@samuelcolvin
Copy link
Member

At this stage, I think most people have worked around this, I think best we leave behaviour as it is.

@AngellusMortis
Copy link

AngellusMortis commented Aug 8, 2022

We have not worked around it in Home Assistant. Instead, we blacklisted this release since it is a breaking change. Introducing breaking changes in a bugfix revision and then not fixing them is not a good way to handle versioning.

@samuelcolvin
Copy link
Member

I understand this isn't ideal @AngellusMortis, the breaking change wasn't intentional.

As far as I can tell, this change wasn't a simple fix for the original problem, it introduced new settings, also as you mentioned it introduced a new breaking change.

If you (or anyone else) can create a fix for the issue, I'll happily review it and try to get this fixed. Otherwise I'll try to get to it soon.

@AngellusMortis
Copy link

AngellusMortis commented Aug 8, 2022

Even if this PR is canceled, as long as it is fixed is the important part. Introducing a breaking change in a bugfix versions and then not fixing it erodes the trust in the package. It would be good enough reason for me to consider an alternative solution as I do not want to use a package whose response to semver is "oh there was a breaking change, oh well, I am sure people will work around it".

@timkpaine
Copy link
Contributor Author

timkpaine commented Aug 8, 2022

If you (or anyone else) can create a fix for the issue

This PR was a compromise solution that kept the breaking change and added in extra functionality to allow users to revert to the old behavior. Simply changing the functionality back will itself be a breaking change (no doubt released in a patch version), so I recommend not doing this and going with this PR instead.

@samuelcolvin
Copy link
Member

I understand this isn't ideal

Again, I'm really sorry about this. Finding time to work on pydantic when I'm not paid by a big company to do so has been hard, hence I've had to prioritise.

Since I've been working on it full time, I've been mostly concentrating on preparing for V2. One of the big reason to rewrite pydantic from the ground up in V2 is to make the codebase easier to work with so we can reduce the chance of breaking changes in minor or patch releases. Again, we try to avoid breaking changes as much as possible.

@samuelcolvin samuelcolvin reopened this Aug 8, 2022
@samuelcolvin
Copy link
Member

samuelcolvin commented Aug 8, 2022 via email

@peterschutt
Copy link

In preparation for both V1.10 and V2 it would be good to have a list of open source projects which use pydantic where we can test functionality and thereby try to avoid more breaking changes?

One for your list - starlite is an API framework that heavily leverages pydantic.

@samuelcolvin samuelcolvin mentioned this pull request Aug 9, 2022
11 tasks
@samuelcolvin
Copy link
Member

Okay I think this is fixed and ready to be deployed.

@AngellusMortis @PrettyWood please can you review and confirm you're happy?

@samuelcolvin
Copy link
Member

In preparation for both V1.10 and V2 it would be good to have a list of open source projects which use pydantic where we can test functionality and thereby try to avoid more breaking changes?

see #4359, moving discussion to there.

Copy link
Member

@PrettyWood PrettyWood left a comment

Choose a reason for hiding this comment

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

Except the default value and the note looks good! Thank you!

@@ -0,0 +1,3 @@
Allow for shallow copies of attributes, adjusting the behavior of #3642
`Config.copy_on_model_validation` is now a str enum of `["none", "deep", "shallow"]` corresponding to
not copying, deep copy, shallow copy.
Copy link
Member

Choose a reason for hiding this comment

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

My main concern is on the default value

With the test example

class User(BaseModel):
    hobbies: list[str]

my_user = User(hobbies=['scuba diving'])

class Transaction(BaseModel):
    user: User

t = Transaction(user=my_user)

print(t.user is my_user)
print(t.user.hobbies is my_user.hobbies)

we used to have in v1.9.0

copy_on_model_validation = True (default): False, True
copy_on_model_validation = False: True, True

which got changed (the famous breaking change) in 1.9.1

copy_on_model_validation = True (default): False, False <-💥
copy_on_model_validation = False: True, True

We now have with this patch

copy_on_model_validation = 'deep' : False, False
copy_on_model_validation = 'shallow': False, True
copy_on_model_validation = 'none': True, True

I wonder if we should write a note saying a breaking change has been introduced by mistake in a release version and that the old 1.9.0 behaviour has been fixed. In which case, the default value should be 'shallow'.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, we should make the default shallow.

I'll try to fix this today.

Copy link
Member

Choose a reason for hiding this comment

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

default changed.

Copy link
Member

@PrettyWood PrettyWood left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks a lot
@samuelcolvin we'll need to add a note at the start of the release note for this issue and patch

@samuelcolvin samuelcolvin merged commit 1d6a6e6 into pydantic:1.9.X-fixes Aug 10, 2022
@samuelcolvin
Copy link
Member

Thanks for the review, will do.

I'll do the release tomorrow.

samuelcolvin added a commit that referenced this pull request Aug 11, 2022
* allow for shallow copies

* Add changes file

* tweak change

* update for comments

* rename attr

* use single quotes

* bump ci

* add warning if not a string, switch to string literals

* fix linting, prompt ci

* fix ci

* extend and fix tests

* change default to "shallow"

Co-authored-by: Samuel Colvin <s@muelcolvin.com>
samuelcolvin added a commit that referenced this pull request Aug 12, 2022
* generate history from changes, uprev

* Pydantic V2 blog (#4218)

* first draft of pydantic V2 blog

* more blog

* blog rendering and formatting

* more section

* completing conversion table

* prompt build

* reviewing blog post

* more reviewing and extending

* recommendations from @Rabscuttler and @PrettyWood

* add implementation details and more suggestions

* comment about breaking changes

* convert namespae to table, more removals

* Apply suggestions from code review by @tiangolo

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>

* feedback from @tiangolo's review

* changes from @adriangb's review

* Apply suggestions from code review

Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>

* convert namespace info to psuedo-code

* rename property, remove schema_json()

* adding validation context

* remove 'model_schema_json', take 2

* more tweaks while reviewing

* comment about pypy and tagged unions

* add thanks :prey:, prepare for release

* suggestions from @PrettyWood

* suggestions from @PrettyWood, model_dump_json comment

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>

* comments mostly from @PrettyWood (#4226)

* comments mostly from @PrettyWood

* add updated comment

* fix pre-commit

* allow for shallow copies (#4093)

* allow for shallow copies

* Add changes file

* tweak change

* update for comments

* rename attr

* use single quotes

* bump ci

* add warning if not a string, switch to string literals

* fix linting, prompt ci

* fix ci

* extend and fix tests

* change default to "shallow"

Co-authored-by: Samuel Colvin <s@muelcolvin.com>

* uprev and prepare for release

* linting

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
Co-authored-by: Tim Paine <t.paine154@gmail.com>
@NunchakusLei
Copy link

Looks like this PR reproduce the issue #3641

from pydantic import BaseModel
from typing import List


class A(BaseModel):
    name: str
        

class B(BaseModel):
    list_a: List[A]


a = A(name="a")
b = B(list_a=[a])

print(a)
print(b)
a.name = "b"  # make a change to variable a's field
print(a)
print(b)

Output in 1.6.2, 1.7.4, 1.8.1, 1.8.2, and 1.9.1,

A(name='a')
B(list_a=[A(name='a')])
A(name='b')
B(list_a=[A(name='a')])

Output in 1.9.0 and 1.9.2,

A(name='a')
B(list_a=[A(name='a')])
A(name='b')
B(list_a=[A(name='b')])

Is this the expected change of behaviour from previous version to v1.9.x?

@timkpaine
Copy link
Contributor Author

timkpaine commented Aug 19, 2022

@NunchakusLei yes, this PR restores state to some combination of 1.9.0 and 1.9.1 behavior, but lets you pick between them.

from the docs:

Allow for shallow copies of model fields, Config.copy_on_model_validation is now a str which must be 'none', 'deep', or 'shallow' corresponding to not copying, deep copy & shallow copy; default 'shallow',

So you'll want deep to restore the (erroneously changed) 1.9.1 behavior

@samuelcolvin
Copy link
Member

See #4369, I need to update the docs.

@NunchakusLei
Copy link

NunchakusLei commented Aug 19, 2022

@NunchakusLei yes, this PR restores state to some combination of 1.9.0 and 1.9.1 behavior, but lets you pick between them.

from the docs:

Allow for shallow copies of model fields, Config.copy_on_model_validation is now a str which must be 'none', 'deep', or 'shallow' corresponding to not copying, deep copy & shallow copy; default 'shallow',

So you'll want deep to restore the (erroneously changed) 1.9.1 behavior

Do you know if the 1.8.2 and previous are equivalent to deep behaviour? That's the version behaviour I try to restore to.

@timkpaine
Copy link
Contributor Author

pre 1.9.1 was default shallow or "none", 1.9.1 was default deep or none, 1.9.2 is none/deep/shallow

@NunchakusLei
Copy link

@timkpaine Base on my test #4093 (comment), pre 1.9.0 was default deep or none (tested in 1.6.2, 1.7.4, 1.8.1, and 1.8.2).

The test result conflicting the statement of,

pre 1.9.1 was default shallow or "none" ...

I got very confuse now so want to clarify.

@samuelcolvin
Copy link
Member

On my phone but I think:

  • Pre 1.9.0: default SHALLOW, config setting for NONE
  • 1.9.0, 1.9.1: default DEEP, config setting for NONE
  • 1.9.2: default SHALLOW, config settings for NONE or DEEP

@NunchakusLei
Copy link

@samuelcolvin

  • Pre 1.9.0: default SHALLOW, config setting for NONE

This doesn't matched my test result in 1.6.2, 1.7.4, 1.8.1, and 1.8.2. #4093 (comment)

@timkpaine
Copy link
Contributor Author

@NunchakusLei your tests results might be wrong, but also...it doesnt really matter at this point, all behaviors are supported now.

@samuelcolvin
Copy link
Member

@samuelcolvin

  • Pre 1.9.0: default SHALLOW, config setting for NONE

This doesn't matched my test result in 1.6.2, 1.7.4, 1.8.1, and 1.8.2. #4093 (comment)

See: https://github.com/pydantic/pydantic/blob/v1.8.2/pydantic/main.py#L731 pretty sure I'm correct.

@efisoft-elt
Copy link

efisoft-elt commented Feb 24, 2023

class M(BaseModel):
    class Config:
        copy_on_model_validation = "!! Some non sens str !!"

Does not raise any error.

This is probably due to the fact that deprecation warning is handled.

            if copy_on_model_validation not in {'deep', 'shallow', 'none'}:
                # Warn about deprecated behavior
                warnings.warn(
                    "`copy_on_model_validation` should be a string: 'deep', 'shallow' or 'none'", DeprecationWarning
                )
                if copy_on_model_validation:
                    deep_copy = False

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

Successfully merging this pull request may close these issues.

None yet

7 participants