-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat: add SendExt
to reduce boilerplate of common send()
calls
#4
Comments
No Traits?In Rust, we have this cool thing called trait. // discord.abc.Messageable
trait Messageable {
fn send(...) -> Message;
}
trait SendExt: Messageable {
fn send_info(...) -> Message { self.send(...) }
fn send_warn(...) -> Message { self.send(...) }
fn send_error(...) -> Message { self.send(...) }
}
impl<T: Messageable> SendExt for T {}
// now we can use `send_{info, warn, error}()` on every Messageable
user.send_info("Prepare thyself")
context.send_warn("Thy end is now")
textchannel.send_error("Judgement") Now, how am I supposed to do something similar to this in Python, |
Coping Mechanism CandidatesTrait at home:
A mixin class with Edit: |
The Seething ProcessPython is not Rust. Pretending as if it is or "hacking" it with things like That said, making As a compromise, I'm going to create a custom context class which subclasses
|
Malding ImplementationQuestion: How do I put trait bounds on a mixin class? In other words, if a mixin depends on method(s) of the mixed-into class,
Can I enforce this with Python's type hints and static type checkers? |
Attempt 1from abc import ABC, abstractmethod
# discord.abc.Messageable
class Messageable:
def send(self, content: str) -> None:
print(content)
# commands.Context
class Context(Messageable):
pass
class SendExt(ABC):
@abstractmethod
def send(self, content: str) -> None: ...
def send_warn(self, content: str) -> None:
self.send(f"WARN: {content}")
# custom `Context` to inject `send_*()`
class MacLak(Context, SendExt):
pass
ctx = MacLak()
ctx.send_warn("I am inside your walls") As seen in the unresolved SSO question, one way is to make # try to mixin `SendExt` without implementing `.send()`
class Nope(SendExt):
pass
nope = Nope() # fails; cannot be instantiated without overriding all @abstractmethod Problems
# can `.send()` to DM
class User(Messageable):
pass
steven = User()
# incompatible type; can be used with duck typing but Pyright is still mad
SendExt.send_warn(steven, "Only language you speak is FAILURE") |
Attempt 2Type hinting the class SendExt(Protocol):
def send_warn(self: Messageable, content: str) -> None:
self.send(f"WARN: {content}") The |
Attempt 3
from abc import abstractmethod
from typing import Protocol
# discord.abc.Messageable
class Messageable(Protocol):
def send(self, content: str) -> None:
print(content)
@abstractmethod
def _get_channel(self, ident: int) -> None: ...
# commands.Context
class Context(Messageable):
def _get_channel(self, ident: int) -> None:
print(f"Context: {ident}")
# send_ext.py
class SendExt(Messageable, Protocol):
def send_warn(self: Messageable, content: str) -> None:
self.send(f"WARN: {content}") This allows us to:
# custom `Context` to inject `send_*()`
class MacLak(Context, SendExt):
pass
ctx = MacLak()
ctx.send_warn("Directly available as a method")
class NotMessageable(SendExt):
pass
# the `Messageable._get_channel` abstract method is not overriden
try:
_ = NotMessageable() # type: ignore[abstract]
except TypeError:
print("Does not override `_get_channel()`, cannot be instantiated")
class User(Messageable):
def _get_channel(self, ident: int) -> None:
print(f"User: {ident}")
user = User()
SendExt.send_warn(user, "Still able to use `SendExt.*()` as a function") ProblemThere is a tiny problem. It turns out... discord.abc.Messageable is actually neither # current state (v2.3.2) of discord.py
class Messageable:
def send(self, content: str) -> None:
print(content)
def _get_channel(self, ident: int) -> None:
raise NotImplementedError It's just a normal class, and the required method |
Investigation
I assume it was because most features of Removing the |
Lessons LearnedHere's an MRE version of what I struggled to achieve so far: trait Foo {
fn foo(&self);
}
trait Bar: Foo {
fn bar(&self) {
self.foo();
}
} from abc import abstractmethod
from typing import Protocol
class Fooable(Protocol):
@abstractmethod
def foo(self):
...
class BarMixin(Foo, Protocol):
def bar(self: Foo):
self.foo() Combination of Honestly, it's kind of depressing to see Rust/Python snippets right next to each other, Well, I could have just suppressed the warnings and get along with duck typing, |
send_ext
- reduce boilerplate of common .send()
callsSendExt
to reduce boilerplate of common send()
calls
SendExt
MixinProvides convenience function to send embed with predefined color palette and icon.
send_code()
- Send code blocksend_info()
- Ordinary responsessend_warn()
- Invalid use of a featuresend_err()
- Critical failure; Found a bugThe text was updated successfully, but these errors were encountered: