diff --git a/pyproject.toml b/pyproject.toml index 67dddb9..6228435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "cryptography", "packaging", "pyasn1 ~= 0.6", - "pydantic", + "pydantic >= 2.10.0", "sigstore~=3.4", "sigstore-protobuf-specs", ] diff --git a/src/pypi_attestations/_impl.py b/src/pypi_attestations/_impl.py index daa149f..34c2703 100644 --- a/src/pypi_attestations/_impl.py +++ b/src/pypi_attestations/_impl.py @@ -17,7 +17,7 @@ from packaging.utils import parse_sdist_filename, parse_wheel_filename from pyasn1.codec.der.decoder import decode as der_decode from pyasn1.type.char import UTF8String -from pydantic import Base64Encoder, BaseModel, ConfigDict, EncodedBytes, Field, field_validator +from pydantic import Base64Bytes, BaseModel, ConfigDict, Field, field_validator from pydantic.alias_generators import to_snake from pydantic_core import ValidationError from sigstore._utils import _sha256_streaming @@ -38,30 +38,6 @@ from sigstore.verify.policy import VerificationPolicy -class Base64EncoderSansNewline(Base64Encoder): - r"""A Base64Encoder that doesn't insert newlines when encoding. - - Pydantic's Base64Bytes type inserts newlines b'\n' every 76 characters because they - use `base64.encodebytes()` instead of `base64.b64encode()`. Pydantic maintainers - have stated that they won't fix this, and that users should work around it by - defining their own Base64 type with a custom encoder. - See https://github.com/pydantic/pydantic/issues/9072 for more details. - """ - - @classmethod - def encode(cls, value: bytes) -> bytes: - """Encode bytes to base64.""" - return base64.b64encode(value) - - @classmethod - def decode(cls, value: bytes) -> bytes: - """Decode base64 bytes.""" - return base64.b64decode(value, validate=True) - - -Base64Bytes = Annotated[bytes, EncodedBytes(encoder=Base64EncoderSansNewline)] - - class Distribution(BaseModel): """Represents a Python package distribution. diff --git a/test/test_impl.py b/test/test_impl.py index e7a5bad..0878379 100644 --- a/test/test_impl.py +++ b/test/test_impl.py @@ -9,7 +9,7 @@ import pretend import pytest import sigstore -from pydantic import BaseModel, TypeAdapter, ValidationError +from pydantic import Base64Bytes, BaseModel, TypeAdapter, ValidationError from sigstore.dsse import DigestSet, StatementBuilder, Subject from sigstore.models import Bundle from sigstore.oidc import IdentityToken @@ -614,18 +614,14 @@ def test_version(self) -> None: class DummyModel(BaseModel): - base64_bytes: impl.Base64Bytes + base64_bytes: Base64Bytes class TestBase64Bytes: - # See the docstrings for `_impl.Base64Bytes` for more details - def test_decoding(self) -> None: - # This raises when using our custom type. When using Pydantic's Base64Bytes, - # this succeeds - # The exception message is different in Python 3.9 vs >=3.10 - with pytest.raises(ValueError, match="Non-base64 digit found|Only base64 data is allowed"): - DummyModel(base64_bytes=b"a\n\naaa") - + # Regression test for an issue with pydantic < 2.10.0 + # The Base64Bytes Pydantic type should not insert newlines + # when encoding to base64. + # See https://github.com/pydantic/pydantic/issues/9072 def test_encoding(self) -> None: model = DummyModel(base64_bytes=b"aaaa" * 76) assert "\\n" not in model.model_dump_json()