Skip to content

Commit

Permalink
Replace __pydantic_self__ with positional-only self
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Nov 15, 2023
1 parent e5e47d8 commit 4b3110a
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 10 deletions.
6 changes: 3 additions & 3 deletions docs/concepts/validators.md
Expand Up @@ -726,10 +726,10 @@ def init_context(value: Dict[str, Any]) -> Iterator[None]:
class Model(BaseModel):
my_number: int

def __init__(__pydantic_self__, **data: Any) -> None:
__pydantic_self__.__pydantic_validator__.validate_python(
def __init__(self, /, **data: Any) -> None:
self.__pydantic_validator__.validate_python(
data,
self_instance=__pydantic_self__,
self_instance=self,
context=_init_context_var.get(),
)

Expand Down
2 changes: 1 addition & 1 deletion pydantic/_internal/_generate_schema.py
Expand Up @@ -2187,7 +2187,7 @@ def generate_pydantic_signature(
# Make sure the parameter for extra kwargs
# does not have the same name as a field
default_model_signature = [
('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD),
('self', Parameter.POSITIONAL_ONLY),
('data', Parameter.VAR_KEYWORD),
]
if [(p.name, p.kind) for p in present_params] == default_model_signature:
Expand Down
7 changes: 3 additions & 4 deletions pydantic/main.py
Expand Up @@ -150,18 +150,17 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass):
__pydantic_complete__ = False
__pydantic_root_model__ = False

def __init__(__pydantic_self__, **data: Any) -> None: # type: ignore
def __init__(self, /, **data: Any) -> None: # type: ignore
"""Create a new model by parsing and validating input data from keyword arguments.
Raises [`ValidationError`][pydantic_core.ValidationError] if the input data cannot be
validated to form a valid model.
`__init__` uses `__pydantic_self__` instead of the more common `self` for the first arg to
allow `self` as a field name.
`self` is explicitly positional-only to allow `self` as a field name.
"""
# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
__tracebackhide__ = True
__pydantic_self__.__pydantic_validator__.validate_python(data, self_instance=__pydantic_self__)
self.__pydantic_validator__.validate_python(data, self_instance=self)

# The following line sets a flag that we use to determine when `__init__` gets overridden by the user
__init__.__pydantic_base_init__ = True
Expand Down
10 changes: 10 additions & 0 deletions pydantic/mypy.py
Expand Up @@ -1148,6 +1148,16 @@ def add_method(
first = [Argument(Var('_cls'), self_type, None, ARG_POS, True)]
else:
self_type = self_type or fill_typevars(info)
# `self` is positional *ONLY* here, but this can't be expressed
# fully in the mypy internal API. ARG_POS is the closest we can get.
# Using ARG_POS will, however, give mypy errors if a `self` field
# is present on a model:
#
# Name "self" already defined (possibly by an import) [no-redef]
#
# As a workaround, we give this argument a name that will
# never conflict. By its positional nature, this name will not
# be used or exposed to users.
first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)]
args = first + args

Expand Down
4 changes: 2 additions & 2 deletions pydantic/root_model.py
Expand Up @@ -52,15 +52,15 @@ def __init_subclass__(cls, **kwargs):
)
super().__init_subclass__(**kwargs)

def __init__(__pydantic_self__, root: RootModelRootType = PydanticUndefined, **data) -> None: # type: ignore
def __init__(self, /, root: RootModelRootType = PydanticUndefined, **data) -> None: # type: ignore
__tracebackhide__ = True
if data:
if root is not PydanticUndefined:
raise ValueError(
'"RootModel.__init__" accepts either a single positional argument or arbitrary keyword arguments'
)
root = data # type: ignore
__pydantic_self__.__pydantic_validator__.validate_python(root, self_instance=__pydantic_self__)
self.__pydantic_validator__.validate_python(root, self_instance=self)

__init__.__pydantic_base_init__ = True

Expand Down
8 changes: 8 additions & 0 deletions tests/test_edge_cases.py
Expand Up @@ -1296,6 +1296,14 @@ class Model(BaseModel):
}


def test_no_name_conflict_in_constructor():
class Model(BaseModel):
self: int

m = Model(**{'__pydantic_self__': 4, 'self': 2})
assert m.self == 2


def test_self_recursive():
class SubModel(BaseModel):
self: int
Expand Down

0 comments on commit 4b3110a

Please sign in to comment.