From 03eaec2fe96f29a4dca430cdd83fb9a6ab560b3e Mon Sep 17 00:00:00 2001 From: Sean True Date: Mon, 25 Apr 2022 17:32:34 +0100 Subject: [PATCH 1/2] Adds reserved word check to signature generation logic. --- changes/4011-strue36.md | 1 + pydantic/utils.py | 15 +++++++++++++-- tests/test_model_signature.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 changes/4011-strue36.md diff --git a/changes/4011-strue36.md b/changes/4011-strue36.md new file mode 100644 index 0000000000..001654412d --- /dev/null +++ b/changes/4011-strue36.md @@ -0,0 +1 @@ +Adds reserved word check to signature generation logic. diff --git a/pydantic/utils.py b/pydantic/utils.py index 2a3960f6d7..a3b7dde16b 100644 --- a/pydantic/utils.py +++ b/pydantic/utils.py @@ -1,3 +1,4 @@ +import keyword import warnings import weakref from collections import OrderedDict, defaultdict, deque @@ -56,6 +57,7 @@ 'lenient_isinstance', 'lenient_issubclass', 'in_ipython', + 'is_valid_identifier', 'deep_update', 'update_not_none', 'almost_equal_floats', @@ -192,6 +194,15 @@ def in_ipython() -> bool: return True +def is_valid_identifier(identifier: str) -> bool: + """ + Checks that a string is a valid identifier and not a reserved word. + :param identifier: The identifier to test. + :return: True if the identifier is valid. + """ + return identifier.isidentifier() and not keyword.iskeyword(identifier) + + KeyType = TypeVar('KeyType') @@ -244,8 +255,8 @@ def generate_model_signature( param_name = field.alias if field_name in merged_params or param_name in merged_params: continue - elif not param_name.isidentifier(): - if allow_names and field_name.isidentifier(): + elif not is_valid_identifier(param_name): + if allow_names and is_valid_identifier(field_name): param_name = field_name else: use_var_kw = True diff --git a/tests/test_model_signature.py b/tests/test_model_signature.py index 789372b9b3..74569b0d33 100644 --- a/tests/test_model_signature.py +++ b/tests/test_model_signature.py @@ -84,6 +84,16 @@ class Config: assert _equals(str(signature(Foo)), '(*, foo: str) -> None') +def test_does_not_use_reserved_word(): + class Foo(BaseModel): + from_: str = Field(..., alias='from') + + class Config: + allow_population_by_field_name = True + + assert _equals(str(signature(Foo)), '(*, from_: str) -> None') + + def test_extra_allow_no_conflict(): class Model(BaseModel): spam: str From 2a76debea0d6013597ca068f19fd61c8e57bba23 Mon Sep 17 00:00:00 2001 From: Sean True Date: Mon, 25 Jul 2022 09:26:02 +0100 Subject: [PATCH 2/2] Improve documentation. --- pydantic/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/utils.py b/pydantic/utils.py index a3b7dde16b..9a08aed72e 100644 --- a/pydantic/utils.py +++ b/pydantic/utils.py @@ -196,7 +196,7 @@ def in_ipython() -> bool: def is_valid_identifier(identifier: str) -> bool: """ - Checks that a string is a valid identifier and not a reserved word. + Checks that a string is a valid identifier and not a Python keyword. :param identifier: The identifier to test. :return: True if the identifier is valid. """