Blob Blame History Raw
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.
     """