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

Graceful handling of extra properties from clients #270

Open
RobertoPrevato opened this issue Jul 23, 2022 · 2 comments
Open

Graceful handling of extra properties from clients #270

RobertoPrevato opened this issue Jul 23, 2022 · 2 comments
Labels
enhancement New feature or request low priority

Comments

@RobertoPrevato
Copy link
Member

Ignore extra properties when parsing binding input from the request payload?

Example for dataclasses:

from dataclasses import fields, is_dataclass


def create_instance(cls, props):
    """
    Creates an instance of a given dataclass type, ignoring extra properties.
    """
    if not is_dataclass(cls):
        raise ValueError("The given type is not a dataclass")
    class_fields = {f.name for f in fields(cls)}
    return cls(**{k: v for k, v in props.items() if k in class_fields})
@RobertoPrevato RobertoPrevato added low priority enhancement New feature or request labels Jul 27, 2022
@RobertoPrevato
Copy link
Member Author

Note:

Python 3.10.4 (main, Apr  2 2022, 11:07:58) [GCC 9.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: class A:
   ...:     def __init__(self, a, b, c, **kwargs):
   ...:         self.a = a
   ...:         self.b = b
   ...:         self.c = c
   ...:

In [2]: from dataclasses import dataclass, fields

In [3]: def create_instance(cls, props):
   ...:     """
   ...:     Creates an instance of a given dataclass type, ignoring extra properties.
   ...:     """
   ...:     class_fields = {f.name for f in fields(cls)}
   ...:     return cls(**{k: v for k, v in props.items() if k in class_fields})
   ...:

In [4]: @dataclass
   ...: class B:
   ...:     a: int
   ...:     b: int
   ...:     c: int
   ...:

In [5]: A(**{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5})
Out[5]: <__main__.A at 0x7fd850abe200>

In [6]: data = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}

In [7]: a = A(**data)

In [8]: a.a
Out[8]: 1

In [9]: a.b
Out[9]: 2

In [10]: a.c
Out[10]: 3

In [11]: %timeit A(**data)
420 ns ± 1.45 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [12]: b = create_instance(B, data)

In [13]: b.a
Out[13]: 1

In [14]: b.b
Out[14]: 2

In [15]: b.c
Out[15]: 3

In [16]: %timeit A(**data)
419 ns ± 0.351 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [17]: B(**data)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [17], in <cell line: 1>()
----> 1 B(**data)

TypeError: B.__init__() got an unexpected keyword argument 'd'

In [18]: %timeit create_instance(B, data)
1.38 µs ± 3.22 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [19]: class_fields = {f.name for f in fields(B)}

In [20]: class_fields
Out[20]: {'a', 'b', 'c'}

In [21]: B(**{k: v for k, v in data.items() if k in class_fields})
Out[21]: B(a=1, b=2, c=3)

In [22]: %timeit B(**{k: v for k, v in data.items() if k in class_fields})
657 ns ± 1.59 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [23]: %timeit B(**{k: v for k, v in data.items() if k in class_fields})
651 ns ± 0.331 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

@RobertoPrevato
Copy link
Member Author

In [24]: FIELDS = {}

In [27]: def get_fields(cls):
    ...:     try:
    ...:         return FIELDS[cls]
    ...:     except KeyError:
    ...:         FIELDS[cls] = {f.name for f in fields(cls)}
    ...:         return FIELDS[cls]
    ...:

In [28]:

In [28]: def create_instance(cls, props):
    ...:     """
    ...:     Creates an instance of a given dataclass type, ignoring extra properties.
    ...:     """
    ...:     class_fields = get_fields(cls)
    ...:     return cls(**{k: v for k, v in props.items() if k in class_fields})
    ...:

In [29]: %timeit create_instance(B, data)
796 ns ± 2.11 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [30]: FIELDS
Out[30]: {__main__.B: {'a', 'b', 'c'}}

In [31]: %timeit create_instance(B, data)
802 ns ± 6.04 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request low priority
Projects
None yet
Development

No branches or pull requests

1 participant