From 03eaec2fe96f29a4dca430cdd83fb9a6ab560b3e Mon Sep 17 00:00:00 2001
From: Sean True <strue36@gmail.com>
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 <strue36@gmail.com>
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.
"""