|
3 | 3 |
|
4 | 4 | """Results from PowerDistributingActor.""" |
5 | 5 |
|
6 | | -from abc import ABC |
7 | | -from typing import Set |
| 6 | +from __future__ import annotations |
| 7 | + |
| 8 | +import dataclasses |
8 | 9 |
|
9 | 10 | from .request import Request |
10 | 11 |
|
11 | 12 |
|
12 | | -class Result(ABC): |
13 | | - """Base class for the power distributor result.""" |
14 | | - |
15 | | - def __init__(self, request: Request) -> None: |
16 | | - """Create class instance. |
17 | | -
|
18 | | - Args: |
19 | | - request: The user's request to which this message responds. |
20 | | - """ |
21 | | - self.request: Request = request |
22 | | - |
23 | | - |
24 | | -class Success(Result): |
25 | | - """Send if setting power for all batteries succeed.""" |
26 | | - |
27 | | - def __init__( |
28 | | - self, |
29 | | - request: Request, |
30 | | - succeed_power: int, |
31 | | - used_batteries: Set[int], |
32 | | - excess_power: int, |
33 | | - ) -> None: |
34 | | - """Create class instance. |
35 | | -
|
36 | | - Args: |
37 | | - request: The user's request to which this message responds. |
38 | | - succeed_power: Part of the requested power that was successfully set. |
39 | | - used_batteries: Subset of the requested batteries, that were used to |
40 | | - realize the request. |
41 | | - excess_power: Part of the requested power that could not be fulfilled, |
42 | | - because it was outside available power bounds. |
43 | | - """ |
44 | | - super().__init__(request) |
45 | | - self.succeed_power: int = succeed_power |
46 | | - self.used_batteries: Set[int] = used_batteries |
47 | | - self.excess_power: int = excess_power |
48 | | - |
49 | | - |
50 | | -class PartialFailure(Result): |
51 | | - """Send if any battery failed and didn't perform the request.""" |
52 | | - |
53 | | - # It is very simple class with only data so it should be ok to disable pylint. |
54 | | - # All these results should be dataclass but in python < 3.10 it is risky |
55 | | - # to derive after dataclass. |
56 | | - def __init__( # pylint: disable=too-many-arguments |
57 | | - self, |
58 | | - request: Request, |
59 | | - succeed_power: int, |
60 | | - succeed_batteries: Set[int], |
61 | | - failed_power: int, |
62 | | - failed_batteries: Set[int], |
63 | | - excess_power: int, |
64 | | - ) -> None: |
65 | | - """Create class instance. |
66 | | -
|
67 | | - Args: |
68 | | - request: The user's request to which this message responds. |
69 | | - succeed_power: Part of the requested power that was successfully set. |
70 | | - succeed_batteries: Subset of the requested batteries for which the request |
71 | | - succeed. |
72 | | - failed_power: Part of the requested power that failed. |
73 | | - failed_batteries: Subset of the requested batteries for which the request |
74 | | - failed. |
75 | | - excess_power: Part of the requested power that could not be fulfilled, |
76 | | - because it was outside available power bounds. |
77 | | - """ |
78 | | - super().__init__(request) |
79 | | - self.succeed_power: int = succeed_power |
80 | | - self.succeed_batteries: Set[int] = succeed_batteries |
81 | | - self.failed_power: int = failed_power |
82 | | - self.failed_batteries: Set[int] = failed_batteries |
83 | | - self.excess_power: int = excess_power |
| 13 | +@dataclasses.dataclass |
| 14 | +class _BaseResultMixin: |
| 15 | + """Base mixin class for reporting power distribution results.""" |
84 | 16 |
|
| 17 | + request: Request |
| 18 | + """The user's request to which this message responds.""" |
| 19 | + |
| 20 | + |
| 21 | +# When moving to Python 3.10+ we should replace this with an union type: |
| 22 | +# Result = Success | PartialFailure | Error | OutOfBound | Ignored |
| 23 | +# For now it can't be done because before 3.10 isinstance(result, Success) |
| 24 | +# doesn't work, so it is hard to figure out what type of result you got in |
| 25 | +# a forward compatible way. |
| 26 | +# When moving we should use the _BaseResultMixin as a base class for all |
| 27 | +# results. |
| 28 | +@dataclasses.dataclass |
| 29 | +class Result(_BaseResultMixin): |
| 30 | + """Power distribution result.""" |
| 31 | + |
| 32 | + |
| 33 | +@dataclasses.dataclass |
| 34 | +class _BaseSuccessMixin: |
| 35 | + """Result returned when setting the power succeed for all batteries.""" |
| 36 | + |
| 37 | + succeeded_power: int |
| 38 | + """The part of the requested power that was successfully set.""" |
| 39 | + |
| 40 | + succeeded_batteries: set[int] |
| 41 | + """The subset of batteries for which power was set successfully.""" |
| 42 | + |
| 43 | + excess_power: int |
| 44 | + """The part of the requested power that could not be fulfilled. |
| 45 | +
|
| 46 | + This happens when the requested power is outside the available power bounds. |
| 47 | + """ |
85 | 48 |
|
86 | | -class Error(Result): |
87 | | - """Error occurred and power was not set.""" |
88 | 49 |
|
89 | | - def __init__(self, request: Request, msg: str) -> None: |
90 | | - """Create class instance. |
| 50 | +# We need to put the _BaseSuccessMixin before Result in the inheritance list to |
| 51 | +# make sure that the Result attributes appear before the _BaseSuccessMixin, |
| 52 | +# otherwise the request attribute will be last in the dataclass constructor |
| 53 | +# because of how MRO works. |
91 | 54 |
|
92 | | - Args: |
93 | | - request: The user's request to which this message responds. |
94 | | - msg: Error message explaining why error happened. |
95 | | - """ |
96 | | - super().__init__(request) |
97 | | - self.msg: str = msg |
98 | 55 |
|
| 56 | +@dataclasses.dataclass |
| 57 | +class Success(_BaseSuccessMixin, Result): # Order matters here. See above. |
| 58 | + """Result returned when setting the power succeeded for all batteries.""" |
99 | 59 |
|
| 60 | + |
| 61 | +@dataclasses.dataclass |
| 62 | +class PartialFailure(_BaseSuccessMixin, Result): |
| 63 | + """Result returned when any battery failed to perform the request.""" |
| 64 | + |
| 65 | + failed_power: int |
| 66 | + """The part of the requested power that failed to be set.""" |
| 67 | + |
| 68 | + failed_batteries: set[int] |
| 69 | + """The subset of batteries for which the request failed.""" |
| 70 | + |
| 71 | + |
| 72 | +@dataclasses.dataclass |
| 73 | +class Error(Result): |
| 74 | + """Result returned when an error occurred and power was not set at all.""" |
| 75 | + |
| 76 | + msg: str |
| 77 | + """The error message explaining why error happened.""" |
| 78 | + |
| 79 | + |
| 80 | +@dataclasses.dataclass |
100 | 81 | class OutOfBound(Result): |
101 | | - """Send if power was not set because requested power was not within bounds. |
| 82 | + """Result returned when the power was not set because it was out of bounds. |
102 | 83 |
|
103 | | - This message is send only if Request.adjust_power = False. |
| 84 | + This result happens when the originating request was done with |
| 85 | + `adjust_power = False` and the requested power is not within the batteries bounds. |
104 | 86 | """ |
105 | 87 |
|
106 | | - def __init__(self, request: Request, bound: int) -> None: |
107 | | - """Create class instance. |
| 88 | + bound: int |
| 89 | + """The total power bound for the requested batteries. |
108 | 90 |
|
109 | | - Args: |
110 | | - request: The user's request to which this message responds. |
111 | | - bound: Total power bound for the requested batteries. |
112 | | - If requested power < 0, then this value is lower bound. |
113 | | - Otherwise it is upper bound. |
114 | | - """ |
115 | | - super().__init__(request) |
116 | | - self.bound: int = bound |
| 91 | + If the requested power negative, then this value is the lower bound. |
| 92 | + Otherwise it is upper bound. |
| 93 | + """ |
117 | 94 |
|
118 | 95 |
|
| 96 | +@dataclasses.dataclass |
119 | 97 | class Ignored(Result): |
120 | | - """Send if request was ignored. |
| 98 | + """Result returned when the request was ignored. |
121 | 99 |
|
122 | | - Request was ignored because new request for the same subset of batteries |
123 | | - was received. |
| 100 | + The request can be ignored when a new request for the same subset of |
| 101 | + batteries was received. |
124 | 102 | """ |
0 commit comments