Skip to content

Commit e1da4b1

Browse files
ModdingfoxTyst Marin
authored andcommitted
Adding support for pydantic extras in models
1 parent 6ad1dd6 commit e1da4b1

File tree

4 files changed

+423
-2
lines changed

4 files changed

+423
-2
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ paths are considered internals and can change in minor and patch releases.
1414
v4.40.1 (2025-05-??)
1515
--------------------
1616

17+
Added
18+
^^^^^
19+
- Support for Pydantic models with ``extra`` field configuration (``allow``,
20+
``forbid``, ``ignore``). Models with ``extra="allow"`` now accept additional
21+
fields, while ``extra="forbid"`` properly rejects them and ``extra="ignore"``
22+
accepts but ignores extra fields during instantiation.
23+
1724
Fixed
1825
^^^^^
1926
- ``print_shtab`` incorrectly parsed from environment variable (`#725

jsonargparse/_core.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,8 +1147,24 @@ def check_values(cfg):
11471147
group_key = next((g for g in self.groups if key.startswith(g + ".")), None)
11481148
if group_key:
11491149
subkey = key[len(group_key) + 1 :]
1150-
raise NSKeyError(f"Group '{group_key}' does not accept nested key '{subkey}'")
1151-
raise NSKeyError(f"Key '{key}' is not expected")
1150+
# Check if this is a Pydantic model with extra configuration
1151+
group = self.groups[group_key]
1152+
should_raise_error = True
1153+
if hasattr(group, "group_class") and group.group_class:
1154+
from ._optionals import get_pydantic_extra_config
1155+
1156+
extra_config = get_pydantic_extra_config(group.group_class)
1157+
if extra_config == "allow":
1158+
# Allow extra fields - don't raise an error
1159+
should_raise_error = False
1160+
elif extra_config == "ignore":
1161+
# Ignore extra fields - don't raise an error, Pydantic will ignore during instantiation
1162+
should_raise_error = False
1163+
# For 'forbid' or None (default), raise error
1164+
if should_raise_error:
1165+
raise NSKeyError(f"Group '{group_key}' does not accept nested key '{subkey}'")
1166+
else:
1167+
raise NSKeyError(f"Key '{key}' is not expected")
11521168

11531169
try:
11541170
with parser_context(load_value_mode=self.parser_mode):

jsonargparse/_optionals.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,58 @@ def validate_annotated(value, typehint: type):
361361
from pydantic import TypeAdapter
362362

363363
return TypeAdapter(typehint).validate_python(value)
364+
365+
366+
def get_pydantic_extra_config(class_type) -> Optional[str]:
367+
"""Get the 'extra' configuration from a Pydantic model.
368+
369+
Args:
370+
class_type: The class to check for Pydantic extra configuration.
371+
372+
Returns:
373+
The extra configuration ('allow', 'forbid', 'ignore') or None if not a Pydantic model.
374+
"""
375+
pydantic_model_version = is_pydantic_model(class_type)
376+
if not pydantic_model_version:
377+
return None
378+
379+
try:
380+
381+
# Handle Pydantic v2 models
382+
if pydantic_model_version > 1:
383+
# Check for model_config attribute (Pydantic v2 style)
384+
if hasattr(class_type, "model_config"):
385+
config = class_type.model_config
386+
if hasattr(config, "get"):
387+
# ConfigDict case
388+
return config.get("extra")
389+
elif hasattr(config, "extra"):
390+
# Direct attribute access
391+
return config.extra
392+
393+
# Check for __config__ attribute (legacy support in v2)
394+
if hasattr(class_type, "__config__"):
395+
config = class_type.__config__
396+
if hasattr(config, "extra"):
397+
return config.extra
398+
399+
# Handle Pydantic v1 models (including v1 compatibility mode in v2)
400+
else:
401+
if hasattr(class_type, "__config__"):
402+
config = class_type.__config__
403+
if hasattr(config, "extra"):
404+
extra_value = config.extra
405+
# Handle Pydantic v1 Extra enum
406+
if hasattr(extra_value, "value"):
407+
return extra_value.value
408+
elif isinstance(extra_value, str):
409+
return extra_value
410+
else:
411+
# Convert enum to string by taking the last part after the dot
412+
return str(extra_value).split(".")[-1]
413+
414+
except Exception:
415+
# If anything goes wrong, return None to fall back to default behavior
416+
pass
417+
418+
return None

0 commit comments

Comments
 (0)