Skip to content

Commit 37f78f6

Browse files
committed
Add pytypes property to parameters to return Python type declaration
1 parent 111cfc1 commit 37f78f6

File tree

2 files changed

+136
-2
lines changed

2 files changed

+136
-2
lines changed

param/__init__.py

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import re
2525
import datetime as dt
2626
import collections
27+
import numbers
28+
import typing
2729

2830
from .parameterized import (
2931
Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides,
@@ -692,7 +694,6 @@ def _force(self,obj,objtype=None):
692694
return gen
693695

694696

695-
import numbers
696697
def _is_number(obj):
697698
if isinstance(obj, numbers.Number): return True
698699
# The extra check is for classes that behave like numbers, such as those
@@ -752,6 +753,10 @@ def __init__(self, default=b"", regex=None, allow_None=False, **kwargs):
752753
self.allow_None = (default is None or allow_None)
753754
self._validate(default)
754755

756+
@property
757+
def pytype(self):
758+
return typing.Union[bytes, None] if self.allow_None else bytes
759+
755760
def _validate_regex(self, val, regex):
756761
if (val is None and self.allow_None):
757762
return
@@ -834,6 +839,10 @@ def __init__(self, default=0.0, bounds=None, softbounds=None,
834839
self.step = step
835840
self._validate(default)
836841

842+
@property
843+
def pytype(self):
844+
return typing.Union[numbers.Number, None] if self.allow_None else numbers.Number
845+
837846
def __get__(self, obj, objtype):
838847
"""
839848
Same as the superclass's __get__, but if the value was
@@ -963,6 +972,10 @@ class Integer(Number):
963972
def __init__(self, default=0, **params):
964973
Number.__init__(self, default=default, **params)
965974

975+
@property
976+
def pytype(self):
977+
return typing.Union[int, None] if self.allow_None else int
978+
966979
def _validate_value(self, val, allow_None):
967980
if callable(val):
968981
return
@@ -1000,6 +1013,10 @@ def __init__(self, default=False, bounds=(0,1), **params):
10001013
self.bounds = bounds
10011014
super(Boolean, self).__init__(default=default, **params)
10021015

1016+
@property
1017+
def pytype(self):
1018+
return typing.Union[bool, None] if self.allow_None else bool
1019+
10031020
def _validate_value(self, val, allow_None):
10041021
if allow_None:
10051022
if not isinstance(val, bool) and val is not None:
@@ -1034,6 +1051,14 @@ def __init__(self, default=(0,0), length=None, **params):
10341051
self.length = length
10351052
self._validate(default)
10361053

1054+
@property
1055+
def pytype(self):
1056+
if self.length:
1057+
pytype = typing.Tuple[(typing.Any,)*self.length]
1058+
else:
1059+
ptype = typing.Tuple[typing.Any, ...]
1060+
return typing.Union[pytype, None] if self.allow_None else pytype
1061+
10371062
def _validate_value(self, val, allow_None):
10381063
if val is None and allow_None:
10391064
return
@@ -1071,6 +1096,14 @@ def deserialize(cls, value):
10711096
class NumericTuple(Tuple):
10721097
"""A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length."""
10731098

1099+
@property
1100+
def pytype(self):
1101+
if self.length:
1102+
pytype = typing.Tuple[(numbers.Number,)*self.length]
1103+
else:
1104+
ptype = typing.Tuple[numbers.Number, ...]
1105+
return typing.Union[pytype, None] if self.allow_None else pytype
1106+
10741107
def _validate_value(self, val, allow_None):
10751108
super(NumericTuple, self)._validate_value(val, allow_None)
10761109
if allow_None and val is None:
@@ -1088,6 +1121,11 @@ class XYCoordinates(NumericTuple):
10881121
def __init__(self, default=(0.0, 0.0), **params):
10891122
super(XYCoordinates,self).__init__(default=default, length=2, **params)
10901123

1124+
@property
1125+
def pytype(self):
1126+
pytype = typing.Tuple[numbers.Number, numbers.Number]
1127+
return typing.Union[pytype, None] if self.allow_None else pytype
1128+
10911129

10921130
class Callable(Parameter):
10931131
"""
@@ -1099,6 +1137,11 @@ class Callable(Parameter):
10991137
2.4, so instantiate must be False for those values.
11001138
"""
11011139

1140+
@proprty
1141+
def pytype(self):
1142+
ctype = typing.Callable[..., typing.Any]
1143+
return typing.Union[ctype, None] if self.allow_None else ctype
1144+
11021145
def _validate_value(self, val, allow_None):
11031146
if (allow_None and val is None) or callable(val):
11041147
return
@@ -1197,6 +1240,11 @@ class SelectorBase(Parameter):
11971240

11981241
__abstract = True
11991242

1243+
@proprty
1244+
def pytype(self):
1245+
literal = typing.Literal[tuple(self.get_range().values())]
1246+
return typing.Union[literal, None] if self.allow_None else literal
1247+
12001248
def get_range(self):
12011249
raise NotImplementedError("get_range() must be implemented in subclasses.")
12021250

@@ -1433,6 +1481,17 @@ def __init__(self, default=[], class_=None, item_type=None,
14331481
**params)
14341482
self._validate(default)
14351483

1484+
@property
1485+
def pytype(self):
1486+
if isinstance(self.item_type, tuple):
1487+
item_type = typing.Union[self.item_type]
1488+
elif self.item_type is not None:
1489+
item_type = self.item_type
1490+
else:
1491+
item_type = typing.Any
1492+
list_type = typing.List[item_type]
1493+
return typing.Union[list_type, None] if self.allow_None else list_type
1494+
14361495
def _validate(self, val):
14371496
"""
14381497
Checks that the value is numeric and that it is within the hard
@@ -1487,6 +1546,11 @@ class HookList(List):
14871546
"""
14881547
__slots__ = ['class_', 'bounds']
14891548

1549+
@property
1550+
def pytype(self):
1551+
list_type = typing.List[typing.Callable[[], None]]
1552+
return typing.Union[list_type, None] if self.allow_None else list_type
1553+
14901554
def _validate_value(self, val, allow_None):
14911555
super(HookList, self)._validate_value(val, allow_None)
14921556
if allow_None and val is None:
@@ -1506,16 +1570,27 @@ class Dict(ClassSelector):
15061570
def __init__(self, default=None, **params):
15071571
super(Dict, self).__init__(dict, default=default, **params)
15081572

1573+
@property
1574+
def pytype(self):
1575+
dict_type = typing.Dict[typing.Hashable, typing.Any]
1576+
return typing.Union[dict_type, None] if self.allow_None else dict_type
1577+
1578+
15091579

15101580
class Array(ClassSelector):
15111581
"""
15121582
Parameter whose value is a numpy array.
15131583
"""
15141584

15151585
def __init__(self, default=None, **params):
1516-
from numpy import ndarray
1586+
15171587
super(Array, self).__init__(ndarray, allow_None=True, default=default, **params)
15181588

1589+
@property
1590+
def pytype(self):
1591+
from numpy import ndarray
1592+
return ndarray
1593+
15191594
@classmethod
15201595
def serialize(cls, value):
15211596
if value is None:
@@ -1559,6 +1634,11 @@ def __init__(self, default=None, rows=None, columns=None, ordered=None, **params
15591634
super(DataFrame,self).__init__(pdDFrame, default=default, **params)
15601635
self._validate(self.default)
15611636

1637+
@property
1638+
def pytype(self):
1639+
from pandas import DataFrame
1640+
return DataFrame
1641+
15621642
def _length_bounds_check(self, bounds, length, name):
15631643
message = '{name} length {length} does not match declared bounds of {bounds}'
15641644
if not isinstance(bounds, tuple):
@@ -1635,6 +1715,11 @@ def __init__(self, default=None, rows=None, allow_None=False, **params):
16351715
**params)
16361716
self._validate(self.default)
16371717

1718+
@property
1719+
def pytype(self):
1720+
from pandas import Series
1721+
return Series
1722+
16381723
def _length_bounds_check(self, bounds, length, name):
16391724
message = '{name} length {length} does not match declared bounds of {bounds}'
16401725
if not isinstance(bounds, tuple):
@@ -1778,6 +1863,13 @@ def __init__(self, default=None, search_paths=None, **params):
17781863
self.search_paths = search_paths
17791864
super(Path,self).__init__(default,**params)
17801865

1866+
@property
1867+
def pytype(self):
1868+
path_types =(str, pathlib.Path)
1869+
if self.allow_None:
1870+
path_types += (None,)
1871+
return typing.Union[path_types]
1872+
17811873
def _resolve(self, path):
17821874
return resolve_path(path, path_to_file=None, search_paths=self.search_paths)
17831875

@@ -1904,6 +1996,12 @@ def __init__(self, default=None, objects=None, **kwargs):
19041996
super(ListSelector,self).__init__(
19051997
objects=objects, default=default, empty_default=True, **kwargs)
19061998

1999+
@property
2000+
def pytype(self):
2001+
literal = typing.Literal[tuple(self.get_range().values())]
2002+
ltype = typing.List[literal]
2003+
return typing.Union[ltype, None] if self.allow_None else ltype
2004+
19072005
def compute_default(self):
19082006
if self.default is None and callable(self.compute_default_fn):
19092007
self.default = self.compute_default_fn()
@@ -1954,6 +2052,14 @@ class Date(Number):
19542052
def __init__(self, default=None, **kwargs):
19552053
super(Date, self).__init__(default=default, **kwargs)
19562054

2055+
@property
2056+
def pytype(self):
2057+
if self.allow_None:
2058+
date_types = dt_types + (None,)
2059+
else:
2060+
date_types = dt_types
2061+
return typing.Union[date_types]
2062+
19572063
def _validate_value(self, val, allow_None):
19582064
"""
19592065
Checks that the value is numeric and that it is within the hard
@@ -1998,6 +2104,10 @@ class CalendarDate(Number):
19982104
def __init__(self, default=None, **kwargs):
19992105
super(CalendarDate, self).__init__(default=default, **kwargs)
20002106

2107+
@property
2108+
def pytype(self):
2109+
return typing.Union[dt.datetime, None] if self.allow_None else dt.datetime
2110+
20012111
def _validate_value(self, val, allow_None):
20022112
"""
20032113
Checks that the value is numeric and that it is within the hard
@@ -2076,6 +2186,10 @@ def __init__(self, default=None, allow_named=True, **kwargs):
20762186
self.allow_named = allow_named
20772187
self._validate(default)
20782188

2189+
@property
2190+
def pytype(self):
2191+
return typing.Union[str, None] if self.allow_None else str
2192+
20792193
def _validate(self, val):
20802194
self._validate_value(val, self.allow_None)
20812195
self._validate_allow_named(val, self.allow_named)
@@ -2151,6 +2265,12 @@ class DateRange(Range):
21512265
Bounds must be specified as datetime or date types (see param.dt_types).
21522266
"""
21532267

2268+
@property
2269+
def pytype(self):
2270+
date_type = typing.Union[dt_types]
2271+
range_type = typing.Tuple[date_type, date_type]
2272+
return typing.Union[range_type, None] if self.allow_None else range_type
2273+
21542274
def _validate_value(self, val, allow_None):
21552275
# Cannot use super()._validate_value as DateRange inherits from
21562276
# NumericTuple which check that the tuple values are numbers and
@@ -2205,6 +2325,7 @@ def deserialize(cls, value):
22052325
# As JSON has no tuple representation
22062326
return tuple(deserialized)
22072327

2328+
22082329
class CalendarDateRange(Range):
22092330
"""
22102331
A date range specified as (start_date, end_date).
@@ -2281,6 +2402,10 @@ def __init__(self,default=False,bounds=(0,1),**params):
22812402
# back to False while triggered callbacks are executing
22822403
super(Event, self).__init__(default=default,**params)
22832404

2405+
@property
2406+
def pytype(self):
2407+
return bool
2408+
22842409
def _reset_event(self, obj, val):
22852410
val = False
22862411
if obj is None:

param/parameterized.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import random
1717
import numbers
1818
import operator
19+
import typing
1920

2021
# Allow this file to be used standalone if desired, albeit without JSON serialization
2122
try:
@@ -1071,6 +1072,10 @@ class hierarchy (see ParameterizedMetaclass).
10711072
self.watchers = {}
10721073
self.per_instance = per_instance
10731074

1075+
@property
1076+
def pytype(self):
1077+
return typing.Any
1078+
10741079
@classmethod
10751080
def serialize(cls, value):
10761081
"Given the parameter value, return a Python value suitable for serialization"
@@ -1331,6 +1336,10 @@ def __init__(self, default="", regex=None, allow_None=False, **kwargs):
13311336
self.allow_None = (default is None or allow_None)
13321337
self._validate(default)
13331338

1339+
@property
1340+
def pytype(self):
1341+
return typing.Union[str, None] if self.allow_None else str
1342+
13341343
def _validate_regex(self, val, regex):
13351344
if (val is None and self.allow_None):
13361345
return

0 commit comments

Comments
 (0)