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 exclude to Config #660

Closed
samuelcolvin opened this issue Jul 15, 2019 · 26 comments · Fixed by #2231
Closed

add exclude to Config #660

samuelcolvin opened this issue Jul 15, 2019 · 26 comments · Fixed by #2231
Labels
dumping how pydantic serialises models, e.g. via `.dict()` and `.json()` feature request Feedback Wanted

Comments

@samuelcolvin
Copy link
Member

From a user perspective I would rather add an exclude in the Config metaclass instead of passing the dict in to .json(). One of my model's fields is a Callable and I would like to call .json() on it, however I need to instead pass a custom encoder to the .json() which still puts the responsibility on the caller.

Originally posted by @enkoder in #648 (comment)

@samuelcolvin
Copy link
Member Author

as per #624 we could easily add exclude to a field, or even a dict to exclude sub-fields.

@samuelcolvin samuelcolvin changed the title add exclude and include to config add exclude to Config Jul 15, 2019
@JrooTJunior
Copy link

I guess to implements this like exporting roles in Schematics
That mechanism is good for writing API's

You can simply describe all of public fields in model and inside controllers make dump in required set of fields by specifying only the role name.

For example:

class User(BaseModel):
	id: int
	login: str
	email: str
	password: str
	first_name: str
	last_name: Optional[str] = None
	articles: Optional[List[Article]] = None

	register_date: datetime.datetime
	last_login: Optional[datetime.datetime] = None

	class Config:
		roles = {
			"self": {"exclude": {"password", "register_date", "last_login"}},
			"admin": {"exclude": {"password"}},
			"public": {"include": {"id", "email", "first_name", "last_name"}}
		}

And then:

user = User(
	id=42,
	login="mylogin",
	email="email@example.com",
	password="hashed secret password",
	first_name="Some",
	last_name="User",
	articles=[Article(id=1, label="foo"), Article(id=2, label="bar"), ...],
	register_date=datetime.datetime(...),
	last_login=datetime.datetime.now()
)
user.dump(role="public")

Should return something like this:

{
	"id": 42,
	"email": "email@example.com",
	"first_name": "Some",
	"last_name": "User",
}

include/exclude describing mechanism can be implemented more simply, for e.g. via function include(...)/exclude(...)

@enkoder
Copy link

enkoder commented Jul 15, 2019

Wow thanks for the quick response @samuelcolvin. I have just been exploring pydantic and I really like it. Having quick responses on PR's and active development certainly makes me even more excited to adopt it. Looking forward to working with you.

@dgasmith
Copy link
Contributor

+1 for this, we code it into our classes as we have "local" variables that should not exported to other objects. Allowing this in the dict() on up would be optimal.

@Bobronium
Copy link
Contributor

I like this idea and love @JrooTJunior's implementation with roles
I guess I'll be able to work on a new PR with these changes very soon, right after #648 will be merged

@Bobronium Bobronium mentioned this issue Jul 25, 2019
4 tasks
@samuelcolvin
Copy link
Member Author

I understand that roles is neat, but I think it's a whole new concept that is unnecessarily complex for pydantic. If you did want roles it would be fairly trivial to add it to config and implement your own CustomBaseModel that used it as per #684.

I think exclude and include should use the fields property already in Config and also be configurable on Schema (soon to be Field, #577).

Combining this with #624, we'd have something like:

class User(BaseModel):
    id: int
    username: str
    password: str

class Transaction(BaseModel):
    id: str
    user: User
    value: int

    class Config:
        fields = {
            'value': {
                'alias': 'Amount',
                'exclude': ...,
            },
            'user': {
                'exclude': {'username', 'password'}
            },
            'id': {
                'dump_alias': 'external_id'
            }
        }

@samuelcolvin samuelcolvin added the dumping how pydantic serialises models, e.g. via `.dict()` and `.json()` label Jan 3, 2020
@xspirus
Copy link
Contributor

xspirus commented May 29, 2020

Hello @samuelcolvin . First of all, I am really amazed by pydantic and, even though I haven't been using it for a long time, I think it can be really helpful in numerous situations.

On the point of this issue, as I understand, this has not been implemented yet, correct? Is there currently any work going on for this? If not, I am willing to have a go on this. Having include and exclude on Field and Config, in the way you described above, sounds like a really nice feature.

@mtucker502
Copy link

I solved this today with the following:

class Resource(BaseModel):
    class Config:
        include: Set[str] = set()
        exclude: Set[str] = set()

    def json(self, **kwargs):
        include = kwargs.pop('include', set())
        include = include.union(getattr(self.Config, "include", set()))
        if len(include) == 0:
            include = None

        exclude = kwargs.pop('exclude', set())
        exclude = exclude.union(getattr(self.Config, "exclude", set()))
        if len(exclude) == 0:
            exclude = None

        return super().json(include=include, exclude=exclude, **kwargs)

Just specify the include/exclude fields as a part of the Class definition:

class SR(Resource):
    synopsis: str
    session: str

    class Config:
        exclude = {'session'}

@xspirus
Copy link
Contributor

xspirus commented Jun 10, 2020

@mtucker502 That is one way to solve when calling the .json method. However, I think that this issue tries to solve this kind of problem:

class User(BaseModel):
    id: int
    username: str
    password: str

class Transaction(BaseModel):
    id: str
    user: User
    value: int

    class Config:
        fields = {
            'user': {
                'exclude': {'username', 'password'}
            },
        }

And then calling the constructor would look like this.

>>> Transaction(id=1, user={"id": 1}, value=1)
Transaction(id=1, user=User(id=1), value=1)

Correct me if I'm wrong @samuelcolvin .

@Bobronium
Copy link
Contributor

Bobronium commented Jun 10, 2020

@xspirus, this is an interesting concept. And it would actually solve/alleviate problem with copiyng the same model with little changes for different contexts.

@enkoder, since this issue is originaly created from your #648 (comment), what do you think?

@samuelcolvin
Copy link
Member Author

Hello @samuelcolvin . First of all, I am really amazed by pydantic and, even though I haven't been using it for a long time, I think it can be really helpful in numerous situations.

On the point of this issue, as I understand, this has not been implemented yet, correct? Is there currently any work going on for this? If not, I am willing to have a go on this. Having include and exclude on Field and Config, in the way you described above, sounds like a really nice feature.

No work on this AFAIK, happy to review a PR if it matches my suggestion above.

@xspirus
Copy link
Contributor

xspirus commented Jun 11, 2020

No work on this AFAIK, happy to review a PR if it matches my suggestion above.

I will try to create a PR, as soon as I find some time to work on this 😄

@enkoder
Copy link

enkoder commented Jun 12, 2020

@MrMrRobat the approach that @mtucker502 took is how I would expect things to look! Defining excluded and included on the config like they did.

@daviskirk
Copy link
Contributor

I'd be using this as well (or a variant of it) so I can also try and take a swing at a PR.

daviskirk added a commit to daviskirk/pydantic that referenced this issue Jan 1, 2021
- Add "exclude" as a field parameter so that it can be configured using model config instead of purely at `.dict` / `.json` export time.
- Closes pydantic#660
daviskirk added a commit to daviskirk/pydantic that referenced this issue Feb 16, 2021
- Add "exclude" as a field parameter so that it can be configured using model config instead of purely at `.dict` / `.json` export time.
- Closes pydantic#660
@phinate
Copy link

phinate commented Feb 17, 2021

+1 for this feature! :)

@anth2o
Copy link

anth2o commented Feb 17, 2021

I started a branch to add this feature, I'll finalize it when I have the time to open a PR https://github.com/anth2o/pydantic/commits/add-config-to-exclude-fields

@PrettyWood
Copy link
Member

@anth2o A PR is already open #2231. No need to waste your time ;) But your review or feedback on it when it's ready is always more than welcome!

@phinate
Copy link

phinate commented Feb 18, 2021

Will the implemented behaviour in #2231 mean that recursive serialization respects the config of sub-classes for exclude?

@daviskirk
Copy link
Contributor

Will the implemented behaviour in #2231 mean that recursive serialization respects the config of sub-classes for exclude?

That is definitely the goal. We will see how it works in practice

daviskirk added a commit to daviskirk/pydantic that referenced this issue Feb 19, 2021
- Add "exclude" / "include" as a field parameter so that it can be
  configured using model config (or fields) instead of purely at
  `.dict` / `.json` export time.
- Unify merging logic of advanced include/exclude fields
- Add tests for merging logic and field/config exclude/include params
- Closes pydantic#660
daviskirk added a commit to daviskirk/pydantic that referenced this issue Feb 19, 2021
- Add "exclude" / "include" as a field parameter so that it can be
  configured using model config (or fields) instead of purely at
  `.dict` / `.json` export time.
- Unify merging logic of advanced include/exclude fields
- Add tests for merging logic and field/config exclude/include params
- Closes pydantic#660
daviskirk added a commit to daviskirk/pydantic that referenced this issue Feb 27, 2021
- Add "exclude" / "include" as a field parameter so that it can be
  configured using model config (or fields) instead of purely at
  `.dict` / `.json` export time.
- Unify merging logic of advanced include/exclude fields
- Add tests for merging logic and field/config exclude/include params
- Closes pydantic#660
daviskirk added a commit to daviskirk/pydantic that referenced this issue Mar 12, 2021
- Add "exclude" / "include" as a field parameter so that it can be
  configured using model config (or fields) instead of purely at
  `.dict` / `.json` export time.
- Unify merging logic of advanced include/exclude fields
- Add tests for merging logic and field/config exclude/include params
- Closes pydantic#660
samuelcolvin added a commit that referenced this issue May 1, 2021
* Add exclude/include as field parameters

- Add "exclude" / "include" as a field parameter so that it can be
  configured using model config (or fields) instead of purely at
  `.dict` / `.json` export time.
- Unify merging logic of advanced include/exclude fields
- Add tests for merging logic and field/config exclude/include params
- Closes #660

* Precompute include/exclude fields for class

* Increase test coverage
* Remove (now) redundant type checks in Model._iter: New
  exclusion/inclusion algorithms guarantee that no sets are passed further down.

* Add docs for advanced field level exclude/include settings

* Minimal optimization for simple exclude/include export

Running benchmarks this vs. master is at:

this: pydantic best=33.225μs/iter avg=33.940μs/iter stdev=1.120μs/iter version=1.7.3
master: pydantic best=32.901μs/iter avg=33.276μs/iter stdev=0.242μs/iter version=1.7.3

* Apply review comments on exclude/enclude field arguments

* Fix/simplify type annotations
* Allow both ``True`` and ``Ellipsis`` to be used to indicate full field
  exclusion
* Reenable hypothesis plugin (removed by mistake)
* Update advanced include/include docs to use ``True`` instead of ``...``

* Move field info exclude/include updates into FieldInfo class

This way, the model field object does not need to concern itself with
dealing with field into specific fields.
(Same was done for alias in a previous commit).

* remove double back tick in markdown.

Co-authored-by: Samuel Colvin <samcolvin@gmail.com>
@johnsturgeon
Copy link

Has this made it's way into production yet?

@johnsturgeon
Copy link

@samuelcolvin -- PyPi does not seem to pull this change. I had to update my requirements to pull from the repo.

@PrettyWood
Copy link
Member

It has been merged but not yet released. It will be part of 1.9

@johnsturgeon
Copy link

Thank you!

@dixonwhitmire
Copy link

Hi Folks,

Thank you very much for supporting the config model "exclude" feature in 1.9.! This is going to help my team simplify things a bit.

Is there a release cadence or timeline for the 1.9 release?
Thanks!

@ludwignyberg
Copy link

Any updates on the 1.9 release timeline?

lyz-code added a commit to lyz-code/blue-book that referenced this issue Nov 18, 2021
…): Define fields to exclude from exporting at config level

Eagerly waiting for the release of the version 1.9 because you can [define the fields to exclude in the `Config` of the model](pydantic/pydantic#660) using something like:

```python
class User(BaseModel):
    id: int
    username: str
    password: str

class Transaction(BaseModel):
    id: str
    user: User
    value: int

    class Config:
        fields = {
            'value': {
                'alias': 'Amount',
                'exclude': ...,
            },
            'user': {
                'exclude': {'username', 'password'}
            },
            'id': {
                'dump_alias': 'external_id'
            }
        }
```

The release it's taking its time because [the developer's gremlin and salaried work are sucking his time off](pydantic/pydantic#3228).

feat(type_hints#Define a TypeVar with restrictions): Define a TypeVar with restrictions

```python
from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)
```

feat(type_hints#Use a constrained TypeVar in the definition of a class attributes): Use a constrained TypeVar in the definition of a class attributes.

If you try to use a `TypeVar` in the definition of a class attribute:

```python
class File:
    """Model a computer file."""

    path: str
    content: Optional[AnyStr] = None # mypy error!
```

[mypy](mypy.md) will complain with `Type variable AnyStr is unbound
[valid-type]`, to solve it, you need to make the class inherit from the
`Generic[AnyStr]`.

```python
class File(Generic[AnyStr]):
    """Model a computer file."""

    path: str
    content: Optional[AnyStr] = None
```

feat(python_properties): Give an overview on Python's @Property decorator

fix(vim): Correct vim snippet to remember the folds when saving a file
@gouthamravella
Copy link

As inspired by @mtucker502 answer, I have solved this issue with a custom class. This class can even includes/excludes the nested model fields too with both model.dict() and model.json(). You can alternatively even pass the params directly in the dict() and json() functions as well. Please try it and let me your suggestions on this. Hope it helps to someone like me.

class Resource(BaseModel):
    class Config:
        include: Dict[str, Any] = dict()
        exclude: Dict[str, Any] = dict()
    
    def dict(self, **kwargs):
        include = kwargs.pop('include', None)
        exclude = kwargs.pop('exclude', None)

        _incl = getattr(self.Config, "include", dict())
        if _incl != None and len(_incl) > 0:
            if isinstance(_incl, set):
                include = _incl
            else:
                if include == None:
                    _inc = dict()
                    include = dict(ChainMap(_inc, _incl))
                else:
                    include = dict(ChainMap(include, _incl))
        
        _excl = getattr(self.Config, "exclude", dict())
        if _excl != None and len(_excl) > 0:
            if isinstance(_excl, set):
                exclude = _excl
            else:
                if exclude == None:
                    _exc = dict()
                    exclude = dict(ChainMap(_exc, _excl))
                else:
                    exclude = dict(ChainMap(exclude, _excl))
        return super().dict(include=include, exclude=exclude, **kwargs)
    
    def json(self, **kwargs):
        include = kwargs.pop('include', None)
        exclude = kwargs.pop('exclude', dict())

        _incl = getattr(self.Config, "include", dict())
        if _incl != None and len(_incl) > 0:
            if isinstance(_incl, set):
                include = _incl
            else:
                if include == None:
                    _inc = dict()
                    include = dict(ChainMap(_inc, _incl))
                else:
                    include = dict(ChainMap(include, _incl))
        
        _excl = getattr(self.Config, "exclude", dict())
        if _excl != None and len(_excl) > 0:
            if isinstance(_excl, set):
                exclude = _excl
            else:
                if exclude == None:
                    _exc = dict()
                    exclude = dict(ChainMap(_exc, _excl))
                else:
                    exclude = dict(ChainMap(exclude, _excl))
        return super().json(include=include, exclude=exclude, **kwargs)

Usage:

class Coords(Resource):
    lat: float
    lng: float

class Location(Resource):
    city: str
    coords: Coords

class User(Resource):
    name: str
    location: Optional[Location]
    other: Optional[str]

    class Config:
        exclude = {}
        include= {'name':...,'location': {'city':...,'coords':{'lat'}}}

data={
    "name":"name",
    "other": "other",
    "location": {
        'city': 'Hyd',
        'coords': {
            'lat': 17.4,
            'lng': 17.6
        }
    }
}

User(**data).json()
(or)
User(**data).dict()
(or)
User(**data).json(exclude={'name'})
(or)
User(**data).json(include={'other':..., 'location':{'coords':{'lat'}} })

alexdrydew pushed a commit to alexdrydew/pydantic that referenced this issue Dec 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dumping how pydantic serialises models, e.g. via `.dict()` and `.json()` feature request Feedback Wanted
Projects
None yet
Development

Successfully merging a pull request may close this issue.