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

Add deprecated marker for fields in a model #2255

Closed
4 tasks done
gautamsinghania95 opened this issue Jan 11, 2021 · 16 comments
Closed
4 tasks done

Add deprecated marker for fields in a model #2255

gautamsinghania95 opened this issue Jan 11, 2021 · 16 comments
Labels
feature request help wanted Pull Request welcome
Milestone

Comments

@gautamsinghania95
Copy link

gautamsinghania95 commented Jan 11, 2021

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this feature/change is needed
  • After submitting this, I commit to one of:
    • Look through open issues and helped at least one other person
    • Hit the "watch" button on this repo to receive notifications and I commit to help at least 2 people that ask questions in the future
    • Implement a Pull Request for a confirmed bug

Feature Request

Pydantic Fields should have a boolean option deprecated (default False) which can be used to mark deprecated fields. Pydantic plugin for Pycharm can also use this field to see if the field should be used any longer, and show code hints if someone is trying to interact with deprecated fields.

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

$ python -c "import pydantic.utils; print(pydantic.utils.version_info())"
             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: ...
               python version: 3.6.8 |Anaconda, Inc.| (default, Dec 29 2018, 19:04:46)  [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
                     platform: Darwin-19.6.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']

Sample Code Snippet

from pydantic import BaseModel, Field

class MyModel(BaseModel):
     oldField: Optional[int] = Field(None, deprecated=True, description="Use `newField` instead of this")
     newField: Dict[str, int] = Field({}, description="New field to accept string to key values")
@gautamsinghania95 gautamsinghania95 changed the title Add deprecation marker for fields in a model Add deprecated marker for fields in a model Jan 11, 2021
@ssbarnea
Copy link

I would find this extremely useful as I often encounter usage that is still accepted but discouraged in favour of other approaches.

@lsorber
Copy link

lsorber commented Mar 10, 2021

Doesn't this work out of the box? The Field docs explain that any keyword arguments are added verbatim to the field's schema.

@gidven
Copy link

gidven commented Mar 10, 2021

As per @lsorber, the schema gets updated with keyword arguments like deprecated when added to the Field() function:

from pydantic import BaseModel, Field
from typing import Optional, Dict

class MyModel(BaseModel):
    oldField: Optional[int] = Field(
        None, deprecated=True, description="Use `newField` instead of this"
    )
    newField: Dict[str, int] = Field(
        {},
        description="New field to accept string to key values",
        my_keyword_arg="my_keyword_value",
    )

print(MyModel.schema_json(indent=2))
{
  "title": "MyModel",
  "type": "object",
  "properties": {
    "oldField": {
      "title": "Oldfield",
      "description": "Use `newField` instead of this",
      "deprecated": true,
      "type": "integer"
    },
    "newField": {
      "title": "Newfield",
      "description": "New field to accept string to key values",
      "default": {},
      "my_keyword_arg": "my_keyword_value",
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      }
    }
  }
}

@dominikandreas
Copy link

Before stumbling upon pydantic, I've implemented something very similar (not as awesome as pydantic of course), but with this deprecation feature and I found this extremely useful. Now we'd like to migrate to pydantic and I'm looking for a way to reproduce this behavior.

Specifically, we had the following features:

  • Deprecation warn version and remove version: Depending of the current schema version, it would either print a warning at construction time or raise an exception at declaration time.
  • Deprecation alias: name of another attribute that would be used instead during de-serialization in case something just got renamed

It looked similar to this:

class DeprecatedField(Field):
     ...
class DeprecatedAlias(DeprecatedField):
    ....

class MyModel(BaseModel):
     version = "0.1.0"
     to_be_removed_field: str = DeprecatedField(warn_version="0.1.0", remove_version="1.0.0", reason="for a very good reason")
     renamed_field: str = DeprecatedAlias(alias="new_field",  warn_version=.... ) 
     new_field: str = Field({}, description="New field to accept string to key values")

Could this be implemented by automatically inserting validators for those fields in the ModelMetaclass? Or is there be a better way to do it?

@nuno-andre
Copy link
Contributor

nuno-andre commented Mar 30, 2021

Maybe this implementation should emit a DeprecationWarning on the model's instantiation. This warning is ignored by default, unless triggered by code in __main__, so it wouldn't modify the common behavior and would make it easier to implement deprecation policies.

If so, it may also be convenient a method to easily distinguish the deprecation warnings, e.g. a global var or a specific Pydantic warning.

import pydantic

class MyLibDeprecation(DeprecationWarning): ...

pydantic.DEPRECATION_WARNING = MyLibDeprecation

Also, deprecated was included as a boolean meta-data annotation in JSON Schema Validation 2019-09, so this Field property would also facilitate to implement JSON Schema to Pydantic proxies.

@dominikandreas I think this option would probably work better just as an annotation to be implemented at a validation level. A field can be deprecated by several fields or by none at all. Also, DeprecatedAlias is confusing since it could be interpreted as the alias property of the DeprecatedField instead of as the substitute field.

@orlevii
Copy link

orlevii commented Mar 1, 2023

Any update regarding this? Can be very helpful.

@dmontagu
Copy link
Contributor

dmontagu commented Mar 1, 2023

It will remain possible to set the deprecated field in the JSON schema in v2, though it will change from being a keyword argument to the Field to going on a new keyword argument called json_schema_extra.

Regarding emitting DeprecationWarnings, IDE-integration, etc., I think that's a bigger discussion. You could definitely create a reusable validator that would emit a DeprecationWarning on initialization and use it in your own code.

Based on the suggestions already received in this thread (most of which I do think are reasonable), I think if we try to provide built-in support there will be a lot of debate about exactly how it should work, and it will come with a significant maintenance burden. I think we'd need to wait until v2 is released to look at this more seriously, but in principle I'd be open to a PR if whoever wants to open it can establish consensus on how the functionality would work. (That would also assume, and maybe this is a big assumption, that the implementation doesn't require adding a significant amount of complexity/maintenance burden to the library.)

@nuno-andre
Copy link
Contributor

nuno-andre commented Mar 6, 2023

@dmontagu There's an open PEP (PEP 702) to marking deprecations using @typing.deprecated, which adds a DeprecationWarning to classes and functions/methods and sets a __deprecated__ attribute with the deprecation message. (It was implemented as an expermiental feature in typing_extensions a couple of moths ago).

I think that enabling a similar approach through Field(deprecated=...) be interesting for several reasons.

  • the warning: DeprecationWarning is the builtin way to mark an object as deprecated. These warnings are ignored by default, so it has less to do with the warning itself as with the generated event. It's much simpler to capture these warnings and act accordingly than to check whether a deprecated field has been instantiated in each case.

  • the field: Relegating the property to a simple annotation in the JSON schema excludes it from the API. Whether a property is deprecated should not only have to do with the JSON schema, but also with the model. Having to go through the schema to determine if an attribute is deprecated would be cumbersome and error prone.

  • type checking: this PEP comes from suggestions made by mypy and Pyright. This would be a simple way of making deprecations visible to type checkers.

The implementation could be something like:

def Field(
    ...
    deprecated: bool | str = False,
):
    ...
    if deprecated is not False:
        if isinstance(deprecated, str):
            _deprecated_msg = deprecated
        else:
            _deprecated_msg = f'{alias!r} is deprecated'

        # wraps the field as in typing.deprecated
        ...

I would be happy to submit a PR if you are open to consider this alternative :)

@dmontagu
Copy link
Contributor

dmontagu commented Mar 7, 2023

Well, if that PEP gets accepted, that would definitely offload the burden of figuring out how it should work; I'd be inclined to follow similar patterns as @typing.deprecated for fields, and use that information to populate the JSON schema fields.

Given that there is a PEP, I'm pretty reluctant to implement our own approach until we see how that shakes out because the last thing I'd want is to have our own approach that ends up incompatible with how that PEP works (especially if dataclasses ends up adding deprecation support in some manner, which I'd want to replicate as closely as possible), and then ending up unable to course-correct for reasons of backwards compatibility.

@nuno-andre
Copy link
Contributor

Glad to hear you will follow this pattern if the PEP is accepted :) And yes, it makes perfect sense to wait for the acceptance of the PEP before making a decision. But although

@typing.deprecated is also meant for dataclasses,
from typing_extensions import deprecated
from dataclasses import dataclass
import warnings

warnings.filterwarnings('error', category=DeprecationWarning)

@deprecated('this model is deprecated')
@dataclass
class Model:
    prop: int = 1

try:
    i = Model()
except DeprecationWarning as e:
    print(e)
#> this model is deprecated

print(Model.__deprecated__)
#> this model is deprecated
(and it just works with Pydantic models)
from typing_extensions import deprecated
from pydantic import BaseModel

@deprecated('submodel is deprecated')
class SubModel(BaseModel):
    prop: int = 1

class Model(BaseModel):
    sub: SubModel | None

m = Model()
#> Ok

m = Model(sub=SubModel(prop=1))
#> DeprecationWarning: submodel is deprecated

I think this is more about deprecating fields than models... and typing.deprecated is not directly usable with fields:

from pydantic import BaseModel, Field
from typing_extensions import deprecated

class Model(BaseModel):
    id: str = deprecated('prop is deprecated')(Field(alias='_id'))
#> raises TypeError (receives an instance, not a class or a callable)

class Model(BaseModel):
    id: str = deprecated('prop is deprecated')(Field)(alias='_id')
#> raises DeprecationWarning (FieldInfo is called on declaration)

So this is a please don't definitively discard the deprecated argument yet, rather than a please implement this asap ;)

@dmontagu
Copy link
Contributor

dmontagu commented Mar 7, 2023

I think this is more about deprecating fields than models... and typing.deprecated is not directly usable with fields:

Yes, I understand this. I'm hoping that it either ends up being clear that dataclasses has no intention of ever supporting deprecating fields, or that some approach to deprecating fields ends up being part of that PEP. (My main concern is another PEP will come along that will add deprecation to individual fields of a dataclass.)

Either way, I think it will make sense to add support for deprecating fields (beyond just on the level of JSON schema) once there is a little more clarity around the right behavior.

frascuchon pushed a commit to argilla-io/argilla that referenced this issue Mar 16, 2023
**Note:** This PR is based on changes added by
#2545 so please review
#2545 first.

In this PR I'm adding `first_name` and `last_name` to `User` Pydantic
schema. I have not found a proper way to deprecate `full_name` field (I
have only found this pydantic/pydantic#2255).
@frascuchon if you know of some proper way to deprecate this field and
automagically show that on swagger docs please tell me.
@samuelcolvin
Copy link
Member

Our plan is to add this to annotated-types, then respect that.

@samuelcolvin
Copy link
Member

This would be nice to have in V2, but since adding it should be entirely backwards compatible, it's not a blocker for V2 - can easily be added in 2.1 etc.

@Kludex Kludex added the help wanted Pull Request welcome label Aug 21, 2023
@ziyuang
Copy link

ziyuang commented Oct 31, 2023

Doesn't this work out of the box? The Field docs explain that any keyword arguments are added verbatim to the field's schema.

The page is gone/moved and shows 404.

@sydney-runkle sydney-runkle added this to the v2.7.0 milestone Jan 16, 2024
@sydney-runkle
Copy link
Member

Marking this as complete via #8237.

Thanks for the awesome work, @Viicos!!! This will be one of our highlight features for 2.7.0 🚀 !!

Next steps:

  • Adding support for deprecated models
  • Adding support for configuring params like stacklevel, etc

@Viicos
Copy link
Contributor

Viicos commented Feb 29, 2024

Great!

I'll open a follow up issue to track remaining features that I had in mind

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request help wanted Pull Request welcome
Projects
None yet
Development

No branches or pull requests