Merge branch 'refs/heads/master_gh'
This commit is contained in:
commit
f8ecc42478
1635
poetry.lock
generated
1635
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,13 +12,12 @@ packages = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
python = "^3.8"
|
||||
black = { version = ">=23.1.0", optional = true }
|
||||
grpclib = "^0.4.1"
|
||||
importlib-metadata = { version = ">=1.6.0", python = "<3.8" }
|
||||
jinja2 = { version = ">=3.0.3", optional = true }
|
||||
python-dateutil = "^2.8"
|
||||
isort = {version = "^5.11.5", optional = true}
|
||||
isort = { version = "^5.11.5", optional = true }
|
||||
typing-extensions = "^4.7.1"
|
||||
betterproto-rust-codec = { version = "0.1.1", optional = true }
|
||||
|
||||
@ -26,7 +25,7 @@ betterproto-rust-codec = { version = "0.1.1", optional = true }
|
||||
asv = "^0.4.2"
|
||||
bpython = "^0.19"
|
||||
jinja2 = ">=3.0.3"
|
||||
mypy = "^0.930"
|
||||
mypy = "^1.11.2"
|
||||
sphinx = "3.1.2"
|
||||
sphinx-rtd-theme = "0.5.0"
|
||||
pre-commit = "^2.17.0"
|
||||
|
@ -169,7 +169,22 @@ class Casing(builtin_enum.Enum):
|
||||
SNAKE = snake_case #: A snake_case sterilization function.
|
||||
|
||||
|
||||
PLACEHOLDER: Any = object()
|
||||
class Placeholder:
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<PLACEHOLDER>"
|
||||
|
||||
def __copy__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, _) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
# We can't simply use object() here because pydantic automatically performs deep-copy of mutable default values
|
||||
# See #606
|
||||
PLACEHOLDER: Any = Placeholder()
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@ -206,7 +221,7 @@ def dataclass_field(
|
||||
) -> dataclasses.Field:
|
||||
"""Creates a dataclass field with attached protobuf metadata."""
|
||||
return dataclasses.field(
|
||||
default=None if optional else PLACEHOLDER,
|
||||
default=None if optional else PLACEHOLDER, # type: ignore
|
||||
metadata={
|
||||
"betterproto": FieldMetadata(
|
||||
number, proto_type, map_types, group, wraps, optional
|
||||
@ -1864,9 +1879,7 @@ class Message(ABC):
|
||||
if getattr(values, field.name, None) is not None
|
||||
]
|
||||
|
||||
if not set_fields:
|
||||
raise ValueError(f"Group {group} has no value; all fields are None")
|
||||
elif len(set_fields) > 1:
|
||||
if len(set_fields) > 1:
|
||||
set_fields_str = ", ".join(set_fields)
|
||||
raise ValueError(
|
||||
f"Group {group} has more than one value; fields {set_fields_str} are not None"
|
||||
|
@ -500,35 +500,6 @@ class FieldCompiler(MessageCompiler):
|
||||
.replace("type_", "")
|
||||
)
|
||||
|
||||
@property
|
||||
def default_value_string(self) -> str:
|
||||
"""Python representation of the default proto value."""
|
||||
if self.repeated:
|
||||
return "[]"
|
||||
if self.optional:
|
||||
return "None"
|
||||
if self.py_type == "int":
|
||||
return "0"
|
||||
if self.py_type == "float":
|
||||
return "0.0"
|
||||
elif self.py_type == "bool":
|
||||
return "False"
|
||||
elif self.py_type == "str":
|
||||
return '""'
|
||||
elif self.py_type == "bytes":
|
||||
return 'b""'
|
||||
elif self.field_type == "enum":
|
||||
enum_proto_obj_name = self.proto_obj.type_name.split(".").pop()
|
||||
enum = next(
|
||||
e
|
||||
for e in self.output_file.enums
|
||||
if e.proto_obj.name == enum_proto_obj_name
|
||||
)
|
||||
return enum.default_value_string
|
||||
else:
|
||||
# Message type
|
||||
return "None"
|
||||
|
||||
@property
|
||||
def packed(self) -> bool:
|
||||
"""True if the wire representation is a packed format."""
|
||||
@ -687,14 +658,6 @@ class EnumDefinitionCompiler(MessageCompiler):
|
||||
]
|
||||
super().__post_init__() # call MessageCompiler __post_init__
|
||||
|
||||
@property
|
||||
def default_value_string(self) -> str:
|
||||
"""Python representation of the default value for Enums.
|
||||
|
||||
As per the spec, this is the first value of the Enum.
|
||||
"""
|
||||
return str(self.entries[0].value) # ideally, should ALWAYS be int(0)!
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceCompiler(ProtoContentBase):
|
||||
@ -755,30 +718,6 @@ class ServiceMethodCompiler(ProtoContentBase):
|
||||
)
|
||||
return f"/{package_part}{self.parent.proto_name}/{self.proto_name}"
|
||||
|
||||
@property
|
||||
def py_input_message(self) -> Optional[MessageCompiler]:
|
||||
"""Find the input message object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[MessageCompiler]
|
||||
Method instance representing the input message.
|
||||
If not input message could be found or there are no
|
||||
input messages, None is returned.
|
||||
"""
|
||||
package, name = parse_source_type_name(self.proto_obj.input_type)
|
||||
|
||||
# Nested types are currently flattened without dots.
|
||||
# Todo: keep a fully quantified name in types, that is
|
||||
# comparable with method.input_type
|
||||
for msg in self.request.all_messages:
|
||||
if (
|
||||
msg.py_name == pythonize_class_name(name.replace(".", ""))
|
||||
and msg.output_file.package == package
|
||||
):
|
||||
return msg
|
||||
return None
|
||||
|
||||
@property
|
||||
def py_input_message_type(self) -> str:
|
||||
"""String representation of the Python type corresponding to the
|
||||
|
@ -5,16 +5,9 @@
|
||||
{% for i in output_file.python_module_imports|sort %}
|
||||
import {{ i }}
|
||||
{% endfor %}
|
||||
{% set type_checking_imported = False %}
|
||||
|
||||
{% if output_file.pydantic_dataclasses %}
|
||||
from typing import TYPE_CHECKING
|
||||
{% set type_checking_imported = True %}
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dataclasses import dataclass
|
||||
else:
|
||||
from pydantic.dataclasses import dataclass
|
||||
from pydantic.dataclasses import dataclass
|
||||
from pydantic.dataclasses import rebuild_dataclass
|
||||
{%- else -%}
|
||||
from dataclasses import dataclass
|
||||
@ -46,7 +39,7 @@ import grpclib
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
|
||||
{% if output_file.imports_type_checking_only and not type_checking_imported %}
|
||||
{% if output_file.imports_type_checking_only %}
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -23,7 +23,11 @@ class {{ enum.py_name }}(betterproto.Enum):
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for message in output_file.messages %}
|
||||
{% if output_file.pydantic_dataclasses %}
|
||||
@dataclass(eq=False, repr=False, config={"extra": "forbid"})
|
||||
{% else %}
|
||||
@dataclass(eq=False, repr=False)
|
||||
{% endif %}
|
||||
class {{ message.py_name }}(betterproto.Message):
|
||||
{% if message.comment %}
|
||||
{{ message.comment }}
|
||||
@ -70,7 +74,7 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub):
|
||||
{% for method in service.methods %}
|
||||
async def {{ method.py_name }}(self
|
||||
{%- if not method.client_streaming -%}
|
||||
{%- if method.py_input_message -%}, {{ method.py_input_message_param }}: "{{ method.py_input_message_type }}"{%- endif -%}
|
||||
, {{ method.py_input_message_param }}: "{{ method.py_input_message_type }}"
|
||||
{%- else -%}
|
||||
{# Client streaming: need a request iterator instead #}
|
||||
, {{ method.py_input_message_param }}_iterator: {{ output_file.typing_compiler.union(output_file.typing_compiler.async_iterable(method.py_input_message_type), output_file.typing_compiler.iterable(method.py_input_message_type)) }}
|
||||
@ -149,7 +153,7 @@ class {{ service.py_name }}Base(ServiceBase):
|
||||
{% for method in service.methods %}
|
||||
async def {{ method.py_name }}(self
|
||||
{%- if not method.client_streaming -%}
|
||||
{%- if method.py_input_message -%}, {{ method.py_input_message_param }}: "{{ method.py_input_message_type }}"{%- endif -%}
|
||||
, {{ method.py_input_message_param }}: "{{ method.py_input_message_type }}"
|
||||
{%- else -%}
|
||||
{# Client streaming: need a request iterator instead #}
|
||||
, {{ method.py_input_message_param }}_iterator: {{ output_file.typing_compiler.async_iterator(method.py_input_message_type) }}
|
||||
|
7
tests/inputs/invalid_field/invalid_field.proto
Normal file
7
tests/inputs/invalid_field/invalid_field.proto
Normal file
@ -0,0 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package invalid_field;
|
||||
|
||||
message Test {
|
||||
int32 x = 1;
|
||||
}
|
17
tests/inputs/invalid_field/test_invalid_field.py
Normal file
17
tests/inputs/invalid_field/test_invalid_field.py
Normal file
@ -0,0 +1,17 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_invalid_field():
|
||||
from tests.output_betterproto.invalid_field import Test
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
Test(unknown_field=12)
|
||||
|
||||
|
||||
def test_invalid_field_pydantic():
|
||||
from pydantic import ValidationError
|
||||
|
||||
from tests.output_betterproto_pydantic.invalid_field import Test
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
Test(unknown_field=12)
|
@ -35,18 +35,16 @@ def test_message_with_deprecated_field(message):
|
||||
|
||||
|
||||
def test_message_with_deprecated_field_not_set(message):
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
Test(value=10)
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_message_with_deprecated_field_not_set_default(message):
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
_ = Test(value=10).message
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_with_deprecated_method():
|
||||
@ -58,7 +56,6 @@ async def test_service_with_deprecated_method():
|
||||
assert len(record) == 1
|
||||
assert str(record[0].message) == f"TestService.deprecated_func is deprecated"
|
||||
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
await stub.func(Empty())
|
||||
|
||||
assert not record
|
||||
|
Loading…
x
Reference in New Issue
Block a user