Reputation: 3725
The Python library pure_protobuf forces its users to use dataclasses, and decorate them with another decorator:
# to be clear: these two decorators are library code (external)
@message
@dataclass
class SearchRequest:
query: str = field(1, default='')
page_number: int32 = field(2, default=int32(0))
result_per_page: int32 = field(3, default=int32(0))
This @message
decorator assigns the SearchRequest
instance a method called dumps
:
SearchRequest(
query='hello',
page_number=int32(1),
result_per_page=int32(10),
).dumps() == b'\x0A\x05hello\x10\x01\x18\x0A'
In my application code, I have a specific use-case where I need to pass an object that has the dumps()
method. It can be a pure_protobuf
Message
instance like above, or it can be any other type, so long as it implements dumps()
.
It's working fine for classes that I've defined myself and implement the dumps()
"interface", but for pure_protobuf
data-classes, it keeps complaining that they have no attribute dumps()
.
What is making this more challenging is I'm not defining these pure_protobuf
data-classes myself, these will be defined by clients of my library, so I can't simply do something (silly) like:
@message
@dataclass
class SearchRequest:
query: str = field(1, default='')
page_number: int32 = field(2, default=int32(0))
result_per_page: int32 = field(3, default=int32(0))
def dumps(self):
self.dumps() # that is Message.dumps from the decorator
Am I out of options?
Upvotes: 13
Views: 1025
Reputation: 7877
Unfortunately, you're really out of solutions here, because you need (no matter that it's external, it's not really important) message
decorator to return Intersection
(or Meet
in terms of types theory) of input class and protocol with 4 methods (dump
, dumps
, load
, loads
). It is not in python type system yet and is not implemented as type checker extension. See discussion regarding Intersection
in this python/typing issue.
The most interesting thing is that you could use pytype instead and leave message
unannotated, according to this tutorial. If using another type checker is an option for you, you could declare your own message
version:
from typing import IO, Protocol, TYPE_CHECKING
from pure_protobuf.dataclasses_ import message as _message
class MessageMixin(Protocol):
def dumps(self) -> bytes: ...
def dump(self, io: IO) -> None: ...
# Other definitions can go here
def message(cls):
if TYPE_CHECKING: # tweak
return type(cls.__name__, (MessageMixin, cls), {})
else: # actually run on runtime
return _message(cls)
Then users of your library can safely use this message
implementation, because in fact it just wraps existing method for type checking purpose, not affecting runtime. So if they don't, they just have mypy
errors (or not, if they do not use type checkers), but runtime is not affected.
However, once again, it does not work for mypy
.
If you are interested in having Intersection
in a tool like mypy
, consider switching to basedmypy
fork that, among other extensions, provides Intersection
support on the best effort basis.
Upvotes: 9
Reputation: 497
mypy has a plugin system that would allow you to inform it about the effect of your decorator. However, the plugin system is still experimental, and there is not enough information in the documentation to be able to write one. Instead, the doc recommends getting in contact with the mypy developers.
Upvotes: 0