Skip to content

Commit 09b1b0c

Browse files
authored
Merge pull request #19 from Team488/saqib/fixing-typings
Starting fixing up typings.
2 parents 4e297ef + 8e570bb commit 09b1b0c

33 files changed

Lines changed: 554 additions & 270 deletions

Alt-Core/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ dependencies = [
2626
"numpy == 2.0.2",
2727
"opencv-python == 4.11.0.86",
2828
"typing-extensions>=4.0.0",
29+
"types-requests == 2.32.4.20250611",
30+
"mypy == 1.16.1",
2931
]
3032

3133
[tool.setuptools]
@@ -38,4 +40,4 @@ namespaces = true
3840
[project.urls]
3941
"Homepage" = "https://github.com/Team488/Alt"
4042
"Bug Tracker" = "https://github.com/Team488/Alt/issues"
41-
"repository" = "https://github.com/Team488/Alt"
43+
"repository" = "https://github.com/Team488/Alt"

Alt-Core/src/Alt/Core/Agents/Agent.py

Lines changed: 132 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,120 @@
1+
from __future__ import annotations
2+
13
from abc import ABC, abstractmethod
24
from logging import Logger
3-
from typing import Dict, Optional, Any, ClassVar
5+
from typing import Any, Dict, Optional, Protocol, TypeVar, TYPE_CHECKING
46

57
from JXTABLES.XTablesClient import XTablesClient
68

79
from ..Utils.network import DEVICEIP
8-
from ..Operators.LogStreamOperator import LogStreamOperator
9-
from ..Operators.UpdateOperator import UpdateOperator
10-
from ..Operators.PropertyOperator import PropertyOperator
11-
from ..Operators.ConfigOperator import ConfigOperator
12-
from ..Operators.ShareOperator import ShareOperator
13-
from ..Operators.StreamOperator import StreamProxy
14-
from ..Operators.TimeOperator import TimeOperator, Timer
15-
from ..Constants.Teams import TEAM
16-
from ..Constants.AgentConstants import Proxy, ProxyType
1710

11+
if TYPE_CHECKING:
12+
from ..Constants.AgentConstants import Proxy, ProxyType
13+
from ..Constants.Teams import TEAM
14+
from ..Operators.ConfigOperator import ConfigOperator
15+
from ..Operators.LogStreamOperator import LogStreamOperator
16+
from ..Operators.PropertyOperator import PropertyOperator
17+
from ..Operators.ShareOperator import ShareOperator
18+
from ..Operators.StreamOperator import StreamProxy
19+
from ..Operators.TimeOperator import TimeOperator, Timer
20+
from ..Operators.UpdateOperator import UpdateOperator
21+
22+
23+
class Agent(Protocol):
24+
hasShutdown: bool = False
25+
hasClosed: bool = False
26+
isCleanedUp: bool = False
27+
isMainThread: bool = False
28+
agentName: str = ""
29+
xclient: XTablesClient
30+
propertyOperator: PropertyOperator
31+
configOperator: ConfigOperator
32+
updateOp: UpdateOperator
33+
timeOp: TimeOperator
34+
Sentinel: Logger
35+
timer: Timer
36+
37+
def _injectCore(
38+
self, shareOperator: ShareOperator, isMainThread: bool, agentName: str
39+
) -> None:
40+
...
41+
42+
def _injectNEW(
43+
self,
44+
xclient: XTablesClient, # new
45+
propertyOperator: PropertyOperator, # new
46+
configOperator: ConfigOperator, # new
47+
updateOperator: UpdateOperator, # new
48+
timeOperator: TimeOperator, # new
49+
logger: Logger, # static/new
50+
) -> None:
51+
...
1852

53+
def getProxy(self, proxyName: str) -> Optional[Proxy]:
54+
...
55+
56+
def _setProxies(self, proxies) -> None:
57+
...
58+
59+
def _cleanup(self) -> None:
60+
...
61+
62+
def getTimer(self) -> Timer:
63+
...
64+
65+
def getTeam(self) -> Optional[TEAM]:
66+
...
67+
68+
def _runOwnCreate(self):
69+
...
70+
71+
@classmethod
72+
def getName(cls) -> str:
73+
...
74+
75+
def create(self) -> None:
76+
...
77+
78+
def runPeriodic(self) -> None:
79+
...
1980

20-
class Agent(ABC):
81+
def isRunning(self) -> bool:
82+
...
83+
84+
def getDescription(self) -> str:
85+
...
86+
87+
def getIntervalMs(self) -> int:
88+
...
89+
90+
def forceShutdown(self) -> None:
91+
...
92+
93+
def onClose(self) -> None:
94+
...
95+
96+
@classmethod
97+
def requestProxies(cls) -> None:
98+
...
99+
100+
@classmethod
101+
def addProxyRequest(cls, proxyName: str, proxyType: ProxyType) -> None:
102+
...
103+
104+
@classmethod
105+
def _getProxyRequests(cls) -> Dict[str, ProxyType]:
106+
...
107+
108+
109+
TAgent = TypeVar("TAgent", bound=Agent)
110+
111+
112+
class AgentBase(ABC):
21113
"""
22114
Base class for all agents.
23115
"""
116+
117+
_proxyRequests: Dict[str, ProxyType] = {}
24118
DEFAULT_LOOP_TIME: int = 0 # 0 ms
25119
TIMERS = "timers"
26120

@@ -31,8 +125,7 @@ def __init__(self, **kwargs: Any) -> None:
31125
self.isCleanedUp: bool = False
32126
self.isMainThread: bool = False
33127
self.agentName = ""
34-
self.__proxies : Dict[str, Proxy] = {}
35-
128+
self.__proxies: Dict[str, Proxy] = {}
36129

37130
def _injectCore(
38131
self, shareOperator: ShareOperator, isMainThread: bool, agentName: str
@@ -70,23 +163,24 @@ def _injectNEW(
70163
self.timer = self.timeOp.getTimer(self.TIMERS)
71164
# other than setting variables, nothing should go here
72165

73-
def _setProxies(self, proxies):
166+
def _setProxies(self, proxies) -> None:
74167
self.__proxies = proxies
75168

76169
def _updateNetworkProxyInfo(self):
77-
""" Put information about the proxies used on xtables. This can be used for the dashboard, and other things"""
170+
"""Put information about the proxies used on xtables. This can be used for the dashboard, and other things"""
78171
streamPaths = []
79172
for proxyName, proxy in self.__proxies.items():
80173
if isinstance(proxy, StreamProxy):
81174
streamPaths.append(f"{proxyName}|{proxy.getStreamPath()}")
82175

83-
self.propertyOperator.createCustomReadOnlyProperty("streamPaths", streamPaths, addBasePrefix=True, addOperatorPrefix=True)
84-
176+
self.propertyOperator.createCustomReadOnlyProperty(
177+
"streamPaths", streamPaths, addBasePrefix=True, addOperatorPrefix=True
178+
)
85179

86-
def getProxy(self, proxyName : str) -> Optional[Proxy]:
180+
def getProxy(self, proxyName: str) -> Optional[Proxy]:
87181
return self.__proxies.get(proxyName)
88182

89-
def _cleanup(self):
183+
def _cleanup(self) -> None:
90184
# xclient shutdown occasionally failing?
91185
# self.xclient.shutdown()
92186
self.propertyOperator.deregisterAll()
@@ -96,7 +190,7 @@ def getTimer(self) -> Timer:
96190
"""Use only when needed, and only when associated with agent"""
97191
if self.timer is None:
98192
raise ValueError("Timer not initialized")
99-
193+
100194
return self.timer
101195

102196
def getTeam(self) -> Optional[TEAM]:
@@ -112,18 +206,19 @@ def getTeam(self) -> Optional[TEAM]:
112206
return TEAM.BLUE
113207
else:
114208
return TEAM.RED
115-
209+
116210
def _runOwnCreate(self):
117-
""" The agent wants to do its own stuff too... okay."""
211+
"""The agent wants to do its own stuff too... okay."""
118212

119213
logIp = f"http://{DEVICEIP}:5000/{self.agentName}/{LogStreamOperator.LOGPATH}"
120214

121-
self.propertyOperator.createCustomReadOnlyProperty("logIP", logIp, addBasePrefix=True, addOperatorPrefix=True)
215+
self.propertyOperator.createCustomReadOnlyProperty(
216+
"logIP", logIp, addBasePrefix=True, addOperatorPrefix=True
217+
)
122218

123219
self.__ensureProxies()
124220
self._updateNetworkProxyInfo()
125221

126-
127222
@classmethod
128223
def getName(cls):
129224
return cls.__name__
@@ -176,28 +271,29 @@ def onClose(self) -> None:
176271
pass
177272

178273
# ----- proxy methods -----
179-
def __ensureProxies(self):
274+
def __ensureProxies(self) -> None:
180275
for proxyName, proxyType in self._getProxyRequests().items():
181-
if proxyName not in self.__proxies or type(self.__proxies[proxyName]) is not proxyType:
276+
if (
277+
proxyName not in self.__proxies
278+
or type(self.__proxies[proxyName]) is not proxyType
279+
):
182280
print(type(self.__proxies[proxyName]) is proxyType)
183-
raise RuntimeError(f"Agent proxies are not correcty initialized!\n{self._getProxyRequests()=}\n{self.__proxies.items()=}")
281+
raise RuntimeError(
282+
f"Agent proxies are not correcty initialized!\n{self._getProxyRequests()=}\n{self.__proxies.items()=}"
283+
)
184284

185285
@classmethod
186286
def requestProxies(cls):
187-
""" Override this, and add all of your proxy requests"""
287+
"""Override this, and add all of your proxy requests"""
188288
pass
189289

190290
@classmethod
191-
def addProxyRequest(cls, proxyName : str, proxyType: ProxyType) -> None:
192-
""" Method to request that a stream proxy will be given to this agent to display streams
193-
NOTE: you must override requestProxies() and add your calls to this there, or else it will not be used!
291+
def addProxyRequest(cls, proxyName: str, proxyType: ProxyType) -> None:
292+
"""Method to request that a stream proxy will be given to this agent to display streams
293+
NOTE: you must override requestProxies() and add your calls to this there, or else it will not be used!
194294
"""
195-
if not hasattr(cls, '_proxyRequests'):
196-
cls._proxyRequests = {}
197-
198295
cls._proxyRequests[proxyName] = proxyType
199296

200297
@classmethod
201298
def _getProxyRequests(cls) -> Dict[str, ProxyType]:
202-
return getattr(cls, '_proxyRequests', {})
203-
299+
return getattr(cls, "_proxyRequests", {})

Alt-Core/src/Alt/Core/Agents/AgentExample.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
from typing import Any
2-
from .Agent import Agent
1+
from __future__ import annotations
32

3+
from typing import Any, Optional, TYPE_CHECKING
44

5-
class AgentExample(Agent):
6-
""" This example agent shows how simple it can be to create a task.
7-
8-
This agent creates a name property (which allows you to change its name), and then it tells it to you 50 times before ending.
5+
from .Agent import AgentBase
6+
7+
if TYPE_CHECKING:
8+
from Core.Operators.PropertyOperator import Property
9+
10+
11+
class AgentExample(AgentBase):
12+
"""This example agent shows how simple it can be to create a task.
13+
14+
This agent creates a name property (which allows you to change its name), and then it tells it to you 50 times before ending.
915
"""
16+
1017
def __init__(self, **kwargs: Any) -> None:
1118
super().__init__(**kwargs)
1219
self.timesRun: int = 0
13-
self.nameProp = None
20+
self.nameProp: Optional[Property] = None
1421

1522
def create(self) -> None:
1623
# for example here i can create a propery to configure what to call myself
@@ -28,6 +35,7 @@ def runPeriodic(self) -> None:
2835
# for example, i can tell the world what im called
2936

3037
self.timesRun += 1
38+
assert self.nameProp is not None
3139
name = self.nameProp.get()
3240
self.projectNameProp.set(name)
3341
self.Sentinel.info(f"My name is {name}")
@@ -36,7 +44,9 @@ def onClose(self) -> None:
3644
# task cleanup here
3745
# for example, i can tell the world that my time has come
3846
if self.nameProp is not None:
39-
self.Sentinel.info(f"My time has come. Never forget the name {self.nameProp.get()}!")
47+
self.Sentinel.info(
48+
f"My time has come. Never forget the name {self.nameProp.get()}!"
49+
)
4050

4151
def isRunning(self) -> bool:
4252
# condition to keep task running here
Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
1+
from __future__ import annotations
2+
13
from abc import abstractmethod
24
from functools import partial
35

6+
47
class BindableAgent:
5-
""" When an Agent requires extra keyword arguments, the Neo class cannot just create an instance of it due to it needing arguments.
6-
To fix this, we use the functools partial method. You can imagine it pre-binding the args of the agent, before it gets created.
7-
This way, when the agent needs to be created, the arguments will be stored inside, and Neo can create an instance.
8+
"""When an Agent requires extra keyword arguments, the Neo class cannot just create an instance of it due to it needing arguments.
9+
To fix this, we use the functools partial method. You can imagine it pre-binding the args of the agent, before it gets created.
10+
This way, when the agent needs to be created, the arguments will be stored inside, and Neo can create an instance.
811
"""
12+
913
@classmethod
1014
@abstractmethod
1115
def bind(cls, *args, **kwargs) -> partial:
12-
""" To make it clearer what arguments an agent needs, please override this bind method and specify the same input arguments as the agents __init__
13-
At the moment, this is the only way to change the static method signature of bind, so people know what arguments to provide.
14-
In the method body, you can just call cls._getBindedAgent() with the input arguments.
16+
"""To make it clearer what arguments an agent needs, please override this bind method and specify the same input arguments as the agents __init__
17+
At the moment, this is the only way to change the static method signature of bind, so people know what arguments to provide.
18+
In the method body, you can just call cls._getBindedAgent() with the input arguments.
1519
16-
Example:
17-
``` python
18-
class bindable(Agent, BindableAgent):
19-
# overriding the bind method to make the static method signature clear
20-
@classmethod
21-
def bind(arg1 : str, arg2 : int, ....):
22-
# you can use keyword or positional arguments, but it should match your constructor
23-
return cls._getBindedAgent(arg1, arg2=arg2, ....)
20+
Example:
21+
``` python
22+
class bindable(Agent, BindableAgent):
23+
# overriding the bind method to make the static method signature clear
24+
@classmethod
25+
def bind(arg1 : str, arg2 : int, ....):
26+
# you can use keyword or positional arguments, but it should match your constructor
27+
return cls._getBindedAgent(arg1, arg2=arg2, ....)
2428
25-
def __init__(arg1 : str, arg2 : int, ....):
26-
# same signature as above, ensures that when neo gets the bound agent, it needs no extra arguments
27-
# init things....
28-
```
29+
def __init__(arg1 : str, arg2 : int, ....):
30+
# same signature as above, ensures that when neo gets the bound agent, it needs no extra arguments
31+
# init things....
32+
```
2933
"""
3034
pass
3135

3236
@classmethod
3337
def _getBindedAgent(cls, *args, **kwargs) -> partial:
34-
return partial(cls, *args, **kwargs)
38+
return partial(cls, *args, **kwargs)

Alt-Core/src/Alt/Core/Agents/PositionLocalizingAgentBase.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from __future__ import annotations
2+
13
from typing import Tuple, Any
24

3-
from .Agent import Agent
5+
from .Agent import AgentBase
46
from ..Utils import NtUtils
57

68

7-
class PositionLocalizingAgentBase(Agent):
9+
class PositionLocalizingAgentBase(AgentBase):
810
"""Agent -> PositionLocalizingAgentBase
911
1012
Extending Agent with Localizing capabilites. Supports only XTables

0 commit comments

Comments
 (0)