Skip to content

Commit

Permalink
Fix json validation for NameEmail
Browse files Browse the repository at this point in the history
Fixes #8649
  • Loading branch information
Holi0317 committed Jan 26, 2024
1 parent b785d5b commit 2c239f1
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 14 deletions.
47 changes: 34 additions & 13 deletions pydantic/networks.py
Expand Up @@ -464,22 +464,15 @@ def __init__(self, name: str, email: str):
def __eq__(self, other: Any) -> bool:
return isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email)

@classmethod
def __get_pydantic_json_schema__(
cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
) -> JsonSchemaValue:
field_schema = handler(core_schema)
field_schema.update(type='string', format='name-email')
return field_schema

@classmethod
def __get_pydantic_core_schema__(
cls,
_source: type[Any],
_handler: GetCoreSchemaHandler,
) -> core_schema.CoreSchema:
import_email_validator()
return core_schema.no_info_after_validator_function(

python_schema = core_schema.no_info_after_validator_function(
cls._validate,
core_schema.union_schema(
[core_schema.is_instance_schema(cls), core_schema.str_schema()],
Expand All @@ -489,13 +482,41 @@ def __get_pydantic_core_schema__(
serialization=core_schema.to_string_ser_schema(),
)

json_schema = core_schema.no_info_after_validator_function(
cls._validate,
core_schema.union_schema(
[
core_schema.str_schema(metadata={'format': 'name-email'}),
core_schema.typed_dict_schema(
{
'name': core_schema.typed_dict_field(core_schema.str_schema()),
'email': core_schema.typed_dict_field(core_schema.str_schema()),
}
),
],
custom_error_type='name_email_type',
custom_error_message='Input is not a valid NameEmail',
),
serialization=core_schema.to_string_ser_schema(),
)

return core_schema.json_or_python_schema(
python_schema=python_schema,
json_schema=json_schema,
serialization=core_schema.to_string_ser_schema(),
)

@classmethod
def _validate(cls, __input_value: NameEmail | str) -> NameEmail:
def _validate(cls, __input_value: NameEmail | dict[str, str] | str) -> NameEmail:
if isinstance(__input_value, cls):
return __input_value
else:
name, email = validate_email(__input_value) # type: ignore[arg-type]
return cls(name, email)

if isinstance(__input_value, dict):
_, email = validate_email(__input_value['email'])
return cls(__input_value['name'], email)

name, email = validate_email(__input_value) # type: ignore[arg-type]
return cls(name, email)

def __str__(self) -> str:
return f'{self.name} <{self.email}>'
Expand Down
33 changes: 32 additions & 1 deletion tests/test_networks.py
Expand Up @@ -844,12 +844,13 @@ def test_email_validator_not_installed():


@pytest.mark.skipif(not email_validator, reason='email_validator not installed')
def test_name_email():
def test_name_email_py():
class Model(BaseModel):
v: NameEmail

assert str(Model(v=NameEmail('foo bar', 'foobaR@example.com')).v) == 'foo bar <foobaR@example.com>'
assert str(Model(v='foo bar <foobaR@example.com>').v) == 'foo bar <foobaR@example.com>'
assert str(Model(v='foobaR@example.com').v) == 'foobaR <foobaR@example.com>'
assert NameEmail('foo bar', 'foobaR@example.com') == NameEmail('foo bar', 'foobaR@example.com')
assert NameEmail('foo bar', 'foobaR@example.com') != NameEmail('foo bar', 'different@example.com')

Expand All @@ -858,3 +859,33 @@ class Model(BaseModel):
assert exc_info.value.errors() == [
{'input': 1, 'loc': ('v',), 'msg': 'Input is not a valid NameEmail', 'type': 'name_email_type'}
]


@pytest.mark.skipif(not email_validator, reason='email_validator not installed')
def test_name_email_json():
class Model(BaseModel):
v: NameEmail

assert Model.model_validate_json('{"v":{"name":"foo bar","email":"foobaR@example.com"}}').v == NameEmail(
'foo bar', 'foobaR@example.com'
)

assert Model.model_validate_json('{"v":{"name":"foo bar","email":"whatever <foobaR@example.com>"}}').v == NameEmail(
'foo bar', 'foobaR@example.com'
)
assert Model.model_validate_json('{"v":"foo bar <foobaR@example.com>"}').v == NameEmail(
'foo bar', 'foobaR@example.com'
)

assert (
Model(v=NameEmail('foo bar', 'foobaR@example.com')).model_dump_json() == '{"v":"foo bar <foobaR@example.com>"}'
)


@pytest.mark.skipif(not email_validator, reason='email_validator not installed')
def test_name_email_json_schema():
class Model(BaseModel):
v: NameEmail

schema = Model.model_json_schema()
assert schema['properties']['v']['anyOf'][0] == {'type': 'string', 'format': 'name-email'}

0 comments on commit 2c239f1

Please sign in to comment.