Param: Make your Python code clearer and more reliable by declaring Parameters

Overview

LinuxTests WinTests Coverage PyPIVersion PyVersion License

Param

Param is a library providing Parameters: Python attributes extended to have features such as type and range checking, dynamically generated values, documentation strings, default values, etc., each of which is inherited from parent classes if not specified in a subclass.

Param contains only two required Python files, with no external dependencies, and is provided freely for both non-commercial and commercial use under a BSD license, so that it can easily be included as part of other projects.

Please see param's website for official releases, installation instructions, documentation, and examples.

Comments
  • Add new Event parameter type

    Add new Event parameter type

    Very WIP PR to illustrate an idea we have been discussing. The API will probably change but here is the current behavior:

    image

    The idea is to add a new Trigger parameter type which would replace many uses of Action. What you want to know about a Trigger parameter is whether it has been triggered (e.g a button clicked). The value is True if the parameter has just been triggered, otherwise False (which means a trigger call has happened since not involving that parameter).

    One possibility which is more complicated is to avoid b.param.trigger('a') by setting b.a = True (setting to True triggers, whether or not the value is True already). This involves working inside __set__ which can get quite tricky.

    At this point, this PR aims to be a place to discuss this suggestion more than something to merge. The idea is that this parameter would mean https://github.com/holoviz/holoviews/pull/4611 is not necessary (though panel would need to be updated to display this parameter type as appropriate buttons).

    TODO

    • [x] Make this work with class parameters (doesn't work with Bar at the moment in the example above)
    • [ ] Improve API, possibly via __set__
    opened by jlstevens 37
  • Alternative implementation of ParamOverrides

    Alternative implementation of ParamOverrides

    I've re-implemented ParamOverrides, following the suggestion made in #85 about sharing parameter objects. This PR isn't meant for merging at this stage, I'd just like some comments on the basic idea.

    This new implementation passes all the tests (including one I added to check that dynamic overrides work), although the implementation's not yet complete (just missing some minor methods that the current ParamOverrides has). However, what I'd like to know is: is the whole idea crazy? If it's not crazy, is there a way to make the implementation simpler? I've left extra comments in the code for now to explain what I was thinking.

    In case it's easier to look at the class in one piece: https://github.com/ceball/param/blob/bug85_newParamOverrides/param/parameterized.py#L1580

    (Also, note that I haven't considered speed.)

    type-bug 
    opened by ceball 37
  • Expressing dependencies between parameters and code

    Expressing dependencies between parameters and code

    I'm pulling some suggestions out of #160 to file as a separate issue. As it turns out there are different things we might want to have param express:

    1. Relationships between parameters i.e update parameter settings based on changes to another parameter. There may be some easy way to express such relationship or there might be some code supplied by the user to link parameters (e.g using the set_hook idea)
    2. Relationships between parameters and regular code in parameterized classes that might consume parameter values and/or set them. This is what this issue is about and the idea is to express that a method might make use of a parameter and might set other parameters. If the method has output, you may make the assumption that the output will have changed when the parameters that are read by that method have changed.
    3. General specifications of input/output types of methods, functions etc. This would be very useful for some things but currently out of the scope of what we want to achieve in the short term.

    Here are my proposals to address 2. made in issue #160:

    class Example(param.Parameterized):
    
        A = param.Number(default=10)
    
        B = param.Number(default=5)
    
        @param.dependencies([A],[B])
        def link_a_to_b(self, **parameters):
            self.B = parameters['A']/2.
    

    This expresses the parameters read ([A]) and the parameters written to ([B]) using a decorator. Alternatively, you could have more granular decorators:

    class Example(param.Parameterized):
    
        A = param.Number(default=10)
    
        B = param.Number(default=5)
    
        @param.reads([A])
        @param.writes([B])
        def link_a_to_b(self, **parameters):
            self.B = parameters['A']/2.
    

    The information captured by the decorator needs to be stored somewhere. Presumably we don't want either 1) a global mapping of parameterized classes and their parameter dependencies as this would be fragile 2) distributing this information among the parameters themselves (would be very space inefficient if each parameter holds such information). This means the natural place to keep track of this is in the parameterized classes, presumably somewhere in param.Parameterized.

    The limitation of this is 1) it wouldn't be customizable per instance 2) it would be information defined once when the class is defined. Neither of these seems to be a real problem as we don't often shove/mutate methods on classes after they are defined.

    As for API, I really would like #154 tackled (putting the API onto a sub-object) first but that seems unlikely to happen. Here is one simple idea of how the information could be accessed:

    example = Example()
    reads, writes = example.param_dependencies('link_a_to_b') # OR
    reads, writes = Example.param_dependencies('link_a_to_b') 
    

    This just regurgitates what was supplied to the @param.dependencies decorator where the reads and writes might be empty lists. I'm not sure whether these lists should just contain parameter names or the parameter objects themselves. Maybe this could be an optional argument to the param_dependencies method where I would favor names by default.

    One thing worth mentioning is by making certain assumptions you could follow a chain of changes without executing user code. For instance, if you assume that changing parameter a read by method1 changes parameter b that it writes to, you might then assume that the output of method2 is changed as that method reads parameter b.

    Note that this proposal simply aims to capture the which parameters are read/written to by methods and doesn't aim to do anything with this information. I have no particular proposals for this just yet but I can imagine param offering utilities to trace the potential dependency graph of parameters and perhaps execute various sorts of callback when parameters change.

    opened by jlstevens 30
  • Defined namespace and implementation on Parameters object

    Defined namespace and implementation on Parameters object

    This PR is still WIP. There are a lot of lines changed from the very first commit as this was the sort of task I couldn't tell would be viable until it was all done.

    In short it moves most of the code of Parameterized onto a param objects of type Parameters which also acts as a namespace. The goal is to make this the face of the public API cleaning up the namespace of classes subclassed by users. Here is an example for a bothmethod params:

    image

    HOW IT WORKS

    • Parameters is instantiated by the metaclass or the regular constructor.
    • All methods use self_ as the first argument.
    • Classmethods have the first line cls = self_.cls after which the code is unchanged.
    • Bothmethods have the first line self_or_cls = self_.cls if self_.self is None else self_.self after which the code is unchanged.
    • Instance methods have the first line self = self_.self after which the code is unchanged.
    • Double underscore methods need special handling (see below) but thankfully they are rare.

    Notes:

    • The param object is per instance when necessary allowing it to hold state either at the class or instance level: this will be important for recording information regarding parameter dependencies etc.
    • Methods decorated with @as_uninitialized are used in the Parameterized constructor and I have left them on that class.
    • I have left the 'special' methods on Parameterized (e.g __repr__)
    • The only exception to the above is _instantiate_param which I can move as it used __dict__ and is used from the Parameterized constructor via _setup_params
    • I have tried to organized methods by their type: classmethods, then bothmethods and finally instance methods. Within these groups I aimed for rough semantic grouping.
    • Double underscore methods need to be moved to Parametersand accessed through self_ to avoid Python name mangling issues. This came up for __db_print in particular.
    • self_or_cls, cls_or_self, cls_or_slf or slf_or_cls? I have seen at least three of these...
    • The actual API could do with some clean up. It is definitely redundant in places...

    TODO

    • [x] Fix issue with existing nosetests
    • [x] Transfer methods for ParameterizedFunction? [Yes except for .instance]
    • [x] Add more tests [copy existing tests and update]
    • [x] Transfer docstrings to aliased methods?
    • [x] Update internal API to use .param namespace so we can just delete the aliases when the time comes.
    • [x] We need a decorator or some other approach to issue deprecation warnings when not using the param object to migrate users to the namespace object.
    • [x] Try to move the @as_uninitialized and special methods too? [Yes]
    opened by jlstevens 29
  • Add support for instance parameters

    Add support for instance parameters

    This PR implements the notion of an instance parameter which can be accessed using a new API. All parameters can in theory become instance parameters (as long as the new per_instance slot is set to True). The main balance to strike here is that a) we do not want to pay the cost of copying parameters for all instances and b) we do not want users to have to add any special boilerplate to get instance parameters.

    The approach I've therefore taken is to only make a copy of a parameter when it is actually needed. This means that all existing param code will never pay the cost of making instance parameters, there's only two situations where the per instance copy is made:

    1. When a instance parameter attribute is watched. This is because you do not want events triggered by changes on the class parameter to trigger events on the instance parameter and vice versa.
    2. The second situation when a copy is made is when one of the new APIs is used to access the parameter on an instance, e.g. when you access instance.param.param_name or instance.param['param_name']. This API will make it easy to change the attributes of instance parameters, but the old obj.params()/obj.param.params() method is unaffected by this.

    Leaving the old API unaffected will ensure that old param code never pays the costs associated with instance parameters.

    • [x] Add new parameterized.param.__getitem__ and parameterized.param.__getattr__ accessors for parameters
    • [x] Modify setters to ensure they use instance parameters for validation
    • [x] Ensure that watching instance parameter attributes makes copies
    • [x] Add tests
    opened by philippjfr 26
  • Subclassing Parameter

    Subclassing Parameter

    Hello,

    i try to add my own information to param.Parameter instances by subclassing it

    here is my attempt (the additional information that i want to store in parameters is whether they should remain enabled in the graphic interface when a run has started)

    import param as pm
    
    # Add run_enabled slot to pm.Parameter classes
    class RunEnabledInfo:
    
        __slots__ = ['run_enabled']
    
        def __init__(self, *args, run_enabled=None, **kwargs):
            super(MyBoolean, self).__init__(*args, **kwargs)
            self.run_enabled = run_enabled
    
    class MyBoolean(pm.Boolean, RunEnabledInfo):
        pass
    
    class MyNumber(pm.Number, RunEnabledInfo):
        pass
    

    But i get

    Traceback (most recent call last):
      File "E:/Users/Thomas/Python/Naivia/AlphaI/test.py", line 18, in <module>
        class MyBoolean(pm.Boolean, RunEnabledInfo):
      File "C:\ProgramData\Anaconda3\lib\site-packages\param\parameterized.py", line 503, in __new__
        return type.__new__(mcs,classname,bases,classdict)
    TypeError: multiple bases have instance lay-out conflict
    

    The problem seems to be with MyBoolean inheriting from two classes. Indeed the following code works, but i wouldn't like to rewrite it for every possible parameter type! Any suggestion how to do?

    class MyBoolean(pm.Boolean):
    
        __slots__ = ['run_enabled']
    
        def __init__(self, *args, run_enabled=None, **kwargs):
            super(MyBoolean, self).__init__(*args, **kwargs)
            self.run_enabled = run_enabled
    
    status: discussion 
    opened by thomasdeneux 23
  • [WIP] Defined pre_set_hooks and post_set_hooks for Parameters

    [WIP] Defined pre_set_hooks and post_set_hooks for Parameters

    This PR is a prototype that replaces set_hook on Number with a more general system of pre- and post- setting hooks. This may be useful for things like linking streams or eventual unit support in param.

    An example of what this might be useful for is shown in ioam/holoviews#1740.

    I'm not quite sure about the appropriate signatures for the hooks right now and this PR will probably need quite a bit of discussion before we are agreed on the appropriate approach.

    opened by jlstevens 22
  • ParameterizedMetaclass now sets  __init__ method docstring

    ParameterizedMetaclass now sets __init__ method docstring

    Introduction

    This implements my suggestion for better IPython support (#71).

    In the end, this was surprisingly easy to implement! Nothing particularly fancy is going on here but I have made this feature optional. You can disable this behavior using the DOCSTRING_SIGNATURE global variable.

    Given that the time taken for class definition (e.g. on import) is rarely a performance issue, this global flag might not be necessary (assuming you are happy enabling this behavior by default).

    Demo

    
    class Foo(param.Parameterized):
        """
        This is the Foo class.
        """
    
        foo = param.Number(default=42)
    
        def __init__(self, **kwargs):
            super(Foo, self).__init__(**kwargs)
    

    Now if in IPython Notebook I do:

    >>> Foo(
    

    I see the following pop-up message:

    Foo(self, **kwargs)
    
    Foo(*args, **kwargs, foo=42)
    

    (The first useless line is automatically generated by IPython - it would be nice to suppress it!)

    If I now press the + button, the pop-up expands to show the following message underneath:

    This is the Foo class.
    

    Note that if a docstring is explicitly supplied to the __init__ method, it will be left untouched.

    The crucial features is that you can now tab-complete the 'foo' keyword and see a list all the parameters by pressing TAB (before a unique match is specified).

    Limitations

    • I simply sort all the keywords alphanumerically. A fancier implementation could list parameters according to the method resolution order (mro).
    • The values used in the signature are not updated if the user overrides the default values of class parameters.
    • Param seems to do something special if there is no __init__ for the particular subclass. For instance:
    class Bar(Foo):
    
        bar = param.String(default='bar')
    
    >>> print Bar.__init__.__doc__
            Initialize this Parameterized instance.
    
            The values of parameters can be supplied as keyword arguments
            to the constructor (using parametername=parametervalue); these
            values will override the class default values for this one
            instance.
    
            If no 'name' parameter is supplied, self.name defaults to the
            object's class name with a unique number appended to it.
    
    • The auto-generated signature created by IPython is useless and annoying. Maybe this is something we could disable for Parameterized objects in the IPython extension?
    • Perhaps if the output is particularly good, this could replace the %params magic, listing all the parameters together with their docstrings? Note that pressing the ^ button expands all the output in the pager (which is where the %param magic also displays its output).
    • No fancy string formatting. Perhaps this is a feature. :-)

    Conclusion

    For now, I just want to sketch out a basic implementation in this PR. The inclusion of fancier features and improvements now depend on your feedback!

    Edit: Bar inherits from Foo.

    opened by jlstevens 21
  • param.DataFrame not accepting input

    param.DataFrame not accepting input

    Maybe I'm not calling this correctly?

    foo = param.DataFrame(columns=3) results in:

    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-2-526280bfed91> in <module>
          5 # class test(param.Parameterized):
          6 #     df = param.DataFrame(rows=3, columns=4)
    ----> 7 foo = param.DataFrame(columns=3)
    
    ~/ers/gitHub/param/master/param/param/__init__.py in __init__(self, rows, columns, ordered, **params)
       1313         self.columns = columns
       1314         self.ordered = ordered
    -> 1315         super(DataFrame,self).__init__(pdDFrame, allow_None=True, **params)
       1316         self._check_value(self.default)
       1317 
    
    ~/ers/gitHub/param/master/param/param/__init__.py in __init__(self, class_, default, instantiate, is_instance, **params)
       1150         self.is_instance = is_instance
       1151         super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params)
    -> 1152         self._check_value(default)
       1153 
       1154 
    
    ~/ers/gitHub/param/master/param/param/__init__.py in _check_value(self, val, obj)
       1348                 raise ValueError(msg.format(found=list(val.columns), expected=sorted(self.columns)))
       1349         else:
    -> 1350             self._length_bounds_check(self.columns, len(val.columns), 'Column')
       1351 
       1352         if self.ordered:
    
    AttributeError: 'NoneType' object has no attribute 'columns'
    

    And

    foo = param.DataFrame(rows=3)

    results in:

    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-3-eff4207c7467> in <module>
          5 # class test(param.Parameterized):
          6 #     df = param.DataFrame(rows=3, columns=4)
    ----> 7 foo = param.DataFrame(rows=3)
    
    ~/ers/gitHub/param/master/param/param/__init__.py in __init__(self, rows, columns, ordered, **params)
       1313         self.columns = columns
       1314         self.ordered = ordered
    -> 1315         super(DataFrame,self).__init__(pdDFrame, allow_None=True, **params)
       1316         self._check_value(self.default)
       1317 
    
    ~/ers/gitHub/param/master/param/param/__init__.py in __init__(self, class_, default, instantiate, is_instance, **params)
       1150         self.is_instance = is_instance
       1151         super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params)
    -> 1152         self._check_value(default)
       1153 
       1154 
    
    ~/ers/gitHub/param/master/param/param/__init__.py in _check_value(self, val, obj)
       1356 
       1357         if self.rows is not None:
    -> 1358             self._length_bounds_check(self.rows, len(val), 'Row')
       1359 
       1360     def __set__(self,obj,val):
    
    TypeError: object of type 'NoneType' has no len()
    
    opened by kcpevey 18
  • JSON serialization and schema generation

    JSON serialization and schema generation

    This PR outlines another attempt at serializing/deserializing to JSON (for reference see #153 for earlier attempts at tackling this problem). The design used in this PR aims to try to keep things simpler but also adds the ability to generate JSON Schemas which should help with testing as well as specification of allowable parameter values (e.g this could be used to specify suitable widgets in panel).

    This PR is based off the ideas suggested in #382.

    TODO:

    • [ ] The main open problem is about finding the appropriate API and JSON (and schema) representation for entire parameterized objects (i.e including the class identity as well as all the parameters).
    opened by jlstevens 17
  • Added pprint function to parameterized.py

    Added pprint function to parameterized.py

    This PR adds something I've long wished for in param: sensible, short reprs that actually work. :-)

    Although I started implementing this in __repr__, I saw the note about repr needing to be fast and then decided against implementing it there once I realized what was required.

    Instead of changing __repr__, I've ended up making a more robust pprint function that can be used as follows:

    class Foo(param.Parameterized):
    
        a = param.Number(4, precedence=6)
    
        b = param.String('B', precedence=-9)
    
        c = param.Number(4, precedence=0)
    
        z = param.Number(4, precedence=0)
    
        def __init__(self, a, b, c=4, d=99, **kwargs):
            self.d = d
            super(Foo, self).__init__(a=a, b=b, c=c, **kwargs)
    
        def __repr__(self):
            return param.parameterized.pprint(self, {'d':self.d})
    

    First the positional arguments are shown as their appropriate values. Then modified keyword arguments are shown in the order they are declared in. Then the varargs (e.g *varargs) is shown (if present). Then if a keyword dictionary (i.e. **kwargs) is present, it is assumed that the modified parameters can then be listed in order of their precedence.

    Here are some examples:

    >>> Foo(1,'C')
    Foo(1, 'C')
    
    # Positional arguments always shown even if at default values
    >>> Foo(4,'B')  
    Foo(4,'B')
    
    # Keyword not shown if supplied at default value
    >>> Foo(4,'B', c=4) 
    Foo(4,'B')
    
    >>> Foo(4, 'B', c=5)
    Foo(4, 'B', c=5)
    
    # 'd' is not a parameter (supplied at the default value)
    >>> Foo(4,'B', c=5, d=99)   
    Foo(4, 'B', c=5)
    
    >>> Foo(4,'B', c=5, d=100)
     Foo(4,'B', c=5, d=100)
    
    >>>  Foo(4,'B', z=44, d=100)
     Foo(4,'B', z=44, d=100)
    
    >>>  Foo(4,'B', z=4, d=100)
    Foo(4, 'B', d=100)
    

    For comparison, here is an example of the default repr:

    >>>Foo(4,'B')
    Foo(a=4, b='B', c=4, name='Foo00456', z=4)
    

    My main concern with pprint is performance. It might be nice to cache some of the results of the introspection for performance reasons.

    EDIT: If you are happy with the addition of this function, I'll make sure to write a good number of unit tests for it.

    opened by jlstevens 17
  • create initial project governance docs

    create initial project governance docs

    The files in this pull request should act as examples of how other HoloViz projects could quickly adopt the default project docs using references to the project docs at holoviz/holoviz.

    The maintainer list is based on the HoloViz Duties spreadsheet. All three maintainers should participate and/or approve.

    I'll mention this at the next docs meeting, but I've written the guidance to create project docs. I'll update this if there are any further changes to this PR.

    opened by droumis 0
  • watched nested dependencies throws AttributeError

    watched nested dependencies throws AttributeError

    Thanks for contacting us! Please read and follow these instructions carefully, then delete this introductory text to keep your issue easy to read. Note that the issue tracker is NOT the place for usage questions and technical assistance; post those at Discourse instead. Issues without the required information below may be closed immediately.

    ALL software version info

    (this library, plus any other relevant software, e.g. bokeh, python, notebook, OS, browser, etc) param v 1.12.3 Reproducible at least on on python 3.10 on Ubuntu.

    Description of expected behavior and the observed behavior

    The error below is thrown when I try nested parameterized dependencies, where there is a watch on a nested dependency. I would expect that the watch flag should work in these circumstances.

    Complete, minimal, self-contained example code that reproduces the issue

    import param
    
    class A(param.Parameterized):
        x=param.Number(default=0)
    
    class B(param.Parameterized):
        a=param.ClassSelector(A, default=A())
    
        @param.depends('a.x', watch=True)
        def y(self):
            pass
    
    
    class C(param.Parameterized):
        b=param.ClassSelector(B, default=B())
    
    
    
    c = C()
    
    
    
    

    Stack traceback and/or browser JavaScript console output

      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3398, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-2-c31ab72dd48f>", line 1, in <cell line: 1>
        runfile('/home/mats/.config/JetBrains/PyCharm2022.2/scratches/scratch_2.py', wdir='/home/mats/.config/JetBrains/PyCharm2022.2/scratches')
      File "/home/mats/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/222.4345.23/plugins/python/helpers/pydev/_pydev_bundle/pydev_umd.py", line 198, in runfile
        pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
      File "/home/mats/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/222.4345.23/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
        exec(compile(contents+"\n", file, 'exec'), glob, loc)
      File "/home/mats/.config/JetBrains/PyCharm2022.2/scratches/scratch_2.py", line 20, in <module>
        c = C()
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 3173, in __init__
        self.param._setup_params(**params)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 1387, in override_initialization
        fn(parameterized_instance, *args, **kw)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 1633, in _setup_params
        self.param._instantiate_param(p)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 1688, in _instantiate_param
        new_object = copy.deepcopy(param_obj.default)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 172, in deepcopy
        y = _reconstruct(x, memo, *rv)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 271, in _reconstruct
        state = deepcopy(state, memo)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 146, in deepcopy
        y = copier(x, memo)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 231, in _deepcopy_dict
        y[deepcopy(key, memo)] = deepcopy(value, memo)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 172, in deepcopy
        y = _reconstruct(x, memo, *rv)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 297, in _reconstruct
        value = deepcopy(value, memo)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 146, in deepcopy
        y = copier(x, memo)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 206, in _deepcopy_list
        append(deepcopy(a, memo))
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 172, in deepcopy
        y = _reconstruct(x, memo, *rv)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 265, in _reconstruct
        y = func(*args)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 264, in <genexpr>
        args = (deepcopy(arg, memo) for arg in args)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 172, in deepcopy
        y = _reconstruct(x, memo, *rv)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/copy.py", line 273, in _reconstruct
        y.__setstate__(state)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 3227, in __setstate__
        watcher_args[2] = _m_caller(self, fn._watcher_name)
      File "/home/mats/miniconda3/envs/quant/lib/python3.10/site-packages/param/parameterized.py", line 661, in _m_caller
        function = getattr(self, method_name)
    AttributeError: 'A' object has no attribute 'y'
    

    When a default value is given to a ClassSelector (like in C above), a deepcopy of that default value happens, and it is the deepcopy that is causing the exception, so a shorter example wold be to just do:

    import param
    import copy
    
    class A(param.Parameterized):
        x=param.Number(default=0)
    
    class B(param.Parameterized):
        a=param.ClassSelector(A, default=A())
    
        @param.depends('a.x', watch=True)
        def y(self):
            pass
    
    copy.deepcopy(B())
    
    

    Removing the "watch" flag removes the issue, but I also lose the watched updates that I need.

    opened by tjader 0
  • Expand button not working

    Expand button not working

    ALL software version info

    panel==0.14.2 param==1.12.3 jupyterlab==3.5.1

    Description of expected behavior and the observed behavior

    Expand button open and closes expansion on objectselector

    Complete, minimal, self-contained example code that reproduces the issue

    import param as pm
    import panel as pn
    pn.extension()
    
    
    class A(pm.Parameterized):
        value = pm.String("Value A")
        
    class B(pm.Parameterized):
        a = pm.ObjectSelector()
    
    a = A()
    b = B(a=a)
    
    pn.Pane(b)
    
    pn.Pane(b, expand=True, expand_button=True)
    

    Stack traceback and/or browser JavaScript console output

    Screenshots or screencasts of the bug in action

    image Clicking the blue buttons does nothing.

    opened by LinuxIsCool 1
  • set_dynamic_time_fn() does not set dynamic time_fn

    set_dynamic_time_fn() does not set dynamic time_fn

    ALL software version info

    param 1.12.2

    Description of expected behavior and the observed behavior

    From the API reference:

    set_dynamic_time_fn(time_fn, sublistattr=None)[source] Set time_fn for all Dynamic Parameters of this class or instance object that are currently being dynamically generated.

    Either I am completely misunderstanding this/using it wrong, or it just doesn't do that. The time_fn is unaffected.

    Additionally, sets _Dynamic_time_fn=time_fn on this class or instance object, ...

    This is true.

    ... so that any future changes to Dynamic Parmeters can inherit time_fn (e.g. if a Number is changed from a float to a number generator, the number generator will inherit time_fn).

    But this is not.

    Complete, minimal, self-contained example code that reproduces the issue

    import param
    import numbergen as ng
    
    class Foo(param.Parameterized):
        a = param.Dynamic()
    
    print(Foo.param.a.time_fn)
        
    foo = Foo()
    print(foo.param.a.time_fn)
    
    time = param.Time()
    print(time)
    
    Foo.param.set_dynamic_time_fn(time)
    print(Foo.param.a.time_fn) # This has not changed
    
    foo.param.set_dynamic_time_fn(time)
    print(foo.param.a.time_fn) # This has not changed
    
    print(Foo._Dynamic_time_fn) # This did get set
    foo2 = Foo()
    print(foo2.param.a.time_fn) # But was not set on the new instance parameter
    foo2.a = ng.UniformRandom()
    print(foo2.param.a.time_fn) # Nor set on change
    
    <Time Time00001>
    <Time Time00001>
    <Time Time00003>
    <Time Time00001>
    <Time Time00001>
    <Time Time00003>
    <Time Time00001>
    <Time Time00001>
    
    opened by AQ18 1
  • Inherited child classes give weird behavior if the class name is identical to a parent class

    Inherited child classes give weird behavior if the class name is identical to a parent class

    Inherited child classes give weird behavior if the class name is identical to a parent class.

    A simple example of this is:

    import param
    
    
    class Parameterized(param.Parameterized):
        number = param.Number()
    
        def __init__(self, **params):
            super().__init__(**params)
            self.param.number.bounds = (1, 2)
    
    
    Parameterized()
    

    Which will raise the following

    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    Cell In[22], line 12
          8         super().__init__(**params)
          9         self.param.number.bounds = (1, 2)
    ---> 12 Parameterized()
    
    Cell In[22], line 9, in Parameterized.__init__(self, **params)
          7 def __init__(self, **params):
          8     super().__init__(**params)
    ----> 9     self.param.number.bounds = (1, 2)
    
    File ~/Development/holoviz/repos/param/param/parameterized.py:1589, in Parameters.__getattr__(self_, attr)
       1586     raise AttributeError("type object '%s.param' has no attribute %r" %
       1587                          (self_.cls.__name__, attr))
       1588 else:
    -> 1589     raise AttributeError("'%s.param' object has no attribute %r" %
       1590                          (self_.cls.__name__, attr))
    
    AttributeError: 'Parameterized.param' object has no attribute 'number'
    

    But if I rename the class to Parameterized2 everything works as expected: image

    type-bug 
    opened by Hoxbro 0
Releases(v1.12.3)
  • v1.12.3(Dec 9, 2022)

    The 1.12.3 release adds support for Python 3.11. Many thanks to @musicinmybrain (first contribution!) and @maximlt for contributing to this release.

    Enhancements:

    • Preserve existing Random seed behavior in Python 3.11 (#638)
    • Add support for Python 3.11 (#658)
    Source code(tar.gz)
    Source code(zip)
  • v1.12.2(Jun 24, 2022)

    The 1.12.2 release fixes a number of bugs and adds support again for Python 2.7, which was unfortunately no longer supported in the last release. Note however that Param 2.0 will still drop support of Python 2.7 as already announced. Many thanks to @Hoxbro and the maintainers @jbednar, @jlstevens, @maximlt and @philippjfr for contributing to this release.

    Bug fixes:

    • Match against complete spec name when determining dynamic watchers (615)
    • Ensure async functionality does not cause python2 syntax errors (624)
    • Allow (de)serializing CalendarRange and DateRange Parameters (625)
    • Improve DateRange validation (627)
    • Fix regression in @param.depends execution ordering (628)
    • Ensure named_objs does not fail on unhashable objects (632)
    • Support comparing date-like objects (629)
    • Fixed BinaryPower example in the docs to use the correct name EvenInteger(634)
    Source code(tar.gz)
    Source code(zip)
  • v1.12.1(Mar 31, 2022)

    The 1.12.1 release fixes a number of bugs related to async callback handling when using param.depends and .param.watch and a number of documentation and error messages. Many thanks to @HoxBro and the maintainers @jbednar, @jlstevens, @maximlt and @philippjfr for contributing to this release.

    Error handling and documentation:

    • Fixed description of shared_parameters (#568)
    • Improve the error messages of Date and DateRange (#579)
    • Clarified step error messages and other docs and links (#604)

    Bug fixes:

    • Make iscoroutinefunction more robust (#572)
    • Fix for handling misspelled parameter (#575)
    • Handle None serialization for Date, CalendarDate, Tuple, Array, and DataFrame (#582)
    • Support async coroutines in param.depends (#591)
    • Handle async functions in depends with watch=True (#611)
    • Avoid equality check on Watcher (#612)

    Documentation:

    • Fix binder (#564)
    • Fixed description of shared_parameters (#568)
    Source code(tar.gz)
    Source code(zip)
  • v1.12.0(Oct 21, 2021)

    Version 1.12.0 introduces a complete user manual and website (for the first time since 2003!) along with extensive API improvements gearing up for the 2.0 release (which will be Python3 only).

    The pre-2.0 API is still being preserved and no new warnings are added in this release, so the older API can continue to be used with this release, but the next 1.x release is expected to enable warnings for deprecated API. If you have older code using the deprecated Param features below, please update your API calls as described below to be compatible with the 2.0 release when it comes out (or pin to param<2 if you don't need any new Param features). For new users, just use the API documented on the website, and you should be ready to go for either 1.12+ or 2.0+.

    Thanks to James A. Bednar for the user guide and 2.0 API support, to Philipp Rudiger for improvements and new capabilities for handling dependencies on subobjects, and to Maxime Liquet and Philipp Rudiger for extensive improvements to the website/docs/package-building/testing.

    New features:

    • Added future-facing API for certain Parameterized().param methods (see Compatibility below; #556, #558, #559)
    • New option on_init=True for @depends decorator, to run the method in the constructor to ensure initial state is consistent when appropriate (#540)
    • Now resolves subobject dependencies dynamically, allowing dependencies on internal parameters of subobjects to resolve appropriately as those objects are replaced. (#552)
    • Added prettyprinting for numbergen expressions (#525)
    • Improved JSON schema generation (#458)
    • Added more-usable script_repr command, availabie in param namespace, with default values, and showing imports (#522)
    • Added Parameterized.param.pprint(); underlying implementation of script_repr but with defaults suitable for interactive usage rather than generating a .py script. (#522)
    • Watchers can now declare precedence so that events are triggered in the desired order (#552, #557)

    Bug fixes:

    • Fix bug setting attributes in some cases before class is initialized (#544)
    • Ensure None is supported on ListSelector (#511)
    • Switched from deprecated inspect.getargspec to the py3 version inspect.getfullargspec, which is similar but splits keyword args into varkw (**) and kw-only args. Falls back to getargspec on Python2. (#521)

    Doc improvements (including complete user guide for the first time!):

    • Misc comments/docstrings/docs cleanup (#507, #518, #528, #553)
    • Added comparison with pydantic (#523)
    • Added new user guide sections:
      • Dependencies_and_Watchers user guide (#536)
      • Dynamic Parameters (#525)
      • Outputs (#523)
      • Serialization and Persistence (#523)

    Infrastructure:

    • Added testing on Python 3.10 and on Mac OS X and removed slow PyPy/pandas/numpy tests (#548, #549, #526)
    • Docs/tests/build infrastructure improvements (#509, #521, #529, #530, #537, #538, #539, #547, #548, #555)

    Compatibility (see #543 for the complete list):

    • Calendardate now accepts date values only (#517)

    • No longer allows modifying name of a Parameter once it is in a Parameterized class, to avoid confusion (#541)

    • Renamed (with old name still accepted for compatibility until 2.0):

      • .param._add_parameter(): Now public .param.add_parameter(); too useful to keep private! (#559)
      • .param.params_depended_on: Now .param.method_dependencies to indicate that it accepts a method name and returns its dependencies (#559)
      • .pprint: Now private ._pprint; use public .param.pprint instead (#559)
      • batch_watch: Now batch_call_watchers, to declare that it does not set up watching, it just invokes it. Removed unused operation argument (#536)
    • Deprecated (but not yet warning unless noted):

      • .param.debug(): Use .param.log(param.DEBUG, ...) instead (#556)
      • .param.verbose(): Use .param.log(param.VERBOSE, ...) instead (#556)
      • .param.message(): Use .param.log(param.MESSAGE, ...) instead (#556)
      • .param.defaults(): Use {k:v.default for k,v in p.param.objects().items()} instead (#559)
      • .param.deprecate(): To be repurposed or deleted after 2.0 (#559)
      • .param.params(): Use .param.values() or .param['param'] instead (#559)
      • .param.print_param_defaults(): Use for k,v in p.param.objects().items(): print(f"{p.__class__.name}.{k}={repr(v.default)}") instead (#559)
      • .param.print_param_values(): Use for k,v in p.param.values().items(): print(f"{p.name}.{k}={v}") instead (#559)
      • .param.set_default(): Use p.param.default= instead (#559)
      • .param.set_param(): Had tricky API; use .param.update instead (#558)
      • .param.get_param_values(): Use .param.values().items() instead (or .param.values() for the common case of dict(....param.get_param_values())) (#559)
      • .state_pop(): Will be renamed to ._state_pop to make private
      • .state_push(): Will be renamed to ._state_push to make private
      • .initialized: Will be renamed to ._initialized to make private
      • Most methods on Parameterized itself have already been deprecated and warning for some time now; see #543 for the list. Use the corresponding method on the .param accessor instead.
    • Added:

      • .param.watchers: Read-only version of private _watchers (#559)
      • .param.log(): Subsumes .debug/verbose/message; all are logging calls. (#556)
      • .param.update(): Dictionary-style updates to parameter values, as a drop-in replacement for set_param except for its optional legacy positional-arg syntax (#558)
      • .values(): Dictionary of name:value pairs for parameter values, replacing get_param_values but now a dict since python3 preserves order (#558)
      • .param.log(): General-purpose interface to the logging module functionailty; replaces .debug, .verbose, .message (#556)
    Source code(tar.gz)
    Source code(zip)
  • v1.11.1(Jul 6, 2021)

    (including changes in 1.11.0; 1.11.1 adds only a minor change to fix param.List(None).)

    Version 1.11 contains entirely new documentation, plus various enhancements and bugfixess. Thanks to James A. Bednar for the documentation, Philipp Rudiger for the website setup and for many of the other fixes and improvements below, and others as noted below.

    Documentation:

    • Brand-new website, with getting started, user manual, reference manual, and more! Some user guide sections are still under construction. (#428,#464,#479,#483,#501,#502,#503,#504)
    • New intro video with examples/Promo.ipynb notebook, thanks to Marc Skov Madsen and Egbert Ammicht (#488)
    • Sort docstring by definition order and precedence, thanks to Andrew Huang (#445)

    Enhancements:

    • Allow printing representations for recursively nested Parameterized objects (#499)
    • Allow named colors for param.Color (#472)
    • Allow FileSelector and MultiFileSelector to accept initial values (#497)
    • Add Step slot to Range, thanks to Simon Hansen (#467)
    • Update FileSelector and MultiFileSelector parameters when setting path (#476)
    • Improved error messages (#475)

    Bug Fixes:

    • Fix Path to allow folders, as documented but previously not supported (#495)
    • Fix previously unimplemented Parameter._on_set (#484)
    • Fix Python2 IPython output parameter precedence (#477)
    • Fix allow_None for param.Series and param.DataFrame (#473)
    • Fix behavior when both instantiate and constant are True (#474)
    • Fix for versioning when param is inside a separate git-controlled repo (port of fix from autover/pull/67) (#469)

    Compatibility:

    • Swapped ObjectSelector and Selector in the inheritance hierarchy, to allow ObjectSelector to be deprecated. (#497)
    • Now get_soft_boundssilently crops softbounds to any hard bounds supplied ; previously soft bounds would be returned even if they were outside the hard bounds (#498)
    • Rename class_ to item_type in List parameter, to avoid clashing semantics with ClassSelector and others with a class_ slot. class_ is still accepted as a keyword but is stored in item_type. (#456)
    Source code(tar.gz)
    Source code(zip)
  • v1.10.1(May 10, 2021)

    Minor release for Panel-related bugfixes and minor features, from @philippjfr.

    • Fix serialization of Tuple, for use in Panel (#446)
    • Declare asynchronous executor for Panel to use (#449)
    • Switch to GitHub Actions (#450)
    Source code(tar.gz)
    Source code(zip)
  • v1.9.3(Jan 26, 2020)

  • v1.9.2(Jan 23, 2020)

  • v1.9.1(Oct 8, 2019)

    Enhancements:

    • Allow param.depends to annotate functions (#334)
    • Add context managers to manage events and edit constant parameters

    Bug fixes:

    • Ensure that Select constructor does not rely on truthiness (#337)
    • Ensure that param.depends allows mixed Parameter types (#338)
    • Date and DateRange now allow dt.date type (#341)
    • Ensure events aren't dropped in batched mode (#343)
    Source code(tar.gz)
    Source code(zip)
  • v1.9.0(Apr 8, 2019)

    Full release with new functionality and some fixes. New features:

    • Added support for instance parameters, allowing parameter metadata to be modified per instance and allowing parameter objects to be passed to Panel objects (#306)
    • Added label slot to Parameter, to allow overriding attribute name for display (#319)
    • Added step slot to Parameter, e.g. to control Panel widget step size (#326)
    • Added keywords_to_params utility for deducing Parameter types and ranges automatically (#317)
    • Added support for multiple outputs from a Parameterized (#312)
    • Added Selector as a more user-friendly version of ObjectSelector, accepting a list of options as a positional argument (#316)

    Changes affecting backwards compatibility:

    • Changed from root logger to a param-specific logger; no change to API but will change format of error and warning messages (#330)
    • Old abstract class Selector renamed to SelectorBase; should be no change unless user code added custom classes inherited from Selector without providing a constructor (#316)

    Bugfixes and other improvements:

    • Various bugfixes (#320, #323, #327, #329)
    • Other improvements (#315, #325)

    For more details, you can see a full list of changes since the previous release.

    Source code(tar.gz)
    Source code(zip)
  • v1.8.2(Feb 4, 2019)

  • v1.8.1(Oct 28, 2018)

  • v1.8.0(Oct 28, 2018)

    Major new feature set: comprehensive support for events, watching, callbacks, and dependencies

    • Parameterized methods can now declare @depends(p,q) to indicate that they depend on parameters p and q (defaulting to all parameters)
    • Parameterized methods can depend on subobjects with @depends(p.param,q.param.r), where p.param indicates dependencies on all parameters of p and q.param.r indicates a dependency on parameter r of q.
    • Functions and methods can watch parameter values, re-running when those values change or when an explicit trigger is issued, and can unwatch them later if needed.
    • Multiple events can be batched to trigger callbacks only once for a coordinated set of changes

    Other new features:

    • Added support in ObjectSelector for selecting lists and dicts (#268)
    • Added pandas DataFrame and Series parameter types (#285)
    • Added support for regular expression validation to String Parameter (#241, #245)

    For more details, you can see a full list of changes since the previous release.

    Source code(tar.gz)
    Source code(zip)
  • v1.7.0(Jun 28, 2018)

    Since the previous release (1.6.1), there should be no changes that affect existing code, only additions:

    • A new param namespace object, which in future will allow subclasses of Parameterized to have much cleaner namespaces (#230).
    • Started testing on python 3.7-dev (#223).
    • param.version now provides functions to simplify dependants' setup.py/setup.cfg files (see https://github.com/pyviz/autover/pull/49).

    Although param should still work on python 3.3, we are no longer testing against it (unsupported by our test environment; #234).

    For more details, you can see a full list of changes since the previous release.

    Source code(tar.gz)
    Source code(zip)
  • v1.6.1(Apr 25, 2018)

    Restores support for the previous versioning system (pre 1.6; see #225), and fixes a number of issues with the new versioning system:

    • Allow package name to differ from repository name (https://github.com/pyviz/autover/pull/39)

    • Allow develop install to work when repository is dirty (https://github.com/pyviz/autover/pull/41)

    • Fixed failure to report dirty when commit count is 0 (https://github.com/pyviz/autover/pull/44)

    Source code(tar.gz)
    Source code(zip)
  • v1.6.0(Apr 5, 2018)

    Notable changes, fixes, and additions since the previous release (1.5.1) are listed below. You can also see a full list of changes since the previous release.

    Changes

    • param.__version__ is now a string
    • param.version.Version now supports a tag-based versioning workflow; if using the Version class, you will need to update your workflow (see autover for more details).
    • Dropped support for python 2.6 (#175).
    • No longer attempt to cythonize param during installation via pip (#166, #194).

    Fixes

    • Allow get_param_values() to work on class (#162).
    • Fixed incorrect default value for param.Integer (#151).
    • Allow a String to be None if its default is None (#104).
    • Fixed ListSelector.compute_default() (#212).
    • Fixed checks for None in various Parameter subclasses (#208); fixes problems for subclasses of Parameterized that define a custom __nonzero__ or __len__.

    Additions

    • Added DateRange parameter.

    Miscellaneous

    • No longer tested on python 3.2 (unsupported by our test environment; #218).
    Source code(tar.gz)
    Source code(zip)
  • v1.5.1(Apr 26, 2017)

    • Fixed error messages for ClassSelector with tuple of classes
    • Added get and contains methods for ParamOverrides

    A full list of changes since the previous release is available here.

    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Feb 27, 2017)

    • Added Range, Color, and Date parameters
    • Improved ObjectSelector error messages
    • Minor bugfixes

    A full list of changes since the previous release is available here.

    Source code(tar.gz)
    Source code(zip)
  • v1.4.2(Oct 14, 2016)

  • v1.4.1(Jul 12, 2016)

  • v1.4.0(Jul 11, 2016)

Owner
HoloViz
High-level tools to simplify visualization in Python
HoloViz
Lightweight data validation and adaptation Python library.

Valideer Lightweight data validation and adaptation library for Python. At a Glance: Supports both validation (check if a value is valid) and adaptati

Podio 258 Nov 22, 2022
An(other) implementation of JSON Schema for Python

jsonschema jsonschema is an implementation of JSON Schema for Python. >>> from jsonschema import validate >>> # A sample schema, like what we'd get f

Julian Berman 4k Jan 4, 2023
Lightweight, extensible data validation library for Python

Cerberus Cerberus is a lightweight and extensible data validation library for Python. >>> v = Validator({'name': {'type': 'string'}}) >>> v.validate({

eve 2.9k Dec 27, 2022
CONTRIBUTIONS ONLY: Voluptuous, despite the name, is a Python data validation library.

CONTRIBUTIONS ONLY What does this mean? I do not have time to fix issues myself. The only way fixes or new features will be added is by people submitt

Alec Thomas 1.8k Dec 31, 2022
Python Data Validation for Humans™.

validators Python data validation for Humans. Python has all kinds of data validation tools, but every one of them seems to require defining a schema

Konsta Vesterinen 670 Jan 9, 2023
A simple, fast, extensible python library for data validation.

Validr A simple, fast, extensible python library for data validation. Simple and readable schema 10X faster than jsonschema, 40X faster than schematic

kk 209 Sep 19, 2022
Typical: Fast, simple, & correct data-validation using Python 3 typing.

typical: Python's Typing Toolkit Introduction Typical is a library devoted to runtime analysis, inference, validation, and enforcement of Python types

Sean 171 Jan 2, 2023
Python Data Structures for Humans™.

Schematics Python Data Structures for Humans™. About Project documentation: https://schematics.readthedocs.io/en/latest/ Schematics is a Python librar

Schematics 2.5k Dec 28, 2022
Type-safe YAML parser and validator.

StrictYAML StrictYAML is a type-safe YAML parser that parses and validates a restricted subset of the YAML specification. Priorities: Beautiful API Re

Colm O'Connor 1.2k Jan 4, 2023
Smaller, easier, more powerful, and more reliable than make. An implementation of djb's redo.

redo - a recursive build system Smaller, easier, more powerful, and more reliable than make. This is an implementation of Daniel J. Bernstein's redo b

null 1.7k Jan 4, 2023
Image enhancing model for making a blurred image to be somehow clearer than before

This is a very small prject which helps in enhancing the images by taking a Input images. This project has many features like detcting the faces and enhaning the faces itself and also a feature which enhances the whole image

null 3 Dec 3, 2021
Milano is a tool for automating hyper-parameters search for your models on a backend of your choice.

Milano (This is a research project, not an official NVIDIA product.) Documentation https://nvidia.github.io/Milano Milano (Machine learning autotuner

NVIDIA Corporation 147 Dec 17, 2022
toldium is a modular, fast, reliable and customizable multiplatform bot library for your communities

toldium The easy multiplatform bot toldium is a modular, fast, reliable and customizable multiplatform bot library for your communities, from a commun

Stockdroid Fans 5 Nov 3, 2021
ChairBot is designed to be reliable, easy to use, and lightweight for every user, and easliy to code add-ons for ChairBot.

ChairBot is designed to be reliable, easy to use, and lightweight for every user, and easliy to code add-ons for ChairBot. Ready to see whats possible with ChairBot?

null 1 Nov 8, 2021
A simple bot that will help you in your learning and make it more fun.

hyperskill-SimpleChattyBot-python A simple bot that will help you in your learning and make it more fun. Syntax bot.py Stages Stage #1: Zuhura Bot we

null 1 Nov 9, 2021
Dome - Subdomain Enumeration Tool. Fast and reliable python script that makes active and/or passive scan to obtain subdomains and search for open ports.

DOME - A subdomain enumeration tool Check the Spanish Version Dome is a fast and reliable python script that makes active and/or passive scan to obtai

Vadi 329 Jan 1, 2023
MM1 and MMC Queue Simulation using python - Results and parameters in excel and csv files

implementation of MM1 and MMC Queue on randomly generated data and evaluate simulation results then compare with analytical results and draw a plot curve for them, simulate some integrals and compare results and run monte carlo algorithm with them

Mohamadreza Rezaei 1 Jan 19, 2022
WebApp Maker make web apps (Duh). It is open source and make with python and shell.

WebApp Maker make web apps (Duh). It is open source and make with python and shell. This app can take any website and turn it into an app. I highly recommend turning these few websites into webapps: - Krunker.io (Fps Game) - play.fancade.com (Minigame Arcade) - Your Own Website If You Have One Apart from that enjoy my app By 220735540 (a.k.a RP400)

null 2 Jan 9, 2022