Skip to content

Commit 48512fc

Browse files
authored
Merge pull request #135 from auscompgeek/tunable-descriptor
magicbot: Refactor tunable to be a descriptor
2 parents 5d5c87d + 6c6ca57 commit 48512fc

File tree

2 files changed

+55
-42
lines changed

2 files changed

+55
-42
lines changed

magicbot/magic_tunable.py

+52-39
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import functools
22
import inspect
3+
import warnings
4+
from typing import Generic, Optional, TypeVar
35

46
from networktables import NetworkTables
57
from ntcore.value import Value
68

7-
# Only used as a marker
8-
class _TunableProperty(property):
9-
pass
9+
V = TypeVar("V")
1010

1111

12-
class _AutosendProperty(_TunableProperty):
13-
pass
14-
15-
16-
def tunable(default, *, writeDefault=True, subtable=None, doc=None):
12+
class tunable(Generic[V]):
1713
"""
1814
This allows you to define simple properties that allow you to easily
1915
communicate with other programs via NetworkTables.
@@ -23,18 +19,17 @@ def tunable(default, *, writeDefault=True, subtable=None, doc=None):
2319
2420
class MyRobot(magicbot.MagicRobot):
2521
26-
my_component = MyComponent
22+
my_component: MyComponent
2723
28-
...
24+
...
2925
3026
from magicbot import tunable
3127
3228
class MyComponent:
3329
3430
# define the tunable property
3531
foo = tunable(True)
36-
37-
32+
3833
def execute(self):
3934
4035
# set the variable
@@ -65,33 +60,51 @@ def execute(self):
6560
# the name of the key is related to the name of the variable name in the
6661
# robot class
6762

68-
nt = NetworkTables
69-
mkv = Value.getFactory(default)
70-
71-
def _get(self):
72-
return getattr(self, prop._ntattr).value
73-
74-
def _set(self, value):
75-
v = getattr(self, prop._ntattr)
76-
nt._api.setEntryValueById(v._local_id, mkv(value))
77-
78-
prop = _TunableProperty(fget=_get, fset=_set, doc=doc)
79-
prop._ntdefault = default
80-
prop._ntsubtable = subtable
81-
prop._ntwritedefault = writeDefault
82-
83-
return prop
84-
85-
86-
def setup_tunables(component, cname, prefix="components"):
63+
__slots__ = (
64+
"_ntdefault",
65+
"_ntsubtable",
66+
"_ntwritedefault",
67+
# "__doc__",
68+
"_mkv",
69+
"_nt",
70+
)
71+
72+
def __init__(
73+
self,
74+
default: V,
75+
*,
76+
writeDefault: bool = True,
77+
subtable: Optional[str] = None,
78+
doc=None
79+
) -> None:
80+
if doc is not None:
81+
warnings.warn("tunable no longer uses the doc argument", stacklevel=2)
82+
83+
self._ntdefault = default
84+
self._ntsubtable = subtable
85+
self._ntwritedefault = writeDefault
86+
# self.__doc__ = doc
87+
88+
self._mkv = Value.getFactory(default)
89+
self._nt = NetworkTables
90+
91+
def __get__(self, instance, owner) -> V:
92+
if instance is not None:
93+
return instance._tunables[self].value
94+
return self
95+
96+
def __set__(self, instance, value) -> None:
97+
v = instance._tunables[self]
98+
self._nt._api.setEntryValueById(v._local_id, self._mkv(value))
99+
100+
101+
def setup_tunables(component, cname: str, prefix: Optional[str] = "components") -> None:
87102
"""
88103
Connects the tunables on an object to NetworkTables.
89104
90105
:param component: Component object
91106
:param cname: Name of component
92-
:type cname: str
93107
:param prefix: Prefix to use, or no prefix if None
94-
:type prefix: str
95108
96109
.. note:: This is not needed in normal use, only useful
97110
for testing
@@ -104,27 +117,27 @@ def setup_tunables(component, cname, prefix="components"):
104117
else:
105118
prefix = "/%s/%s" % (prefix, cname)
106119

120+
tunables = {}
121+
107122
for n in dir(cls):
108123
if n.startswith("_"):
109124
continue
110125

111126
prop = getattr(cls, n)
112-
if not isinstance(prop, _TunableProperty):
127+
if not isinstance(prop, tunable):
113128
continue
114129

115130
if prop._ntsubtable:
116131
key = "%s/%s/%s" % (prefix, prop._ntsubtable, n)
117132
else:
118133
key = "%s/%s" % (prefix, n)
119134

120-
ntattr = "_Tunable__%s" % n
121-
122135
ntvalue = NetworkTables.getGlobalAutoUpdateValue(
123136
key, prop._ntdefault, prop._ntwritedefault
124137
)
125-
# double indirection
126-
setattr(component, ntattr, ntvalue)
127-
prop._ntattr = ntattr
138+
tunables[prop] = ntvalue
139+
140+
component._tunables = tunables
128141

129142

130143
def feedback(f=None, *, key: str = None):

magicbot/magicrobot.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from robotpy_ext.misc.orderedclass import OrderedClass
1414
from robotpy_ext.misc.annotations import get_class_annotations
1515

16-
from .magic_tunable import setup_tunables, _TunableProperty, collect_feedbacks
16+
from .magic_tunable import setup_tunables, tunable, collect_feedbacks
1717
from .magic_reset import will_reset_to
1818

1919
__all__ = ["MagicRobot"]
@@ -514,7 +514,7 @@ def _create_components(self):
514514

515515
# - Iterate over set class variables
516516
for m in self.members:
517-
if m.startswith("_") or isinstance(getattr(cls, m, None), _TunableProperty):
517+
if m.startswith("_") or isinstance(getattr(cls, m, None), tunable):
518518
continue
519519

520520
ctyp = getattr(self, m)
@@ -533,7 +533,7 @@ def _create_components(self):
533533
if (
534534
n.startswith("_")
535535
or n in self._exclude_from_injection
536-
or isinstance(getattr(cls, n, None), _TunableProperty)
536+
or isinstance(getattr(cls, n, None), tunable)
537537
):
538538
continue
539539

0 commit comments

Comments
 (0)