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

Other Docstring improvements #998

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
1 change: 1 addition & 0 deletions benchmarks/benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Benchmark package."""
46 changes: 29 additions & 17 deletions numbergen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,28 @@ 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))


class BinaryOperator(NumberGenerator):
"""Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator."""

def __init__(self,lhs,rhs,operator,reverse=False,**args):
"""Initialize a BinaryOperator with operands, an operator, and optional arguments.

Args:
lhs: The left-hand side operand, which can be a NumberGenerator or a number.
rhs: The right-hand side operand, which can be a NumberGenerator or a number.
operator (Callable): The binary operator to apply to the operands.
reverse (bool, optional): If `True`, swaps the left and right operands. Defaults to `False`.
**args: Optional keyword arguments to pass to the operator when it is called.

Notes
-----
It is currently not possible to set parameters in the superclass during
initialization because `**args` is used by this class itself.
"""
Accepts two NumberGenerator operands, an operator, and
optional arguments to be provided to the operator when calling
it on the two operands.
"""
# Note that it's currently not possible to set
# parameters in the superclass when creating an instance,
# because **args is used by this class itself.
super().__init__()

if reverse:
Expand All @@ -174,14 +180,18 @@ class UnaryOperator(NumberGenerator):
"""Applies any unary operator to a NumberGenerator to yield another NumberGenerator."""

def __init__(self,operand,operator,**args):
"""Initialize a UnaryOperator with an operand, operator, and optional arguments.

Args:
operand (NumberGenerator): The NumberGenerator to which the operator is applied.
operator (Callable): The unary operator to apply to the operand.
**args: Optional keyword arguments to pass to the operator when it is called.

Notes
-----
It is currently not possible to set parameters in the superclass during
initialization because `**args` is used by this class itself.
"""
Accepts a NumberGenerator operand, an operator, and
optional arguments to be provided to the operator when calling
it on the operand.
"""
# Note that it's currently not possible to set
# parameters in the superclass when creating an instance,
# because **args is used by this class itself.
super().__init__()

self.operand=operand
Expand Down Expand Up @@ -239,7 +249,7 @@ def _rational(self, val):


def __getstate__(self):
"""Avoid Hashlib.md5 TypeError in deepcopy (hashlib issue)"""
"""Avoid Hashlib.md5 TypeError in deepcopy (hashlib issue)."""
d = self.__dict__.copy()
d.pop('_digest')
d.pop('_hash_struct')
Expand Down Expand Up @@ -330,7 +340,9 @@ class TimeAwareRandomState(TimeAware):

def _initialize_random_state(self, seed=None, shared=True, name=None):
"""
Initialization method to be called in the constructor of
Initialize the random state correctly.

Method to be called in the constructor of
subclasses to initialize the random state correctly.

If seed is None, there is no control over the random stream
Expand Down
62 changes: 52 additions & 10 deletions param/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,59 @@

"""
Param: A declarative framework for managing parameters and reactive programming in Python.

Param is a lightweight library for defining and managing user-modifiable parameters,
designed to simplify Python programs and enhance their readability, maintainability,
and robustness. In addition Param provides the `rx` framework for reactive programming.

Param is well-suited for use in scientific computing, data analysis tools,
graphical user interfaces (GUIs), and any Python application where well-defined,
validated parameters are needed.

Documentation
-------------
For detailed documentation, see https://param.holoviz.org/.

Examples
--------
Here is an example of using `Parameterized` to define a named class with observable parameters:

>>> import param
>>> class MyClass(param.Parameterized):
... my_number = param.Number(default=1, bounds=(0, 10))
... my_list = param.List(default=[1, 2, 3], item_type=int)

>>> obj = MyClass()
>>> obj.my_number = 5 # Valid
>>> obj.my_number = 15 # Raises ValueError: must be in range (0, 10)

Here is an example of using `rx` to define a reactive expression:

>>> import param
>>> rx_value = param.rx([1,2,3])
>>> rx_value.rx.len()
3

Lets update the reactive value and check its length:

>>> rx_value.rx.value = [1,2,3,4]
>>> rx_value.rx.len()
4
"""
import os

from . import version # noqa: api import
from .depends import depends # noqa: api import
from .parameterized import ( # noqa: api import
from . import version
from .depends import depends
from .parameterized import (
Parameterized, Parameter, Skip, String, ParameterizedFunction,
ParamOverrides, Undefined, get_logger
)
from .parameterized import (batch_watch, output, script_repr, # noqa: api import
from .parameterized import (batch_watch, output, script_repr,
discard_events, edit_constant)
from .parameterized import shared_parameters # noqa: api import
from .parameterized import logging_level # noqa: api import
from .parameterized import DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL # noqa: api import
from .parameters import ( # noqa: api import
from .parameterized import shared_parameters
from .parameterized import logging_level
from .parameterized import DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL
from .parameters import (
guess_param_types,
param_union,
parameterized_class,
Expand Down Expand Up @@ -58,8 +100,8 @@
CalendarDateRange,
Event,
)
from .reactive import bind, rx # noqa: api import
from ._utils import ( # noqa: api import
from .reactive import bind, rx
from ._utils import (
produce_value,
as_unicode,
is_ordered_dict,
Expand Down
119 changes: 86 additions & 33 deletions param/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
MUTABLE_TYPES = (abc.MutableSequence, abc.MutableSet, abc.MutableMapping)

class ParamWarning(Warning):
"""Base Param Warning"""
"""Base Param Warning."""

class ParamPendingDeprecationWarning(ParamWarning, PendingDeprecationWarning):
"""
Param PendingDeprecationWarning
Param PendingDeprecationWarning.

This warning type is useful when the warning is not meant to be displayed
to REPL/notebooks users, as DeprecationWarning are displayed when triggered
Expand All @@ -48,15 +48,15 @@ class ParamPendingDeprecationWarning(ParamWarning, PendingDeprecationWarning):

class ParamDeprecationWarning(ParamWarning, DeprecationWarning):
"""
Param DeprecationWarning
Param DeprecationWarning.

Ignored by default except when triggered by code in __main__
"""


class ParamFutureWarning(ParamWarning, FutureWarning):
"""
Param FutureWarning
Param FutureWarning.

Always displayed.
"""
Expand All @@ -66,7 +66,19 @@ class Skip(Exception):

def _deprecated(extra_msg="", warning_cat=ParamDeprecationWarning):
def decorator(func):
"""Internal decorator used to mark functions/methods as deprecated."""
"""Mark a function or method as deprecated.

This internal decorator issues a warning when the decorated function
or method is called, indicating that it has been deprecated and will
be removed in a future version.

Args:
func: The function or method to mark as deprecated.

Returns
-------
Callable: The wrapped function that issues a deprecation warning.
"""
@functools.wraps(func)
def inner(*args, **kwargs):
msg = f"{func.__name__!r} has been deprecated and will be removed in a future version."
Expand All @@ -81,11 +93,19 @@ def inner(*args, **kwargs):


def _deprecate_positional_args(func):
"""
Internal decorator for methods that issues warnings for positional arguments
Using the keyword-only argument syntax in pep 3102, arguments after the
``*`` will issue a warning when passed as a positional argument.
Adapted from scikit-learn
"""Issue warnings for methods using deprecated positional arguments.

This internal decorator warns when arguments after the `*` separator
are passed as positional arguments, in accordance with PEP 3102.
It adapts the behavior from scikit-learn.

Args:
func: The function to wrap with positional argument deprecation warnings.

Returns
-------
Callable: The wrapped function that issues warnings for deprecated
positional arguments.
"""
signature = inspect.signature(func)

Expand Down Expand Up @@ -124,7 +144,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"""
"""Decorate a repr function to return a fill value for recursive calls."""

def decorating_function(user_function):
repr_running = set()
Expand Down Expand Up @@ -201,12 +221,15 @@ def _validate_error_prefix(parameter, attribute=None):


def _is_mutable_container(value):
"""True for mutable containers, which typically need special handling when being copied"""
"""Determine if the value is a mutable container.

Mutable containers typically require special handling when being copied.
"""
return isinstance(value, MUTABLE_TYPES)


def full_groupby(l, key=lambda x: x):
"""Groupby implementation which does not require a prior sort"""
"""Groupby implementation which does not require a prior sort."""
d = defaultdict(list)
for item in l:
d[key(item)].append(item)
Expand All @@ -225,7 +248,7 @@ def iscoroutinefunction(function):
)

async def _to_thread(func, /, *args, **kwargs):
"""Polyfill for asyncio.to_thread in Python < 3.9"""
"""Polyfill for asyncio.to_thread in Python < 3.9."""
loop = asyncio.get_running_loop()
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
Expand Down Expand Up @@ -281,18 +304,17 @@ def flatten(line):
def accept_arguments(
f: Callable[Concatenate[CallableT, P], R]
) -> Callable[P, Callable[[CallableT], R]]:
"""Decorator for decorators that accept arguments"""
"""Decorate a decorator to accept arguments."""
@functools.wraps(f)
def _f(*args: P.args, **kwargs: P.kwargs) -> Callable[[CallableT], R]:
return lambda actual_f: f(actual_f, *args, **kwargs)
return _f


def _produce_value(value_obj):
"""
A helper function that produces an actual parameter from a stored
object: if the object is callable, call it, otherwise return the
object.
"""Produce an actual value from a stored object.

If the object is callable, call it; otherwise, return the object.
"""
if callable(value_obj):
return value_obj()
Expand All @@ -303,10 +325,9 @@ def _produce_value(value_obj):
# PARAM3_DEPRECATION
@_deprecated(warning_cat=ParamFutureWarning)
def produce_value(value_obj):
"""
A helper function that produces an actual parameter from a stored
object: if the object is callable, call it, otherwise return the
object.
"""Produce an actual value from a stored object.

If the object is callable, call it; otherwise, return the object.

.. deprecated:: 2.0.0
"""
Expand All @@ -330,7 +351,7 @@ def as_unicode(obj):
def is_ordered_dict(d):
"""
Predicate checking for ordered dictionaries. OrderedDict is always
ordered, and vanilla Python dictionaries are ordered for Python 3.6+
ordered, and vanilla Python dictionaries are ordered for Python 3.6+.

.. deprecated:: 2.0.0
"""
Expand Down Expand Up @@ -556,9 +577,18 @@ def abbreviate_paths(pathspec,named_paths):


def _to_datetime(x):
"""
Internal function that will convert date objs to datetime objs, used
for comparing date and datetime objects without error.
"""Convert a date object to a datetime object for comparison.

This internal function ensures that date and datetime objects can be
compared without errors by converting date objects to datetime objects.

Args:
x: The object to convert, which may be a `date` or `datetime` object.

Returns
-------
datetime.datetime: A datetime object if the input was a date object;
otherwise, the input is returned unchanged.
"""
if isinstance(x, dt.date) and not isinstance(x, dt.datetime):
return dt.datetime(*x.timetuple()[:6])
Expand All @@ -567,9 +597,19 @@ def _to_datetime(x):

@contextmanager
def exceptions_summarized():
"""
Useful utility for writing docs that need to show expected errors.
Shows exception only, concisely, without a traceback.
"""Context manager to display exceptions concisely without tracebacks.

This utility is useful for writing documentation or examples where
only the exception type and message are needed, without the full
traceback.

Yields
------
None: Allows the code inside the context to execute.

Prints:
A concise summary of any exception raised, including the exception
type and message, to `sys.stderr`.
"""
try:
yield
Expand Down Expand Up @@ -616,9 +656,22 @@ def __iter__(cls):
yield from cls.types()

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.
"""Decorate a generator function to support type checking.

This decorator modifies a generator function that yields different types
so that it can be used with `isinstance` and `issubclass`.

Args:
gen_func (function): The generator function to decorate.

Returns
-------
type: A new type that supports `isinstance` and `issubclass` checks
based on the generator function's yielded types.

Raises
------
TypeError: If the provided function is not a generator function.
"""
if not inspect.isgeneratorfunction(gen_func):
msg = "gen_types decorator can only be applied to generator"
Expand Down
Loading
Loading