-
Notifications
You must be signed in to change notification settings - Fork 179
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
refactor(api): Make sure command implementations return something compatible with the command's result type #15051
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
TypeVar, | ||
Tuple, | ||
List, | ||
Type, | ||
Union, | ||
) | ||
|
||
from pydantic import BaseModel, Field | ||
|
@@ -29,11 +31,11 @@ | |
from ..state import StateView | ||
|
||
|
||
CommandParamsT = TypeVar("CommandParamsT", bound=BaseModel) | ||
|
||
CommandResultT = TypeVar("CommandResultT", bound=BaseModel) | ||
|
||
CommandPrivateResultT = TypeVar("CommandPrivateResultT") | ||
_ParamsT = TypeVar("_ParamsT", bound=BaseModel) | ||
_ParamsT_contra = TypeVar("_ParamsT_contra", bound=BaseModel, contravariant=True) | ||
_ResultT = TypeVar("_ResultT", bound=BaseModel) | ||
_ResultT_co = TypeVar("_ResultT_co", bound=BaseModel, covariant=True) | ||
_PrivateResultT_co = TypeVar("_PrivateResultT_co", covariant=True) | ||
|
||
|
||
class CommandStatus(str, Enum): | ||
|
@@ -58,7 +60,7 @@ class CommandIntent(str, Enum): | |
FIXIT = "fixit" | ||
|
||
|
||
class BaseCommandCreate(GenericModel, Generic[CommandParamsT]): | ||
class BaseCommandCreate(GenericModel, Generic[_ParamsT]): | ||
"""Base class for command creation requests. | ||
|
||
You shouldn't use this class directly; instead, use or define | ||
|
@@ -72,7 +74,7 @@ class BaseCommandCreate(GenericModel, Generic[CommandParamsT]): | |
"execution behavior" | ||
), | ||
) | ||
params: CommandParamsT = Field(..., description="Command execution data payload") | ||
params: _ParamsT = Field(..., description="Command execution data payload") | ||
intent: Optional[CommandIntent] = Field( | ||
None, | ||
description=( | ||
|
@@ -97,7 +99,7 @@ class BaseCommandCreate(GenericModel, Generic[CommandParamsT]): | |
) | ||
|
||
|
||
class BaseCommand(GenericModel, Generic[CommandParamsT, CommandResultT]): | ||
class BaseCommand(GenericModel, Generic[_ParamsT, _ResultT]): | ||
"""Base command model. | ||
|
||
You shouldn't use this class directly; instead, use or define | ||
|
@@ -127,8 +129,8 @@ class BaseCommand(GenericModel, Generic[CommandParamsT, CommandResultT]): | |
), | ||
) | ||
status: CommandStatus = Field(..., description="Command execution status") | ||
params: CommandParamsT = Field(..., description="Command execution data payload") | ||
result: Optional[CommandResultT] = Field( | ||
params: _ParamsT = Field(..., description="Command execution data payload") | ||
result: Optional[_ResultT] = Field( | ||
None, | ||
description="Command execution result data, if succeeded", | ||
) | ||
|
@@ -167,10 +169,15 @@ class BaseCommand(GenericModel, Generic[CommandParamsT, CommandResultT]): | |
), | ||
) | ||
|
||
_ImplementationCls: Union[ | ||
Type[AbstractCommandImpl[_ParamsT, _ResultT]], | ||
Type[AbstractCommandWithPrivateResultImpl[_ParamsT, _ResultT, object]], | ||
] | ||
|
||
Comment on lines
+172
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're using VS Code or another editor with Pyright integration, you'll get a red squiggly on every command's override of I think it has to do with the fact that |
||
|
||
class AbstractCommandImpl( | ||
ABC, | ||
Generic[CommandParamsT, CommandResultT], | ||
Generic[_ParamsT_contra, _ResultT_co], | ||
Comment on lines
-173
to
+180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In English:
This is the usual behavior for callables, which is basically what a command implementation is. I think we need to spell it out manually because we're going through a wrapper class; the type-checker would understand this automatically if, e.g., the |
||
): | ||
"""Abstract command creation and execution implementation. | ||
|
||
|
@@ -204,14 +211,14 @@ def __init__( | |
pass | ||
|
||
@abstractmethod | ||
async def execute(self, params: CommandParamsT) -> CommandResultT: | ||
async def execute(self, params: _ParamsT_contra) -> _ResultT_co: | ||
"""Execute the command, mapping data from execution into a response model.""" | ||
... | ||
|
||
|
||
class AbstractCommandWithPrivateResultImpl( | ||
ABC, | ||
Generic[CommandParamsT, CommandResultT, CommandPrivateResultT], | ||
Generic[_ParamsT_contra, _ResultT_co, _PrivateResultT_co], | ||
): | ||
"""Abstract command creation and execution implementation if the command has private results. | ||
|
||
|
@@ -247,7 +254,7 @@ def __init__( | |
|
||
@abstractmethod | ||
async def execute( | ||
self, params: CommandParamsT | ||
) -> Tuple[CommandResultT, CommandPrivateResultT]: | ||
self, params: _ParamsT_contra | ||
) -> Tuple[_ResultT_co, _PrivateResultT_co]: | ||
"""Execute the command, mapping data from execution into a response model.""" | ||
... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By convention, each command links itself to its implementation via an
_ImplementationCls
class attribute.Here, I'm declaring that attribute on the base class. This enforces that each command's
._ImplementationCls
matches its.params
and.result
. In the near future, this will also make sure it matches its.error
.