From 76b8c0b58a693618c789196f61b23f213b212294 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 27 Aug 2026 11:13:10 +0900 Subject: [PATCH] Wire in typing_extensions and set Message generic parameter default --- README.md | 2 +- docs/index.md | 4 ++-- packages/protoc-gen-py/src/protoc_gen_py/__init__.py | 5 +++-- pyproject.toml | 3 +++ src/protobuf/_descriptors.py | 5 +++-- src/protobuf/_field_values.py | 7 ++++--- src/protobuf/_from_binary.py | 3 ++- src/protobuf/_from_json.py | 4 +++- src/protobuf/_jsonschema.py | 9 +++++---- src/protobuf/_message.py | 6 ++++-- src/protobuf/_registry.py | 5 +++-- src/protobuf/_to_binary.py | 3 ++- src/protobuf/_to_json.py | 6 ++++-- src/protobuf/_typing.py | 9 ++------- src/protobuf/_unknown.py | 5 +++-- src/protobuf/_validate.py | 3 ++- src/protobuf/_wire/_text_format.py | 5 +++-- src/protobuf/plugin/_file.py | 5 +++-- src/protobuf/txtpb/_from_text.py | 3 ++- src/protobuf/txtpb/_to_text.py | 3 ++- tests/test_from_binary.py | 2 +- tests/test_new_oneofs.py | 5 +++-- uv.lock | 4 ++++ 23 files changed, 64 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 0f87972..7784e02 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ `protobuf-py` is an **ergonomic and modern Protobuf library** for Python. Well-typed, generated code you can read. High-performance encoder/decoder written in Rust. 100% conformance.
-Zero dependencies or tooling required. +Zero third-party dependencies or tooling required. [Getting started](https://protobufpy.com/getting-started/installation/) • [Examples](https://github.com/bufbuild/protobuf-py/tree/main/examples/protobuf) • diff --git a/docs/index.md b/docs/index.md index 90ec4b2..6238f8a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,8 +3,8 @@ The only modern, complete Python protobuf library. 100% Protobuf conformance with full support for `proto2`, `proto3`, and editions. -Generated code is readable, typed, and works out of the box with no dependencies or extra tooling required. -Native Rust module for high-performance encoding/decoding. +Generated code is readable, typed, and works out of the box with no third-party dependencies +or extra tooling required. Native Rust module for high-performance encoding/decoding. The Pythonic API results in code that you are happy to read. diff --git a/packages/protoc-gen-py/src/protoc_gen_py/__init__.py b/packages/protoc-gen-py/src/protoc_gen_py/__init__.py index 115eb8b..0475835 100644 --- a/packages/protoc-gen-py/src/protoc_gen_py/__init__.py +++ b/packages/protoc-gen-py/src/protoc_gen_py/__init__.py @@ -21,6 +21,8 @@ from itertools import chain from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from protobuf import ( DescEnum, DescEnumValue, @@ -42,7 +44,6 @@ from protobuf._descriptors import SupportedFieldPresence from protobuf._names import proto_camel_case from protobuf._sanitization import escape_extension_name -from protobuf._typing import assert_never from protobuf.plugin import File, Ident, Module, Schema, get_comments, run if TYPE_CHECKING: @@ -484,7 +485,7 @@ def _generate_docstring( f.print("```") -def _provenance_text( # noqa: RET503 +def _provenance_text( desc: DescMessage | DescField | DescEnum diff --git a/pyproject.toml b/pyproject.toml index 48bbd64..f40391a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,9 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] +dependencies = [ + "typing_extensions>=4.13.0", +] [project.urls] Documentation = "https://protobufpy.com/" diff --git a/src/protobuf/_descriptors.py b/src/protobuf/_descriptors.py index 592d7d2..1acc0da 100644 --- a/src/protobuf/_descriptors.py +++ b/src/protobuf/_descriptors.py @@ -17,7 +17,8 @@ from enum import IntEnum from typing import TYPE_CHECKING, Literal, TypeAlias, cast, final -from ._typing import assert_never +from typing_extensions import assert_never + from ._wire import WireType try: @@ -513,7 +514,7 @@ def __post_init__(self) -> None: """The wire type for the unpacked encoding, used to validate when parsing an unpacked repeated field.""" -def element_wire_type( # noqa: RET503 +def element_wire_type( element_type: DescMessage | DescEnum | ScalarType, *, delimited_encoding: bool ) -> WireType: match element_type: diff --git a/src/protobuf/_field_values.py b/src/protobuf/_field_values.py index d0aebf5..33e929a 100644 --- a/src/protobuf/_field_values.py +++ b/src/protobuf/_field_values.py @@ -16,6 +16,8 @@ from math import copysign from typing import Any +from typing_extensions import assert_never + from ._descriptors import ( DescField, DescFieldValue, @@ -29,10 +31,9 @@ ScalarType, SupportedFieldPresence, ) -from ._typing import assert_never -def scalar_zero_value(scalar_type: ScalarType) -> Any: # noqa: RET503 +def scalar_zero_value(scalar_type: ScalarType) -> Any: """Return the zero value for a scalar type.""" match scalar_type: case ScalarType.BOOL: @@ -77,7 +78,7 @@ def requires_presence(member: DescField) -> bool: return False -def default_value(member: DescFieldValue | DescOneof) -> Any: # noqa: RET503 +def default_value(member: DescFieldValue | DescOneof) -> Any: """Return the default value for the given field or oneof.""" match member: case DescOneof() | DescFieldValueMessage(): diff --git a/src/protobuf/_from_binary.py b/src/protobuf/_from_binary.py index 43ab3ad..2b943aa 100644 --- a/src/protobuf/_from_binary.py +++ b/src/protobuf/_from_binary.py @@ -17,6 +17,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, TypeVar +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescFieldValueEnum, @@ -29,7 +31,6 @@ ) from ._enum import Enum from ._field_values import scalar_zero_value -from ._typing import assert_never from ._wire._binary_reader import DEPTH_LIMIT, BinaryReader from ._wire._binary_writer import BinaryWriter from ._wire._wire_type import WireType diff --git a/src/protobuf/_from_json.py b/src/protobuf/_from_json.py index 59bbcb3..722ba39 100644 --- a/src/protobuf/_from_json.py +++ b/src/protobuf/_from_json.py @@ -19,6 +19,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Literal, TypeVar, cast +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescExtension, @@ -32,7 +34,6 @@ ScalarType, ) from ._oneof import Oneof -from ._typing import JsonValue, assert_never from ._validate import ( FLOAT32_MAX, FLOAT32_MIN, @@ -56,6 +57,7 @@ from ._enum import Enum from ._message import Message from ._registry import Registry + from ._typing import JsonValue from .wkt import ListValue, NullValue, Struct, Value T = TypeVar("T", bound=Message) diff --git a/src/protobuf/_jsonschema.py b/src/protobuf/_jsonschema.py index 515fb33..4b0c65c 100644 --- a/src/protobuf/_jsonschema.py +++ b/src/protobuf/_jsonschema.py @@ -20,6 +20,8 @@ from textwrap import dedent from typing import TYPE_CHECKING, Any, TypeAlias +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescField, @@ -31,7 +33,6 @@ DescMessage, ScalarType, ) -from ._typing import assert_never from ._wkt_registry import ( WktAny, WktDuration, @@ -108,7 +109,7 @@ def to_json(self) -> dict[str, JSON]: return res -def _generate_wkt_schema(wkt: WktMatch) -> JsonSchemaField | None: # noqa: RET503 +def _generate_wkt_schema(wkt: WktMatch) -> JsonSchemaField | None: match wkt: case WktTimestamp(): return JsonSchemaField(type="string", format="date-time") @@ -165,7 +166,7 @@ def _scalar_field(scalar: ScalarType, description: str = "") -> JsonSchemaField: return JsonSchemaField(type=_scalar_type(scalar), description=description) -def _scalar_type(s: ScalarType) -> str: # noqa: RET503 +def _scalar_type(s: ScalarType) -> str: match s: case ScalarType.BOOL: return "boolean" @@ -310,7 +311,7 @@ def singular(value: DescMessage | DescEnum | ScalarType) -> CoreSchema: return enum_ref(value) return leaf(_scalar_field(value)) - def field_core(field: DescField) -> CoreSchema: # noqa: RET503 + def field_core(field: DescField) -> CoreSchema: description = _generate_docstring(field) # Scalars carry title/description in their own JSON body. List and dict # schemas are generated by pydantic, so their title/description are merged diff --git a/src/protobuf/_message.py b/src/protobuf/_message.py index eb52534..3715f81 100644 --- a/src/protobuf/_message.py +++ b/src/protobuf/_message.py @@ -14,7 +14,9 @@ from __future__ import annotations from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, overload +from typing import TYPE_CHECKING, Any, ClassVar, Generic, overload + +from typing_extensions import TypeVar from . import _native_message from ._descriptors import ( @@ -50,7 +52,7 @@ Self = TypeVar("Self", bound="Message") # TypeVar for making Message generic over its field names -FieldNamesT = TypeVar("FieldNamesT", bound=str) +FieldNamesT = TypeVar("FieldNamesT", bound=str, default=Any) M = TypeVar("M", bound="Message") E = TypeVar("E") diff --git a/src/protobuf/_registry.py b/src/protobuf/_registry.py index 6b8b9f8..3cab4e7 100644 --- a/src/protobuf/_registry.py +++ b/src/protobuf/_registry.py @@ -16,10 +16,11 @@ from collections import defaultdict from typing import TYPE_CHECKING, TypeVar, final +from typing_extensions import assert_never + from ._descriptors import DescEnum, DescExtension, DescFile, DescMessage, DescService from ._extension import Extension from ._message import Message -from ._typing import assert_never if TYPE_CHECKING: from collections.abc import Iterator @@ -119,7 +120,7 @@ def add( case Registry(): self._extend(desc_or_reg) case _: - assert_never(desc_or_reg) # ty: ignore[invalid-argument-type] # https://github.com/astral-sh/ty/issues/4370 + assert_never(desc_or_reg) # ty: ignore[type-assertion-failure] # https://github.com/astral-sh/ty/issues/4370 def file(self, path: str) -> DescFile | None: """Look up a file descriptor by its path. diff --git a/src/protobuf/_to_binary.py b/src/protobuf/_to_binary.py index 5d092c0..8b0eee5 100644 --- a/src/protobuf/_to_binary.py +++ b/src/protobuf/_to_binary.py @@ -16,6 +16,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescFieldValueEnum, @@ -26,7 +28,6 @@ DescMessage, ScalarType, ) -from ._typing import assert_never from ._wire._binary_writer import BinaryWriter from ._wire._wire_type import WireType diff --git a/src/protobuf/_to_json.py b/src/protobuf/_to_json.py index d2cad88..c1a2216 100644 --- a/src/protobuf/_to_json.py +++ b/src/protobuf/_to_json.py @@ -21,6 +21,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescFieldValueEnum, @@ -33,7 +35,6 @@ SupportedFieldPresence, ) from ._oneof import Oneof -from ._typing import JsonPrimitive, JsonValue, assert_never from ._wkt_registry import is_null_value_enum, match_wkt if TYPE_CHECKING: @@ -41,6 +42,7 @@ from ._message import Message from ._registry import Registry + from ._typing import JsonPrimitive, JsonValue from .wkt import Struct, Value @@ -115,7 +117,7 @@ def _enum_to_json_value( return value -def _container_value_to_json_value( # noqa: RET503 +def _container_value_to_json_value( desc_element: ScalarType | DescEnum | DescMessage, value: Any, opts: ToJsonOptions ) -> JsonValue: match desc_element: diff --git a/src/protobuf/_typing.py b/src/protobuf/_typing.py index fc3d7e4..587e0e8 100644 --- a/src/protobuf/_typing.py +++ b/src/protobuf/_typing.py @@ -12,15 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Small set of helpers and Python 3.10 compatibility shims +# Small set of helpers from __future__ import annotations -from typing import NoReturn as Never, TypeAlias +from typing import TypeAlias JsonPrimitive: TypeAlias = bool | int | float | str | None JsonValue: TypeAlias = JsonPrimitive | list["JsonValue"] | dict[str, "JsonValue"] - - -def assert_never(value: Never) -> Never: - msg = f"unexpected value: {value!r}" - raise AssertionError(msg) diff --git a/src/protobuf/_unknown.py b/src/protobuf/_unknown.py index f9439b1..b2e31c9 100644 --- a/src/protobuf/_unknown.py +++ b/src/protobuf/_unknown.py @@ -15,6 +15,8 @@ from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescFieldValue, @@ -41,7 +43,6 @@ write_message_field, write_scalar_field, ) -from ._typing import assert_never from ._wire import BinaryReader, BinaryWriter if TYPE_CHECKING: @@ -66,7 +67,7 @@ def has_unknown_field( return number in uf -def get_unknown_field( # noqa: RET503 +def get_unknown_field( message: Message, number: int, field_value: DescFieldValue ) -> Any: if not (uf := message._unknown_fields) or not (binary_fields := uf.get(number)): diff --git a/src/protobuf/_validate.py b/src/protobuf/_validate.py index b279a7e..2dbdb91 100644 --- a/src/protobuf/_validate.py +++ b/src/protobuf/_validate.py @@ -16,6 +16,8 @@ import math from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from ._descriptors import ( DescEnum, DescField, @@ -30,7 +32,6 @@ SupportedFieldPresence, ) from ._oneof import Oneof -from ._typing import assert_never if TYPE_CHECKING: from ._message import Message diff --git a/src/protobuf/_wire/_text_format.py b/src/protobuf/_wire/_text_format.py index c50d6dc..bab0233 100644 --- a/src/protobuf/_wire/_text_format.py +++ b/src/protobuf/_wire/_text_format.py @@ -15,8 +15,9 @@ import re +from typing_extensions import assert_never + from protobuf._descriptors import DescEnum, ScalarType -from protobuf._typing import assert_never def parse_text_format_enum_value(desc: DescEnum, value: str) -> int: @@ -28,7 +29,7 @@ def parse_text_format_enum_value(desc: DescEnum, value: str) -> int: return value_desc.number -def parse_text_format_scalar_value( # noqa: RET503 +def parse_text_format_scalar_value( scalar: ScalarType, value: str ) -> int | float | bytes | str | bool: """Parse a scalar value from the Protobuf text format.""" diff --git a/src/protobuf/plugin/_file.py b/src/protobuf/plugin/_file.py index f957404..6e8f5cf 100644 --- a/src/protobuf/plugin/_file.py +++ b/src/protobuf/plugin/_file.py @@ -18,8 +18,9 @@ from contextlib import AbstractContextManager, contextmanager from typing import TYPE_CHECKING, Any, Final, Protocol +from typing_extensions import assert_never + from protobuf import DescEnum, DescExtension, DescFile, DescMessage, ScalarType -from protobuf._typing import assert_never from protobuf.plugin._ident import Ident, Module if TYPE_CHECKING: @@ -555,7 +556,7 @@ def _resolve(self, ident: Ident) -> str: return self._resolve(Ident(candidate, ident.module)) or candidate -def _scalar_type(scalar: ScalarType) -> str: # noqa: RET503 +def _scalar_type(scalar: ScalarType) -> str: """Map a protobuf scalar type to its Python type name.""" match scalar: case ScalarType.DOUBLE | ScalarType.FLOAT: diff --git a/src/protobuf/txtpb/_from_text.py b/src/protobuf/txtpb/_from_text.py index 1932bf4..f12af38 100644 --- a/src/protobuf/txtpb/_from_text.py +++ b/src/protobuf/txtpb/_from_text.py @@ -19,6 +19,8 @@ from enum import Enum as _StdEnum from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from protobuf._descriptors import ( DescEnum, DescExtension, @@ -33,7 +35,6 @@ ) from protobuf._field_values import scalar_zero_value from protobuf._from_json import _read_int -from protobuf._typing import assert_never from protobuf._wire._binary_reader import DEPTH_LIMIT from ._to_text import fround, group_like_message diff --git a/src/protobuf/txtpb/_to_text.py b/src/protobuf/txtpb/_to_text.py index 8daf42a..d32461f 100644 --- a/src/protobuf/txtpb/_to_text.py +++ b/src/protobuf/txtpb/_to_text.py @@ -20,6 +20,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from typing_extensions import assert_never + from protobuf._descriptors import ( DescEnum, DescField, @@ -31,7 +33,6 @@ DescMessage, ScalarType, ) -from protobuf._typing import assert_never from protobuf._wire import BinaryReader, WireType from protobuf._wire._binary_reader import DEPTH_LIMIT diff --git a/tests/test_from_binary.py b/tests/test_from_binary.py index bc93e29..4894658 100644 --- a/tests/test_from_binary.py +++ b/tests/test_from_binary.py @@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Any import pytest +from typing_extensions import assert_never from protobuf import ( DescEnum, @@ -26,7 +27,6 @@ Message, ScalarType, ) -from protobuf._typing import assert_never from protobuf._wire import BinaryWriter, WireType from .gen.delimited_encoding_pb import DelimitedEncoding diff --git a/tests/test_new_oneofs.py b/tests/test_new_oneofs.py index 5821632..3ce1f70 100644 --- a/tests/test_new_oneofs.py +++ b/tests/test_new_oneofs.py @@ -15,8 +15,9 @@ from typing import Literal, TypeAlias +from typing_extensions import assert_never + from protobuf import Oneof -from protobuf._typing import assert_never from tests.gen.messages_pb import MixedFields # Type alias for pattern matching test @@ -44,7 +45,7 @@ def test_oneof_creation() -> None: def test_oneof_pattern_matching() -> None: """Test pattern matching with Oneof for type narrowing.""" - def process_value(val: StringOrInt) -> str: # noqa: RET503 + def process_value(val: StringOrInt) -> str: match val: case Oneof(field="text", value=v): # v should be narrowed to str diff --git a/uv.lock b/uv.lock index 2589194..4298ca6 100644 --- a/uv.lock +++ b/uv.lock @@ -729,6 +729,9 @@ wheels = [ name = "protobuf-py" version = "0.3.0" source = { editable = "." } +dependencies = [ + { name = "typing-extensions" }, +] [package.dev-dependencies] dev = [ @@ -791,6 +794,7 @@ docs = [ ] [package.metadata] +requires-dist = [{ name = "typing-extensions", specifier = ">=4.13.0" }] [package.metadata.requires-dev] dev = [