diff --git a/doc/user_guide/ParameterizedFunctions.ipynb b/doc/user_guide/ParameterizedFunctions.ipynb index e38aff00e..be389eb7d 100644 --- a/doc/user_guide/ParameterizedFunctions.ipynb +++ b/doc/user_guide/ParameterizedFunctions.ipynb @@ -22,7 +22,7 @@ "from param import Parameter, ParameterizedFunction, ParamOverrides\n", "\n", "class multiply(ParameterizedFunction):\n", - " \"Function to multiply two arguments.\"\n", + " \"\"\"Function to multiply two arguments.\"\"\"\n", "\n", " left = Parameter(2, doc=\"Left-hand-side argument\")\n", " right = Parameter(4, doc=\"Right-hand-side argument\")\n", diff --git a/numbergen/__init__.py b/numbergen/__init__.py index ee1dfbf74..1b9974dcb 100644 --- a/numbergen/__init__.py +++ b/numbergen/__init__.py @@ -135,7 +135,7 @@ def __abs__ (self): return UnaryOperator(self,operator.abs) } def pprint(x, *args, **kwargs): - "Pretty-print the provided item, translating operators to their symbols" + """Pretty-print the provided item, translating operators to their symbols""" return x.pprint(*args, **kwargs) if hasattr(x,'pprint') else operator_symbols.get(x, repr(x)) @@ -214,6 +214,7 @@ class Hash: for __call__ must be specified in the constructor and must stay constant across calls. """ + def __init__(self, name, input_count): self.name = name self.input_count = input_count @@ -224,7 +225,6 @@ def __init__(self, name, input_count): def _rational(self, val): """Convert the given value to a rational, if necessary.""" - I32 = 4294967296 # Maximum 32 bit unsigned int (i.e. 'I') value if isinstance(val, int): numer, denom = val, 1 diff --git a/param/_utils.py b/param/_utils.py index 650632eef..fedad877e 100644 --- a/param/_utils.py +++ b/param/_utils.py @@ -122,7 +122,7 @@ def inner(*args, **kwargs): # Copy of Python 3.2 reprlib's recursive_repr but allowing extra arguments def _recursive_repr(fillvalue='...'): - 'Decorator to make a repr function return fillvalue for a recursive call' + """Decorator to make a repr function return fillvalue for a recursive call""" def decorating_function(user_function): repr_running = set() @@ -631,7 +631,8 @@ def __iter__(cls): def gen_types(gen_func): """ Decorator which takes a generator function which yields difference types - make it so it can be called with isinstance and issubclass.""" + make it so it can be called with isinstance and issubclass. + """ if not inspect.isgeneratorfunction(gen_func): msg = "gen_types decorator can only be applied to generator" raise TypeError(msg) diff --git a/param/ipython.py b/param/ipython.py index 27414f664..da3c26cc0 100644 --- a/param/ipython.py +++ b/param/ipython.py @@ -61,7 +61,6 @@ def get_param_info(self, obj, include_super=True): and the dictionary of parameter values. If include_super is True, parameters are also collected from the super classes. """ - params = dict(obj.param.objects('existing')) if isinstance(obj,type): changed = [] @@ -86,7 +85,6 @@ def param_docstrings(self, info, max_col_len=100, only_changed=False): docstrings in a clean format (alternating red and blue for readability). """ - (params, val_dict, changed) = info contents = [] displayed_params = [] @@ -147,7 +145,6 @@ def _build_table(self, info, order, max_col_len=40, only_changed=False): Collect the information about parameters needed to build a properly formatted table and then tabulate it. """ - info_list, bounds_dict = [], {} (params, val_dict, changed) = info col_widths = {k:0 for k in order} @@ -209,7 +206,6 @@ def _tabulate(self, info_list, col_widths, changed, order, bounds_dict): order: The order of the table columns bound_dict: Dictionary of appropriately formatted bounds """ - contents, tail = [], [] column_set = {k for _, row in info_list for k in row} columns = [col for col in order if col in column_set] @@ -316,6 +312,7 @@ class ParamMagics(Magics): Implements the %params line magic used to inspect the parameters of a parameterized class or object. """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.param_pager = ParamPager() diff --git a/param/parameterized.py b/param/parameterized.py index 2576659dc..697901a01 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -1,6 +1,4 @@ -""" -Generic support for objects with full-featured Parameters and -messaging. +"""Generic support for objects with full-featured Parameters and messaging. This file comes from the Param library (https://github.com/holoviz/param) but can be taken out of the param module and used on its own if desired, @@ -127,9 +125,7 @@ def get_logger(name=None): _reference_transforms = [] def register_reference_transform(transform): - """ - Appends a transform to extract potential parameter dependencies - from an object. + """Append a transform to extract potential parameter dependencies from an object. Arguments --------- @@ -139,9 +135,9 @@ def register_reference_transform(transform): def transform_reference(arg): """ - Applies transforms to turn objects which should be treated like - a parameter reference into a valid reference that can be resolved - by Param. This is useful for adding handling for depending on objects + Apply transforms to turn objects which should be treated like a parameter reference into a valid reference that can be resolved by Param. + + This is useful for adding handling for depending on objects that are not simple Parameters or functions with dependency definitions. """ @@ -152,7 +148,7 @@ def transform_reference(arg): return arg def eval_function_with_deps(function): - """Evaluates a function after resolving its dependencies. + """Evaluate a function after resolving its dependencies. Calls and returns a function after resolving any dependencies stored on the _dinfo attribute and passing the resolved values @@ -168,9 +164,7 @@ def eval_function_with_deps(function): return function(*args, **kwargs) def resolve_value(value, recursive=True): - """ - Resolves the current value of a dynamic reference. - """ + """Resolve the current value of a dynamic reference.""" if not recursive: pass elif isinstance(value, (list, tuple)): @@ -194,9 +188,7 @@ def resolve_value(value, recursive=True): return value def resolve_ref(reference, recursive=False): - """ - Resolves all parameters a dynamic reference depends on. - """ + """Resolve all parameters a dynamic reference depends on.""" if recursive: if isinstance(reference, (list, tuple, set)): return [r for v in reference for r in resolve_ref(v, recursive)] @@ -230,15 +222,12 @@ def resolve_ref(reference, recursive=False): return [] def _identity_hook(obj, val): - """To be removed when set_hook is removed""" + """To be removed when set_hook is removed.""" return val class _Undefined: - """ - Dummy value to signal completely undefined values rather than - simple None values. - """ + """Dummy value to signal completely undefined values rather than simple None values.""" def __bool__(self): # Haven't defined whether Undefined is falsy or truthy, @@ -255,9 +244,7 @@ def __repr__(self): @contextmanager def logging_level(level): - """ - Temporarily modify param's logging level. - """ + """Temporarily modify param's logging level.""" level = level.upper() levels = [DEBUG, INFO, WARNING, ERROR, CRITICAL, VERBOSE] level_names = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'VERBOSE'] @@ -276,8 +263,10 @@ def logging_level(level): @contextmanager def _batch_call_watchers(parameterized, enable=True, run=True): - """ - Internal version of batch_call_watchers, adding control over queueing and running. + """Add control over queueing and running. + + Internal version of batch_call_watchers. + Only actually batches events if enable=True; otherwise a no-op. Only actually calls the accumulated watchers on exit if run=True; otherwise they remain queued. """ @@ -302,8 +291,9 @@ def batch_watch(parameterized, enable=True, run=True): @contextmanager def batch_call_watchers(parameterized): """ - Context manager to batch events to provide to Watchers on a - parameterized object. This context manager queues any events + Context manager to batch events to provide to Watchers on a parameterized object. + + This context manager queues any events triggered by setting a parameter on the supplied parameterized object, saving them up to dispatch them all at once when the context manager exits. @@ -330,10 +320,7 @@ def _syncing(parameterized, parameters): @contextmanager def edit_constant(parameterized): - """ - Temporarily set parameters on Parameterized object to constant=False - to allow editing them. - """ + """Temporarily set parameters on Parameterized object to constant=False to allow editing them.""" params = parameterized.param.objects('existing').values() constants = [p.constant for p in params] for p in params: @@ -349,10 +336,7 @@ def edit_constant(parameterized): @contextmanager def discard_events(parameterized): - """ - Context manager that discards any events within its scope - triggered on the supplied parameterized object. - """ + """Context manager that discards any events within its scope triggered on the supplied parameterized object.""" batch_watch = parameterized.param._BATCH_WATCH parameterized.param._BATCH_WATCH = True watchers, events = (list(parameterized.param._state_watchers), @@ -456,6 +440,7 @@ class bothmethod: object (if called on the class) or the instance object (if called on the instance) as its first argument. """ + def __init__(self, method): self.method = method @@ -484,7 +469,6 @@ def no_instance_params(cls): def _instantiate_param_obj(paramobj, owner=None): """Return a Parameter object suitable for instantiation given the class's Parameter object""" - # Shallow-copy Parameter object without the watchers p = copy.copy(paramobj) p.owner = owner @@ -922,6 +906,7 @@ class ParameterMetaclass(type): """ Metaclass allowing control over creation of Parameter classes. """ + def __new__(mcs, classname, bases, classdict): # store the class's docstring in __classdoc @@ -961,10 +946,7 @@ def __getattribute__(mcs,name): class _ParameterBase(metaclass=ParameterMetaclass): - """ - Base Parameter class used to dynamically update the signature of all - the Parameters. - """ + """Base Parameter class used to dynamically update the signature of all the Parameters.""" @classmethod def _modified_slots_defaults(cls): @@ -1203,7 +1185,6 @@ def __init__(self, default=Undefined, *, doc=Undefined, # pylint: disable-msg=R0 instantiate=Undefined, constant=Undefined, readonly=Undefined, pickle_default_value=Undefined, allow_None=Undefined, per_instance=Undefined, allow_refs=Undefined, nested_refs=Undefined): - """Initialize a new Parameter object and store the supplied attributes: default: the owning class's value for the attribute represented @@ -1283,7 +1264,6 @@ def __init__(self, default=Undefined, *, doc=Undefined, # pylint: disable-msg=R0 inheritance of Parameter slots (attributes) from the owning-class' class hierarchy (see ParameterizedMetaclass). """ - self.name = None self.owner = None self.allow_refs = allow_refs @@ -1302,12 +1282,12 @@ class hierarchy (see ParameterizedMetaclass). @classmethod def serialize(cls, value): - "Given the parameter value, return a Python value suitable for serialization" + """Given the parameter value, return a Python value suitable for serialization""" return value @classmethod def deserialize(cls, value): - "Given a serializable Python value, return a value that the parameter can be set to" + """Given a serializable Python value, return a value that the parameter can be set to""" return value def schema(self, safe=False, subset=None, mode='json'): @@ -1320,6 +1300,43 @@ def schema(self, safe=False, subset=None, mode='json'): @property def rx(self): + """ + The reactive namespace. + + Provides reactive versions of operations that cannot be made reactive through operator overloading, such as + `.rx.and_` and `.rx.bool`. Calling this namespace (`()`) returns a reactive expression. + + Parameters + ---------- + None + + Returns + ------- + Reactive expression + The result of calling the reactive namespace is a reactive expression. + + User Guide + ---------- + https://param.holoviz.org/user_guide/Reactive_Expressions.html#special-methods-on-rx + + Examples + -------- + Create a Parameterized instance: + + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P(a=1, b="hello") + + Get the current value: + + >>> a = p.param.a.rx.value + + Call it to get a reactive expression: + + >>> rx_value = p.param.a.rx() + """ from .reactive import reactive_ops return reactive_ops(self) @@ -2260,12 +2277,41 @@ def set_default(self_,param_name,value): cls = self_.cls setattr(cls,param_name,value) - def add_parameter(self_, param_name, param_obj): + def add_parameter(self_, param_name: str, param_obj: Parameter): """ - Add a new Parameter object into this object's class. + Add a new Parameter object to this class. + + This method allows dynamically adding a Parameter to the class, resulting in behavior equivalent to declaring + the Parameter in the class's source code. + + Parameters + ---------- + param_name : str + The name of the parameter to add. + param_obj : Parameter + The Parameter object to add. + + Examples + -------- + Create a Parameterized class: - Should result in a Parameter equivalent to one declared - in the class's source code. + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P() + + Add a new parameter to the class via the class: + + >>> P.param.add_parameter('c', param.Tuple(default=(1, 2, 3))) + >>> print(p.c) + (1, 2, 3) + + Add a new parameter to the class via the instance: + + >>> p.param.add_parameter('d', param.Tuple(default=(3, 2, 1))) + >>> print(p.d) + (3, 2, 1) """ # Could have just done setattr(cls,param_name,param_obj), # which is supported by the metaclass's __setattr__ , but @@ -2309,12 +2355,45 @@ def params(self_, parameter_name=None): def update(self_, arg=Undefined, /, **kwargs): """ - For the given dictionary or iterable or set of param=value - keyword arguments, sets the corresponding parameter of this - object or class to the given value. + Update one or more parameters of this object or class. + + Allows setting the parameters of the object or class using a dictionary, an iterable, or keyword arguments + in the form of `param=value`. The specified parameters will be updated to the given values. + + This method can also be used as a context manager to temporarily set and then reset parameter values. + + Parameters + ---------- + **params : dict or iterable or keyword arguments + The parameters to update, provided as a dictionary, iterable, or keyword arguments in `param=value` format. + + References + ---------- + User Guide: https://param.holoviz.org/user_guide/Parameters.html#other-parameterized-methods + + Examples + -------- + Create a Parameterized instance: + + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P() - May also be used as a context manager to temporarily set and - then reset parameter values. + Update parameters permanently: + + >>> p.param.update(a=1, b="Hello") + >>> print(p.a, p.b) + 1 Hello + + Update parameters temporarily: + + >>> with p.param.update(a=2, b="World"): + >>> print(p.a, p.b) + 2 World + >>> print(p.a, p.b) + 1 Hello """ refs = {} if self_.self is not None: @@ -2598,6 +2677,41 @@ class or instance that contains an iterable collection of obj.param.set_dynamic_time_fn(time_fn,sublistattr) def serialize_parameters(self_, subset=None, mode='json'): + """ + Return the serialized parameters of the Parameterized object. + + Parameters + ---------- + subset : list, optional + A list of parameter names to serialize. If None, all parameters will be serialized. Defaults to None. + mode : str, optional + The serialization format. By default, only 'json' is supported. Defaults to 'json'. + + Returns + ------- + Any + The serialized value. + + User Guide + ---------- + https://param.holoviz.org/user_guide/Serialization_and_Persistence.html#serializing-with-json + + Examples + -------- + Create a Parameterized instance and serialize its parameters: + + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P(a=1, b="hello") + + Serialize parameters: + + >>> serialized_data = p.param.serialize_parameters() + >>> print(serialized_data) + {"name": "P00002", "a": 1, "b": "hello"} + """ self_or_cls = self_.self_or_cls if mode not in Parameter._serializers: raise ValueError(f'Mode {mode!r} not in available serialization formats {list(Parameter._serializers.keys())!r}') @@ -2611,7 +2725,44 @@ def serialize_value(self_, pname, mode='json'): serializer = Parameter._serializers[mode] return serializer.serialize_parameter_value(self_or_cls, pname) - def deserialize_parameters(self_, serialization, subset=None, mode='json'): + def deserialize_parameters(self_, serialization, subset=None, mode='json') -> dict: + """ + Deserialize the given serialized data. This data can be used to create a + `Parameterized` object or update the parameters of an existing `Parameterized` object. + + Parameters + ---------- + serialization : str + The serialized parameter data as a JSON string. + subset : list of str, optional + A list of parameter names to deserialize. If `None`, all parameters will be + deserialized. Defaults to `None`. + mode : str, optional + The serialization format. By default, only 'json' is supported. + Defaults to 'json'. + + Returns + ------- + dict + A dictionary with parameter names as keys and deserialized values. + + User Guide + ---------- + https://param.holoviz.org/user_guide/Serialization_and_Persistence.html#serializing-with-json + + Examples + -------- + >>> import param + >>> class P(param.Parameterized): + ... a = param.Number() + ... b = param.String() + ... + >>> serialized_data = '{"a": 1, "b": "hello"}' + >>> deserialized_data = P.param.deserialize_parameters(serialized_data) + >>> print(deserialized_data) + {'a': 1, 'b': 'hello'} + >>> instance = P(**deserialized_data) + """ self_or_cls = self_.self_or_cls serializer = Parameter._serializers[mode] return serializer.deserialize_parameters(self_or_cls, serialization, subset=subset) @@ -3316,6 +3467,7 @@ class ParameterizedMetaclass(type): attribute __abstract set to True. The 'abstract' attribute can be used to find out if a class is abstract or not. """ + def __init__(mcs, name, bases, dict_): """ Initialize the class object (not an instance of the class, but @@ -3747,7 +3899,6 @@ def script_repr(val, imports=None, prefix="\n ", settings=[], ways that are more suitable for saving as a separate script than for e.g. pretty-printing at the Python prompt. """ - if imports is None: imports = [] @@ -3805,7 +3956,6 @@ def pprint(val,imports=None, prefix="\n ", settings=[], parameter can be suppressed by returning None from the appropriate hook in script_repr_reg. """ - if imports is None: imports = [] @@ -4206,6 +4356,37 @@ def __init__(self, **params): @property def param(self): + """ + The `.param` namespace for `Parameterized` classes and instances. + + This namespace provides access to powerful methods and properties for managing + parameters in a `Parameterized` object. It includes utilities for adding parameters, + updating parameters, debugging, serialization, logging, and more. + + User Guide + ---------- + For more details on parameter objects and instances, see: + https://param.holoviz.org/user_guide/Parameters.html#parameter-objects-and-instances + + Examples + -------- + Basic usage of `.param` in a `Parameterized` class: + + >>> import param + >>> + >>> class MyClass(param.Parameterized): + ... value = param.Parameter() + >>> + >>> my_instance = MyClass(value=0) + + Access the `value` parameter of `my_instance`: + + >>> my_instance.param.value # the Parameter instance + + Note that this is different from the current `value` of `my_instance`: + + >>> my_instance.value # 0, the current parameter value + """ return Parameters(self.__class__, self=self) #PARAM3_DEPRECATION @@ -4444,6 +4625,7 @@ class ParameterizedFunction(Parameterized): To obtain an instance of this class, call instance(). """ + __abstract = True def __str__(self): @@ -4455,7 +4637,6 @@ def instance(self_or_cls,**params): Return an instance of this class, copying parameters from any existing instance provided. """ - if isinstance (self_or_cls,ParameterizedMetaclass): cls = self_or_cls else: @@ -4504,7 +4685,7 @@ def _pprint(self, imports=None, prefix="\n ",unknown_value='', class default_label_formatter(ParameterizedFunction): - "Default formatter to turn parameter names into appropriate widget labels." + """Default formatter to turn parameter names into appropriate widget labels.""" capitalize = Parameter(default=True, doc=""" Whether or not the label should be capitalized.""") @@ -4542,6 +4723,7 @@ class overridable_property: .. deprecated:: 2.0.0 """ + # Delays looking up the accessors until they're needed, rather # than finding them when the class is first created. diff --git a/param/parameters.py b/param/parameters.py index 61f82d8a0..ba84559d4 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -396,7 +396,6 @@ def __call__(self, val=None, time_type=None): the current state remains consistent, this is normally the only way to change the time_type of an existing Time instance. """ - if time_type and val is None: raise Exception("Please specify a value for the new time_type.") if time_type: @@ -560,7 +559,6 @@ def _produce_value(self,gen,force=False): value will be produced and returned. Otherwise, the last value gen produced will be returned. """ - if hasattr(gen,"_Dynamic_time_fn"): time_fn = gen._Dynamic_time_fn else: @@ -613,6 +611,7 @@ def _force(self,obj,objtype=None): class __compute_set_hook: """Remove when set_hook is removed""" + def __call__(self, p): return _identity_hook @@ -1431,6 +1430,7 @@ class CalendarDateRange(Range): """ A date range specified as (start_date, end_date). """ + def _validate_value(self, val, allow_None): if allow_None and val is None: return @@ -1780,6 +1780,7 @@ class __compute_selector_default: then the object in _slot_defaults would itself be updated and the next Selector instance created wouldn't have [] as the default but a populated list. """ + def __call__(self, p): return [] @@ -1996,6 +1997,7 @@ class FileSelector(Selector): """ Given a path glob, allows one file to be selected from those matching. """ + __slots__ = ['path'] _slot_defaults = _dict_update( @@ -2106,6 +2108,7 @@ class MultiFileSelector(ListSelector): """ Given a path glob, allows multiple files to be selected from the list of matches. """ + __slots__ = ['path'] _slot_defaults = _dict_update( @@ -2523,7 +2526,7 @@ def _validate(self, val): self._validate_item_type(val, self.item_type) def _validate_bounds(self, val, bounds): - "Checks that the list is of the right length and has the right contents." + """Checks that the list is of the right length and has the right contents.""" if bounds is None or (val is None and self.allow_None): return min_length, max_length = bounds @@ -2576,6 +2579,7 @@ class HookList(List): for users to register a set of commands to be called at a specified place in some sequence of processing steps. """ + __slots__ = ['class_', 'bounds'] def _validate_value(self, val, allow_None): diff --git a/param/reactive.py b/param/reactive.py index 00e9c06e4..fbea21de2 100644 --- a/param/reactive.py +++ b/param/reactive.py @@ -181,10 +181,41 @@ class NestedResolver(Resolver): class reactive_ops: """ - Namespace for reactive operators. + The reactive namespace. - Implements operators that cannot be implemented using regular - Python syntax. + Provides reactive versions of operations that cannot be made reactive through operator overloading, such as + `.rx.and_` and `.rx.bool`. Calling this namespace (`()`) returns a reactive expression. + + Parameters + ---------- + None + + Returns + ------- + Reactive expression + The result of calling the reactive namespace is a reactive expression. + + User Guide + ---------- + https://param.holoviz.org/user_guide/Reactive_Expressions.html#special-methods-on-rx + + Examples + -------- + Create a Parameterized instance: + + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P(a=1, b="hello") + + Get the current value: + + >>> a = p.param.a.rx.value + + Call it to get a reactive expression: + + >>> rx_value = p.param.a.rx() """ def __init__(self, reactive): @@ -194,6 +225,7 @@ def _as_rx(self): return self._reactive if isinstance(self._reactive, rx) else self() def __call__(self): + """Creates a reactive expression.""" rxi = self._reactive return rxi if isinstance(rx, rx) else rx(rxi) @@ -755,7 +787,7 @@ def __init__( ] self._setup_invalidations(depth) self._kwargs = kwargs - self.rx = reactive_ops(self) + self._rx = reactive_ops(self) self._init = True for name, accessor in _display_accessors.items(): setattr(self, name, accessor(self)) @@ -763,6 +795,47 @@ def __init__( if predicate is None or predicate(self._current): setattr(self, name, accessor(self)) + @property + def rx(self) -> reactive_ops: + """ + The reactive namespace. + + Provides reactive versions of operations that cannot be made reactive through operator overloading, such as + `.rx.and_` and `.rx.bool`. Calling this namespace (`()`) returns a reactive expression. + + Parameters + ---------- + None + + Returns + ------- + Reactive expression + The result of calling the reactive namespace is a reactive expression. + + User Guide + ---------- + https://param.holoviz.org/user_guide/Reactive_Expressions.html#special-methods-on-rx + + Examples + -------- + Create a Parameterized instance: + + >>> import param + >>> class P(param.Parameterized): + >>> a = param.Number() + >>> b = param.String() + >>> p = P(a=1, b="hello") + + Get the current value: + + >>> a = p.param.a.rx.value + + Call it to get a reactive expression: + + >>> rx_value = p.param.a.rx() + """ + return self._rx + @property def _obj(self): if self._shared_obj is None: diff --git a/param/serializer.py b/param/serializer.py index ab094ba51..a04c98ba0 100644 --- a/param/serializer.py +++ b/param/serializer.py @@ -13,7 +13,7 @@ class UnsafeserializableException(Exception): pass def JSONNullable(json_type): - "Express a JSON schema type as nullable to easily support Parameters that allow_None" + """Express a JSON schema type as nullable to easily support Parameters that allow_None""" return {'anyOf': [ json_type, {'type': 'null'}] } @@ -118,7 +118,7 @@ def deserialize_parameters(cls, pobj, serialization, subset=None): @classmethod def _get_method(cls, ptype, suffix): - "Returns specialized method if available, otherwise None" + """Returns specialized method if available, otherwise None""" method_name = ptype.lower()+'_' + suffix return getattr(cls, method_name, None) @@ -200,7 +200,7 @@ def number_schema(cls, p, safe=False): @classmethod def declare_numeric_bounds(cls, schema, bounds, inclusive_bounds): - "Given an applicable numeric schema, augment with bounds information" + """Given an applicable numeric schema, augment with bounds information""" if bounds is not None: (low, high) = bounds if low is not None: diff --git a/param/version.py b/param/version.py index ed8637406..63aaa69af 100644 --- a/param/version.py +++ b/param/version.py @@ -81,7 +81,8 @@ class Version: obtained via git describe. This later portion is only shown if the commit count since the last tag is non zero. Instead of '.post', an alternate valid prefix such as '.rev', '_rev', '_r' or '.r' may be - supplied.""" + supplied. + """ def __new__(cls,**kw): # If called in the old way, provide the previous class. Means @@ -131,22 +132,22 @@ def prerelease(self): @property def release(self): - "Return the release tuple" + """Return the release tuple""" return self.fetch()._release @property def commit(self): - "A specification for this particular VCS version, e.g. a short git SHA" + """A specification for this particular VCS version, e.g. a short git SHA""" return self.fetch()._commit @property def commit_count(self): - "Return the number of commits since the last release" + """Return the number of commits since the last release""" return self.fetch()._commit_count @property def dirty(self): - "True if there are uncommited changes, False otherwise" + """True if there are uncommited changes, False otherwise""" return self.fetch()._dirty @@ -256,7 +257,7 @@ def _output_from_file(self, entry='git_describe'): def _update_from_vcs(self, output): - "Update state based on the VCS state e.g the output of git describe" + """Update state based on the VCS state e.g the output of git describe""" split = output[1:].split('-') dot_split = split[0].split('.') for prefix in ['a','b','rc']: @@ -599,22 +600,22 @@ def __init__(self, release=None, fpath=None, commit=None, @property def release(self): - "Return the release tuple" + """Return the release tuple""" return self.fetch()._release @property def commit(self): - "A specification for this particular VCS version, e.g. a short git SHA" + """A specification for this particular VCS version, e.g. a short git SHA""" return self.fetch()._commit @property def commit_count(self): - "Return the number of commits since the last release" + """Return the number of commits since the last release""" return self.fetch()._commit_count @property def dirty(self): - "True if there are uncommited changes, False otherwise" + """True if there are uncommited changes, False otherwise""" return self.fetch()._dirty @@ -667,7 +668,7 @@ def git_fetch(self, cmd='git'): self._update_from_vcs(output) def _update_from_vcs(self, output): - "Update state based on the VCS state e.g the output of git describe" + """Update state based on the VCS state e.g the output of git describe""" split = output[1:].split('-') if 'dev' in split[0]: dev_split = split[0].split('dev') diff --git a/pyproject.toml b/pyproject.toml index cc4dababe..56c9e6451 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,3 +113,9 @@ asyncio_default_fixture_loop_scope="function" [tool.coverage.report] omit = ["param/version.py"] + +[tool.ruff.lint] +select = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/tests/testcompositeparams.py b/tests/testcompositeparams.py index 95c5bbafd..7df55dd6c 100644 --- a/tests/testcompositeparams.py +++ b/tests/testcompositeparams.py @@ -25,7 +25,8 @@ class A(param.Parameterized): self.a = self.A() class SomeSequence: - "Can't use iter with Dynamic (doesn't pickle, doesn't copy)" + """Can't use iter with Dynamic (doesn't pickle, doesn't copy)""" + def __init__(self,sequence): self.sequence=sequence self.index=0 @@ -67,7 +68,7 @@ def test_defaults_unbound(self): assert not hasattr(c, 'objtype') def test_initialization(self): - "Make an instance and do default checks" + """Make an instance and do default checks""" self.assertEqual(self.a.x, 0) self.assertEqual(self.a.y, 0) self.assertEqual(self.a.xy, [0,0]) @@ -82,7 +83,7 @@ def test_set_compound(self): self.assertEqual(self.a.y, 3) def test_compound_class(self): - " Get the compound on the class " + """Get the compound on the class""" self.assertEqual(self.A.xy, [0,0]) def test_set_compound_class_set(self): diff --git a/tests/testdynamicparams.py b/tests/testdynamicparams.py index a25e9ce4a..5b419efde 100644 --- a/tests/testdynamicparams.py +++ b/tests/testdynamicparams.py @@ -53,7 +53,7 @@ def test_set_dynamic_time_fn_y(self): self.t1.param['y']._value_is_dynamic(self.t1), False) def test_inspect_x(self): - "no value generated yet" + """No value generated yet""" self.assertEqual(self.t1.param.inspect_value('x'), None) def test_inspect_y(self): @@ -69,17 +69,17 @@ def test_set_dynamic_numbergen(self): self.assertEqual(is_numbergen, True) def test_matching_numbergen_streams(self): - "check that t2 and t3 have identical streams" + """Check that t2 and t3 have identical streams""" self.assertEqual(self.t2.x, self.t3.x) def test_numbergen_objects_distinct(self): - "check t2 and t3 do not share UniformRandom objects" + """Check t2 and t3 do not share UniformRandom objects""" self.t2.x self.assertNotEqual(self.t2.param.inspect_value('x'), self.t3.param.inspect_value('x')) def test_numbergen_inspect(self): - " inspect_value() should return last generated value " + """inspect_value() should return last generated value""" self.t2.x # Call 1 self.t2.x # Call 2 t2_last_value = self.t2.x # advance t2 beyond t3 @@ -95,7 +95,7 @@ def test_dynamic_value_instantiated(self): t6_first_value) def test_non_dynamic_value_not_instantiated(self): - " non-dynamic value not instantiated" + """non-dynamic value not instantiated""" self.TestPO2.y = 4 self.assertEqual(self.t6.y, 4) self.assertEqual(self.t7.y, 4) @@ -122,7 +122,7 @@ def test_shared_numbergen(self): self.assertEqual(self.TestPO2().param['y'].default.__class__.__name__, 'UniformRandom') def test_copy_match(self): - "check a copy is the same" + """Check a copy is the same""" t9 = copy.deepcopy(self.t7) self.assertEqual(t9.param.get_value_generator('y') is self.TestPO2().param['y'].default, True) @@ -139,7 +139,8 @@ class TestPO3(param.Parameterized): time_dependent=True)) class TestPO4(self.TestPO1): - "Nested parameterized objects" + """Nested parameterized objects""" + z = param.Parameter(default=self.TestPO1()) self.TestPO3 = TestPO3 @@ -204,21 +205,21 @@ def test_dynamic_value_change_independent(self): self.assertEqual(t12.y, t12.y) def test_dynamic_value_change_disabled(self): - " time_fn set on the UniformRandom() when t13.y was set" + """time_fn set on the UniformRandom() when t13.y was set""" t13 = self.TestPO1() t13.param.set_dynamic_time_fn(None) t13.y = numbergen.UniformRandom() self.assertNotEqual(t13.y, t13.y) def test_dynamic_value_change_enabled(self): - " time_fn set on the UniformRandom() when t13.y was set" + """time_fn set on the UniformRandom() when t13.y was set""" t14 = self.TestPO1() t14.y = numbergen.UniformRandom() self.assertEqual(t14.y, t14.y) def test_dynamic_time_fn_not_inherited(self): - " time_fn not inherited" + """time_fn not inherited""" t15 = self.TestPO4() t15.param.set_dynamic_time_fn(None) with param.Dynamic.time_fn as t: @@ -230,7 +231,8 @@ def test_dynamic_time_fn_not_inherited(self): class TestDynamicSharedNumbergen(TestDynamicParameters): - "Check shared generator" + """Check shared generator""" + def setUp(self): super().setUp() self.shared = numbergen.UniformRandom(lbound=-1,ubound=1,seed=20) diff --git a/tests/testparameterizedobject.py b/tests/testparameterizedobject.py index f9b5ea762..939a8b9f4 100644 --- a/tests/testparameterizedobject.py +++ b/tests/testparameterizedobject.py @@ -272,14 +272,16 @@ class C(A, B): pass def test_constant_parameter_modify_class_before(self): """Test you can set on class and the new default is picked up - by new instances""" + by new instances + """ TestPO.const=9 testpo = TestPO() self.assertEqual(testpo.const,9) def test_constant_parameter_modify_class_after_init(self): """Test that setting the value on the class doesn't update the instance value - even when the instance value hasn't yet been set""" + even when the instance value hasn't yet been set + """ oobj = [] class P(param.Parameterized): x = param.Parameter(default=oobj, constant=True) @@ -333,7 +335,6 @@ def test_readonly_parameter(self): def test_basic_instantiation(self): """Check that instantiated parameters are copied into objects.""" - testpo = TestPO() self.assertEqual(testpo.inst,TestPO.inst) @@ -523,7 +524,6 @@ class P(param.Parameterized): def test_values(self): """Basic tests of params() method.""" - # CB: test not so good because it requires changes if params # of PO are changed assert 'name' in param.Parameterized.param.values() diff --git a/tests/testreactive.py b/tests/testreactive.py index 68e6b9ee3..afe08a1b5 100644 --- a/tests/testreactive.py +++ b/tests/testreactive.py @@ -4,6 +4,7 @@ import os import unittest import time +from textwrap import dedent try: import numpy as np @@ -24,8 +25,8 @@ import param import pytest -from param.parameterized import Skip -from param.reactive import bind, rx +from param.parameterized import Skip, Parameter +from param.reactive import bind, rx, reactive_ops from .utils import async_wait_until @@ -767,3 +768,8 @@ def test_reactive_callback_resolve_accessor(): dfx = rx(df) out = dfx["name"].str._callback() assert out is df["name"].str + +def test_docstrings_in_sync(): + # The docstring needs to be explicitly written to work with LSP. + assert dedent(reactive_ops.__doc__) == dedent(Parameter.rx.__doc__) + assert dedent(reactive_ops.__doc__) == dedent(rx.rx.__doc__) diff --git a/tests/utils.py b/tests/utils.py index bec38f3b5..3c31666a7 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -34,7 +34,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def emit(self, record): - "Store a message to the instance's messages dictionary" + """Store a message to the instance's messages dictionary""" self.acquire() try: self.messages[record.levelname].append(record.getMessage()) @@ -48,7 +48,7 @@ def reset(self): self.release() def tail(self, level, n=1): - "Returns the last n lines captured at the given level" + """Returns the last n lines captured at the given level""" return [str(el) for el in self.messages[level][-n:]] def assertEndsWith(self, level, substring):