Skip to content
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

Address deprecations: remove _param_watchers, raise RuntimeError on unsafe ops during init, and failed validation of a parameter default after inheritance #973

Merged
merged 5 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions doc/user_guide/Parameters.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,7 @@
" - `param`: Property that helps keep the Parameterized namespace clean and disambiguate between Parameter objects and parameter values, it gives access to a namespace that offers various methods (see the section below) to update and inspect the Parameterized object at hand.\n",
"- Private attributes:\n",
" - `_param__parameters`: Store the object returned by `.param` on the class\n",
" - `_param__private`: Store various internal data on Parameterized class and instances\n",
" - `_param_watchers` (deprecated in Param 2.0 and to be removed soon): Store a dictionary of instance watchers"
" - `_param__private`: Store various internal data on Parameterized class and instances"
]
},
{
Expand Down
45 changes: 9 additions & 36 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -2431,15 +2431,13 @@ def objects(self_, instance=True):
instance='existing'.
"""
if self_.self is not None and not self_.self._param__private.initialized and instance is True:
warnings.warn(
raise RuntimeError(
'Looking up instance Parameter objects (`.param.objects()`) until '
'the Parameterized instance has been fully initialized is deprecated and will raise an error in a future version. '
'the Parameterized instance has been fully initialized is not allowed. '
'Ensure you have called `super().__init__(**params)` in your Parameterized '
'constructor before trying to access instance Parameter objects, or '
'looking up the class Parameter objects with `.param.objects(instance=False)` '
'may be enough for your use case.',
category=_ParamFutureWarning,
stacklevel=2,
)

pdict = self_._cls_parameters
Expand All @@ -2461,13 +2459,10 @@ def trigger(self_, *param_names):
that it is clear which Event parameter has been triggered.
"""
if self_.self is not None and not self_.self._param__private.initialized:
warnings.warn(
raise RuntimeError(
'Triggering watchers on a partially initialized Parameterized instance '
'is deprecated and will raise an error in a future version. '
'Ensure you have called super().__init__(**params) in '
'is not allowed. Ensure you have called super().__init__(**params) in '
'the Parameterized instance constructor before trying to set up a watcher.',
category=_ParamFutureWarning,
stacklevel=2,
)

trigger_params = [p for p in self_
Expand Down Expand Up @@ -2804,8 +2799,6 @@ def outputs(self_):
outputs = {}
for cls in classlist(self_.cls):
for name in dir(cls):
if name == '_param_watchers':
continue
method = getattr(self_.self_or_cls, name)
dinfo = getattr(method, '_dinfo', {})
if 'outputs' not in dinfo:
Expand Down Expand Up @@ -2916,13 +2909,10 @@ def _spec_to_obj(self_, spec, dynamic=True, intermediate=True):

def _register_watcher(self_, action, watcher, what='value'):
if self_.self is not None and not self_.self._param__private.initialized:
warnings.warn(
raise RuntimeError(
'(Un)registering a watcher on a partially initialized Parameterized instance '
'is deprecated and will raise an error in a future version. Ensure '
'you have called super().__init__(**) in the Parameterized instance '
'constructor before trying to set up a watcher.',
category=_ParamFutureWarning,
stacklevel=4,
'is not allowed. Ensure you have called super().__init__(**) in the '
'Parameterized instance constructor before trying to set up a watcher.',
)

parameter_names = watcher.parameter_names
Expand Down Expand Up @@ -3664,8 +3654,7 @@ def __param_inheritance(mcs, param_name, param):
# might raise other types of error, so we catch them all.
except Exception as e:
msg = f'{_validate_error_prefix(param)} failed to validate its ' \
'default value on class creation, this is going to raise ' \
'an error in the future. '
'default value on class creation. '
parents = ', '.join(klass.__name__ for klass in mcs.__mro__[1:-2])
if not type_change and slot_overridden:
msg += (
Expand All @@ -3686,11 +3675,7 @@ def __param_inheritance(mcs, param_name, param):
# performance reasons.
pass
msg += f'\nValidation failed with:\n{e}'
warnings.warn(
msg,
category=_ParamFutureWarning,
stacklevel=4,
)
raise RuntimeError(msg) from e

def get_param_descriptor(mcs,param_name):
"""
Expand Down Expand Up @@ -4208,18 +4193,6 @@ def __init__(self, **params):
def param(self):
return Parameters(self.__class__, self=self)

#PARAM3_DEPRECATION
@property
@_deprecated(extra_msg="Use `inst.param.watchers` instead.", warning_cat=_ParamFutureWarning)
def _param_watchers(self):
return self._param__private.watchers

#PARAM3_DEPRECATION
@_param_watchers.setter
@_deprecated(extra_msg="Use `inst.param.watchers = ...` instead.", warning_cat=_ParamFutureWarning)
def _param_watchers(self, value):
self._param__private.watchers = value

# 'Special' methods

def __getstate__(self):
Expand Down
156 changes: 0 additions & 156 deletions tests/testdeprecations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Test deprecation warnings.
"""
import re
import warnings

import param
Expand Down Expand Up @@ -102,161 +101,6 @@ def test_deprecate_all_equal(self):
with pytest.raises(param._utils.ParamDeprecationWarning):
param.parameterized.all_equal(1, 1)

def test_deprecate_param_watchers(self):
with pytest.raises(param._utils.ParamFutureWarning):
param.parameterized.Parameterized()._param_watchers

def test_deprecate_param_watchers_setter(self):
with pytest.raises(param._utils.ParamFutureWarning):
param.parameterized.Parameterized()._param_watchers = {}

def test_param_error_unsafe_ops_before_initialized(self):
class P(param.Parameterized):

x = param.Parameter()

def __init__(self, **params):
with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
'Looking up instance Parameter objects (`.param.objects()`) until '
'the Parameterized instance has been fully initialized is deprecated and will raise an error in a future version. '
'Ensure you have called `super().__init__(**params)` in your Parameterized '
'constructor before trying to access instance Parameter objects, or '
'looking up the class Parameter objects with `.param.objects(instance=False)` '
'may be enough for your use case.',
)
):
self.param.objects()

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
'Triggering watchers on a partially initialized Parameterized instance '
'is deprecated and will raise an error in a future version. '
'Ensure you have called super().__init__(**params) in '
'the Parameterized instance constructor before trying to set up a watcher.',
)
):
self.param.trigger('x')

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
'(Un)registering a watcher on a partially initialized Parameterized instance '
'is deprecated and will raise an error in a future version. Ensure '
'you have called super().__init__(**) in the Parameterized instance '
'constructor before trying to set up a watcher.',
)
):
self.param.watch(print, 'x')

self.param.objects(instance=False)
super().__init__(**params)

P()

# Inheritance tests to be move to testparameterizedobject.py when warnings will be turned into errors

def test_inheritance_with_incompatible_defaults(self):
class A(param.Parameterized):
p = param.String()

class B(A): pass

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
"Number parameter 'C.p' failed to validate its "
"default value on class creation, this is going to raise "
"an error in the future. The Parameter type changed between class 'C' "
"and one of its parent classes (B, A) which made it invalid. "
"Please fix the Parameter type."
"\nValidation failed with:\nNumber parameter 'C.p' only takes numeric values, not <class 'str'>."
)
):
class C(B):
p = param.Number()

def test_inheritance_default_validation_with_more_specific_type(self):
class A(param.Parameterized):
p = param.Tuple(default=('a', 'b'))

class B(A): pass

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
"NumericTuple parameter 'C.p' failed to validate its "
"default value on class creation, this is going to raise "
"an error in the future. The Parameter type changed between class 'C' "
"and one of its parent classes (B, A) which made it invalid. "
"Please fix the Parameter type."
"\nValidation failed with:\nNumericTuple parameter 'C.p' only takes numeric values, not <class 'str'>."
)
):
class C(B):
p = param.NumericTuple()

def test_inheritance_with_changing_bounds(self):
class A(param.Parameterized):
p = param.Number(default=5)

class B(A): pass

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
"Number parameter 'C.p' failed to validate its "
"default value on class creation, this is going to raise "
"an error in the future. The Parameter is defined with attributes "
"which when combined with attributes inherited from its parent "
"classes (B, A) make it invalid. Please fix the Parameter attributes."
"\nValidation failed with:\nNumber parameter 'C.p' must be at most 3, not 5."
)
):
class C(B):
p = param.Number(bounds=(-1, 3))

def test_inheritance_with_changing_default(self):
class A(param.Parameterized):
p = param.Number(default=5, bounds=(3, 10))

class B(A): pass

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
"Number parameter 'C.p' failed to validate its "
"default value on class creation, this is going to raise "
"an error in the future. The Parameter is defined with attributes "
"which when combined with attributes inherited from its parent "
"classes (B, A) make it invalid. Please fix the Parameter attributes."
"\nValidation failed with:\nNumber parameter 'C.p' must be at least 3, not 1."
)
):
class C(B):
p = param.Number(default=1)

def test_inheritance_with_changing_class_(self):
class A(param.Parameterized):
p = param.ClassSelector(class_=int, default=5)

class B(A): pass

with pytest.raises(
param._utils.ParamFutureWarning,
match=re.escape(
"ClassSelector parameter 'C.p' failed to validate its "
"default value on class creation, this is going to raise "
"an error in the future. The Parameter is defined with attributes "
"which when combined with attributes inherited from its parent "
"classes (B, A) make it invalid. Please fix the Parameter attributes."
"\nValidation failed with:\nClassSelector parameter 'C.p' value must be an instance of str, not 5."
)
):
class C(B):
p = param.ClassSelector(class_=str)

class TestDeprecateParameters:

Expand Down
Loading