From nobody Sat Oct 26 23:32:59 2024 X-Original-To: dev-commits-ports-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4XbbYS2DgTz5bJP0; Sat, 26 Oct 2024 23:33:00 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R10" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4XbbYS0vc4z4TNH; Sat, 26 Oct 2024 23:33:00 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1729985580; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EE01R+AbBYZ8M9MlQnNcTku4yPUTHT80h+H7RbhMvo4=; b=PBvuxzy5yloiK7CsHjJ+KuH21pU4neKZgUqOsC8Gt6GJjz1EOlcHBPsyeKkmhjxTKyE6DQ f0LVVXOFhanK3PV25iAAmWabgFNWRaTa9BN/zPwHIm3AB1PGsi4/Twok24DasB2MNZQO3R 7xODS3F4Uo2u1xtts9P3ZNqzBxG8026KlttqZU2UKa6LJeHs+/TEyJtqPJu5MyyGT/gvCG yD6wQXJjNxNAYEhbu/1nL0WujbnFvd2nrwT1hA5G1DpFFK60RfEXRuI/W6gVD9iMqcZ77g enqwke9qQdzj4X5z7+1AlgNODm5/Kp1cB1pEHGc20N8cHHy5H4bW60M0jFQWyQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1729985580; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EE01R+AbBYZ8M9MlQnNcTku4yPUTHT80h+H7RbhMvo4=; b=NoqOX+ScEvQFlrNNmpJMc4NC4Tt6ROM2y9S2OEyfi8FHl2fUwITpnqCijDsE0LCK5LhqS4 pZ3v7Ar4kZG96M+yWte6NPQPtFyLddnLzxx+IE3UWE03kqr4chKXK+l/1OTa/QbeotZ/fg uvtpiu30PpN9QbdBWrp6VGsBuyPwDe1Hhc8lI0xmoNcRLS/8uvOxDpkEegZXe60VM4zoWq UVSkIswQkcz5Rzki/Uc65f229CSlEIOfx+MNOGqw1MoSiwtQcgQx8uzM0aNAlvxqlb+lEu MvD0DoaRmkCi8GnUknaJZHVwAqum7jIz0v/8AdqL6tLfnyVtc7Tf2bSPMXYGfA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1729985580; a=rsa-sha256; cv=none; b=IvWYwJEBNNnjTXb/dJrHZ2N1GwsQoO/HbrAMns4PK0UHdNi99ad7uiDwlt/+njJf9nVPSc 0l6s1NiKMLIfmJf5EOTNHsnlTTyj4WjrSzjq4yy6ArR0I4RMHSebwgyzTCBuHnNb2nbklo WBdkNVEv6WM0ngiHZGefogIJiReMnnU1GUxHU6343g48iI89rBtu5CzHpbip158I/tSqS7 6/Msp+BTJXsljKLc1MZ5MT0y2ra2MEnXf5N1RlrLx5odmRfk3woMReWNwvgRR/Nctje4JX D3AU6SvZW9QZPleyZGIo6/avSUPlGAPKR6qOnU8NgYHML9xuzqDkCgIF5Qxyug== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4XbbYR710yzW1x; Sat, 26 Oct 2024 23:32:59 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.18.1/8.18.1) with ESMTP id 49QNWx03043678; Sat, 26 Oct 2024 23:32:59 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 49QNWx0W043675; Sat, 26 Oct 2024 23:32:59 GMT (envelope-from git) Date: Sat, 26 Oct 2024 23:32:59 GMT Message-Id: <202410262332.49QNWx0W043675@gitrepo.freebsd.org> To: ports-committers@FreeBSD.org, dev-commits-ports-all@FreeBSD.org, dev-commits-ports-main@FreeBSD.org From: Po-Chuan Hsieh Subject: git: ab86f311f99e - main - devel/py-pydantic2: Allow build with py-pydantic-core 2.25.0 List-Id: Commits to the main branch of the FreeBSD ports repository List-Archive: https://lists.freebsd.org/archives/dev-commits-ports-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-ports-main@freebsd.org Sender: owner-dev-commits-ports-main@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: sunpoet X-Git-Repository: ports X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: ab86f311f99e6c9d92e0572a7f474bb857580d5f Auto-Submitted: auto-generated The branch main has been updated by sunpoet: URL: https://cgit.FreeBSD.org/ports/commit/?id=ab86f311f99e6c9d92e0572a7f474bb857580d5f commit ab86f311f99e6c9d92e0572a7f474bb857580d5f Author: Po-Chuan Hsieh AuthorDate: 2024-10-26 23:26:40 +0000 Commit: Po-Chuan Hsieh CommitDate: 2024-10-26 23:26:40 +0000 devel/py-pydantic2: Allow build with py-pydantic-core 2.25.0 - Bump PORTREVISION for package change Obtained from: https://github.com/pydantic/pydantic/commit/27afdfc9120c7a5b1071d907a25c37b46b103292 --- devel/py-pydantic2/Makefile | 4 +- devel/py-pydantic2/files/patch-pydantic-core | 768 ++++++++++++++++++++++++++- 2 files changed, 769 insertions(+), 3 deletions(-) diff --git a/devel/py-pydantic2/Makefile b/devel/py-pydantic2/Makefile index 213ebb1c44ad..62e98e887b3f 100644 --- a/devel/py-pydantic2/Makefile +++ b/devel/py-pydantic2/Makefile @@ -1,6 +1,6 @@ PORTNAME= pydantic PORTVERSION= 2.9.2 -PORTREVISION= 3 +PORTREVISION= 4 CATEGORIES= devel python MASTER_SITES= PYPI PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} @@ -17,7 +17,7 @@ LICENSE_FILE= ${WRKSRC}/LICENSE BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}hatch-fancy-pypi-readme>=22.5.0:devel/py-hatch-fancy-pypi-readme@${PY_FLAVOR} \ ${PYTHON_PKGNAMEPREFIX}hatchling>=0:devel/py-hatchling@${PY_FLAVOR} RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}annotated-types>=0.6.0:devel/py-annotated-types@${PY_FLAVOR} \ - ${PYTHON_PKGNAMEPREFIX}pydantic-core>=2.24.2<2.24.2_99:devel/py-pydantic-core@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}pydantic-core>=2.25.0<2.25.0_99:devel/py-pydantic-core@${PY_FLAVOR} \ ${PYTHON_PKGNAMEPREFIX}typing-extensions>=4.12.2:devel/py-typing-extensions@${PY_FLAVOR} USES= python diff --git a/devel/py-pydantic2/files/patch-pydantic-core b/devel/py-pydantic2/files/patch-pydantic-core index 6d55d0804033..5e393098a21a 100644 --- a/devel/py-pydantic2/files/patch-pydantic-core +++ b/devel/py-pydantic2/files/patch-pydantic-core @@ -1,5 +1,6 @@ Obtained from: https://github.com/pydantic/pydantic/commit/9b69920888054df4ef544ecbdc03e09b90646ac2 https://github.com/pydantic/pydantic/commit/51cf3cbfa767abfba1048cae41ea766d6add4f4a + https://github.com/pydantic/pydantic/commit/27afdfc9120c7a5b1071d907a25c37b46b103292 --- pydantic/_internal/_config.py.orig 2020-02-02 00:00:00 UTC +++ pydantic/_internal/_config.py @@ -124,6 +125,761 @@ Obtained from: https://github.com/pydantic/pydantic/commit/9b69920888054df4ef544 return {'type': 'number'} return {'type': 'string', 'format': 'duration'} +--- pydantic/networks.py.orig 2020-02-02 00:00:00 UTC ++++ pydantic/networks.py +@@ -4,13 +4,16 @@ import re + + import dataclasses as _dataclasses + import re ++from dataclasses import fields + from importlib.metadata import version + from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +-from typing import TYPE_CHECKING, Any ++from typing import TYPE_CHECKING, Any, ClassVar + + from pydantic_core import MultiHostUrl, PydanticCustomError, Url, core_schema + from typing_extensions import Annotated, Self, TypeAlias + ++from pydantic.errors import PydanticUserError ++ + from ._internal import _fields, _repr, _schema_generation_shared + from ._migration import getattr_migration + from .annotated_handlers import GetCoreSchemaHandler +@@ -86,136 +89,294 @@ class UrlConstraints(_fields.PydanticMetadata): + ) + ) + ++ @property ++ def defined_constraints(self) -> dict[str, Any]: ++ """Fetch a key / value mapping of constraints to values that are not None. Used for core schema updates.""" ++ return {field.name: getattr(self, field.name) for field in fields(self)} + +-AnyUrl = Url +-"""Base type for all URLs. + +-* Any scheme allowed +-* Top-level domain (TLD) not required +-* Host required ++# TODO: there's a lot of repeated code in these two base classes - should we consolidate, or does that up ++# the complexity enough that it's not worth saving a few lines? + +-Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`, +-the types export the following properties: + +-- `scheme`: the URL scheme (`http`), always set. +-- `host`: the URL host (`example.com`), always set. +-- `username`: optional username if included (`samuel`). +-- `password`: optional password if included (`pass`). +-- `port`: optional port (`8000`). +-- `path`: optional path (`/the/path/`). +-- `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`). +-- `fragment`: optional fragment (`fragment=is;this=bit`). +-""" +-AnyHttpUrl = Annotated[Url, UrlConstraints(allowed_schemes=['http', 'https'])] +-"""A type that will accept any http or https URL. ++class _BaseUrl(Url): ++ _constraints: ClassVar[UrlConstraints] = UrlConstraints() + +-* TLD not required +-* Host required +-""" +-HttpUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=['http', 'https'])] +-"""A type that will accept any http or https URL. ++ @classmethod ++ def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: ++ if issubclass(cls, source): ++ return core_schema.url_schema(**cls._constraints.defined_constraints) ++ else: ++ schema = handler(source) ++ # TODO: this logic is used in types.py as well in the _check_annotated_type function, should we move that to somewhere more central? ++ if annotated_type := schema['type'] not in ('url', 'multi-host-url'): ++ raise PydanticUserError( ++ f"'{cls.__name__}' cannot annotate '{annotated_type}'.", code='invalid-annotated-type' ++ ) ++ for constraint_key, constraint_value in cls._constraints.defined_constraints.items(): ++ schema[constraint_key] = constraint_value ++ return schema + +-* TLD not required +-* Host required +-* Max length 2083 + +-```py +-from pydantic import BaseModel, HttpUrl, ValidationError ++class _BaseMultiHostUrl(MultiHostUrl): ++ _constraints: ClassVar[UrlConstraints] = UrlConstraints() + +-class MyModel(BaseModel): +- url: HttpUrl ++ @classmethod ++ def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: ++ if issubclass(cls, source): ++ return core_schema.multi_host_url_schema(**cls._constraints.defined_constraints) ++ else: ++ schema = handler(source) ++ # TODO: this logic is used in types.py as well in the _check_annotated_type function, should we move that to somewhere more central? ++ if annotated_type := schema['type'] not in ('url', 'multi-host-url'): ++ raise PydanticUserError( ++ f"'{cls.__name__}' cannot annotate '{annotated_type}'.", code='invalid-annotated-type' ++ ) ++ for constraint_key, constraint_value in cls._constraints.defined_constraints.items(): ++ schema[constraint_key] = constraint_value ++ return schema + +-m = MyModel(url='http://www.example.com') # (1)! +-print(m.url) +-#> http://www.example.com/ + +-try: +- MyModel(url='ftp://invalid.url') +-except ValidationError as e: +- print(e) +- ''' +- 1 validation error for MyModel +- url +- URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str] +- ''' ++class AnyUrl(_BaseUrl): ++ """Base type for all URLs. + +-try: +- MyModel(url='not a url') +-except ValidationError as e: +- print(e) +- ''' +- 1 validation error for MyModel +- url +- Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str] +- ''' +-``` ++ * Any scheme allowed ++ * Top-level domain (TLD) not required ++ * Host required + +-1. Note: mypy would prefer `m = MyModel(url=HttpUrl('http://www.example.com'))`, but Pydantic will convert the string to an HttpUrl instance anyway. ++ Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`, ++ the types export the following properties: + +-"International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via +-[punycode](https://en.wikipedia.org/wiki/Punycode) (see +-[this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important): ++ - `scheme`: the URL scheme (`http`), always set. ++ - `host`: the URL host (`example.com`), always set. ++ - `username`: optional username if included (`samuel`). ++ - `password`: optional password if included (`pass`). ++ - `port`: optional port (`8000`). ++ - `path`: optional path (`/the/path/`). ++ - `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`). ++ - `fragment`: optional fragment (`fragment=is;this=bit`). ++ """ + +-```py +-from pydantic import BaseModel, HttpUrl ++ _constraints = UrlConstraints(host_required=True) + +-class MyModel(BaseModel): +- url: HttpUrl ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + +-m1 = MyModel(url='http://puny£code.com') +-print(m1.url) +-#> http://xn--punycode-eja.com/ +-m2 = MyModel(url='https://www.аррӏе.com/') +-print(m2.url) +-#> https://www.xn--80ak6aa92e.com/ +-m3 = MyModel(url='https://www.example.珠宝/') +-print(m3.url) +-#> https://www.example.xn--pbt977c/ +-``` + ++class AnyHttpUrl(_BaseUrl): ++ """A type that will accept any http or https URL. + +-!!! warning "Underscores in Hostnames" +- In Pydantic, underscores are allowed in all parts of a domain except the TLD. +- Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can. ++ * TLD not required ++ * Host required ++ """ + +- To explain this; consider the following two cases: ++ _constraints = UrlConstraints(host_required=True, allowed_schemes=['http', 'https']) + +- - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore. +- - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain. ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + +- Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore +- underscores are allowed, but you can always do further validation in a validator if desired. + +- Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good +- (or at least big) company. +-""" +-AnyWebsocketUrl = Annotated[Url, UrlConstraints(allowed_schemes=['ws', 'wss'])] +-"""A type that will accept any ws or wss URL. ++class HttpUrl(_BaseUrl): ++ """A type that will accept any http or https URL. + +-* TLD not required +-* Host required +-""" +-WebsocketUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=['ws', 'wss'])] +-"""A type that will accept any ws or wss URL. ++ * TLD not required ++ * Host required ++ * Max length 2083 + +-* TLD not required +-* Host required +-* Max length 2083 +-""" +-FileUrl = Annotated[Url, UrlConstraints(allowed_schemes=['file'])] +-"""A type that will accept any file URL. ++ ```py ++ from pydantic import BaseModel, HttpUrl, ValidationError + +-* Host not required +-""" +-FtpUrl = Annotated[Url, UrlConstraints(allowed_schemes=['ftp'])] +-"""A type that will accept ftp URL. ++ class MyModel(BaseModel): ++ url: HttpUrl + +-* TLD not required +-* Host required +-""" +-PostgresDsn = Annotated[ +- MultiHostUrl, +- UrlConstraints( ++ m = MyModel(url='http://www.example.com') # (1)! ++ print(m.url) ++ #> http://www.example.com/ ++ ++ try: ++ MyModel(url='ftp://invalid.url') ++ except ValidationError as e: ++ print(e) ++ ''' ++ 1 validation error for MyModel ++ url ++ URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str] ++ ''' ++ ++ try: ++ MyModel(url='not a url') ++ except ValidationError as e: ++ print(e) ++ ''' ++ 1 validation error for MyModel ++ url ++ Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str] ++ ''' ++ ``` ++ ++ 1. Note: mypy would prefer `m = MyModel(url=HttpUrl('http://www.example.com'))`, but Pydantic will convert the string to an HttpUrl instance anyway. ++ ++ "International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via ++ [punycode](https://en.wikipedia.org/wiki/Punycode) (see ++ [this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important): ++ ++ ```py ++ from pydantic import BaseModel, HttpUrl ++ ++ class MyModel(BaseModel): ++ url: HttpUrl ++ ++ m1 = MyModel(url='http://puny£code.com') ++ print(m1.url) ++ #> http://xn--punycode-eja.com/ ++ m2 = MyModel(url='https://www.аррӏе.com/') ++ print(m2.url) ++ #> https://www.xn--80ak6aa92e.com/ ++ m3 = MyModel(url='https://www.example.珠宝/') ++ print(m3.url) ++ #> https://www.example.xn--pbt977c/ ++ ``` ++ ++ ++ !!! warning "Underscores in Hostnames" ++ In Pydantic, underscores are allowed in all parts of a domain except the TLD. ++ Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can. ++ ++ To explain this; consider the following two cases: ++ ++ - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore. ++ - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain. ++ ++ Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore ++ underscores are allowed, but you can always do further validation in a validator if desired. ++ ++ Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good ++ (or at least big) company. ++ """ ++ ++ _constraints = UrlConstraints(max_length=2083, allowed_schemes=['http', 'https'], host_required=True) ++ ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class AnyWebsocketUrl(_BaseUrl): ++ """A type that will accept any ws or wss URL. ++ ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints(allowed_schemes=['ws', 'wss'], host_required=True) ++ ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class WebsocketUrl(_BaseUrl): ++ """A type that will accept any ws or wss URL. ++ ++ * TLD not required ++ * Host required ++ * Max length 2083 ++ """ ++ ++ _constraints = UrlConstraints(max_length=2083, allowed_schemes=['ws', 'wss'], host_required=True) ++ ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class FileUrl(_BaseUrl): ++ """A type that will accept any file URL. ++ ++ * Host not required ++ """ ++ ++ _constraints = UrlConstraints(allowed_schemes=['file']) ++ ++ ++class FtpUrl(_BaseUrl): ++ """A type that will accept ftp URL. ++ ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints(allowed_schemes=['ftp'], host_required=True) ++ ++ ++class PostgresDsn(_BaseMultiHostUrl): ++ """A type that will accept any Postgres DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ * Supports multiple hosts ++ ++ If further validation is required, these properties can be used by validators to enforce specific behaviour: ++ ++ ```py ++ from pydantic import ( ++ BaseModel, ++ HttpUrl, ++ PostgresDsn, ++ ValidationError, ++ field_validator, ++ ) ++ ++ class MyModel(BaseModel): ++ url: HttpUrl ++ ++ m = MyModel(url='http://www.example.com') ++ ++ # the repr() method for a url will display all properties of the url ++ print(repr(m.url)) ++ #> Url('http://www.example.com/') ++ print(m.url.scheme) ++ #> http ++ print(m.url.host) ++ #> www.example.com ++ print(m.url.port) ++ #> 80 ++ ++ class MyDatabaseModel(BaseModel): ++ db: PostgresDsn ++ ++ @field_validator('db') ++ def check_db_name(cls, v): ++ assert v.path and len(v.path) > 1, 'database must be provided' ++ return v ++ ++ m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar') ++ print(m.db) ++ #> postgres://user:pass@localhost:5432/foobar ++ ++ try: ++ MyDatabaseModel(db='postgres://user:pass@localhost:5432') ++ except ValidationError as e: ++ print(e) ++ ''' ++ 1 validation error for MyDatabaseModel ++ db ++ Assertion failed, database must be provided ++ assert (None) ++ + where None = MultiHostUrl('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str] ++ ''' ++ ``` ++ """ ++ ++ _constraints = UrlConstraints( + host_required=True, + allowed_schemes=[ + 'postgres', +@@ -228,130 +389,118 @@ PostgresDsn = Annotated[ + 'postgresql+py-postgresql', + 'postgresql+pygresql', + ], +- ), +-] +-"""A type that will accept any Postgres DSN. ++ ) + +-* User info required +-* TLD not required +-* Host required +-* Supports multiple hosts ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + +-If further validation is required, these properties can be used by validators to enforce specific behaviour: + +-```py +-from pydantic import ( +- BaseModel, +- HttpUrl, +- PostgresDsn, +- ValidationError, +- field_validator, +-) ++class CockroachDsn(_BaseUrl): ++ """A type that will accept any Cockroach DSN. + +-class MyModel(BaseModel): +- url: HttpUrl ++ * User info required ++ * TLD not required ++ * Host required ++ """ + +-m = MyModel(url='http://www.example.com') ++ _constraints = UrlConstraints( ++ host_required=True, ++ allowed_schemes=[ ++ 'cockroachdb', ++ 'cockroachdb+psycopg2', ++ 'cockroachdb+asyncpg', ++ ], ++ ) + +-# the repr() method for a url will display all properties of the url +-print(repr(m.url)) +-#> Url('http://www.example.com/') +-print(m.url.scheme) +-#> http +-print(m.url.host) +-#> www.example.com +-print(m.url.port) +-#> 80 ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + +-class MyDatabaseModel(BaseModel): +- db: PostgresDsn + +- @field_validator('db') +- def check_db_name(cls, v): +- assert v.path and len(v.path) > 1, 'database must be provided' +- return v ++class AmqpDsn(_BaseUrl): ++ """A type that will accept any AMQP DSN. + +-m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar') +-print(m.db) +-#> postgres://user:pass@localhost:5432/foobar ++ * User info required ++ * TLD not required ++ * Host not required ++ """ + +-try: +- MyDatabaseModel(db='postgres://user:pass@localhost:5432') +-except ValidationError as e: +- print(e) +- ''' +- 1 validation error for MyDatabaseModel +- db +- Assertion failed, database must be provided +- assert (None) +- + where None = MultiHostUrl('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str] +- ''' +-``` +-""" ++ _constraints = UrlConstraints(allowed_schemes=['amqp', 'amqps']) + +-CockroachDsn = Annotated[ +- Url, +- UrlConstraints( ++ ++class RedisDsn(_BaseUrl): ++ """A type that will accept any Redis DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required (e.g., `rediss://:pass@localhost`) ++ """ ++ ++ _constraints = UrlConstraints( ++ allowed_schemes=['redis', 'rediss'], ++ default_host='localhost', ++ default_port=6379, ++ default_path='/0', + host_required=True, +- allowed_schemes=[ +- 'cockroachdb', +- 'cockroachdb+psycopg2', +- 'cockroachdb+asyncpg', +- ], +- ), +-] +-"""A type that will accept any Cockroach DSN. ++ ) + +-* User info required +-* TLD not required +-* Host required +-""" +-AmqpDsn = Annotated[Url, UrlConstraints(allowed_schemes=['amqp', 'amqps'])] +-"""A type that will accept any AMQP DSN. ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + +-* User info required +-* TLD not required +-* Host required +-""" +-RedisDsn = Annotated[ +- Url, +- UrlConstraints(allowed_schemes=['redis', 'rediss'], default_host='localhost', default_port=6379, default_path='/0'), +-] +-"""A type that will accept any Redis DSN. + +-* User info required +-* TLD not required +-* Host required (e.g., `rediss://:pass@localhost`) +-""" +-MongoDsn = Annotated[MultiHostUrl, UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017)] +-"""A type that will accept any MongoDB DSN. ++class MongoDsn(_BaseMultiHostUrl): ++ """A type that will accept any MongoDB DSN. + +-* User info not required +-* Database name not required +-* Port not required +-* User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`). +-""" +-KafkaDsn = Annotated[Url, UrlConstraints(allowed_schemes=['kafka'], default_host='localhost', default_port=9092)] +-"""A type that will accept any Kafka DSN. ++ * User info not required ++ * Database name not required ++ * Port not required ++ * User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`). ++ """ + +-* User info required +-* TLD not required +-* Host required +-""" +-NatsDsn = Annotated[ +- MultiHostUrl, +- UrlConstraints(allowed_schemes=['nats', 'tls', 'ws', 'wss'], default_host='localhost', default_port=4222), +-] +-"""A type that will accept any NATS DSN. ++ _constraints = UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017) + +-NATS is a connective technology built for the ever increasingly hyper-connected world. +-It is a single technology that enables applications to securely communicate across +-any combination of cloud vendors, on-premise, edge, web and mobile, and devices. +-More: https://nats.io +-""" +-MySQLDsn = Annotated[ +- Url, +- UrlConstraints( ++ ++class KafkaDsn(_BaseUrl): ++ """A type that will accept any Kafka DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints( ++ allowed_schemes=['kafka'], default_host='localhost', default_port=9092, host_required=True ++ ) ++ ++ ++class NatsDsn(_BaseMultiHostUrl): ++ """A type that will accept any NATS DSN. ++ ++ NATS is a connective technology built for the ever increasingly hyper-connected world. ++ It is a single technology that enables applications to securely communicate across ++ any combination of cloud vendors, on-premise, edge, web and mobile, and devices. ++ More: https://nats.io ++ """ ++ ++ _constraints = UrlConstraints( ++ allowed_schemes=['nats', 'tls', 'ws', 'wss'], default_host='localhost', default_port=4222 ++ ) ++ ++ ++class MySQLDsn(_BaseUrl): ++ """A type that will accept any MySQL DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints( + allowed_schemes=[ + 'mysql', + 'mysql+mysqlconnector', +@@ -363,54 +512,73 @@ MySQLDsn = Annotated[ + 'mysql+pyodbc', + ], + default_port=3306, +- ), +-] +-"""A type that will accept any MySQL DSN. ++ host_required=True, ++ ) + +-* User info required +-* TLD not required +-* Host required +-""" +-MariaDBDsn = Annotated[ +- Url, +- UrlConstraints( ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class MariaDBDsn(_BaseUrl): ++ """A type that will accept any MariaDB DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints( + allowed_schemes=['mariadb', 'mariadb+mariadbconnector', 'mariadb+pymysql'], + default_port=3306, +- ), +-] +-"""A type that will accept any MariaDB DSN. ++ host_required=True, ++ ) + +-* User info required +-* TLD not required +-* Host required +-""" +-ClickHouseDsn = Annotated[ +- Url, +- UrlConstraints( ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class ClickHouseDsn(_BaseUrl): ++ """A type that will accept any ClickHouse DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints( + allowed_schemes=['clickhouse+native', 'clickhouse+asynch'], + default_host='localhost', + default_port=9000, +- ), +-] +-"""A type that will accept any ClickHouse DSN. ++ host_required=True, ++ ) + +-* User info required +-* TLD not required +-* Host required +-""" +-SnowflakeDsn = Annotated[ +- Url, +- UrlConstraints( ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... ++ ++ ++class SnowflakeDsn(_BaseUrl): ++ """A type that will accept any Snowflake DSN. ++ ++ * User info required ++ * TLD not required ++ * Host required ++ """ ++ ++ _constraints = UrlConstraints( + allowed_schemes=['snowflake'], + host_required=True, +- ), +-] +-"""A type that will accept any Snowflake DSN. ++ ) + +-* User info required +-* TLD not required +-* Host required +-""" ++ @property ++ def host(self) -> str: ++ """The required URL host.""" ++ ... + + + def import_email_validator() -> None: --- pydantic/warnings.py.orig 2020-02-02 00:00:00 UTC +++ pydantic/warnings.py @@ -67,6 +67,13 @@ class PydanticDeprecatedSince29(PydanticDeprecationWar @@ -147,7 +903,7 @@ Obtained from: https://github.com/pydantic/pydantic/commit/9b69920888054df4ef544 'typing-extensions>=4.12.2; python_version >= "3.13"', 'annotated-types>=0.6.0', - "pydantic-core==2.23.4", -+ "pydantic-core==2.24.2", ++ "pydantic-core==2.25.0", ] dynamic = ['version', 'readme'] @@ -298,3 +1054,13 @@ Obtained from: https://github.com/pydantic/pydantic/commit/9b69920888054df4ef544 @pytest.mark.parametrize( +--- tests/test_networks.py.orig 2020-02-02 00:00:00 UTC ++++ tests/test_networks.py +@@ -107,7 +107,6 @@ except ImportError: + 'http://example.org/path#fragment', + 'http://example.org/path?query#', + 'http://example.org/path?query#fragment', +- 'file://localhost/foo/bar', + ], + ) + def test_any_url_success(value):