Coverage for pydantic/networks.py: 97.54%
200 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
1"""The networks module contains types for common network-related fields."""
3from __future__ import annotations as _annotations 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
5import dataclasses as _dataclasses 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
6import re 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
7from importlib.metadata import version 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
8from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
9from typing import TYPE_CHECKING, Any 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
11from pydantic_core import MultiHostUrl, PydanticCustomError, Url, core_schema 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
12from typing_extensions import Annotated, Self, TypeAlias 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
14from ._internal import _fields, _repr, _schema_generation_shared 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
15from ._migration import getattr_migration 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
16from .annotated_handlers import GetCoreSchemaHandler 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
17from .json_schema import JsonSchemaValue 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
19if TYPE_CHECKING: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
20 import email_validator
22 NetworkType: TypeAlias = 'str | bytes | int | tuple[str | bytes | int, str | int]' 1a
24else:
25 email_validator = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
28__all__ = [ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
29 'AnyUrl',
30 'AnyHttpUrl',
31 'FileUrl',
32 'FtpUrl',
33 'HttpUrl',
34 'WebsocketUrl',
35 'AnyWebsocketUrl',
36 'UrlConstraints',
37 'EmailStr',
38 'NameEmail',
39 'IPvAnyAddress',
40 'IPvAnyInterface',
41 'IPvAnyNetwork',
42 'PostgresDsn',
43 'CockroachDsn',
44 'AmqpDsn',
45 'RedisDsn',
46 'MongoDsn',
47 'KafkaDsn',
48 'NatsDsn',
49 'validate_email',
50 'MySQLDsn',
51 'MariaDBDsn',
52 'ClickHouseDsn',
53]
56@_dataclasses.dataclass 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
57class UrlConstraints(_fields.PydanticMetadata): 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
58 """Url constraints.
60 Attributes:
61 max_length: The maximum length of the url. Defaults to `None`.
62 allowed_schemes: The allowed schemes. Defaults to `None`.
63 host_required: Whether the host is required. Defaults to `None`.
64 default_host: The default host. Defaults to `None`.
65 default_port: The default port. Defaults to `None`.
66 default_path: The default path. Defaults to `None`.
67 """
69 max_length: int | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
70 allowed_schemes: list[str] | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
71 host_required: bool | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
72 default_host: str | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
73 default_port: int | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
74 default_path: str | None = None 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
76 def __hash__(self) -> int: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
77 return hash( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
78 (
79 self.max_length,
80 tuple(self.allowed_schemes) if self.allowed_schemes is not None else None,
81 self.host_required,
82 self.default_host,
83 self.default_port,
84 self.default_path,
85 )
86 )
89AnyUrl = Url 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
90"""Base type for all URLs. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
92* Any scheme allowed
93* Top-level domain (TLD) not required
94* Host required
96Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`,
97the types export the following properties:
99- `scheme`: the URL scheme (`http`), always set.
100- `host`: the URL host (`example.com`), always set.
101- `username`: optional username if included (`samuel`).
102- `password`: optional password if included (`pass`).
103- `port`: optional port (`8000`).
104- `path`: optional path (`/the/path/`).
105- `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`).
106- `fragment`: optional fragment (`fragment=is;this=bit`).
107"""
108AnyHttpUrl = Annotated[Url, UrlConstraints(allowed_schemes=['http', 'https'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
109"""A type that will accept any http or https URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
111* TLD not required
112* Host required
113"""
114HttpUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=['http', 'https'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
115"""A type that will accept any http or https URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
117* TLD not required
118* Host required
119* Max length 2083
121```py
122from pydantic import BaseModel, HttpUrl, ValidationError
124class MyModel(BaseModel):
125 url: HttpUrl
127m = MyModel(url='http://www.example.com') # (1)!
128print(m.url)
129#> http://www.example.com/
131try:
132 MyModel(url='ftp://invalid.url')
133except ValidationError as e:
134 print(e)
135 '''
136 1 validation error for MyModel
137 url
138 URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str]
139 '''
141try:
142 MyModel(url='not a url')
143except ValidationError as e:
144 print(e)
145 '''
146 1 validation error for MyModel
147 url
148 Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str]
149 '''
150```
1521. Note: mypy would prefer `m = MyModel(url=HttpUrl('http://www.example.com'))`, but Pydantic will convert the string to an HttpUrl instance anyway.
154"International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via
155[punycode](https://en.wikipedia.org/wiki/Punycode) (see
156[this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important):
158```py
159from pydantic import BaseModel, HttpUrl
161class MyModel(BaseModel):
162 url: HttpUrl
164m1 = MyModel(url='http://puny£code.com')
165print(m1.url)
166#> http://xn--punycode-eja.com/
167m2 = MyModel(url='https://www.аррӏе.com/')
168print(m2.url)
169#> https://www.xn--80ak6aa92e.com/
170m3 = MyModel(url='https://www.example.珠宝/')
171print(m3.url)
172#> https://www.example.xn--pbt977c/
173```
176!!! warning "Underscores in Hostnames"
177 In Pydantic, underscores are allowed in all parts of a domain except the TLD.
178 Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can.
180 To explain this; consider the following two cases:
182 - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore.
183 - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain.
185 Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore
186 underscores are allowed, but you can always do further validation in a validator if desired.
188 Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good
189 (or at least big) company.
190"""
191AnyWebsocketUrl = Annotated[Url, UrlConstraints(allowed_schemes=['ws', 'wss'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
192"""A type that will accept any ws or wss URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
194* TLD not required
195* Host required
196"""
197WebsocketUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=['ws', 'wss'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
198"""A type that will accept any ws or wss URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
200* TLD not required
201* Host required
202* Max length 2083
203"""
204FileUrl = Annotated[Url, UrlConstraints(allowed_schemes=['file'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
205"""A type that will accept any file URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
207* Host not required
208"""
209FtpUrl = Annotated[Url, UrlConstraints(allowed_schemes=['ftp'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
210"""A type that will accept ftp URL. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
212* TLD not required
213* Host required
214"""
215PostgresDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
216 MultiHostUrl,
217 UrlConstraints(
218 host_required=True,
219 allowed_schemes=[
220 'postgres',
221 'postgresql',
222 'postgresql+asyncpg',
223 'postgresql+pg8000',
224 'postgresql+psycopg',
225 'postgresql+psycopg2',
226 'postgresql+psycopg2cffi',
227 'postgresql+py-postgresql',
228 'postgresql+pygresql',
229 ],
230 ),
231]
232"""A type that will accept any Postgres DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
234* User info required
235* TLD not required
236* Host required
237* Supports multiple hosts
239If further validation is required, these properties can be used by validators to enforce specific behaviour:
241```py
242from pydantic import (
243 BaseModel,
244 HttpUrl,
245 PostgresDsn,
246 ValidationError,
247 field_validator,
248)
250class MyModel(BaseModel):
251 url: HttpUrl
253m = MyModel(url='http://www.example.com')
255# the repr() method for a url will display all properties of the url
256print(repr(m.url))
257#> Url('http://www.example.com/')
258print(m.url.scheme)
259#> http
260print(m.url.host)
261#> www.example.com
262print(m.url.port)
263#> 80
265class MyDatabaseModel(BaseModel):
266 db: PostgresDsn
268 @field_validator('db')
269 def check_db_name(cls, v):
270 assert v.path and len(v.path) > 1, 'database must be provided'
271 return v
273m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar')
274print(m.db)
275#> postgres://user:pass@localhost:5432/foobar
277try:
278 MyDatabaseModel(db='postgres://user:pass@localhost:5432')
279except ValidationError as e:
280 print(e)
281 '''
282 1 validation error for MyDatabaseModel
283 db
284 Assertion failed, database must be provided
285 assert (None)
286 + where None = MultiHostUrl('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str]
287 '''
288```
289"""
291CockroachDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
292 Url,
293 UrlConstraints(
294 host_required=True,
295 allowed_schemes=[
296 'cockroachdb',
297 'cockroachdb+psycopg2',
298 'cockroachdb+asyncpg',
299 ],
300 ),
301]
302"""A type that will accept any Cockroach DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
304* User info required
305* TLD not required
306* Host required
307"""
308AmqpDsn = Annotated[Url, UrlConstraints(allowed_schemes=['amqp', 'amqps'])] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
309"""A type that will accept any AMQP DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
311* User info required
312* TLD not required
313* Host required
314"""
315RedisDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
316 Url,
317 UrlConstraints(allowed_schemes=['redis', 'rediss'], default_host='localhost', default_port=6379, default_path='/0'),
318]
319"""A type that will accept any Redis DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
321* User info required
322* TLD not required
323* Host required (e.g., `rediss://:pass@localhost`)
324"""
325MongoDsn = Annotated[MultiHostUrl, UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017)] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
326"""A type that will accept any MongoDB DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
328* User info not required
329* Database name not required
330* Port not required
331* User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`).
332"""
333KafkaDsn = Annotated[Url, UrlConstraints(allowed_schemes=['kafka'], default_host='localhost', default_port=9092)] 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
334"""A type that will accept any Kafka DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
336* User info required
337* TLD not required
338* Host required
339"""
340NatsDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
341 MultiHostUrl, UrlConstraints(allowed_schemes=['nats', 'tls', 'ws'], default_host='localhost', default_port=4222)
342]
343"""A type that will accept any NATS DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
345NATS is a connective technology built for the ever increasingly hyper-connected world.
346It is a single technology that enables applications to securely communicate across
347any combination of cloud vendors, on-premise, edge, web and mobile, and devices.
348More: https://nats.io
349"""
350MySQLDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
351 Url,
352 UrlConstraints(
353 allowed_schemes=[
354 'mysql',
355 'mysql+mysqlconnector',
356 'mysql+aiomysql',
357 'mysql+asyncmy',
358 'mysql+mysqldb',
359 'mysql+pymysql',
360 'mysql+cymysql',
361 'mysql+pyodbc',
362 ],
363 default_port=3306,
364 ),
365]
366"""A type that will accept any MySQL DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
368* User info required
369* TLD not required
370* Host required
371"""
372MariaDBDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
373 Url,
374 UrlConstraints(
375 allowed_schemes=['mariadb', 'mariadb+mariadbconnector', 'mariadb+pymysql'],
376 default_port=3306,
377 ),
378]
379"""A type that will accept any MariaDB DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
381* User info required
382* TLD not required
383* Host required
384"""
385ClickHouseDsn = Annotated[ 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
386 Url,
387 UrlConstraints(
388 allowed_schemes=['clickhouse+native', 'clickhouse+asynch'],
389 default_host='localhost',
390 default_port=9000,
391 ),
392]
393"""A type that will accept any ClickHouse DSN. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
395* User info required
396* TLD not required
397* Host required
398"""
401def import_email_validator() -> None: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
402 global email_validator
403 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
404 import email_validator 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
405 except ImportError as e:
406 raise ImportError('email-validator is not installed, run `pip install pydantic[email]`') from e
407 if not version('email-validator').partition('.')[0] == '2': 407 ↛ 408line 407 didn't jump to line 408 because the condition on line 407 was never true1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
408 raise ImportError('email-validator version >= 2.0 required, run pip install -U email-validator')
411if TYPE_CHECKING: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
412 EmailStr = Annotated[str, ...]
413else:
415 class EmailStr: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
416 """
417 Info:
418 To use this type, you need to install the optional
419 [`email-validator`](https://github.com/JoshData/python-email-validator) package:
421 ```bash
422 pip install email-validator
423 ```
425 Validate email addresses.
427 ```py
428 from pydantic import BaseModel, EmailStr
430 class Model(BaseModel):
431 email: EmailStr
433 print(Model(email='contact@mail.com'))
434 #> email='contact@mail.com'
435 ```
436 """ # noqa: D212
438 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
439 def __get_pydantic_core_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
440 cls,
441 _source: type[Any],
442 _handler: GetCoreSchemaHandler,
443 ) -> core_schema.CoreSchema:
444 import_email_validator() 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
445 return core_schema.no_info_after_validator_function(cls._validate, core_schema.str_schema()) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
447 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
448 def __get_pydantic_json_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
449 cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
450 ) -> JsonSchemaValue:
451 field_schema = handler(core_schema) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
452 field_schema.update(type='string', format='email') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
453 return field_schema 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
455 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
456 def _validate(cls, input_value: str, /) -> str: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
457 return validate_email(input_value)[1] 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
460class NameEmail(_repr.Representation): 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
461 """
462 Info:
463 To use this type, you need to install the optional
464 [`email-validator`](https://github.com/JoshData/python-email-validator) package:
466 ```bash
467 pip install email-validator
468 ```
470 Validate a name and email address combination, as specified by
471 [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4).
473 The `NameEmail` has two properties: `name` and `email`.
474 In case the `name` is not provided, it's inferred from the email address.
476 ```py
477 from pydantic import BaseModel, NameEmail
479 class User(BaseModel):
480 email: NameEmail
482 user = User(email='Fred Bloggs <fred.bloggs@example.com>')
483 print(user.email)
484 #> Fred Bloggs <fred.bloggs@example.com>
485 print(user.email.name)
486 #> Fred Bloggs
488 user = User(email='fred.bloggs@example.com')
489 print(user.email)
490 #> fred.bloggs <fred.bloggs@example.com>
491 print(user.email.name)
492 #> fred.bloggs
493 ```
494 """ # noqa: D212
496 __slots__ = 'name', 'email' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
498 def __init__(self, name: str, email: str): 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
499 self.name = name 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
500 self.email = email 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
502 def __eq__(self, other: Any) -> bool: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
503 return isinstance(other, NameEmail) and (self.name, self.email) == (other.name, other.email) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
505 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
506 def __get_pydantic_json_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
507 cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
508 ) -> JsonSchemaValue:
509 field_schema = handler(core_schema) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
510 field_schema.update(type='string', format='name-email') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
511 return field_schema 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
513 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
514 def __get_pydantic_core_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
515 cls,
516 _source: type[Any],
517 _handler: GetCoreSchemaHandler,
518 ) -> core_schema.CoreSchema:
519 import_email_validator() 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
521 return core_schema.no_info_after_validator_function( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
522 cls._validate,
523 core_schema.json_or_python_schema(
524 json_schema=core_schema.str_schema(),
525 python_schema=core_schema.union_schema(
526 [core_schema.is_instance_schema(cls), core_schema.str_schema()],
527 custom_error_type='name_email_type',
528 custom_error_message='Input is not a valid NameEmail',
529 ),
530 serialization=core_schema.to_string_ser_schema(),
531 ),
532 )
534 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
535 def _validate(cls, input_value: Self | str, /) -> Self: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
536 if isinstance(input_value, str): 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
537 name, email = validate_email(input_value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
538 return cls(name, email) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
539 else:
540 return input_value 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
542 def __str__(self) -> str: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
543 if '@' in self.name: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
544 return f'"{self.name}" <{self.email}>' 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
546 return f'{self.name} <{self.email}>' 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
549class IPvAnyAddress: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
550 """Validate an IPv4 or IPv6 address.
552 ```py
553 from pydantic import BaseModel
554 from pydantic.networks import IPvAnyAddress
556 class IpModel(BaseModel):
557 ip: IPvAnyAddress
559 print(IpModel(ip='127.0.0.1'))
560 #> ip=IPv4Address('127.0.0.1')
562 try:
563 IpModel(ip='http://www.example.com')
564 except ValueError as e:
565 print(e.errors())
566 '''
567 [
568 {
569 'type': 'ip_any_address',
570 'loc': ('ip',),
571 'msg': 'value is not a valid IPv4 or IPv6 address',
572 'input': 'http://www.example.com',
573 }
574 ]
575 '''
576 ```
577 """
579 __slots__ = () 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
581 def __new__(cls, value: Any) -> IPv4Address | IPv6Address: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
582 """Validate an IPv4 or IPv6 address."""
583 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
584 return IPv4Address(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
585 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
586 pass 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
588 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
589 return IPv6Address(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
590 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
591 raise PydanticCustomError('ip_any_address', 'value is not a valid IPv4 or IPv6 address') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
593 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
594 def __get_pydantic_json_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
595 cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
596 ) -> JsonSchemaValue:
597 field_schema = {} 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
598 field_schema.update(type='string', format='ipvanyaddress') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
599 return field_schema 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
601 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
602 def __get_pydantic_core_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
603 cls,
604 _source: type[Any],
605 _handler: GetCoreSchemaHandler,
606 ) -> core_schema.CoreSchema:
607 return core_schema.no_info_plain_validator_function( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
608 cls._validate, serialization=core_schema.to_string_ser_schema()
609 )
611 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
612 def _validate(cls, input_value: Any, /) -> IPv4Address | IPv6Address: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
613 return cls(input_value) # type: ignore[return-value] 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
616class IPvAnyInterface: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
617 """Validate an IPv4 or IPv6 interface."""
619 __slots__ = () 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
621 def __new__(cls, value: NetworkType) -> IPv4Interface | IPv6Interface: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
622 """Validate an IPv4 or IPv6 interface."""
623 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
624 return IPv4Interface(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
625 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
626 pass 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
628 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
629 return IPv6Interface(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
630 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
631 raise PydanticCustomError('ip_any_interface', 'value is not a valid IPv4 or IPv6 interface') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
633 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
634 def __get_pydantic_json_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
635 cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
636 ) -> JsonSchemaValue:
637 field_schema = {} 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
638 field_schema.update(type='string', format='ipvanyinterface') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
639 return field_schema 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
641 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
642 def __get_pydantic_core_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
643 cls,
644 _source: type[Any],
645 _handler: GetCoreSchemaHandler,
646 ) -> core_schema.CoreSchema:
647 return core_schema.no_info_plain_validator_function( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
648 cls._validate, serialization=core_schema.to_string_ser_schema()
649 )
651 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
652 def _validate(cls, input_value: NetworkType, /) -> IPv4Interface | IPv6Interface: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
653 return cls(input_value) # type: ignore[return-value] 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
656IPvAnyNetworkType: TypeAlias = 'IPv4Network | IPv6Network' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
658if TYPE_CHECKING: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
659 IPvAnyNetwork = IPvAnyNetworkType
660else:
662 class IPvAnyNetwork: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
663 """Validate an IPv4 or IPv6 network."""
665 __slots__ = () 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
667 def __new__(cls, value: NetworkType) -> IPvAnyNetworkType: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
668 """Validate an IPv4 or IPv6 network."""
669 # Assume IP Network is defined with a default value for `strict` argument.
670 # Define your own class if you want to specify network address check strictness.
671 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
672 return IPv4Network(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
673 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
674 pass 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
676 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
677 return IPv6Network(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
678 except ValueError: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
679 raise PydanticCustomError('ip_any_network', 'value is not a valid IPv4 or IPv6 network') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
681 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
682 def __get_pydantic_json_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
683 cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
684 ) -> JsonSchemaValue:
685 field_schema = {} 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
686 field_schema.update(type='string', format='ipvanynetwork') 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
687 return field_schema 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
689 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
690 def __get_pydantic_core_schema__( 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
691 cls,
692 _source: type[Any],
693 _handler: GetCoreSchemaHandler,
694 ) -> core_schema.CoreSchema:
695 return core_schema.no_info_plain_validator_function( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
696 cls._validate, serialization=core_schema.to_string_ser_schema()
697 )
699 @classmethod 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
700 def _validate(cls, input_value: NetworkType, /) -> IPvAnyNetworkType: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
701 return cls(input_value) # type: ignore[return-value] 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
704def _build_pretty_email_regex() -> re.Pattern[str]: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
705 name_chars = r'[\w!#$%&\'*+\-/=?^_`{|}~]' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
706 unquoted_name_group = rf'((?:{name_chars}+\s+)*{name_chars}+)' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
707 quoted_name_group = r'"((?:[^"]|\")+)"' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
708 email_group = r'<\s*(.+)\s*>' 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
709 return re.compile(rf'\s*(?:{unquoted_name_group}|{quoted_name_group})?\s*{email_group}\s*') 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
712pretty_email_regex = _build_pretty_email_regex() 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
714MAX_EMAIL_LENGTH = 2048 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
715"""Maximum length for an email. 1bcdefghiajklmnopqMNOPQRSTrstuvwxy
716A somewhat arbitrary but very generous number compared to what is allowed by most implementations.
717"""
720def validate_email(value: str) -> tuple[str, str]: 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy
721 """Email address validation using [email-validator](https://pypi.org/project/email-validator/).
723 Note:
724 Note that:
726 * Raw IP address (literal) domain parts are not allowed.
727 * `"John Doe <local_part@domain.com>"` style "pretty" email addresses are processed.
728 * Spaces are striped from the beginning and end of addresses, but no error is raised.
729 """
730 if email_validator is None: 730 ↛ 731line 730 didn't jump to line 731 because the condition on line 730 was never true1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
731 import_email_validator()
733 if len(value) > MAX_EMAIL_LENGTH: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
734 raise PydanticCustomError( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
735 'value_error',
736 'value is not a valid email address: {reason}',
737 {'reason': f'Length must not exceed {MAX_EMAIL_LENGTH} characters'},
738 )
740 m = pretty_email_regex.fullmatch(value) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
741 name: str | None = None 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
742 if m: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
743 unquoted_name, quoted_name, value = m.groups() 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
744 name = unquoted_name or quoted_name 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
746 email = value.strip() 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
748 try: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
749 parts = email_validator.validate_email(email, check_deliverability=False) 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
750 except email_validator.EmailNotValidError as e: 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
751 raise PydanticCustomError( 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
752 'value_error', 'value is not a valid email address: {reason}', {'reason': str(e.args[0])}
753 ) from e
755 email = parts.normalized 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
756 assert email is not None 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
757 name = name or parts.local_part 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
758 return name, email 1zABCbcdefghiDaEFGHjklmnopqIJKLrstuvwxy
761__getattr__ = getattr_migration(__name__) 1zABCbcdefghiDaEFGHjklmnopqUVMNOPQRSTIJKLrstuvwxy