Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Do not reset a one-of group with a None value #174

Merged
merged 1 commit into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ build:
.PHONY: docs
docs:
poetry run mkdocs build --site-dir _site

.PHONY: docs/serve
docs/serve:
poetry run mkdocs serve
11 changes: 10 additions & 1 deletion docs/annotating_fields.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Annotating fields

Field types are specified via [`#!python Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) [type hints](https://www.python.org/dev/peps/pep-0484/). Each field may include a `#!python pure_protobuf.annotations.Field` annotation, otherwise it gets ignored by `#!python BaseMessage`. For older Python versions one can use `#!python typing_extensions.Annotated`.
Field types are specified via [`#!python Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) [type hints](https://www.python.org/dev/peps/pep-0484/). Each field may include a [`#!python Field`][pure_protobuf.annotations.Field] annotation, otherwise it gets ignored by `#!python BaseMessage`. For older Python versions one can use `#!python typing_extensions.Annotated`.

::: pure_protobuf.annotations.Field
options:
show_root_heading: true
heading_level: 2

## Supported types

Expand Down Expand Up @@ -255,3 +260,7 @@ assert message.which_one() == "bar"
- When assigning a one-of member, `#!python BaseMessage` resets the other fields to `#!python None`, **regardless** of any defaults defined by, for example, `#!python dataclasses.field`.
- The `#!python OneOf` descriptor simply iterates over its members in order to return an assigned `Oneof` value, so it takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time).
- It's impossible to set a value via a `OneOf` descriptor, one needs to assign the value to a specific attribute.

::: pure_protobuf.one_of.OneOf
options:
heading_level: 3
12 changes: 9 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,28 @@ theme:
- navigation.footer
- navigation.indexes
- navigation.instant
- navigation.instant.progress
- navigation.sections
# - navigation.tabs
- navigation.top
- navigation.tracking
- search.suggest
- search.highlight
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: System theme
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
name: Dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
name: Light mode

plugins:
- git-revision-date-localized:
Expand Down Expand Up @@ -87,7 +93,7 @@ extra:
provider: google
property: G-CPNSYW2HX7

copyright: Copyright © 2011-2023 Pavel Perestoronin
copyright: Copyright © 2011-2024 Pavel Perestoronin

site_url: "https://eigenein.github.io/protobuf"

Expand Down
3 changes: 1 addition & 2 deletions pure_protobuf/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ def dumps(self) -> bytes:
def __setattr__(self, name: str, value: Any) -> None: # noqa: D105
super().__setattr__(name, value)
descriptor = self.__PROTOBUF_FIELDS_BY_NAME__[name]
one_of = descriptor.one_of
if one_of is not None:
if (one_of := descriptor.one_of) is not None and value is not None:
one_of._keep_attribute(self, descriptor.number)

@classmethod
Expand Down
6 changes: 6 additions & 0 deletions pure_protobuf/one_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ class OneOf(Generic[OneOfT]):
__slots__ = ("_fields",)

def __init__(self) -> None:
"""
Define a one-of group of fields.

A [`Field`][pure_protobuf.annotations.Field] then should be assigned to the group
via the [`one_of`][pure_protobuf.annotations.Field.one_of] parameter.
"""
self._fields: List[Tuple[int, str]] = []

def _add_field(self, number: int, name: str) -> None:
Expand Down
21 changes: 21 additions & 0 deletions tests/test_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from typing import ClassVar, Optional

from typing_extensions import Annotated

from pure_protobuf.annotations import Field
from pure_protobuf.message import BaseMessage
from pure_protobuf.one_of import OneOf


def test_initialize_dataclass_with_one_of() -> None:
"""Verify the fix for https://github.com/eigenein/protobuf/issues/171."""

@dataclass
class Message(BaseMessage):
payload: ClassVar[OneOf] = OneOf()

foo: Annotated[Optional[int], Field(1, one_of=payload)] = None
bar: Annotated[Optional[bool], Field(2, one_of=payload)] = None

assert Message(foo=3).foo == 3