Import upstream version 1.12.1
Debian Janitor
2 years ago
0 | environment: | |
1 | matrix: | |
2 | - TOXENV: py36 | |
3 | - TOXENV: py35 | |
4 | - TOXENV: py34 | |
5 | - TOXENV: py27 | |
6 | ||
7 | matrix: | |
8 | fast_finish: true | |
9 | ||
10 | build: false | |
11 | ||
12 | install: C:\Python36\python -m pip install -U tox | |
13 | ||
14 | test_script: C:\Python36\scripts\tox |
0 | sudo: false | |
1 | ||
2 | language: python | |
3 | ||
4 | # quick hack to determine what tag is (improvements welcomed) | |
5 | # release: ^v(\d+|\.)+[^a-z]\d+$ | |
6 | # dev release: ^v(\d+|\.)+[a-z]\d+$ | |
7 | ||
8 | stages: | |
9 | - lint | |
10 | - test | |
11 | - name: pip_dev_package | |
12 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ | |
13 | - name: pip_package | |
14 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ | |
15 | - name: conda_dev_package | |
16 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ | |
17 | - name: conda_package | |
18 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ | |
19 | - name: website_dev | |
20 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ OR tag = website_dev | |
21 | - name: website_release | |
22 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ OR tag = website | |
23 | ||
24 | ||
25 | jobs: | |
26 | fast_finish: true | |
27 | include: | |
28 | - &default | |
29 | stage: test | |
30 | python: 3.6 | |
31 | env: TOX_ENV=py36 | |
32 | install: | |
33 | - pip install tox | |
34 | script: | |
35 | - tox -e $TOX_ENV | |
36 | ||
37 | - <<: *default | |
38 | python: 3.7-dev | |
39 | env: TOX_ENV=py37 | |
40 | ||
41 | - <<: *default | |
42 | python: 3.5 | |
43 | env: TOX_ENV=py35 | |
44 | ||
45 | - <<: *default | |
46 | python: 3.4 | |
47 | env: TOX_ENV=py34 | |
48 | ||
49 | - <<: *default | |
50 | python: 2.7 | |
51 | env: TOX_ENV=py27 | |
52 | ||
53 | - <<: *default | |
54 | python: pypy | |
55 | env: TOX_ENV=pypy | |
56 | ||
57 | # could consider running with_ipython,numpy over py36 and 27 | |
58 | ||
59 | - <<: *default | |
60 | env: TOX_ENV=with_ipython | |
61 | ||
62 | - <<: *default | |
63 | env: TOX_ENV=with_numpy | |
64 | ||
65 | - <<: *default | |
66 | env: TOX_ENV=with_pandas | |
67 | ||
68 | - <<: *default | |
69 | env: TOX_ENV=coverage | |
70 | ||
71 | - <<: *default | |
72 | stage: lint | |
73 | env: TOX_ENV=flakes | |
74 | ||
75 | # TODO: the below packaging sections will be simplified with | |
76 | # doit/pyct (and note that using after_success means no alert to | |
77 | # failure uploading) | |
78 | ||
79 | - &conda_default | |
80 | env: LABELS="--label dev" | |
81 | stage: conda_dev_package | |
82 | install: | |
83 | - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; | |
84 | - bash miniconda.sh -b -p $HOME/miniconda | |
85 | - export PATH="$HOME/miniconda/bin:$PATH" | |
86 | - conda config --set always_yes yes --set changeps1 no | |
87 | - conda update conda | |
88 | - conda install anaconda-client conda-build | |
89 | script: | |
90 | - conda build conda.recipe/ | |
91 | after_success: | |
92 | - anaconda --token $CONDA_UPLOAD_TOKEN upload --user pyviz $LABELS $(conda build --output conda.recipe) | |
93 | ||
94 | - <<: *conda_default | |
95 | env: LABELS="--label dev --label main" | |
96 | stage: conda_package | |
97 | ||
98 | - <<: *default | |
99 | stage: pip_dev_package | |
100 | deploy: | |
101 | provider: pypi | |
102 | server: https://test.pypi.org/legacy/ | |
103 | distributions: "sdist bdist_wheel" | |
104 | on: | |
105 | tags: true | |
106 | user: $TESTPYPI_USER | |
107 | password: $TESTPYPI_PWD | |
108 | ||
109 | - <<: *default | |
110 | stage: pip_package | |
111 | deploy: | |
112 | provider: pypi | |
113 | distributions: "sdist bdist_wheel" | |
114 | on: | |
115 | tags: true | |
116 | user: $PYPI_USER | |
117 | password: $PYPI_PWD | |
118 | ||
119 | - &website | |
120 | <<: *default | |
121 | addons: | |
122 | apt: | |
123 | packages: | |
124 | - graphviz | |
125 | stage: website_release | |
126 | before_install: | |
127 | - pip install graphviz | |
128 | install: | |
129 | - pip install nbsite sphinx_ioam_theme "tornado<6" | |
130 | - pip install -e . | |
131 | script: | |
132 | # TODO: nbsite commands will be simplified eventually... | |
133 | - nbsite generate-rst --org pyviz --repo param --project-name param | |
134 | - mkdir doc/Reference_Manual && nbsite_generate_modules.py param -d ./doc/Reference_Manual -n param -e tests | |
135 | - nbsite build --examples-assets='' | |
136 | deploy: | |
137 | - provider: pages | |
138 | skip_cleanup: true | |
139 | github_token: $GITHUB_TOKEN | |
140 | local_dir: ./builtdocs | |
141 | fqdn: param.pyviz.org | |
142 | on: | |
143 | tags: true | |
144 | all_branches: true | |
145 | ||
146 | - <<: *website | |
147 | stage: website_dev | |
148 | env: DESC="pyviz-dev.github.io/param" | |
149 | deploy: | |
150 | - provider: pages | |
151 | skip_cleanup: true | |
152 | github_token: $GITHUB_TOKEN | |
153 | local_dir: ./builtdocs | |
154 | repo: pyviz-dev/param | |
155 | on: | |
156 | tags: true | |
157 | all_branches: true |
0 | Copyright (c) 2005-2018, IOAM (ioam.github.com) | |
0 | Copyright (c) 2005-2022, HoloViz team. | |
1 | 1 | All rights reserved. |
2 | 2 | |
3 | 3 | Redistribution and use in source and binary forms, with or without |
12 | 12 | documentation and/or other materials provided with the |
13 | 13 | distribution. |
14 | 14 | |
15 | * Neither the name of IOAM nor the names of its contributors | |
16 | may be used to endorse or promote products derived from this | |
17 | software without specific prior written permission. | |
15 | * Neither the name of the copyright holder nor the names of any | |
16 | contributors may be used to endorse or promote products derived | |
17 | from this software without specific prior written permission. | |
18 | 18 | |
19 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
0 | Metadata-Version: 2.1 | |
1 | Name: param | |
2 | Version: None | |
3 | Summary: Make your Python code clearer and more reliable by declaring Parameters. | |
4 | Home-page: http://param.holoviz.org/ | |
5 | Author: HoloViz | |
6 | Author-email: developers@holoviz.org | |
7 | Maintainer: HoloViz | |
8 | Maintainer-email: developers@holoviz.org | |
9 | License: BSD | |
10 | Project-URL: Documentation, https://param.holoviz.org/ | |
11 | Project-URL: Releases, https://github.com/holoviz/param/releases | |
12 | Project-URL: Bug Tracker, https://github.com/holoviz/param/issues | |
13 | Project-URL: Source Code, https://github.com/holoviz/param | |
14 | Project-URL: Panel Examples, https://panel.holoviz.org/user_guide/Param.html | |
15 | Platform: Windows | |
16 | Platform: Mac OS X | |
17 | Platform: Linux | |
18 | Classifier: License :: OSI Approved :: BSD License | |
19 | Classifier: Development Status :: 5 - Production/Stable | |
20 | Classifier: Programming Language :: Python :: 2 | |
21 | Classifier: Programming Language :: Python :: 2.7 | |
22 | Classifier: Programming Language :: Python :: 3 | |
23 | Classifier: Programming Language :: Python :: 3.6 | |
24 | Classifier: Programming Language :: Python :: 3.7 | |
25 | Classifier: Programming Language :: Python :: 3.8 | |
26 | Classifier: Programming Language :: Python :: 3.9 | |
27 | Classifier: Programming Language :: Python :: 3.10 | |
28 | Classifier: Operating System :: OS Independent | |
29 | Classifier: Intended Audience :: Science/Research | |
30 | Classifier: Intended Audience :: Developers | |
31 | Classifier: Natural Language :: English | |
32 | Classifier: Topic :: Scientific/Engineering | |
33 | Classifier: Topic :: Software Development :: Libraries | |
34 | Provides: param | |
35 | Provides: numbergen | |
36 | Requires-Python: >=2.7 | |
37 | Description-Content-Type: text/markdown | |
38 | Provides-Extra: all | |
39 | Provides-Extra: doc | |
40 | Provides-Extra: tests | |
41 | License-File: LICENSE.txt | |
42 | ||
43 | <img src="https://raw.githubusercontent.com/holoviz/param/master/doc/_static/logo_horizontal.png" width=250> | |
44 | ||
45 | | | | | |
46 | | --- | --- | | |
47 | | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml) | |
48 | | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/master/graph/badge.svg)](https://codecov.io/gh/holoviz/param) || | |
49 | | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/pyviz-dev.github.io/param.svg?label=dev%20website)](https://pyviz-dev.github.io/param/) | | |
50 | | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/param) | | |
51 | | Python | [![Python support](https://img.shields.io/pypi/pyversions/param.svg)](https://pypi.org/project/param/) | |
52 | | Docs | [![gh-pages](https://img.shields.io/github/last-commit/holoviz/param/gh-pages.svg)](https://github.com/holoviz/param/tree/gh-pages) [![site](https://img.shields.io/website-up-down-green-red/https/param.holoviz.org.svg)](https://param.holoviz.org) | | |
53 | | Binder | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/holoviz/param/master?labpath=examples) | | |
54 | | Support | [![Discourse](https://img.shields.io/discourse/status?server=https%3A%2F%2Fdiscourse.holoviz.org)](https://discourse.holoviz.org/) | | |
55 | ||
56 | 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. | |
57 | ||
58 | 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. | |
59 | ||
60 | Please see [param's website](https://param.holoviz.org) for official releases, installation instructions, documentation, and examples. | |
61 | ||
62 |
0 | <img src="https://raw.githubusercontent.com/holoviz/param/master/doc/_static/logo_horizontal.png" width=250> | |
1 | ||
2 | | | | | |
3 | | --- | --- | | |
4 | | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml) | |
5 | | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/master/graph/badge.svg)](https://codecov.io/gh/holoviz/param) || | |
6 | | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/pyviz-dev.github.io/param.svg?label=dev%20website)](https://pyviz-dev.github.io/param/) | | |
7 | | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/param) | | |
8 | | Python | [![Python support](https://img.shields.io/pypi/pyversions/param.svg)](https://pypi.org/project/param/) | |
9 | | Docs | [![gh-pages](https://img.shields.io/github/last-commit/holoviz/param/gh-pages.svg)](https://github.com/holoviz/param/tree/gh-pages) [![site](https://img.shields.io/website-up-down-green-red/https/param.holoviz.org.svg)](https://param.holoviz.org) | | |
10 | | Binder | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/holoviz/param/master?labpath=examples) | | |
11 | | Support | [![Discourse](https://img.shields.io/discourse/status?server=https%3A%2F%2Fdiscourse.holoviz.org)](https://discourse.holoviz.org/) | | |
12 | ||
13 | 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. | |
14 | ||
15 | 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. | |
16 | ||
17 | Please see [param's website](https://param.holoviz.org) for official releases, installation instructions, documentation, and examples. |
0 | |LinuxTests|_ |WinTests|_ |Coverage|_ |PyPIVersion|_ |PyVersion|_ |License|_ | |
1 | ||
2 | Param | |
3 | ===== | |
4 | ||
5 | Param is a library providing Parameters: Python attributes extended to | |
6 | have features such as type and range checking, dynamically generated | |
7 | values, documentation strings, default values, etc., each of which is | |
8 | inherited from parent classes if not specified in a subclass. | |
9 | ||
10 | Param contains only two required Python files, with no external | |
11 | dependencies, and is provided freely for both non-commercial and | |
12 | commercial use under a BSD license, so that it can easily be included | |
13 | as part of other projects. | |
14 | ||
15 | Please see `param's website <http://param.pyviz.org>`_ for | |
16 | official releases, installation instructions, documentation, and examples. | |
17 | ||
18 | .. |LinuxTests| image:: https://travis-ci.org/pyviz/param.svg?branch=master | |
19 | .. _LinuxTests: https://travis-ci.org/pyviz/param | |
20 | ||
21 | .. |WinTests| image:: https://ci.appveyor.com/api/projects/status/1p5aom8o0tfgok1r?svg=true | |
22 | .. _WinTests: https://ci.appveyor.com/project/pyviz/param/branch/master | |
23 | ||
24 | .. |Coverage| image:: https://img.shields.io/coveralls/pyviz/param.svg | |
25 | .. _Coverage: https://coveralls.io/r/pyviz/param?branch=master | |
26 | ||
27 | .. |PyPIVersion| image:: http://img.shields.io/pypi/v/param.svg | |
28 | .. _PyPIVersion: https://pypi.python.org/pypi/param | |
29 | ||
30 | .. |PyVersion| image:: https://img.shields.io/pypi/pyversions/param.svg | |
31 | .. _PyVersion: https://pypi.python.org/pypi/param | |
32 | ||
33 | .. |License| image:: https://img.shields.io/pypi/l/param.svg | |
34 | .. _License: https://pypi.python.org/pypi/param |
0 | {% set sdata = load_setup_py_data() %} | |
1 | ||
2 | package: | |
3 | name: param | |
4 | version: {{ sdata['version'] }} | |
5 | ||
6 | source: | |
7 | path: .. | |
8 | ||
9 | build: | |
10 | noarch: python | |
11 | script: python setup.py install --single-version-externally-managed --record=record.txt | |
12 | ||
13 | requirements: | |
14 | build: | |
15 | - python | |
16 | - setuptools | |
17 | run: | |
18 | - python {{ sdata['python_requires'] }} | |
19 | ||
20 | test: | |
21 | requires: | |
22 | {% for dep in sdata['extras_require']['tests'] %} | |
23 | - {{ dep }} | |
24 | {% endfor %} | |
25 | source_files: | |
26 | # for nose config | |
27 | - setup.cfg | |
28 | - tests | |
29 | imports: | |
30 | - param | |
31 | - numbergen | |
32 | commands: | |
33 | # https://github.com/ioam/param/issues/219 | |
34 | - nosetests tests | |
35 | ||
36 | about: | |
37 | home: {{ sdata['url'] }} | |
38 | summary: {{ sdata['description'] }} | |
39 | license: {{ sdata['license'] }} |
0 | # -*- coding: utf-8 -*- | |
1 | ||
2 | from nbsite.shared_conf import * | |
3 | ||
4 | project = u'Param' | |
5 | authors = u'PyViz authors' | |
6 | copyright = u'\u00a9 2005-2018, ' + authors | |
7 | description = 'Declarative Python programming using Parameters.' | |
8 | ||
9 | import param | |
10 | version = release = param.__version__ | |
11 | ||
12 | html_static_path += ['_static'] | |
13 | html_theme = 'sphinx_ioam_theme' | |
14 | html_theme_options = { | |
15 | 'logo':'logo.png', | |
16 | 'favicon':'favicon.ico', | |
17 | # 'css':'site.css' | |
18 | } | |
19 | ||
20 | _NAV = ( | |
21 | ('API', 'Reference_Manual/param'), | |
22 | ('About', 'About'), | |
23 | ) | |
24 | ||
25 | html_context.update({ | |
26 | 'PROJECT': project, | |
27 | 'DESCRIPTION': description, | |
28 | 'AUTHOR': authors, | |
29 | # canonical URL (for search engines); can ignore for local builds | |
30 | 'WEBSITE_SERVER': 'https://param.pyviz.org', | |
31 | 'VERSION': version, | |
32 | 'NAV': _NAV, | |
33 | 'LINKS': _NAV, | |
34 | 'SOCIAL': ( | |
35 | ('Gitter', '//gitter.im/pyviz/pyviz'), | |
36 | ('Github', '//github.com/ioam/param'), | |
37 | ) | |
38 | }) |
0 | ************************ | |
1 | Historical release notes | |
2 | ************************ | |
3 | ||
4 | Note: current release notes are on `GitHub | |
5 | <https://github.com/ioam/param/releases>`_. | |
6 | ||
7 | Notable additions, or changes that may require users to alter code, | |
8 | are listed below. | |
9 | ||
10 | ||
11 | 1.4.1 (2016/07) | |
12 | _______________ | |
13 | ||
14 | * Selector parameters now respect order of options supplied | |
15 | * Allowed softbounds to be accessed like an attribute | |
16 | ||
17 | A full list of changes since the previous release is available | |
18 | `on GitHub <https://github.com/ioam/param/compare/v1.4.0...v1.4.1>`_. | |
19 | ||
20 | ||
21 | 1.4.0 (2016/07) | |
22 | _______________ | |
23 | ||
24 | * Added support for new `ParamNB <https://github.com/ioam/paramnb>`_ project | |
25 | * Added new parameter types Action, FileSelector, and ListSelector | |
26 | ||
27 | A full list of changes since the previous release is available | |
28 | `on GitHub <https://github.com/ioam/param/compare/v1.3.2...v1.4.0>`_. | |
29 | ||
30 | ||
31 | 1.3.2 (2015/04) | |
32 | _______________ | |
33 | ||
34 | * Added Unicode support for param.String. | |
35 | * Minor bugfixes. | |
36 | ||
37 | A full list of changes since the previous release is available | |
38 | `on GitHub <https://github.com/ioam/param/compare/v1.3.1...v1.3.2>`_. | |
39 | ||
40 | ||
41 | 1.3.1 (2015/03) | |
42 | _______________ | |
43 | ||
44 | * Minor bugfix release to restore pre-1.3.0 script_repr behavior | |
45 | (accidentally changed in 1.3.0) and to fix issues with logging. | |
46 | * Param's logging interface now matches that of Python's logging | |
47 | module, making it simpler to use logging (see Python's logging | |
48 | module for details). Note therefore that Param's logging methods (a) | |
49 | no longer call functions that are passed as arguments (instead, | |
50 | Python's logging module does lazy string merges), and (b) no longer | |
51 | automatically combine strings passed as arguments (instead, Python's | |
52 | logging module supports string formatting). | |
53 | * Improved set_param() method, now allowing multiple parameters to be | |
54 | set easily via keyword arguments (as on initialization). | |
55 | ||
56 | A full list of changes since the previous release is available | |
57 | `on GitHub <https://github.com/ioam/param/compare/v1.3.0...v1.3.1>`_. | |
58 | ||
59 | ||
60 | 1.3.0 (2015/03) | |
61 | _______________ | |
62 | ||
63 | * Added 'allow_None' support to all Parameters. Any subclass of | |
64 | Parameter that checks types and/or values should be modified to add | |
65 | appropriate handling of allow_None. | |
66 | * Improved pretty printing (script_repr) of Parameterized instances, | |
67 | and made available via the pprint method. The script_repr name will | |
68 | be removed in a future release. | |
69 | * Added (reproducible) time-dependent random streams | |
70 | (numbergen.TimeAwareRandomState). | |
71 | * Added label and unit parameters to param.Time class. | |
72 | * Improved optional IPython extension. | |
73 | ||
74 | A full list of changes since the previous release is available | |
75 | `on GitHub <https://github.com/ioam/param/compare/v1.2.1...v1.3.0>`_. | |
76 | ||
77 | ||
78 | 1.2.1 (2014/06) | |
79 | _______________ | |
80 | ||
81 | * Minor bugfix release to fix issues with version when param is | |
82 | installed in a foreign git repository | |
83 | * Made version module optional | |
84 | * Improved ClassSelector and ParamOverrides | |
85 | ||
86 | A full list of changes since the previous release is available | |
87 | `on GitHub <https://github.com/ioam/param/compare/v1.2.0...v1.2.1>`_. | |
88 | ||
89 | ||
90 | 1.2.0 (2014/06) | |
91 | _______________ | |
92 | ||
93 | * Added support for Python 3 (thanks to Marco Elver). | |
94 | * Dropped support for Python 2.5. | |
95 | * Added version module. | |
96 | * Added optional numbergen package. | |
97 | ||
98 | A full list of changes since the previous release is available | |
99 | `on GitHub <https://github.com/ioam/param/compare/v1.1.0...v1.2.0>`_. | |
100 | ||
101 | ||
102 | 1.1.0 (2014/05) | |
103 | _______________ | |
104 | ||
105 | * Switched to Python's own logging module. | |
106 | * Improved support for time when using Dynamic parameters. | |
107 | * Optional extension for IPython users. | |
108 | ||
109 | A full list of changes since the previous release is available | |
110 | `on GitHub <https://github.com/ioam/param/compare/v1.0.0...v1.1.0>`_. | |
111 | ||
112 | ||
113 | 1.0.0 (2012/07) | |
114 | _______________ | |
115 | ||
116 | * First standalone release. | |
117 | ||
118 | ||
119 | Pre-1.0 (2003) | |
120 | ______________ | |
121 | ||
122 | * Param was originally developed as part of `Topographica | |
123 | <http://ioam.github.io/topographica/>`_, and has been in heavy | |
124 | usage as part of that project since 2003. |
0 | .. | |
1 | Originally generated by nbsite (0.4.4a13+gdbf7de7-dirty): | |
2 | nbsite generate-rst --org ioam --project param --repo param --examples-path examples --doc-path doc | |
3 | Will not subsequently be overwritten by nbsite, so can be edited. | |
4 | ||
5 | ***** | |
6 | Param | |
7 | ***** | |
8 | ||
9 | .. notebook:: param ../examples/index.ipynb | |
10 | :offset: 0 | |
11 | ||
12 | .. toctree:: | |
13 | :titlesonly: | |
14 | :maxdepth: 2 | |
15 | ||
16 | Introduction <self> | |
17 | API <Reference_Manual/param> | |
18 | About <About> | |
19 |
0 | { | |
1 | "cells": [ | |
2 | { | |
3 | "cell_type": "markdown", | |
4 | "metadata": {}, | |
5 | "source": [ | |
6 | "Param is part of [PyViz](http://pyviz.org), a collaborative project to produce a coherent solution to a wide range of Python visualization problems." | |
7 | ] | |
8 | } | |
9 | ], | |
10 | "metadata": { | |
11 | "language_info": { | |
12 | "name": "python", | |
13 | "pygments_lexer": "ipython3" | |
14 | } | |
15 | }, | |
16 | "nbformat": 4, | |
17 | "nbformat_minor": 2 | |
18 | } |
0 | { | |
1 | "cells": [ | |
2 | { | |
3 | "cell_type": "markdown", | |
4 | "metadata": {}, | |
5 | "source": [ | |
6 | "Param is a library providing Parameters: Python attributes extended to\n", | |
7 | "have features such as type and range checking, dynamically generated\n", | |
8 | "values, documentation strings, default values, etc., each of which is\n", | |
9 | "inherited from parent classes if not specified in a subclass. Param\n", | |
10 | "lets you program declaratively in Python, by just stating facts about\n", | |
11 | "each of your parameters, and then using them throughout your code.\n", | |
12 | "With Parameters, error checking will be automatic, which eliminates\n", | |
13 | "huge amounts of boilerplate code that would otherwise be required to\n", | |
14 | "verify or test user-supplied values.\n", | |
15 | "\n", | |
16 | "Param-based programs tend to contain much less code than other Python\n", | |
17 | "programs, instead just having easily readable and maintainable\n", | |
18 | "manifests of Parameters for each object or function. This way your\n", | |
19 | "remaining code can be much simpler and clearer, while users can also\n", | |
20 | "easily see how to use it properly." | |
21 | ] | |
22 | }, | |
23 | { | |
24 | "cell_type": "markdown", | |
25 | "metadata": {}, | |
26 | "source": [ | |
27 | "# What is a Parameter?\n", | |
28 | "\n", | |
29 | "A Parameter is a special type of Python attribute 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.\n", | |
30 | "\n", | |
31 | "```python\n", | |
32 | ">>> import param,random\n", | |
33 | ">>> class A(param.Parameterized):\n", | |
34 | "... a = param.Number(0.5,bounds=(0,1),doc=\"Probability that...\")\n", | |
35 | "... b = param.Boolean(False,doc=\"Enable feature...\")\n", | |
36 | "\n", | |
37 | ">>> class B(A):\n", | |
38 | "... b = param.Boolean(True)\n", | |
39 | "\n", | |
40 | ">>> x = B(a=lambda: random.uniform(0,1))\n", | |
41 | "\n", | |
42 | ">>> x.a\n", | |
43 | "0.37053399325641945\n", | |
44 | "\n", | |
45 | ">>> x.a\n", | |
46 | "0.64907392300071842\n", | |
47 | "```\n", | |
48 | "\n", | |
49 | "## Parameters provide optional range and type checking\n", | |
50 | "\n", | |
51 | "```python\n", | |
52 | ">>> x.a=5\n", | |
53 | "[...]\n", | |
54 | "ValueError: Parameter 'a' must be at most 1\n", | |
55 | "\n", | |
56 | ">>> x.a=\"0.5\"\n", | |
57 | "[...]\n", | |
58 | "ValueError: Parameter 'a' only takes numeric values\n", | |
59 | "```\n", | |
60 | "\n", | |
61 | "## Parameters have docstrings\n", | |
62 | "\n", | |
63 | "```python\n", | |
64 | ">>> help(x)\n", | |
65 | "[...]\n", | |
66 | "class B(A)\n", | |
67 | "[...]\n", | |
68 | " Data descriptors defined here:\n", | |
69 | " b\n", | |
70 | " Enable feature...\n", | |
71 | "[...]\n", | |
72 | " Data descriptors inherited from A:\n", | |
73 | " a\n", | |
74 | " Probability that...\n", | |
75 | "```\n", | |
76 | "\n", | |
77 | "## Param is lightweight\n", | |
78 | "\n", | |
79 | "Param consists of two required BSD-licensed Python files, with no\n", | |
80 | "dependencies outside of the standard library, and so it can easily be\n", | |
81 | "included as part of larger projects without adding external dependencies.\n", | |
82 | "\n", | |
83 | "\n", | |
84 | "## Parameters make GUI programming simpler\n", | |
85 | "\n", | |
86 | "Parameters make it simple to generate GUIs by separating your semantic\n", | |
87 | "information (what is this parameter? what type can it have? does it\n", | |
88 | "have bounds?) from anything to do with a particular GUI library. To\n", | |
89 | "use Parameters in a particular GUI toolkit, you just need to write a\n", | |
90 | "simple set of interfaces that indicate how a given Parameter type\n", | |
91 | "should be displayed, and what widgets to generate for it. Currently,\n", | |
92 | "interfaces are provided for use in Jupyter Notebooks ([ParamNB]\n", | |
93 | "(https://github.com/ioam/paramnb)) \n", | |
94 | "or in Tk ([ParamTk](http://ioam.github.com/paramtk)), both of which\n", | |
95 | "make it simple to provide a property sheet that automatically\n", | |
96 | "generates a set of widgets for viewing and editing an object's\n", | |
97 | "Parameters.\n", | |
98 | "\n", | |
99 | "\n", | |
100 | "## Optional dynamic parameter values using `numbergen`\n", | |
101 | "\n", | |
102 | "Providing random or other types of varying values for parameters can\n", | |
103 | "be tricky, because unnamed (\"lambda\") functions as used above cannot\n", | |
104 | "easily be pickled, causing problems for people who wish to store\n", | |
105 | "Parameterized objects containing random state. To avoid users having\n", | |
106 | "to write a separate function for each random value, Param includes an\n", | |
107 | "optional set of value-generating objects that are easily configured\n", | |
108 | "and support pickling. These objects are available if you import the\n", | |
109 | "optional `numbergen` module. If you wish to use numbergen, the above\n", | |
110 | "example can be rewritten as:\n", | |
111 | "\n", | |
112 | "```python\n", | |
113 | ">>> import param,numbergen\n", | |
114 | ">>> class A(param.Parameterized):\n", | |
115 | "... a = param.Number(0.5,bounds=(0,1),doc=\"Probability that...\")\n", | |
116 | "... b = param.Boolean(False,doc=\"Enable feature...\")\n", | |
117 | "\n", | |
118 | ">>> class B(A):\n", | |
119 | "... b = param.Boolean(True)\n", | |
120 | "\n", | |
121 | ">>> x = B(a=numbergen.UniformRandom())\n", | |
122 | "``` \n", | |
123 | "\n", | |
124 | "Numbergen objects support the usual arithmetic operations like `+`, `-`,\n", | |
125 | "`*`, `/`, `//`, `%`, `**`, and `abs()`, and so they can be freely combined with\n", | |
126 | "each other or with mathematical constants:\n", | |
127 | "\n", | |
128 | "```python\n", | |
129 | ">>> y = B(a=2.0*numbergen.UniformRandom()/(numbergen.NormalRandom()+1.5))\n", | |
130 | "```\n", | |
131 | "\n", | |
132 | "Note that unlike the lambda-function approach, all varying numbergen\n", | |
133 | "objects respect `param.Dynamic.time_fn`, e.g. to ensure that new\n", | |
134 | "values will be generated only when Param's time has changed. \n", | |
135 | "Parameterized programs can define a time function to maintain a\n", | |
136 | "logical/simulated time, such as the state of a simulator, which\n", | |
137 | "allows all Parameter values to be kept synchronized without\n", | |
138 | "any special coordination code.\n", | |
139 | "\n" | |
140 | ] | |
141 | }, | |
142 | { | |
143 | "cell_type": "markdown", | |
144 | "metadata": {}, | |
145 | "source": [ | |
146 | "# Installation\n", | |
147 | "\n", | |
148 | "Param has no required dependencies outside of Python's standard\n", | |
149 | "library.\n", | |
150 | "\n", | |
151 | "Official releases of Param are available on\n", | |
152 | "[Anaconda](https://anaconda.org/ioam/param) and\n", | |
153 | "[PyPI](http://pypi.python.org/pypi/param), and can be installed via\n", | |
154 | "`conda install -c ioam param`, `pip install --user param`, or \n", | |
155 | "`pip install param`.\n", | |
156 | "\n", | |
157 | "The very latest changes can be obtained via `conda install -c pyviz/label/dev\n", | |
158 | "param` or `pip install\n", | |
159 | "https://github.com/ioam/param/archive/master.zip`.\n", | |
160 | "\n", | |
161 | "For development, the [git repository](http://github.com/ioam/param)\n", | |
162 | "can be cloned and then 'develop installed' (`pip install -e .` or\n", | |
163 | "`python setup.py develop`). Tests can be run via [tox]\n", | |
164 | "(https://tox.readthedocs.io/en/latest/): `tox` for all tests, or\n", | |
165 | "e.g. `tox -e coverage` to run unit tests with coverage for the\n", | |
166 | "currently active python. Alternatively, unit tests can be run via\n", | |
167 | "`nosetests` (after installing [nose](http://nose.readthedocs.io/en/latest)." | |
168 | ] | |
169 | }, | |
170 | { | |
171 | "cell_type": "markdown", | |
172 | "metadata": {}, | |
173 | "source": [ | |
174 | "# Comparison to other packages\n", | |
175 | "\n", | |
176 | "Param was first developed in 2003, in the context of the Topographica brain simulator project, and\n", | |
177 | "was made into a separate package in 2012. In the interim other parameter libraries were\n", | |
178 | "developed, including [Traits](http://code.enthought.com/projects/traits) and \n", | |
179 | "[Traitlets](https://github.com/ipython/traitlets/). These libraries have broadly similar goals,\n", | |
180 | "but each differs in important ways:\n", | |
181 | "\n", | |
182 | "**Dependencies**: \n", | |
183 | " Traits is a much more heavyweight solution, requiring \n", | |
184 | " installation of a large suite of tools, including C code, which makes it difficult to include in \n", | |
185 | " separate projects. In contrast, Param and Traitlets are both pure Python projects, with minimal dependencies. \n", | |
186 | "\n", | |
187 | "**GUI toolkits**: \n", | |
188 | " Although any of the packages could in principle add support for any\n", | |
189 | " GUI toolkit, the toolkits actually provided differ: Traits (via the\n", | |
190 | " separate TraitsUI package) supports wxWidgets and QT, while Param\n", | |
191 | " supports Tkinter (via the separate ParamTk package) and\n", | |
192 | " browser-based IPython widgets (via the separate ParamNB package),\n", | |
193 | " while Traitlets only supports IPython widgets.\n", | |
194 | "\n", | |
195 | " ```python\n", | |
196 | " >>> from time import time\n", | |
197 | " >>> import traitlets as tr\n", | |
198 | " >>> class A(tr.HasTraits):\n", | |
199 | " ... instantiation_time = tr.Float()\n", | |
200 | " ... @tr.default('instantiation_time')\n", | |
201 | " ... def _look_up_time(self):\n", | |
202 | " ... return time()\n", | |
203 | " ... \n", | |
204 | " >>> a=A()\n", | |
205 | " >>> a.instantiation_time\n", | |
206 | " 1475587151.967874\n", | |
207 | " >>> a.instantiation_time\n", | |
208 | " 1475587151.967874\n", | |
209 | " >>> b=A()\n", | |
210 | " >>> b.instantiation_time\n", | |
211 | " 1475587164.750875\n", | |
212 | " ```\n", | |
213 | "\n", | |
214 | "**Dynamic values**:\n", | |
215 | " Param, Traits, and Traitlets all allow any Python expression to be\n", | |
216 | " supplied for initializing parameters, allowing parameter default\n", | |
217 | " values to be computed at the time a module is first loaded. Traits\n", | |
218 | " and Traitlets also allow a class author to add code for a given\n", | |
219 | " parameter to compute a default value on first access. Param does\n", | |
220 | " not provide any special support for programmatic default values,\n", | |
221 | " instead allowing fully dynamic values for *any* numeric Parameter\n", | |
222 | " instance:\n", | |
223 | "\n", | |
224 | " ```python\n", | |
225 | " >>> from time import time\n", | |
226 | " >>> import param\n", | |
227 | " >>> class A(param.Parameterized):\n", | |
228 | " ... val=param.Number(0)\n", | |
229 | " ... \n", | |
230 | " >>> a=A()\n", | |
231 | " >>> a.val\n", | |
232 | " 0\n", | |
233 | " >>> a.val=lambda:time()\n", | |
234 | " >>> a.val\n", | |
235 | " 1475587455.437027\n", | |
236 | " >>> a.val\n", | |
237 | " 1475587456.501314\n", | |
238 | " ```\n", | |
239 | " \n", | |
240 | " Note that here it is the *user* of a Parameterized class, not the\n", | |
241 | " author of the class, that decides whether any particular value is\n", | |
242 | " dynamic, without writing any new methods or other code. All the\n", | |
243 | " usual type checking, etc. is done on dynamic values when they are\n", | |
244 | " computed, and so the rest of the code does not need to know or care\n", | |
245 | " whether the user has set a particular parameter to a dynamic value.\n", | |
246 | " This approach provides an enormous amount of power to the user,\n", | |
247 | " without making the code more complex.\n", | |
248 | "\n", | |
249 | "**On_change callbacks**\n", | |
250 | " Traitlets and Traits allow the author of a HasTraits-derived class\n", | |
251 | " to specify code to run when a specific parameter used in that class\n", | |
252 | " instance is modified. Param supports similar capabilities, but not\n", | |
253 | " at the Parameterized class level, only at the Parameter class level\n", | |
254 | " or as part of ParamNB. I.e., a class author needs to first write a\n", | |
255 | " new Parameter class, adding methods to implement checking on\n", | |
256 | " changes, and then add it to a Parameterized class, or else such\n", | |
257 | " functionality can be added as callbacks at the whole-object level,\n", | |
258 | " using ParamNB. Each approach has advantages and disadvantages, and\n", | |
259 | " per-parameter on_change callbacks could be added in the future if\n", | |
260 | " there are clear use cases.\n", | |
261 | "\n", | |
262 | "All of these packages also overlap in functionality with Python\n", | |
263 | "properties, which were added to the language after Traits and Param\n", | |
264 | "were developed. Like parameters and traits, properties act like\n", | |
265 | "attributes with possible method-like actions, and so they can all be\n", | |
266 | "used to provide the same user-visible functionality. However,\n", | |
267 | "implementing Param/Traits-like functionality using properties would\n", | |
268 | "require vastly more code (multiple method definitions for *every*\n", | |
269 | "parameter in a class), and so in practice Parameters and Traits are\n", | |
270 | "much more practical for the use cases that they cover.\n", | |
271 | " " | |
272 | ] | |
273 | }, | |
274 | { | |
275 | "cell_type": "markdown", | |
276 | "metadata": {}, | |
277 | "source": [ | |
278 | "# Release notes\n", | |
279 | "\n", | |
280 | "Recent release notes are available on [GitHub](https://github.com/ioam/param/releases).\n", | |
281 | "\n", | |
282 | "For older releases, see our [historical release notes](historical_release_notes.html).\n", | |
283 | "\n", | |
284 | "\n", | |
285 | "# Support\n", | |
286 | "\n", | |
287 | "Questions and comments are welcome at https://github.com/ioam/param/issues." | |
288 | ] | |
289 | } | |
290 | ], | |
291 | "metadata": { | |
292 | "language_info": { | |
293 | "name": "python", | |
294 | "pygments_lexer": "ipython3" | |
295 | } | |
296 | }, | |
297 | "nbformat": 4, | |
298 | "nbformat_minor": 2 | |
299 | } |
119 | 119 | def __abs__ (self): return UnaryOperator(self,operator.abs) |
120 | 120 | |
121 | 121 | |
122 | operator_symbols = { | |
123 | operator.add:'+', | |
124 | operator.sub:'-', | |
125 | operator.mul:'*', | |
126 | operator.mod:'%', | |
127 | operator.pow:'**', | |
128 | operator.truediv:'/', | |
129 | operator.floordiv:'//', | |
130 | operator.neg:'-', | |
131 | operator.pos:'+', | |
132 | operator.abs:'abs', | |
133 | } | |
134 | ||
135 | def pprint(x, *args, **kwargs): | |
136 | "Pretty-print the provided item, translating operators to their symbols" | |
137 | return x.pprint(*args, **kwargs) if hasattr(x,'pprint') else operator_symbols.get(x, repr(x)) | |
138 | ||
122 | 139 | |
123 | 140 | class BinaryOperator(NumberGenerator): |
124 | 141 | """Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator.""" |
147 | 164 | return self.operator(self.lhs() if callable(self.lhs) else self.lhs, |
148 | 165 | self.rhs() if callable(self.rhs) else self.rhs, **self.args) |
149 | 166 | |
167 | def pprint(self, *args, **kwargs): | |
168 | return (pprint(self.lhs, *args, **kwargs) + | |
169 | pprint(self.operator, *args, **kwargs) + | |
170 | pprint(self.rhs, *args, **kwargs)) | |
150 | 171 | |
151 | 172 | |
152 | 173 | class UnaryOperator(NumberGenerator): |
170 | 191 | def __call__(self): |
171 | 192 | return self.operator(self.operand(),**self.args) |
172 | 193 | |
194 | def pprint(self, *args, **kwargs): | |
195 | return (pprint(self.operator, *args, **kwargs) + '(' + | |
196 | pprint(self.operand, *args, **kwargs) + ')') | |
173 | 197 | |
174 | 198 | |
175 | 199 | class Hash(object): |
201 | 225 | |
202 | 226 | I32 = 4294967296 # Maximum 32 bit unsigned int (i.e. 'I') value |
203 | 227 | if isinstance(val, int): |
204 | numer, denom = val, 1 | |
228 | numer, denom = val, 1 | |
205 | 229 | elif isinstance(val, fractions.Fraction): |
206 | 230 | numer, denom = val.numerator, val.denominator |
207 | 231 | elif hasattr(val, 'numer'): |
208 | 232 | (numer, denom) = (int(val.numer()), int(val.denom())) |
209 | 233 | else: |
210 | param.main.param.warning("Casting type '%s' to Fraction.fraction" | |
234 | param.main.param.log(param.WARNING, "Casting type '%s' to Fraction.fraction" | |
211 | 235 | % type(val).__name__) |
212 | 236 | frac = fractions.Fraction(str(val)) |
213 | 237 | numer, denom = frac.numerator, frac.denominator |
341 | 365 | """ |
342 | 366 | Warn if the object name is not explicitly set. |
343 | 367 | """ |
344 | changed_params = dict(self.param.get_param_values(onlychanged=True)) | |
368 | changed_params = self.param.values(onlychanged=True) | |
345 | 369 | if self.time_dependent and ('name' not in changed_params): |
346 | self.param.warning("Default object name used to set the seed: " | |
347 | "random values conditional on object instantiation order.") | |
370 | self.param.log(param.WARNING, "Default object name used to set the seed: " | |
371 | "random values conditional on object instantiation order.") | |
348 | 372 | |
349 | 373 | def _hash_and_seed(self): |
350 | 374 | """ |
414 | 438 | self._hash_and_seed() |
415 | 439 | |
416 | 440 | |
417 | ||
418 | 441 | class UniformRandom(RandomDistribution): |
419 | 442 | """ |
420 | 443 | Specified with lbound and ubound; when called, return a random |
0 | {"version_string": "None"}⏎ |
0 | from __future__ import print_function | |
0 | 1 | """ |
1 | 2 | Parameters are a kind of class attribute allowing special behavior, |
2 | 3 | including dynamically generated parameter values, documentation |
27 | 28 | Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides, |
28 | 29 | descendents, get_logger, instance_descriptor, basestring) |
29 | 30 | |
30 | from .parameterized import (batch_watch, depends, output, # noqa: api import | |
31 | discard_events, edit_constant) | |
31 | from .parameterized import (batch_watch, depends, output, script_repr, # noqa: api import | |
32 | discard_events, edit_constant, instance_descriptor) | |
33 | from .parameterized import shared_parameters # noqa: api import | |
32 | 34 | from .parameterized import logging_level # noqa: api import |
33 | from .parameterized import shared_parameters # noqa: api import | |
35 | from .parameterized import DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL # noqa: api import | |
34 | 36 | |
35 | 37 | from collections import OrderedDict |
36 | 38 | from numbers import Real |
40 | 42 | # only two required files. |
41 | 43 | try: |
42 | 44 | from .version import Version |
43 | __version__ = str(Version(fpath=__file__, archive_commit="9123ba0", reponame="param")) | |
45 | __version__ = str(Version(fpath=__file__, archive_commit="$Format:%h$", reponame="param")) | |
44 | 46 | except: |
45 | 47 | __version__ = "0.0.0+unknown" |
46 | 48 | |
68 | 70 | |
69 | 71 | |
70 | 72 | # A global random seed (integer or rational) available for controlling |
71 | # the behaviour of parameterized objects with random state. | |
73 | # the behaviour of Parameterized objects with random state. | |
72 | 74 | random_seed = 42 |
73 | 75 | |
74 | 76 | |
220 | 222 | supplied parameters, inheriting from the specified base(s). |
221 | 223 | """ |
222 | 224 | if not (isinstance(bases, list) or isinstance(bases, tuple)): |
223 | bases=[bases] | |
225 | bases=[bases] | |
224 | 226 | return type(name, tuple(bases), params) |
225 | 227 | |
226 | 228 | |
467 | 469 | raise StopIteration |
468 | 470 | return self._time |
469 | 471 | |
470 | # For Python 2 compatibility; can be removed for Python 3. | |
472 | # PARAM2_DEPRECATION: For Python 2 compatibility; can be removed for Python 3. | |
471 | 473 | next = __next__ |
472 | 474 | |
473 | 475 | def __call__(self, val=None, time_type=None): |
563 | 565 | time_fn = Time() |
564 | 566 | time_dependent = False |
565 | 567 | |
566 | # CBENHANCEMENT: Add an 'epsilon' slot. | |
567 | # See email 'Re: simulation-time-controlled Dynamic parameters' | |
568 | # Dec 22, 2007 CB->JAB | |
569 | ||
570 | 568 | def __init__(self,**params): |
571 | 569 | """ |
572 | 570 | Call the superclass's __init__ and set instantiate=True if the |
583 | 581 | """ |
584 | 582 | Add 'last time' and 'last value' attributes to the generator. |
585 | 583 | """ |
586 | # CEBALERT: use a dictionary to hold these things. | |
584 | # Could use a dictionary to hold these things. | |
587 | 585 | if hasattr(obj,"_Dynamic_time_fn"): |
588 | 586 | gen._Dynamic_time_fn = obj._Dynamic_time_fn |
589 | 587 | |
590 | 588 | gen._Dynamic_last = None |
591 | # CEB: I'd use None for this, except can't compare a fixedpoint | |
589 | # Would have usede None for this, but can't compare a fixedpoint | |
592 | 590 | # number with None (e.g. 1>None but FixedPoint(1)>None can't be done) |
593 | 591 | gen._Dynamic_time = -1 |
594 | 592 | |
702 | 700 | |
703 | 701 | def identity_hook(obj,val): return val |
704 | 702 | |
703 | def get_soft_bounds(bounds, softbounds): | |
704 | """ | |
705 | For each soft bound (upper and lower), if there is a defined bound | |
706 | (not equal to None) and does not exceed the hard bound, then it is | |
707 | returned. Otherwise it defaults to the hard bound. The hard bound | |
708 | could still be None. | |
709 | """ | |
710 | if bounds is None: | |
711 | hl, hu = (None, None) | |
712 | else: | |
713 | hl, hu = bounds | |
714 | ||
715 | if softbounds is None: | |
716 | sl, su = (None, None) | |
717 | else: | |
718 | sl, su = softbounds | |
719 | ||
720 | if sl is None or (hl is not None and sl<hl): | |
721 | l = hl | |
722 | else: | |
723 | l = sl | |
724 | ||
725 | if su is None or (hu is not None and su>hu): | |
726 | u = hu | |
727 | else: | |
728 | u = su | |
729 | ||
730 | return (l, u) | |
705 | 731 | |
706 | 732 | |
707 | 733 | class Number(Dynamic): |
749 | 775 | |
750 | 776 | """ |
751 | 777 | |
752 | __slots__ = ['bounds','_softbounds','inclusive_bounds','set_hook', 'step'] | |
753 | ||
754 | def __init__(self,default=0.0,bounds=None,softbounds=None, | |
778 | __slots__ = ['bounds', 'softbounds', 'inclusive_bounds', 'set_hook', 'step'] | |
779 | ||
780 | def __init__(self, default=0.0, bounds=None, softbounds=None, | |
755 | 781 | inclusive_bounds=(True,True), step=None, **params): |
756 | 782 | """ |
757 | 783 | Initialize this parameter object and store the bounds. |
758 | 784 | |
759 | 785 | Non-dynamic default values are checked against the bounds. |
760 | 786 | """ |
761 | super(Number,self).__init__(default=default,**params) | |
787 | super(Number,self).__init__(default=default, **params) | |
762 | 788 | |
763 | 789 | self.set_hook = identity_hook |
764 | 790 | self.bounds = bounds |
765 | 791 | self.inclusive_bounds = inclusive_bounds |
766 | self._softbounds = softbounds | |
792 | self.softbounds = softbounds | |
767 | 793 | self.step = step |
768 | 794 | self._validate(default) |
769 | 795 | |
770 | ||
771 | def __get__(self,obj,objtype): | |
796 | def __get__(self, obj, objtype): | |
772 | 797 | """ |
773 | 798 | Same as the superclass's __get__, but if the value was |
774 | 799 | dynamically generated, check the bounds. |
775 | 800 | """ |
776 | result = super(Number,self).__get__(obj,objtype) | |
777 | # CEBALERT: results in extra lookups (_value_is_dynamic() is | |
778 | # also looking up 'result' - should just pass it in). Note | |
779 | # that this method is called often. | |
780 | if self._value_is_dynamic(obj,objtype): self._validate(result) | |
801 | result = super(Number, self).__get__(obj, objtype) | |
802 | # Should be able to optimize this commonly used method by | |
803 | # avoiding extra lookups (e.g. _value_is_dynamic() is also | |
804 | # looking up 'result' - should just pass it in). | |
805 | if self._value_is_dynamic(obj, objtype): | |
806 | self._validate(result) | |
781 | 807 | return result |
782 | ||
783 | # Allow softbounds to be used like a normal attribute, as it | |
784 | # probably should have been already (not _softbounds) | |
785 | @property | |
786 | def softbounds(self): return self._softbounds | |
787 | ||
788 | @softbounds.setter | |
789 | def softbounds(self,value): self._softbounds = value | |
790 | ||
791 | 808 | |
792 | 809 | def set_in_bounds(self,obj,val): |
793 | 810 | """ |
799 | 816 | bounded_val = self.crop_to_bounds(val) |
800 | 817 | else: |
801 | 818 | bounded_val = val |
802 | super(Number,self).__set__(obj,bounded_val) | |
803 | ||
804 | ||
805 | # CEBERRORALERT: doesn't take account of exclusive bounds; see | |
806 | # https://github.com/ioam/param/issues/80. | |
807 | def crop_to_bounds(self,val): | |
819 | super(Number, self).__set__(obj, bounded_val) | |
820 | ||
821 | def crop_to_bounds(self, val): | |
808 | 822 | """ |
809 | 823 | Return the given value cropped to be within the hard bounds |
810 | 824 | for this parameter. |
815 | 829 | returned value could be None. If a non-numeric value is passed |
816 | 830 | in, set to be the default value (which could be None). In no |
817 | 831 | case is an exception raised; all values are accepted. |
818 | """ | |
819 | # Currently, values outside the bounds are silently cropped to | |
820 | # be inside the bounds; it may be appropriate to add a warning | |
821 | # in such cases. | |
832 | ||
833 | As documented in https://github.com/holoviz/param/issues/80, | |
834 | currently does not respect exclusive bounds, which would | |
835 | strictly require setting to one less for integer values or | |
836 | an epsilon less for floats. | |
837 | """ | |
838 | # Values outside the bounds are silently cropped to | |
839 | # be inside the bounds. | |
822 | 840 | if _is_number(val): |
823 | 841 | if self.bounds is None: |
824 | 842 | return val |
836 | 854 | |
837 | 855 | else: |
838 | 856 | # non-numeric value sent in: reverts to default value |
839 | return self.default | |
857 | return self.default | |
840 | 858 | |
841 | 859 | return val |
842 | 860 | |
843 | ||
844 | def _checkBounds(self, val): | |
845 | ||
846 | if self.bounds is not None: | |
847 | vmin,vmax = self.bounds | |
848 | incmin,incmax = self.inclusive_bounds | |
849 | ||
850 | # Could simplify: see https://github.com/ioam/param/issues/83 | |
851 | if vmax is not None: | |
852 | if incmax is True: | |
853 | if not val <= vmax: | |
854 | raise ValueError("Parameter '%s' must be at most %s"%(self.name,vmax)) | |
855 | else: | |
856 | if not val < vmax: | |
857 | raise ValueError("Parameter '%s' must be less than %s"%(self.name,vmax)) | |
858 | ||
859 | if vmin is not None: | |
860 | if incmin is True: | |
861 | if not val >= vmin: | |
862 | raise ValueError("Parameter '%s' must be at least %s"%(self.name,vmin)) | |
863 | else: | |
864 | if not val > vmin: | |
865 | raise ValueError("Parameter '%s' must be greater than %s"%(self.name,vmin)) | |
866 | ||
867 | ||
861 | def _validate_bounds(self, val, bounds, inclusive_bounds): | |
862 | if bounds is None or (val is None and self.allow_None) or callable(val): | |
863 | return | |
864 | vmin, vmax = bounds | |
865 | incmin, incmax = inclusive_bounds | |
866 | if vmax is not None: | |
867 | if incmax is True: | |
868 | if not val <= vmax: | |
869 | raise ValueError("Parameter %r must be at most %s, " | |
870 | "not %s." % (self.name, vmax, val)) | |
871 | else: | |
872 | if not val < vmax: | |
873 | raise ValueError("Parameter %r must be less than %s, " | |
874 | "not %s." % (self.name, vmax, val)) | |
875 | ||
876 | if vmin is not None: | |
877 | if incmin is True: | |
878 | if not val >= vmin: | |
879 | raise ValueError("Parameter %r must be at least %s, " | |
880 | "not %s." % (self.name, vmin, val)) | |
881 | else: | |
882 | if not val > vmin: | |
883 | raise ValueError("Parameter %r must be greater than %s, " | |
884 | "not %s." % (self.name, vmin, val)) | |
885 | ||
886 | def _validate_value(self, val, allow_None): | |
887 | if (allow_None and val is None) or callable(val): | |
888 | return | |
889 | ||
890 | if not _is_number(val): | |
891 | raise ValueError("Parameter %r only takes numeric values, " | |
892 | "not type %r." % (self.name, type(val))) | |
893 | ||
894 | def _validate_step(self, val, step): | |
895 | if step is not None and not _is_number(step): | |
896 | raise ValueError("Step can only be None or a " | |
897 | "numeric value, not type %r." % type(step)) | |
868 | 898 | |
869 | 899 | def _validate(self, val): |
870 | 900 | """ |
871 | 901 | Checks that the value is numeric and that it is within the hard |
872 | 902 | bounds; if not, an exception is raised. |
873 | 903 | """ |
874 | if callable(val): | |
875 | return val | |
876 | ||
877 | if self.allow_None and val is None: | |
878 | return | |
879 | ||
880 | if not _is_number(val): | |
881 | raise ValueError("Parameter '%s' only takes numeric values"%(self.name)) | |
882 | ||
883 | if self.step is not None and not _is_number(self.step): | |
884 | raise ValueError("Step parameter can only be None or a numeric value") | |
885 | ||
886 | self._checkBounds(val) | |
887 | ||
904 | self._validate_value(val, self.allow_None) | |
905 | self._validate_step(val, self.step) | |
906 | self._validate_bounds(val, self.bounds, self.inclusive_bounds) | |
888 | 907 | |
889 | 908 | def get_soft_bounds(self): |
890 | """ | |
891 | For each soft bound (upper and lower), if there is a defined bound (not equal to None) | |
892 | then it is returned, otherwise it defaults to the hard bound. The hard bound could still be None. | |
893 | """ | |
894 | if self.bounds is None: | |
895 | hl,hu=(None,None) | |
896 | else: | |
897 | hl,hu=self.bounds | |
898 | ||
899 | if self._softbounds is None: | |
900 | sl,su=(None,None) | |
901 | else: | |
902 | sl,su=self._softbounds | |
903 | ||
904 | ||
905 | if sl is None: l = hl | |
906 | else: l = sl | |
907 | ||
908 | if su is None: u = hu | |
909 | else: u = su | |
910 | ||
911 | return (l,u) | |
912 | ||
909 | return get_soft_bounds(self.bounds, self.softbounds) | |
913 | 910 | |
914 | 911 | def __setstate__(self,state): |
915 | 912 | if 'step' not in state: |
916 | 913 | state['step'] = None |
917 | 914 | |
918 | super(Number,self).__setstate__(state) | |
915 | super(Number, self).__setstate__(state) | |
919 | 916 | |
920 | 917 | |
921 | 918 | |
922 | 919 | class Integer(Number): |
923 | 920 | """Numeric Parameter required to be an Integer""" |
924 | 921 | |
925 | def __init__(self,default=0,**params): | |
926 | Number.__init__(self,default=default,**params) | |
927 | ||
928 | def _validate(self, val): | |
929 | if callable(val): return | |
930 | ||
931 | if self.allow_None and val is None: | |
932 | return | |
933 | ||
934 | if not isinstance(val,int): | |
935 | raise ValueError("Parameter '%s' must be an integer."%self.name) | |
936 | ||
937 | if self.step is not None and not isinstance(self.step, int): | |
938 | raise ValueError("Step parameter can only be None or an integer value") | |
939 | ||
940 | ||
941 | self._checkBounds(val) | |
922 | def __init__(self, default=0, **params): | |
923 | Number.__init__(self, default=default, **params) | |
924 | ||
925 | def _validate_value(self, val, allow_None): | |
926 | if callable(val): | |
927 | return | |
928 | ||
929 | if allow_None and val is None: | |
930 | return | |
931 | ||
932 | if not isinstance(val, int): | |
933 | raise ValueError("Integer parameter %r must be an integer, " | |
934 | "not type %r." % (self.name, type(val))) | |
935 | ||
936 | def _validate_step(self, val, step): | |
937 | if step is not None and not isinstance(step, int): | |
938 | raise ValueError("Step can only be None or an " | |
939 | "integer value, not type %r" % type(step)) | |
942 | 940 | |
943 | 941 | |
944 | 942 | |
945 | 943 | class Magnitude(Number): |
946 | 944 | """Numeric Parameter required to be in the range [0.0-1.0].""" |
947 | 945 | |
948 | def __init__(self,default=1.0,softbounds=None,**params): | |
949 | Number.__init__(self,default=default,bounds=(0.0,1.0),softbounds=softbounds,**params) | |
946 | def __init__(self, default=1.0, softbounds=None, **params): | |
947 | Number.__init__(self, default=default, bounds=(0.0,1.0), softbounds=softbounds, **params) | |
950 | 948 | |
951 | 949 | |
952 | 950 | |
955 | 953 | |
956 | 954 | __slots__ = ['bounds'] |
957 | 955 | |
958 | # CB: bounds have no effect; see https://github.com/ioam/param/issues/82 | |
959 | def __init__(self,default=False,bounds=(0,1),**params): | |
956 | # Bounds are set for consistency and are arguably accurate, but have | |
957 | # no effect since values are either False, True, or None (if allowed). | |
958 | def __init__(self, default=False, bounds=(0,1), **params): | |
960 | 959 | self.bounds = bounds |
961 | super(Boolean, self).__init__(default=default,**params) | |
962 | ||
963 | def _validate(self, val): | |
964 | if self.allow_None: | |
965 | if not isinstance(val,bool) and val is not None: | |
966 | raise ValueError("Boolean '%s' only takes a Boolean value or None." | |
967 | %self.name) | |
968 | ||
969 | if val is not True and val is not False and val is not None: | |
970 | raise ValueError("Boolean '%s' must be True, False, or None."%self.name) | |
971 | else: | |
972 | if not isinstance(val,bool): | |
973 | raise ValueError("Boolean '%s' only takes a Boolean value."%self.name) | |
974 | ||
975 | if val is not True and val is not False: | |
976 | raise ValueError("Boolean '%s' must be True or False."%self.name) | |
977 | super(Boolean, self)._validate(val) | |
960 | super(Boolean, self).__init__(default=default, **params) | |
961 | ||
962 | def _validate_value(self, val, allow_None): | |
963 | if allow_None: | |
964 | if not isinstance(val, bool) and val is not None: | |
965 | raise ValueError("Boolean parameter %r only takes a " | |
966 | "Boolean value or None, not %s." | |
967 | % (self.name, val)) | |
968 | elif not isinstance(val, bool): | |
969 | raise ValueError("Boolean parameter %r must be True or False, " | |
970 | "not %s." % (self.name, val)) | |
978 | 971 | |
979 | 972 | |
980 | 973 | |
983 | 976 | |
984 | 977 | __slots__ = ['length'] |
985 | 978 | |
986 | def __init__(self,default=(0,0),length=None,**params): | |
979 | def __init__(self, default=(0,0), length=None, **params): | |
987 | 980 | """ |
988 | 981 | Initialize a tuple parameter with a fixed length (number of |
989 | 982 | elements). The length is determined by the initial default |
990 | 983 | value, if any, and must be supplied explicitly otherwise. The |
991 | 984 | length is not allowed to change after instantiation. |
992 | 985 | """ |
993 | super(Tuple,self).__init__(default=default,**params) | |
986 | super(Tuple,self).__init__(default=default, **params) | |
994 | 987 | if length is None and default is not None: |
995 | 988 | self.length = len(default) |
996 | 989 | elif length is None and default is None: |
1000 | 993 | self.length = length |
1001 | 994 | self._validate(default) |
1002 | 995 | |
996 | def _validate_value(self, val, allow_None): | |
997 | if val is None and allow_None: | |
998 | return | |
999 | ||
1000 | if not isinstance(val, tuple): | |
1001 | raise ValueError("Tuple parameter %r only takes a tuple value, " | |
1002 | "not %r." % (self.name, type(val))) | |
1003 | ||
1004 | def _validate_length(self, val, length): | |
1005 | if val is None and self.allow_None: | |
1006 | return | |
1007 | ||
1008 | if not len(val) == length: | |
1009 | raise ValueError("Tuple parameter %r is not of the correct " | |
1010 | "length (%d instead of %d)." % | |
1011 | (self.name, len(val), length)) | |
1003 | 1012 | |
1004 | 1013 | def _validate(self, val): |
1005 | if val is None and self.allow_None: | |
1006 | return | |
1007 | ||
1008 | if not isinstance(val,tuple): | |
1009 | raise ValueError("Tuple '%s' only takes a tuple value."%self.name) | |
1010 | ||
1011 | if not len(val)==self.length: | |
1012 | raise ValueError("%s: tuple is not of the correct length (%d instead of %d)." % | |
1013 | (self.name,len(val),self.length)) | |
1014 | ||
1014 | self._validate_value(val, self.allow_None) | |
1015 | self._validate_length(val, self.length) | |
1016 | ||
1017 | @classmethod | |
1018 | def serialize(cls, value): | |
1019 | if value is None: | |
1020 | return 'null' | |
1021 | return list(value) # As JSON has no tuple representation | |
1022 | ||
1023 | @classmethod | |
1024 | def deserialize(cls, value): | |
1025 | if value == 'null': | |
1026 | return None | |
1027 | return tuple(value) # As JSON has no tuple representation | |
1015 | 1028 | |
1016 | 1029 | |
1017 | 1030 | class NumericTuple(Tuple): |
1018 | 1031 | """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length.""" |
1019 | 1032 | |
1020 | def _validate(self, val): | |
1021 | super(NumericTuple, self)._validate(val) | |
1022 | if not (self.allow_None and val is None): | |
1023 | for n in val: | |
1024 | if not _is_number(n): | |
1025 | raise ValueError("%s: tuple element is not numeric: %s." % | |
1026 | (self.name,str(n))) | |
1027 | ||
1033 | def _validate_value(self, val, allow_None): | |
1034 | super(NumericTuple, self)._validate_value(val, allow_None) | |
1035 | if allow_None and val is None: | |
1036 | return | |
1037 | for n in val: | |
1038 | if _is_number(n): | |
1039 | continue | |
1040 | raise ValueError("NumericTuple parameter %r only takes numeric " | |
1041 | "values, not type %r." % (self.name, type(n))) | |
1028 | 1042 | |
1029 | 1043 | |
1030 | 1044 | class XYCoordinates(NumericTuple): |
1031 | 1045 | """A NumericTuple for an X,Y coordinate.""" |
1032 | 1046 | |
1033 | def __init__(self,default=(0.0,0.0),**params): | |
1034 | super(XYCoordinates,self).__init__(default=default,length=2,**params) | |
1035 | ||
1047 | def __init__(self, default=(0.0, 0.0), **params): | |
1048 | super(XYCoordinates,self).__init__(default=default, length=2, **params) | |
1036 | 1049 | |
1037 | 1050 | |
1038 | 1051 | class Callable(Parameter): |
1045 | 1058 | 2.4, so instantiate must be False for those values. |
1046 | 1059 | """ |
1047 | 1060 | |
1048 | def _validate(self, val): | |
1049 | if not (self.allow_None and val is None) and (not callable(val)): | |
1050 | raise ValueError("Callable '%s' only takes a callable object."%self.name) | |
1051 | super(Callable, self)._validate(val) | |
1052 | ||
1061 | def _validate_value(self, val, allow_None): | |
1062 | if (allow_None and val is None) or callable(val): | |
1063 | return | |
1064 | ||
1065 | raise ValueError("Callable parameter %r only takes a callable object, " | |
1066 | "not objects of type %r." % (self.name, type(val))) | |
1053 | 1067 | |
1054 | 1068 | |
1055 | 1069 | class Action(Callable): |
1067 | 1081 | return False |
1068 | 1082 | |
1069 | 1083 | |
1070 | ||
1071 | # CEBALERT: this should be a method of ClassSelector. | |
1084 | # Could be a method of ClassSelector. | |
1072 | 1085 | def concrete_descendents(parentclass): |
1073 | 1086 | """ |
1074 | 1087 | Return a dictionary containing all subclasses of the specified |
1081 | 1094 | """ |
1082 | 1095 | return dict((c.__name__,c) for c in descendents(parentclass) |
1083 | 1096 | if not _is_abstract(c)) |
1084 | ||
1085 | 1097 | |
1086 | 1098 | |
1087 | 1099 | class Composite(Parameter): |
1094 | 1106 | in the order specified. Likewise, setting the parameter takes a |
1095 | 1107 | sequence of values and sets the value of the constituent |
1096 | 1108 | attributes. |
1097 | """ | |
1098 | ||
1099 | __slots__=['attribs','objtype'] | |
1100 | ||
1101 | def __init__(self,attribs=None,**kw): | |
1109 | ||
1110 | This Parameter type has not been tested with watchers and | |
1111 | dependencies, and may not support them properly. | |
1112 | """ | |
1113 | ||
1114 | __slots__ = ['attribs', 'objtype'] | |
1115 | ||
1116 | def __init__(self, attribs=None, **kw): | |
1102 | 1117 | if attribs is None: |
1103 | 1118 | attribs = [] |
1104 | super(Composite,self).__init__(default=None,**kw) | |
1119 | super(Composite, self).__init__(default=None, **kw) | |
1105 | 1120 | self.attribs = attribs |
1106 | 1121 | |
1107 | def __get__(self,obj,objtype): | |
1122 | def __get__(self, obj, objtype): | |
1108 | 1123 | """ |
1109 | 1124 | Return the values of all the attribs, as a list. |
1110 | 1125 | """ |
1111 | 1126 | if obj is None: |
1112 | return [getattr(objtype,a) for a in self.attribs] | |
1127 | return [getattr(objtype, a) for a in self.attribs] | |
1113 | 1128 | else: |
1114 | return [getattr(obj,a) for a in self.attribs] | |
1129 | return [getattr(obj, a) for a in self.attribs] | |
1130 | ||
1131 | def _validate_attribs(self, val, attribs): | |
1132 | if len(val) == len(attribs): | |
1133 | return | |
1134 | raise ValueError("Compound parameter %r got the wrong number " | |
1135 | "of values (needed %d, but got %d)." % | |
1136 | (self.name, len(attribs), len(val))) | |
1115 | 1137 | |
1116 | 1138 | def _validate(self, val): |
1117 | assert len(val) == len(self.attribs),"Compound parameter '%s' got the wrong number of values (needed %d, but got %d)." % (self.name,len(self.attribs),len(val)) | |
1139 | self._validate_attribs(val, self.attribs) | |
1118 | 1140 | |
1119 | 1141 | def _post_setter(self, obj, val): |
1120 | 1142 | if obj is None: |
1121 | for a,v in zip(self.attribs,val): | |
1122 | setattr(self.objtype,a,v) | |
1143 | for a, v in zip(self.attribs, val): | |
1144 | setattr(self.objtype, a, v) | |
1123 | 1145 | else: |
1124 | for a,v in zip(self.attribs,val): | |
1125 | setattr(obj,a,v) | |
1146 | for a, v in zip(self.attribs, val): | |
1147 | setattr(obj, a, v) | |
1126 | 1148 | |
1127 | 1149 | |
1128 | 1150 | class SelectorBase(Parameter): |
1138 | 1160 | raise NotImplementedError("get_range() must be implemented in subclasses.") |
1139 | 1161 | |
1140 | 1162 | |
1141 | class ObjectSelector(SelectorBase): | |
1163 | class Selector(SelectorBase): | |
1142 | 1164 | """ |
1143 | 1165 | Parameter whose value must be one object from a list of possible objects. |
1166 | ||
1167 | By default, if no default is specified, picks the first object from | |
1168 | the provided set of objects, as long as the objects are in an | |
1169 | ordered data collection. | |
1144 | 1170 | |
1145 | 1171 | check_on_set restricts the value to be among the current list of |
1146 | 1172 | objects. By default, if objects are initially supplied, |
1161 | 1187 | up from the object value. |
1162 | 1188 | """ |
1163 | 1189 | |
1164 | __slots__ = ['objects','compute_default_fn','check_on_set','names'] | |
1165 | ||
1166 | # ObjectSelector is usually used to allow selection from a list of | |
1190 | __slots__ = ['objects', 'compute_default_fn', 'check_on_set', 'names'] | |
1191 | ||
1192 | # Selector is usually used to allow selection from a list of | |
1167 | 1193 | # existing objects, therefore instantiate is False by default. |
1168 | def __init__(self,default=None,objects=None,instantiate=False, | |
1169 | compute_default_fn=None,check_on_set=None,allow_None=None,**params): | |
1194 | def __init__(self, objects=None, default=None, instantiate=False, | |
1195 | compute_default_fn=None, check_on_set=None, | |
1196 | allow_None=None, empty_default=False, **params): | |
1197 | ||
1198 | autodefault = None | |
1199 | if objects: | |
1200 | if is_ordered_dict(objects): | |
1201 | autodefault = list(objects.values())[0] | |
1202 | elif isinstance(objects, dict): | |
1203 | main.param.warning("Parameter default value is arbitrary due to " | |
1204 | "dictionaries prior to Python 3.6 not being " | |
1205 | "ordered; should use an ordered dict or " | |
1206 | "supply an explicit default value.") | |
1207 | autodefault = list(objects.values())[0] | |
1208 | elif isinstance(objects, list): | |
1209 | autodefault = objects[0] | |
1210 | ||
1211 | default = autodefault if (not empty_default and default is None) else default | |
1212 | ||
1170 | 1213 | if objects is None: |
1171 | 1214 | objects = [] |
1172 | 1215 | if isinstance(objects, collections_abc.Mapping): |
1178 | 1221 | self.compute_default_fn = compute_default_fn |
1179 | 1222 | |
1180 | 1223 | if check_on_set is not None: |
1181 | self.check_on_set=check_on_set | |
1182 | elif len(objects)==0: | |
1183 | self.check_on_set=False | |
1224 | self.check_on_set = check_on_set | |
1225 | elif len(objects) == 0: | |
1226 | self.check_on_set = False | |
1184 | 1227 | else: |
1185 | self.check_on_set=True | |
1186 | ||
1187 | super(ObjectSelector,self).__init__(default=default,instantiate=instantiate, | |
1188 | **params) | |
1228 | self.check_on_set = True | |
1229 | ||
1230 | super(Selector,self).__init__( | |
1231 | default=default, instantiate=instantiate, **params) | |
1189 | 1232 | # Required as Parameter sets allow_None=True if default is None |
1190 | 1233 | self.allow_None = allow_None |
1191 | 1234 | if default is not None and self.check_on_set is True: |
1192 | 1235 | self._validate(default) |
1193 | 1236 | |
1194 | ||
1195 | # CBNOTE: if the list of objects is changed, the current value for | |
1196 | # this parameter in existing POs could be out of the new range. | |
1237 | # Note that if the list of objects is changed, the current value for | |
1238 | # this parameter in existing POs could be outside of the new range. | |
1197 | 1239 | |
1198 | 1240 | def compute_default(self): |
1199 | 1241 | """ |
1204 | 1246 | no longer None). |
1205 | 1247 | """ |
1206 | 1248 | if self.default is None and callable(self.compute_default_fn): |
1207 | self.default=self.compute_default_fn() | |
1249 | self.default = self.compute_default_fn() | |
1208 | 1250 | if self.default not in self.objects: |
1209 | 1251 | self.objects.append(self.default) |
1210 | 1252 | |
1211 | ||
1212 | 1253 | def _validate(self, val): |
1213 | 1254 | """ |
1214 | 1255 | val must be None or one of the objects in self.objects. |
1218 | 1259 | return |
1219 | 1260 | |
1220 | 1261 | if not (val in self.objects or (self.allow_None and val is None)): |
1221 | # CEBALERT: can be called before __init__ has called | |
1222 | # super's __init__, i.e. before attrib_name has been set. | |
1223 | try: | |
1224 | attrib_name = self.name | |
1225 | except AttributeError: | |
1262 | # This method can be called before __init__ has called | |
1263 | # super's __init__, so there may not be any name set yet. | |
1264 | if (hasattr(self, "name") and self.name): | |
1265 | attrib_name = " " + self.name | |
1266 | else: | |
1226 | 1267 | attrib_name = "" |
1227 | 1268 | |
1228 | 1269 | items = [] |
1237 | 1278 | limiter = ', ...]' |
1238 | 1279 | break |
1239 | 1280 | items = '[' + ', '.join(items) + limiter |
1240 | raise ValueError("%s not in Parameter %s's list of possible objects, " | |
1241 | "valid options include %s"%(val,attrib_name, items)) | |
1281 | raise ValueError("%s not in parameter%s's list of possible objects, " | |
1282 | "valid options include %s" % (val, attrib_name, items)) | |
1242 | 1283 | |
1243 | 1284 | def _ensure_value_is_in_objects(self,val): |
1244 | 1285 | """ |
1247 | 1288 | to check each item instead. |
1248 | 1289 | """ |
1249 | 1290 | if not (val in self.objects): |
1250 | self.objects.append(val) | |
1291 | self.objects.append(val) | |
1251 | 1292 | |
1252 | 1293 | def get_range(self): |
1253 | 1294 | """ |
1258 | 1299 | return named_objs(self.objects, self.names) |
1259 | 1300 | |
1260 | 1301 | |
1261 | class Selector(ObjectSelector): | |
1262 | """ | |
1263 | A more user friendly ObjectSelector that picks the first object for | |
1264 | the default (by default) given an ordered data collection. As the | |
1265 | first argument is now objects, this can be passed in as a positional | |
1266 | argument which sufficient in many common use cases. | |
1267 | """ | |
1268 | def __init__(self,objects=None, default=None, instantiate=False, | |
1269 | compute_default_fn=None,check_on_set=None,allow_None=None,**params): | |
1270 | ||
1271 | if is_ordered_dict(objects): | |
1272 | autodefault = list(objects.values())[0] | |
1273 | elif isinstance(objects, dict): | |
1274 | main.param.warning("Parameter default value is arbitrary due to " | |
1275 | "dictionaries prior to Python 3.6 not being " | |
1276 | "ordered; should use an ordered dict or " | |
1277 | "supply an explicit default value.") | |
1278 | autodefault = list(objects.values())[0] | |
1279 | elif isinstance(objects, list): | |
1280 | autodefault = objects[0] | |
1281 | else: | |
1282 | autodefault = None | |
1283 | ||
1284 | default = autodefault if default is None else default | |
1285 | ||
1286 | super(Selector,self).__init__(default=default, objects=objects, | |
1287 | instantiate=instantiate, | |
1288 | compute_default_fn=compute_default_fn, | |
1289 | check_on_set=check_on_set, | |
1290 | allow_None=allow_None, **params) | |
1302 | class ObjectSelector(Selector): | |
1303 | """ | |
1304 | Deprecated. Same as Selector, but with a different constructor for | |
1305 | historical reasons. | |
1306 | """ | |
1307 | def __init__(self, default=None, objects=None, **kwargs): | |
1308 | super(ObjectSelector,self).__init__(objects=objects, default=default, | |
1309 | empty_default=True, **kwargs) | |
1310 | ||
1291 | 1311 | |
1292 | 1312 | class ClassSelector(SelectorBase): |
1293 | 1313 | """ |
1297 | 1317 | for is_instance=True. |
1298 | 1318 | """ |
1299 | 1319 | |
1300 | __slots__ = ['class_','is_instance'] | |
1320 | __slots__ = ['class_', 'is_instance'] | |
1301 | 1321 | |
1302 | 1322 | def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params): |
1303 | 1323 | self.class_ = class_ |
1305 | 1325 | super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params) |
1306 | 1326 | self._validate(default) |
1307 | 1327 | |
1308 | ||
1309 | def _validate(self,val): | |
1310 | """val must be None, an instance of self.class_ if self.is_instance=True or a subclass of self_class if self.is_instance=False""" | |
1311 | if isinstance(self.class_, tuple): | |
1312 | class_name = ('(%s)' % ', '.join(cl.__name__ for cl in self.class_)) | |
1328 | def _validate(self, val): | |
1329 | super(ClassSelector, self)._validate(val) | |
1330 | self._validate_class_(val, self.class_, self.is_instance) | |
1331 | ||
1332 | def _validate_class_(self, val, class_, is_instance): | |
1333 | if (val is None and self.allow_None): | |
1334 | return | |
1335 | if isinstance(class_, tuple): | |
1336 | class_name = ('(%s)' % ', '.join(cl.__name__ for cl in class_)) | |
1313 | 1337 | else: |
1314 | class_name = self.class_.__name__ | |
1315 | if self.is_instance: | |
1316 | if not (isinstance(val,self.class_)) and not (val is None and self.allow_None): | |
1338 | class_name = class_.__name__ | |
1339 | param_cls = self.__class__.__name__ | |
1340 | if is_instance: | |
1341 | if not (isinstance(val, class_)): | |
1317 | 1342 | raise ValueError( |
1318 | "Parameter '%s' value must be an instance of %s, not '%s'" % | |
1319 | (self.name, class_name, val)) | |
1343 | "%s parameter %r value must be an instance of %s, not %r." % | |
1344 | (param_cls, self.name, class_name, val)) | |
1320 | 1345 | else: |
1321 | if not (val is None and self.allow_None) and not (issubclass(val,self.class_)): | |
1346 | if not (issubclass(val, class_)): | |
1322 | 1347 | raise ValueError( |
1323 | "Parameter '%s' must be a subclass of %s, not '%s'" % | |
1324 | (val.__name__, class_name, val.__class__.__name__)) | |
1325 | ||
1348 | "%s parameter %r must be a subclass of %s, not %r." % | |
1349 | (param_cls, self.name, class_name, val.__name__)) | |
1326 | 1350 | |
1327 | 1351 | def get_range(self): |
1328 | 1352 | """ |
1329 | 1353 | Return the possible types for this parameter's value. |
1330 | 1354 | |
1331 | (I.e. return {name: <class>} for all classes that are | |
1332 | concrete_descendents() of self.class_.) | |
1355 | (I.e. return `{name: <class>}` for all classes that are | |
1356 | concrete_descendents() of `self.class_`.) | |
1333 | 1357 | |
1334 | 1358 | Only classes from modules that have been imported are added |
1335 | 1359 | (see concrete_descendents()). |
1338 | 1362 | all_classes = {} |
1339 | 1363 | for cls in classes: |
1340 | 1364 | all_classes.update(concrete_descendents(cls)) |
1341 | d=OrderedDict((name,class_) for name,class_ in all_classes.items()) | |
1365 | d = OrderedDict((name, class_) for name,class_ in all_classes.items()) | |
1342 | 1366 | if self.allow_None: |
1343 | d['None']=None | |
1367 | d['None'] = None | |
1344 | 1368 | return d |
1345 | 1369 | |
1346 | 1370 | |
1349 | 1373 | Parameter whose value is a list of objects, usually of a specified type. |
1350 | 1374 | |
1351 | 1375 | The bounds allow a minimum and/or maximum length of |
1352 | list to be enforced. If the class is non-None, all | |
1376 | list to be enforced. If the item_type is non-None, all | |
1353 | 1377 | items in the list are checked to be of that type. |
1354 | """ | |
1355 | ||
1356 | __slots__ = ['class_','bounds'] | |
1357 | ||
1358 | def __init__(self,default=[],class_=None,instantiate=True, | |
1359 | bounds=(0,None),**params): | |
1360 | self.class_ = class_ | |
1378 | ||
1379 | `class_` is accepted as an alias for `item_type`, but is | |
1380 | deprecated due to conflict with how the `class_` slot is | |
1381 | used in Selector classes. | |
1382 | """ | |
1383 | ||
1384 | __slots__ = ['bounds', 'item_type', 'class_'] | |
1385 | ||
1386 | def __init__(self, default=[], class_=None, item_type=None, | |
1387 | instantiate=True, bounds=(0, None), **params): | |
1388 | self.item_type = item_type or class_ | |
1389 | self.class_ = self.item_type | |
1361 | 1390 | self.bounds = bounds |
1362 | Parameter.__init__(self,default=default,instantiate=instantiate, | |
1391 | Parameter.__init__(self, default=default, instantiate=instantiate, | |
1363 | 1392 | **params) |
1364 | 1393 | self._validate(default) |
1365 | 1394 | |
1366 | 1395 | def _validate(self, val): |
1367 | 1396 | """ |
1368 | Checks that the list is of the right length and has the right contents. | |
1369 | Otherwise, an exception is raised. | |
1370 | """ | |
1371 | if self.allow_None and val is None: | |
1372 | return | |
1373 | ||
1397 | Checks that the value is numeric and that it is within the hard | |
1398 | bounds; if not, an exception is raised. | |
1399 | """ | |
1400 | self._validate_value(val, self.allow_None) | |
1401 | self._validate_bounds(val, self.bounds) | |
1402 | self._validate_item_type(val, self.item_type) | |
1403 | ||
1404 | def _validate_bounds(self, val, bounds): | |
1405 | "Checks that the list is of the right length and has the right contents." | |
1406 | if bounds is None or (val is None and self.allow_None): | |
1407 | return | |
1408 | min_length, max_length = bounds | |
1409 | l = len(val) | |
1410 | if min_length is not None and max_length is not None: | |
1411 | if not (min_length <= l <= max_length): | |
1412 | raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length)) | |
1413 | elif min_length is not None: | |
1414 | if not min_length <= l: | |
1415 | raise ValueError("%s: list length must be at least %s." | |
1416 | % (self.name, min_length)) | |
1417 | elif max_length is not None: | |
1418 | if not l <= max_length: | |
1419 | raise ValueError("%s: list length must be at most %s." | |
1420 | % (self.name, max_length)) | |
1421 | ||
1422 | def _validate_value(self, val, allow_None): | |
1423 | if allow_None and val is None: | |
1424 | return | |
1374 | 1425 | if not isinstance(val, list): |
1375 | raise ValueError("List '%s' must be a list."%(self.name)) | |
1376 | ||
1377 | if self.bounds is not None: | |
1378 | min_length,max_length = self.bounds | |
1379 | l=len(val) | |
1380 | if min_length is not None and max_length is not None: | |
1381 | if not (min_length <= l <= max_length): | |
1382 | raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length)) | |
1383 | elif min_length is not None: | |
1384 | if not min_length <= l: | |
1385 | raise ValueError("%s: list length must be at least %s."%(self.name,min_length)) | |
1386 | elif max_length is not None: | |
1387 | if not l <= max_length: | |
1388 | raise ValueError("%s: list length must be at most %s."%(self.name,max_length)) | |
1389 | ||
1390 | self._check_type(val) | |
1391 | ||
1392 | def _check_type(self,val): | |
1393 | if self.class_ is not None: | |
1394 | for v in val: | |
1395 | assert isinstance(v,self.class_),repr(self.name)+": "+repr(v)+" is not an instance of " + repr(self.class_) + "." | |
1396 | ||
1426 | raise ValueError("List parameter %r must be a list, not an object of type %s." | |
1427 | % (self.name, type(val))) | |
1428 | ||
1429 | def _validate_item_type(self, val, item_type): | |
1430 | if item_type is None or (self.allow_None and val is None): | |
1431 | return | |
1432 | for v in val: | |
1433 | if isinstance(v, item_type): | |
1434 | continue | |
1435 | raise TypeError("List parameter %r items must be instances " | |
1436 | "of type %r, not %r." % (self.name, item_type, val)) | |
1397 | 1437 | |
1398 | 1438 | |
1399 | 1439 | class HookList(List): |
1404 | 1444 | for users to register a set of commands to be called at a |
1405 | 1445 | specified place in some sequence of processing steps. |
1406 | 1446 | """ |
1407 | __slots__ = ['class_','bounds'] | |
1408 | ||
1409 | def _check_type(self,val): | |
1447 | __slots__ = ['class_', 'bounds'] | |
1448 | ||
1449 | def _validate_value(self, val, allow_None): | |
1450 | super(HookList, self)._validate_value(val, allow_None) | |
1451 | if allow_None and val is None: | |
1452 | return | |
1410 | 1453 | for v in val: |
1411 | assert callable(v),repr(self.name)+": "+repr(v)+" is not callable." | |
1412 | ||
1454 | if callable(v): | |
1455 | continue | |
1456 | raise ValueError("HookList parameter %r items must be callable, " | |
1457 | "not %r." % (self.name, v)) | |
1413 | 1458 | |
1414 | 1459 | |
1415 | 1460 | class Dict(ClassSelector): |
1416 | 1461 | """ |
1417 | 1462 | Parameter whose value is a dictionary. |
1418 | 1463 | """ |
1464 | ||
1419 | 1465 | def __init__(self, default=None, **params): |
1420 | super(Dict,self).__init__(dict, default=default, **params) | |
1466 | super(Dict, self).__init__(dict, default=default, **params) | |
1421 | 1467 | |
1422 | 1468 | |
1423 | 1469 | class Array(ClassSelector): |
1426 | 1472 | """ |
1427 | 1473 | |
1428 | 1474 | def __init__(self, default=None, **params): |
1429 | # CEBALERT: instead use python array as default? | |
1430 | 1475 | from numpy import ndarray |
1431 | super(Array,self).__init__(ndarray, allow_None=True, default=default, **params) | |
1476 | super(Array, self).__init__(ndarray, allow_None=True, default=default, **params) | |
1477 | ||
1478 | @classmethod | |
1479 | def serialize(cls, value): | |
1480 | if value is None: | |
1481 | return 'null' | |
1482 | return value.tolist() | |
1483 | ||
1484 | @classmethod | |
1485 | def deserialize(cls, value): | |
1486 | if value == 'null': | |
1487 | return None | |
1488 | from numpy import asarray | |
1489 | return asarray(value) | |
1432 | 1490 | |
1433 | 1491 | |
1434 | 1492 | class DataFrame(ClassSelector): |
1449 | 1507 | if a list is given, the supplied DataFrame must contain exactly the |
1450 | 1508 | same columns and in the same order and no other columns. |
1451 | 1509 | """ |
1452 | __slots__ = ['rows','columns', 'ordered'] | |
1510 | ||
1511 | __slots__ = ['rows', 'columns', 'ordered'] | |
1453 | 1512 | |
1454 | 1513 | def __init__(self, default=None, rows=None, columns=None, ordered=None, **params): |
1455 | 1514 | from pandas import DataFrame as pdDFrame |
1456 | 1515 | self.rows = rows |
1457 | 1516 | self.columns = columns |
1458 | 1517 | self.ordered = ordered |
1459 | super(DataFrame,self).__init__(pdDFrame, default=default, allow_None=True, **params) | |
1518 | super(DataFrame,self).__init__(pdDFrame, default=default, **params) | |
1460 | 1519 | self._validate(self.default) |
1461 | ||
1462 | 1520 | |
1463 | 1521 | def _length_bounds_check(self, bounds, length, name): |
1464 | 1522 | message = '{name} length {length} does not match declared bounds of {bounds}' |
1478 | 1536 | |
1479 | 1537 | if isinstance(self.columns, set) and self.ordered is True: |
1480 | 1538 | raise ValueError('Columns cannot be ordered when specified as a set') |
1539 | ||
1540 | if self.allow_None and val is None: | |
1541 | return | |
1481 | 1542 | |
1482 | 1543 | if self.columns is None: |
1483 | 1544 | pass |
1501 | 1562 | if self.rows is not None: |
1502 | 1563 | self._length_bounds_check(self.rows, len(val), 'Row') |
1503 | 1564 | |
1565 | @classmethod | |
1566 | def serialize(cls, value): | |
1567 | if value is None: | |
1568 | return 'null' | |
1569 | return value.to_dict('records') | |
1570 | ||
1571 | @classmethod | |
1572 | def deserialize(cls, value): | |
1573 | if value == 'null': | |
1574 | return None | |
1575 | from pandas import DataFrame as pdDFrame | |
1576 | return pdDFrame(value) | |
1577 | ||
1504 | 1578 | |
1505 | 1579 | class Series(ClassSelector): |
1506 | 1580 | """ |
1510 | 1584 | which may be a number or an integer bounds tuple to constrain the |
1511 | 1585 | allowable number of rows. |
1512 | 1586 | """ |
1587 | ||
1513 | 1588 | __slots__ = ['rows'] |
1589 | ||
1590 | def __init__(self, default=None, rows=None, allow_None=False, **params): | |
1591 | from pandas import Series as pdSeries | |
1592 | self.rows = rows | |
1593 | super(Series,self).__init__(pdSeries, default=default, allow_None=allow_None, | |
1594 | **params) | |
1595 | self._validate(self.default) | |
1514 | 1596 | |
1515 | 1597 | def _length_bounds_check(self, bounds, length, name): |
1516 | 1598 | message = '{name} length {length} does not match declared bounds of {bounds}' |
1525 | 1607 | if failure: |
1526 | 1608 | raise ValueError(message.format(name=name,length=length, bounds=bounds)) |
1527 | 1609 | |
1528 | def __init__(self, default=None, rows=None, **params): | |
1529 | from pandas import Series as pdSeries | |
1530 | self.rows = rows | |
1531 | super(Series,self).__init__(pdSeries, allow_None=True, default=default, **params) | |
1532 | self._validate(self.default) | |
1533 | ||
1534 | 1610 | def _validate(self, val): |
1535 | 1611 | super(Series, self)._validate(val) |
1536 | 1612 | |
1613 | if self.allow_None and val is None: | |
1614 | return | |
1615 | ||
1537 | 1616 | if self.rows is not None: |
1538 | 1617 | self._length_bounds_check(self.rows, len(val), 'Row') |
1539 | 1618 | |
1541 | 1620 | |
1542 | 1621 | # For portable code: |
1543 | 1622 | # - specify paths in unix (rather than Windows) style; |
1544 | # - use resolve_file_path() for paths to existing files to be read, | |
1545 | # - use resolve_folder_path() for paths to existing folders to be read, | |
1623 | # - use resolve_path(path_to_file=True) for paths to existing files to be read, | |
1624 | # - use resolve_path(path_to_file=False) for paths to existing folders to be read, | |
1546 | 1625 | # and normalize_path() for paths to new files to be written. |
1547 | 1626 | |
1548 | 1627 | class resolve_path(ParameterizedFunction): |
1566 | 1645 | Prepended to a non-relative path, in order, until a file is |
1567 | 1646 | found.""") |
1568 | 1647 | |
1569 | path_to_file = Boolean(default=True, pickle_default_value=False, doc=""" | |
1570 | String specifying whether the path refers to a 'File' or a 'Folder'.""") | |
1648 | path_to_file = Boolean(default=True, pickle_default_value=False, | |
1649 | allow_None=True, doc=""" | |
1650 | String specifying whether the path refers to a 'File' or a | |
1651 | 'Folder'. If None, the path may point to *either* a 'File' *or* | |
1652 | a 'Folder'.""") | |
1571 | 1653 | |
1572 | 1654 | def __call__(self, path, **params): |
1573 | 1655 | p = ParamOverrides(self, params) |
1574 | ||
1575 | 1656 | path = os.path.normpath(path) |
1657 | ftype = "File" if p.path_to_file is True \ | |
1658 | else "Folder" if p.path_to_file is False else "Path" | |
1659 | ||
1660 | if not p.search_paths: | |
1661 | p.search_paths = [os.getcwd()] | |
1576 | 1662 | |
1577 | 1663 | if os.path.isabs(path): |
1578 | if p.path_to_file: | |
1579 | if os.path.isfile(path): | |
1580 | return path | |
1581 | else: | |
1582 | raise IOError("File '%s' not found." %path) | |
1583 | elif not p.path_to_file: | |
1584 | if os.path.isdir(path): | |
1585 | return path | |
1586 | else: | |
1587 | raise IOError("Folder '%s' not found." %path) | |
1588 | else: | |
1589 | raise IOError("Type '%s' not recognised." %p.path_type) | |
1664 | if ((p.path_to_file is None and os.path.exists(path)) or | |
1665 | (p.path_to_file is True and os.path.isfile(path)) or | |
1666 | (p.path_to_file is False and os.path.isdir( path))): | |
1667 | return path | |
1668 | raise IOError("%s '%s' not found." % (ftype,path)) | |
1590 | 1669 | |
1591 | 1670 | else: |
1592 | 1671 | paths_tried = [] |
1593 | 1672 | for prefix in p.search_paths: |
1594 | 1673 | try_path = os.path.join(os.path.normpath(prefix), path) |
1595 | 1674 | |
1596 | if p.path_to_file: | |
1597 | if os.path.isfile(try_path): | |
1598 | return try_path | |
1599 | elif not p.path_to_file: | |
1600 | if os.path.isdir(try_path): | |
1601 | return try_path | |
1602 | else: | |
1603 | raise IOError("Type '%s' not recognised." %p.path_type) | |
1675 | if ((p.path_to_file is None and os.path.exists(try_path)) or | |
1676 | (p.path_to_file is True and os.path.isfile(try_path)) or | |
1677 | (p.path_to_file is False and os.path.isdir( try_path))): | |
1678 | return try_path | |
1604 | 1679 | |
1605 | 1680 | paths_tried.append(try_path) |
1606 | 1681 | |
1607 | raise IOError(os.path.split(path)[1] + " was not found in the following place(s): " + str(paths_tried) + ".") | |
1682 | raise IOError(ftype + " " + os.path.split(path)[1] + " was not found in the following place(s): " + str(paths_tried) + ".") | |
1608 | 1683 | |
1609 | 1684 | |
1610 | 1685 | class normalize_path(ParameterizedFunction): |
1645 | 1720 | The specified path can be absolute, or relative to either: |
1646 | 1721 | |
1647 | 1722 | * any of the paths specified in the search_paths attribute (if |
1648 | search_paths is not None); | |
1723 | search_paths is not None); | |
1649 | 1724 | |
1650 | 1725 | or |
1651 | 1726 | |
1663 | 1738 | super(Path,self).__init__(default,**params) |
1664 | 1739 | |
1665 | 1740 | def _resolve(self, path): |
1666 | if self.search_paths: | |
1667 | return resolve_path(path, search_paths=self.search_paths) | |
1668 | else: | |
1669 | return resolve_path(path) | |
1741 | return resolve_path(path, path_to_file=None, search_paths=self.search_paths) | |
1670 | 1742 | |
1671 | 1743 | def _validate(self, val): |
1672 | 1744 | if val is None: |
1673 | 1745 | if not self.allow_None: |
1674 | Parameterized(name="%s.%s"%(self.owner.name,self.name)).warning('None is not allowed') | |
1746 | Parameterized(name="%s.%s"%(self.owner.name,self.name)).param.warning('None is not allowed') | |
1675 | 1747 | else: |
1676 | 1748 | try: |
1677 | 1749 | self._resolve(val) |
1678 | 1750 | except IOError as e: |
1679 | Parameterized(name="%s.%s"%(self.owner.name,self.name)).warning('%s',e.args[0]) | |
1751 | Parameterized(name="%s.%s"%(self.owner.name,self.name)).param.warning('%s',e.args[0]) | |
1680 | 1752 | |
1681 | 1753 | def __get__(self, obj, objtype): |
1682 | 1754 | """ |
1707 | 1779 | |
1708 | 1780 | * any of the paths specified in the search_paths attribute (if |
1709 | 1781 | search_paths is not None); |
1782 | ||
1710 | 1783 | or |
1711 | 1784 | |
1712 | 1785 | * any of the paths searched by resolve_path() (if search_paths |
1714 | 1787 | """ |
1715 | 1788 | |
1716 | 1789 | def _resolve(self, path): |
1717 | if self.search_paths: | |
1718 | return resolve_path(path, path_to_file=True, search_paths=self.search_paths) | |
1719 | else: | |
1720 | return resolve_path(path, path_to_file=True) | |
1790 | return resolve_path(path, path_to_file=True, search_paths=self.search_paths) | |
1721 | 1791 | |
1722 | 1792 | |
1723 | 1793 | class Foldername(Path): |
1731 | 1801 | |
1732 | 1802 | * any of the paths specified in the search_paths attribute (if |
1733 | 1803 | search_paths is not None); |
1804 | ||
1734 | 1805 | or |
1735 | 1806 | |
1736 | 1807 | * any of the paths searched by resolve_dir_path() (if search_paths |
1738 | 1809 | """ |
1739 | 1810 | |
1740 | 1811 | def _resolve(self, path): |
1741 | if self.search_paths: | |
1742 | return resolve_path(path, path_to_file=False, search_paths=self.search_paths) | |
1743 | else: | |
1744 | return resolve_path(path, path_to_file=False) | |
1812 | return resolve_path(path, path_to_file=False, search_paths=self.search_paths) | |
1745 | 1813 | |
1746 | 1814 | |
1747 | 1815 | |
1757 | 1825 | |
1758 | 1826 | |
1759 | 1827 | |
1760 | class FileSelector(ObjectSelector): | |
1828 | class FileSelector(Selector): | |
1761 | 1829 | """ |
1762 | 1830 | Given a path glob, allows one file to be selected from those matching. |
1763 | 1831 | """ |
1764 | 1832 | __slots__ = ['path'] |
1765 | 1833 | |
1766 | 1834 | def __init__(self, default=None, path="", **kwargs): |
1767 | super(FileSelector, self).__init__(default, **kwargs) | |
1835 | self.default = default | |
1768 | 1836 | self.path = path |
1769 | 1837 | self.update() |
1838 | super(FileSelector, self).__init__(default=default, objects=self.objects, | |
1839 | empty_default=True, **kwargs) | |
1840 | ||
1841 | def _on_set(self, attribute, old, new): | |
1842 | super(FileSelector, self)._on_set(attribute, new, old) | |
1843 | if attribute == 'path': | |
1844 | self.update() | |
1770 | 1845 | |
1771 | 1846 | def update(self): |
1772 | 1847 | self.objects = sorted(glob.glob(self.path)) |
1778 | 1853 | return abbreviate_paths(self.path,super(FileSelector, self).get_range()) |
1779 | 1854 | |
1780 | 1855 | |
1781 | class ListSelector(ObjectSelector): | |
1782 | """ | |
1783 | Variant of ObjectSelector where the value can be multiple objects from | |
1856 | class ListSelector(Selector): | |
1857 | """ | |
1858 | Variant of Selector where the value can be multiple objects from | |
1784 | 1859 | a list of possible objects. |
1785 | 1860 | """ |
1861 | ||
1862 | def __init__(self, default=None, objects=None, **kwargs): | |
1863 | super(ListSelector,self).__init__( | |
1864 | objects=objects, default=default, empty_default=True, **kwargs) | |
1786 | 1865 | |
1787 | 1866 | def compute_default(self): |
1788 | 1867 | if self.default is None and callable(self.compute_default_fn): |
1792 | 1871 | self.objects.append(o) |
1793 | 1872 | |
1794 | 1873 | def _validate(self, val): |
1874 | if (val is None and self.allow_None): | |
1875 | return | |
1795 | 1876 | for o in val: |
1796 | 1877 | super(ListSelector, self)._validate(o) |
1797 | 1878 | |
1804 | 1885 | __slots__ = ['path'] |
1805 | 1886 | |
1806 | 1887 | def __init__(self, default=None, path="", **kwargs): |
1807 | super(MultiFileSelector, self).__init__(default, **kwargs) | |
1888 | self.default = default | |
1808 | 1889 | self.path = path |
1809 | 1890 | self.update() |
1891 | super(MultiFileSelector, self).__init__(default=default, objects=self.objects, **kwargs) | |
1892 | ||
1893 | def _on_set(self, attribute, old, new): | |
1894 | super(MultiFileSelector, self)._on_set(attribute, new, old) | |
1895 | if attribute == 'path': | |
1896 | self.update() | |
1810 | 1897 | |
1811 | 1898 | def update(self): |
1812 | 1899 | self.objects = sorted(glob.glob(self.path)) |
1826 | 1913 | def __init__(self, default=None, **kwargs): |
1827 | 1914 | super(Date, self).__init__(default=default, **kwargs) |
1828 | 1915 | |
1829 | def _validate(self, val): | |
1916 | def _validate_value(self, val, allow_None): | |
1830 | 1917 | """ |
1831 | 1918 | Checks that the value is numeric and that it is within the hard |
1832 | 1919 | bounds; if not, an exception is raised. |
1834 | 1921 | if self.allow_None and val is None: |
1835 | 1922 | return |
1836 | 1923 | |
1837 | if not isinstance(val, dt_types) and not (self.allow_None and val is None): | |
1838 | raise ValueError("Date '%s' only takes datetime and date types."%self.name) | |
1839 | ||
1840 | if self.step is not None and not isinstance(self.step, dt_types): | |
1841 | raise ValueError("Step parameter can only be None, a datetime or datetime type") | |
1842 | ||
1843 | self._checkBounds(val) | |
1924 | if not isinstance(val, dt_types) and not (allow_None and val is None): | |
1925 | raise ValueError( | |
1926 | "Date parameter %r only takes datetime and date types, " | |
1927 | "not type %r." % (self.name, type(val)) | |
1928 | ) | |
1929 | ||
1930 | def _validate_step(self, val, step): | |
1931 | if step is not None and not isinstance(step, dt_types): | |
1932 | raise ValueError( | |
1933 | "Step can only be None, a datetime " | |
1934 | "or datetime type, not type %r." % type(val) | |
1935 | ) | |
1936 | ||
1937 | @classmethod | |
1938 | def serialize(cls, value): | |
1939 | if value is None: | |
1940 | return 'null' | |
1941 | if not isinstance(value, (dt.datetime, dt.date)): # i.e np.datetime64 | |
1942 | value = value.astype(dt.datetime) | |
1943 | return value.strftime("%Y-%m-%dT%H:%M:%S.%f") | |
1944 | ||
1945 | @classmethod | |
1946 | def deserialize(cls, value): | |
1947 | if value == 'null': | |
1948 | return None | |
1949 | return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") | |
1844 | 1950 | |
1845 | 1951 | |
1846 | 1952 | class CalendarDate(Number): |
1847 | 1953 | """ |
1848 | CalendarDate parameter of date type. | |
1954 | Parameter specifically allowing dates (not datetimes). | |
1849 | 1955 | """ |
1850 | 1956 | |
1851 | 1957 | def __init__(self, default=None, **kwargs): |
1852 | 1958 | super(CalendarDate, self).__init__(default=default, **kwargs) |
1853 | 1959 | |
1854 | def _validate(self, val): | |
1960 | def _validate_value(self, val, allow_None): | |
1855 | 1961 | """ |
1856 | 1962 | Checks that the value is numeric and that it is within the hard |
1857 | 1963 | bounds; if not, an exception is raised. |
1859 | 1965 | if self.allow_None and val is None: |
1860 | 1966 | return |
1861 | 1967 | |
1862 | if not isinstance(val, dt.date) and not (self.allow_None and val is None): | |
1863 | raise ValueError("CalendarDate '%s' only takes datetime types."%self.name) | |
1864 | ||
1865 | if self.step is not None and not isinstance(self.step, dt.date): | |
1866 | raise ValueError("Step parameter can only be None or a date type") | |
1867 | ||
1868 | self._checkBounds(val) | |
1968 | if (not isinstance(val, dt.date) or isinstance(val, dt.datetime)) and not (allow_None and val is None): | |
1969 | raise ValueError("CalendarDate parameter %r only takes date types." % self.name) | |
1970 | ||
1971 | def _validate_step(self, val, step): | |
1972 | if step is not None and not isinstance(step, dt.date): | |
1973 | raise ValueError("Step can only be None or a date type.") | |
1974 | ||
1975 | @classmethod | |
1976 | def serialize(cls, value): | |
1977 | if value is None: | |
1978 | return 'null' | |
1979 | return value.strftime("%Y-%m-%d") | |
1980 | ||
1981 | @classmethod | |
1982 | def deserialize(cls, value): | |
1983 | if value == 'null': | |
1984 | return None | |
1985 | return dt.datetime.strptime(value, "%Y-%m-%d").date() | |
1869 | 1986 | |
1870 | 1987 | |
1871 | 1988 | class Color(Parameter): |
1872 | 1989 | """ |
1873 | 1990 | Color parameter defined as a hex RGB string with an optional # |
1874 | prefix. | |
1875 | """ | |
1876 | ||
1877 | def __init__(self, default=None, allow_None=False, **kwargs): | |
1991 | prefix or (optionally) as a CSS3 color name. | |
1992 | """ | |
1993 | ||
1994 | # CSS3 color specification https://www.w3.org/TR/css-color-3/#svg-color | |
1995 | _named_colors = [ 'aliceblue', 'antiquewhite', 'aqua', | |
1996 | 'aquamarine', 'azure', 'beige', 'bisque', 'black', | |
1997 | 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', | |
1998 | 'cadetblue', 'chartreuse', 'chocolate', 'coral', | |
1999 | 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', | |
2000 | 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', | |
2001 | 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', | |
2002 | 'darkorange', 'darkorchid', 'darkred', 'darksalmon', | |
2003 | 'darkseagreen', 'darkslateblue', 'darkslategray', | |
2004 | 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', | |
2005 | 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', | |
2006 | 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', | |
2007 | 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', | |
2008 | 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', | |
2009 | 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', | |
2010 | 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', | |
2011 | 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', | |
2012 | 'lightgray', 'lightgrey', 'lightgreen', 'lightpink', | |
2013 | 'lightsalmon', 'lightseagreen', 'lightskyblue', | |
2014 | 'lightslategray', 'lightslategrey', 'lightsteelblue', | |
2015 | 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', | |
2016 | 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', | |
2017 | 'mediumpurple', 'mediumseagreen', 'mediumslateblue', | |
2018 | 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', | |
2019 | 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', | |
2020 | 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', | |
2021 | 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', | |
2022 | 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', | |
2023 | 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red', | |
2024 | 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', | |
2025 | 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', | |
2026 | 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', | |
2027 | 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', | |
2028 | 'tomato', 'turquoise', 'violet', 'wheat', 'white', | |
2029 | 'whitesmoke', 'yellow', 'yellowgreen'] | |
2030 | ||
2031 | __slots__ = ['allow_named'] | |
2032 | ||
2033 | def __init__(self, default=None, allow_named=True, **kwargs): | |
1878 | 2034 | super(Color, self).__init__(default=default, **kwargs) |
2035 | self.allow_named = allow_named | |
1879 | 2036 | self._validate(default) |
1880 | 2037 | |
1881 | 2038 | def _validate(self, val): |
1882 | if (self.allow_None and val is None): | |
2039 | self._validate_value(val, self.allow_None) | |
2040 | self._validate_allow_named(val, self.allow_named) | |
2041 | ||
2042 | def _validate_value(self, val, allow_None): | |
2043 | if (allow_None and val is None): | |
1883 | 2044 | return |
1884 | 2045 | if not isinstance(val, basestring): |
1885 | raise ValueError("Color '%s' only takes a string value."%self.name) | |
1886 | if not re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val): | |
1887 | raise ValueError("Color '%s' only accepts valid RGB hex codes." | |
1888 | % self.name) | |
1889 | ||
2046 | raise ValueError("Color parameter %r expects a string value, " | |
2047 | "not an object of type %s." % (self.name, type(val))) | |
2048 | ||
2049 | def _validate_allow_named(self, val, allow_named): | |
2050 | if (val is None and self.allow_None): | |
2051 | return | |
2052 | is_hex = re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val) | |
2053 | if self.allow_named: | |
2054 | if not is_hex and val not in self._named_colors: | |
2055 | raise ValueError("Color '%s' only takes RGB hex codes " | |
2056 | "or named colors, received '%s'." % (self.name, val)) | |
2057 | elif not is_hex: | |
2058 | raise ValueError("Color '%s' only accepts valid RGB hex " | |
2059 | "codes, received '%s'." % (self.name, val)) | |
1890 | 2060 | |
1891 | 2061 | |
1892 | 2062 | class Range(NumericTuple): |
1893 | "A numeric range with optional bounds and softbounds" | |
1894 | ||
1895 | __slots__ = ['bounds', 'inclusive_bounds', 'softbounds'] | |
1896 | ||
2063 | """ | |
2064 | A numeric range with optional bounds and softbounds. | |
2065 | """ | |
2066 | ||
2067 | __slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step'] | |
1897 | 2068 | |
1898 | 2069 | def __init__(self,default=None, bounds=None, softbounds=None, |
1899 | inclusive_bounds=(True,True), **params): | |
2070 | inclusive_bounds=(True,True), step=None, **params): | |
1900 | 2071 | self.bounds = bounds |
1901 | 2072 | self.inclusive_bounds = inclusive_bounds |
1902 | 2073 | self.softbounds = softbounds |
2074 | self.step = step | |
1903 | 2075 | super(Range,self).__init__(default=default,length=2,**params) |
1904 | 2076 | |
1905 | ||
1906 | 2077 | def _validate(self, val): |
1907 | """ | |
1908 | Checks that the value is numeric and that it is within the hard | |
1909 | bounds; if not, an exception is raised. | |
1910 | """ | |
1911 | if self.allow_None and val is None: | |
1912 | return | |
1913 | 2078 | super(Range, self)._validate(val) |
1914 | ||
1915 | self._checkBounds(val) | |
2079 | self._validate_bounds(val, self.bounds, self.inclusive_bounds) | |
2080 | ||
2081 | def _validate_bounds(self, val, bounds, inclusive_bounds): | |
2082 | if bounds is None or (val is None and self.allow_None): | |
2083 | return | |
2084 | vmin, vmax = bounds | |
2085 | incmin, incmax = inclusive_bounds | |
2086 | for bound, v in zip(['lower', 'upper'], val): | |
2087 | too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin) | |
2088 | too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax) | |
2089 | if too_low or too_high: | |
2090 | raise ValueError("Range parameter %r's %s bound must be in range %s." | |
2091 | % (self.name, bound, self.rangestr())) | |
1916 | 2092 | |
1917 | 2093 | |
1918 | 2094 | def get_soft_bounds(self): |
1919 | """ | |
1920 | For each soft bound (upper and lower), if there is a defined bound (not equal to None) | |
1921 | then it is returned, otherwise it defaults to the hard bound. The hard bound could still be None. | |
1922 | """ | |
1923 | if self.bounds is None: | |
1924 | hl,hu=(None,None) | |
1925 | else: | |
1926 | hl,hu=self.bounds | |
1927 | ||
1928 | if self.softbounds is None: | |
1929 | sl,su=(None,None) | |
1930 | else: | |
1931 | sl,su=self.softbounds | |
1932 | ||
1933 | ||
1934 | if sl is None: l = hl | |
1935 | else: l = sl | |
1936 | ||
1937 | if su is None: u = hu | |
1938 | else: u = su | |
1939 | ||
1940 | return (l,u) | |
2095 | return get_soft_bounds(self.bounds, self.softbounds) | |
1941 | 2096 | |
1942 | 2097 | |
1943 | 2098 | def rangestr(self): |
1948 | 2103 | return '%s%s, %s%s' % (incmin, vmin, vmax, incmax) |
1949 | 2104 | |
1950 | 2105 | |
1951 | def _checkBounds(self, val): | |
1952 | if self.bounds is not None: | |
1953 | vmin,vmax = self.bounds | |
1954 | incmin,incmax = self.inclusive_bounds | |
1955 | for bound, v in zip(['lower', 'upper'], val): | |
1956 | too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin) | |
1957 | too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax) | |
1958 | if too_low or too_high: | |
1959 | raise ValueError("Parameter '%s' %s bound must be in range %s" | |
1960 | % (self.name, bound, self.rangestr())) | |
1961 | ||
1962 | ||
1963 | 2106 | class DateRange(Range): |
1964 | 2107 | """ |
1965 | 2108 | A datetime or date range specified as (start, end). |
1966 | 2109 | |
1967 | 2110 | Bounds must be specified as datetime or date types (see param.dt_types). |
1968 | 2111 | """ |
1969 | def _validate(self, val): | |
1970 | if self.allow_None and val is None: | |
2112 | ||
2113 | def _validate_value(self, val, allow_None): | |
2114 | if allow_None and val is None: | |
1971 | 2115 | return |
1972 | 2116 | |
1973 | 2117 | for n in val: |
1974 | if not isinstance(n, dt_types): | |
1975 | raise ValueError("DateRange '%s' only takes datetime types: %s"%(self.name,val)) | |
2118 | if isinstance(n, dt_types): | |
2119 | continue | |
2120 | raise ValueError("DateRange parameter %r only takes datetime " | |
2121 | "types, not %r." % (self.name, type(val))) | |
1976 | 2122 | |
1977 | 2123 | start, end = val |
1978 | 2124 | if not end >= start: |
1979 | raise ValueError("DateRange '%s': end datetime %s is before start datetime %s."%(self.name,val[1],val[0])) | |
1980 | ||
1981 | # Calling super(DateRange, self)._check(val) would also check | |
1982 | # values are numeric, which is redundant, so just call | |
1983 | # _checkBounds(). | |
1984 | self._checkBounds(val) | |
2125 | raise ValueError("DateRange parameter %r's end datetime %s " | |
2126 | "is before start datetime %s." % | |
2127 | (self.name, val[1], val[0])) | |
2128 | ||
1985 | 2129 | |
1986 | 2130 | |
1987 | 2131 | class CalendarDateRange(Range): |
1988 | 2132 | """ |
1989 | 2133 | A date range specified as (start_date, end_date). |
1990 | 2134 | """ |
1991 | def _validate(self, val): | |
1992 | if self.allow_None and val is None: | |
2135 | def _validate_value(self, val, allow_None): | |
2136 | if allow_None and val is None: | |
1993 | 2137 | return |
1994 | 2138 | |
1995 | 2139 | for n in val: |
1996 | 2140 | if not isinstance(n, dt.date): |
1997 | raise ValueError("CalendarDateRange '%s' only takes date types: %s"%(self.name,val)) | |
2141 | raise ValueError("CalendarDateRange parameter %r only " | |
2142 | "takes date types, not %s." % (self.name, val)) | |
1998 | 2143 | |
1999 | 2144 | start, end = val |
2000 | 2145 | if not end >= start: |
2001 | raise ValueError("CalendarDateRange '%s': end date %s is before start date %s."%(self.name,val[1],val[0])) | |
2002 | ||
2003 | # Calling super(CalendarDateRange, self)._check(val) would also check | |
2004 | # values are numeric, which is redundant, so just call | |
2005 | # _checkBounds(). | |
2006 | self._checkBounds(val) | |
2146 | raise ValueError("CalendarDateRange parameter %r's end date " | |
2147 | "%s is before start date %s." % | |
2148 | (self.name, val[1], val[0])) | |
2149 | ||
2150 | ||
2151 | class Event(Boolean): | |
2152 | """ | |
2153 | An Event Parameter is one whose value is intimately linked to the | |
2154 | triggering of events for watchers to consume. Event has a Boolean | |
2155 | value, which when set to True triggers the associated watchers (as | |
2156 | any Parameter does) and then is automatically set back to | |
2157 | False. Conversely, if events are triggered directly via `.trigger`, | |
2158 | the value is transiently set to True (so that it's clear which of | |
2159 | many parameters being watched may have changed), then restored to | |
2160 | False when the triggering completes. An Event parameter is thus like | |
2161 | a momentary switch or pushbutton with a transient True value that | |
2162 | serves only to launch some other action (e.g. via a param.depends | |
2163 | decorator), rather than encapsulating the action itself as | |
2164 | param.Action does. | |
2165 | """ | |
2166 | ||
2167 | # _autotrigger_value specifies the value used to set the parameter | |
2168 | # to when the parameter is supplied to the trigger method. This | |
2169 | # value change is then what triggers the watcher callbacks. | |
2170 | __slots__ = ['_autotrigger_value', '_mode', '_autotrigger_reset_value'] | |
2171 | ||
2172 | def __init__(self,default=False,bounds=(0,1),**params): | |
2173 | self._autotrigger_value = True | |
2174 | self._autotrigger_reset_value = False | |
2175 | self._mode = 'set-reset' | |
2176 | # Mode can be one of 'set', 'set-reset' or 'reset' | |
2177 | ||
2178 | # 'set' is normal Boolean parameter behavior when set with a value. | |
2179 | # 'set-reset' temporarily sets the parameter (which triggers | |
2180 | # watching callbacks) but immediately resets the value back to | |
2181 | # False. | |
2182 | # 'reset' applies the reset from True to False without | |
2183 | # triggering watched callbacks | |
2184 | ||
2185 | # This _mode attribute is one of the few places where a specific | |
2186 | # parameter has a special behavior that is relied upon by the | |
2187 | # core functionality implemented in | |
2188 | # parameterized.py. Specifically, the set_param method | |
2189 | # temporarily sets this attribute in order to disable resetting | |
2190 | # back to False while triggered callbacks are executing | |
2191 | super(Event, self).__init__(default=default,**params) | |
2192 | ||
2193 | def _reset_event(self, obj, val): | |
2194 | val = False | |
2195 | if obj is None: | |
2196 | self.default = val | |
2197 | else: | |
2198 | obj.__dict__[self._internal_name] = val | |
2199 | self._post_setter(obj, val) | |
2200 | ||
2201 | @instance_descriptor | |
2202 | def __set__(self, obj, val): | |
2203 | if self._mode in ['set-reset', 'set']: | |
2204 | super(Event, self).__set__(obj, val) | |
2205 | if self._mode in ['set-reset', 'reset']: | |
2206 | self._reset_event(obj, val) | |
2207 | ||
2208 | ||
2209 | from contextlib import contextmanager | |
2210 | @contextmanager | |
2211 | def exceptions_summarized(): | |
2212 | """Useful utility for writing docs that need to show expected errors. | |
2213 | Shows exception only, concisely, without a traceback. | |
2214 | """ | |
2215 | try: | |
2216 | yield | |
2217 | except Exception: | |
2218 | import sys | |
2219 | etype, value, tb = sys.exc_info() | |
2220 | print("{}: {}".format(etype.__name__,value), file=sys.stderr) |
19 | 19 | __author__ = "Jean-Luc Stevens" |
20 | 20 | |
21 | 21 | import re |
22 | import sys | |
23 | import itertools | |
22 | 24 | import textwrap |
23 | 25 | import param |
24 | 26 | |
54 | 56 | def get_param_info(self, obj, include_super=True): |
55 | 57 | """ |
56 | 58 | Get the parameter dictionary, the list of modifed parameters |
57 | and the dictionary or parameter values. If include_super is | |
59 | and the dictionary of parameter values. If include_super is | |
58 | 60 | True, parameters are also collected from the super classes. |
59 | 61 | """ |
60 | 62 | |
64 | 66 | val_dict = dict((k,p.default) for (k,p) in params.items()) |
65 | 67 | self_class = obj |
66 | 68 | else: |
67 | changed = [name for (name,_) in obj.param.get_param_values(onlychanged=True)] | |
68 | val_dict = dict(obj.param.get_param_values()) | |
69 | changed = list(obj.param.values(onlychanged=True).keys()) | |
70 | val_dict = obj.param.values() | |
69 | 71 | self_class = obj.__class__ |
70 | 72 | |
71 | 73 | if not include_super: |
85 | 87 | |
86 | 88 | (params, val_dict, changed) = info |
87 | 89 | contents = [] |
88 | displayed_params = {} | |
89 | for name, p in params.items(): | |
90 | displayed_params = [] | |
91 | for name in self.sort_by_precedence(params): | |
90 | 92 | if only_changed and not (name in changed): |
91 | 93 | continue |
92 | displayed_params[name] = p | |
93 | ||
94 | right_shift = max(len(name) for name in displayed_params.keys())+2 | |
95 | ||
96 | for i, name in enumerate(sorted(displayed_params)): | |
97 | p = displayed_params[name] | |
94 | displayed_params.append((name, params[name])) | |
95 | ||
96 | right_shift = max(len(name) for name, _ in displayed_params)+2 | |
97 | ||
98 | for i, (name, p) in enumerate(displayed_params): | |
98 | 99 | heading = "%s: " % name |
99 | 100 | unindented = textwrap.dedent("< No docstring available >" if p.doc is None else p.doc) |
100 | 101 | |
123 | 124 | return "\n".join(contents) |
124 | 125 | |
125 | 126 | |
127 | def sort_by_precedence(self, parameters): | |
128 | """ | |
129 | Sort the provided dictionary of parameters by their precedence value. | |
130 | In Python 3, preserves the original ordering for parameters with the | |
131 | same precedence; for Python 2 sorts them lexicographically by name, | |
132 | unless explicit precedences are provided. | |
133 | """ | |
134 | params = [(p, pobj) for p, pobj in parameters.items()] | |
135 | key_fn = lambda x: x[1].precedence if x[1].precedence is not None else 1e-8 | |
136 | sorted_params = sorted(params, key=key_fn) | |
137 | groups = itertools.groupby(sorted_params, key=key_fn) | |
138 | # Params preserve definition order in Python 3.6+ | |
139 | dict_ordered = ( | |
140 | (sys.version_info.major == 3 and sys.version_info.minor >= 6) or | |
141 | (sys.version_info.major > 3) or | |
142 | all(p.precedence is not None for p in parameters.values()) | |
143 | ) | |
144 | ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups] | |
145 | ordered_params = [el[0] for group in ordered_groups for el in group | |
146 | if (el[0] != 'name' or el[0] in parameters)] | |
147 | return ordered_params | |
148 | ||
149 | ||
126 | 150 | def _build_table(self, info, order, max_col_len=40, only_changed=False): |
127 | 151 | """ |
128 | 152 | Collect the information about parameters needed to build a |
129 | 153 | properly formatted table and then tabulate it. |
130 | 154 | """ |
131 | 155 | |
132 | info_dict, bounds_dict = {}, {} | |
156 | info_list, bounds_dict = [], {} | |
133 | 157 | (params, val_dict, changed) = info |
134 | 158 | col_widths = dict((k,0) for k in order) |
135 | 159 | |
136 | for name, p in params.items(): | |
160 | ordering = self.sort_by_precedence(params) | |
161 | for name in ordering: | |
162 | p = params[name] | |
137 | 163 | if only_changed and not (name in changed): |
138 | 164 | continue |
139 | 165 | |
142 | 168 | allow_None = ' AN' if hasattr(p, 'allow_None') and p.allow_None else '' |
143 | 169 | |
144 | 170 | mode = '%s %s%s' % (constant, readonly, allow_None) |
145 | info_dict[name] = {'name': name, 'type':p.__class__.__name__, | |
146 | 'mode':mode} | |
171 | ||
172 | value = repr(val_dict[name]) | |
173 | if len(value) > (max_col_len - 3): | |
174 | value = value[:max_col_len-3] + '...' | |
175 | ||
176 | p_dict = {'name': name, 'type': p.__class__.__name__, | |
177 | 'mode': mode, 'value': value} | |
147 | 178 | |
148 | 179 | if hasattr(p, 'bounds'): |
149 | 180 | lbound, ubound = (None,None) if p.bounds is None else p.bounds |
161 | 192 | |
162 | 193 | if (lbound, ubound) != (None,None): |
163 | 194 | bounds_dict[name] = (mark_lbound, mark_ubound) |
164 | info_dict[name]['bounds'] = '(%s, %s)' % (lbound, ubound) | |
165 | ||
166 | value = repr(val_dict[name]) | |
167 | if len(value) > (max_col_len - 3): | |
168 | value = value[:max_col_len-3] + '...' | |
169 | info_dict[name]['value'] = value | |
170 | ||
171 | for col in info_dict[name]: | |
172 | max_width = max([col_widths[col], len(info_dict[name][col])]) | |
195 | p_dict['bounds'] = '(%s, %s)' % (lbound, ubound) | |
196 | ||
197 | for col in p_dict: | |
198 | max_width = max([col_widths[col], len(p_dict[col])]) | |
173 | 199 | col_widths[col] = max_width |
174 | 200 | |
175 | return self._tabulate(info_dict, col_widths, changed, order, bounds_dict) | |
176 | ||
177 | ||
178 | def _tabulate(self, info_dict, col_widths, changed, order, bounds_dict): | |
201 | info_list.append((name, p_dict)) | |
202 | ||
203 | return self._tabulate(info_list, col_widths, changed, order, bounds_dict) | |
204 | ||
205 | ||
206 | def _tabulate(self, info_list, col_widths, changed, order, bounds_dict): | |
179 | 207 | """ |
180 | 208 | Returns the supplied information as a table suitable for |
181 | 209 | printing or paging. |
182 | 210 | |
183 | info_dict: Dictionary of the parameters name, type and mode. | |
211 | info_list: List of the parameters name, type and mode. | |
184 | 212 | col_widths: Dictionary of column widths in characters |
185 | 213 | changed: List of parameters modified from their defaults. |
186 | 214 | order: The order of the table columns |
188 | 216 | """ |
189 | 217 | |
190 | 218 | contents, tail = [], [] |
191 | column_set = set(k for row in info_dict.values() for k in row) | |
219 | column_set = set(k for _, row in info_list for k in row) | |
192 | 220 | columns = [col for col in order if col in column_set] |
193 | 221 | |
194 | 222 | title_row = [] |
201 | 229 | contents.append(blue % ''.join(title_row)+"\n") |
202 | 230 | |
203 | 231 | # Format the table rows |
204 | for row in sorted(info_dict): | |
232 | for row, info in info_list: | |
205 | 233 | row_list = [] |
206 | info = info_dict[row] | |
207 | 234 | for i,col in enumerate(columns): |
208 | 235 | width = col_widths[col]+2 |
209 | 236 | val = info[col] if (col in info) else '' |
257 | 284 | heading_text = "%s\n%s\n" % (title, heading_line) |
258 | 285 | |
259 | 286 | param_info = self.get_param_info(param_obj, include_super=True) |
260 | ||
261 | 287 | if not param_info[0]: |
262 | 288 | return "%s\n%s" % ((green % heading_text), "Object has no parameters.") |
263 | 289 | |
265 | 291 | only_changed=False) |
266 | 292 | |
267 | 293 | docstrings = self.param_docstrings(param_info, max_col_len=100, only_changed=False) |
268 | ||
269 | 294 | dflt_msg = "Parameters changed from their default values are marked in red." |
270 | 295 | top_heading = (green % heading_text) |
271 | 296 | top_heading += "\n%s" % (red % dflt_msg) |
278 | 303 | return "%s\n\n%s\n\n%s\n\n%s" % (top_heading, table, docstring_heading, docstrings) |
279 | 304 | |
280 | 305 | |
281 | message = """Welcome to the param IPython extension! (http://ioam.github.io/param/)""" | |
306 | message = """Welcome to the param IPython extension! (https://param.holoviz.org/)""" | |
282 | 307 | message += '\nAvailable magics: %params' |
283 | 308 | |
284 | 309 | _loaded = False |
0 | 0 | """ |
1 | 1 | Generic support for objects with full-featured Parameters and |
2 | 2 | messaging. |
3 | ||
4 | This file comes from the Param library (https://github.com/holoviz/param) | |
5 | but can be taken out of the param module and used on its own if desired, | |
6 | either alone (providing basic Parameter support) or with param's | |
7 | __init__.py (providing specialized Parameter types). | |
3 | 8 | """ |
4 | 9 | |
5 | 10 | import copy |
10 | 15 | import numbers |
11 | 16 | import operator |
12 | 17 | |
13 | from collections import namedtuple, OrderedDict | |
18 | # Allow this file to be used standalone if desired, albeit without JSON serialization | |
19 | try: | |
20 | from . import serializer | |
21 | except ImportError: | |
22 | serializer = None | |
23 | ||
24 | ||
25 | from collections import defaultdict, namedtuple, OrderedDict | |
26 | from functools import partial, wraps, reduce | |
14 | 27 | from operator import itemgetter,attrgetter |
15 | 28 | from types import FunctionType |
16 | from functools import partial, wraps, reduce | |
17 | 29 | |
18 | 30 | import logging |
19 | 31 | from contextlib import contextmanager |
25 | 37 | param_pager = ParamPager(metaclass=True) # Generates param description |
26 | 38 | except: |
27 | 39 | param_pager = None |
40 | ||
41 | try: | |
42 | from inspect import getfullargspec | |
43 | except: | |
44 | from inspect import getargspec as getfullargspec # python2 | |
28 | 45 | |
29 | 46 | basestring = basestring if sys.version_info[0]==2 else str # noqa: it is defined |
30 | 47 | |
64 | 81 | object_count = 0 |
65 | 82 | warning_count = 0 |
66 | 83 | |
84 | class _Undefined: | |
85 | """ | |
86 | Dummy value to signal completely undefined values rather than | |
87 | simple None values. | |
88 | """ | |
67 | 89 | |
68 | 90 | @contextmanager |
69 | 91 | def logging_level(level): |
87 | 109 | |
88 | 110 | |
89 | 111 | @contextmanager |
90 | def batch_watch(parameterized, enable=True, run=True): | |
91 | """ | |
92 | Context manager to batch watcher events on a parameterized object. | |
93 | The context manager will queue any events triggered by setting a | |
94 | parameter on the supplied parameterized object and dispatch them | |
95 | all at once when the context manager exits. If run=False the | |
96 | queued events are not dispatched and should be processed manually. | |
112 | def _batch_call_watchers(parameterized, enable=True, run=True): | |
113 | """ | |
114 | Internal version of batch_call_watchers, adding control over queueing and running. | |
115 | Only actually batches events if enable=True; otherwise a no-op. Only actually | |
116 | calls the accumulated watchers on exit if run=True; otherwise they remain queued. | |
97 | 117 | """ |
98 | 118 | BATCH_WATCH = parameterized.param._BATCH_WATCH |
99 | 119 | parameterized.param._BATCH_WATCH = enable or parameterized.param._BATCH_WATCH |
102 | 122 | finally: |
103 | 123 | parameterized.param._BATCH_WATCH = BATCH_WATCH |
104 | 124 | if run and not BATCH_WATCH: |
125 | parameterized.param._batch_call_watchers() | |
126 | ||
127 | batch_watch = _batch_call_watchers # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later. | |
128 | ||
129 | @contextmanager | |
130 | def batch_call_watchers(parameterized): | |
131 | """ | |
132 | Context manager to batch events to provide to Watchers on a | |
133 | parameterized object. This context manager queues any events | |
134 | triggered by setting a parameter on the supplied parameterized | |
135 | object, saving them up to dispatch them all at once when the | |
136 | context manager exits. | |
137 | """ | |
138 | BATCH_WATCH = parameterized.param._BATCH_WATCH | |
139 | parameterized.param._BATCH_WATCH = True | |
140 | try: | |
141 | yield | |
142 | finally: | |
143 | parameterized.param._BATCH_WATCH = BATCH_WATCH | |
144 | if not BATCH_WATCH: | |
105 | 145 | parameterized.param._batch_call_watchers() |
106 | 146 | |
107 | 147 | |
132 | 172 | """ |
133 | 173 | batch_watch = parameterized.param._BATCH_WATCH |
134 | 174 | parameterized.param._BATCH_WATCH = True |
135 | watchers, events = parameterized.param._watchers, parameterized.param._events | |
175 | watchers, events = (list(parameterized.param._watchers), | |
176 | list(parameterized.param._events)) | |
136 | 177 | try: |
137 | 178 | yield |
138 | 179 | except: |
143 | 184 | parameterized.param._events = events |
144 | 185 | |
145 | 186 | |
187 | # External components can register an async executor which will run | |
188 | # async functions | |
189 | async_executor = None | |
190 | ||
191 | ||
146 | 192 | def classlist(class_): |
147 | 193 | """ |
148 | 194 | Return a list of the class hierarchy above (and including) the given class. |
149 | 195 | |
150 | Same as inspect.getmro(class_)[::-1] | |
196 | Same as `inspect.getmro(class_)[::-1]` | |
151 | 197 | """ |
152 | 198 | return inspect.getmro(class_)[::-1] |
153 | 199 | |
173 | 219 | |
174 | 220 | def get_all_slots(class_): |
175 | 221 | """ |
176 | Return a list of slot names for slots defined in class_ and its | |
222 | Return a list of slot names for slots defined in `class_` and its | |
177 | 223 | superclasses. |
178 | 224 | """ |
179 | 225 | # A subclass's __slots__ attribute does not contain slots defined |
217 | 263 | return arg1==arg2 |
218 | 264 | |
219 | 265 | |
220 | ||
221 | # For Python 2 compatibility. | |
266 | # PARAM2_DEPRECATION: For Python 2 compatibility only; can be removed in param2. | |
222 | 267 | # |
223 | 268 | # The syntax to use a metaclass changed incompatibly between 2 and |
224 | 269 | # 3. The add_metaclass() class decorator below creates a class using a |
239 | 284 | return wrapper |
240 | 285 | |
241 | 286 | |
287 | ||
242 | 288 | class bothmethod(object): # pylint: disable-msg=R0903 |
243 | 289 | """ |
244 | 290 | 'optional @classmethod' |
269 | 315 | return reduce(_getattr, [obj] + attr.split('.')) |
270 | 316 | |
271 | 317 | |
272 | # (thought I was going to have a few decorators following this pattern) | |
273 | 318 | def accept_arguments(f): |
319 | """ | |
320 | Decorator for decorators that accept arguments | |
321 | """ | |
274 | 322 | @wraps(f) |
275 | 323 | def _f(*args, **kwargs): |
276 | 324 | return lambda actual_f: f(actual_f, *args, **kwargs) |
285 | 333 | return cls |
286 | 334 | |
287 | 335 | |
336 | def iscoroutinefunction(function): | |
337 | """ | |
338 | Whether the function is an asynchronous coroutine function. | |
339 | """ | |
340 | if not hasattr(inspect, 'iscoroutinefunction'): | |
341 | return False | |
342 | import asyncio | |
343 | try: | |
344 | return ( | |
345 | inspect.isasyncgenfunction(function) or | |
346 | asyncio.iscoroutinefunction(function) | |
347 | ) | |
348 | except AttributeError: | |
349 | return False | |
350 | ||
351 | ||
288 | 352 | def instance_descriptor(f): |
289 | # If parameter has an instance Parameter delegate setting | |
353 | # If parameter has an instance Parameter, delegate setting | |
290 | 354 | def _f(self, obj, val): |
291 | 355 | instance_param = getattr(obj, '_instance__params', {}).get(self.name) |
292 | 356 | if instance_param is not None and self is not instance_param: |
296 | 360 | return _f |
297 | 361 | |
298 | 362 | |
363 | def get_method_owner(method): | |
364 | """ | |
365 | Gets the instance that owns the supplied method | |
366 | """ | |
367 | if not inspect.ismethod(method): | |
368 | return None | |
369 | if isinstance(method, partial): | |
370 | method = method.func | |
371 | return method.__self__ if sys.version_info.major >= 3 else method.im_self | |
372 | ||
373 | ||
299 | 374 | @accept_arguments |
300 | 375 | def depends(func, *dependencies, **kw): |
301 | 376 | """ |
308 | 383 | on Parameter values, or on other metadata about the Parameter. |
309 | 384 | """ |
310 | 385 | |
311 | # python3 would allow kw-only args | |
312 | # (i.e. "func,*dependencies,watch=False" rather than **kw and the check below) | |
313 | watch = kw.pop("watch",False) | |
314 | ||
315 | @wraps(func) | |
316 | def _depends(*args,**kw): | |
317 | return func(*args,**kw) | |
386 | # PARAM2_DEPRECATION: python2 workaround; python3 allows kw-only args | |
387 | # (i.e. "func, *dependencies, watch=False" rather than **kw and the check below) | |
388 | watch = kw.pop("watch", False) | |
389 | on_init = kw.pop("on_init", False) | |
390 | ||
391 | if iscoroutinefunction(func): | |
392 | import asyncio | |
393 | @asyncio.coroutine | |
394 | def _depends(*args, **kw): | |
395 | yield from func(*args, **kw) | |
396 | else: | |
397 | @wraps(func) | |
398 | def _depends(*args, **kw): | |
399 | return func(*args, **kw) | |
318 | 400 | |
319 | 401 | deps = list(dependencies)+list(kw.values()) |
320 | 402 | string_specs = False |
345 | 427 | 'or function is not supported when referencing ' |
346 | 428 | 'parameters by name.') |
347 | 429 | |
348 | if not string_specs and watch: | |
349 | def cb(event): | |
350 | args = (getattr(dep.owner, dep.name) for dep in dependencies) | |
351 | dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()} | |
352 | return func(*args, **dep_kwargs) | |
353 | ||
430 | if not string_specs and watch: # string_specs case handled elsewhere (later), in Parameterized.__init__ | |
431 | if iscoroutinefunction(func): | |
432 | import asyncio | |
433 | @asyncio.coroutine | |
434 | def cb(*events): | |
435 | args = (getattr(dep.owner, dep.name) for dep in dependencies) | |
436 | dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()} | |
437 | yield from func(*args, **dep_kwargs) | |
438 | else: | |
439 | def cb(*events): | |
440 | args = (getattr(dep.owner, dep.name) for dep in dependencies) | |
441 | dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()} | |
442 | return func(*args, **dep_kwargs) | |
443 | ||
444 | grouped = defaultdict(list) | |
354 | 445 | for dep in deps: |
355 | dep.owner.param.watch(cb, dep.name) | |
446 | grouped[id(dep.owner)].append(dep) | |
447 | for group in grouped.values(): | |
448 | group[0].owner.param.watch(cb, [dep.name for dep in group]) | |
356 | 449 | |
357 | 450 | _dinfo = getattr(func, '_dinfo', {}) |
358 | 451 | _dinfo.update({'dependencies': dependencies, |
359 | 'kw': kw, 'watch': watch}) | |
452 | 'kw': kw, 'watch': watch, 'on_init': on_init}) | |
360 | 453 | |
361 | 454 | _depends._dinfo = _dinfo |
362 | 455 | |
458 | 551 | return _output |
459 | 552 | |
460 | 553 | |
461 | def _params_depended_on(minfo): | |
462 | params = [] | |
463 | dinfo = getattr(minfo.method,"_dinfo", {}) | |
554 | def _parse_dependency_spec(spec): | |
555 | """ | |
556 | Parses param.depends specifications into three components: | |
557 | ||
558 | 1. The dotted path to the sub-object | |
559 | 2. The attribute being depended on, i.e. either a parameter or method | |
560 | 3. The parameter attribute being depended on | |
561 | """ | |
562 | assert spec.count(":")<=1 | |
563 | spec = spec.strip() | |
564 | m = re.match("(?P<path>[^:]*):?(?P<what>.*)", spec) | |
565 | what = m.group('what') | |
566 | path = "."+m.group('path') | |
567 | m = re.match(r"(?P<obj>.*)(\.)(?P<attr>.*)", path) | |
568 | obj = m.group('obj') | |
569 | attr = m.group("attr") | |
570 | return obj or None, attr, what or 'value' | |
571 | ||
572 | ||
573 | def _params_depended_on(minfo, dynamic=True, intermediate=True): | |
574 | """ | |
575 | Resolves dependencies declared on a Parameterized method. | |
576 | Dynamic dependencies, i.e. dependencies on sub-objects which may | |
577 | or may not yet be available, are only resolved if dynamic=True. | |
578 | By default intermediate dependencies, i.e. dependencies on the | |
579 | path to a sub-object are returned. For example for a dependency | |
580 | on 'a.b.c' dependencies on 'a' and 'b' are returned as long as | |
581 | intermediate=True. | |
582 | ||
583 | Returns lists of concrete dependencies on available parameters | |
584 | and dynamic dependencies specifications which have to resolved | |
585 | if the referenced sub-objects are defined. | |
586 | """ | |
587 | deps, dynamic_deps = [], [] | |
588 | dinfo = getattr(minfo.method, "_dinfo", {}) | |
464 | 589 | for d in dinfo.get('dependencies', list(minfo.cls.param)): |
465 | things = (minfo.inst or minfo.cls).param._spec_to_obj(d) | |
466 | for thing in things: | |
467 | if isinstance(thing,PInfo): | |
468 | params.append(thing) | |
590 | ddeps, ddynamic_deps = (minfo.inst or minfo.cls).param._spec_to_obj(d, dynamic, intermediate) | |
591 | dynamic_deps += ddynamic_deps | |
592 | for dep in ddeps: | |
593 | if isinstance(dep, PInfo): | |
594 | deps.append(dep) | |
469 | 595 | else: |
470 | params += _params_depended_on(thing) | |
471 | return params | |
472 | ||
473 | ||
474 | def _m_caller(self,n): | |
475 | return lambda event: getattr(self,n)() | |
476 | ||
477 | ||
478 | PInfo = namedtuple("PInfo","inst cls name pobj what") | |
479 | MInfo = namedtuple("MInfo","inst cls name method") | |
480 | Event = namedtuple("Event","what name obj cls old new type") | |
481 | Watcher = namedtuple("Watcher","inst cls fn mode onlychanged parameter_names what queued") | |
596 | method_deps, method_dynamic_deps = _params_depended_on(dep, dynamic, intermediate) | |
597 | deps += method_deps | |
598 | dynamic_deps += method_dynamic_deps | |
599 | return deps, dynamic_deps | |
600 | ||
601 | ||
602 | def _resolve_mcs_deps(obj, resolved, dynamic, intermediate=True): | |
603 | """ | |
604 | Resolves constant and dynamic parameter dependencies previously | |
605 | obtained using the _params_depended_on function. Existing resolved | |
606 | dependencies are updated with a supplied parameter instance while | |
607 | dynamic dependencies are resolved if possible. | |
608 | """ | |
609 | dependencies = [] | |
610 | for dep in resolved: | |
611 | if not issubclass(type(obj), dep.cls): | |
612 | dependencies.append(dep) | |
613 | continue | |
614 | inst = obj if dep.inst is None else dep.inst | |
615 | dep = PInfo(inst=inst, cls=dep.cls, name=dep.name, | |
616 | pobj=inst.param[dep.name], what=dep.what) | |
617 | dependencies.append(dep) | |
618 | for dep in dynamic: | |
619 | subresolved, _ = obj.param._spec_to_obj(dep.spec, intermediate=intermediate) | |
620 | for subdep in subresolved: | |
621 | if isinstance(subdep, PInfo): | |
622 | dependencies.append(subdep) | |
623 | else: | |
624 | dependencies += _params_depended_on(subdep, intermediate=intermediate)[0] | |
625 | return dependencies | |
626 | ||
627 | ||
628 | def _skip_event(*events, **kwargs): | |
629 | """ | |
630 | Checks whether a subobject event should be skipped. | |
631 | Returns True if all the values on the new subobject | |
632 | match the values on the previous subobject. | |
633 | """ | |
634 | what = kwargs.get('what', 'value') | |
635 | changed = kwargs.get('changed') | |
636 | if changed is None: | |
637 | return False | |
638 | for e in events: | |
639 | for p in changed: | |
640 | if what == 'value': | |
641 | old = _Undefined if e.old is None else _getattrr(e.old, p, None) | |
642 | new = _Undefined if e.new is None else _getattrr(e.new, p, None) | |
643 | else: | |
644 | old = _Undefined if e.old is None else _getattrr(e.old.param[p], what, None) | |
645 | new = _Undefined if e.new is None else _getattrr(e.new.param[p], what, None) | |
646 | if not Comparator.is_equal(old, new): | |
647 | return False | |
648 | return True | |
649 | ||
650 | ||
651 | def _m_caller(self, method_name, what='value', changed=None, callback=None): | |
652 | """ | |
653 | Wraps a method call adding support for scheduling a callback | |
654 | before it is executed and skipping events if a subobject has | |
655 | changed but its values have not. | |
656 | """ | |
657 | function = getattr(self, method_name) | |
658 | if iscoroutinefunction(function): | |
659 | import asyncio | |
660 | @asyncio.coroutine | |
661 | def caller(*events): | |
662 | if callback: callback(*events) | |
663 | if not _skip_event(*events, what=what, changed=changed): | |
664 | yield from function() | |
665 | else: | |
666 | def caller(*events): | |
667 | if callback: callback(*events) | |
668 | if not _skip_event(*events, what=what, changed=changed): | |
669 | return function() | |
670 | caller._watcher_name = method_name | |
671 | return caller | |
672 | ||
673 | ||
674 | def _add_doc(obj, docstring): | |
675 | """Add a docstring to a namedtuple, if on python3 where that's allowed""" | |
676 | if sys.version_info[0]>2: | |
677 | obj.__doc__ = docstring | |
678 | ||
679 | ||
680 | PInfo = namedtuple("PInfo", "inst cls name pobj what"); _add_doc(PInfo, | |
681 | """ | |
682 | Object describing something being watched about a Parameter. | |
683 | ||
684 | `inst`: Parameterized instance owning the Parameter, or None | |
685 | ||
686 | `cls`: Parameterized class owning the Parameter | |
687 | ||
688 | `name`: Name of the Parameter being watched | |
689 | ||
690 | `pobj`: Parameter object being watched | |
691 | ||
692 | `what`: What is being watched on the Parameter (either 'value' or a slot name) | |
693 | """) | |
694 | ||
695 | MInfo = namedtuple("MInfo", "inst cls name method"); _add_doc(MInfo, | |
696 | """ | |
697 | Object describing a Parameterized method being watched. | |
698 | ||
699 | `inst`: Parameterized instance owning the method, or None | |
700 | ||
701 | `cls`: Parameterized class owning the method | |
702 | ||
703 | `name`: Name of the method being watched | |
704 | ||
705 | `method`: bound method of the object being watched | |
706 | """) | |
707 | ||
708 | DInfo = namedtuple("DInfo", "spec"); _add_doc(DInfo, | |
709 | """ | |
710 | Object describing dynamic dependencies. | |
711 | `spec`: Dependency specification to resolve | |
712 | """) | |
713 | ||
714 | Event = namedtuple("Event", "what name obj cls old new type"); _add_doc(Event, | |
715 | """ | |
716 | Object representing an event that triggers a Watcher. | |
717 | ||
718 | `what`: What is being watched on the Parameter (either value or a slot name) | |
719 | ||
720 | `name`: Name of the Parameter that was set or triggered | |
721 | ||
722 | `obj`: Parameterized instance owning the watched Parameter, or None | |
723 | ||
724 | `cls`: Parameterized class owning the watched Parameter | |
725 | ||
726 | `old`: Previous value of the item being watched | |
727 | ||
728 | `new`: New value of the item being watched | |
729 | ||
730 | `type`: `triggered` if this event was triggered explicitly), `changed` if | |
731 | the item was set and watching for `onlychanged`, `set` if the item was set, | |
732 | or None if type not yet known | |
733 | """) | |
734 | ||
735 | _Watcher = namedtuple("Watcher", "inst cls fn mode onlychanged parameter_names what queued precedence") | |
736 | ||
737 | class Watcher(_Watcher): | |
738 | """ | |
739 | Object declaring a callback function to invoke when an Event is | |
740 | triggered on a watched item. | |
741 | ||
742 | `inst`: Parameterized instance owning the watched Parameter, or | |
743 | None | |
744 | ||
745 | `cls`: Parameterized class owning the watched Parameter | |
746 | ||
747 | `fn`: Callback function to invoke when triggered by a watched | |
748 | Parameter | |
749 | ||
750 | `mode`: 'args' for param.watch (call `fn` with PInfo object | |
751 | positional args), or 'kwargs' for param.watch_values (call `fn` | |
752 | with <param_name>:<new_value> keywords) | |
753 | ||
754 | `onlychanged`: If True, only trigger for actual changes, not | |
755 | setting to the current value | |
756 | ||
757 | `parameter_names`: List of Parameters to watch, by name | |
758 | ||
759 | `what`: What to watch on the Parameters (either 'value' or a slot | |
760 | name) | |
761 | ||
762 | `queued`: Immediately invoke callbacks triggered during processing | |
763 | of an Event (if False), or queue them up for processing | |
764 | later, after this event has been handled (if True) | |
765 | ||
766 | `precedence`: A numeric value which determines the precedence of | |
767 | the watcher. Lower precedence values are executed | |
768 | with higher priority. | |
769 | """ | |
770 | ||
771 | def __new__(cls_, *args, **kwargs): | |
772 | """ | |
773 | Allows creating Watcher without explicit precedence value. | |
774 | """ | |
775 | values = dict(zip(cls_._fields, args)) | |
776 | values.update(kwargs) | |
777 | if 'precedence' not in values: | |
778 | values['precedence'] = 0 | |
779 | return super(Watcher, cls_).__new__(cls_, **values) | |
780 | ||
781 | def __iter__(self): | |
782 | """ | |
783 | Backward compatibility layer to allow tuple unpacking without | |
784 | the precedence value. Important for Panel which creates a | |
785 | custom Watcher and uses tuple unpacking. Will be dropped in | |
786 | Param 3.x. | |
787 | """ | |
788 | return iter(self[:-1]) | |
789 | ||
790 | def __str__(self): | |
791 | cls = type(self) | |
792 | attrs = ', '.join(['%s=%r' % (f, getattr(self, f)) for f in cls._fields]) | |
793 | return "{cls}({attrs})".format(cls=cls.__name__, attrs=attrs) | |
794 | ||
795 | ||
796 | ||
482 | 797 | |
483 | 798 | class ParameterMetaclass(type): |
484 | 799 | """ |
485 | 800 | Metaclass allowing control over creation of Parameter classes. |
486 | 801 | """ |
487 | 802 | def __new__(mcs,classname,bases,classdict): |
803 | ||
488 | 804 | # store the class's docstring in __classdoc |
489 | 805 | if '__doc__' in classdict: |
490 | 806 | classdict['__classdoc']=classdict['__doc__'] |
491 | # when asking for help on Parameter *object*, return the doc | |
492 | # slot | |
807 | ||
808 | # when asking for help on Parameter *object*, return the doc slot | |
493 | 809 | classdict['__doc__']=property(attrgetter('doc')) |
494 | 810 | |
495 | 811 | # To get the benefit of slots, subclasses must themselves define |
498 | 814 | # a __dict__ unless it also defines __slots__. |
499 | 815 | if '__slots__' not in classdict: |
500 | 816 | classdict['__slots__']=[] |
817 | ||
818 | # No special handling for a __dict__ slot; should there be? | |
501 | 819 | |
502 | 820 | return type.__new__(mcs,classname,bases,classdict) |
503 | 821 | |
511 | 829 | |
512 | 830 | |
513 | 831 | |
514 | # CEBALERT: we break some aspects of slot handling for Parameter and | |
515 | # Parameterized. The __new__ methods in the metaclasses for those two | |
516 | # classes omit to handle the case where __dict__ is passed in | |
517 | # __slots__ (and they possibly omit other things too). Additionally, | |
518 | # various bits of code in the Parameterized class assumes that all | |
519 | # Parameterized instances have a __dict__, but I'm not sure that's | |
520 | # guaranteed to be true (although it's true at the moment). | |
521 | ||
522 | ||
523 | # CB: we could maybe reduce the complexity by doing something to allow | |
524 | # a parameter to discover things about itself when created (would also | |
525 | # allow things like checking a Parameter is owned by a | |
526 | # Parameterized). I have some vague ideas about what to do. | |
527 | 832 | @add_metaclass(ParameterMetaclass) |
528 | 833 | class Parameter(object): |
529 | 834 | """ |
541 | 846 | objects Foo and Bar, such that Bar has a parameter delta, Foo is a |
542 | 847 | subclass of Bar, and Foo has parameters alpha, sigma, and gamma |
543 | 848 | (and delta inherited from Bar). She would begin her class |
544 | definitions with something like this: | |
545 | ||
546 | class Bar(Parameterized): | |
547 | delta = Parameter(default=0.6, doc='The difference between steps.') | |
548 | ... | |
549 | ||
550 | class Foo(Bar): | |
551 | alpha = Parameter(default=0.1, doc='The starting value.') | |
552 | sigma = Parameter(default=0.5, doc='The standard deviation.', | |
553 | constant=True) | |
554 | gamma = Parameter(default=1.0, doc='The ending value.') | |
555 | ... | |
849 | definitions with something like this:: | |
850 | ||
851 | class Bar(Parameterized): | |
852 | delta = Parameter(default=0.6, doc='The difference between steps.') | |
853 | ... | |
854 | class Foo(Bar): | |
855 | alpha = Parameter(default=0.1, doc='The starting value.') | |
856 | sigma = Parameter(default=0.5, doc='The standard deviation.', | |
857 | constant=True) | |
858 | gamma = Parameter(default=1.0, doc='The ending value.') | |
859 | ... | |
556 | 860 | |
557 | 861 | Class Foo would then have four parameters, with delta defaulting |
558 | 862 | to 0.6. |
563 | 867 | constructed: The default constructor for Foo (and Bar) will |
564 | 868 | accept arbitrary keyword arguments, each of which can be used |
565 | 869 | to specify the value of a Parameter of Foo (or any of Foo's |
566 | superclasses). E.g., if a script does this: | |
870 | superclasses). E.g., if a script does this:: | |
567 | 871 | |
568 | 872 | myfoo = Foo(alpha=0.5) |
569 | 873 | |
580 | 884 | 2. A Parameterized class need specify only the attributes of a |
581 | 885 | Parameter whose values differ from those declared in |
582 | 886 | superclasses; the other values will be inherited. E.g. if Foo |
583 | declares | |
887 | declares:: | |
584 | 888 | |
585 | 889 | delta = Parameter(default=0.2) |
586 | 890 | |
659 | 963 | # attributes. Using __slots__ requires special support for |
660 | 964 | # operations to copy and restore Parameters (e.g. for Python |
661 | 965 | # persistent storage pickling); see __getstate__ and __setstate__. |
662 | __slots__ = ['name','_internal_name','default','doc', | |
663 | 'precedence','instantiate','constant','readonly', | |
664 | 'pickle_default_value','allow_None', 'per_instance', | |
966 | __slots__ = ['name', '_internal_name', 'default', 'doc', | |
967 | 'precedence', 'instantiate', 'constant', 'readonly', | |
968 | 'pickle_default_value', 'allow_None', 'per_instance', | |
665 | 969 | 'watchers', 'owner', '_label'] |
666 | 970 | |
667 | 971 | # Note: When initially created, a Parameter does not know which |
670 | 974 | # class is created, owner, name, and _internal_name are |
671 | 975 | # set. |
672 | 976 | |
673 | def __init__(self,default=None,doc=None,label=None,precedence=None, # pylint: disable-msg=R0913 | |
674 | instantiate=False,constant=False,readonly=False, | |
977 | _serializers = {'json': serializer.JSONSerialization} | |
978 | ||
979 | def __init__(self,default=None, doc=None, label=None, precedence=None, # pylint: disable-msg=R0913 | |
980 | instantiate=False, constant=False, readonly=False, | |
675 | 981 | pickle_default_value=True, allow_None=False, |
676 | 982 | per_instance=True): |
677 | """ | |
678 | Initialize a new Parameter object: store the supplied attributes. | |
679 | ||
680 | default: the owning class's value for the attribute | |
681 | represented by this Parameter. | |
682 | ||
683 | precedence is a value, usually in the range 0.0 to 1.0, that | |
684 | allows the order of Parameters in a class to be defined (for | |
685 | e.g. in GUI menus). A negative precedence indicates a | |
686 | parameter that should be hidden in e.g. GUI menus. | |
687 | ||
688 | default, doc, and precedence default to None. This is to allow | |
983 | ||
984 | """Initialize a new Parameter object and store the supplied attributes: | |
985 | ||
986 | default: the owning class's value for the attribute represented | |
987 | by this Parameter, which can be overridden in an instance. | |
988 | ||
989 | doc: docstring explaining what this parameter represents. | |
990 | ||
991 | label: optional text label to be used when this Parameter is | |
992 | shown in a listing. If no label is supplied, the attribute name | |
993 | for this parameter in the owning Parameterized object is used. | |
994 | ||
995 | precedence: a numeric value, usually in the range 0.0 to 1.0, | |
996 | which allows the order of Parameters in a class to be defined in | |
997 | a listing or e.g. in GUI menus. A negative precedence indicates | |
998 | a parameter that should be hidden in such listings. | |
999 | ||
1000 | instantiate: controls whether the value of this Parameter will | |
1001 | be deepcopied when a Parameterized object is instantiated (if | |
1002 | True), or if the single default value will be shared by all | |
1003 | Parameterized instances (if False). For an immutable Parameter | |
1004 | value, it is best to leave instantiate at the default of | |
1005 | False, so that a user can choose to change the value at the | |
1006 | Parameterized instance level (affecting only that instance) or | |
1007 | at the Parameterized class or superclass level (affecting all | |
1008 | existing and future instances of that class or superclass). For | |
1009 | a mutable Parameter value, the default of False is also appropriate | |
1010 | if you want all instances to share the same value state, e.g. if | |
1011 | they are each simply referring to a single global object like | |
1012 | a singleton. If instead each Parameterized should have its own | |
1013 | independently mutable value, instantiate should be set to | |
1014 | True, but note that there is then no simple way to change the | |
1015 | value of this Parameter at the class or superclass level, | |
1016 | because each instance, once created, will then have an | |
1017 | independently instantiated value. | |
1018 | ||
1019 | constant: if true, the Parameter value can be changed only at | |
1020 | the class level or in a Parameterized constructor call. The | |
1021 | value is otherwise constant on the Parameterized instance, | |
1022 | once it has been constructed. | |
1023 | ||
1024 | readonly: if true, the Parameter value cannot ordinarily be | |
1025 | changed by setting the attribute at the class or instance | |
1026 | levels at all. The value can still be changed in code by | |
1027 | temporarily overriding the value of this slot and then | |
1028 | restoring it, which is useful for reporting values that the | |
1029 | _user_ should never change but which do change during code | |
1030 | execution. | |
1031 | ||
1032 | pickle_default_value: whether the default value should be | |
1033 | pickled. Usually, you would want the default value to be pickled, | |
1034 | but there are rare cases where that would not be the case (e.g. | |
1035 | for file search paths that are specific to a certain system). | |
1036 | ||
1037 | per_instance: whether a separate Parameter instance will be | |
1038 | created for every Parameterized instance. True by default. | |
1039 | If False, all instances of a Parameterized class will share | |
1040 | the same Parameter object, including all validation | |
1041 | attributes (bounds, etc.). See also instantiate, which is | |
1042 | conceptually similar but affects the Parameter value rather | |
1043 | than the Parameter object. | |
1044 | ||
1045 | allow_None: if True, None is accepted as a valid value for | |
1046 | this Parameter, in addition to any other values that are | |
1047 | allowed. If the default value is defined as None, allow_None | |
1048 | is set to True automatically. | |
1049 | ||
1050 | default, doc, and precedence all default to None, which allows | |
689 | 1051 | inheritance of Parameter slots (attributes) from the owning-class' |
690 | 1052 | class hierarchy (see ParameterizedMetaclass). |
691 | ||
692 | per_instance defaults to True and controls whether a new | |
693 | Parameter instance can be created for every Parameterized | |
694 | instance. If False, all instances of a Parameterized class | |
695 | will share the same parameter object, including all validation | |
696 | attributes. | |
697 | ||
698 | In rare cases where the default value should not be pickled, | |
699 | set pickle_default_value=False (e.g. for file search paths). | |
700 | """ | |
1053 | """ | |
1054 | ||
701 | 1055 | self.name = None |
702 | self._internal_name = None | |
703 | 1056 | self.owner = None |
704 | self._label = label | |
705 | 1057 | self.precedence = precedence |
706 | 1058 | self.default = default |
707 | 1059 | self.doc = doc |
708 | 1060 | self.constant = constant or readonly # readonly => constant |
709 | 1061 | self.readonly = readonly |
1062 | self._label = label | |
1063 | self._internal_name = None | |
710 | 1064 | self._set_instantiate(instantiate) |
711 | 1065 | self.pickle_default_value = pickle_default_value |
712 | 1066 | self.allow_None = (default is None or allow_None) |
713 | 1067 | self.watchers = {} |
714 | 1068 | self.per_instance = per_instance |
715 | 1069 | |
1070 | @classmethod | |
1071 | def serialize(cls, value): | |
1072 | "Given the parameter value, return a Python value suitable for serialization" | |
1073 | return value | |
1074 | ||
1075 | @classmethod | |
1076 | def deserialize(cls, value): | |
1077 | "Given a serializable Python value, return a value that the parameter can be set to" | |
1078 | return value | |
1079 | ||
1080 | def schema(self, safe=False, subset=None, mode='json'): | |
1081 | if serializer is None: | |
1082 | raise ImportError('Cannot import serializer.py needed to generate schema') | |
1083 | if mode not in self._serializers: | |
1084 | raise KeyError('Mode %r not in available serialization formats %r' | |
1085 | % (mode, list(self._serializers.keys()))) | |
1086 | return self._serializers[mode].param_schema(self.__class__.__name__, self, | |
1087 | safe=safe, subset=subset) | |
716 | 1088 | |
717 | 1089 | @property |
718 | 1090 | def label(self): |
727 | 1099 | |
728 | 1100 | def _set_instantiate(self,instantiate): |
729 | 1101 | """Constant parameters must be instantiated.""" |
730 | # CB: instantiate doesn't actually matter for read-only | |
1102 | # instantiate doesn't actually matter for read-only | |
731 | 1103 | # parameters, since they can't be set even on a class. But |
732 | # this avoids needless instantiation. | |
1104 | # having this code avoids needless instantiation. | |
733 | 1105 | if self.readonly: |
734 | 1106 | self.instantiate = False |
735 | 1107 | else: |
736 | 1108 | self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201 |
737 | 1109 | |
738 | ||
739 | # TODO: quick trick to allow subscription to the setting of | |
740 | # parameter metadata. ParameterParameter? | |
741 | ||
742 | # Note that unlike with parameter value setting, there's no access | |
743 | # to the Parameterized instance, so no per-instance subscription. | |
744 | ||
745 | def __setattr__(self,attribute,value): | |
746 | implemented = (attribute!="default" and hasattr(self,'watchers') and attribute in self.watchers) | |
1110 | def __setattr__(self, attribute, value): | |
1111 | if attribute == 'name' and getattr(self, 'name', None) and value != self.name: | |
1112 | raise AttributeError("Parameter name cannot be modified after " | |
1113 | "it has been bound to a Parameterized.") | |
1114 | ||
1115 | implemented = (attribute != "default" and hasattr(self, 'watchers') and attribute in self.watchers) | |
1116 | slot_attribute = attribute in self.__slots__ | |
747 | 1117 | try: |
748 | old = getattr(self,attribute) if implemented else NotImplemented | |
1118 | old = getattr(self, attribute) if implemented else NotImplemented | |
1119 | if slot_attribute: | |
1120 | self._on_set(attribute, old, value) | |
749 | 1121 | except AttributeError as e: |
750 | if attribute in self.__slots__: | |
1122 | if slot_attribute: | |
751 | 1123 | # If Parameter slot is defined but an AttributeError was raised |
752 | 1124 | # we are in __setstate__ and watchers should not be triggered |
753 | 1125 | old = NotImplemented |
756 | 1128 | |
757 | 1129 | super(Parameter, self).__setattr__(attribute, value) |
758 | 1130 | |
759 | if old is not NotImplemented: | |
760 | event = Event(what=attribute,name=self.name,obj=None,cls=self.owner,old=old,new=value, type=None) | |
761 | for watcher in self.watchers[attribute]: | |
762 | self.owner.param._call_watcher(watcher, event) | |
763 | if not self.owner.param._BATCH_WATCH: | |
764 | self.owner.param._batch_call_watchers() | |
765 | ||
766 | ||
767 | def __get__(self,obj,objtype): # pylint: disable-msg=W0613 | |
1131 | if old is NotImplemented: | |
1132 | return | |
1133 | ||
1134 | event = Event(what=attribute, name=self.name, obj=None, cls=self.owner, | |
1135 | old=old, new=value, type=None) | |
1136 | for watcher in self.watchers[attribute]: | |
1137 | self.owner.param._call_watcher(watcher, event) | |
1138 | if not self.owner.param._BATCH_WATCH: | |
1139 | self.owner.param._batch_call_watchers() | |
1140 | ||
1141 | def _on_set(self, attribute, old, value): | |
1142 | """ | |
1143 | Can be overridden on subclasses to handle changes when parameter | |
1144 | attribute is set. | |
1145 | """ | |
1146 | ||
1147 | def __get__(self, obj, objtype): # pylint: disable-msg=W0613 | |
768 | 1148 | """ |
769 | 1149 | Return the value for this Parameter. |
770 | 1150 | |
776 | 1156 | instance's value, if one has been set - otherwise produce the |
777 | 1157 | class's value (default). |
778 | 1158 | """ |
779 | # NB: obj can be None (when __get__ called for a | |
780 | # Parameterized class); objtype is never None | |
781 | ||
782 | if obj is None: | |
1159 | if obj is None: # e.g. when __get__ called for a Parameterized class | |
783 | 1160 | result = self.default |
784 | 1161 | else: |
785 | 1162 | result = obj.__dict__.get(self._internal_name,self.default) |
786 | 1163 | return result |
787 | 1164 | |
788 | ||
789 | 1165 | @instance_descriptor |
790 | def __set__(self,obj,val): | |
1166 | def __set__(self, obj, val): | |
791 | 1167 | """ |
792 | 1168 | Set the value for this Parameter. |
793 | 1169 | |
797 | 1173 | If called for a Parameterized instance, set the value of |
798 | 1174 | this Parameter on that instance (i.e. in the instance's |
799 | 1175 | __dict__, under the parameter's internal_name). |
800 | ||
801 | 1176 | |
802 | 1177 | If the Parameter's constant attribute is True, only allows |
803 | 1178 | the value to be set for a Parameterized class or on |
810 | 1185 | |
811 | 1186 | Note that until we support some form of read-only |
812 | 1187 | object, it is still possible to change the attributes of the |
813 | object stored in a constant or read-only Parameter (e.g. the | |
814 | left bound of a BoundingBox). | |
815 | """ | |
816 | ||
817 | # ALERT: Deprecated Number set_hook called here to avoid duplicating | |
818 | # setter, should be removed in 2.0 | |
1188 | object stored in a constant or read-only Parameter (e.g. one | |
1189 | item in a list). | |
1190 | """ | |
1191 | ||
1192 | # PARAM2_DEPRECATION: For Python 2 compatibility only; | |
1193 | # Deprecated Number set_hook called here to avoid duplicating setter | |
819 | 1194 | if hasattr(self, 'set_hook'): |
820 | 1195 | val = self.set_hook(obj,val) |
821 | 1196 | |
822 | 1197 | self._validate(val) |
823 | 1198 | |
824 | 1199 | _old = NotImplemented |
825 | # NB: obj can be None (when __set__ called for a | |
826 | # Parameterized class) | |
1200 | # obj can be None if __set__ is called for a Parameterized class | |
827 | 1201 | if self.constant or self.readonly: |
828 | 1202 | if self.readonly: |
829 | raise TypeError("Read-only parameter '%s' cannot be modified"%self.name) | |
830 | elif obj is None: #not obj | |
1203 | raise TypeError("Read-only parameter '%s' cannot be modified" % self.name) | |
1204 | elif obj is None: | |
831 | 1205 | _old = self.default |
832 | 1206 | self.default = val |
833 | 1207 | elif not obj.initialized: |
834 | _old = obj.__dict__.get(self._internal_name,self.default) | |
1208 | _old = obj.__dict__.get(self._internal_name, self.default) | |
835 | 1209 | obj.__dict__[self._internal_name] = val |
836 | 1210 | else: |
837 | raise TypeError("Constant parameter '%s' cannot be modified"%self.name) | |
838 | ||
1211 | _old = obj.__dict__.get(self._internal_name, self.default) | |
1212 | if val is not _old: | |
1213 | raise TypeError("Constant parameter '%s' cannot be modified"%self.name) | |
839 | 1214 | else: |
840 | 1215 | if obj is None: |
841 | 1216 | _old = self.default |
842 | 1217 | self.default = val |
843 | 1218 | else: |
844 | _old = obj.__dict__.get(self._internal_name,self.default) | |
1219 | _old = obj.__dict__.get(self._internal_name, self.default) | |
845 | 1220 | obj.__dict__[self._internal_name] = val |
846 | 1221 | |
847 | 1222 | self._post_setter(obj, val) |
848 | 1223 | |
1224 | if obj is not None: | |
1225 | if not getattr(obj, 'initialized', False): | |
1226 | return | |
1227 | obj.param._update_deps(self.name) | |
1228 | ||
849 | 1229 | if obj is None: |
850 | watchers = self.watchers.get("value",[]) | |
1230 | watchers = self.watchers.get("value") | |
1231 | elif hasattr(obj, '_param_watchers') and self.name in obj._param_watchers: | |
1232 | watchers = obj._param_watchers[self.name].get('value') | |
1233 | if watchers is None: | |
1234 | watchers = self.watchers.get("value") | |
851 | 1235 | else: |
852 | watchers = getattr(obj,"_param_watchers",{}).get(self.name,{}).get('value',self.watchers.get("value",[])) | |
853 | ||
854 | event = Event(what='value',name=self.name,obj=obj,cls=self.owner,old=_old,new=val, type=None) | |
1236 | watchers = None | |
1237 | ||
855 | 1238 | obj = self.owner if obj is None else obj |
856 | if obj is None: | |
1239 | ||
1240 | if obj is None or not watchers: | |
857 | 1241 | return |
858 | 1242 | |
859 | for watcher in watchers: | |
1243 | event = Event(what='value', name=self.name, obj=obj, cls=self.owner, | |
1244 | old=_old, new=val, type=None) | |
1245 | ||
1246 | # Copy watchers here since they may be modified inplace during iteration | |
1247 | for watcher in sorted(watchers, key=lambda w: w.precedence): | |
860 | 1248 | obj.param._call_watcher(watcher, event) |
861 | 1249 | if not obj.param._BATCH_WATCH: |
862 | 1250 | obj.param._batch_call_watchers() |
863 | 1251 | |
1252 | def _validate_value(self, value, allow_None): | |
1253 | """Implements validation for parameter value""" | |
864 | 1254 | |
865 | 1255 | def _validate(self, val): |
866 | """Implements validation for the parameter""" | |
867 | ||
1256 | """Implements validation for the parameter value and attributes""" | |
1257 | self._validate_value(val, self.allow_None) | |
868 | 1258 | |
869 | 1259 | def _post_setter(self, obj, val): |
870 | 1260 | """Called after the parameter value has been validated and set""" |
871 | 1261 | |
872 | ||
873 | 1262 | def __delete__(self,obj): |
874 | 1263 | raise TypeError("Cannot delete '%s': Parameters deletion not allowed." % self.name) |
875 | ||
876 | 1264 | |
877 | 1265 | def _set_names(self, attrib_name): |
878 | 1266 | if None not in (self.owner, self.name) and attrib_name != self.name: |
885 | 1273 | % (type(self).__name__, self.name, |
886 | 1274 | self.owner.name, attrib_name)) |
887 | 1275 | self.name = attrib_name |
888 | ||
889 | self._internal_name = "_%s_param_value"%attrib_name | |
890 | ||
1276 | self._internal_name = "_%s_param_value" % attrib_name | |
891 | 1277 | |
892 | 1278 | def __getstate__(self): |
893 | 1279 | """ |
920 | 1306 | |
921 | 1307 | # Define one particular type of Parameter that is used in this file |
922 | 1308 | class String(Parameter): |
923 | """ | |
1309 | r""" | |
924 | 1310 | A String Parameter, with a default value and optional regular expression (regex) matching. |
925 | 1311 | |
926 | 1312 | Example of using a regex to implement IPv4 address matching:: |
928 | 1314 | class IPAddress(String): |
929 | 1315 | '''IPv4 address as a string (dotted decimal notation)''' |
930 | 1316 | def __init__(self, default="0.0.0.0", allow_None=False, **kwargs): |
931 | ip_regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' | |
1317 | ip_regex = r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' | |
932 | 1318 | super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs) |
933 | ||
934 | 1319 | |
935 | 1320 | """ |
936 | 1321 | |
942 | 1327 | self.allow_None = (default is None or allow_None) |
943 | 1328 | self._validate(default) |
944 | 1329 | |
1330 | def _validate_regex(self, val, regex): | |
1331 | if (val is None and self.allow_None): | |
1332 | return | |
1333 | if regex is not None and re.match(regex, val) is None: | |
1334 | raise ValueError("String parameter %r value %r does not match regex %r." | |
1335 | % (self.name, val, regex)) | |
1336 | ||
1337 | def _validate_value(self, val, allow_None): | |
1338 | if allow_None and val is None: | |
1339 | return | |
1340 | if not isinstance(val, basestring): | |
1341 | raise ValueError("String parameter %r only takes a string value, " | |
1342 | "not value of type %s." % (self.name, type(val))) | |
1343 | ||
945 | 1344 | def _validate(self, val): |
946 | if self.allow_None and val is None: | |
947 | return | |
948 | ||
949 | if not isinstance(val, basestring): | |
950 | raise ValueError("String '%s' only takes a string value."%self.name) | |
951 | ||
952 | if self.regex is not None and re.match(self.regex, val) is None: | |
953 | raise ValueError("String '%s': '%s' does not match regex '%s'."%(self.name,val,self.regex)) | |
1345 | self._validate_value(val, self.allow_None) | |
1346 | self._validate_regex(val, self.regex) | |
954 | 1347 | |
955 | 1348 | |
956 | 1349 | class shared_parameters(object): |
985 | 1378 | @wraps(fn) |
986 | 1379 | def override_initialization(self_,*args,**kw): |
987 | 1380 | parameterized_instance = self_.self |
988 | original_initialized=parameterized_instance.initialized | |
989 | parameterized_instance.initialized=False | |
990 | fn(parameterized_instance,*args,**kw) | |
991 | parameterized_instance.initialized=original_initialized | |
1381 | original_initialized = parameterized_instance.initialized | |
1382 | parameterized_instance.initialized = False | |
1383 | fn(parameterized_instance, *args, **kw) | |
1384 | parameterized_instance.initialized = original_initialized | |
992 | 1385 | return override_initialization |
993 | 1386 | |
994 | 1387 | |
1061 | 1454 | class or the instance as necessary. |
1062 | 1455 | """ |
1063 | 1456 | |
1064 | _disable_stubs = None # Flag used to disable stubs in the API1 tests | |
1457 | _disable_stubs = False # Flag used to disable stubs in the API1 tests | |
1065 | 1458 | # None for no action, True to raise and False to warn. |
1066 | 1459 | |
1067 | 1460 | def __init__(self_, cls, self=None): |
1071 | 1464 | """ |
1072 | 1465 | self_.cls = cls |
1073 | 1466 | self_.self = self |
1074 | self_._BATCH_WATCH = False # If true, Event and watcher objects are queued. | |
1075 | self_._TRIGGER = False | |
1076 | self_._events = [] # Queue of batched eventd | |
1077 | self_._watchers = [] # Queue of batched watchers | |
1467 | ||
1468 | @property | |
1469 | def _BATCH_WATCH(self_): | |
1470 | return self_.self_or_cls._parameters_state['BATCH_WATCH'] | |
1471 | ||
1472 | @_BATCH_WATCH.setter | |
1473 | def _BATCH_WATCH(self_, value): | |
1474 | self_.self_or_cls._parameters_state['BATCH_WATCH'] = value | |
1475 | ||
1476 | @property | |
1477 | def _TRIGGER(self_): | |
1478 | return self_.self_or_cls._parameters_state['TRIGGER'] | |
1479 | ||
1480 | @_TRIGGER.setter | |
1481 | def _TRIGGER(self_, value): | |
1482 | self_.self_or_cls._parameters_state['TRIGGER'] = value | |
1483 | ||
1484 | @property | |
1485 | def _events(self_): | |
1486 | return self_.self_or_cls._parameters_state['events'] | |
1487 | ||
1488 | @_events.setter | |
1489 | def _events(self_, value): | |
1490 | self_.self_or_cls._parameters_state['events'] = value | |
1491 | ||
1492 | @property | |
1493 | def _watchers(self_): | |
1494 | return self_.self_or_cls._parameters_state['watchers'] | |
1495 | ||
1496 | @_watchers.setter | |
1497 | def _watchers(self_, value): | |
1498 | self_.self_or_cls._parameters_state['watchers'] = value | |
1499 | ||
1500 | @property | |
1501 | def watchers(self): | |
1502 | """Read-only list of watchers on this Parameterized""" | |
1503 | return self._watchers | |
1078 | 1504 | |
1079 | 1505 | @property |
1080 | 1506 | def self_or_cls(self_): |
1081 | 1507 | return self_.cls if self_.self is None else self_.self |
1082 | 1508 | |
1509 | def __setstate__(self, state): | |
1510 | # Set old parameters state on Parameterized._parameters_state | |
1511 | self_or_cls = state.get('self', state.get('cls')) | |
1512 | for k in self_or_cls._parameters_state: | |
1513 | key = '_'+k | |
1514 | if key in state: | |
1515 | self_or_cls._parameters_state[k] = state.pop(key) | |
1516 | for k, v in state.items(): | |
1517 | setattr(self, k, v) | |
1083 | 1518 | |
1084 | 1519 | def __getitem__(self_, key): |
1085 | 1520 | """ |
1088 | 1523 | inst = self_.self |
1089 | 1524 | parameters = self_.objects(False) if inst is None else inst.param.objects(False) |
1090 | 1525 | p = parameters[key] |
1091 | if (inst is not None and p.per_instance and | |
1526 | if (inst is not None and getattr(inst, 'initialized', False) and p.per_instance and | |
1092 | 1527 | not getattr(inst, '_disable_instance__params', False)): |
1093 | 1528 | if key not in inst._instance__params: |
1094 | 1529 | try: |
1099 | 1534 | except: |
1100 | 1535 | raise |
1101 | 1536 | finally: |
1102 | p.watchers = watchers | |
1537 | p.watchers = {k: list(v) for k, v in watchers.items()} | |
1103 | 1538 | p.owner = inst |
1104 | 1539 | inst._instance__params[key] = p |
1105 | 1540 | else: |
1170 | 1605 | First, ensures that all Parameters with 'instantiate=True' |
1171 | 1606 | (typically used for mutable Parameters) are copied directly |
1172 | 1607 | into each object, to ensure that there is an independent copy |
1173 | (to avoid suprising aliasing errors). Then sets each of the | |
1608 | (to avoid surprising aliasing errors). Then sets each of the | |
1174 | 1609 | keyword arguments, warning when any of them are not defined as |
1175 | 1610 | parameters. |
1176 | 1611 | |
1178 | 1613 | """ |
1179 | 1614 | self = self_.param.self |
1180 | 1615 | ## Deepcopy all 'instantiate=True' parameters |
1181 | # (build a set of names first to avoid redundantly instantiating | |
1182 | # a later-overridden parent class's parameter) | |
1616 | # (building a set of names first to avoid redundantly | |
1617 | # instantiating a later-overridden parent class's parameter) | |
1183 | 1618 | params_to_instantiate = {} |
1184 | 1619 | for class_ in classlist(type(self)): |
1185 | 1620 | if not issubclass(class_, Parameterized): |
1186 | 1621 | continue |
1187 | for (k,v) in class_.__dict__.items(): | |
1622 | for (k, v) in class_.param._parameters.items(): | |
1188 | 1623 | # (avoid replacing name with the default of None) |
1189 | if isinstance(v,Parameter) and v.instantiate and k!="name": | |
1190 | params_to_instantiate[k]=v | |
1624 | if v.instantiate and k != "name": | |
1625 | params_to_instantiate[k] = v | |
1191 | 1626 | |
1192 | 1627 | for p in params_to_instantiate.values(): |
1193 | 1628 | self.param._instantiate_param(p) |
1194 | 1629 | |
1195 | 1630 | ## keyword arg setting |
1196 | for name,val in params.items(): | |
1631 | for name, val in params.items(): | |
1197 | 1632 | desc = self.__class__.get_param_descriptor(name)[0] # pylint: disable-msg=E1101 |
1198 | 1633 | if not desc: |
1199 | self.param.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters",name,val) | |
1634 | self.param.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters", name, val) | |
1200 | 1635 | # i.e. if not desc it's setting an attribute in __dict__, not a Parameter |
1201 | setattr(self,name,val) | |
1202 | ||
1636 | setattr(self, name, val) | |
1637 | ||
1638 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1203 | 1639 | @classmethod |
1204 | 1640 | def deprecate(cls, fn): |
1205 | 1641 | """ |
1230 | 1666 | return not Comparator.is_equal(event.old, event.new) |
1231 | 1667 | |
1232 | 1668 | |
1233 | # CEBALERT: this is a bit ugly | |
1234 | def _instantiate_param(self_,param_obj,dict_=None,key=None): | |
1669 | def _instantiate_param(self_, param_obj, dict_=None, key=None): | |
1235 | 1670 | # deepcopy param_obj.default into self.__dict__ (or dict_ if supplied) |
1236 | 1671 | # under the parameter's _internal_name (or key if supplied) |
1237 | 1672 | self = self_.self |
1238 | 1673 | dict_ = dict_ or self.__dict__ |
1239 | 1674 | key = key or param_obj._internal_name |
1240 | param_key = (str(type(self)), param_obj.name) | |
1241 | 1675 | if shared_parameters._share: |
1676 | param_key = (str(type(self)), param_obj.name) | |
1242 | 1677 | if param_key in shared_parameters._shared_cache: |
1243 | 1678 | new_object = shared_parameters._shared_cache[param_key] |
1244 | 1679 | else: |
1246 | 1681 | shared_parameters._shared_cache[param_key] = new_object |
1247 | 1682 | else: |
1248 | 1683 | new_object = copy.deepcopy(param_obj.default) |
1249 | dict_[key]=new_object | |
1250 | ||
1251 | if isinstance(new_object,Parameterized): | |
1684 | ||
1685 | dict_[key] = new_object | |
1686 | ||
1687 | if isinstance(new_object, Parameterized): | |
1252 | 1688 | global object_count |
1253 | object_count+=1 | |
1254 | # CB: writes over name given to the original object; | |
1255 | # should it instead keep the same name? | |
1689 | object_count += 1 | |
1690 | # Writes over name given to the original object; | |
1691 | # could instead have kept the same name | |
1256 | 1692 | new_object.param._generate_name() |
1257 | 1693 | |
1694 | def _update_deps(self_, attribute=None, init=False): | |
1695 | obj = self_.self | |
1696 | init_methods = [] | |
1697 | for method, queued, on_init, constant, dynamic in type(obj).param._depends['watch']: | |
1698 | # On initialization set up constant watchers; otherwise | |
1699 | # clean up previous dynamic watchers for the updated attribute | |
1700 | dynamic = [d for d in dynamic if attribute is None or d.spec.startswith(attribute)] | |
1701 | if init: | |
1702 | constant_grouped = defaultdict(list) | |
1703 | for dep in _resolve_mcs_deps(obj, constant, []): | |
1704 | constant_grouped[(id(dep.inst), id(dep.cls), dep.what)].append((None, dep)) | |
1705 | for group in constant_grouped.values(): | |
1706 | self_._watch_group(obj, method, queued, group) | |
1707 | m = getattr(self_.self, method) | |
1708 | if on_init and m not in init_methods: | |
1709 | init_methods.append(m) | |
1710 | elif dynamic: | |
1711 | for w in obj._dynamic_watchers.pop(method, []): | |
1712 | (w.inst or w.cls).param.unwatch(w) | |
1713 | else: | |
1714 | continue | |
1715 | ||
1716 | # Resolve dynamic dependencies one-by-one to be able to trace their watchers | |
1717 | grouped = defaultdict(list) | |
1718 | for ddep in dynamic: | |
1719 | for dep in _resolve_mcs_deps(obj, [], [ddep]): | |
1720 | grouped[(id(dep.inst), id(dep.cls), dep.what)].append((ddep, dep)) | |
1721 | ||
1722 | for group in grouped.values(): | |
1723 | watcher = self_._watch_group(obj, method, queued, group, attribute) | |
1724 | obj._dynamic_watchers[method].append(watcher) | |
1725 | for m in init_methods: | |
1726 | m() | |
1727 | ||
1728 | def _resolve_dynamic_deps(self, obj, dynamic_dep, param_dep, attribute): | |
1729 | """ | |
1730 | If a subobject whose parameters are being depended on changes | |
1731 | we should only trigger events if the actual parameter values | |
1732 | of the new object differ from those on the old subobject, | |
1733 | therefore we accumulate parameters to compare on a subobject | |
1734 | change event. | |
1735 | ||
1736 | Additionally we need to make sure to notify the parent object | |
1737 | if a subobject changes so the dependencies can be | |
1738 | reinitialized so we return a callback which updates the | |
1739 | dependencies. | |
1740 | """ | |
1741 | subobj = obj | |
1742 | subobjs = [obj] | |
1743 | for subpath in dynamic_dep.spec.split('.')[:-1]: | |
1744 | subobj = getattr(subobj, subpath.split(':')[0], None) | |
1745 | subobjs.append(subobj) | |
1746 | ||
1747 | dep_obj = (param_dep.inst or param_dep.cls) | |
1748 | if dep_obj not in subobjs[:-1]: | |
1749 | return None, None, param_dep.what | |
1750 | ||
1751 | depth = subobjs.index(dep_obj) | |
1752 | callback = None | |
1753 | if depth > 0: | |
1754 | def callback(*events): | |
1755 | """ | |
1756 | If a subobject changes, we need to notify the main | |
1757 | object to update the dependencies. | |
1758 | """ | |
1759 | obj.param._update_deps(attribute) | |
1760 | ||
1761 | p = '.'.join(dynamic_dep.spec.split(':')[0].split('.')[depth+1:]) | |
1762 | if p == 'param': | |
1763 | subparams = [sp for sp in list(subobjs[-1].param)] | |
1764 | else: | |
1765 | subparams = [p] | |
1766 | ||
1767 | if ':' in dynamic_dep.spec: | |
1768 | what = dynamic_dep.spec.split(':')[-1] | |
1769 | else: | |
1770 | what = param_dep.what | |
1771 | ||
1772 | return subparams, callback, what | |
1773 | ||
1774 | def _watch_group(self_, obj, name, queued, group, attribute=None): | |
1775 | """ | |
1776 | Sets up a watcher for a group of dependencies. Ensures that | |
1777 | if the dependency was dynamically generated we check whether | |
1778 | a subobject change event actually causes a value change and | |
1779 | that we update the existing watchers, i.e. clean up watchers | |
1780 | on the old subobject and create watchers on the new subobject. | |
1781 | """ | |
1782 | dynamic_dep, param_dep = group[0] | |
1783 | dep_obj = (param_dep.inst or param_dep.cls) | |
1784 | params = [] | |
1785 | for _, g in group: | |
1786 | if g.name not in params: | |
1787 | params.append(g.name) | |
1788 | ||
1789 | if dynamic_dep is None: | |
1790 | subparams, callback, what = None, None, param_dep.what | |
1791 | else: | |
1792 | subparams, callback, what = self_._resolve_dynamic_deps( | |
1793 | obj, dynamic_dep, param_dep, attribute) | |
1794 | ||
1795 | mcaller = _m_caller(obj, name, what, subparams, callback) | |
1796 | return dep_obj.param._watch( | |
1797 | mcaller, params, param_dep.what, queued=queued, precedence=-1) | |
1798 | ||
1258 | 1799 | # Classmethods |
1259 | 1800 | |
1801 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1260 | 1802 | def print_param_defaults(self_): |
1261 | 1803 | """Print the default values of all cls's Parameters.""" |
1262 | 1804 | cls = self_.cls |
1265 | 1807 | print(cls.__name__+'.'+key+ '='+ repr(val.default)) |
1266 | 1808 | |
1267 | 1809 | |
1810 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1268 | 1811 | def set_default(self_,param_name,value): |
1269 | 1812 | """ |
1270 | 1813 | Set the default value of param_name. |
1275 | 1818 | setattr(cls,param_name,value) |
1276 | 1819 | |
1277 | 1820 | |
1278 | def _add_parameter(self_, param_name,param_obj): | |
1821 | def add_parameter(self_, param_name, param_obj): | |
1279 | 1822 | """ |
1280 | 1823 | Add a new Parameter object into this object's class. |
1281 | 1824 | |
1282 | Supposed to result in a Parameter equivalent to one declared | |
1825 | Should result in a Parameter equivalent to one declared | |
1283 | 1826 | in the class's source code. |
1284 | 1827 | """ |
1285 | # CEBALERT: can't we just do | |
1286 | # setattr(cls,param_name,param_obj)? The metaclass's | |
1287 | # __setattr__ is actually written to handle that. (Would also | |
1288 | # need to do something about the params() cache. That cache | |
1289 | # is a pain, but it definitely improved the startup time; it | |
1290 | # would be worthwhile making sure no method except for one | |
1291 | # "add_param()" method has to deal with it (plus any future | |
1292 | # remove_param() method.) | |
1828 | # Could have just done setattr(cls,param_name,param_obj), | |
1829 | # which is supported by the metaclass's __setattr__ , but | |
1830 | # would need to handle the params() cache as well | |
1831 | # (which is tricky but important for startup speed). | |
1293 | 1832 | cls = self_.cls |
1294 | 1833 | type.__setattr__(cls,param_name,param_obj) |
1295 | 1834 | ParameterizedMetaclass._initialize_parameter(cls,param_name,param_obj) |
1299 | 1838 | except AttributeError: |
1300 | 1839 | pass |
1301 | 1840 | |
1302 | ||
1841 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1842 | _add_parameter = add_parameter | |
1843 | ||
1844 | ||
1845 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1303 | 1846 | def params(self_, parameter_name=None): |
1304 | 1847 | """ |
1305 | 1848 | Return the Parameters of this class as the |
1308 | 1851 | Includes Parameters from this class and its |
1309 | 1852 | superclasses. |
1310 | 1853 | """ |
1311 | if self_.self is not None and self_.self._instance__params: | |
1312 | self_.warning('The Parameterized instance has instance ' | |
1313 | 'parameters created using new-style param ' | |
1314 | 'APIs, which are incompatible with .params. ' | |
1315 | 'Use the new more explicit APIs on the ' | |
1316 | '.param accessor to query parameter instances.' | |
1317 | 'To query all parameter instances use ' | |
1318 | '.param.objects with the option to return ' | |
1319 | 'either class or instance parameter objects. ' | |
1320 | 'Alternatively use .param[name] indexing to ' | |
1321 | 'access a specific parameter object by name.') | |
1322 | ||
1323 | 1854 | pdict = self_.objects(instance='existing') |
1324 | 1855 | if parameter_name is None: |
1325 | 1856 | return pdict |
1328 | 1859 | |
1329 | 1860 | # Bothmethods |
1330 | 1861 | |
1331 | def set_param(self_, *args,**kwargs): | |
1332 | """ | |
1333 | For each param=value keyword argument, sets the corresponding | |
1334 | parameter of this object or class to the given value. | |
1335 | ||
1336 | For backwards compatibility, also accepts | |
1337 | set_param("param",value) for a single parameter value using | |
1338 | positional arguments, but the keyword interface is preferred | |
1339 | because it is more compact and can set multiple values. | |
1862 | def update(self_, *args, **kwargs): | |
1863 | """ | |
1864 | For the given dictionary or iterable or set of param=value keyword arguments, | |
1865 | sets the corresponding parameter of this object or class to the given value. | |
1340 | 1866 | """ |
1341 | 1867 | BATCH_WATCH = self_.self_or_cls.param._BATCH_WATCH |
1342 | 1868 | self_.self_or_cls.param._BATCH_WATCH = True |
1343 | 1869 | self_or_cls = self_.self_or_cls |
1344 | 1870 | if args: |
1345 | if len(args) == 2 and not args[0] in kwargs and not kwargs: | |
1346 | kwargs[args[0]] = args[1] | |
1871 | if len(args) == 1 and not kwargs: | |
1872 | kwargs = args[0] | |
1347 | 1873 | else: |
1348 | 1874 | self_.self_or_cls.param._BATCH_WATCH = False |
1349 | raise ValueError("Invalid positional arguments for %s.set_param" % | |
1875 | raise ValueError("%s.update accepts *either* an iterable or key=value pairs, not both" % | |
1350 | 1876 | (self_or_cls.name)) |
1877 | ||
1878 | trigger_params = [k for k in kwargs | |
1879 | if ((k in self_.self_or_cls.param) and | |
1880 | hasattr(self_.self_or_cls.param[k], '_autotrigger_value'))] | |
1881 | ||
1882 | for tp in trigger_params: | |
1883 | self_.self_or_cls.param[tp]._mode = 'set' | |
1351 | 1884 | |
1352 | 1885 | for (k, v) in kwargs.items(): |
1353 | 1886 | if k not in self_or_cls.param: |
1363 | 1896 | if not BATCH_WATCH: |
1364 | 1897 | self_._batch_call_watchers() |
1365 | 1898 | |
1899 | for tp in trigger_params: | |
1900 | p = self_.self_or_cls.param[tp] | |
1901 | p._mode = 'reset' | |
1902 | setattr(self_or_cls, tp, p._autotrigger_reset_value) | |
1903 | p._mode = 'set-reset' | |
1904 | ||
1905 | ||
1906 | # PARAM2_DEPRECATION: Could be removed post param 2.0; use update() instead. | |
1907 | def set_param(self_, *args,**kwargs): | |
1908 | """ | |
1909 | For each param=value keyword argument, sets the corresponding | |
1910 | parameter of this object or class to the given value. | |
1911 | ||
1912 | For backwards compatibility, also accepts | |
1913 | set_param("param",value) for a single parameter value using | |
1914 | positional arguments, but the keyword interface is preferred | |
1915 | because it is more compact and can set multiple values. | |
1916 | """ | |
1917 | self_or_cls = self_.self_or_cls | |
1918 | if args: | |
1919 | if len(args) == 2 and not args[0] in kwargs and not kwargs: | |
1920 | kwargs[args[0]] = args[1] | |
1921 | else: | |
1922 | raise ValueError("Invalid positional arguments for %s.set_param" % | |
1923 | (self_or_cls.name)) | |
1924 | return self_.update(kwargs) | |
1925 | ||
1366 | 1926 | |
1367 | 1927 | def objects(self_, instance=True): |
1368 | 1928 | """ |
1377 | 1937 | instance='existing'. |
1378 | 1938 | """ |
1379 | 1939 | cls = self_.cls |
1380 | # CB: we cache the parameters because this method is called often, | |
1940 | # We cache the parameters because this method is called often, | |
1381 | 1941 | # and parameters are rarely added (and cannot be deleted) |
1382 | 1942 | try: |
1383 | 1943 | pdict = getattr(cls, '_%s__params' % cls.__name__) |
1397 | 1957 | |
1398 | 1958 | if instance and self_.self is not None: |
1399 | 1959 | if instance == 'existing': |
1400 | if self_.self._instance__params: | |
1960 | if getattr(self_.self, 'initialized', False) and self_.self._instance__params: | |
1401 | 1961 | return dict(pdict, **self_.self._instance__params) |
1402 | 1962 | return pdict |
1403 | 1963 | else: |
1409 | 1969 | """ |
1410 | 1970 | Trigger watchers for the given set of parameter names. Watchers |
1411 | 1971 | will be triggered whether or not the parameter values have |
1412 | actually changed. | |
1413 | """ | |
1972 | actually changed. As a special case, the value will actually be | |
1973 | changed for a Parameter of type Event, setting it to True so | |
1974 | that it is clear which Event parameter has been triggered. | |
1975 | """ | |
1976 | trigger_params = [p for p in self_.self_or_cls.param | |
1977 | if hasattr(self_.self_or_cls.param[p], '_autotrigger_value')] | |
1978 | triggers = {p:self_.self_or_cls.param[p]._autotrigger_value | |
1979 | for p in trigger_params if p in param_names} | |
1980 | ||
1414 | 1981 | events = self_.self_or_cls.param._events |
1415 | 1982 | watchers = self_.self_or_cls.param._watchers |
1416 | 1983 | self_.self_or_cls.param._events = [] |
1417 | 1984 | self_.self_or_cls.param._watchers = [] |
1418 | param_values = dict(self_.get_param_values()) | |
1985 | param_values = self_.values() | |
1419 | 1986 | params = {name: param_values[name] for name in param_names} |
1420 | 1987 | self_.self_or_cls.param._TRIGGER = True |
1421 | self_.set_param(**params) | |
1988 | self_.set_param(**dict(params, **triggers)) | |
1422 | 1989 | self_.self_or_cls.param._TRIGGER = False |
1423 | 1990 | self_.self_or_cls.param._events += events |
1424 | 1991 | self_.self_or_cls.param._watchers += watchers |
1435 | 2002 | return Event(what=event.what, name=event.name, obj=event.obj, cls=event.cls, |
1436 | 2003 | old=event.old, new=event.new, type=event_type) |
1437 | 2004 | |
2005 | def _execute_watcher(self, watcher, events): | |
2006 | if watcher.mode == 'args': | |
2007 | args, kwargs = events, {} | |
2008 | else: | |
2009 | args, kwargs = (), {event.name: event.new for event in events} | |
2010 | ||
2011 | if iscoroutinefunction(watcher.fn): | |
2012 | if async_executor is None: | |
2013 | raise RuntimeError("Could not execute %s coroutine function. " | |
2014 | "Please register a asynchronous executor on " | |
2015 | "param.parameterized.async_executor, which " | |
2016 | "schedules the function on an event loop." % | |
2017 | watcher.fn) | |
2018 | async_executor(partial(watcher.fn, *args, **kwargs)) | |
2019 | else: | |
2020 | watcher.fn(*args, **kwargs) | |
2021 | ||
1438 | 2022 | def _call_watcher(self_, watcher, event): |
1439 | 2023 | """ |
1440 | Invoke the given the watcher appropriately given a Event object. | |
2024 | Invoke the given watcher appropriately given an Event object. | |
1441 | 2025 | """ |
1442 | 2026 | if self_.self_or_cls.param._TRIGGER: |
1443 | 2027 | pass |
1446 | 2030 | |
1447 | 2031 | if self_.self_or_cls.param._BATCH_WATCH: |
1448 | 2032 | self_._events.append(event) |
1449 | if watcher not in self_._watchers: | |
2033 | if not any(watcher is w for w in self_._watchers): | |
1450 | 2034 | self_._watchers.append(watcher) |
1451 | 2035 | else: |
1452 | 2036 | event = self_._update_event_type(watcher, event, self_.self_or_cls.param._TRIGGER) |
1453 | with batch_watch(self_.self_or_cls, enable=watcher.queued, run=False): | |
1454 | if watcher.mode == 'args': | |
1455 | watcher.fn(event) | |
1456 | else: | |
1457 | watcher.fn(**{event.name: event.new}) | |
1458 | ||
2037 | with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False): | |
2038 | self_._execute_watcher(watcher, (event,)) | |
1459 | 2039 | |
1460 | 2040 | def _batch_call_watchers(self_): |
1461 | 2041 | """ |
1469 | 2049 | self_.self_or_cls.param._events = [] |
1470 | 2050 | self_.self_or_cls.param._watchers = [] |
1471 | 2051 | |
1472 | for watcher in watchers: | |
2052 | for watcher in sorted(watchers, key=lambda w: w.precedence): | |
1473 | 2053 | events = [self_._update_event_type(watcher, event_dict[(name, watcher.what)], |
1474 | 2054 | self_.self_or_cls.param._TRIGGER) |
1475 | 2055 | for name in watcher.parameter_names |
1476 | 2056 | if (name, watcher.what) in event_dict] |
1477 | with batch_watch(self_.self_or_cls, enable=watcher.queued, run=False): | |
1478 | if watcher.mode == 'args': | |
1479 | watcher.fn(*events) | |
1480 | else: | |
1481 | watcher.fn(**{c.name:c.new for c in events}) | |
1482 | ||
2057 | with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False): | |
2058 | self_._execute_watcher(watcher, events) | |
1483 | 2059 | |
1484 | 2060 | def set_dynamic_time_fn(self_,time_fn,sublistattr=None): |
1485 | 2061 | """ |
1522 | 2098 | for obj in sublist: |
1523 | 2099 | obj.param.set_dynamic_time_fn(time_fn,sublistattr) |
1524 | 2100 | |
1525 | def get_param_values(self_,onlychanged=False): | |
1526 | """ | |
2101 | def serialize_parameters(self_, subset=None, mode='json'): | |
2102 | self_or_cls = self_.self_or_cls | |
2103 | if mode not in Parameter._serializers: | |
2104 | raise ValueError('Mode %r not in available serialization formats %r' | |
2105 | % (mode, list(Parameter._serializers.keys()))) | |
2106 | serializer = Parameter._serializers[mode] | |
2107 | return serializer.serialize_parameters(self_or_cls, subset=subset) | |
2108 | ||
2109 | def serialize_value(self_, pname, mode='json'): | |
2110 | self_or_cls = self_.self_or_cls | |
2111 | if mode not in Parameter._serializers: | |
2112 | raise ValueError('Mode %r not in available serialization formats %r' | |
2113 | % (mode, list(Parameter._serializers.keys()))) | |
2114 | serializer = Parameter._serializers[mode] | |
2115 | return serializer.serialize_parameter_value(self_or_cls, pname) | |
2116 | ||
2117 | def deserialize_parameters(self_, serialization, subset=None, mode='json'): | |
2118 | self_or_cls = self_.self_or_cls | |
2119 | serializer = Parameter._serializers[mode] | |
2120 | return serializer.deserialize_parameters(self_or_cls, serialization, subset=subset) | |
2121 | ||
2122 | def deserialize_value(self_, pname, value, mode='json'): | |
2123 | self_or_cls = self_.self_or_cls | |
2124 | if mode not in Parameter._serializers: | |
2125 | raise ValueError('Mode %r not in available serialization formats %r' | |
2126 | % (mode, list(Parameter._serializers.keys()))) | |
2127 | serializer = Parameter._serializers[mode] | |
2128 | return serializer.deserialize_parameter_value(self_or_cls, pname, value) | |
2129 | ||
2130 | def schema(self_, safe=False, subset=None, mode='json'): | |
2131 | """ | |
2132 | Returns a schema for the parameters on this Parameterized object. | |
2133 | """ | |
2134 | self_or_cls = self_.self_or_cls | |
2135 | if mode not in Parameter._serializers: | |
2136 | raise ValueError('Mode %r not in available serialization formats %r' | |
2137 | % (mode, list(Parameter._serializers.keys()))) | |
2138 | serializer = Parameter._serializers[mode] | |
2139 | return serializer.schema(self_or_cls, safe=safe, subset=subset) | |
2140 | ||
2141 | # PARAM2_DEPRECATION: Could be removed post param 2.0; same as values() but returns list, not dict | |
2142 | def get_param_values(self_, onlychanged=False): | |
2143 | """ | |
2144 | (Deprecated; use .values() instead.) | |
2145 | ||
1527 | 2146 | Return a list of name,value pairs for all Parameters of this |
1528 | 2147 | object. |
1529 | 2148 | |
1532 | 2151 | (onlychanged has no effect when called on a class). |
1533 | 2152 | """ |
1534 | 2153 | self_or_cls = self_.self_or_cls |
1535 | # CEB: we'd actually like to know whether a value has been | |
1536 | # explicitly set on the instance, but I'm not sure that's easy | |
1537 | # (would need to distinguish instantiation of default from | |
1538 | # user setting of value). | |
1539 | 2154 | vals = [] |
1540 | for name,val in self_or_cls.param.objects('existing').items(): | |
2155 | for name, val in self_or_cls.param.objects('existing').items(): | |
1541 | 2156 | value = self_or_cls.param.get_value_generator(name) |
1542 | # (this is pointless for cls) | |
1543 | if not onlychanged or not all_equal(value,val.default): | |
1544 | vals.append((name,value)) | |
2157 | if not onlychanged or not all_equal(value, val.default): | |
2158 | vals.append((name, value)) | |
1545 | 2159 | |
1546 | 2160 | vals.sort(key=itemgetter(0)) |
1547 | 2161 | return vals |
1548 | 2162 | |
2163 | def values(self_, onlychanged=False): | |
2164 | """ | |
2165 | Return a dictionary of name,value pairs for the Parameters of this | |
2166 | object. | |
2167 | ||
2168 | When called on an instance with onlychanged set to True, will | |
2169 | only return values that are not equal to the default value | |
2170 | (onlychanged has no effect when called on a class). | |
2171 | """ | |
2172 | # Defined in terms of get_param_values() to avoid ordering | |
2173 | # issues in python2, but can be inverted if get_param_values | |
2174 | # is removed when python2 support is dropped | |
2175 | return dict(self_.get_param_values(onlychanged)) | |
1549 | 2176 | |
1550 | 2177 | def force_new_dynamic_value(self_, name): # pylint: disable-msg=E0213 |
1551 | 2178 | """ |
1571 | 2198 | return param_obj.__get__(slf, cls) |
1572 | 2199 | else: |
1573 | 2200 | return param_obj._force(slf, cls) |
1574 | ||
1575 | 2201 | |
1576 | 2202 | def get_value_generator(self_,name): # pylint: disable-msg=E0213 |
1577 | 2203 | """ |
1632 | 2258 | |
1633 | 2259 | return value |
1634 | 2260 | |
1635 | ||
1636 | def params_depended_on(self_,name): | |
1637 | return _params_depended_on(MInfo(cls=self_.cls,inst=self_.self,name=name,method=getattr(self_.self_or_cls,name))) | |
1638 | ||
2261 | def method_dependencies(self_, name, intermediate=False): | |
2262 | """ | |
2263 | Given the name of a method, returns a PInfo object for each dependency | |
2264 | of this method. See help(PInfo) for the contents of these objects. | |
2265 | ||
2266 | By default intermediate dependencies on sub-objects are not | |
2267 | returned as these are primarily useful for internal use to | |
2268 | determine when a sub-object dependency has to be updated. | |
2269 | """ | |
2270 | method = getattr(self_.self_or_cls, name) | |
2271 | minfo = MInfo(cls=self_.cls, inst=self_.self, name=name, | |
2272 | method=method) | |
2273 | deps, dynamic = _params_depended_on( | |
2274 | minfo, dynamic=False, intermediate=intermediate) | |
2275 | if self_.self is None: | |
2276 | return deps | |
2277 | return _resolve_mcs_deps( | |
2278 | self_.self, deps, dynamic, intermediate=intermediate) | |
2279 | ||
2280 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
2281 | params_depended_on = method_dependencies | |
1639 | 2282 | |
1640 | 2283 | def outputs(self_): |
1641 | 2284 | """ |
1656 | 2299 | outputs[name] = (otype, method, idx) |
1657 | 2300 | return outputs |
1658 | 2301 | |
1659 | ||
1660 | def _spec_to_obj(self_,spec): | |
1661 | # TODO: when we decide on spec, this method should be | |
1662 | # rewritten | |
1663 | ||
2302 | def _spec_to_obj(self_, spec, dynamic=True, intermediate=True): | |
2303 | """ | |
2304 | Resolves a dependency specification into lists of explicit | |
2305 | parameter dependencies and dynamic dependencies. | |
2306 | ||
2307 | Dynamic dependencies are specifications to be resolved when | |
2308 | the sub-object whose parameters are being depended on is | |
2309 | defined. | |
2310 | ||
2311 | During class creation dynamic=False which means sub-object | |
2312 | dependencies are not resolved. At instance creation and | |
2313 | whenever a sub-object is set on an object this method will be | |
2314 | invoked to determine whether the dependency is available. | |
2315 | ||
2316 | For sub-object dependencies we also return dependencies for | |
2317 | every part of the path, e.g. for a dependency specification | |
2318 | like "a.b.c" we return dependencies for sub-object "a" and the | |
2319 | sub-sub-object "b" in addition to the dependency on the actual | |
2320 | parameter "c" on object "b". This is to ensure that if a | |
2321 | sub-object is swapped out we are notified and can update the | |
2322 | dynamic dependency to the new object. Even if a sub-object | |
2323 | dependency can only partially resolved, e.g. if object "a" | |
2324 | does not yet have a sub-object "b" we must watch for changes | |
2325 | to "b" on sub-object "a" in case such a subobject is put in "b". | |
2326 | """ | |
1664 | 2327 | if isinstance(spec, Parameter): |
1665 | 2328 | inst = spec.owner if isinstance(spec.owner, Parameterized) else None |
1666 | 2329 | cls = spec.owner if inst is None else type(inst) |
1667 | 2330 | info = PInfo(inst=inst, cls=cls, name=spec.name, |
1668 | 2331 | pobj=spec, what='value') |
1669 | return [info] | |
1670 | ||
1671 | assert spec.count(":")<=1 | |
1672 | ||
1673 | spec = spec.strip() | |
1674 | m = re.match("(?P<path>[^:]*):?(?P<what>.*)", spec) | |
1675 | what = m.group('what') | |
1676 | path = "."+m.group('path') | |
1677 | m = re.match(r"(?P<obj>.*)(\.)(?P<attr>.*)",path) | |
1678 | obj = m.group('obj') | |
1679 | attr = m.group("attr") | |
1680 | ||
1681 | src = self_.self_or_cls if obj=='' else _getattrr(self_.self_or_cls,obj[1::]) | |
1682 | cls,inst = (src, None) if isinstance(src, type) else (type(src), src) | |
1683 | ||
2332 | return [] if intermediate == 'only' else [info], [] | |
2333 | ||
2334 | obj, attr, what = _parse_dependency_spec(spec) | |
2335 | if obj is None: | |
2336 | src = self_.self_or_cls | |
2337 | elif not dynamic: | |
2338 | return [], [DInfo(spec=spec)] | |
2339 | else: | |
2340 | src = _getattrr(self_.self_or_cls, obj[1::], None) | |
2341 | if src is None: | |
2342 | path = obj[1:].split('.') | |
2343 | deps = [] | |
2344 | # Attempt to partially resolve subobject path to ensure | |
2345 | # that if a subobject is later updated making the full | |
2346 | # subobject path available we have to be notified and | |
2347 | # set up watchers | |
2348 | if len(path) >= 1 and intermediate: | |
2349 | sub_src = None | |
2350 | subpath = path | |
2351 | while sub_src is None and subpath: | |
2352 | subpath = subpath[:-1] | |
2353 | sub_src = _getattrr(self_.self_or_cls, '.'.join(subpath), None) | |
2354 | if subpath: | |
2355 | subdeps, _ = self_._spec_to_obj( | |
2356 | '.'.join(path[:len(subpath)+1]), dynamic, intermediate) | |
2357 | deps += subdeps | |
2358 | return deps, [] if intermediate == 'only' else [DInfo(spec=spec)] | |
2359 | ||
2360 | cls, inst = (src, None) if isinstance(src, type) else (type(src), src) | |
1684 | 2361 | if attr == 'param': |
1685 | dependencies = self_._spec_to_obj(obj[1:]) | |
2362 | deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate) | |
1686 | 2363 | for p in src.param: |
1687 | dependencies += src.param._spec_to_obj(p) | |
1688 | return dependencies | |
2364 | param_deps, param_dynamic_deps = src.param._spec_to_obj(p, dynamic, intermediate) | |
2365 | deps += param_deps | |
2366 | dynamic_deps += param_dynamic_deps | |
2367 | return deps, dynamic_deps | |
1689 | 2368 | elif attr in src.param: |
1690 | what = what if what != '' else 'value' | |
1691 | 2369 | info = PInfo(inst=inst, cls=cls, name=attr, |
1692 | 2370 | pobj=src.param[attr], what=what) |
2371 | elif hasattr(src, attr): | |
2372 | info = MInfo(inst=inst, cls=cls, name=attr, | |
2373 | method=getattr(src, attr)) | |
2374 | elif getattr(src, "abstract", None): | |
2375 | return [], [] if intermediate == 'only' else [DInfo(spec=spec)] | |
1693 | 2376 | else: |
1694 | info = MInfo(inst=inst, cls=cls, name=attr, | |
1695 | method=getattr(src,attr)) | |
1696 | return [info] | |
1697 | ||
1698 | ||
1699 | def _watch(self_, action, watcher, what='value', operation='add'): #'add' | 'remove' | |
2377 | raise AttributeError("Attribute %r could not be resolved on %s." | |
2378 | % (attr, src)) | |
2379 | ||
2380 | if obj is None or not intermediate: | |
2381 | return [info], [] | |
2382 | deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate) | |
2383 | if intermediate != 'only': | |
2384 | deps.append(info) | |
2385 | return deps, dynamic_deps | |
2386 | ||
2387 | def _register_watcher(self_, action, watcher, what='value'): | |
1700 | 2388 | parameter_names = watcher.parameter_names |
1701 | 2389 | for parameter_name in parameter_names: |
1702 | 2390 | if parameter_name not in self_.cls.param: |
1717 | 2405 | watchers[what] = [] |
1718 | 2406 | getattr(watchers[what], action)(watcher) |
1719 | 2407 | |
1720 | def watch(self_,fn,parameter_names, what='value', onlychanged=True, queued=False): | |
2408 | def watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0): | |
2409 | """ | |
2410 | Register the given callback function `fn` to be invoked for | |
2411 | events on the indicated parameters. | |
2412 | ||
2413 | `what`: What to watch on each parameter; either the value (by | |
2414 | default) or else the indicated slot (e.g. 'constant'). | |
2415 | ||
2416 | `onlychanged`: By default, only invokes the function when the | |
2417 | watched item changes, but if `onlychanged=False` also invokes | |
2418 | it when the `what` item is set to its current value again. | |
2419 | ||
2420 | `queued`: By default, additional watcher events generated | |
2421 | inside the callback fn are dispatched immediately, effectively | |
2422 | doing depth-first processing of Watcher events. However, in | |
2423 | certain scenarios, it is helpful to wait to dispatch such | |
2424 | downstream events until all events that triggered this watcher | |
2425 | have been processed. In such cases setting `queued=True` on | |
2426 | this Watcher will queue up new downstream events generated | |
2427 | during `fn` until `fn` completes and all other watchers | |
2428 | invoked by that same event have finished executing), | |
2429 | effectively doing breadth-first processing of Watcher events. | |
2430 | ||
2431 | `precedence`: Declares a precedence level for the Watcher that | |
2432 | determines the priority with which the callback is executed. | |
2433 | Lower precedence levels are executed earlier. Negative | |
2434 | precedences are reserved for internal Watchers, i.e. those | |
2435 | set up by param.depends. | |
2436 | ||
2437 | When the `fn` is called, it will be provided the relevant | |
2438 | Event objects as positional arguments, which allows it to | |
2439 | determine which of the possible triggering events occurred. | |
2440 | ||
2441 | Returns a Watcher object. | |
2442 | ||
2443 | See help(Watcher) and help(Event) for the contents of those objects. | |
2444 | """ | |
2445 | if precedence < 0: | |
2446 | raise ValueError("User-defined watch callbacks must declare " | |
2447 | "a positive precedence. Negative precedences " | |
2448 | "are reserved for internal Watchers.") | |
2449 | return self_._watch(fn, parameter_names, what, onlychanged, queued, precedence) | |
2450 | ||
2451 | def _watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=-1): | |
1721 | 2452 | parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,) |
1722 | 2453 | watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, mode='args', |
1723 | 2454 | onlychanged=onlychanged, parameter_names=parameter_names, |
1724 | what=what, queued=queued) | |
1725 | self_._watch('append', watcher, what) | |
2455 | what=what, queued=queued, precedence=precedence) | |
2456 | self_._register_watcher('append', watcher, what) | |
1726 | 2457 | return watcher |
1727 | 2458 | |
1728 | def unwatch(self_,watcher): | |
1729 | """ | |
1730 | Unwatch watchers set either with watch or watch_values. | |
2459 | def unwatch(self_, watcher): | |
2460 | """ | |
2461 | Remove the given Watcher object (from `watch` or `watch_values`) from this object's list. | |
1731 | 2462 | """ |
1732 | 2463 | try: |
1733 | self_._watch('remove',watcher) | |
1734 | except: | |
1735 | self_.warning('No such watcher {watcher} to remove.'.format(watcher=watcher)) | |
1736 | ||
1737 | ||
1738 | def watch_values(self_, fn, parameter_names, what='value', onlychanged=True, queued=False): | |
1739 | parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,) | |
2464 | self_._register_watcher('remove', watcher, what=watcher.what) | |
2465 | except Exception: | |
2466 | self_.warning('No such watcher {watcher} to remove.'.format(watcher=str(watcher))) | |
2467 | ||
2468 | def watch_values(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0): | |
2469 | """ | |
2470 | Easier-to-use version of `watch` specific to watching for changes in parameter values. | |
2471 | ||
2472 | Only allows `what` to be 'value', and invokes the callback `fn` using keyword | |
2473 | arguments <param_name>=<new_value> rather than with a list of Event objects. | |
2474 | """ | |
2475 | if precedence < 0: | |
2476 | raise ValueError("User-defined watch callbacks must declare " | |
2477 | "a positive precedence. Negative precedences " | |
2478 | "are reserved for internal Watchers.") | |
2479 | assert what == 'value' | |
2480 | if isinstance(parameter_names, list): | |
2481 | parameter_names = tuple(parameter_names) | |
2482 | else: | |
2483 | parameter_names = (parameter_names,) | |
1740 | 2484 | watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, |
1741 | 2485 | mode='kwargs', onlychanged=onlychanged, |
1742 | parameter_names=parameter_names, what='value', | |
1743 | queued=queued) | |
1744 | self_._watch('append', watcher, what) | |
2486 | parameter_names=parameter_names, what=what, | |
2487 | queued=queued, precedence=precedence) | |
2488 | self_._register_watcher('append', watcher, what) | |
1745 | 2489 | return watcher |
1746 | 2490 | |
1747 | ||
1748 | 2491 | # Instance methods |
1749 | 2492 | |
1750 | ||
2493 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1751 | 2494 | def defaults(self_): |
1752 | 2495 | """ |
1753 | 2496 | Return {parameter_name:parameter.default} for all non-constant |
1758 | 2501 | """ |
1759 | 2502 | self = self_.self |
1760 | 2503 | d = {} |
1761 | for param_name,param in self.param.objects('existing').items(): | |
2504 | for param_name, param in self.param.objects('existing').items(): | |
1762 | 2505 | if param.constant: |
1763 | 2506 | pass |
1764 | elif param.instantiate: | |
1765 | self.param._instantiate_param(param,dict_=d,key=param_name) | |
1766 | else: | |
1767 | d[param_name]=param.default | |
2507 | if param.instantiate: | |
2508 | self.param._instantiate_param(param, dict_=d, key=param_name) | |
2509 | d[param_name] = param.default | |
1768 | 2510 | return d |
1769 | 2511 | |
1770 | # CEBALERT: designed to avoid any processing unless the print | |
1771 | # level is high enough, but not all callers of message(), | |
1772 | # verbose(), debug(), etc are taking advantage of this. Need to | |
1773 | # document, and also check other ioam projects. | |
2512 | # Designed to avoid any processing unless the print | |
2513 | # level is high enough, though not all callers of message(), | |
2514 | # verbose(), debug(), etc are taking advantage of this. | |
1774 | 2515 | def __db_print(self_,level,msg,*args,**kw): |
1775 | 2516 | """ |
1776 | 2517 | Calls the logger returned by the get_logger() function, |
1786 | 2527 | |
1787 | 2528 | get_logger(name=self_or_cls.name).log(level, msg, *args, **kw) |
1788 | 2529 | |
2530 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1789 | 2531 | def print_param_values(self_): |
1790 | 2532 | """Print the values of all this object's Parameters.""" |
1791 | 2533 | self = self_.self |
1792 | for name,val in self.param.get_param_values(): | |
2534 | for name, val in self.param.values().items(): | |
1793 | 2535 | print('%s.%s = %s' % (self.name,name,val)) |
1794 | 2536 | |
1795 | 2537 | def warning(self_, msg,*args,**kw): |
1800 | 2542 | |
1801 | 2543 | See Python's logging module for details of message formatting. |
1802 | 2544 | """ |
1803 | if not warnings_as_exceptions: | |
1804 | global warning_count | |
1805 | warning_count+=1 | |
1806 | self_.__db_print(WARNING,msg,*args,**kw) | |
1807 | else: | |
1808 | raise Exception("Warning: " + msg % args) | |
1809 | ||
2545 | self_.log(WARNING, msg, *args, **kw) | |
2546 | ||
2547 | # PARAM2_DEPRECATION: Could be removed post param 2.0 | |
1810 | 2548 | def message(self_,msg,*args,**kw): |
1811 | 2549 | """ |
1812 | 2550 | Print msg merged with args as a message. |
1815 | 2553 | """ |
1816 | 2554 | self_.__db_print(INFO,msg,*args,**kw) |
1817 | 2555 | |
2556 | # PARAM2_DEPRECATION: Could be removed post param 2.0 | |
1818 | 2557 | def verbose(self_,msg,*args,**kw): |
1819 | 2558 | """ |
1820 | 2559 | Print msg merged with args as a verbose message. |
1823 | 2562 | """ |
1824 | 2563 | self_.__db_print(VERBOSE,msg,*args,**kw) |
1825 | 2564 | |
2565 | # PARAM2_DEPRECATION: Could be removed post param 2.0 | |
1826 | 2566 | def debug(self_,msg,*args,**kw): |
1827 | 2567 | """ |
1828 | 2568 | Print msg merged with args as a debugging statement. |
1831 | 2571 | """ |
1832 | 2572 | self_.__db_print(DEBUG,msg,*args,**kw) |
1833 | 2573 | |
1834 | ||
1835 | # CEBALERT: I think I've noted elsewhere the fact that we | |
1836 | # sometimes have a method on Parameter that requires passing the | |
1837 | # owning Parameterized instance or class, and other times we have | |
1838 | # the method on Parameterized itself. In case I haven't written | |
1839 | # that down elsewhere, here it is again. We should clean that up | |
1840 | # (at least we should be consistent). | |
1841 | ||
1842 | # cebalert: it's really time to stop and clean up this bothmethod | |
1843 | # stuff and repeated code in methods using it. | |
2574 | def log(self_, level, msg, *args, **kw): | |
2575 | """ | |
2576 | Print msg merged with args as a message at the indicated logging level. | |
2577 | ||
2578 | Logging levels include those provided by the Python logging module | |
2579 | plus VERBOSE, either obtained directly from the logging module like | |
2580 | `logging.INFO`, or from parameterized like `param.parameterized.INFO`. | |
2581 | ||
2582 | Supported logging levels include (in order of severity) | |
2583 | DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL | |
2584 | ||
2585 | See Python's logging module for details of message formatting. | |
2586 | """ | |
2587 | if level is WARNING: | |
2588 | if warnings_as_exceptions: | |
2589 | raise Exception("Warning: " + msg % args) | |
2590 | else: | |
2591 | global warning_count | |
2592 | warning_count+=1 | |
2593 | self_.__db_print(level, msg, *args, **kw) | |
2594 | ||
2595 | ||
2596 | def pprint(self_, imports=None, prefix=" ", unknown_value='<?>', | |
2597 | qualify=False, separator=""): | |
2598 | """See Parameterized.pprint""" | |
2599 | self = self_.self | |
2600 | return self._pprint(imports, prefix, unknown_value, qualify, separator) | |
1844 | 2601 | |
1845 | 2602 | |
1846 | 2603 | |
1867 | 2624 | attribute __abstract set to True. The 'abstract' attribute can be |
1868 | 2625 | used to find out if a class is abstract or not. |
1869 | 2626 | """ |
1870 | def __init__(mcs,name,bases,dict_): | |
2627 | def __init__(mcs, name, bases, dict_): | |
1871 | 2628 | """ |
1872 | 2629 | Initialize the class object (not an instance of the class, but |
1873 | 2630 | the class itself). |
1876 | 2633 | default values (see __param_inheritance()) and setting |
1877 | 2634 | attrib_names (see _set_names()). |
1878 | 2635 | """ |
1879 | type.__init__(mcs,name,bases,dict_) | |
2636 | type.__init__(mcs, name, bases, dict_) | |
1880 | 2637 | |
1881 | 2638 | # Give Parameterized classes a useful 'name' attribute. |
1882 | # (Could instead consider changing the instance Parameter | |
1883 | # 'name' to '__name__'?) | |
1884 | 2639 | mcs.name = name |
1885 | 2640 | |
1886 | mcs.param = Parameters(mcs) | |
2641 | mcs._parameters_state = { | |
2642 | "BATCH_WATCH": False, # If true, Event and watcher objects are queued. | |
2643 | "TRIGGER": False, | |
2644 | "events": [], # Queue of batched events | |
2645 | "watchers": [] # Queue of batched watchers | |
2646 | } | |
2647 | mcs._param = Parameters(mcs) | |
1887 | 2648 | |
1888 | 2649 | # All objects (with their names) of type Parameter that are |
1889 | 2650 | # defined in this class |
1890 | parameters = [(n,o) for (n,o) in dict_.items() | |
1891 | if isinstance(o,Parameter)] | |
2651 | parameters = [(n, o) for (n, o) in dict_.items() | |
2652 | if isinstance(o, Parameter)] | |
2653 | ||
2654 | mcs._param._parameters = dict(parameters) | |
1892 | 2655 | |
1893 | 2656 | for param_name,param in parameters: |
1894 | mcs._initialize_parameter(param_name,param) | |
2657 | mcs._initialize_parameter(param_name, param) | |
1895 | 2658 | |
1896 | 2659 | # retrieve depends info from methods and store more conveniently |
1897 | dependers = [(n,m._dinfo) for (n,m) in dict_.items() | |
1898 | if hasattr(m,'_dinfo')] | |
2660 | dependers = [(n, m, m._dinfo) for (n, m) in dict_.items() | |
2661 | if hasattr(m, '_dinfo')] | |
1899 | 2662 | |
1900 | 2663 | _watch = [] |
1901 | # TODO: probably copy dependencies here too and have | |
1902 | # everything else access from here rather than from method | |
1903 | # object | |
1904 | for n,dinfo in dependers: | |
2664 | for name, method, dinfo in dependers: | |
1905 | 2665 | watch = dinfo.get('watch', False) |
1906 | if watch: | |
1907 | _watch.append((n, watch == 'queued')) | |
2666 | on_init = dinfo.get('on_init', False) | |
2667 | if not watch: | |
2668 | continue | |
2669 | minfo = MInfo(cls=mcs, inst=None, name=name, | |
2670 | method=method) | |
2671 | deps, dynamic_deps = _params_depended_on(minfo, dynamic=False) | |
2672 | _watch.append((name, watch == 'queued', on_init, deps, dynamic_deps)) | |
2673 | ||
2674 | # Resolve other dependencies in remainder of class hierarchy | |
2675 | for cls in classlist(mcs)[:-1][::-1]: | |
2676 | if not hasattr(cls, '_param'): | |
2677 | continue | |
2678 | for dep in cls.param._depends['watch']: | |
2679 | method = getattr(mcs, dep[0], None) | |
2680 | dinfo = getattr(method, '_dinfo', {'watch': False}) | |
2681 | if (not any(dep[0] == w[0] for w in _watch) | |
2682 | and dinfo.get('watch')): | |
2683 | _watch.append(dep) | |
1908 | 2684 | |
1909 | 2685 | mcs.param._depends = {'watch': _watch} |
1910 | 2686 | |
1938 | 2714 | |
1939 | 2715 | |
1940 | 2716 | def _initialize_parameter(mcs,param_name,param): |
1941 | # parameter has no way to find out the name a | |
2717 | # A Parameter has no way to find out the name a | |
1942 | 2718 | # Parameterized class has for it |
1943 | 2719 | param._set_names(param_name) |
1944 | 2720 | mcs.__param_inheritance(param_name,param) |
1945 | 2721 | |
1946 | 2722 | |
1947 | # Python 2.6 added abstract base classes; see | |
1948 | # https://github.com/ioam/param/issues/84 | |
2723 | # Should use the official Python 2.6+ abstract base classes; see | |
2724 | # https://github.com/holoviz/param/issues/84 | |
1949 | 2725 | def __is_abstract(mcs): |
1950 | 2726 | """ |
1951 | 2727 | Return True if the class has an attribute __abstract set to True. |
1960 | 2736 | # _ParameterizedMetaclass__abstract before running, but |
1961 | 2737 | # the actual class object will have an attribute |
1962 | 2738 | # _ClassName__abstract. So, we have to mangle it ourselves at |
1963 | # runtime. | |
2739 | # runtime. Mangling follows description in | |
2740 | # https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references | |
1964 | 2741 | try: |
1965 | return getattr(mcs,'_%s__abstract'%mcs.__name__) | |
2742 | return getattr(mcs,'_%s__abstract'%mcs.__name__.lstrip("_")) | |
1966 | 2743 | except AttributeError: |
1967 | 2744 | return False |
1968 | 2745 | |
1969 | 2746 | abstract = property(__is_abstract) |
1970 | 2747 | |
1971 | ||
1972 | ||
1973 | def __setattr__(mcs,attribute_name,value): | |
2748 | def _get_param(mcs): | |
2749 | return mcs._param | |
2750 | ||
2751 | param = property(_get_param) | |
2752 | ||
2753 | def __setattr__(mcs, attribute_name, value): | |
1974 | 2754 | """ |
1975 | 2755 | Implements 'self.attribute_name=value' in a way that also supports Parameters. |
1976 | 2756 | |
2012 | 2792 | # (For instance, python's own pickling mechanism |
2013 | 2793 | # caches __slotnames__ on the class: |
2014 | 2794 | # http://mail.python.org/pipermail/python-checkins/2003-February/033517.html.) |
2015 | # CEBALERT: this warning bypasses the usual | |
2016 | # mechanisms, which has have consequences for warning | |
2017 | # counts, warnings as exceptions, etc. | |
2795 | # This warning bypasses the usual mechanisms, which | |
2796 | # has have consequences for warning counts, warnings | |
2797 | # as exceptions, etc. | |
2018 | 2798 | if not attribute_name.startswith('_'): |
2019 | 2799 | get_logger().log(WARNING, |
2020 | 2800 | "Setting non-Parameter class attribute %s.%s = %s ", |
2048 | 2828 | Note that instantiate is handled differently: if there is a |
2049 | 2829 | parameter with the same name in one of the superclasses with |
2050 | 2830 | instantiate set to True, this parameter will inherit |
2051 | instatiate=True. | |
2831 | instantiate=True. | |
2052 | 2832 | """ |
2053 | 2833 | # get all relevant slots (i.e. slots defined in all |
2054 | 2834 | # superclasses of this parameter) |
2112 | 2892 | |
2113 | 2893 | |
2114 | 2894 | |
2115 | # JABALERT: Only partially achieved so far -- objects of the same | |
2116 | # type and parameter values are treated as different, so anything | |
2117 | # for which instantiate == True is reported as being non-default. | |
2118 | ||
2119 | 2895 | # Whether script_repr should avoid reporting the values of parameters |
2120 | 2896 | # that are just inheriting their values from the class defaults. |
2897 | # Because deepcopying creates a new object, cannot detect such | |
2898 | # inheritance when instantiate = True, so such values will be printed | |
2899 | # even if they are just being copied from the default. | |
2121 | 2900 | script_repr_suppress_defaults=True |
2122 | 2901 | |
2123 | 2902 | |
2124 | # CEBALERT: How about some defaults? | |
2125 | # Also, do we need an option to return repr without path, if desired? | |
2126 | # E.g. to get 'pre_plot_hooks()' instead of | |
2127 | # 'topo.command.analysis.pre_plot_hooks()' in the gui? | |
2128 | def script_repr(val,imports,prefix,settings): | |
2129 | """ | |
2130 | Variant of repr() designed for generating a runnable script. | |
2131 | ||
2132 | Instances of types that require special handling can use the | |
2133 | script_repr_reg dictionary. Using the type as a key, add a | |
2134 | function that returns a suitable representation of instances of | |
2135 | that type, and adds the required import statement. | |
2136 | ||
2137 | The repr of a parameter can be suppressed by returning None from | |
2138 | the appropriate hook in script_repr_reg. | |
2139 | """ | |
2140 | return pprint(val,imports,prefix,settings,unknown_value=None, | |
2141 | qualify=True,separator="\n") | |
2142 | ||
2143 | ||
2144 | # CB: when removing script_repr, merge its docstring here and improve. | |
2145 | # And the ALERT by script_repr about defaults can go. | |
2146 | # CEBALERT: remove settings, add default argument for imports | |
2147 | def pprint(val,imports, prefix="\n ", settings=[], | |
2903 | def script_repr(val, imports=None, prefix="\n ", settings=[], | |
2904 | qualify=True, unknown_value=None, separator="\n", | |
2905 | show_imports=True): | |
2906 | """ | |
2907 | Variant of pprint() designed for generating a (nearly) runnable script. | |
2908 | ||
2909 | The output of script_repr(parameterized_obj) is meant to be a | |
2910 | string suitable for running using `python file.py`. Not every | |
2911 | object is guaranteed to have a runnable script_repr | |
2912 | representation, but it is meant to be a good starting point for | |
2913 | generating a Python script that (after minor edits) can be | |
2914 | evaluated to get a newly initialized object similar to the one | |
2915 | provided. | |
2916 | ||
2917 | The new object will only have the same parameter state, not the | |
2918 | same internal (attribute) state; the script_repr captures only | |
2919 | the state of the Parameters of that object and not any other | |
2920 | attributes it may have. | |
2921 | ||
2922 | If show_imports is True (default), includes import statements | |
2923 | for each of the modules required for the objects being | |
2924 | instantiated. This list may not be complete, as it typically | |
2925 | includes only the imports needed for the Parameterized object | |
2926 | itself, not for values that may have been supplied to Parameters. | |
2927 | ||
2928 | Apart from show_imports, accepts the same arguments as pprint(), | |
2929 | so see pprint() for explanations of the arguments accepted. The | |
2930 | default values of each of these arguments differ from pprint() in | |
2931 | ways that are more suitable for saving as a separate script than | |
2932 | for e.g. pretty-printing at the Python prompt. | |
2933 | """ | |
2934 | ||
2935 | if imports is None: | |
2936 | imports = [] | |
2937 | ||
2938 | rep = pprint(val, imports, prefix, settings, unknown_value, | |
2939 | qualify, separator) | |
2940 | ||
2941 | imports = list(set(imports)) | |
2942 | imports_str = ("\n".join(imports) + "\n\n") if show_imports else "" | |
2943 | ||
2944 | return imports_str + rep | |
2945 | ||
2946 | ||
2947 | # PARAM2_DEPRECATION: Remove entirely unused settings argument | |
2948 | def pprint(val,imports=None, prefix="\n ", settings=[], | |
2148 | 2949 | unknown_value='<?>', qualify=False, separator=''): |
2149 | 2950 | """ |
2150 | (Experimental) Pretty printed representation of a parameterized | |
2951 | Pretty printed representation of a parameterized | |
2151 | 2952 | object that may be evaluated with eval. |
2152 | 2953 | |
2153 | 2954 | Similar to repr except introspection of the constructor (__init__) |
2181 | 2982 | (e.g. a newline could be supplied to have each Parameter appear on a |
2182 | 2983 | separate line). |
2183 | 2984 | |
2184 | NOTE: pprint will replace script_repr in a future version of | |
2185 | param, but is not yet a complete replacement for script_repr. | |
2186 | """ | |
2187 | # CB: doc prefix & settings or realize they don't need to be | |
2188 | # passed around, etc. | |
2189 | # JLS: The settings argument is not used anywhere. To be removed | |
2190 | # in a separate PR. | |
2985 | Instances of types that require special handling can use the | |
2986 | script_repr_reg dictionary. Using the type as a key, add a | |
2987 | function that returns a suitable representation of instances of | |
2988 | that type, and adds the required import statement. The repr of a | |
2989 | parameter can be suppressed by returning None from the appropriate | |
2990 | hook in script_repr_reg. | |
2991 | """ | |
2992 | ||
2993 | if imports is None: | |
2994 | imports = [] | |
2995 | ||
2191 | 2996 | if isinstance(val,type): |
2192 | 2997 | rep = type_script_repr(val,imports,prefix,settings) |
2193 | 2998 | |
2194 | 2999 | elif type(val) in script_repr_reg: |
2195 | 3000 | rep = script_repr_reg[type(val)](val,imports,prefix,settings) |
2196 | 3001 | |
2197 | # CEBALERT: remove with script_repr | |
2198 | elif hasattr(val,'script_repr'): | |
2199 | rep=val.script_repr(imports, prefix+" ") | |
2200 | ||
2201 | elif hasattr(val,'pprint'): | |
2202 | rep=val.pprint(imports=imports, prefix=prefix+" ", | |
2203 | qualify=qualify, unknown_value=unknown_value, | |
2204 | separator=separator) | |
2205 | ||
3002 | elif hasattr(val,'_pprint'): | |
3003 | rep=val._pprint(imports=imports, prefix=prefix+" ", | |
3004 | qualify=qualify, unknown_value=unknown_value, | |
3005 | separator=separator) | |
2206 | 3006 | else: |
2207 | 3007 | rep=repr(val) |
2208 | 3008 | |
2209 | 3009 | return rep |
2210 | 3010 | |
2211 | 3011 | |
2212 | #: see script_repr() | |
3012 | # Registry for special handling for certain types in script_repr and pprint | |
2213 | 3013 | script_repr_reg = {} |
2214 | 3014 | |
2215 | 3015 | |
2246 | 3046 | pass # Support added only if those libraries are available |
2247 | 3047 | |
2248 | 3048 | |
2249 | # why I have to type prefix and settings? | |
2250 | 3049 | def function_script_repr(fn,imports,prefix,settings): |
2251 | 3050 | name = fn.__name__ |
2252 | 3051 | module = fn.__module__ |
2271 | 3070 | dbprint_prefix=None |
2272 | 3071 | |
2273 | 3072 | |
2274 | ||
2275 | ||
2276 | ||
3073 | # Copy of Python 3.2 reprlib's recursive_repr but allowing extra arguments | |
3074 | if sys.version_info.major >= 3: | |
3075 | from threading import get_ident | |
3076 | def recursive_repr(fillvalue='...'): | |
3077 | 'Decorator to make a repr function return fillvalue for a recursive call' | |
3078 | ||
3079 | def decorating_function(user_function): | |
3080 | repr_running = set() | |
3081 | ||
3082 | def wrapper(self, *args, **kwargs): | |
3083 | key = id(self), get_ident() | |
3084 | if key in repr_running: | |
3085 | return fillvalue | |
3086 | repr_running.add(key) | |
3087 | try: | |
3088 | result = user_function(self, *args, **kwargs) | |
3089 | finally: | |
3090 | repr_running.discard(key) | |
3091 | return result | |
3092 | return wrapper | |
3093 | ||
3094 | return decorating_function | |
3095 | else: | |
3096 | def recursive_repr(fillvalue='...'): | |
3097 | def decorating_function(user_function): | |
3098 | return user_function | |
3099 | return decorating_function | |
2277 | 3100 | |
2278 | 3101 | |
2279 | 3102 | @add_metaclass(ParameterizedMetaclass) |
2320 | 3143 | see documentation for the 'logging' module. |
2321 | 3144 | """ |
2322 | 3145 | |
2323 | name = String(default=None,constant=True,doc=""" | |
2324 | String identifier for this object.""") | |
2325 | ||
2326 | ||
2327 | def __init__(self,**params): | |
3146 | name = String(default=None, constant=True, doc=""" | |
3147 | String identifier for this object.""") | |
3148 | ||
3149 | def __init__(self, **params): | |
2328 | 3150 | global object_count |
2329 | 3151 | |
2330 | 3152 | # Flag that can be tested to see if e.g. constant Parameters |
2331 | 3153 | # can still be set |
2332 | self.initialized=False | |
2333 | # Override class level param namespace with instance namespace | |
2334 | self.param = Parameters(self.__class__, self=self) | |
3154 | self.initialized = False | |
3155 | self._parameters_state = { | |
3156 | "BATCH_WATCH": False, # If true, Event and watcher objects are queued. | |
3157 | "TRIGGER": False, | |
3158 | "events": [], # Queue of batched events | |
3159 | "watchers": [] # Queue of batched watchers | |
3160 | } | |
2335 | 3161 | self._instance__params = {} |
2336 | 3162 | self._param_watchers = {} |
3163 | self._dynamic_watchers = defaultdict(list) | |
2337 | 3164 | |
2338 | 3165 | self.param._generate_name() |
2339 | 3166 | self.param._setup_params(**params) |
2340 | 3167 | object_count += 1 |
2341 | 3168 | |
2342 | # add watched dependencies | |
2343 | for cls in classlist(self.__class__): | |
2344 | if not issubclass(cls, Parameterized): | |
2345 | continue | |
2346 | for n, queued in cls.param._depends['watch']: | |
2347 | # TODO: should improve this - will happen for every | |
2348 | # instantiation of Parameterized with watched deps. Will | |
2349 | # probably store expanded deps on class - see metaclass | |
2350 | # 'dependers'. | |
2351 | for p in self.param.params_depended_on(n): | |
2352 | # TODO: can't remember why not just pass m (rather than _m_caller) here | |
2353 | (p.inst or p.cls).param.watch(_m_caller(self, n), p.name, p.what, queued=queued) | |
2354 | ||
2355 | self.initialized=True | |
3169 | self.param._update_deps(init=True) | |
3170 | ||
3171 | self.initialized = True | |
3172 | ||
3173 | @property | |
3174 | def param(self): | |
3175 | return Parameters(self.__class__, self=self) | |
2356 | 3176 | |
2357 | 3177 | # 'Special' methods |
2358 | 3178 | |
2362 | 3182 | copy of the object's __dict__ and that also includes the |
2363 | 3183 | object's __slots__ (if it has any). |
2364 | 3184 | """ |
2365 | # remind me, why is it a copy? why not just state.update(self.__dict__)? | |
3185 | # Unclear why this is a copy and not simply state.update(self.__dict__) | |
2366 | 3186 | state = self.__dict__.copy() |
2367 | ||
2368 | 3187 | for slot in get_occupied_slots(self): |
2369 | 3188 | state[slot] = getattr(self,slot) |
2370 | 3189 | |
2385 | 3204 | """ |
2386 | 3205 | self.initialized=False |
2387 | 3206 | |
3207 | # When making a copy the internal watchers have to be | |
3208 | # recreated and point to the new instance | |
3209 | if '_param_watchers' in state: | |
3210 | param_watchers = state['_param_watchers'] | |
3211 | for p, attrs in param_watchers.items(): | |
3212 | for attr, watchers in attrs.items(): | |
3213 | new_watchers = [] | |
3214 | for watcher in watchers: | |
3215 | watcher_args = list(watcher) | |
3216 | if watcher.inst is not None: | |
3217 | watcher_args[0] = self | |
3218 | fn = watcher.fn | |
3219 | if hasattr(fn, '_watcher_name'): | |
3220 | watcher_args[2] = _m_caller(self, fn._watcher_name) | |
3221 | elif get_method_owner(fn) is watcher.inst: | |
3222 | watcher_args[2] = getattr(self, fn.__name__) | |
3223 | new_watchers.append(Watcher(*watcher_args)) | |
3224 | param_watchers[p][attr] = new_watchers | |
3225 | ||
2388 | 3226 | if '_instance__params' not in state: |
2389 | 3227 | state['_instance__params'] = {} |
2390 | 3228 | if '_param_watchers' not in state: |
2391 | 3229 | state['_param_watchers'] = {} |
3230 | state.pop('param', None) | |
2392 | 3231 | |
2393 | 3232 | for name,value in state.items(): |
2394 | 3233 | setattr(self,name,value) |
2395 | 3234 | self.initialized=True |
2396 | 3235 | |
3236 | @recursive_repr() | |
2397 | 3237 | def __repr__(self): |
2398 | 3238 | """ |
2399 | 3239 | Provide a nearly valid Python representation that could be used to recreate |
2404 | 3244 | """ |
2405 | 3245 | try: |
2406 | 3246 | settings = ['%s=%s' % (name, repr(val)) |
2407 | for name,val in self.param.get_param_values()] | |
3247 | # PARAM2_DEPRECATION: Update to self.param.values.items() | |
3248 | # (once python2 support is dropped) | |
3249 | for name, val in self.param.get_param_values()] | |
2408 | 3250 | except RuntimeError: # Handle recursion in parameter depth |
2409 | 3251 | settings = [] |
2410 | 3252 | return self.__class__.__name__ + "(" + ", ".join(settings) + ")" |
2414 | 3256 | return "<%s %s>" % (self.__class__.__name__,self.name) |
2415 | 3257 | |
2416 | 3258 | |
3259 | # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead | |
2417 | 3260 | def script_repr(self,imports=[],prefix=" "): |
2418 | 3261 | """ |
2419 | Variant of __repr__ designed for generating a runnable script. | |
3262 | Deprecated variant of __repr__ designed for generating a runnable script. | |
2420 | 3263 | """ |
2421 | 3264 | return self.pprint(imports,prefix, unknown_value=None, qualify=True, |
2422 | 3265 | separator="\n") |
2423 | 3266 | |
2424 | # CEBALERT: not yet properly documented | |
2425 | def pprint(self, imports=None, prefix=" ", unknown_value='<?>', | |
3267 | @recursive_repr() | |
3268 | def _pprint(self, imports=None, prefix=" ", unknown_value='<?>', | |
2426 | 3269 | qualify=False, separator=""): |
2427 | 3270 | """ |
2428 | 3271 | (Experimental) Pretty printed representation that may be |
2429 | 3272 | evaluated with eval. See pprint() function for more details. |
2430 | 3273 | """ |
2431 | 3274 | if imports is None: |
2432 | imports = [] | |
2433 | ||
2434 | # CEBALERT: imports should just be a set rather than a list; | |
2435 | # change in next release? | |
3275 | imports = [] # would have been simpler to use a set from the start | |
2436 | 3276 | imports[:] = list(set(imports)) |
3277 | ||
2437 | 3278 | # Generate import statement |
2438 | 3279 | mod = self.__module__ |
2439 | 3280 | bits = mod.split('.') |
2440 | 3281 | imports.append("import %s"%mod) |
2441 | 3282 | imports.append("import %s"%bits[0]) |
2442 | 3283 | |
2443 | changed_params = dict(self.param.get_param_values(onlychanged=script_repr_suppress_defaults)) | |
2444 | values = dict(self.param.get_param_values()) | |
2445 | spec = inspect.getargspec(self.__init__) | |
3284 | changed_params = self.param.values(onlychanged=script_repr_suppress_defaults) | |
3285 | values = self.param.values() | |
3286 | spec = getfullargspec(self.__init__) | |
2446 | 3287 | args = spec.args[1:] if spec.args[0] == 'self' else spec.args |
2447 | 3288 | |
2448 | 3289 | if spec.defaults is not None: |
2486 | 3327 | if k in posargs: |
2487 | 3328 | # value will be unknown_value unless k is a parameter |
2488 | 3329 | arglist.append(value) |
2489 | elif k in kwargs or (spec.keywords is not None): | |
3330 | elif (k in kwargs or | |
3331 | (hasattr(spec, 'varkw') and (spec.varkw is not None)) or | |
3332 | (hasattr(spec, 'keywords') and (spec.keywords is not None))): | |
2490 | 3333 | # Explicit modified keywords or parameters in |
2491 | 3334 | # precendence order (if **kwargs present) |
2492 | 3335 | keywords.append('%s=%s' % (k, value)) |
2497 | 3340 | arguments = arglist + keywords + (['**%s' % spec.varargs] if spec.varargs else []) |
2498 | 3341 | return qualifier + '%s(%s)' % (self.__class__.__name__, (','+separator+prefix).join(arguments)) |
2499 | 3342 | |
2500 | ||
2501 | # CEBALERT: note there's no state_push method on the class, so | |
3343 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
3344 | pprint = _pprint | |
3345 | ||
3346 | # Note that there's no state_push method on the class, so | |
2502 | 3347 | # dynamic parameters set on a class can't have state saved. This |
2503 | 3348 | # is because, to do this, state_push() would need to be a |
2504 | 3349 | # @bothmethod, but that complicates inheritance in cases where we |
2505 | # already have a state_push() method. I need to decide what to do | |
2506 | # about that. (isinstance(g,Parameterized) below is used to exclude classes.) | |
3350 | # already have a state_push() method. | |
3351 | # (isinstance(g,Parameterized) below is used to exclude classes.) | |
2507 | 3352 | |
2508 | 3353 | def state_push(self): |
2509 | 3354 | """ |
2644 | 3489 | |
2645 | 3490 | |
2646 | 3491 | |
2647 | # Note that with Python 2.6, a fn's **args no longer has to be a | |
3492 | # As of Python 2.6+, a fn's **args no longer has to be a | |
2648 | 3493 | # dictionary. This might allow us to use a decorator to simplify using |
2649 | 3494 | # ParamOverrides (if that does indeed make them simpler to use). |
2650 | 3495 | # http://docs.python.org/whatsnew/2.6.html |
2671 | 3516 | supplied dict_ that are not also parameters of the overridden |
2672 | 3517 | object will be available via the extra_keywords() method. |
2673 | 3518 | """ |
2674 | # we'd like __init__ to be fast because it's going to be | |
2675 | # called a lot. What's the fastest way to move the existing | |
2676 | # params dictionary into this one? Would | |
3519 | # This method should be fast because it's going to be | |
3520 | # called a lot. This _might_ be faster (not tested): | |
2677 | 3521 | # def __init__(self,overridden,**kw): |
2678 | 3522 | # ... |
2679 | 3523 | # dict.__init__(self,**kw) |
2680 | # be faster/easier to use? | |
2681 | 3524 | self._overridden = overridden |
2682 | 3525 | dict.__init__(self,dict_) |
2683 | 3526 | |
2689 | 3532 | def extra_keywords(self): |
2690 | 3533 | """ |
2691 | 3534 | Return a dictionary containing items from the originally |
2692 | supplied dict_ whose names are not parameters of the | |
3535 | supplied `dict_` whose names are not parameters of the | |
2693 | 3536 | overridden object. |
2694 | 3537 | """ |
2695 | 3538 | return self._extra_keywords |
2697 | 3540 | def param_keywords(self): |
2698 | 3541 | """ |
2699 | 3542 | Return a dictionary containing items from the originally |
2700 | supplied dict_ whose names are parameters of the | |
3543 | supplied `dict_` whose names are parameters of the | |
2701 | 3544 | overridden object (i.e. not extra keywords/parameters). |
2702 | 3545 | """ |
2703 | 3546 | return dict((key, self[key]) for key in self if key not in self.extra_keywords()) |
2754 | 3597 | for name, val in params.items(): |
2755 | 3598 | if name not in overridden_object_params: |
2756 | 3599 | extra_keywords[name]=val |
2757 | # CEBALERT: should we remove name from params | |
2758 | # (i.e. del params[name]) so that it's only available | |
2759 | # via extra_keywords()? | |
3600 | # Could remove name from params (i.e. del params[name]) | |
3601 | # so that it's only available via extra_keywords() | |
2760 | 3602 | return extra_keywords |
2761 | 3603 | |
2762 | 3604 | |
2777 | 3619 | """ |
2778 | 3620 | __abstract = True |
2779 | 3621 | |
2780 | # CEBALERT: shouldn't this have come from a parent class | |
2781 | # somewhere? | |
2782 | 3622 | def __str__(self): |
2783 | 3623 | return self.__class__.__name__+"()" |
2784 | 3624 | |
2793 | 3633 | cls = self_or_cls |
2794 | 3634 | else: |
2795 | 3635 | p = params |
2796 | params = dict(self_or_cls.get_param_values()) | |
3636 | params = self_or_cls.param.values() | |
2797 | 3637 | params.update(p) |
2798 | 3638 | params.pop('name') |
2799 | 3639 | cls = self_or_cls.__class__ |
2817 | 3657 | # Control reconstruction (during unpickling and copying): |
2818 | 3658 | # ensure that ParameterizedFunction.__new__ is skipped |
2819 | 3659 | state = ParameterizedFunction.__getstate__(self) |
2820 | # CB: here it's necessary to use a function defined at the | |
3660 | # Here it's necessary to use a function defined at the | |
2821 | 3661 | # module level rather than Parameterized.__new__ directly |
2822 | 3662 | # because otherwise pickle will find .__new__'s module to be |
2823 | # __main__. Pretty obscure aspect of pickle.py, or a bug? | |
3663 | # __main__. Pretty obscure aspect of pickle.py... | |
2824 | 3664 | return (_new_parameterized,(self.__class__,),state) |
2825 | 3665 | |
3666 | # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead | |
2826 | 3667 | def script_repr(self,imports=[],prefix=" "): |
2827 | 3668 | """ |
2828 | 3669 | Same as Parameterized.script_repr, except that X.classname(Y |
2832 | 3673 | separator="\n") |
2833 | 3674 | |
2834 | 3675 | |
2835 | def pprint(self, imports=None, prefix="\n ",unknown_value='<?>', | |
2836 | qualify=False, separator=""): | |
2837 | """ | |
2838 | Same as Parameterized.pprint, except that X.classname(Y | |
3676 | def _pprint(self, imports=None, prefix="\n ",unknown_value='<?>', | |
3677 | qualify=False, separator=""): | |
3678 | """ | |
3679 | Same as Parameterized._pprint, except that X.classname(Y | |
2839 | 3680 | is replaced with X.classname.instance(Y |
2840 | 3681 | """ |
2841 | r = Parameterized.pprint(self,imports,prefix, | |
2842 | unknown_value=unknown_value, | |
2843 | qualify=qualify,separator=separator) | |
3682 | r = Parameterized._pprint(self,imports,prefix, | |
3683 | unknown_value=unknown_value, | |
3684 | qualify=qualify,separator=separator) | |
2844 | 3685 | classname=self.__class__.__name__ |
2845 | 3686 | return r.replace(".%s("%classname,".%s.instance("%classname) |
2846 | 3687 | |
2873 | 3714 | label_formatter = default_label_formatter |
2874 | 3715 | |
2875 | 3716 | |
2876 | # CBENHANCEMENT: should be able to remove overridable_property when we | |
2877 | # switch to Python 2.6: | |
2878 | # "Properties now have three attributes, getter, setter and deleter, | |
2879 | # that are decorators providing useful shortcuts for adding a getter, | |
2880 | # setter or deleter function to an existing property." | |
2881 | # http://docs.python.org/whatsnew/2.6.html | |
2882 | ||
2883 | # Renamed & documented version of OProperty from | |
3717 | # PARAM2_DEPRECATION: Should be able to remove this; was originally | |
3718 | # adapted from OProperty from | |
2884 | 3719 | # infinitesque.net/articles/2005/enhancing%20Python's%20property.xhtml |
3720 | # but since python 2.6 the getter, setter, and deleter attributes of | |
3721 | # a property should provide similar functionality already. | |
2885 | 3722 | class overridable_property(object): |
2886 | 3723 | """ |
2887 | 3724 | The same as Python's "property" attribute, but allows the accessor |
0 | """ | |
1 | Classes used to support string serialization of Parameters and | |
2 | Parameterized objects. | |
3 | """ | |
4 | ||
5 | import json | |
6 | import textwrap | |
7 | ||
8 | class UnserializableException(Exception): | |
9 | pass | |
10 | ||
11 | class UnsafeserializableException(Exception): | |
12 | pass | |
13 | ||
14 | def JSONNullable(json_type): | |
15 | "Express a JSON schema type as nullable to easily support Parameters that allow_None" | |
16 | return {'anyOf': [ json_type, {'type': 'null'}] } | |
17 | ||
18 | ||
19 | ||
20 | class Serialization(object): | |
21 | """ | |
22 | Base class used to implement different types of serialization. | |
23 | """ | |
24 | ||
25 | @classmethod | |
26 | def schema(cls, pobj, subset=None): | |
27 | raise NotImplementedError # noqa: unimplemented method | |
28 | ||
29 | @classmethod | |
30 | def serialize_parameters(cls, pobj, subset=None): | |
31 | """ | |
32 | Serialize the parameters on a Parameterized object into a | |
33 | single serialized object, e.g. a JSON string. | |
34 | """ | |
35 | raise NotImplementedError # noqa: unimplemented method | |
36 | ||
37 | @classmethod | |
38 | def deserialize_parameters(cls, pobj, serialized, subset=None): | |
39 | """ | |
40 | Deserialize a serialized object representing one or | |
41 | more Parameters into a dictionary of parameter values. | |
42 | """ | |
43 | raise NotImplementedError # noqa: unimplemented method | |
44 | ||
45 | @classmethod | |
46 | def serialize_parameter_value(cls, pobj, pname): | |
47 | """ | |
48 | Serialize a single parameter value. | |
49 | """ | |
50 | raise NotImplementedError # noqa: unimplemented method | |
51 | ||
52 | @classmethod | |
53 | def deserialize_parameter_value(cls, pobj, pname, value): | |
54 | """ | |
55 | Deserialize a single parameter value. | |
56 | """ | |
57 | raise NotImplementedError # noqa: unimplemented method | |
58 | ||
59 | ||
60 | class JSONSerialization(Serialization): | |
61 | """ | |
62 | Class responsible for specifying JSON serialization, deserialization | |
63 | and JSON schemas for Parameters and Parameterized classes and | |
64 | objects. | |
65 | """ | |
66 | ||
67 | unserializable_parameter_types = ['Callable'] | |
68 | ||
69 | json_schema_literal_types = { | |
70 | int:'integer', float:'number', str:'string', | |
71 | type(None): 'null' | |
72 | } | |
73 | ||
74 | @classmethod | |
75 | def loads(cls, serialized): | |
76 | return json.loads(serialized) | |
77 | ||
78 | @classmethod | |
79 | def dumps(cls, obj): | |
80 | return json.dumps(obj) | |
81 | ||
82 | @classmethod | |
83 | def schema(cls, pobj, safe=False, subset=None): | |
84 | schema = {} | |
85 | for name, p in pobj.param.objects('existing').items(): | |
86 | if subset is not None and name not in subset: | |
87 | continue | |
88 | schema[name] = p.schema(safe=safe) | |
89 | if p.doc: | |
90 | schema[name]['description'] = textwrap.dedent(p.doc).replace('\n', ' ').strip() | |
91 | if p.label: | |
92 | schema[name]['title'] = p.label | |
93 | return schema | |
94 | ||
95 | @classmethod | |
96 | def serialize_parameters(cls, pobj, subset=None): | |
97 | components = {} | |
98 | for name, p in pobj.param.objects('existing').items(): | |
99 | if subset is not None and name not in subset: | |
100 | continue | |
101 | value = pobj.param.get_value_generator(name) | |
102 | components[name] = p.serialize(value) | |
103 | return cls.dumps(components) | |
104 | ||
105 | @classmethod | |
106 | def deserialize_parameters(cls, pobj, serialization, subset=None): | |
107 | deserialized = cls.loads(serialization) | |
108 | components = {} | |
109 | for name, value in deserialized.items(): | |
110 | if subset is not None and name not in subset: | |
111 | continue | |
112 | deserialized = pobj.param[name].deserialize(value) | |
113 | components[name] = deserialized | |
114 | return components | |
115 | ||
116 | # Parameter level methods | |
117 | ||
118 | @classmethod | |
119 | def _get_method(cls, ptype, suffix): | |
120 | "Returns specialized method if available, otherwise None" | |
121 | method_name = ptype.lower()+'_' + suffix | |
122 | return getattr(cls, method_name, None) | |
123 | ||
124 | @classmethod | |
125 | def param_schema(cls, ptype, p, safe=False, subset=None): | |
126 | if ptype in cls.unserializable_parameter_types: | |
127 | raise UnserializableException | |
128 | dispatch_method = cls._get_method(ptype, 'schema') | |
129 | if dispatch_method: | |
130 | schema = dispatch_method(p, safe=safe) | |
131 | else: | |
132 | schema = {'type': ptype.lower()} | |
133 | return JSONNullable(schema) if p.allow_None else schema | |
134 | ||
135 | @classmethod | |
136 | def serialize_parameter_value(cls, pobj, pname): | |
137 | value = pobj.param.get_value_generator(pname) | |
138 | return cls.dumps(pobj.param[pname].serialize(value)) | |
139 | ||
140 | @classmethod | |
141 | def deserialize_parameter_value(cls, pobj, pname, value): | |
142 | value = cls.loads(value) | |
143 | return pobj.param[pname].deserialize(value) | |
144 | ||
145 | # Custom Schemas | |
146 | ||
147 | @classmethod | |
148 | def class__schema(cls, class_, safe=False): | |
149 | from .parameterized import Parameterized | |
150 | if isinstance(class_, tuple): | |
151 | return {'anyOf': [cls.class__schema(cls_) for cls_ in class_]} | |
152 | elif class_ in cls.json_schema_literal_types: | |
153 | return {'type': cls.json_schema_literal_types[class_]} | |
154 | elif issubclass(class_, Parameterized): | |
155 | return {'type': 'object', 'properties': class_.param.schema(safe)} | |
156 | else: | |
157 | return {'type': 'object'} | |
158 | ||
159 | @classmethod | |
160 | def array_schema(cls, p, safe=False): | |
161 | if safe is True: | |
162 | msg = ('Array is not guaranteed to be safe for ' | |
163 | 'serialization as the dtype is unknown') | |
164 | raise UnsafeserializableException(msg) | |
165 | return {'type': 'array'} | |
166 | ||
167 | @classmethod | |
168 | def classselector_schema(cls, p, safe=False): | |
169 | return cls.class__schema(p.class_, safe=safe) | |
170 | ||
171 | @classmethod | |
172 | def dict_schema(cls, p, safe=False): | |
173 | if safe is True: | |
174 | msg = ('Dict is not guaranteed to be safe for ' | |
175 | 'serialization as the key and value types are unknown') | |
176 | raise UnsafeserializableException(msg) | |
177 | return {'type': 'object'} | |
178 | ||
179 | @classmethod | |
180 | def date_schema(cls, p, safe=False): | |
181 | return {'type': 'string', 'format': 'date-time'} | |
182 | ||
183 | @classmethod | |
184 | def calendardate_schema(cls, p, safe=False): | |
185 | return {'type': 'string', 'format': 'date'} | |
186 | ||
187 | @classmethod | |
188 | def tuple_schema(cls, p, safe=False): | |
189 | schema = {'type': 'array'} | |
190 | if p.length is not None: | |
191 | schema['minItems'] = p.length | |
192 | schema['maxItems'] = p.length | |
193 | return schema | |
194 | ||
195 | @classmethod | |
196 | def number_schema(cls, p, safe=False): | |
197 | schema = {'type': p.__class__.__name__.lower() } | |
198 | return cls.declare_numeric_bounds(schema, p.bounds, p.inclusive_bounds) | |
199 | ||
200 | @classmethod | |
201 | def declare_numeric_bounds(cls, schema, bounds, inclusive_bounds): | |
202 | "Given an applicable numeric schema, augment with bounds information" | |
203 | if bounds is not None: | |
204 | (low, high) = bounds | |
205 | if low is not None: | |
206 | key = 'minimum' if inclusive_bounds[0] else 'exclusiveMinimum' | |
207 | schema[key] = low | |
208 | if high is not None: | |
209 | key = 'maximum' if inclusive_bounds[1] else 'exclusiveMaximum' | |
210 | schema[key] = high | |
211 | return schema | |
212 | ||
213 | @classmethod | |
214 | def integer_schema(cls, p, safe=False): | |
215 | return cls.number_schema(p) | |
216 | ||
217 | @classmethod | |
218 | def numerictuple_schema(cls, p, safe=False): | |
219 | schema = cls.tuple_schema(p, safe=safe) | |
220 | schema['additionalItems'] = {'type': 'number'} | |
221 | return schema | |
222 | ||
223 | @classmethod | |
224 | def xycoordinates_schema(cls, p, safe=False): | |
225 | return cls.numerictuple_schema(p, safe=safe) | |
226 | ||
227 | @classmethod | |
228 | def range_schema(cls, p, safe=False): | |
229 | schema = cls.tuple_schema(p, safe=safe) | |
230 | bounded_number = cls.declare_numeric_bounds( | |
231 | {'type': 'number'}, p.bounds, p.inclusive_bounds) | |
232 | schema['additionalItems'] = bounded_number | |
233 | return schema | |
234 | ||
235 | @classmethod | |
236 | def list_schema(cls, p, safe=False): | |
237 | schema = {'type': 'array'} | |
238 | if safe is True and p.item_type is None: | |
239 | msg = ('List without a class specified cannot be guaranteed ' | |
240 | 'to be safe for serialization') | |
241 | raise UnsafeserializableException(msg) | |
242 | if p.class_ is not None: | |
243 | schema['items'] = cls.class__schema(p.item_type, safe=safe) | |
244 | return schema | |
245 | ||
246 | @classmethod | |
247 | def objectselector_schema(cls, p, safe=False): | |
248 | try: | |
249 | allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]} | |
250 | for obj in p.objects] | |
251 | schema = {'anyOf': allowed_types} | |
252 | schema['enum'] = p.objects | |
253 | return schema | |
254 | except: | |
255 | if safe is True: | |
256 | msg = ('ObjectSelector cannot be guaranteed to be safe for ' | |
257 | 'serialization due to unserializable type in objects') | |
258 | raise UnsafeserializableException(msg) | |
259 | return {} | |
260 | ||
261 | @classmethod | |
262 | def selector_schema(cls, p, safe=False): | |
263 | try: | |
264 | allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]} | |
265 | for obj in p.objects.values()] | |
266 | schema = {'anyOf': allowed_types} | |
267 | schema['enum'] = p.objects | |
268 | return schema | |
269 | except: | |
270 | if safe is True: | |
271 | msg = ('Selector cannot be guaranteed to be safe for ' | |
272 | 'serialization due to unserializable type in objects') | |
273 | raise UnsafeserializableException(msg) | |
274 | return {} | |
275 | ||
276 | @classmethod | |
277 | def listselector_schema(cls, p, safe=False): | |
278 | if p.objects is None: | |
279 | if safe is True: | |
280 | msg = ('ListSelector cannot be guaranteed to be safe for ' | |
281 | 'serialization as allowed objects unspecified') | |
282 | return {'type': 'array'} | |
283 | for obj in p.objects: | |
284 | if type(obj) not in cls.json_schema_literal_types: | |
285 | msg = 'ListSelector cannot serialize type %s' % type(obj) | |
286 | raise UnserializableException(msg) | |
287 | return {'type': 'array', 'items': {'enum': p.objects}} | |
288 | ||
289 | @classmethod | |
290 | def dataframe_schema(cls, p, safe=False): | |
291 | schema = {'type': 'array'} | |
292 | if safe is True: | |
293 | msg = ('DataFrame is not guaranteed to be safe for ' | |
294 | 'serialization as the column dtypes are unknown') | |
295 | raise UnsafeserializableException(msg) | |
296 | if p.columns is None: | |
297 | schema['items'] = {'type': 'object'} | |
298 | return schema | |
299 | ||
300 | mincols, maxcols = None, None | |
301 | if isinstance(p.columns, int): | |
302 | mincols, maxcols = p.columns, p.columns | |
303 | elif isinstance(p.columns, tuple): | |
304 | mincols, maxcols = p.columns | |
305 | ||
306 | if isinstance(p.columns, int) or isinstance(p.columns, tuple): | |
307 | schema['items'] = {'type': 'object', 'minItems': mincols, | |
308 | 'maxItems': maxcols} | |
309 | ||
310 | if isinstance(p.columns, list) or isinstance(p.columns, set): | |
311 | literal_types = [{'type':el} for el in cls.json_schema_literal_types.values()] | |
312 | allowable_types = {'anyOf': literal_types} | |
313 | properties = {name: allowable_types for name in p.columns} | |
314 | schema['items'] = {'type': 'object', 'properties': properties} | |
315 | ||
316 | minrows, maxrows = None, None | |
317 | if isinstance(p.rows, int): | |
318 | minrows, maxrows = p.rows, p.rows | |
319 | elif isinstance(p.rows, tuple): | |
320 | minrows, maxrows = p.rows | |
321 | ||
322 | if minrows is not None: | |
323 | schema['minItems'] = minrows | |
324 | if maxrows is not None: | |
325 | schema['maxItems'] = maxrows | |
326 | ||
327 | return schema |
1 | 1 | Provide consistent and up-to-date ``__version__`` strings for |
2 | 2 | Python packages. |
3 | 3 | |
4 | See https://github.com/pyviz/autover for more information. | |
4 | See https://github.com/holoviz/autover for more information. | |
5 | 5 | """ |
6 | 6 | |
7 | 7 | # The Version class is a copy of autover.version.Version v0.2.5, |
25 | 25 | cwd=cwd) |
26 | 26 | output, error = (str(s.decode()).strip() for s in proc.communicate()) |
27 | 27 | |
28 | if proc.returncode != 0: | |
28 | # Detects errors as _either_ a non-zero return code _or_ messages | |
29 | # printed to stderr, because the return code is erroneously fixed at | |
30 | # zero in some cases (see https://github.com/holoviz/param/pull/389). | |
31 | if proc.returncode != 0 or len(error) > 0: | |
29 | 32 | raise Exception(proc.returncode, error) |
30 | 33 | return output |
31 | 34 | |
173 | 176 | # Verify this is the correct repository (since fpath could |
174 | 177 | # be an unrelated git repository, and autover could just have |
175 | 178 | # been copied/installed into it). |
176 | output = run_cmd([cmd, 'remote', '-v'], | |
177 | cwd=os.path.dirname(self.fpath)) | |
178 | repo_matches = ['', # No remote set | |
179 | '/' + self.reponame + '.git' , | |
179 | remotes = run_cmd([cmd, 'remote', '-v'], | |
180 | cwd=os.path.dirname(self.fpath)) | |
181 | repo_matches = ['/' + self.reponame + '.git' , | |
180 | 182 | # A remote 'server:reponame.git' can also be referred |
181 | 183 | # to (i.e. cloned) as `server:reponame`. |
182 | 184 | '/' + self.reponame + ' '] |
183 | if not any(m in output for m in repo_matches): | |
184 | return self | |
185 | ||
186 | output = run_cmd([cmd, 'describe', '--long', '--match', | |
187 | "v[0-9]*.[0-9]*.[0-9]*", '--dirty'], | |
188 | cwd=os.path.dirname(self.fpath)) | |
185 | if not any(m in remotes for m in repo_matches): | |
186 | try: | |
187 | output = self._output_from_file() | |
188 | if output is not None: | |
189 | self._update_from_vcs(output) | |
190 | except: pass | |
191 | if output is None: | |
192 | # glob pattern (not regexp) matching vX.Y.Z* tags | |
193 | output = run_cmd([cmd, 'describe', '--long', '--match', | |
194 | "v[0-9]*.[0-9]*.[0-9]*", '--dirty'], | |
195 | cwd=os.path.dirname(self.fpath)) | |
189 | 196 | if as_string: return output |
190 | 197 | except Exception as e1: |
191 | 198 | try: |
0 | Metadata-Version: 2.1 | |
1 | Name: param | |
2 | Version: None | |
3 | Summary: Make your Python code clearer and more reliable by declaring Parameters. | |
4 | Home-page: http://param.holoviz.org/ | |
5 | Author: HoloViz | |
6 | Author-email: developers@holoviz.org | |
7 | Maintainer: HoloViz | |
8 | Maintainer-email: developers@holoviz.org | |
9 | License: BSD | |
10 | Project-URL: Documentation, https://param.holoviz.org/ | |
11 | Project-URL: Releases, https://github.com/holoviz/param/releases | |
12 | Project-URL: Bug Tracker, https://github.com/holoviz/param/issues | |
13 | Project-URL: Source Code, https://github.com/holoviz/param | |
14 | Project-URL: Panel Examples, https://panel.holoviz.org/user_guide/Param.html | |
15 | Platform: Windows | |
16 | Platform: Mac OS X | |
17 | Platform: Linux | |
18 | Classifier: License :: OSI Approved :: BSD License | |
19 | Classifier: Development Status :: 5 - Production/Stable | |
20 | Classifier: Programming Language :: Python :: 2 | |
21 | Classifier: Programming Language :: Python :: 2.7 | |
22 | Classifier: Programming Language :: Python :: 3 | |
23 | Classifier: Programming Language :: Python :: 3.6 | |
24 | Classifier: Programming Language :: Python :: 3.7 | |
25 | Classifier: Programming Language :: Python :: 3.8 | |
26 | Classifier: Programming Language :: Python :: 3.9 | |
27 | Classifier: Programming Language :: Python :: 3.10 | |
28 | Classifier: Operating System :: OS Independent | |
29 | Classifier: Intended Audience :: Science/Research | |
30 | Classifier: Intended Audience :: Developers | |
31 | Classifier: Natural Language :: English | |
32 | Classifier: Topic :: Scientific/Engineering | |
33 | Classifier: Topic :: Software Development :: Libraries | |
34 | Provides: param | |
35 | Provides: numbergen | |
36 | Requires-Python: >=2.7 | |
37 | Description-Content-Type: text/markdown | |
38 | Provides-Extra: all | |
39 | Provides-Extra: doc | |
40 | Provides-Extra: tests | |
41 | License-File: LICENSE.txt | |
42 | ||
43 | <img src="https://raw.githubusercontent.com/holoviz/param/master/doc/_static/logo_horizontal.png" width=250> | |
44 | ||
45 | | | | | |
46 | | --- | --- | | |
47 | | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml) | |
48 | | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/master/graph/badge.svg)](https://codecov.io/gh/holoviz/param) || | |
49 | | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/pyviz-dev.github.io/param.svg?label=dev%20website)](https://pyviz-dev.github.io/param/) | | |
50 | | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/param) | | |
51 | | Python | [![Python support](https://img.shields.io/pypi/pyversions/param.svg)](https://pypi.org/project/param/) | |
52 | | Docs | [![gh-pages](https://img.shields.io/github/last-commit/holoviz/param/gh-pages.svg)](https://github.com/holoviz/param/tree/gh-pages) [![site](https://img.shields.io/website-up-down-green-red/https/param.holoviz.org.svg)](https://param.holoviz.org) | | |
53 | | Binder | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/holoviz/param/master?labpath=examples) | | |
54 | | Support | [![Discourse](https://img.shields.io/discourse/status?server=https%3A%2F%2Fdiscourse.holoviz.org)](https://discourse.holoviz.org/) | | |
55 | ||
56 | 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. | |
57 | ||
58 | 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. | |
59 | ||
60 | Please see [param's website](https://param.holoviz.org) for official releases, installation instructions, documentation, and examples. | |
61 | ||
62 |
0 | LICENSE.txt | |
1 | MANIFEST.in | |
2 | README.md | |
3 | setup.cfg | |
4 | setup.py | |
5 | numbergen/__init__.py | |
6 | param/.version | |
7 | param/__init__.py | |
8 | param/ipython.py | |
9 | param/parameterized.py | |
10 | param/serializer.py | |
11 | param/version.py | |
12 | param.egg-info/PKG-INFO | |
13 | param.egg-info/SOURCES.txt | |
14 | param.egg-info/dependency_links.txt | |
15 | param.egg-info/requires.txt | |
16 | param.egg-info/top_level.txt⏎ |
0 | ||
1 | [all] | |
2 | aiohttp | |
3 | flake8 | |
4 | graphviz | |
5 | jinja2<3.1 | |
6 | myst-parser | |
7 | myst_nb==0.12.2 | |
8 | nbconvert | |
9 | nbsite>=0.7.1 | |
10 | pandas | |
11 | panel | |
12 | pydata-sphinx-theme | |
13 | pygraphviz | |
14 | pytest | |
15 | pytest-cov | |
16 | sphinx-copybutton | |
17 | ||
18 | [doc] | |
19 | aiohttp | |
20 | graphviz | |
21 | jinja2<3.1 | |
22 | myst-parser | |
23 | myst_nb==0.12.2 | |
24 | nbconvert | |
25 | nbsite>=0.7.1 | |
26 | pandas | |
27 | panel | |
28 | pydata-sphinx-theme | |
29 | pygraphviz | |
30 | sphinx-copybutton | |
31 | ||
32 | [tests] | |
33 | flake8 | |
34 | pytest | |
35 | pytest-cov |
1 | 1 | universal = 1 |
2 | 2 | |
3 | 3 | [flake8] |
4 | # TODO tests (one day) | |
5 | 4 | include = setup.py param numbergen |
6 | exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,tests | |
7 | ignore = E1, | |
8 | E2, | |
9 | E3, | |
10 | E4, | |
11 | E5, | |
12 | E701, | |
13 | E702, | |
14 | E703, | |
15 | E704, | |
16 | E722, | |
17 | E741, | |
18 | E742, | |
19 | E743, | |
20 | W503 | |
5 | exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,tests,.ipynb_checkpoints,run_test.py | |
6 | ignore = E114, | |
7 | E116, | |
8 | E126, | |
9 | E128, | |
10 | E129, | |
11 | E2, | |
12 | E3, | |
13 | E4, | |
14 | E5, | |
15 | E731, | |
16 | E701, | |
17 | E702, | |
18 | E703, | |
19 | E704, | |
20 | E722, | |
21 | E741, | |
22 | E742, | |
23 | E743, | |
24 | W503, | |
25 | W504, | |
21 | 26 | |
22 | [nosetests] | |
23 | verbosity = 2 | |
24 | with-doctest = 1 | |
25 | nologcapture = 1 | |
27 | [tool:pytest] | |
28 | python_files = test*.py | |
29 | ||
30 | [egg_info] | |
31 | tag_build = | |
32 | tag_date = 0 | |
33 |
18 | 18 | # pip doesn't support tests_require |
19 | 19 | # (https://github.com/pypa/pip/issues/1197) |
20 | 20 | 'tests': [ |
21 | 'nose', | |
22 | 'flake8' | |
21 | 'pytest', | |
22 | 'pytest-cov', | |
23 | 'flake8', | |
24 | ], | |
25 | 'doc': [ | |
26 | 'pygraphviz', | |
27 | 'nbsite >=0.7.1', | |
28 | 'pydata-sphinx-theme', | |
29 | 'jinja2 <3.1', # API breakage | |
30 | 'myst-parser', | |
31 | 'nbconvert', | |
32 | 'graphviz', | |
33 | 'myst_nb ==0.12.2', | |
34 | 'sphinx-copybutton', | |
35 | 'aiohttp', | |
36 | 'panel', | |
37 | 'pandas', | |
23 | 38 | ] |
24 | 39 | } |
25 | 40 | |
31 | 46 | setup_args = dict( |
32 | 47 | name='param', |
33 | 48 | version=get_setup_version("param"), |
34 | description='Declarative Python programming using Parameters.', | |
35 | long_description=open('README.rst').read() if os.path.isfile('README.rst') else 'Consult README.rst', | |
36 | author="IOAM", | |
37 | author_email="developers@topographica.org", | |
38 | maintainer="IOAM", | |
39 | maintainer_email="developers@topographica.org", | |
49 | description='Make your Python code clearer and more reliable by declaring Parameters.', | |
50 | long_description=open('README.md').read() if os.path.isfile('README.md') else 'Consult README.md', | |
51 | long_description_content_type="text/markdown", | |
52 | author="HoloViz", | |
53 | author_email="developers@holoviz.org", | |
54 | maintainer="HoloViz", | |
55 | maintainer_email="developers@holoviz.org", | |
40 | 56 | platforms=['Windows', 'Mac OS X', 'Linux'], |
41 | 57 | license='BSD', |
42 | url='http://ioam.github.com/param/', | |
58 | url='http://param.holoviz.org/', | |
43 | 59 | packages=["param","numbergen"], |
44 | 60 | provides=["param","numbergen"], |
45 | 61 | include_package_data = True, |
47 | 63 | install_requires=[], |
48 | 64 | extras_require=extras_require, |
49 | 65 | tests_require=extras_require['tests'], |
66 | project_urls={ | |
67 | "Documentation": "https://param.holoviz.org/", | |
68 | "Releases": "https://github.com/holoviz/param/releases", | |
69 | "Bug Tracker": "https://github.com/holoviz/param/issues", | |
70 | "Source Code": "https://github.com/holoviz/param", | |
71 | "Panel Examples": "https://panel.holoviz.org/user_guide/Param.html", | |
72 | }, | |
50 | 73 | classifiers=[ |
51 | 74 | "License :: OSI Approved :: BSD License", |
52 | 75 | "Development Status :: 5 - Production/Stable", |
53 | 76 | "Programming Language :: Python :: 2", |
54 | 77 | "Programming Language :: Python :: 2.7", |
55 | 78 | "Programming Language :: Python :: 3", |
56 | "Programming Language :: Python :: 3.4", | |
57 | "Programming Language :: Python :: 3.5", | |
58 | 79 | "Programming Language :: Python :: 3.6", |
80 | "Programming Language :: Python :: 3.7", | |
81 | "Programming Language :: Python :: 3.8", | |
82 | "Programming Language :: Python :: 3.9", | |
83 | "Programming Language :: Python :: 3.10", | |
59 | 84 | "Operating System :: OS Independent", |
60 | 85 | "Intended Audience :: Science/Research", |
61 | 86 | "Intended Audience :: Developers", |
0 | """ | |
1 | Unit test for CalendarDate parameters. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import datetime as dt | |
6 | import param | |
7 | ||
8 | ||
9 | class TestDateTimeParameters(unittest.TestCase): | |
10 | ||
11 | def test_initialization_out_of_bounds(self): | |
12 | try: | |
13 | class Q(param.Parameterized): | |
14 | q = param.Date(dt.date(2017,2,27), | |
15 | bounds=(dt.date(2017,2,1), | |
16 | dt.date(2017,2,26))) | |
17 | except ValueError: | |
18 | pass | |
19 | else: | |
20 | raise AssertionError("No exception raised on out-of-bounds date") | |
21 | ||
22 | def test_set_out_of_bounds(self): | |
23 | class Q(param.Parameterized): | |
24 | q = param.Date(bounds=(dt.date(2017,2,1), | |
25 | dt.date(2017,2,26))) | |
26 | try: | |
27 | Q.q = dt.date(2017,2,27) | |
28 | except ValueError: | |
29 | pass | |
30 | else: | |
31 | raise AssertionError("No exception raised on out-of-bounds date") | |
32 | ||
33 | def test_set_exclusive_out_of_bounds(self): | |
34 | class Q(param.Parameterized): | |
35 | q = param.Date(bounds=(dt.date(2017,2,1), | |
36 | dt.date(2017,2,26)), | |
37 | inclusive_bounds=(True, False)) | |
38 | try: | |
39 | Q.q = dt.date(2017,2,26) | |
40 | except ValueError: | |
41 | pass | |
42 | else: | |
43 | raise AssertionError("No exception raised on out-of-bounds date") | |
44 | ||
45 | def test_get_soft_bounds(self): | |
46 | q = param.Date(dt.date(2017,2,25), | |
47 | bounds=(dt.date(2017,2,1), | |
48 | dt.date(2017,2,26)), | |
49 | softbounds=(dt.date(2017,2,1), | |
50 | dt.date(2017,2,25))) | |
51 | self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1), | |
52 | dt.date(2017,2,25))) |
0 | """ | |
1 | Unit tests for CalendarDateRange parameter. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import datetime as dt | |
6 | import param | |
7 | ||
8 | ||
9 | # Assuming tests of range parameter cover most of what's needed to | |
10 | # test date range. | |
11 | ||
12 | class TestDateTimeRange(unittest.TestCase): | |
13 | ||
14 | bad_range = (dt.date(2017,2,27),dt.date(2017,2,26)) | |
15 | ||
16 | def test_wrong_type_default(self): | |
17 | try: | |
18 | class Q(param.Parameterized): | |
19 | a = param.CalendarDateRange(default=(1.0,2.0)) | |
20 | except ValueError: | |
21 | pass | |
22 | else: | |
23 | raise AssertionError("Bad date type was accepted.") | |
24 | ||
25 | def test_wrong_type_init(self): | |
26 | class Q(param.Parameterized): | |
27 | a = param.CalendarDateRange() | |
28 | ||
29 | try: | |
30 | Q(a=self.bad_range) | |
31 | except ValueError: | |
32 | pass | |
33 | else: | |
34 | raise AssertionError("Bad date type was accepted.") | |
35 | ||
36 | def test_wrong_type_set(self): | |
37 | class Q(param.Parameterized): | |
38 | a = param.CalendarDateRange() | |
39 | q = Q() | |
40 | ||
41 | try: | |
42 | q.a = self.bad_range | |
43 | except ValueError: | |
44 | pass | |
45 | else: | |
46 | raise AssertionError("Bad date type was accepted.") | |
47 | ||
48 | def test_start_before_end_default(self): | |
49 | try: | |
50 | class Q(param.Parameterized): | |
51 | a = param.CalendarDateRange(default=self.bad_range) | |
52 | except ValueError: | |
53 | pass | |
54 | else: | |
55 | raise AssertionError("Bad date range was accepted.") | |
56 | ||
57 | def test_start_before_end_init(self): | |
58 | class Q(param.Parameterized): | |
59 | a = param.CalendarDateRange() | |
60 | ||
61 | try: | |
62 | Q(a=self.bad_range) | |
63 | except ValueError: | |
64 | pass | |
65 | else: | |
66 | raise AssertionError("Bad date range was accepted.") | |
67 | ||
68 | def test_start_before_end_set(self): | |
69 | class Q(param.Parameterized): | |
70 | a = param.CalendarDateRange() | |
71 | ||
72 | q = Q() | |
73 | try: | |
74 | q.a = self.bad_range | |
75 | except ValueError: | |
76 | pass | |
77 | else: | |
78 | raise AssertionError("Bad date range was accepted.") |
0 | """ | |
1 | Unit test for ClassSelector parameters. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | from numbers import Number | |
6 | ||
7 | import param | |
8 | ||
9 | ||
10 | class TestClassSelectorParameters(unittest.TestCase): | |
11 | ||
12 | def setUp(self): | |
13 | ||
14 | class P(param.Parameterized): | |
15 | e = param.ClassSelector(default=1,class_=int) | |
16 | f = param.ClassSelector(default=int,class_=Number, is_instance=False) | |
17 | g = param.ClassSelector(default=1,class_=(int,str)) | |
18 | h = param.ClassSelector(default=int,class_=(int,str), is_instance=False) | |
19 | ||
20 | self.P = P | |
21 | ||
22 | def test_single_class_instance_constructor(self): | |
23 | p = self.P(e=6) | |
24 | self.assertEqual(p.e, 6) | |
25 | ||
26 | def test_single_class_instance_error(self): | |
27 | exception = "Parameter 'e' value must be an instance of int, not 'a'" | |
28 | with self.assertRaisesRegexp(ValueError, exception): | |
29 | self.P(e='a') | |
30 | ||
31 | def test_single_class_type_constructor(self): | |
32 | p = self.P(f=float) | |
33 | self.assertEqual(p.f, float) | |
34 | ||
35 | def test_single_class_type_error(self): | |
36 | exception = "Parameter 'str' must be a subclass of Number, not 'type'" | |
37 | with self.assertRaisesRegexp(ValueError, exception): | |
38 | self.P(f=str) | |
39 | ||
40 | def test_multiple_class_instance_constructor1(self): | |
41 | p = self.P(g=1) | |
42 | self.assertEqual(p.g, 1) | |
43 | ||
44 | def test_multiple_class_instance_constructor2(self): | |
45 | p = self.P(g='A') | |
46 | self.assertEqual(p.g, 'A') | |
47 | ||
48 | def test_multiple_class_instance_error(self): | |
49 | exception = "Parameter 'g' value must be an instance of \(int, str\), not '3.0'" | |
50 | with self.assertRaisesRegexp(ValueError, exception): | |
51 | self.P(g=3.0) | |
52 | ||
53 | def test_multiple_class_type_constructor1(self): | |
54 | p = self.P(h=int) | |
55 | self.assertEqual(p.h, int) | |
56 | ||
57 | def test_multiple_class_type_constructor2(self): | |
58 | p = self.P(h=str) | |
59 | self.assertEqual(p.h, str) | |
60 | ||
61 | def test_multiple_class_type_error(self): | |
62 | exception = "Parameter 'float' must be a subclass of \(int, str\), not 'type'" | |
63 | with self.assertRaisesRegexp(ValueError, exception): | |
64 | self.P(h=float) |
0 | """ | |
1 | Unit test for Color parameters. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import param | |
6 | ||
7 | ||
8 | class TestColorParameters(unittest.TestCase): | |
9 | ||
10 | def test_initialization_invalid_string(self): | |
11 | try: | |
12 | class Q(param.Parameterized): | |
13 | q = param.Color('red') | |
14 | except ValueError: | |
15 | pass | |
16 | else: | |
17 | raise AssertionError("No exception raised on out-of-bounds date") | |
18 | ||
19 | def test_set_invalid_string(self): | |
20 | class Q(param.Parameterized): | |
21 | q = param.Color() | |
22 | try: | |
23 | Q.q = 'red' | |
24 | except ValueError: | |
25 | pass | |
26 | else: | |
27 | raise AssertionError("No exception raised on out-of-bounds date") | |
28 | ||
29 | def test_valid_long_hex(self): | |
30 | class Q(param.Parameterized): | |
31 | q = param.Color() | |
32 | Q.q = '#ffffff' | |
33 | self.assertEqual(Q.q, '#ffffff') | |
34 | ||
35 | def test_valid_short_hex(self): | |
36 | class Q(param.Parameterized): | |
37 | q = param.Color() | |
38 | Q.q = '#fff' | |
39 | self.assertEqual(Q.q, '#fff') | |
40 |
0 | """ | |
1 | Unit test for composite parameters. | |
2 | ||
3 | Originally implemented as doctests in Topographica in the file | |
4 | testCompositeParameter.txt | |
5 | """ | |
6 | ||
7 | import unittest | |
8 | import param | |
9 | ||
10 | class TestCompositeParameters(unittest.TestCase): | |
11 | ||
12 | def setUp(self): | |
13 | # initialize a class with a compound parameter | |
14 | class A(param.Parameterized): | |
15 | x = param.Number(default=0) | |
16 | y = param.Number(default=0) | |
17 | xy = param.Composite(attribs=['x','y']) | |
18 | ||
19 | self.A = A | |
20 | self.a = self.A() | |
21 | ||
22 | class SomeSequence(object): | |
23 | "Can't use iter with Dynamic (doesn't pickle, doesn't copy)" | |
24 | def __init__(self,sequence): | |
25 | self.sequence=sequence | |
26 | self.index=0 | |
27 | def __call__(self): | |
28 | val=self.sequence[self.index] | |
29 | self.index+=1 | |
30 | return val | |
31 | ||
32 | self.SomeSequence = SomeSequence | |
33 | ||
34 | def test_initialization(self): | |
35 | "Make an instance and do default checks" | |
36 | self.assertEqual(self.a.x, 0) | |
37 | self.assertEqual(self.a.y, 0) | |
38 | self.assertEqual(self.a.xy, [0,0]) | |
39 | ||
40 | ||
41 | def test_set_component(self): | |
42 | self.a.x = 1 | |
43 | self.assertEqual(self.a.xy, [1,0]) | |
44 | ||
45 | def test_set_compound(self): | |
46 | self.a.xy = (2,3) | |
47 | self.assertEqual(self.a.x, 2) | |
48 | self.assertEqual(self.a.y, 3) | |
49 | ||
50 | def test_compound_class(self): | |
51 | " Get the compound on the class " | |
52 | self.assertEqual(self.A.xy, [0,0]) | |
53 | ||
54 | def test_set_compound_class_set(self): | |
55 | self.A.xy = (5,6) | |
56 | self.assertEqual(self.A.x, 5) | |
57 | self.assertEqual(self.A.y, 6) | |
58 | ||
59 | def test_set_compound_class_instance(self): | |
60 | self.A.xy = (5,6) | |
61 | # # Make a new instance | |
62 | b = self.A() | |
63 | self.assertEqual(b.x, 5) | |
64 | self.assertEqual(b.y, 6) | |
65 | ||
66 | def test_set_compound_class_instance_unchanged(self): | |
67 | self.a.xy = (2,3) | |
68 | self.A.xy = (5,6) | |
69 | self.assertEqual(self.a.x, 2) | |
70 | self.assertEqual(self.a.y, 3) | |
71 | ||
72 | def test_composite_dynamic(self): | |
73 | """ | |
74 | Check CompositeParameter is ok with Dynamic | |
75 | CB: this test is really of Parameterized. | |
76 | """ | |
77 | a2 = self.A(x=self.SomeSequence([1,2,3]), | |
78 | y=self.SomeSequence([4,5,6])) | |
79 | ||
80 | a2.x, a2.y # Call of x and y params | |
81 | # inspect should not advance numbers | |
82 | self.assertEqual(a2.inspect_value('xy'), [1, 4]) | |
83 | ||
84 | def test_composite_dynamic_generator(self): | |
85 | ||
86 | a2 = self.A(x=self.SomeSequence([1,2,3]), | |
87 | y=self.SomeSequence([4,5,6])) | |
88 | ||
89 | a2.x, a2.y # Call of x and y params | |
90 | ix,iy = a2.get_value_generator('xy') | |
91 | # get_value_generator() should give the objects | |
92 | self.assertEqual(ix(), 2) | |
93 | self.assertEqual(iy(), 5) | |
94 | ||
95 | ||
96 | if __name__ == "__main__": | |
97 | import nose | |
98 | nose.runmodule() |
0 | """ | |
1 | Unit test for Date parameters. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import datetime as dt | |
6 | import param | |
7 | ||
8 | ||
9 | class TestDateParameters(unittest.TestCase): | |
10 | ||
11 | def test_initialization_out_of_bounds(self): | |
12 | try: | |
13 | class Q(param.Parameterized): | |
14 | q = param.Date(dt.datetime(2017,2,27), | |
15 | bounds=(dt.datetime(2017,2,1), | |
16 | dt.datetime(2017,2,26))) | |
17 | except ValueError: | |
18 | pass | |
19 | else: | |
20 | raise AssertionError("No exception raised on out-of-bounds date") | |
21 | ||
22 | def test_set_out_of_bounds(self): | |
23 | class Q(param.Parameterized): | |
24 | q = param.Date(bounds=(dt.datetime(2017,2,1), | |
25 | dt.datetime(2017,2,26))) | |
26 | try: | |
27 | Q.q = dt.datetime(2017,2,27) | |
28 | except ValueError: | |
29 | pass | |
30 | else: | |
31 | raise AssertionError("No exception raised on out-of-bounds date") | |
32 | ||
33 | def test_set_exclusive_out_of_bounds(self): | |
34 | class Q(param.Parameterized): | |
35 | q = param.Date(bounds=(dt.datetime(2017,2,1), | |
36 | dt.datetime(2017,2,26)), | |
37 | inclusive_bounds=(True, False)) | |
38 | try: | |
39 | Q.q = dt.datetime(2017,2,26) | |
40 | except ValueError: | |
41 | pass | |
42 | else: | |
43 | raise AssertionError("No exception raised on out-of-bounds date") | |
44 | ||
45 | def test_get_soft_bounds(self): | |
46 | q = param.Date(dt.datetime(2017,2,25), | |
47 | bounds=(dt.datetime(2017,2,1), | |
48 | dt.datetime(2017,2,26)), | |
49 | softbounds=(dt.datetime(2017,2,1), | |
50 | dt.datetime(2017,2,25))) | |
51 | self.assertEqual(q.get_soft_bounds(), (dt.datetime(2017,2,1), | |
52 | dt.datetime(2017,2,25))) | |
53 |
0 | """ | |
1 | Unit tests for DateRange parameter. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import datetime as dt | |
6 | import param | |
7 | ||
8 | ||
9 | # Assuming tests of range parameter cover most of what's needed to | |
10 | # test date range. | |
11 | ||
12 | class TestDateRange(unittest.TestCase): | |
13 | ||
14 | bad_range = (dt.datetime(2017,2,27),dt.datetime(2017,2,26)) | |
15 | ||
16 | def test_wrong_type_default(self): | |
17 | try: | |
18 | class Q(param.Parameterized): | |
19 | a = param.DateRange(default=(1.0,2.0)) | |
20 | except ValueError: | |
21 | pass | |
22 | else: | |
23 | raise AssertionError("Bad date type was accepted.") | |
24 | ||
25 | def test_wrong_type_init(self): | |
26 | class Q(param.Parameterized): | |
27 | a = param.DateRange() | |
28 | ||
29 | try: | |
30 | Q(a=self.bad_range) | |
31 | except ValueError: | |
32 | pass | |
33 | else: | |
34 | raise AssertionError("Bad date type was accepted.") | |
35 | ||
36 | def test_wrong_type_set(self): | |
37 | class Q(param.Parameterized): | |
38 | a = param.DateRange() | |
39 | q = Q() | |
40 | ||
41 | try: | |
42 | q.a = self.bad_range | |
43 | except ValueError: | |
44 | pass | |
45 | else: | |
46 | raise AssertionError("Bad date type was accepted.") | |
47 | ||
48 | def test_start_before_end_default(self): | |
49 | try: | |
50 | class Q(param.Parameterized): | |
51 | a = param.DateRange(default=self.bad_range) | |
52 | except ValueError: | |
53 | pass | |
54 | else: | |
55 | raise AssertionError("Bad date range was accepted.") | |
56 | ||
57 | def test_start_before_end_init(self): | |
58 | class Q(param.Parameterized): | |
59 | a = param.DateRange() | |
60 | ||
61 | try: | |
62 | Q(a=self.bad_range) | |
63 | except ValueError: | |
64 | pass | |
65 | else: | |
66 | raise AssertionError("Bad date range was accepted.") | |
67 | ||
68 | def test_start_before_end_set(self): | |
69 | class Q(param.Parameterized): | |
70 | a = param.DateRange() | |
71 | ||
72 | q = Q() | |
73 | try: | |
74 | q.a = self.bad_range | |
75 | except ValueError: | |
76 | pass | |
77 | else: | |
78 | raise AssertionError("Bad date range was accepted.") | |
79 | ||
80 |
0 | """ | |
1 | Do all subclasses of Parameter supply a valid default? | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | ||
6 | from param.parameterized import add_metaclass | |
7 | from param import concrete_descendents, Parameter | |
8 | ||
9 | # import all parameter types | |
10 | from param import ClassSelector | |
11 | from param import * # noqa | |
12 | ||
13 | ||
14 | positional_args = { | |
15 | ClassSelector: (object,) | |
16 | } | |
17 | ||
18 | skip = [] | |
19 | ||
20 | try: | |
21 | import numpy # noqa | |
22 | except ImportError: | |
23 | skip.append('Array') | |
24 | ||
25 | try: | |
26 | import pandas # noqa | |
27 | except ImportError: | |
28 | skip.append('DataFrame') | |
29 | skip.append('Series') | |
30 | ||
31 | ||
32 | class TestDefaultsMetaclass(type): | |
33 | def __new__(mcs, name, bases, dict_): | |
34 | ||
35 | def test_skip(*args,**kw): | |
36 | from nose.exc import SkipTest | |
37 | raise SkipTest | |
38 | ||
39 | def add_test(p): | |
40 | def test(self): | |
41 | # instantiate parameter with no default (but supply | |
42 | # any required args) | |
43 | p(*positional_args.get(p,tuple())) | |
44 | return test | |
45 | ||
46 | for p_name, p_type in concrete_descendents(Parameter).items(): | |
47 | dict_["test_default_of_%s"%p_name] = add_test(p_type) if p_name not in skip else test_skip | |
48 | ||
49 | return type.__new__(mcs, name, bases, dict_) | |
50 | ||
51 | ||
52 | @add_metaclass(TestDefaultsMetaclass) | |
53 | class TestDefaults(unittest.TestCase): | |
54 | pass | |
55 | ||
56 | ||
57 | if __name__ == "__main__": | |
58 | import nose | |
59 | nose.runmodule() |
0 | """ | |
1 | Unit test for dynamic parameters. | |
2 | ||
3 | Tests __get__, __set__ and that inspect_value() and | |
4 | get_value_generator() work. | |
5 | ||
6 | Originally implemented as doctests in Topographica in the file | |
7 | testDynamicParameter.txt | |
8 | """ | |
9 | ||
10 | import copy | |
11 | import unittest | |
12 | import param | |
13 | import numbergen | |
14 | ||
15 | ||
16 | class TestDynamicParameters(unittest.TestCase): | |
17 | ||
18 | def setUp(self): | |
19 | param.Dynamic.time_dependent = False | |
20 | ||
21 | class TestPO1(param.Parameterized): | |
22 | x = param.Dynamic(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),doc="nothing") | |
23 | y = param.Dynamic(default=1) | |
24 | ||
25 | class TestPO2(param.Parameterized): | |
26 | x = param.Dynamic(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=30)) | |
27 | y = param.Dynamic(default=1.0) | |
28 | ||
29 | self.TestPO2 = TestPO2 | |
30 | self.TestPO1 = TestPO1 | |
31 | ||
32 | self.t1 = self.TestPO1() | |
33 | self.t2 = self.TestPO1(x=numbergen.UniformRandom(lbound=-1,ubound=1,seed=10)) | |
34 | self.t3 = self.TestPO1(x=numbergen.UniformRandom(lbound=-1,ubound=1,seed=10)) | |
35 | self.t2.set_dynamic_time_fn(None) | |
36 | self.t3.set_dynamic_time_fn(None) | |
37 | ||
38 | self.t6 = self.TestPO2() | |
39 | self.t7 = self.TestPO2() | |
40 | ||
41 | ||
42 | class TestDynamicParameterBasics(TestDynamicParameters): | |
43 | ||
44 | def test_set_dynamic_time_fn_x(self): | |
45 | self.t1.set_dynamic_time_fn(None) | |
46 | self.assertEqual( | |
47 | self.t1.params()['x']._value_is_dynamic(self.t1), True) | |
48 | ||
49 | def test_set_dynamic_time_fn_y(self): | |
50 | self.assertEqual( | |
51 | self.t1.params()['y']._value_is_dynamic(self.t1), False) | |
52 | ||
53 | def test_inspect_x(self): | |
54 | "no value generated yet" | |
55 | self.assertEqual(self.t1.inspect_value('x'), None) | |
56 | ||
57 | def test_inspect_y(self): | |
58 | self.assertEqual(self.t1.inspect_value('y'), 1) | |
59 | ||
60 | def test_inspect_y_set(self): | |
61 | self.t1.y = 2 | |
62 | self.assertEqual(self.t1.inspect_value('y'), 2) | |
63 | ||
64 | def test_set_dynamic_numbergen(self): | |
65 | is_numbergen = isinstance(self.t2.get_value_generator('x'), | |
66 | numbergen.UniformRandom) | |
67 | self.assertEqual(is_numbergen, True) | |
68 | ||
69 | def test_matching_numbergen_streams(self): | |
70 | "check that t2 and t3 have identical streams" | |
71 | self.assertEqual(self.t2.x, self.t3.x) | |
72 | ||
73 | def test_numbergen_objects_distinct(self): | |
74 | "check t2 and t3 do not share UniformRandom objects" | |
75 | self.t2.x | |
76 | self.assertNotEqual(self.t2.inspect_value('x'), | |
77 | self.t3.inspect_value('x')) | |
78 | ||
79 | def test_numbergen_inspect(self): | |
80 | " inspect_value() should return last generated value " | |
81 | self.t2.x # Call 1 | |
82 | self.t2.x # Call 2 | |
83 | t2_last_value = self.t2.x # advance t2 beyond t3 | |
84 | ||
85 | self.assertEqual(self.t2.inspect_value('x'), | |
86 | t2_last_value) | |
87 | # ensure last_value is not shared | |
88 | self.assertNotEqual(self.t3.inspect_value('x'), t2_last_value) | |
89 | ||
90 | def test_dynamic_value_instantiated(self): | |
91 | t6_first_value = self.t6.x | |
92 | self.assertNotEqual(self.t7.inspect_value('x'), | |
93 | t6_first_value) | |
94 | ||
95 | def test_non_dynamic_value_not_instantiated(self): | |
96 | " non-dynamic value not instantiated" | |
97 | self.TestPO2.y = 4 | |
98 | self.assertEqual(self.t6.y, 4) | |
99 | self.assertEqual(self.t7.y, 4) | |
100 | ||
101 | def test_dynamic_value_setting(self): | |
102 | self.t6.y = numbergen.UniformRandom() | |
103 | t8 = self.TestPO2() | |
104 | self.TestPO2.y = 10 | |
105 | # t6 got a dynamic value, but shouldn't have changed Parameter's instantiate | |
106 | self.assertEqual(t8.y, 10) | |
107 | ||
108 | def test_setting_y_param_numbergen(self): | |
109 | self.TestPO2.y=numbergen.UniformRandom() # now the Parameter instantiate should be true | |
110 | t9 = self.TestPO2() | |
111 | self.assertEqual('_y_param_value' in t9.__dict__, True) | |
112 | ||
113 | def test_shared_numbergen(self): | |
114 | """ | |
115 | Instances of TestPO2 that don't have their own value for the | |
116 | parameter share one UniformRandom object | |
117 | """ | |
118 | self.TestPO2.y=numbergen.UniformRandom() # now the Parameter instantiate should be true | |
119 | self.assertEqual(self.t7.get_value_generator('y') is self.TestPO2().params()['y'].default, True) | |
120 | self.assertEqual(self.TestPO2().params()['y'].default.__class__.__name__, 'UniformRandom') | |
121 | ||
122 | def test_copy_match(self): | |
123 | "check a copy is the same" | |
124 | t9 = copy.deepcopy(self.t7) | |
125 | self.assertEqual(t9.get_value_generator('y') is self.TestPO2().params()['y'].default, True) | |
126 | ||
127 | ||
128 | ||
129 | class TestDynamicTimeDependent(TestDynamicParameters): | |
130 | ||
131 | def setUp(self): | |
132 | super(TestDynamicTimeDependent, self).setUp() | |
133 | param.Dynamic.time_dependent = True | |
134 | ||
135 | class TestPO3(param.Parameterized): | |
136 | x = param.Dynamic(default=numbergen.UniformRandom(name='xgen', | |
137 | time_dependent=True)) | |
138 | ||
139 | class TestPO4(self.TestPO1): | |
140 | "Nested parameterized objects" | |
141 | z = param.Parameter(default=self.TestPO1()) | |
142 | ||
143 | self.TestPO3 = TestPO3 | |
144 | self.TestPO4 = TestPO4 | |
145 | ||
146 | self.t10 = self.TestPO1() | |
147 | self.t11 = TestPO3() | |
148 | ||
149 | def test_dynamic_values_unchanged_dependent(self): | |
150 | param.Dynamic.time_dependent = True | |
151 | call_1 = self.t10.x | |
152 | call_2 = self.t10.x | |
153 | call_3 = self.t10.x | |
154 | self.assertEqual(call_1, call_2) | |
155 | self.assertEqual(call_2, call_3) | |
156 | ||
157 | def test_dynamic_values_changed_independent(self): | |
158 | param.Dynamic.time_dependent = False | |
159 | call_1 = self.t10.x | |
160 | call_2 = self.t10.x | |
161 | call_3 = self.t10.x | |
162 | self.assertNotEqual(call_1, call_2) | |
163 | self.assertNotEqual(call_2, call_3) | |
164 | ||
165 | def test_dynamic_values_change(self): | |
166 | param.Dynamic.time_dependent = True | |
167 | with param.Dynamic.time_fn as t: | |
168 | t(0) | |
169 | call_1 = self.t10.x | |
170 | t += 1 | |
171 | call_2 = self.t10.x | |
172 | t(0) | |
173 | call_3 = self.t10.x | |
174 | self.assertNotEqual(call_1, call_2) | |
175 | self.assertNotEqual(call_1, call_3) | |
176 | ||
177 | def test_dynamic_values_time_dependent(self): | |
178 | param.Dynamic.time_dependent = True | |
179 | with param.Dynamic.time_fn as t: | |
180 | t(0) | |
181 | call_1 = self.t11.x | |
182 | t += 1 | |
183 | call_2 = self.t11.x | |
184 | t(0) | |
185 | call_3 = self.t11.x | |
186 | self.assertNotEqual(call_1, call_2) | |
187 | self.assertEqual(call_1, call_3) | |
188 | ||
189 | def test_class_dynamic_values_change(self): | |
190 | call_1 = self.TestPO3.x | |
191 | call_2 = self.TestPO3.x | |
192 | self.assertEqual(call_1, call_2) | |
193 | with param.Dynamic.time_fn as t: | |
194 | t += 1 | |
195 | call_3 = self.TestPO3.x | |
196 | self.assertNotEqual(call_2, call_3) | |
197 | ||
198 | def test_dynamic_value_change_independent(self): | |
199 | t12 = self.TestPO1() | |
200 | t12.set_dynamic_time_fn(None) | |
201 | self.assertNotEqual(t12.x, t12.x) | |
202 | self.assertEqual(t12.y, t12.y) | |
203 | ||
204 | def test_dynamic_value_change_disabled(self): | |
205 | " time_fn set on the UniformRandom() when t13.y was set" | |
206 | t13 = self.TestPO1() | |
207 | t13.set_dynamic_time_fn(None) | |
208 | t13.y = numbergen.UniformRandom() | |
209 | self.assertNotEqual(t13.y, t13.y) | |
210 | ||
211 | def test_dynamic_value_change_enabled(self): | |
212 | " time_fn set on the UniformRandom() when t13.y was set" | |
213 | t14 = self.TestPO1() | |
214 | t14.y = numbergen.UniformRandom() | |
215 | self.assertEqual(t14.y, t14.y) | |
216 | ||
217 | ||
218 | def test_dynamic_time_fn_not_inherited(self): | |
219 | " time_fn not inherited" | |
220 | t15 = self.TestPO4() | |
221 | t15.set_dynamic_time_fn(None) | |
222 | with param.Dynamic.time_fn as t: | |
223 | call_1 = t15.z.x | |
224 | t += 1 | |
225 | call_2 = t15.z.x | |
226 | self.assertNotEqual(call_1, call_2) | |
227 | ||
228 | ||
229 | ||
230 | class TestDynamicSharedNumbergen(TestDynamicParameters): | |
231 | "Check shared generator" | |
232 | def setUp(self): | |
233 | super(TestDynamicSharedNumbergen, self).setUp() | |
234 | self.shared = numbergen.UniformRandom(lbound=-1,ubound=1,seed=20) | |
235 | ||
236 | def test_dynamic_shared_numbergen(self): | |
237 | param.Dynamic.time_dependent = True | |
238 | t11 = self.TestPO1(x=self.shared) | |
239 | t12 = self.TestPO1(x=self.shared) | |
240 | ||
241 | with param.Dynamic.time_fn as t: | |
242 | t += 1 | |
243 | call_1 = t11.x | |
244 | self.assertEqual(call_1, t12.x) | |
245 | t += 1 | |
246 | self.assertNotEqual(call_1, t12.x) | |
247 | ||
248 | ||
249 | ||
250 | if __name__ == "__main__": | |
251 | import nose | |
252 | nose.runmodule() | |
253 | ||
254 | ||
255 | # Commented out block in the original doctest version. | |
256 | # Maybe these are features originally planned but never implemented | |
257 | ||
258 | """ | |
259 | It is not yet possible to set time_fn for a Parameter instance | |
260 | >>> class TestPO5(param.Parameterized): | |
261 | ... x = param.Dynamic(default=numbergen.UniformRandom(),dynamic_time_fn=None) | |
262 | """ | |
263 | ||
264 | """ | |
265 | We currently don't support iterators/generators in Dynamic unless | |
266 | they're wrapped. | |
267 | ||
268 | >>> i = iter([1,2,3]) | |
269 | >>> t11.x = i | |
270 | ||
271 | >>> topo.sim.run(1) | |
272 | ||
273 | >>> t11.x | |
274 | 1 | |
275 | ||
276 | >>> def gen(): | |
277 | ... yield 2 | |
278 | ... yield 4 | |
279 | ... yield 6 | |
280 | ||
281 | >>> g = gen() | |
282 | ||
283 | >>> t11.x = g | |
284 | ||
285 | >>> t11.x | |
286 | 2 | |
287 | ||
288 | >>> topo.sim.run(1) | |
289 | ||
290 | >>> t11.x | |
291 | 4 | |
292 | """ |
0 | """ | |
1 | Unit test for the IPython magic | |
2 | """ | |
3 | ||
4 | import re | |
5 | import sys | |
6 | import unittest | |
7 | import param | |
8 | ||
9 | ||
10 | try: | |
11 | import IPython # noqa | |
12 | except ImportError: | |
13 | import os | |
14 | if os.getenv('PARAM_TEST_IPYTHON','0') == '1': | |
15 | raise ImportError("PARAM_TEST_IPYTHON=1 but ipython not available.") | |
16 | ||
17 | # TODO: is the below actually true? | |
18 | ||
19 | # SkipTest will be raised if IPython unavailable | |
20 | from param.ipython import ParamPager | |
21 | ||
22 | test1_repr = """\x1b[1;32mParameters of 'TestClass'\n=========================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
23 | ||
24 | ||
25 | test2_repr = """\x1b[1;32mParameters of 'TestClass' instance\n==================================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
26 | ||
27 | class TestParamPager(unittest.TestCase): | |
28 | ||
29 | def setUp(self): | |
30 | self.maxDiff = None | |
31 | class TestClass(param.Parameterized): | |
32 | u = param.Number(4) | |
33 | v = param.Number(4, constant=True) | |
34 | w = param.Number(4, readonly=True) | |
35 | x = param.String(None, allow_None=True) | |
36 | y = param.Number(4, bounds=(-1, None)) | |
37 | z = param.Number(4, bounds=(-1, 100), softbounds=(-100, -200)) | |
38 | ||
39 | self.TestClass = TestClass | |
40 | self.pager = ParamPager() | |
41 | ||
42 | def test_parameterized_class(self): | |
43 | page_string = self.pager(self.TestClass) | |
44 | # Remove params automatic numbered names | |
45 | page_string = re.sub('TestClass(\d+)', 'TestClass', page_string) | |
46 | ref_string = re.sub('TestClass(\d+)', 'TestClass', test1_repr) | |
47 | ||
48 | try: | |
49 | self.assertEqual(page_string, ref_string) | |
50 | except Exception as e: | |
51 | sys.stderr.write(page_string) # Coloured output | |
52 | sys.stderr.write("\nRAW STRING:\n\n%r\n\n" % page_string) | |
53 | raise e | |
54 | ||
55 | def test_parameterized_instance(self): | |
56 | page_string = self.pager(self.TestClass()) | |
57 | # Remove params automatic numbered names | |
58 | page_string = re.sub('TestClass(\d+)', 'TestClass', page_string) | |
59 | ref_string = re.sub('TestClass(\d+)', 'TestClass', test2_repr) | |
60 | ||
61 | try: | |
62 | self.assertEqual(page_string, ref_string) | |
63 | except Exception as e: | |
64 | sys.stderr.write(page_string) # Coloured output | |
65 | sys.stderr.write("\nRAW STRING:\n\n%r\n\n" % page_string) | |
66 | raise e |
0 | import unittest | |
1 | import param | |
2 | ||
3 | # TODO: I copied the tests from testobjectselector, although I | |
4 | # struggled to understand some of them. Both files should be reviewed | |
5 | # and cleaned up together. | |
6 | ||
7 | # TODO: tests copied from testobjectselector could use assertRaises | |
8 | # context manager (and could be updated in testobjectselector too). | |
9 | ||
10 | class TestListSelectorParameters(unittest.TestCase): | |
11 | ||
12 | def setUp(self): | |
13 | ||
14 | class P(param.Parameterized): | |
15 | e = param.ListSelector(default=[5],objects=[5,6,7]) | |
16 | f = param.ListSelector(default=10) | |
17 | h = param.ListSelector(default=None) | |
18 | g = param.ListSelector(default=None,objects=[7,8]) | |
19 | i = param.ListSelector(default=[7],objects=[9],check_on_set=False) | |
20 | ||
21 | self.P = P | |
22 | ||
23 | def test_default_None(self): | |
24 | class Q(param.Parameterized): | |
25 | r = param.ListSelector(default=None) | |
26 | ||
27 | def test_set_object_constructor(self): | |
28 | p = self.P(e=[6]) | |
29 | self.assertEqual(p.e, [6]) | |
30 | ||
31 | def test_set_object_outside_bounds(self): | |
32 | p = self.P(e=[6]) | |
33 | try: | |
34 | p.e = [9] | |
35 | except ValueError: | |
36 | pass | |
37 | else: | |
38 | raise AssertionError("Object set outside range.") | |
39 | ||
40 | def test_set_object_setattr(self): | |
41 | p = self.P(e=[6]) | |
42 | p.f = [9] | |
43 | self.assertEqual(p.f, [9]) | |
44 | p.g = [7] | |
45 | self.assertEqual(p.g, [7]) | |
46 | p.i = [12] | |
47 | self.assertEqual(p.i, [12]) | |
48 | ||
49 | ||
50 | def test_set_object_not_None(self): | |
51 | p = self.P(e=[6]) | |
52 | p.g = [7] | |
53 | try: | |
54 | p.g = None | |
55 | except TypeError: | |
56 | pass | |
57 | else: | |
58 | raise AssertionError("Object set outside range.") | |
59 | ||
60 | def test_set_one_object_not_None(self): | |
61 | p = self.P(e=[6]) | |
62 | p.g = [7] | |
63 | try: | |
64 | p.g = [None] | |
65 | except ValueError: | |
66 | pass | |
67 | else: | |
68 | raise AssertionError("Object set outside range.") | |
69 | ||
70 | ||
71 | def test_set_object_setattr_post_error(self): | |
72 | p = self.P(e=[6]) | |
73 | p.f = [9] | |
74 | self.assertEqual(p.f, [9]) | |
75 | p.g = [7] | |
76 | try: | |
77 | p.g = [None] | |
78 | except ValueError: | |
79 | pass | |
80 | else: | |
81 | raise AssertionError("Object set outside range.") | |
82 | ||
83 | self.assertEqual(p.g, [7]) | |
84 | p.i = [12] | |
85 | self.assertEqual(p.i, [12]) | |
86 | ||
87 | def test_initialization_out_of_bounds(self): | |
88 | try: | |
89 | class Q(param.Parameterized): | |
90 | q = param.ListSelector([5],objects=[4]) | |
91 | except ValueError: | |
92 | pass | |
93 | else: | |
94 | raise AssertionError("ListSelector created outside range.") | |
95 | ||
96 | ||
97 | def test_initialization_no_bounds(self): | |
98 | try: | |
99 | class Q(param.Parameterized): | |
100 | q = param.ListSelector([5],objects=10) | |
101 | except TypeError: | |
102 | pass | |
103 | else: | |
104 | raise AssertionError("ListSelector created without range.") | |
105 | ||
106 | ||
107 | ################################################################## | |
108 | ################################################################## | |
109 | ### new tests (not copied from testobjectselector) | |
110 | ||
111 | def test_bad_default(self): | |
112 | with self.assertRaises(TypeError): | |
113 | class Q(param.Parameterized): | |
114 | r = param.ListSelector(default=6,check_on_set=True) | |
115 | ||
116 | def test_implied_check_on_set(self): | |
117 | with self.assertRaises(TypeError): | |
118 | class Q(param.Parameterized): | |
119 | r = param.ListSelector(default=7,objects=[7,8]) | |
120 | ||
121 | def test_default_not_checked(self): | |
122 | class Q(param.Parameterized): | |
123 | r = param.ListSelector(default=[6]) | |
124 | ||
125 | ########################## | |
126 | # CEBALERT: not sure it makes sense for ListSelector to be set to | |
127 | # a non-iterable value (except None). I.e. I think this first test | |
128 | # should fail. | |
129 | def test_default_not_checked_to_be_iterable(self): | |
130 | class Q(param.Parameterized): | |
131 | r = param.ListSelector(default=6) | |
132 | ||
133 | def test_set_checked_to_be_iterable(self): | |
134 | class Q(param.Parameterized): | |
135 | r = param.ListSelector(default=6,check_on_set=False) | |
136 | ||
137 | with self.assertRaises(TypeError): | |
138 | Q.r = 6 | |
139 | ########################## | |
140 | ||
141 | ||
142 | def test_compute_default(self): | |
143 | class Q(param.Parameterized): | |
144 | r = param.ListSelector(default=None, compute_default_fn=lambda: [1,2,3]) | |
145 | ||
146 | self.assertEqual(Q.r, None) | |
147 | Q.params('r').compute_default() | |
148 | self.assertEqual(Q.r, [1,2,3]) | |
149 | self.assertEqual(Q.params('r').objects, [1,2,3]) | |
150 | ||
151 | def test_bad_compute_default(self): | |
152 | class Q(param.Parameterized): | |
153 | r = param.ListSelector(default=None,compute_default_fn=lambda:1) | |
154 | ||
155 | with self.assertRaises(TypeError): | |
156 | Q.params('r').compute_default() | |
157 | ||
158 | if __name__ == "__main__": | |
159 | import nose | |
160 | nose.runmodule() |
0 | """ | |
1 | Test cases for the numbergen module. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import numbergen | |
6 | ||
7 | ||
8 | _seed = 0 # keep tests deterministic | |
9 | _iterations = 1000 | |
10 | ||
11 | ||
12 | class TestUniformRandom(unittest.TestCase): | |
13 | def test_range(self): | |
14 | lbound = 2.0 | |
15 | ubound = 5.0 | |
16 | gen = numbergen.UniformRandom( | |
17 | seed=_seed, | |
18 | lbound=lbound, | |
19 | ubound=ubound) | |
20 | for _ in range(_iterations): | |
21 | value = gen() | |
22 | self.assertTrue(lbound <= value < ubound) | |
23 | ||
24 | class TestUniformRandomOffset(unittest.TestCase): | |
25 | def test_range(self): | |
26 | lbound = 2.0 | |
27 | ubound = 5.0 | |
28 | gen = numbergen.UniformRandomOffset( | |
29 | seed=_seed, | |
30 | mean=(ubound + lbound) / 2, | |
31 | range=ubound - lbound) | |
32 | for _ in range(_iterations): | |
33 | value = gen() | |
34 | self.assertTrue(lbound <= value < ubound) | |
35 | ||
36 | if __name__ == "__main__": | |
37 | import nose | |
38 | nose.runmodule() |
0 | """ | |
1 | If numpy's present, is numpy stuff ok? | |
2 | """ | |
3 | ||
4 | import os | |
5 | import unittest | |
6 | ||
7 | import param | |
8 | ||
9 | ||
10 | try: | |
11 | import numpy | |
12 | import numpy.testing | |
13 | except ImportError: | |
14 | if os.getenv('PARAM_TEST_NUMPY','0') == '1': | |
15 | raise ImportError("PARAM_TEST_NUMPY=1 but numpy not available.") | |
16 | else: | |
17 | raise unittest.SkipTest("numpy not available") | |
18 | ||
19 | ||
20 | def _is_array_and_equal(test,ref): | |
21 | if not type(test) == numpy.ndarray: | |
22 | raise AssertionError | |
23 | numpy.testing.assert_array_equal(test,ref) | |
24 | ||
25 | # TODO: incomplete | |
26 | class TestNumpy(unittest.TestCase): | |
27 | def test_array_param(self): | |
28 | class Z(param.Parameterized): | |
29 | z = param.Array(default=numpy.array([1])) | |
30 | ||
31 | _is_array_and_equal(Z.z,[1]) | |
32 | ||
33 | z = Z(z=numpy.array([1,2])) | |
34 | _is_array_and_equal(z.z,[1,2]) | |
35 | ||
36 | ||
37 | if __name__ == "__main__": | |
38 | import nose | |
39 | nose.runmodule() |
0 | """ | |
1 | Unit test for object selector parameters. | |
2 | ||
3 | Originally implemented as doctests in Topographica in the file | |
4 | testEnumerationParameter.txt | |
5 | """ | |
6 | ||
7 | import unittest | |
8 | import param | |
9 | ||
10 | ||
11 | opts=dict(A=[1,2],B=[3,4],C=dict(a=1,b=2)) | |
12 | ||
13 | ||
14 | class TestObjectSelectorParameters(unittest.TestCase): | |
15 | ||
16 | def setUp(self): | |
17 | ||
18 | class P(param.Parameterized): | |
19 | e = param.ObjectSelector(default=5,objects=[5,6,7]) | |
20 | f = param.ObjectSelector(default=10) | |
21 | h = param.ObjectSelector(default=None) | |
22 | g = param.ObjectSelector(default=None,objects=[7,8]) | |
23 | i = param.ObjectSelector(default=7,objects=[9],check_on_set=False) | |
24 | d = param.ObjectSelector(default=opts['B'],objects=opts) | |
25 | ||
26 | self.P = P | |
27 | ||
28 | def test_set_object_constructor(self): | |
29 | p = self.P(e=6) | |
30 | self.assertEqual(p.e, 6) | |
31 | ||
32 | def test_get_range_mutable(self): | |
33 | r = self.P.param.params("d").get_range() | |
34 | self.assertEqual(r['A'],opts['A']) | |
35 | self.assertEqual(r['C'],opts['C']) | |
36 | self.d=opts['A'] | |
37 | self.d=opts['C'] | |
38 | self.d=opts['B'] | |
39 | ||
40 | def test_set_object_outside_bounds(self): | |
41 | p = self.P(e=6) | |
42 | try: | |
43 | p.e = 9 | |
44 | except ValueError: | |
45 | pass | |
46 | else: | |
47 | raise AssertionError("Object set outside range.") | |
48 | ||
49 | def test_set_object_setattr(self): | |
50 | p = self.P(e=6) | |
51 | p.f = 9 | |
52 | self.assertEqual(p.f, 9) | |
53 | p.g = 7 | |
54 | self.assertEqual(p.g, 7) | |
55 | p.i = 12 | |
56 | self.assertEqual(p.i, 12) | |
57 | ||
58 | ||
59 | def test_set_object_not_None(self): | |
60 | p = self.P(e=6) | |
61 | p.g = 7 | |
62 | try: | |
63 | p.g = None | |
64 | except ValueError: | |
65 | pass | |
66 | else: | |
67 | raise AssertionError("Object set outside range.") | |
68 | ||
69 | def test_set_object_setattr_post_error(self): | |
70 | p = self.P(e=6) | |
71 | p.f = 9 | |
72 | self.assertEqual(p.f, 9) | |
73 | p.g = 7 | |
74 | try: | |
75 | p.g = None | |
76 | except ValueError: | |
77 | pass | |
78 | else: | |
79 | raise AssertionError("Object set outside range.") | |
80 | ||
81 | self.assertEqual(p.g, 7) | |
82 | p.i = 12 | |
83 | self.assertEqual(p.i, 12) | |
84 | ||
85 | def test_initialization_out_of_bounds(self): | |
86 | try: | |
87 | class Q(param.Parameterized): | |
88 | q = param.ObjectSelector(5,objects=[4]) | |
89 | except ValueError: | |
90 | pass | |
91 | else: | |
92 | raise AssertionError("ObjectSelector created outside range.") | |
93 | ||
94 | ||
95 | def test_initialization_no_bounds(self): | |
96 | try: | |
97 | class Q(param.Parameterized): | |
98 | q = param.ObjectSelector(5,objects=10) | |
99 | except TypeError: | |
100 | pass | |
101 | else: | |
102 | raise AssertionError("ObjectSelector created without range.") | |
103 | ||
104 | ||
105 | if __name__ == "__main__": | |
106 | import nose | |
107 | nose.runmodule() |
0 | """ | |
1 | Unit test for Parameterized. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import param | |
6 | import numbergen | |
7 | ||
8 | # CEBALERT: not anything like a complete test of Parameterized! | |
9 | ||
10 | ||
11 | import random | |
12 | from nose.tools import istest, nottest | |
13 | ||
14 | ||
15 | from param.parameterized import ParamOverrides, shared_parameters | |
16 | ||
17 | @nottest | |
18 | class _SomeRandomNumbers(object): | |
19 | def __call__(self): | |
20 | return random.random() | |
21 | ||
22 | @nottest | |
23 | class TestPO(param.Parameterized): | |
24 | inst = param.Parameter(default=[1,2,3],instantiate=True) | |
25 | notinst = param.Parameter(default=[1,2,3],instantiate=False) | |
26 | const = param.Parameter(default=1,constant=True) | |
27 | ro = param.Parameter(default="Hello",readonly=True) | |
28 | ro2 = param.Parameter(default=object(),readonly=True,instantiate=True) | |
29 | ||
30 | dyn = param.Dynamic(default=1) | |
31 | ||
32 | @nottest | |
33 | class AnotherTestPO(param.Parameterized): | |
34 | instPO = param.Parameter(default=TestPO(),instantiate=True) | |
35 | notinstPO = param.Parameter(default=TestPO(),instantiate=False) | |
36 | ||
37 | @nottest | |
38 | class TestAbstractPO(param.Parameterized): | |
39 | __abstract = True | |
40 | ||
41 | @nottest | |
42 | class TestParamInstantiation(AnotherTestPO): | |
43 | instPO = param.Parameter(default=AnotherTestPO(),instantiate=False) | |
44 | ||
45 | @istest | |
46 | class TestParameterized(unittest.TestCase): | |
47 | ||
48 | def test_constant_parameter(self): | |
49 | """Test that you can't set a constant parameter after construction.""" | |
50 | testpo = TestPO(const=17) | |
51 | self.assertEqual(testpo.const,17) | |
52 | self.assertRaises(TypeError,setattr,testpo,'const',10) | |
53 | ||
54 | # check you can set on class | |
55 | TestPO.const=9 | |
56 | testpo = TestPO() | |
57 | self.assertEqual(testpo.const,9) | |
58 | ||
59 | def test_readonly_parameter(self): | |
60 | """Test that you can't set a read-only parameter on construction or as an attribute.""" | |
61 | testpo = TestPO() | |
62 | self.assertEqual(testpo.ro,"Hello") | |
63 | ||
64 | with self.assertRaises(TypeError): | |
65 | t = TestPO(ro=20) | |
66 | ||
67 | t=TestPO() | |
68 | self.assertRaises(TypeError,setattr,t,'ro',10) | |
69 | ||
70 | # check you cannot set on class | |
71 | self.assertRaises(TypeError,setattr,TestPO,'ro',5) | |
72 | ||
73 | self.assertEqual(testpo.params()['ro'].constant,True) | |
74 | ||
75 | # check that instantiate was ignored for readonly | |
76 | self.assertEqual(testpo.params()['ro2'].instantiate,False) | |
77 | ||
78 | ||
79 | ||
80 | def test_basic_instantiation(self): | |
81 | """Check that instantiated parameters are copied into objects.""" | |
82 | ||
83 | testpo = TestPO() | |
84 | ||
85 | self.assertEqual(testpo.inst,TestPO.inst) | |
86 | self.assertEqual(testpo.notinst,TestPO.notinst) | |
87 | ||
88 | TestPO.inst[1]=7 | |
89 | TestPO.notinst[1]=7 | |
90 | ||
91 | self.assertEqual(testpo.notinst,[1,7,3]) | |
92 | self.assertEqual(testpo.inst,[1,2,3]) | |
93 | ||
94 | ||
95 | def test_more_instantiation(self): | |
96 | """Show that objects in instantiated Parameters can still share data.""" | |
97 | anothertestpo = AnotherTestPO() | |
98 | ||
99 | ### CB: AnotherTestPO.instPO is instantiated, but | |
100 | ### TestPO.notinst is not instantiated - so notinst is still | |
101 | ### shared, even by instantiated parameters of AnotherTestPO. | |
102 | ### Seems like this behavior of Parameterized could be | |
103 | ### confusing, so maybe mention it in documentation somewhere. | |
104 | TestPO.notinst[1]=7 | |
105 | # (if you thought your instPO was completely an independent object, you | |
106 | # might be expecting [1,2,3] here) | |
107 | self.assertEqual(anothertestpo.instPO.notinst,[1,7,3]) | |
108 | ||
109 | ||
110 | def test_instantiation_inheritance(self): | |
111 | """Check that instantiate=True is always inherited (SF.net #2483932).""" | |
112 | t = TestParamInstantiation() | |
113 | assert t.params('instPO').instantiate is True | |
114 | assert isinstance(t.instPO,AnotherTestPO) | |
115 | ||
116 | ||
117 | def test_abstract_class(self): | |
118 | """Check that a class declared abstract actually shows up as abstract.""" | |
119 | self.assertEqual(TestAbstractPO.abstract,True) | |
120 | self.assertEqual(TestPO.abstract,False) | |
121 | ||
122 | ||
123 | def test_params(self): | |
124 | """Basic tests of params() method.""" | |
125 | ||
126 | ||
127 | # CB: test not so good because it requires changes if params | |
128 | # of PO are changed | |
129 | assert 'name' in param.Parameterized.params() | |
130 | assert len(param.Parameterized.params()) in [1,2] | |
131 | ||
132 | ## check for bug where subclass Parameters were not showing up | |
133 | ## if params() already called on a super class. | |
134 | assert 'inst' in TestPO.params() | |
135 | assert 'notinst' in TestPO.params() | |
136 | ||
137 | ## check caching | |
138 | assert param.Parameterized.params() is param.Parameterized().params(), "Results of params() should be cached." # just for performance reasons | |
139 | ||
140 | ||
141 | def test_state_saving(self): | |
142 | t = TestPO(dyn=_SomeRandomNumbers()) | |
143 | g = t.get_value_generator('dyn') | |
144 | g._Dynamic_time_fn=None | |
145 | assert t.dyn!=t.dyn | |
146 | orig = t.dyn | |
147 | t.state_push() | |
148 | t.dyn | |
149 | assert t.inspect_value('dyn')!=orig | |
150 | t.state_pop() | |
151 | assert t.inspect_value('dyn')==orig | |
152 | ||
153 | ||
154 | ||
155 | from param import parameterized | |
156 | ||
157 | @nottest | |
158 | class some_fn(param.ParameterizedFunction): | |
159 | num_phase = param.Number(18) | |
160 | frequencies = param.List([99]) | |
161 | scale = param.Number(0.3) | |
162 | ||
163 | def __call__(self,**params_to_override): | |
164 | params = parameterized.ParamOverrides(self,params_to_override) | |
165 | num_phase = params['num_phase'] | |
166 | frequencies = params['frequencies'] | |
167 | scale = params['scale'] | |
168 | return scale,num_phase,frequencies | |
169 | ||
170 | instance = some_fn.instance() | |
171 | ||
172 | @istest | |
173 | class TestParameterizedFunction(unittest.TestCase): | |
174 | ||
175 | def _basic_tests(self,fn): | |
176 | self.assertEqual(fn(),(0.3,18,[99])) | |
177 | self.assertEqual(fn(frequencies=[1,2,3]),(0.3,18,[1,2,3])) | |
178 | self.assertEqual(fn(),(0.3,18,[99])) | |
179 | ||
180 | fn.frequencies=[10,20,30] | |
181 | self.assertEqual(fn(frequencies=[1,2,3]),(0.3,18,[1,2,3])) | |
182 | self.assertEqual(fn(),(0.3,18,[10,20,30])) | |
183 | ||
184 | def test_parameterized_function(self): | |
185 | self._basic_tests(some_fn) | |
186 | ||
187 | def test_parameterized_function_instance(self): | |
188 | self._basic_tests(instance) | |
189 | ||
190 | def test_pickle_instance(self): | |
191 | import pickle | |
192 | s = pickle.dumps(instance) | |
193 | instance.scale=0.8 | |
194 | i = pickle.loads(s) | |
195 | self.assertEqual(i(),(0.3,18,[10,20,30])) | |
196 | ||
197 | ||
198 | @nottest | |
199 | class TestPO1(param.Parameterized): | |
200 | x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1)) | |
201 | y = param.Number(default=1,bounds=(-1,1)) | |
202 | ||
203 | @istest | |
204 | class TestNumberParameter(unittest.TestCase): | |
205 | ||
206 | def test_outside_bounds(self): | |
207 | t1 = TestPO1() | |
208 | # Test bounds (non-dynamic number) | |
209 | try: | |
210 | t1.y = 10 | |
211 | except ValueError: | |
212 | pass | |
213 | else: | |
214 | assert False, "Should raise ValueError." | |
215 | ||
216 | def test_outside_bounds_numbergen(self): | |
217 | t1 = TestPO1() | |
218 | # Test bounds (dynamic number) | |
219 | t1.x = numbergen.UniformRandom(lbound=2,ubound=3) # bounds not checked on set | |
220 | try: | |
221 | t1.x | |
222 | except ValueError: | |
223 | pass | |
224 | else: | |
225 | assert False, "Should raise ValueError." | |
226 | ||
227 | ||
228 | @istest | |
229 | class TestStringParameter(unittest.TestCase): | |
230 | ||
231 | def setUp(self): | |
232 | super(TestStringParameter, self).setUp() | |
233 | ||
234 | class TestString(param.Parameterized): | |
235 | a = param.String() | |
236 | b = param.String(default='',allow_None=True) | |
237 | c = param.String(default=None) | |
238 | ||
239 | self._TestString = TestString | |
240 | ||
241 | def test_handling_of_None(self): | |
242 | t = self._TestString() | |
243 | ||
244 | with self.assertRaises(ValueError): | |
245 | t.a = None | |
246 | ||
247 | t.b = None | |
248 | ||
249 | assert t.c is None | |
250 | ||
251 | ||
252 | ||
253 | @istest | |
254 | class TestParamOverrides(unittest.TestCase): | |
255 | ||
256 | def setUp(self): | |
257 | super(TestParamOverrides, self).setUp() | |
258 | self.po = param.Parameterized(name='A',print_level=0) | |
259 | ||
260 | def test_init_name(self): | |
261 | self.assertEqual(self.po.name, 'A') | |
262 | ||
263 | def test_simple_override(self): | |
264 | overrides = ParamOverrides(self.po,{'name':'B'}) | |
265 | self.assertEqual(overrides['name'], 'B') | |
266 | self.assertEqual(overrides['print_level'], 0) | |
267 | ||
268 | # CEBALERT: missing test for allow_extra_keywords (e.g. getting a | |
269 | # warning on attempting to override non-existent parameter when | |
270 | # allow_extra_keywords is False) | |
271 | ||
272 | def test_missing_key(self): | |
273 | overrides = ParamOverrides(self.po,{'name':'B'}) | |
274 | with self.assertRaises(AttributeError): | |
275 | overrides['doesnotexist'] | |
276 | ||
277 | ||
278 | class TestSharedParameters(unittest.TestCase): | |
279 | ||
280 | def setUp(self): | |
281 | with shared_parameters(): | |
282 | self.p1 = TestPO(name='A', print_level=0) | |
283 | self.p2 = TestPO(name='B', print_level=0) | |
284 | self.ap1 = AnotherTestPO(name='A', print_level=0) | |
285 | self.ap2 = AnotherTestPO(name='B', print_level=0) | |
286 | ||
287 | def test_shared_object(self): | |
288 | self.assertTrue(self.ap1.instPO is self.ap2.instPO) | |
289 | self.assertTrue(self.ap1.params('instPO').default is not self.ap2.instPO) | |
290 | ||
291 | def test_shared_list(self): | |
292 | self.assertTrue(self.p1.inst is self.p2.inst) | |
293 | self.assertTrue(self.p1.params('inst').default is not self.p2.inst) | |
294 | ||
295 | ||
296 | ||
297 | if __name__ == "__main__": | |
298 | import nose | |
299 | nose.runmodule() |
0 | """ | |
1 | Unit test for the repr and pprint of parameterized objects. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import param | |
6 | ||
7 | ||
8 | ||
9 | class TestParameterizedRepr(unittest.TestCase): | |
10 | ||
11 | def setUp(self): | |
12 | # initialize a parameterized class | |
13 | class A(param.Parameterized): | |
14 | a = param.Number(4, precedence=-5) | |
15 | b = param.String('B', precedence=-4) | |
16 | c = param.Number(4, precedence=0) | |
17 | d = param.Integer(-22, precedence=1) | |
18 | ||
19 | x = param.Number(1, precedence=2) | |
20 | y = param.Number(2, precedence=-1) | |
21 | z = param.Number(3, precedence=-2) | |
22 | def __init__(self, a, b, c=4, d=-22, **kwargs): | |
23 | super(A, self).__init__(a=a, b=b, c=c, **kwargs) | |
24 | ||
25 | self.A = A | |
26 | ||
27 | class B(param.Parameterized): # Similar to A but no **kwargs | |
28 | a = param.Number(4, precedence=-5) | |
29 | b = param.String('B', precedence=-4) | |
30 | c = param.Number(4, precedence=0) | |
31 | d = param.Integer(-22, precedence=1) | |
32 | ||
33 | x = param.Number(1, precedence=2) | |
34 | def __init__(self, a, b, c=4, d=-22): | |
35 | super(B, self).__init__(a=a, b=b, c=c, name='ClassB') | |
36 | ||
37 | self.B = B | |
38 | ||
39 | class C(param.Parameterized): # Similar to A but with *varargs | |
40 | a = param.Number(4, precedence=-5) | |
41 | b = param.String('B', precedence=-4) | |
42 | c = param.Number(4, precedence=0) | |
43 | d = param.Integer(-22, precedence=1) | |
44 | ||
45 | x = param.Number(1, precedence=2) | |
46 | y = param.Number(2, precedence=-1) | |
47 | z = param.Number(3, precedence=-2) | |
48 | ||
49 | def __init__(self, a, b, c=4, d=-22, *varargs, **kwargs): | |
50 | super(C, self).__init__(a=a, b=b, c=c, **kwargs) | |
51 | ||
52 | self.C = C | |
53 | ||
54 | ||
55 | class D(param.Parameterized): # Similar to A but with missing parameters | |
56 | a = param.Number(4, precedence=-5) | |
57 | b = param.String('B', precedence=-4) | |
58 | ||
59 | def __init__(self, a, b, c=4, d=-22, **kwargs): | |
60 | super(D, self).__init__(a=a, b=b, **kwargs) | |
61 | ||
62 | self.D = D | |
63 | ||
64 | ||
65 | # More realistically, positional args are not params | |
66 | class E(param.Parameterized): | |
67 | a = param.Number(4, precedence=-5) | |
68 | ||
69 | def __init__(self, p, q=4, **params): # (plus non-param kw too) | |
70 | super(E, self).__init__(**params) | |
71 | ||
72 | self.E = E | |
73 | ||
74 | ||
75 | def testparameterizedrepr(self): | |
76 | obj = self.A(4,'B', name='test1') | |
77 | self.assertEqual(repr(obj), | |
78 | "A(a=4, b='B', c=4, d=-22, name='test1', x=1, y=2, z=3)") | |
79 | ||
80 | def testparameterizedscriptrepr1(self): | |
81 | obj = self.A(4,'B', name='test') | |
82 | self.assertEqual(obj.pprint(), | |
83 | "A(4, 'B', name='test')") | |
84 | ||
85 | def testparameterizedscriptrepr2(self): | |
86 | obj = self.A(4,'B', c=5, name='test') | |
87 | self.assertEqual(obj.pprint(), | |
88 | "A(4, 'B', c=5, name='test')") | |
89 | ||
90 | def testparameterizedscriptrepr3(self): | |
91 | obj = self.A(4,'B', c=5, x=True, name='test') | |
92 | self.assertEqual(obj.pprint(), | |
93 | "A(4, 'B', c=5, name='test')") | |
94 | ||
95 | def testparameterizedscriptrepr4(self): | |
96 | obj = self.A(4,'B', c=5, x=10, name='test') | |
97 | self.assertEqual(obj.pprint(), | |
98 | "A(4, 'B', c=5, name='test', x=10)") | |
99 | ||
100 | ||
101 | def testparameterizedscriptrepr5(self): | |
102 | obj = self.A(4,'B', x=10, y=11, z=12, name='test') | |
103 | self.assertEqual(obj.pprint(), | |
104 | "A(4, 'B', name='test', z=12, y=11, x=10)") | |
105 | ||
106 | def testparameterizedscriptrepr_nokwargs(self): | |
107 | obj = self.B(4,'B', c=99) | |
108 | obj.x = 10 # Modified but not passable through constructor | |
109 | self.assertEqual(obj.pprint(), | |
110 | "B(4, 'B', c=99)") | |
111 | ||
112 | def testparameterizedscriptrepr_varags(self): | |
113 | obj = self.C(4,'C', c=99) | |
114 | self.assertEqual(obj.pprint(), | |
115 | "C(4, 'C', c=99, **varargs)") | |
116 | ||
117 | def testparameterizedscriptrepr_varags_kwargs(self): | |
118 | obj = self.C(4,'C', c=99, x=10, y=11, z=12) | |
119 | self.assertEqual(obj.pprint(), | |
120 | "C(4, 'C', c=99, z=12, y=11, x=10, **varargs)") | |
121 | ||
122 | def testparameterizedscriptrepr_missing_values(self): | |
123 | obj = self.D(4,'D', c=99) | |
124 | self.assertEqual(obj.pprint(), | |
125 | "D(4, 'D', c=<?>, d=<?>)") | |
126 | ||
127 | def testparameterizedscriptrepr_nonparams(self): | |
128 | obj = self.E(10,q='hi', a=99) | |
129 | self.assertEqual(obj.pprint(), | |
130 | "E(<?>, q=<?>, a=99)") | |
131 | ||
132 | def test_exceptions(self): | |
133 | obj = self.E(10,q='hi',a=99) | |
134 | try: | |
135 | obj.pprint(unknown_value=False) | |
136 | except Exception: | |
137 | pass | |
138 | else: | |
139 | raise AssertionError | |
140 | ||
141 | def test_suppression(self): | |
142 | obj = self.E(10,q='hi',a=99) | |
143 | self.assertEqual(obj.pprint(unknown_value=None), | |
144 | "E(a=99)") | |
145 | ||
146 | def test_imports_deduplication(self): | |
147 | obj = self.E(10,q='hi', a=99) | |
148 | imports = ['import me','import me'] | |
149 | obj.pprint(imports=imports) | |
150 | self.assertEqual(imports.count('import me'),1) | |
151 | ||
152 | def test_qualify(self): | |
153 | obj = self.E(10,q='hi', a=99) | |
154 | ||
155 | r = "E(<?>, q=<?>, a=99)" | |
156 | self.assertEqual(obj.pprint(qualify=False), | |
157 | r) | |
158 | ||
159 | self.assertEqual(obj.pprint(qualify=True), | |
160 | "tests.API0.testparameterizedrepr."+r) | |
161 | ||
162 | ||
163 | ||
164 | if __name__ == "__main__": | |
165 | import nose | |
166 | nose.runmodule() |
0 | """ | |
1 | Unit test for Range parameters. | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | import param | |
6 | ||
7 | ||
8 | class TestRangeParameters(unittest.TestCase): | |
9 | ||
10 | def test_initialization_out_of_bounds(self): | |
11 | try: | |
12 | class Q(param.Parameterized): | |
13 | q = param.Range((0, 2), bounds=(0, 1)) | |
14 | except ValueError: | |
15 | pass | |
16 | else: | |
17 | raise AssertionError("No exception raised on out-of-bounds date") | |
18 | ||
19 | def test_set_exclusive_out_of_bounds_upper(self): | |
20 | class Q(param.Parameterized): | |
21 | q = param.Range(bounds=(0, 10), inclusive_bounds=(True, False)) | |
22 | try: | |
23 | Q.q = (0, 10) | |
24 | except ValueError: | |
25 | pass | |
26 | else: | |
27 | raise AssertionError("No exception raised on out-of-bounds date") | |
28 | ||
29 | def test_set_exclusive_out_of_bounds_lower(self): | |
30 | class Q(param.Parameterized): | |
31 | q = param.Range(bounds=(0, 10), inclusive_bounds=(False, True)) | |
32 | try: | |
33 | Q.q = (0, 10) | |
34 | except ValueError: | |
35 | pass | |
36 | else: | |
37 | raise AssertionError("No exception raised on out-of-bounds date") | |
38 | ||
39 | def test_set_out_of_bounds(self): | |
40 | class Q(param.Parameterized): | |
41 | q = param.Range(bounds=(0, 10)) | |
42 | try: | |
43 | Q.q = (5, 11) | |
44 | except ValueError: | |
45 | pass | |
46 | else: | |
47 | raise AssertionError("No exception raised on out-of-bounds date") | |
48 | ||
49 | def test_get_soft_bounds(self): | |
50 | q = param.Range((1,3), bounds=(0, 10), softbounds=(1, 9)) | |
51 | self.assertEqual(q.get_soft_bounds(), (1, 9)) | |
52 |
0 | """ | |
1 | Unit test for String parameters | |
2 | """ | |
3 | ||
4 | import unittest | |
5 | ||
6 | import param | |
7 | ||
8 | ||
9 | ip_regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' | |
10 | ||
11 | class TestStringParameters(unittest.TestCase): | |
12 | ||
13 | def test_regex_ok(self): | |
14 | class A(param.Parameterized): | |
15 | s = param.String('0.0.0.0', ip_regex) | |
16 | ||
17 | a = A() | |
18 | a.s = '123.123.0.1' | |
19 | ||
20 | def test_reject_none(self): | |
21 | class A(param.Parameterized): | |
22 | s = param.String('0.0.0.0', ip_regex) | |
23 | ||
24 | a = A() | |
25 | ||
26 | exception = "String 's' only takes a string value." | |
27 | with self.assertRaisesRegexp(ValueError, exception): | |
28 | a.s = None # because allow_None should be False | |
29 | ||
30 | def test_default_none(self): | |
31 | class A(param.Parameterized): | |
32 | s = param.String(None, ip_regex) | |
33 | ||
34 | a = A() | |
35 | a.s = '123.123.0.1' | |
36 | a.s = None # because allow_None should be True with default of None | |
37 | ||
38 | def test_regex_incorrect(self): | |
39 | ||
40 | class A(param.Parameterized): | |
41 | s = param.String('0.0.0.0', regex=ip_regex) | |
42 | ||
43 | a = A() | |
44 | ||
45 | exception = "String 's': '123.123.0.256' does not match regex" | |
46 | with self.assertRaisesRegexp(ValueError, exception): | |
47 | a.s = '123.123.0.256' | |
48 | ||
49 | def test_regex_incorrect_default(self): | |
50 | ||
51 | exception = "String 'None': '' does not match regex" | |
52 | with self.assertRaisesRegexp(ValueError, exception): | |
53 | class A(param.Parameterized): | |
54 | s = param.String(regex=ip_regex) # default value '' does not match regular expression | |
55 | ||
56 |
0 | """ | |
1 | Unit tests for the param.Time class, time dependent parameters and | |
2 | time-dependent numbergenerators. | |
3 | """ | |
4 | import unittest | |
5 | import param | |
6 | import numbergen | |
7 | import copy | |
8 | ||
9 | from nose.plugins.skip import SkipTest | |
10 | import fractions | |
11 | ||
12 | try: | |
13 | import gmpy | |
14 | except: | |
15 | gmpy = None | |
16 | ||
17 | ||
18 | ||
19 | class TestTimeClass(unittest.TestCase): | |
20 | ||
21 | def test_time_init(self): | |
22 | param.Time() | |
23 | ||
24 | def test_time_init_int(self): | |
25 | t = param.Time(time_type=int) | |
26 | self.assertEqual(t(), 0) | |
27 | ||
28 | def test_time_int_iter(self): | |
29 | t = param.Time(time_type=int) | |
30 | self.assertEqual(next(t), 0) | |
31 | self.assertEqual(next(t), 1) | |
32 | ||
33 | def test_time_init_timestep(self): | |
34 | t = param.Time(time_type=int, timestep=2) | |
35 | self.assertEqual(next(t), 0) | |
36 | self.assertEqual(next(t), 2) | |
37 | ||
38 | def test_time_int_until(self): | |
39 | t = param.Time(time_type=int, until=3) | |
40 | self.assertEqual(next(t), 0) | |
41 | self.assertEqual(next(t), 1) | |
42 | self.assertEqual(next(t), 2) | |
43 | self.assertEqual(next(t), 3) | |
44 | try: | |
45 | self.assertEqual(next(t), 4) | |
46 | raise AssertionError("StopIteration should have been raised") | |
47 | except StopIteration: | |
48 | pass | |
49 | ||
50 | def test_time_int_eq(self): | |
51 | t = param.Time(time_type=int) | |
52 | s = param.Time(time_type=int) | |
53 | t(3); s(3) | |
54 | self.assertEqual(t == s, True) | |
55 | ||
56 | def test_time_int_context(self): | |
57 | t = param.Time(time_type=int) | |
58 | t(3) | |
59 | with t: | |
60 | self.assertEqual(t(), 3) | |
61 | t(5) | |
62 | self.assertEqual(t(), 5) | |
63 | self.assertEqual(t(), 3) | |
64 | ||
65 | def test_time_int_context_iadd(self): | |
66 | ||
67 | with param.Time(time_type=int) as t: | |
68 | self.assertEqual(t(), 0) | |
69 | t += 5 | |
70 | self.assertEqual(t(), 5) | |
71 | self.assertEqual(t(), 0) | |
72 | ||
73 | def test_time_int_change_type(self): | |
74 | t = param.Time(time_type=int) | |
75 | self.assertEqual(t(), 0) | |
76 | t(1, fractions.Fraction) | |
77 | self.assertEqual(t(), 1) | |
78 | self.assertEqual(t.time_type, fractions.Fraction) | |
79 | ||
80 | def test_time_init_gmpy(self): | |
81 | if gmpy is None: raise SkipTest | |
82 | ||
83 | t = param.Time(time_type=gmpy.mpq) | |
84 | self.assertEqual(t(), gmpy.mpq(0)) | |
85 | t.advance(gmpy.mpq(0.25)) | |
86 | self.assertEqual(t(), gmpy.mpq(1,4)) | |
87 | ||
88 | def test_time_init_gmpy_advanced(self): | |
89 | if gmpy is None: raise SkipTest | |
90 | t = param.Time(time_type=gmpy.mpq, | |
91 | timestep=gmpy.mpq(0.25), | |
92 | until=1.5) | |
93 | self.assertEqual(t(), gmpy.mpq(0,1)) | |
94 | t(0.5) | |
95 | self.assertEqual(t(), gmpy.mpq(1,2)) | |
96 | with t: | |
97 | t.advance(0.25) | |
98 | self.assertEqual(t(), gmpy.mpq(3,4)) | |
99 | self.assertEqual(t(), gmpy.mpq(1,2)) | |
100 | tvals = [tval for tval in t] | |
101 | self.assertEqual(tvals, [gmpy.mpq(1,2), | |
102 | gmpy.mpq(3,4), | |
103 | gmpy.mpq(1,1), | |
104 | gmpy.mpq(5,4), | |
105 | gmpy.mpq(3,2)]) | |
106 | ||
107 | ||
108 | class TestTimeDependentDynamic(unittest.TestCase): | |
109 | ||
110 | def setUp(self): | |
111 | param.Dynamic.time_dependent=None | |
112 | self.time_fn= param.Time(time_type=int) | |
113 | ||
114 | class Incrementer(object): | |
115 | def __init__(self): | |
116 | self.i = -1 | |
117 | def __call__(self): | |
118 | self.i+=1 | |
119 | return self.i | |
120 | ||
121 | self.Incrementer = Incrementer | |
122 | ||
123 | class DynamicClass(param.Parameterized): | |
124 | a = param.Number(default = self.Incrementer()) | |
125 | ||
126 | self.DynamicClass = DynamicClass | |
127 | self._start_state = copy.copy([param.Dynamic.time_dependent, | |
128 | numbergen.TimeAware.time_dependent, | |
129 | param.Dynamic.time_fn, | |
130 | numbergen.TimeAware.time_fn, | |
131 | param.random_seed]) | |
132 | ||
133 | def tearDown(self): | |
134 | param.Dynamic.time_dependent = self._start_state[0] | |
135 | numbergen.TimeAware.time_dependent = self._start_state[1] | |
136 | param.Dynamic.time_fn = self._start_state[2] | |
137 | numbergen.TimeAware.time_fn = self._start_state[3] | |
138 | param.random_seed = self._start_state[4] | |
139 | ||
140 | ||
141 | def test_non_time_dependent(self): | |
142 | """ | |
143 | With param.Dynamic.time_dependent=None every call should | |
144 | increment. | |
145 | """ | |
146 | param.Dynamic.time_dependent=None | |
147 | param.Dynamic.time_fn = self.time_fn | |
148 | ||
149 | dynamic = self.DynamicClass() | |
150 | self.assertEqual(dynamic.a, 0) | |
151 | self.assertEqual(dynamic.a, 1) | |
152 | self.assertEqual(dynamic.a, 2) | |
153 | ||
154 | def test_time_fixed(self): | |
155 | """ | |
156 | With param.Dynamic.time_dependent=True the value should only | |
157 | increment when the time value changes. | |
158 | """ | |
159 | param.Dynamic.time_dependent=True | |
160 | param.Dynamic.time_fn = self.time_fn | |
161 | ||
162 | dynamic = self.DynamicClass() | |
163 | self.assertEqual(dynamic.a, 0) | |
164 | self.assertEqual(dynamic.a, 0) | |
165 | ||
166 | self.time_fn += 1 | |
167 | self.assertEqual(dynamic.a, 1) | |
168 | self.assertEqual(dynamic.a, 1) | |
169 | param.Dynamic.time_fn -= 5 | |
170 | self.assertEqual(dynamic.a, 2) | |
171 | self.assertEqual(dynamic.a, 2) | |
172 | ||
173 | ||
174 | def test_time_dependent(self): | |
175 | """ | |
176 | With param.Dynamic.time_dependent=True and param.Dynamic and | |
177 | numbergen.TimeDependent sharing a common time_fn, the value | |
178 | should be a function of time. | |
179 | """ | |
180 | param.Dynamic.time_dependent=True | |
181 | param.Dynamic.time_fn = self.time_fn | |
182 | numbergen.TimeDependent.time_fn = self.time_fn | |
183 | ||
184 | class DynamicClass(param.Parameterized): | |
185 | b = param.Number(default = numbergen.ScaledTime(factor=2)) | |
186 | ||
187 | dynamic = DynamicClass() | |
188 | self.time_fn(0) | |
189 | self.assertEqual(dynamic.b, 0.0) | |
190 | self.time_fn += 5 | |
191 | self.assertEqual(dynamic.b, 10.0) | |
192 | self.assertEqual(dynamic.b, 10.0) | |
193 | self.time_fn -= 2 | |
194 | self.assertEqual(dynamic.b, 6.0) | |
195 | self.assertEqual(dynamic.b, 6.0) | |
196 | self.time_fn -= 3 | |
197 | self.assertEqual(dynamic.b, 0.0) | |
198 | ||
199 | ||
200 | def test_time_dependent_random(self): | |
201 | """ | |
202 | When set to time_dependent=True, random number generators | |
203 | should also be a function of time. | |
204 | """ | |
205 | param.Dynamic.time_dependent=True | |
206 | numbergen.TimeAware.time_dependent=True | |
207 | param.Dynamic.time_fn = self.time_fn | |
208 | numbergen.TimeAware.time_fn = self.time_fn | |
209 | param.random_seed = 42 | |
210 | ||
211 | class DynamicClass(param.Parameterized): | |
212 | c = param.Number(default = numbergen.UniformRandom(name = 'test1')) | |
213 | d = param.Number(default = numbergen.UniformRandom(name = 'test2')) | |
214 | e = param.Number(default = numbergen.UniformRandom(name = 'test1')) | |
215 | ||
216 | dynamic = DynamicClass() | |
217 | ||
218 | test1_t1 = 0.23589388250988552 | |
219 | test2_t1 = 0.12576257837158122 | |
220 | test1_t2 = 0.14117586161849593 | |
221 | test2_t2 = 0.9134917395930359 | |
222 | ||
223 | self.time_fn(0) | |
224 | self.assertEqual(dynamic.c, test1_t1) | |
225 | self.assertEqual(dynamic.c, dynamic.e) | |
226 | self.assertNotEqual(dynamic.c, dynamic.d) | |
227 | self.assertEqual(dynamic.d, test2_t1) | |
228 | self.time_fn(1) | |
229 | self.assertEqual(dynamic.c, test1_t2) | |
230 | self.assertEqual(dynamic.c, test1_t2) | |
231 | self.assertEqual(dynamic.d, test2_t2) | |
232 | self.time_fn(0) | |
233 | self.assertEqual(dynamic.c, test1_t1) | |
234 | self.assertEqual(dynamic.d, test2_t1) | |
235 | ||
236 | ||
237 | def test_time_hashing_integers(self): | |
238 | """ | |
239 | Check that ints, fractions and strings hash to the same value | |
240 | for integer values. | |
241 | """ | |
242 | hashfn = numbergen.Hash("test", input_count=1) | |
243 | hash_1 = hashfn(1) | |
244 | hash_42 = hashfn(42) | |
245 | hash_200001 = hashfn(200001) | |
246 | ||
247 | self.assertEqual(hash_1, hashfn(fractions.Fraction(1))) | |
248 | self.assertEqual(hash_1, hashfn("1")) | |
249 | ||
250 | self.assertEqual(hash_42, hashfn(fractions.Fraction(42))) | |
251 | self.assertEqual(hash_42, hashfn("42")) | |
252 | ||
253 | self.assertEqual(hash_200001, hashfn(fractions.Fraction(200001))) | |
254 | self.assertEqual(hash_200001, hashfn("200001")) | |
255 | ||
256 | ||
257 | def test_time_hashing_rationals(self): | |
258 | """ | |
259 | Check that hashes fractions and strings match for some | |
260 | reasonable rational numbers. | |
261 | """ | |
262 | hashfn = numbergen.Hash("test", input_count=1) | |
263 | pi = "3.141592" | |
264 | half = fractions.Fraction(0.5) | |
265 | self.assertEqual(hashfn(0.5), hashfn(half)) | |
266 | self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi))) | |
267 | ||
268 | ||
269 | def test_time_hashing_integers_gmpy(self): | |
270 | """ | |
271 | Check that hashes for gmpy values at the integers also matches | |
272 | those of ints, fractions and strings. | |
273 | """ | |
274 | if gmpy is None: raise SkipTest | |
275 | hashfn = numbergen.Hash("test", input_count=1) | |
276 | hash_1 = hashfn(1) | |
277 | hash_42 = hashfn(42) | |
278 | ||
279 | self.assertEqual(hash_1, hashfn(gmpy.mpq(1))) | |
280 | self.assertEqual(hash_1, hashfn(1)) | |
281 | ||
282 | self.assertEqual(hash_42, hashfn(gmpy.mpq(42))) | |
283 | self.assertEqual(hash_42, hashfn(42)) | |
284 | ||
285 | def test_time_hashing_rationals_gmpy(self): | |
286 | """ | |
287 | Check that hashes of fractions and gmpy mpqs match for some | |
288 | reasonable rational numbers. | |
289 | """ | |
290 | if gmpy is None: raise SkipTest | |
291 | pi = "3.141592" | |
292 | hashfn = numbergen.Hash("test", input_count=1) | |
293 | self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5))) | |
294 | self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592))) | |
295 | ||
296 | ||
297 | ||
298 | ||
299 | if __name__ == "__main__": | |
300 | import nose | |
301 | nose.runmodule() |
0 | import param | |
1 | import unittest | |
2 | ||
3 | class API1TestCase(unittest.TestCase): | |
4 | ||
5 | def setUp(self): | |
6 | param.parameterized.Parameters._disable_stubs = True | |
7 | ||
8 | def tearDown(self): | |
9 | param.parameterized.Parameters._disable_stubs = False |
0 | """ | |
1 | Unit test for CalendarDate parameters. | |
2 | """ | |
3 | ||
4 | ||
5 | import datetime as dt | |
6 | import param | |
7 | from . import API1TestCase | |
8 | ||
9 | ||
10 | class TestDateTimeParameters(API1TestCase): | |
11 | ||
12 | def test_initialization_out_of_bounds(self): | |
13 | try: | |
14 | class Q(param.Parameterized): | |
15 | q = param.CalendarDate(dt.date(2017,2,27), | |
16 | bounds=(dt.date(2017,2,1), | |
17 | dt.date(2017,2,26))) | |
18 | except ValueError: | |
19 | pass | |
20 | else: | |
21 | raise AssertionError("No exception raised on out-of-bounds date") | |
22 | ||
23 | def test_set_out_of_bounds(self): | |
24 | class Q(param.Parameterized): | |
25 | q = param.CalendarDate(bounds=(dt.date(2017,2,1), | |
26 | dt.date(2017,2,26))) | |
27 | try: | |
28 | Q.q = dt.date(2017,2,27) | |
29 | except ValueError: | |
30 | pass | |
31 | else: | |
32 | raise AssertionError("No exception raised on out-of-bounds date") | |
33 | ||
34 | def test_set_exclusive_out_of_bounds(self): | |
35 | class Q(param.Parameterized): | |
36 | q = param.CalendarDate(bounds=(dt.date(2017,2,1), | |
37 | dt.date(2017,2,26)), | |
38 | inclusive_bounds=(True, False)) | |
39 | try: | |
40 | Q.q = dt.date(2017,2,26) | |
41 | except ValueError: | |
42 | pass | |
43 | else: | |
44 | raise AssertionError("No exception raised on out-of-bounds date") | |
45 | ||
46 | def test_get_soft_bounds(self): | |
47 | q = param.CalendarDate(dt.date(2017,2,25), | |
48 | bounds=(dt.date(2017,2,1), | |
49 | dt.date(2017,2,26)), | |
50 | softbounds=(dt.date(2017,2,1), | |
51 | dt.date(2017,2,25))) | |
52 | self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1), | |
53 | dt.date(2017,2,25))) |
0 | """ | |
1 | Unit tests for CalendarDateRange parameter. | |
2 | """ | |
3 | ||
4 | import datetime as dt | |
5 | import param | |
6 | from . import API1TestCase | |
7 | ||
8 | # Assuming tests of range parameter cover most of what's needed to | |
9 | # test date range. | |
10 | ||
11 | class TestDateTimeRange(API1TestCase): | |
12 | ||
13 | bad_range = (dt.date(2017,2,27),dt.date(2017,2,26)) | |
14 | ||
15 | def test_wrong_type_default(self): | |
16 | try: | |
17 | class Q(param.Parameterized): | |
18 | a = param.CalendarDateRange(default=(1.0,2.0)) | |
19 | except ValueError: | |
20 | pass | |
21 | else: | |
22 | raise AssertionError("Bad date type was accepted.") | |
23 | ||
24 | def test_wrong_type_init(self): | |
25 | class Q(param.Parameterized): | |
26 | a = param.CalendarDateRange() | |
27 | ||
28 | try: | |
29 | Q(a=self.bad_range) | |
30 | except ValueError: | |
31 | pass | |
32 | else: | |
33 | raise AssertionError("Bad date type was accepted.") | |
34 | ||
35 | def test_wrong_type_set(self): | |
36 | class Q(param.Parameterized): | |
37 | a = param.CalendarDateRange() | |
38 | q = Q() | |
39 | ||
40 | try: | |
41 | q.a = self.bad_range | |
42 | except ValueError: | |
43 | pass | |
44 | else: | |
45 | raise AssertionError("Bad date type was accepted.") | |
46 | ||
47 | def test_start_before_end_default(self): | |
48 | try: | |
49 | class Q(param.Parameterized): | |
50 | a = param.CalendarDateRange(default=self.bad_range) | |
51 | except ValueError: | |
52 | pass | |
53 | else: | |
54 | raise AssertionError("Bad date range was accepted.") | |
55 | ||
56 | def test_start_before_end_init(self): | |
57 | class Q(param.Parameterized): | |
58 | a = param.CalendarDateRange() | |
59 | ||
60 | try: | |
61 | Q(a=self.bad_range) | |
62 | except ValueError: | |
63 | pass | |
64 | else: | |
65 | raise AssertionError("Bad date range was accepted.") | |
66 | ||
67 | def test_start_before_end_set(self): | |
68 | class Q(param.Parameterized): | |
69 | a = param.CalendarDateRange() | |
70 | ||
71 | q = Q() | |
72 | try: | |
73 | q.a = self.bad_range | |
74 | except ValueError: | |
75 | pass | |
76 | else: | |
77 | raise AssertionError("Bad date range was accepted.") |
0 | """ | |
1 | Unit test for ClassSelector parameters. | |
2 | """ | |
3 | ||
4 | ||
5 | from numbers import Number | |
6 | ||
7 | import param | |
8 | from . import API1TestCase | |
9 | ||
10 | ||
11 | class TestClassSelectorParameters(API1TestCase): | |
12 | ||
13 | def setUp(self): | |
14 | super(TestClassSelectorParameters, self).setUp() | |
15 | class P(param.Parameterized): | |
16 | e = param.ClassSelector(default=1,class_=int) | |
17 | f = param.ClassSelector(default=int,class_=Number, is_instance=False) | |
18 | g = param.ClassSelector(default=1,class_=(int,str)) | |
19 | h = param.ClassSelector(default=int,class_=(int,str), is_instance=False) | |
20 | ||
21 | self.P = P | |
22 | ||
23 | def test_single_class_instance_constructor(self): | |
24 | p = self.P(e=6) | |
25 | self.assertEqual(p.e, 6) | |
26 | ||
27 | def test_single_class_instance_error(self): | |
28 | exception = "Parameter 'e' value must be an instance of int, not 'a'" | |
29 | with self.assertRaisesRegexp(ValueError, exception): | |
30 | self.P(e='a') | |
31 | ||
32 | def test_single_class_type_constructor(self): | |
33 | p = self.P(f=float) | |
34 | self.assertEqual(p.f, float) | |
35 | ||
36 | def test_single_class_type_error(self): | |
37 | exception = "Parameter 'str' must be a subclass of Number, not 'type'" | |
38 | with self.assertRaisesRegexp(ValueError, exception): | |
39 | self.P(f=str) | |
40 | ||
41 | def test_multiple_class_instance_constructor1(self): | |
42 | p = self.P(g=1) | |
43 | self.assertEqual(p.g, 1) | |
44 | ||
45 | def test_multiple_class_instance_constructor2(self): | |
46 | p = self.P(g='A') | |
47 | self.assertEqual(p.g, 'A') | |
48 | ||
49 | def test_multiple_class_instance_error(self): | |
50 | exception = "Parameter 'g' value must be an instance of \(int, str\), not '3.0'" | |
51 | with self.assertRaisesRegexp(ValueError, exception): | |
52 | self.P(g=3.0) | |
53 | ||
54 | def test_multiple_class_type_constructor1(self): | |
55 | p = self.P(h=int) | |
56 | self.assertEqual(p.h, int) | |
57 | ||
58 | def test_multiple_class_type_constructor2(self): | |
59 | p = self.P(h=str) | |
60 | self.assertEqual(p.h, str) | |
61 | ||
62 | def test_class_selector_get_range(self): | |
63 | p = self.P() | |
64 | classes = p.param.g.get_range() | |
65 | self.assertIn('int', classes) | |
66 | self.assertIn('str', classes) | |
67 | ||
68 | def test_multiple_class_type_error(self): | |
69 | exception = "Parameter 'float' must be a subclass of \(int, str\), not 'type'" | |
70 | with self.assertRaisesRegexp(ValueError, exception): | |
71 | self.P(h=float) | |
72 | ||
73 | ||
74 | class TestDictParameters(API1TestCase): | |
75 | ||
76 | def test_valid_dict_parameter(self): | |
77 | valid_dict = {1:2, 3:3} | |
78 | ||
79 | class Test(param.Parameterized): | |
80 | items = param.Dict(default=valid_dict) | |
81 | ||
82 | def test_valid_dict_parameter_positional(self): | |
83 | valid_dict = {1:2, 3:3} | |
84 | ||
85 | class Test(param.Parameterized): | |
86 | items = param.Dict(valid_dict) | |
87 | ||
88 | def test_dict_invalid_set(self): | |
89 | valid_dict = {1:2, 3:3} | |
90 | class Test(param.Parameterized): | |
91 | items = param.Dict(valid_dict) | |
92 | ||
93 | test = Test() | |
94 | exception = "Parameter 'items' value must be an instance of dict, not '3'" | |
95 | with self.assertRaisesRegexp(ValueError, exception): | |
96 | test.items = 3 |
0 | """ | |
1 | Unit test for Color parameters. | |
2 | """ | |
3 | import param | |
4 | from . import API1TestCase | |
5 | ||
6 | class TestColorParameters(API1TestCase): | |
7 | ||
8 | def test_initialization_invalid_string(self): | |
9 | try: | |
10 | class Q(param.Parameterized): | |
11 | q = param.Color('red') | |
12 | except ValueError: | |
13 | pass | |
14 | else: | |
15 | raise AssertionError("No exception raised on out-of-bounds date") | |
16 | ||
17 | def test_set_invalid_string(self): | |
18 | class Q(param.Parameterized): | |
19 | q = param.Color() | |
20 | try: | |
21 | Q.q = 'red' | |
22 | except ValueError: | |
23 | pass | |
24 | else: | |
25 | raise AssertionError("No exception raised on out-of-bounds date") | |
26 | ||
27 | def test_valid_long_hex(self): | |
28 | class Q(param.Parameterized): | |
29 | q = param.Color() | |
30 | Q.q = '#ffffff' | |
31 | self.assertEqual(Q.q, '#ffffff') | |
32 | ||
33 | def test_valid_short_hex(self): | |
34 | class Q(param.Parameterized): | |
35 | q = param.Color() | |
36 | Q.q = '#fff' | |
37 | self.assertEqual(Q.q, '#fff') | |
38 |
0 | """ | |
1 | Unit test for composite parameters. | |
2 | ||
3 | Originally implemented as doctests in Topographica in the file | |
4 | testCompositeParameter.txt | |
5 | """ | |
6 | ||
7 | import param | |
8 | from . import API1TestCase | |
9 | ||
10 | class TestCompositeParameters(API1TestCase): | |
11 | ||
12 | def setUp(self): | |
13 | super(TestCompositeParameters, self).setUp() | |
14 | # initialize a class with a compound parameter | |
15 | class A(param.Parameterized): | |
16 | x = param.Number(default=0) | |
17 | y = param.Number(default=0) | |
18 | xy = param.Composite(attribs=['x','y']) | |
19 | ||
20 | self.A = A | |
21 | self.a = self.A() | |
22 | ||
23 | class SomeSequence(object): | |
24 | "Can't use iter with Dynamic (doesn't pickle, doesn't copy)" | |
25 | def __init__(self,sequence): | |
26 | self.sequence=sequence | |
27 | self.index=0 | |
28 | def __call__(self): | |
29 | val=self.sequence[self.index] | |
30 | self.index+=1 | |
31 | return val | |
32 | ||
33 | self.SomeSequence = SomeSequence | |
34 | ||
35 | def test_initialization(self): | |
36 | "Make an instance and do default checks" | |
37 | self.assertEqual(self.a.x, 0) | |
38 | self.assertEqual(self.a.y, 0) | |
39 | self.assertEqual(self.a.xy, [0,0]) | |
40 | ||
41 | ||
42 | def test_set_component(self): | |
43 | self.a.x = 1 | |
44 | self.assertEqual(self.a.xy, [1,0]) | |
45 | ||
46 | def test_set_compound(self): | |
47 | self.a.xy = (2,3) | |
48 | self.assertEqual(self.a.x, 2) | |
49 | self.assertEqual(self.a.y, 3) | |
50 | ||
51 | def test_compound_class(self): | |
52 | " Get the compound on the class " | |
53 | self.assertEqual(self.A.xy, [0,0]) | |
54 | ||
55 | def test_set_compound_class_set(self): | |
56 | self.A.xy = (5,6) | |
57 | self.assertEqual(self.A.x, 5) | |
58 | self.assertEqual(self.A.y, 6) | |
59 | ||
60 | def test_set_compound_class_instance(self): | |
61 | self.A.xy = (5,6) | |
62 | # # Make a new instance | |
63 | b = self.A() | |
64 | self.assertEqual(b.x, 5) | |
65 | self.assertEqual(b.y, 6) | |
66 | ||
67 | def test_set_compound_class_instance_unchanged(self): | |
68 | self.a.xy = (2,3) | |
69 | self.A.xy = (5,6) | |
70 | self.assertEqual(self.a.x, 2) | |
71 | self.assertEqual(self.a.y, 3) | |
72 | ||
73 | def test_composite_dynamic(self): | |
74 | """ | |
75 | Check CompositeParameter is ok with Dynamic | |
76 | CB: this test is really of Parameterized. | |
77 | """ | |
78 | a2 = self.A(x=self.SomeSequence([1,2,3]), | |
79 | y=self.SomeSequence([4,5,6])) | |
80 | ||
81 | a2.x, a2.y # Call of x and y params | |
82 | # inspect should not advance numbers | |
83 | self.assertEqual(a2.param.inspect_value('xy'), [1, 4]) | |
84 | ||
85 | def test_composite_dynamic_generator(self): | |
86 | ||
87 | a2 = self.A(x=self.SomeSequence([1,2,3]), | |
88 | y=self.SomeSequence([4,5,6])) | |
89 | ||
90 | a2.x, a2.y # Call of x and y params | |
91 | ix,iy = a2.param.get_value_generator('xy') | |
92 | # get_value_generator() should give the objects | |
93 | self.assertEqual(ix(), 2) | |
94 | self.assertEqual(iy(), 5) | |
95 | ||
96 | ||
97 | if __name__ == "__main__": | |
98 | import nose | |
99 | nose.runmodule() |
0 | """ | |
1 | Unit test for Date parameters. | |
2 | """ | |
3 | ||
4 | ||
5 | import datetime as dt | |
6 | import param | |
7 | from . import API1TestCase | |
8 | ||
9 | class TestDateParameters(API1TestCase): | |
10 | ||
11 | def test_initialization_out_of_bounds(self): | |
12 | try: | |
13 | class Q(param.Parameterized): | |
14 | q = param.Date(dt.datetime(2017,2,27), | |
15 | bounds=(dt.datetime(2017,2,1), | |
16 | dt.datetime(2017,2,26))) | |
17 | except ValueError: | |
18 | pass | |
19 | else: | |
20 | raise AssertionError("No exception raised on out-of-bounds date") | |
21 | ||
22 | def test_set_out_of_bounds(self): | |
23 | class Q(param.Parameterized): | |
24 | q = param.Date(bounds=(dt.datetime(2017,2,1), | |
25 | dt.datetime(2017,2,26))) | |
26 | try: | |
27 | Q.q = dt.datetime(2017,2,27) | |
28 | except ValueError: | |
29 | pass | |
30 | else: | |
31 | raise AssertionError("No exception raised on out-of-bounds date") | |
32 | ||
33 | def test_set_exclusive_out_of_bounds(self): | |
34 | class Q(param.Parameterized): | |
35 | q = param.Date(bounds=(dt.datetime(2017,2,1), | |
36 | dt.datetime(2017,2,26)), | |
37 | inclusive_bounds=(True, False)) | |
38 | try: | |
39 | Q.q = dt.datetime(2017,2,26) | |
40 | except ValueError: | |
41 | pass | |
42 | else: | |
43 | raise AssertionError("No exception raised on out-of-bounds date") | |
44 | ||
45 | def test_get_soft_bounds(self): | |
46 | q = param.Date(dt.datetime(2017,2,25), | |
47 | bounds=(dt.datetime(2017,2,1), | |
48 | dt.datetime(2017,2,26)), | |
49 | softbounds=(dt.datetime(2017,2,1), | |
50 | dt.datetime(2017,2,25))) | |
51 | self.assertEqual(q.get_soft_bounds(), (dt.datetime(2017,2,1), | |
52 | dt.datetime(2017,2,25))) | |
53 |
0 | """ | |
1 | Unit tests for DateRange parameter. | |
2 | """ | |
3 | ||
4 | import datetime as dt | |
5 | import param | |
6 | from . import API1TestCase | |
7 | ||
8 | # Assuming tests of range parameter cover most of what's needed to | |
9 | # test date range. | |
10 | ||
11 | class TestDateRange(API1TestCase): | |
12 | ||
13 | bad_range = (dt.datetime(2017,2,27),dt.datetime(2017,2,26)) | |
14 | ||
15 | def test_wrong_type_default(self): | |
16 | try: | |
17 | class Q(param.Parameterized): | |
18 | a = param.DateRange(default=(1.0,2.0)) | |
19 | except ValueError: | |
20 | pass | |
21 | else: | |
22 | raise AssertionError("Bad date type was accepted.") | |
23 | ||
24 | def test_wrong_type_init(self): | |
25 | class Q(param.Parameterized): | |
26 | a = param.DateRange() | |
27 | ||
28 | try: | |
29 | Q(a=self.bad_range) | |
30 | except ValueError: | |
31 | pass | |
32 | else: | |
33 | raise AssertionError("Bad date type was accepted.") | |
34 | ||
35 | def test_wrong_type_set(self): | |
36 | class Q(param.Parameterized): | |
37 | a = param.DateRange() | |
38 | q = Q() | |
39 | ||
40 | try: | |
41 | q.a = self.bad_range | |
42 | except ValueError: | |
43 | pass | |
44 | else: | |
45 | raise AssertionError("Bad date type was accepted.") | |
46 | ||
47 | def test_start_before_end_default(self): | |
48 | try: | |
49 | class Q(param.Parameterized): | |
50 | a = param.DateRange(default=self.bad_range) | |
51 | except ValueError: | |
52 | pass | |
53 | else: | |
54 | raise AssertionError("Bad date range was accepted.") | |
55 | ||
56 | def test_start_before_end_init(self): | |
57 | class Q(param.Parameterized): | |
58 | a = param.DateRange() | |
59 | ||
60 | try: | |
61 | Q(a=self.bad_range) | |
62 | except ValueError: | |
63 | pass | |
64 | else: | |
65 | raise AssertionError("Bad date range was accepted.") | |
66 | ||
67 | def test_start_before_end_set(self): | |
68 | class Q(param.Parameterized): | |
69 | a = param.DateRange() | |
70 | ||
71 | q = Q() | |
72 | try: | |
73 | q.a = self.bad_range | |
74 | except ValueError: | |
75 | pass | |
76 | else: | |
77 | raise AssertionError("Bad date range was accepted.") | |
78 | ||
79 |
0 | """ | |
1 | Do all subclasses of Parameter supply a valid default? | |
2 | """ | |
3 | ||
4 | from param.parameterized import add_metaclass | |
5 | from param import concrete_descendents, Parameter | |
6 | ||
7 | # import all parameter types | |
8 | from param import * # noqa | |
9 | from param import ClassSelector | |
10 | from . import API1TestCase | |
11 | ||
12 | positional_args = { | |
13 | ClassSelector: (object,) | |
14 | } | |
15 | ||
16 | skip = [] | |
17 | ||
18 | try: | |
19 | import numpy # noqa | |
20 | except ImportError: | |
21 | skip.append('Array') | |
22 | try: | |
23 | import pandas # noqa | |
24 | except ImportError: | |
25 | skip.append('DataFrame') | |
26 | skip.append('Series') | |
27 | ||
28 | ||
29 | class TestDefaultsMetaclass(type): | |
30 | def __new__(mcs, name, bases, dict_): | |
31 | ||
32 | def test_skip(*args,**kw): | |
33 | from nose.exc import SkipTest | |
34 | raise SkipTest | |
35 | ||
36 | def add_test(p): | |
37 | def test(self): | |
38 | # instantiate parameter with no default (but supply | |
39 | # any required args) | |
40 | p(*positional_args.get(p,tuple())) | |
41 | return test | |
42 | ||
43 | for p_name, p_type in concrete_descendents(Parameter).items(): | |
44 | dict_["test_default_of_%s"%p_name] = add_test(p_type) if p_name not in skip else test_skip | |
45 | ||
46 | return type.__new__(mcs, name, bases, dict_) | |
47 | ||
48 | ||
49 | @add_metaclass(TestDefaultsMetaclass) | |
50 | class TestDefaults(API1TestCase): | |
51 | pass | |
52 | ||
53 | ||
54 | if __name__ == "__main__": | |
55 | import nose | |
56 | nose.runmodule() |
0 | """ | |
1 | Unit test for dynamic parameters. | |
2 | ||
3 | Tests __get__, __set__ and that inspect_value() and | |
4 | get_value_generator() work. | |
5 | ||
6 | Originally implemented as doctests in Topographica in the file | |
7 | testDynamicParameter.txt | |
8 | """ | |
9 | ||
10 | import copy | |
11 | import param | |
12 | import numbergen | |
13 | from . import API1TestCase | |
14 | ||
15 | ||
16 | class TestDynamicParameters(API1TestCase): | |
17 | ||
18 | def setUp(self): | |
19 | super(TestDynamicParameters, self).setUp() | |
20 | param.Dynamic.time_dependent = False | |
21 | ||
22 | class TestPO1(param.Parameterized): | |
23 | x = param.Dynamic(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),doc="nothing") | |
24 | y = param.Dynamic(default=1) | |
25 | ||
26 | class TestPO2(param.Parameterized): | |
27 | x = param.Dynamic(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=30)) | |
28 | y = param.Dynamic(default=1.0) | |
29 | ||
30 | self.TestPO2 = TestPO2 | |
31 | self.TestPO1 = TestPO1 | |
32 | ||
33 | self.t1 = self.TestPO1() | |
34 | self.t2 = self.TestPO1(x=numbergen.UniformRandom(lbound=-1,ubound=1,seed=10)) | |
35 | self.t3 = self.TestPO1(x=numbergen.UniformRandom(lbound=-1,ubound=1,seed=10)) | |
36 | self.t2.param.set_dynamic_time_fn(None) | |
37 | self.t3.param.set_dynamic_time_fn(None) | |
38 | ||
39 | self.t6 = self.TestPO2() | |
40 | self.t7 = self.TestPO2() | |
41 | ||
42 | ||
43 | class TestDynamicParameterBasics(TestDynamicParameters): | |
44 | ||
45 | def test_set_dynamic_time_fn_x(self): | |
46 | self.t1.param.set_dynamic_time_fn(None) | |
47 | self.assertEqual( | |
48 | self.t1.param.params()['x']._value_is_dynamic(self.t1), True) | |
49 | ||
50 | def test_set_dynamic_time_fn_y(self): | |
51 | self.assertEqual( | |
52 | self.t1.param.params()['y']._value_is_dynamic(self.t1), False) | |
53 | ||
54 | def test_inspect_x(self): | |
55 | "no value generated yet" | |
56 | self.assertEqual(self.t1.param.inspect_value('x'), None) | |
57 | ||
58 | def test_inspect_y(self): | |
59 | self.assertEqual(self.t1.param.inspect_value('y'), 1) | |
60 | ||
61 | def test_inspect_y_set(self): | |
62 | self.t1.y = 2 | |
63 | self.assertEqual(self.t1.param.inspect_value('y'), 2) | |
64 | ||
65 | def test_set_dynamic_numbergen(self): | |
66 | is_numbergen = isinstance(self.t2.param.get_value_generator('x'), | |
67 | numbergen.UniformRandom) | |
68 | self.assertEqual(is_numbergen, True) | |
69 | ||
70 | def test_matching_numbergen_streams(self): | |
71 | "check that t2 and t3 have identical streams" | |
72 | self.assertEqual(self.t2.x, self.t3.x) | |
73 | ||
74 | def test_numbergen_objects_distinct(self): | |
75 | "check t2 and t3 do not share UniformRandom objects" | |
76 | self.t2.x | |
77 | self.assertNotEqual(self.t2.param.inspect_value('x'), | |
78 | self.t3.param.inspect_value('x')) | |
79 | ||
80 | def test_numbergen_inspect(self): | |
81 | " inspect_value() should return last generated value " | |
82 | self.t2.x # Call 1 | |
83 | self.t2.x # Call 2 | |
84 | t2_last_value = self.t2.x # advance t2 beyond t3 | |
85 | ||
86 | self.assertEqual(self.t2.param.inspect_value('x'), | |
87 | t2_last_value) | |
88 | # ensure last_value is not shared | |
89 | self.assertNotEqual(self.t3.param.inspect_value('x'), t2_last_value) | |
90 | ||
91 | def test_dynamic_value_instantiated(self): | |
92 | t6_first_value = self.t6.x | |
93 | self.assertNotEqual(self.t7.param.inspect_value('x'), | |
94 | t6_first_value) | |
95 | ||
96 | def test_non_dynamic_value_not_instantiated(self): | |
97 | " non-dynamic value not instantiated" | |
98 | self.TestPO2.y = 4 | |
99 | self.assertEqual(self.t6.y, 4) | |
100 | self.assertEqual(self.t7.y, 4) | |
101 | ||
102 | def test_dynamic_value_setting(self): | |
103 | self.t6.y = numbergen.UniformRandom() | |
104 | t8 = self.TestPO2() | |
105 | self.TestPO2.y = 10 | |
106 | # t6 got a dynamic value, but shouldn't have changed Parameter's instantiate | |
107 | self.assertEqual(t8.y, 10) | |
108 | ||
109 | def test_setting_y_param_numbergen(self): | |
110 | self.TestPO2.y=numbergen.UniformRandom() # now the Parameter instantiate should be true | |
111 | t9 = self.TestPO2() | |
112 | self.assertEqual('_y_param_value' in t9.__dict__, True) | |
113 | ||
114 | def test_shared_numbergen(self): | |
115 | """ | |
116 | Instances of TestPO2 that don't have their own value for the | |
117 | parameter share one UniformRandom object | |
118 | """ | |
119 | self.TestPO2.y=numbergen.UniformRandom() # now the Parameter instantiate should be true | |
120 | self.assertEqual(self.t7.param.get_value_generator('y') is self.TestPO2().param.params()['y'].default, True) | |
121 | self.assertEqual(self.TestPO2().param.params()['y'].default.__class__.__name__, 'UniformRandom') | |
122 | ||
123 | def test_copy_match(self): | |
124 | "check a copy is the same" | |
125 | t9 = copy.deepcopy(self.t7) | |
126 | self.assertEqual(t9.param.get_value_generator('y') is self.TestPO2().param.params()['y'].default, True) | |
127 | ||
128 | ||
129 | ||
130 | class TestDynamicTimeDependent(TestDynamicParameters): | |
131 | ||
132 | def setUp(self): | |
133 | super(TestDynamicTimeDependent, self).setUp() | |
134 | param.Dynamic.time_dependent = True | |
135 | ||
136 | class TestPO3(param.Parameterized): | |
137 | x = param.Dynamic(default=numbergen.UniformRandom(name='xgen', | |
138 | time_dependent=True)) | |
139 | ||
140 | class TestPO4(self.TestPO1): | |
141 | "Nested parameterized objects" | |
142 | z = param.Parameter(default=self.TestPO1()) | |
143 | ||
144 | self.TestPO3 = TestPO3 | |
145 | self.TestPO4 = TestPO4 | |
146 | ||
147 | self.t10 = self.TestPO1() | |
148 | self.t11 = TestPO3() | |
149 | ||
150 | def test_dynamic_values_unchanged_dependent(self): | |
151 | param.Dynamic.time_dependent = True | |
152 | call_1 = self.t10.x | |
153 | call_2 = self.t10.x | |
154 | call_3 = self.t10.x | |
155 | self.assertEqual(call_1, call_2) | |
156 | self.assertEqual(call_2, call_3) | |
157 | ||
158 | def test_dynamic_values_changed_independent(self): | |
159 | param.Dynamic.time_dependent = False | |
160 | call_1 = self.t10.x | |
161 | call_2 = self.t10.x | |
162 | call_3 = self.t10.x | |
163 | self.assertNotEqual(call_1, call_2) | |
164 | self.assertNotEqual(call_2, call_3) | |
165 | ||
166 | def test_dynamic_values_change(self): | |
167 | param.Dynamic.time_dependent = True | |
168 | with param.Dynamic.time_fn as t: | |
169 | t(0) | |
170 | call_1 = self.t10.x | |
171 | t += 1 | |
172 | call_2 = self.t10.x | |
173 | t(0) | |
174 | call_3 = self.t10.x | |
175 | self.assertNotEqual(call_1, call_2) | |
176 | self.assertNotEqual(call_1, call_3) | |
177 | ||
178 | def test_dynamic_values_time_dependent(self): | |
179 | param.Dynamic.time_dependent = True | |
180 | with param.Dynamic.time_fn as t: | |
181 | t(0) | |
182 | call_1 = self.t11.x | |
183 | t += 1 | |
184 | call_2 = self.t11.x | |
185 | t(0) | |
186 | call_3 = self.t11.x | |
187 | self.assertNotEqual(call_1, call_2) | |
188 | self.assertEqual(call_1, call_3) | |
189 | ||
190 | def test_class_dynamic_values_change(self): | |
191 | call_1 = self.TestPO3.x | |
192 | call_2 = self.TestPO3.x | |
193 | self.assertEqual(call_1, call_2) | |
194 | with param.Dynamic.time_fn as t: | |
195 | t += 1 | |
196 | call_3 = self.TestPO3.x | |
197 | self.assertNotEqual(call_2, call_3) | |
198 | ||
199 | def test_dynamic_value_change_independent(self): | |
200 | t12 = self.TestPO1() | |
201 | t12.param.set_dynamic_time_fn(None) | |
202 | self.assertNotEqual(t12.x, t12.x) | |
203 | self.assertEqual(t12.y, t12.y) | |
204 | ||
205 | def test_dynamic_value_change_disabled(self): | |
206 | " time_fn set on the UniformRandom() when t13.y was set" | |
207 | t13 = self.TestPO1() | |
208 | t13.param.set_dynamic_time_fn(None) | |
209 | t13.y = numbergen.UniformRandom() | |
210 | self.assertNotEqual(t13.y, t13.y) | |
211 | ||
212 | def test_dynamic_value_change_enabled(self): | |
213 | " time_fn set on the UniformRandom() when t13.y was set" | |
214 | t14 = self.TestPO1() | |
215 | t14.y = numbergen.UniformRandom() | |
216 | self.assertEqual(t14.y, t14.y) | |
217 | ||
218 | ||
219 | def test_dynamic_time_fn_not_inherited(self): | |
220 | " time_fn not inherited" | |
221 | t15 = self.TestPO4() | |
222 | t15.param.set_dynamic_time_fn(None) | |
223 | with param.Dynamic.time_fn as t: | |
224 | call_1 = t15.z.x | |
225 | t += 1 | |
226 | call_2 = t15.z.x | |
227 | self.assertNotEqual(call_1, call_2) | |
228 | ||
229 | ||
230 | ||
231 | class TestDynamicSharedNumbergen(TestDynamicParameters): | |
232 | "Check shared generator" | |
233 | def setUp(self): | |
234 | super(TestDynamicSharedNumbergen, self).setUp() | |
235 | self.shared = numbergen.UniformRandom(lbound=-1,ubound=1,seed=20) | |
236 | ||
237 | def test_dynamic_shared_numbergen(self): | |
238 | param.Dynamic.time_dependent = True | |
239 | t11 = self.TestPO1(x=self.shared) | |
240 | t12 = self.TestPO1(x=self.shared) | |
241 | ||
242 | with param.Dynamic.time_fn as t: | |
243 | t += 1 | |
244 | call_1 = t11.x | |
245 | self.assertEqual(call_1, t12.x) | |
246 | t += 1 | |
247 | self.assertNotEqual(call_1, t12.x) | |
248 | ||
249 | ||
250 | ||
251 | if __name__ == "__main__": | |
252 | import nose | |
253 | nose.runmodule() | |
254 | ||
255 | ||
256 | # Commented out block in the original doctest version. | |
257 | # Maybe these are features originally planned but never implemented | |
258 | ||
259 | """ | |
260 | It is not yet possible to set time_fn for a Parameter instance | |
261 | >>> class TestPO5(param.Parameterized): | |
262 | ... x = param.Dynamic(default=numbergen.UniformRandom(),dynamic_time_fn=None) | |
263 | """ | |
264 | ||
265 | """ | |
266 | We currently don't support iterators/generators in Dynamic unless | |
267 | they're wrapped. | |
268 | ||
269 | >>> i = iter([1,2,3]) | |
270 | >>> t11.x = i | |
271 | ||
272 | >>> topo.sim.run(1) | |
273 | ||
274 | >>> t11.x | |
275 | 1 | |
276 | ||
277 | >>> def gen(): | |
278 | ... yield 2 | |
279 | ... yield 4 | |
280 | ... yield 6 | |
281 | ||
282 | >>> g = gen() | |
283 | ||
284 | >>> t11.x = g | |
285 | ||
286 | >>> t11.x | |
287 | 2 | |
288 | ||
289 | >>> topo.sim.run(1) | |
290 | ||
291 | >>> t11.x | |
292 | 4 | |
293 | """ |
0 | """ | |
1 | Unit test for the IPython magic | |
2 | """ | |
3 | ||
4 | import re | |
5 | import sys | |
6 | import param | |
7 | from . import API1TestCase | |
8 | ||
9 | try: | |
10 | import IPython # noqa | |
11 | except ImportError: | |
12 | import os | |
13 | if os.getenv('PARAM_TEST_IPYTHON','0') == '1': | |
14 | raise ImportError("PARAM_TEST_IPYTHON=1 but ipython not available.") | |
15 | ||
16 | # TODO: is the below actually true? | |
17 | ||
18 | # SkipTest will be raised if IPython unavailable | |
19 | from param.ipython import ParamPager | |
20 | ||
21 | test1_repr = """\x1b[1;32mParameters of 'TestClass'\n=========================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
22 | ||
23 | ||
24 | test2_repr = """\x1b[1;32mParameters of 'TestClass' instance\n==================================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
25 | ||
26 | class TestParamPager(API1TestCase): | |
27 | ||
28 | def setUp(self): | |
29 | super(TestParamPager, self).setUp() | |
30 | self.maxDiff = None | |
31 | class TestClass(param.Parameterized): | |
32 | u = param.Number(4) | |
33 | v = param.Number(4, constant=True) | |
34 | w = param.Number(4, readonly=True) | |
35 | x = param.String(None, allow_None=True) | |
36 | y = param.Number(4, bounds=(-1, None)) | |
37 | z = param.Number(4, bounds=(-1, 100), softbounds=(-100, -200)) | |
38 | ||
39 | self.TestClass = TestClass | |
40 | self.pager = ParamPager() | |
41 | ||
42 | def test_parameterized_class(self): | |
43 | page_string = self.pager(self.TestClass) | |
44 | # Remove params automatic numbered names | |
45 | page_string = re.sub('TestClass(\d+)', 'TestClass', page_string) | |
46 | ref_string = re.sub('TestClass(\d+)', 'TestClass', test1_repr) | |
47 | ||
48 | try: | |
49 | self.assertEqual(page_string, ref_string) | |
50 | except Exception as e: | |
51 | sys.stderr.write(page_string) # Coloured output | |
52 | sys.stderr.write("\nRAW STRING:\n\n%r\n\n" % page_string) | |
53 | raise e | |
54 | ||
55 | def test_parameterized_instance(self): | |
56 | page_string = self.pager(self.TestClass()) | |
57 | # Remove params automatic numbered names | |
58 | page_string = re.sub('TestClass(\d+)', 'TestClass', page_string) | |
59 | ref_string = re.sub('TestClass(\d+)', 'TestClass', test2_repr) | |
60 | ||
61 | try: | |
62 | self.assertEqual(page_string, ref_string) | |
63 | except Exception as e: | |
64 | sys.stderr.write(page_string) # Coloured output | |
65 | sys.stderr.write("\nRAW STRING:\n\n%r\n\n" % page_string) | |
66 | raise e |
0 | import param | |
1 | from . import API1TestCase | |
2 | # TODO: I copied the tests from testobjectselector, although I | |
3 | # struggled to understand some of them. Both files should be reviewed | |
4 | # and cleaned up together. | |
5 | ||
6 | # TODO: tests copied from testobjectselector could use assertRaises | |
7 | # context manager (and could be updated in testobjectselector too). | |
8 | ||
9 | class TestListSelectorParameters(API1TestCase): | |
10 | ||
11 | def setUp(self): | |
12 | super(TestListSelectorParameters, self).setUp() | |
13 | class P(param.Parameterized): | |
14 | e = param.ListSelector(default=[5],objects=[5,6,7]) | |
15 | f = param.ListSelector(default=10) | |
16 | h = param.ListSelector(default=None) | |
17 | g = param.ListSelector(default=None,objects=[7,8]) | |
18 | i = param.ListSelector(default=[7],objects=[9],check_on_set=False) | |
19 | ||
20 | self.P = P | |
21 | ||
22 | def test_default_None(self): | |
23 | class Q(param.Parameterized): | |
24 | r = param.ListSelector(default=None) | |
25 | ||
26 | def test_set_object_constructor(self): | |
27 | p = self.P(e=[6]) | |
28 | self.assertEqual(p.e, [6]) | |
29 | ||
30 | def test_set_object_outside_bounds(self): | |
31 | p = self.P(e=[6]) | |
32 | try: | |
33 | p.e = [9] | |
34 | except ValueError: | |
35 | pass | |
36 | else: | |
37 | raise AssertionError("Object set outside range.") | |
38 | ||
39 | def test_set_object_setattr(self): | |
40 | p = self.P(e=[6]) | |
41 | p.f = [9] | |
42 | self.assertEqual(p.f, [9]) | |
43 | p.g = [7] | |
44 | self.assertEqual(p.g, [7]) | |
45 | p.i = [12] | |
46 | self.assertEqual(p.i, [12]) | |
47 | ||
48 | ||
49 | def test_set_object_not_None(self): | |
50 | p = self.P(e=[6]) | |
51 | p.g = [7] | |
52 | try: | |
53 | p.g = None | |
54 | except TypeError: | |
55 | pass | |
56 | else: | |
57 | raise AssertionError("Object set outside range.") | |
58 | ||
59 | def test_set_one_object_not_None(self): | |
60 | p = self.P(e=[6]) | |
61 | p.g = [7] | |
62 | try: | |
63 | p.g = [None] | |
64 | except ValueError: | |
65 | pass | |
66 | else: | |
67 | raise AssertionError("Object set outside range.") | |
68 | ||
69 | ||
70 | def test_set_object_setattr_post_error(self): | |
71 | p = self.P(e=[6]) | |
72 | p.f = [9] | |
73 | self.assertEqual(p.f, [9]) | |
74 | p.g = [7] | |
75 | try: | |
76 | p.g = [None] | |
77 | except ValueError: | |
78 | pass | |
79 | else: | |
80 | raise AssertionError("Object set outside range.") | |
81 | ||
82 | self.assertEqual(p.g, [7]) | |
83 | p.i = [12] | |
84 | self.assertEqual(p.i, [12]) | |
85 | ||
86 | def test_initialization_out_of_bounds(self): | |
87 | try: | |
88 | class Q(param.Parameterized): | |
89 | q = param.ListSelector([5],objects=[4]) | |
90 | except ValueError: | |
91 | pass | |
92 | else: | |
93 | raise AssertionError("ListSelector created outside range.") | |
94 | ||
95 | ||
96 | def test_initialization_no_bounds(self): | |
97 | try: | |
98 | class Q(param.Parameterized): | |
99 | q = param.ListSelector([5],objects=10) | |
100 | except TypeError: | |
101 | pass | |
102 | else: | |
103 | raise AssertionError("ListSelector created without range.") | |
104 | ||
105 | ||
106 | ################################################################## | |
107 | ################################################################## | |
108 | ### new tests (not copied from testobjectselector) | |
109 | ||
110 | def test_bad_default(self): | |
111 | with self.assertRaises(TypeError): | |
112 | class Q(param.Parameterized): | |
113 | r = param.ListSelector(default=6,check_on_set=True) | |
114 | ||
115 | def test_implied_check_on_set(self): | |
116 | with self.assertRaises(TypeError): | |
117 | class Q(param.Parameterized): | |
118 | r = param.ListSelector(default=7,objects=[7,8]) | |
119 | ||
120 | def test_default_not_checked(self): | |
121 | class Q(param.Parameterized): | |
122 | r = param.ListSelector(default=[6]) | |
123 | ||
124 | ########################## | |
125 | # CEBALERT: not sure it makes sense for ListSelector to be set to | |
126 | # a non-iterable value (except None). I.e. I think this first test | |
127 | # should fail. | |
128 | def test_default_not_checked_to_be_iterable(self): | |
129 | class Q(param.Parameterized): | |
130 | r = param.ListSelector(default=6) | |
131 | ||
132 | def test_set_checked_to_be_iterable(self): | |
133 | class Q(param.Parameterized): | |
134 | r = param.ListSelector(default=6,check_on_set=False) | |
135 | ||
136 | with self.assertRaises(TypeError): | |
137 | Q.r = 6 | |
138 | ########################## | |
139 | ||
140 | ||
141 | def test_compute_default(self): | |
142 | class Q(param.Parameterized): | |
143 | r = param.ListSelector(default=None, compute_default_fn=lambda: [1,2,3]) | |
144 | ||
145 | self.assertEqual(Q.r, None) | |
146 | Q.param.params('r').compute_default() | |
147 | self.assertEqual(Q.r, [1,2,3]) | |
148 | self.assertEqual(Q.param.params('r').objects, [1,2,3]) | |
149 | ||
150 | def test_bad_compute_default(self): | |
151 | class Q(param.Parameterized): | |
152 | r = param.ListSelector(default=None,compute_default_fn=lambda:1) | |
153 | ||
154 | with self.assertRaises(TypeError): | |
155 | Q.param.params('r').compute_default() | |
156 | ||
157 | if __name__ == "__main__": | |
158 | import nose | |
159 | nose.runmodule() |
0 | """ | |
1 | Test cases for the numbergen module. | |
2 | """ | |
3 | import numbergen | |
4 | from . import API1TestCase | |
5 | ||
6 | _seed = 0 # keep tests deterministic | |
7 | _iterations = 1000 | |
8 | ||
9 | ||
10 | class TestUniformRandom(API1TestCase): | |
11 | def test_range(self): | |
12 | lbound = 2.0 | |
13 | ubound = 5.0 | |
14 | gen = numbergen.UniformRandom( | |
15 | seed=_seed, | |
16 | lbound=lbound, | |
17 | ubound=ubound) | |
18 | for _ in range(_iterations): | |
19 | value = gen() | |
20 | self.assertTrue(lbound <= value < ubound) | |
21 | ||
22 | class TestUniformRandomOffset(API1TestCase): | |
23 | def test_range(self): | |
24 | lbound = 2.0 | |
25 | ubound = 5.0 | |
26 | gen = numbergen.UniformRandomOffset( | |
27 | seed=_seed, | |
28 | mean=(ubound + lbound) / 2, | |
29 | range=ubound - lbound) | |
30 | for _ in range(_iterations): | |
31 | value = gen() | |
32 | self.assertTrue(lbound <= value < ubound) | |
33 | ||
34 | if __name__ == "__main__": | |
35 | import nose | |
36 | nose.runmodule() |
0 | """ | |
1 | Unit test for Number parameters and their subclasses. | |
2 | """ | |
3 | import param | |
4 | import datetime as dt | |
5 | from . import API1TestCase | |
6 | ||
7 | ||
8 | class TestNumberParameters(API1TestCase): | |
9 | ||
10 | def test_initialization_without_step_class(self): | |
11 | class Q(param.Parameterized): | |
12 | q = param.Number(default=1) | |
13 | ||
14 | self.assertEqual(Q.param['q'].step, None) | |
15 | ||
16 | def test_initialization_with_step_class(self): | |
17 | class Q(param.Parameterized): | |
18 | q = param.Number(default=1, step=0.5) | |
19 | ||
20 | self.assertEqual(Q.param['q'].step, 0.5) | |
21 | ||
22 | def test_initialization_without_step_instance(self): | |
23 | class Q(param.Parameterized): | |
24 | q = param.Number(default=1) | |
25 | ||
26 | self.assertEqual(Q.param['q'].step, None) | |
27 | ||
28 | def test_initialization_with_step_instance(self): | |
29 | class Q(param.Parameterized): | |
30 | q = param.Number(default=1, step=0.5) | |
31 | ||
32 | qobj = Q() | |
33 | self.assertEqual(qobj.param['q'].step, 0.5) | |
34 | ||
35 | def test_step_invalid_type_number_parameter(self): | |
36 | exception = "Step parameter can only be None or a numeric value" | |
37 | with self.assertRaisesRegexp(ValueError, exception): | |
38 | param.Number(step='invalid value') | |
39 | ||
40 | def test_step_invalid_type_integer_parameter(self): | |
41 | exception = "Step parameter can only be None or an integer value" | |
42 | with self.assertRaisesRegexp(ValueError, exception): | |
43 | param.Integer(step=3.4) | |
44 | ||
45 | def test_step_invalid_type_datetime_parameter(self): | |
46 | exception = "Step parameter can only be None, a datetime or datetime type" | |
47 | with self.assertRaisesRegexp(ValueError, exception): | |
48 | param.Date(dt.datetime(2017,2,27), step=3.2) | |
49 | ||
50 | def test_step_invalid_type_date_parameter(self): | |
51 | exception = "Step parameter can only be None or a date type" | |
52 | with self.assertRaisesRegexp(ValueError, exception): | |
53 | param.CalendarDate(dt.date(2017,2,27), step=3.2) |
0 | """ | |
1 | If numpy's present, is numpy stuff ok? | |
2 | """ | |
3 | import unittest | |
4 | import os | |
5 | ||
6 | import param | |
7 | from . import API1TestCase | |
8 | ||
9 | try: | |
10 | import numpy | |
11 | import numpy.testing | |
12 | except ImportError: | |
13 | if os.getenv('PARAM_TEST_NUMPY','0') == '1': | |
14 | raise ImportError("PARAM_TEST_NUMPY=1 but numpy not available.") | |
15 | else: | |
16 | raise unittest.SkipTest("numpy not available") | |
17 | ||
18 | ||
19 | def _is_array_and_equal(test,ref): | |
20 | if not type(test) == numpy.ndarray: | |
21 | raise AssertionError | |
22 | numpy.testing.assert_array_equal(test,ref) | |
23 | ||
24 | # TODO: incomplete | |
25 | class TestNumpy(API1TestCase): | |
26 | def test_array_param(self): | |
27 | class Z(param.Parameterized): | |
28 | z = param.Array(default=numpy.array([1])) | |
29 | ||
30 | _is_array_and_equal(Z.z,[1]) | |
31 | ||
32 | z = Z(z=numpy.array([1,2])) | |
33 | _is_array_and_equal(z.z,[1,2]) | |
34 | ||
35 | def test_array_param_positional(self): | |
36 | class Z(param.Parameterized): | |
37 | z = param.Array(numpy.array([1])) | |
38 | ||
39 | _is_array_and_equal(Z.z,[1]) | |
40 | ||
41 | z = Z(z=numpy.array([1,2])) | |
42 | _is_array_and_equal(z.z,[1,2]) | |
43 | ||
44 | if __name__ == "__main__": | |
45 | import nose | |
46 | nose.runmodule() |
0 | """ | |
1 | Unit test for object selector parameters. | |
2 | ||
3 | Originally implemented as doctests in Topographica in the file | |
4 | testEnumerationParameter.txt | |
5 | """ | |
6 | ||
7 | import param | |
8 | from . import API1TestCase | |
9 | from collections import OrderedDict | |
10 | ||
11 | ||
12 | opts=dict(A=[1,2],B=[3,4],C=dict(a=1,b=2)) | |
13 | ||
14 | ||
15 | class TestObjectSelectorParameters(API1TestCase): | |
16 | ||
17 | def setUp(self): | |
18 | super(TestObjectSelectorParameters, self).setUp() | |
19 | class P(param.Parameterized): | |
20 | e = param.ObjectSelector(default=5,objects=[5,6,7]) | |
21 | f = param.ObjectSelector(default=10) | |
22 | h = param.ObjectSelector(default=None) | |
23 | g = param.ObjectSelector(default=None,objects=[7,8]) | |
24 | i = param.ObjectSelector(default=7,objects=[9],check_on_set=False) | |
25 | s = param.ObjectSelector(default=3,objects=OrderedDict(one=1,two=2,three=3)) | |
26 | d = param.ObjectSelector(default=opts['B'],objects=opts) | |
27 | ||
28 | self.P = P | |
29 | ||
30 | def test_set_object_constructor(self): | |
31 | p = self.P(e=6) | |
32 | self.assertEqual(p.e, 6) | |
33 | ||
34 | def test_get_range_list(self): | |
35 | r = self.P.param.params("g").get_range() | |
36 | self.assertEqual(r['7'],7) | |
37 | self.assertEqual(r['8'],8) | |
38 | ||
39 | def test_get_range_dict(self): | |
40 | r = self.P.param.params("s").get_range() | |
41 | self.assertEqual(r['one'],1) | |
42 | self.assertEqual(r['two'],2) | |
43 | ||
44 | def test_get_range_mutable(self): | |
45 | r = self.P.param.params("d").get_range() | |
46 | self.assertEqual(r['A'],opts['A']) | |
47 | self.assertEqual(r['C'],opts['C']) | |
48 | self.d=opts['A'] | |
49 | self.d=opts['C'] | |
50 | self.d=opts['B'] | |
51 | ||
52 | def test_set_object_outside_bounds(self): | |
53 | p = self.P(e=6) | |
54 | try: | |
55 | p.e = 9 | |
56 | except ValueError: | |
57 | pass | |
58 | else: | |
59 | raise AssertionError("Object set outside range.") | |
60 | ||
61 | def test_set_object_setattr(self): | |
62 | p = self.P(e=6) | |
63 | p.f = 9 | |
64 | self.assertEqual(p.f, 9) | |
65 | p.g = 7 | |
66 | self.assertEqual(p.g, 7) | |
67 | p.i = 12 | |
68 | self.assertEqual(p.i, 12) | |
69 | ||
70 | ||
71 | def test_set_object_not_None(self): | |
72 | p = self.P(e=6) | |
73 | p.g = 7 | |
74 | try: | |
75 | p.g = None | |
76 | except ValueError: | |
77 | pass | |
78 | else: | |
79 | raise AssertionError("Object set outside range.") | |
80 | ||
81 | def test_set_object_setattr_post_error(self): | |
82 | p = self.P(e=6) | |
83 | p.f = 9 | |
84 | self.assertEqual(p.f, 9) | |
85 | p.g = 7 | |
86 | try: | |
87 | p.g = None | |
88 | except ValueError: | |
89 | pass | |
90 | else: | |
91 | raise AssertionError("Object set outside range.") | |
92 | ||
93 | self.assertEqual(p.g, 7) | |
94 | p.i = 12 | |
95 | self.assertEqual(p.i, 12) | |
96 | ||
97 | def test_initialization_out_of_bounds(self): | |
98 | try: | |
99 | class Q(param.Parameterized): | |
100 | q = param.ObjectSelector(5,objects=[4]) | |
101 | except ValueError: | |
102 | pass | |
103 | else: | |
104 | raise AssertionError("ObjectSelector created outside range.") | |
105 | ||
106 | ||
107 | def test_initialization_no_bounds(self): | |
108 | try: | |
109 | class Q(param.Parameterized): | |
110 | q = param.ObjectSelector(5,objects=10) | |
111 | except TypeError: | |
112 | pass | |
113 | else: | |
114 | raise AssertionError("ObjectSelector created without range.") | |
115 | ||
116 | ||
117 | if __name__ == "__main__": | |
118 | import nose | |
119 | nose.runmodule() |
0 | """ | |
1 | Test Parameters based on pandas | |
2 | """ | |
3 | import unittest | |
4 | import os | |
5 | ||
6 | import param | |
7 | from . import API1TestCase | |
8 | ||
9 | try: | |
10 | import pandas | |
11 | except ImportError: | |
12 | if os.getenv('PARAM_TEST_PANDAS','0') == '1': | |
13 | raise ImportError("PARAM_TEST_PANDAS=1 but pandas not available.") | |
14 | else: | |
15 | raise unittest.SkipTest("pandas not available") | |
16 | ||
17 | ||
18 | class TestDataFrame(API1TestCase): | |
19 | ||
20 | def test_dataframe_positional_argument(self): | |
21 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, | |
22 | columns=['b', 'a', 'c']) | |
23 | class Test(param.Parameterized): | |
24 | df = param.DataFrame(valid_df) | |
25 | ||
26 | def test_empty_dataframe_param_invalid_set(self): | |
27 | empty = pandas.DataFrame() | |
28 | class Test(param.Parameterized): | |
29 | df = param.DataFrame(default=empty) | |
30 | ||
31 | test = Test() | |
32 | exception = "Parameter 'df' value must be an instance of DataFrame, not '3'" | |
33 | with self.assertRaisesRegexp(ValueError, exception): | |
34 | test.df = 3 | |
35 | ||
36 | def test_dataframe_unordered_column_set_valid(self): | |
37 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
38 | class Test(param.Parameterized): | |
39 | df = param.DataFrame(default=valid_df, columns={'a', 'b'}) | |
40 | ||
41 | ||
42 | def test_dataframe_unordered_column_set_invalid(self): | |
43 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'd':[4,5]}, columns=['b', 'a', 'd']) | |
44 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
45 | ||
46 | class Test(param.Parameterized): | |
47 | df = param.DataFrame(default=valid_df, columns={'a', 'd'}) | |
48 | ||
49 | ||
50 | test = Test() | |
51 | self.assertEquals(test.param.params('df').ordered, False) | |
52 | exception = "Provided DataFrame columns \['b', 'a', 'c'\] does not contain required columns \['a', 'd'\]" | |
53 | with self.assertRaisesRegexp(ValueError, exception): | |
54 | test.df = invalid_df | |
55 | ||
56 | def test_dataframe_ordered_column_list_valid(self): | |
57 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
58 | class Test(param.Parameterized): | |
59 | test = param.DataFrame(default=valid_df, columns=['b', 'a', 'c']) | |
60 | ||
61 | ||
62 | def test_dataframe_ordered_column_list_invalid(self): | |
63 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'd':[4,5]}, columns=['b', 'a', 'd']) | |
64 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['a', 'b', 'd']) | |
65 | ||
66 | class Test(param.Parameterized): | |
67 | df = param.DataFrame(default=valid_df, columns=['b', 'a', 'd']) | |
68 | ||
69 | test = Test() | |
70 | self.assertEquals(test.param.params('df').ordered, True) | |
71 | ||
72 | exception = "Provided DataFrame columns \['a', 'b', 'd'\] must exactly match \['b', 'a', 'd'\]" | |
73 | with self.assertRaisesRegexp(ValueError, exception): | |
74 | test.df = invalid_df | |
75 | ||
76 | ||
77 | def test_dataframe_unordered_column_number_valid_df(self): | |
78 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
79 | class Test(param.Parameterized): | |
80 | df = param.DataFrame(default=valid_df, columns=3) | |
81 | ||
82 | def test_dataframe_unordered_column_number_invalid(self): | |
83 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
84 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3]}, columns=['b', 'a']) | |
85 | class Test(param.Parameterized): | |
86 | df = param.DataFrame(default=valid_df, columns=3) | |
87 | ||
88 | test = Test() | |
89 | self.assertEquals(test.param.params('df').ordered, None) | |
90 | ||
91 | exception = "Column length 2 does not match declared bounds of 3" | |
92 | with self.assertRaisesRegexp(ValueError, exception): | |
93 | test.df = invalid_df | |
94 | ||
95 | ||
96 | def test_dataframe_unordered_column_tuple_valid(self): | |
97 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
98 | class Test(param.Parameterized): | |
99 | df = param.DataFrame(default=valid_df, columns=(None,3)) | |
100 | ||
101 | def test_dataframe_unordered_column_tuple_invalid(self): | |
102 | ||
103 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
104 | ||
105 | exception = "Columns length 3 does not match declared bounds of \(None, 2\)" | |
106 | with self.assertRaisesRegexp(ValueError, exception): | |
107 | class Test(param.Parameterized): | |
108 | df = param.DataFrame(default=invalid_df, columns=(None,2)) | |
109 | ||
110 | def test_dataframe_row_number_valid_df(self): | |
111 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
112 | class Test(param.Parameterized): | |
113 | df = param.DataFrame(default=valid_df, rows=2) | |
114 | ||
115 | def test_dataframe_row_number_invalid(self): | |
116 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3]}, columns=['b', 'a']) | |
117 | invalid_df = pandas.DataFrame({'a':[1,2,4], 'b':[2,3,4]}, columns=['b', 'a']) | |
118 | class Test(param.Parameterized): | |
119 | df = param.DataFrame(default=valid_df, rows=2) | |
120 | ||
121 | test = Test() | |
122 | exception = "Row length 3 does not match declared bounds of 2" | |
123 | with self.assertRaisesRegexp(ValueError, exception): | |
124 | test.df = invalid_df | |
125 | ||
126 | def test_dataframe_unordered_row_tuple_valid(self): | |
127 | valid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
128 | class Test(param.Parameterized): | |
129 | df = param.DataFrame(default=valid_df, rows=(None,3)) | |
130 | ||
131 | def test_dataframe_unordered_row_tuple_invalid(self): | |
132 | ||
133 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) | |
134 | ||
135 | exception = "Row length 2 does not match declared bounds of \(5, 7\)" | |
136 | with self.assertRaisesRegexp(ValueError, exception): | |
137 | class Test(param.Parameterized): | |
138 | df = param.DataFrame(default=invalid_df, rows=(5,7)) | |
139 | ||
140 | ||
141 | class TestSeries(API1TestCase): | |
142 | ||
143 | def test_series_positional_argument(self): | |
144 | valid_series = pandas.Series([1,2]) | |
145 | class Test(param.Parameterized): | |
146 | series = param.Series(valid_series, rows=2) | |
147 | ||
148 | def test_series_row_number_valid(self): | |
149 | valid_series = pandas.Series([1,2]) | |
150 | class Test(param.Parameterized): | |
151 | series = param.Series(default=valid_series, rows=2) | |
152 | ||
153 | def test_series_row_number_invalid(self): | |
154 | valid_series = pandas.Series([1,2]) | |
155 | invalid_series = pandas.Series([1,2,3]) | |
156 | class Test(param.Parameterized): | |
157 | series = param.Series(default=valid_series, rows=2) | |
158 | ||
159 | test = Test() | |
160 | exception = "Row length 3 does not match declared bounds of 2" | |
161 | with self.assertRaisesRegexp(ValueError, exception): | |
162 | test.series = invalid_series | |
163 | ||
164 | def test_series_unordered_row_tuple_valid(self): | |
165 | valid_series = pandas.Series([1,2,3]) | |
166 | class Test(param.Parameterized): | |
167 | series = param.Series(default=valid_series, rows=(None,3)) | |
168 | ||
169 | def test_series_unordered_row_tuple_invalid(self): | |
170 | ||
171 | invalid_series = pandas.Series([1,2]) | |
172 | ||
173 | exception = "Row length 2 does not match declared bounds of \(5, 7\)" | |
174 | with self.assertRaisesRegexp(ValueError, exception): | |
175 | class Test(param.Parameterized): | |
176 | series = param.Series(default=invalid_series, rows=(5,7)) | |
177 | ||
178 | if __name__ == "__main__": | |
179 | import nose | |
180 | nose.runmodule() |
0 | """ | |
1 | Unit test for param.depends. | |
2 | """ | |
3 | ||
4 | ||
5 | import param | |
6 | from . import API1TestCase | |
7 | ||
8 | ||
9 | class TestParamDepends(API1TestCase): | |
10 | ||
11 | def setUp(self): | |
12 | class P(param.Parameterized): | |
13 | a = param.Parameter() | |
14 | b = param.Parameter() | |
15 | ||
16 | @param.depends('a') | |
17 | def single_parameter(self): | |
18 | pass | |
19 | ||
20 | @param.depends('a:constant') | |
21 | def constant(self): | |
22 | pass | |
23 | ||
24 | @param.depends('a.param') | |
25 | def nested(self): | |
26 | pass | |
27 | ||
28 | class P2(param.Parameterized): | |
29 | ||
30 | @param.depends(P.param.a) | |
31 | def external_param(self, a): | |
32 | pass | |
33 | ||
34 | self.P = P | |
35 | self.P2 = P2 | |
36 | ||
37 | def test_param_depends_instance(self): | |
38 | p = self.P() | |
39 | pinfos = p.param.params_depended_on('single_parameter') | |
40 | self.assertEqual(len(pinfos), 1) | |
41 | pinfo = pinfos[0] | |
42 | self.assertIs(pinfo.cls, self.P) | |
43 | self.assertIs(pinfo.inst, p) | |
44 | self.assertEqual(pinfo.name, 'a') | |
45 | self.assertEqual(pinfo.what, 'value') | |
46 | ||
47 | def test_param_depends_class(self): | |
48 | pinfos = self.P.param.params_depended_on('single_parameter') | |
49 | self.assertEqual(len(pinfos), 1) | |
50 | pinfo = pinfos[0] | |
51 | self.assertIs(pinfo.cls, self.P) | |
52 | self.assertIs(pinfo.inst, None) | |
53 | self.assertEqual(pinfo.name, 'a') | |
54 | self.assertEqual(pinfo.what, 'value') | |
55 | ||
56 | def test_param_depends_constant(self): | |
57 | pinfos = self.P.param.params_depended_on('constant') | |
58 | self.assertEqual(len(pinfos), 1) | |
59 | pinfo = pinfos[0] | |
60 | self.assertIs(pinfo.cls, self.P) | |
61 | self.assertIs(pinfo.inst, None) | |
62 | self.assertEqual(pinfo.name, 'a') | |
63 | self.assertEqual(pinfo.what, 'constant') | |
64 | ||
65 | def test_param_depends_nested(self): | |
66 | inst = self.P(a=self.P()) | |
67 | pinfos = inst.param.params_depended_on('nested') | |
68 | self.assertEqual(len(pinfos), 4) | |
69 | pinfos = {(pi.inst, pi.name): pi for pi in pinfos} | |
70 | pinfo = pinfos[(inst, 'a')] | |
71 | self.assertIs(pinfo.cls, self.P) | |
72 | self.assertIs(pinfo.inst, inst) | |
73 | self.assertEqual(pinfo.name, 'a') | |
74 | self.assertEqual(pinfo.what, 'value') | |
75 | for p in ['name', 'a', 'b']: | |
76 | info = pinfos[(inst.a, p)] | |
77 | self.assertEqual(info.name, p) | |
78 | self.assertIs(info.inst, inst.a) | |
79 | ||
80 | def test_param_external_param_instance(self): | |
81 | inst = self.P2() | |
82 | pinfos = inst.param.params_depended_on('external_param') | |
83 | pinfo = pinfos[0] | |
84 | self.assertIs(pinfo.cls, self.P) | |
85 | self.assertIs(pinfo.inst, None) | |
86 | self.assertEqual(pinfo.name, 'a') | |
87 | self.assertEqual(pinfo.what, 'value') | |
88 | ||
89 | ||
90 | ||
91 | class TestParamDependsFunction(API1TestCase): | |
92 | ||
93 | def setUp(self): | |
94 | class P(param.Parameterized): | |
95 | a = param.Parameter() | |
96 | b = param.Parameter() | |
97 | ||
98 | ||
99 | self.P = P | |
100 | ||
101 | def test_param_depends_function_instance_params(self): | |
102 | p = self.P() | |
103 | ||
104 | @param.depends(p.param.a, c=p.param.b) | |
105 | def function(value, c): | |
106 | pass | |
107 | ||
108 | dependencies = { | |
109 | 'dependencies': (p.param.a,), | |
110 | 'kw': {'c': p.param.b}, | |
111 | 'watch': False | |
112 | } | |
113 | self.assertEqual(function._dinfo, dependencies) | |
114 | ||
115 | def test_param_depends_function_class_params(self): | |
116 | p = self.P | |
117 | ||
118 | @param.depends(p.param.a, c=p.param.b) | |
119 | def function(value, c): | |
120 | pass | |
121 | ||
122 | dependencies = { | |
123 | 'dependencies': (p.param.a,), | |
124 | 'kw': {'c': p.param.b}, | |
125 | 'watch': False | |
126 | } | |
127 | self.assertEqual(function._dinfo, dependencies) | |
128 | ||
129 | def test_param_depends_function_instance_params_watch(self): | |
130 | p = self.P(a=1, b=2) | |
131 | ||
132 | d = [] | |
133 | ||
134 | @param.depends(p.param.a, c=p.param.b, watch=True) | |
135 | def function(value, c): | |
136 | d.append(value+c) | |
137 | ||
138 | p.a = 2 | |
139 | self.assertEqual(d, [4]) | |
140 | p.b = 3 | |
141 | self.assertEqual(d, [4, 5]) | |
142 | ||
143 | def test_param_depends_function_class_params_watch(self): | |
144 | p = self.P | |
145 | p.a = 1 | |
146 | p.b = 2 | |
147 | ||
148 | d = [] | |
149 | ||
150 | @param.depends(p.param.a, c=p.param.b, watch=True) | |
151 | def function(value, c): | |
152 | d.append(value+c) | |
153 | ||
154 | p.a = 2 | |
155 | self.assertEqual(d, [4]) | |
156 | p.b = 3 | |
157 | self.assertEqual(d, [4, 5]) |
0 | """ | |
1 | Unit test for Parameterized. | |
2 | """ | |
3 | ||
4 | import param | |
5 | import numbergen | |
6 | ||
7 | from . import API1TestCase | |
8 | from .utils import MockLoggingHandler | |
9 | ||
10 | # CEBALERT: not anything like a complete test of Parameterized! | |
11 | ||
12 | ||
13 | import random | |
14 | from nose.tools import istest, nottest | |
15 | ||
16 | ||
17 | from param.parameterized import ParamOverrides, shared_parameters | |
18 | from param.parameterized import default_label_formatter, no_instance_params | |
19 | ||
20 | @nottest | |
21 | class _SomeRandomNumbers(object): | |
22 | def __call__(self): | |
23 | return random.random() | |
24 | ||
25 | @nottest | |
26 | class TestPO(param.Parameterized): | |
27 | inst = param.Parameter(default=[1,2,3],instantiate=True) | |
28 | notinst = param.Parameter(default=[1,2,3],instantiate=False, per_instance=False) | |
29 | const = param.Parameter(default=1,constant=True) | |
30 | ro = param.Parameter(default="Hello",readonly=True) | |
31 | ro2 = param.Parameter(default=object(),readonly=True,instantiate=True) | |
32 | ro_label = param.Parameter(default=object(), label='Ro Label') | |
33 | ro_format = param.Parameter(default=object()) | |
34 | ||
35 | dyn = param.Dynamic(default=1) | |
36 | ||
37 | @nottest | |
38 | class TestPOValidation(param.Parameterized): | |
39 | value = param.Number(default=2, bounds=(0, 4)) | |
40 | ||
41 | @nottest | |
42 | @no_instance_params | |
43 | class TestPONoInstance(TestPO): | |
44 | pass | |
45 | ||
46 | @nottest | |
47 | class AnotherTestPO(param.Parameterized): | |
48 | instPO = param.Parameter(default=TestPO(),instantiate=True) | |
49 | notinstPO = param.Parameter(default=TestPO(),instantiate=False) | |
50 | ||
51 | @nottest | |
52 | class TestAbstractPO(param.Parameterized): | |
53 | __abstract = True | |
54 | ||
55 | @nottest | |
56 | class TestParamInstantiation(AnotherTestPO): | |
57 | instPO = param.Parameter(default=AnotherTestPO(),instantiate=False) | |
58 | ||
59 | @istest | |
60 | class TestParameterized(API1TestCase): | |
61 | ||
62 | @classmethod | |
63 | def setUpClass(cls): | |
64 | super(TestParameterized, cls).setUpClass() | |
65 | log = param.parameterized.get_logger() | |
66 | cls.log_handler = MockLoggingHandler(level='DEBUG') | |
67 | log.addHandler(cls.log_handler) | |
68 | ||
69 | ||
70 | def test_constant_parameter(self): | |
71 | """Test that you can't set a constant parameter after construction.""" | |
72 | testpo = TestPO(const=17) | |
73 | self.assertEqual(testpo.const,17) | |
74 | self.assertRaises(TypeError,setattr,testpo,'const',10) | |
75 | ||
76 | # check you can set on class | |
77 | TestPO.const=9 | |
78 | testpo = TestPO() | |
79 | self.assertEqual(testpo.const,9) | |
80 | ||
81 | ||
82 | def test_readonly_parameter(self): | |
83 | """Test that you can't set a read-only parameter on construction or as an attribute.""" | |
84 | testpo = TestPO() | |
85 | self.assertEqual(testpo.ro,"Hello") | |
86 | ||
87 | with self.assertRaises(TypeError): | |
88 | t = TestPO(ro=20) | |
89 | ||
90 | t=TestPO() | |
91 | self.assertRaises(TypeError,setattr,t,'ro',10) | |
92 | ||
93 | # check you cannot set on class | |
94 | self.assertRaises(TypeError,setattr,TestPO,'ro',5) | |
95 | ||
96 | self.assertEqual(testpo.param.params()['ro'].constant,True) | |
97 | ||
98 | # check that instantiate was ignored for readonly | |
99 | self.assertEqual(testpo.param.params()['ro2'].instantiate,False) | |
100 | ||
101 | ||
102 | def test_basic_instantiation(self): | |
103 | """Check that instantiated parameters are copied into objects.""" | |
104 | ||
105 | testpo = TestPO() | |
106 | ||
107 | self.assertEqual(testpo.inst,TestPO.inst) | |
108 | self.assertEqual(testpo.notinst,TestPO.notinst) | |
109 | ||
110 | TestPO.inst[1]=7 | |
111 | TestPO.notinst[1]=7 | |
112 | ||
113 | self.assertEqual(testpo.notinst,[1,7,3]) | |
114 | self.assertEqual(testpo.inst,[1,2,3]) | |
115 | ||
116 | ||
117 | def test_more_instantiation(self): | |
118 | """Show that objects in instantiated Parameters can still share data.""" | |
119 | anothertestpo = AnotherTestPO() | |
120 | ||
121 | ### CB: AnotherTestPO.instPO is instantiated, but | |
122 | ### TestPO.notinst is not instantiated - so notinst is still | |
123 | ### shared, even by instantiated parameters of AnotherTestPO. | |
124 | ### Seems like this behavior of Parameterized could be | |
125 | ### confusing, so maybe mention it in documentation somewhere. | |
126 | TestPO.notinst[1]=7 | |
127 | # (if you thought your instPO was completely an independent object, you | |
128 | # might be expecting [1,2,3] here) | |
129 | self.assertEqual(anothertestpo.instPO.notinst,[1,7,3]) | |
130 | ||
131 | ||
132 | def test_instantiation_inheritance(self): | |
133 | """Check that instantiate=True is always inherited (SF.net #2483932).""" | |
134 | t = TestParamInstantiation() | |
135 | assert t.param.params('instPO').instantiate is True | |
136 | assert isinstance(t.instPO,AnotherTestPO) | |
137 | ||
138 | ||
139 | def test_abstract_class(self): | |
140 | """Check that a class declared abstract actually shows up as abstract.""" | |
141 | self.assertEqual(TestAbstractPO.abstract,True) | |
142 | self.assertEqual(TestPO.abstract,False) | |
143 | ||
144 | ||
145 | def test_override_class_param_validation(self): | |
146 | test = TestPOValidation() | |
147 | test.param.value.bounds = (0, 3) | |
148 | with self.assertRaises(ValueError): | |
149 | test.value = 4 | |
150 | TestPOValidation.value = 4 | |
151 | ||
152 | ||
153 | def test_remove_class_param_validation(self): | |
154 | test = TestPOValidation() | |
155 | test.param.value.bounds = None | |
156 | test.value = 20 | |
157 | with self.assertRaises(ValueError): | |
158 | TestPOValidation.value = 10 | |
159 | ||
160 | ||
161 | def test_params(self): | |
162 | """Basic tests of params() method.""" | |
163 | ||
164 | # CB: test not so good because it requires changes if params | |
165 | # of PO are changed | |
166 | assert 'name' in param.Parameterized.param.params() | |
167 | assert len(param.Parameterized.param.params()) in [1,2] | |
168 | ||
169 | ## check for bug where subclass Parameters were not showing up | |
170 | ## if params() already called on a super class. | |
171 | assert 'inst' in TestPO.param.params() | |
172 | assert 'notinst' in TestPO.param.params() | |
173 | ||
174 | ## check caching | |
175 | assert param.Parameterized.param.params() is param.Parameterized().param.params(), "Results of params() should be cached." # just for performance reasons | |
176 | ||
177 | ||
178 | def test_param_iterator(self): | |
179 | self.assertEqual(set(TestPO.param), {'name', 'inst', 'notinst', 'const', 'dyn', | |
180 | 'ro', 'ro2', 'ro_label', 'ro_format'}) | |
181 | ||
182 | ||
183 | def test_param_contains(self): | |
184 | for p in ['name', 'inst', 'notinst', 'const', 'dyn', 'ro', 'ro2']: | |
185 | self.assertIn(p, TestPO.param) | |
186 | ||
187 | ||
188 | def test_class_param_objects(self): | |
189 | objects = TestPO.param.objects() | |
190 | ||
191 | self.assertEqual(set(objects), | |
192 | {'name', 'inst', 'notinst', 'const', 'dyn', | |
193 | 'ro', 'ro2', 'ro_label', 'ro_format'}) | |
194 | ||
195 | # Check caching | |
196 | assert TestPO.param.objects() is objects | |
197 | ||
198 | ||
199 | def test_instance_param_objects(self): | |
200 | inst = TestPO() | |
201 | objects = inst.param.objects() | |
202 | ||
203 | for p, obj in objects.items(): | |
204 | if p == 'notinst': | |
205 | assert obj is TestPO.param[p] | |
206 | else: | |
207 | assert obj is not TestPO.param[p] | |
208 | ||
209 | ||
210 | def test_instance_param_objects_set_to_false(self): | |
211 | inst = TestPO() | |
212 | objects = inst.param.objects(instance=False) | |
213 | ||
214 | for p, obj in objects.items(): | |
215 | assert obj is TestPO.param[p] | |
216 | ||
217 | ||
218 | def test_instance_param_objects_set_to_current(self): | |
219 | inst = TestPO() | |
220 | inst_param = inst.param.inst | |
221 | objects = inst.param.objects(instance='existing') | |
222 | ||
223 | for p, obj in objects.items(): | |
224 | if p == 'inst': | |
225 | assert obj is inst_param | |
226 | else: | |
227 | assert obj is TestPO.param[p] | |
228 | ||
229 | ||
230 | def test_instance_param_objects_warn_on_params(self): | |
231 | inst = TestPO() | |
232 | inst.param['inst'] | |
233 | ||
234 | inst.param.params() | |
235 | self.log_handler.assertContains( | |
236 | 'WARNING', 'The Parameterized instance has instance parameters') | |
237 | ||
238 | ||
239 | def test_instance_param_getitem(self): | |
240 | test = TestPO() | |
241 | assert test.param['inst'] is not TestPO.param['inst'] | |
242 | ||
243 | ||
244 | def test_instance_param_getitem_not_per_instance(self): | |
245 | test = TestPO() | |
246 | assert test.param['notinst'] is TestPO.param['notinst'] | |
247 | ||
248 | ||
249 | def test_instance_param_getitem_no_instance_params(self): | |
250 | test = TestPONoInstance() | |
251 | assert test.param['inst'] is TestPO.param['inst'] | |
252 | ||
253 | ||
254 | def test_instance_param_getattr(self): | |
255 | test = TestPO() | |
256 | assert test.param.inst is not TestPO.param.inst | |
257 | ||
258 | # Assert no deep copy | |
259 | assert test.param.inst.default is TestPO.param.inst.default | |
260 | ||
261 | ||
262 | def test_pprint_instance_params(self): | |
263 | # Ensure pprint does not make instance parameter copies | |
264 | test = TestPO() | |
265 | test.pprint() | |
266 | for p, obj in TestPO.param.objects('current').items(): | |
267 | assert obj is TestPO.param[p] | |
268 | ||
269 | ||
270 | def test_set_param_instance_params(self): | |
271 | # Ensure set_param does not make instance parameter copies | |
272 | test = TestPO() | |
273 | test.param.set_param(inst=3) | |
274 | for p, obj in TestPO.param.objects('current').items(): | |
275 | assert obj is TestPO.param[p] | |
276 | ||
277 | ||
278 | def test_get_param_values_instance_params(self): | |
279 | # Ensure get_param_values does not make instance parameter copies | |
280 | test = TestPO() | |
281 | test.param.get_param_values() | |
282 | for p, obj in TestPO.param.objects('current').items(): | |
283 | assert obj is TestPO.param[p] | |
284 | ||
285 | ||
286 | def test_defaults_instance_params(self): | |
287 | # Ensure get_param_values does not make instance parameter copies | |
288 | test = TestPO() | |
289 | test.param.defaults() | |
290 | for p, obj in TestPO.param.objects('current').items(): | |
291 | assert obj is TestPO.param[p] | |
292 | ||
293 | ||
294 | def test_state_saving(self): | |
295 | t = TestPO(dyn=_SomeRandomNumbers()) | |
296 | g = t.param.get_value_generator('dyn') | |
297 | g._Dynamic_time_fn=None | |
298 | assert t.dyn!=t.dyn | |
299 | orig = t.dyn | |
300 | t.state_push() | |
301 | t.dyn | |
302 | assert t.param.inspect_value('dyn')!=orig | |
303 | t.state_pop() | |
304 | assert t.param.inspect_value('dyn')==orig | |
305 | ||
306 | ||
307 | def test_label(self): | |
308 | t = TestPO() | |
309 | assert t.param.params('ro_label').label == 'Ro Label' | |
310 | ||
311 | def test_label_set(self): | |
312 | t = TestPO() | |
313 | assert t.param.params('ro_label').label == 'Ro Label' | |
314 | t.param.params('ro_label').label = 'Ro relabeled' | |
315 | assert t.param.params('ro_label').label == 'Ro relabeled' | |
316 | ||
317 | def test_label_default_format(self): | |
318 | t = TestPO() | |
319 | assert t.param.params('ro_format').label == 'Ro format' | |
320 | ||
321 | ||
322 | def test_label_custom_format(self): | |
323 | param.parameterized.label_formatter = default_label_formatter.instance(capitalize=False) | |
324 | t = TestPO() | |
325 | assert t.param.params('ro_format').label == 'ro format' | |
326 | param.parameterized.label_formatter = default_label_formatter | |
327 | ||
328 | def test_label_constant_format(self): | |
329 | param.parameterized.label_formatter = lambda x: 'Foo' | |
330 | t = TestPO() | |
331 | assert t.param.params('ro_format').label == 'Foo' | |
332 | param.parameterized.label_formatter = default_label_formatter | |
333 | ||
334 | ||
335 | from param import parameterized | |
336 | ||
337 | @nottest | |
338 | class some_fn(param.ParameterizedFunction): | |
339 | num_phase = param.Number(18) | |
340 | frequencies = param.List([99]) | |
341 | scale = param.Number(0.3) | |
342 | ||
343 | def __call__(self,**params_to_override): | |
344 | params = parameterized.ParamOverrides(self,params_to_override) | |
345 | num_phase = params['num_phase'] | |
346 | frequencies = params['frequencies'] | |
347 | scale = params['scale'] | |
348 | return scale,num_phase,frequencies | |
349 | ||
350 | instance = some_fn.instance() | |
351 | ||
352 | @istest | |
353 | class TestParameterizedFunction(API1TestCase): | |
354 | ||
355 | def _basic_tests(self,fn): | |
356 | self.assertEqual(fn(),(0.3,18,[99])) | |
357 | self.assertEqual(fn(frequencies=[1,2,3]),(0.3,18,[1,2,3])) | |
358 | self.assertEqual(fn(),(0.3,18,[99])) | |
359 | ||
360 | fn.frequencies=[10,20,30] | |
361 | self.assertEqual(fn(frequencies=[1,2,3]),(0.3,18,[1,2,3])) | |
362 | self.assertEqual(fn(),(0.3,18,[10,20,30])) | |
363 | ||
364 | def test_parameterized_function(self): | |
365 | self._basic_tests(some_fn) | |
366 | ||
367 | def test_parameterized_function_instance(self): | |
368 | self._basic_tests(instance) | |
369 | ||
370 | def test_pickle_instance(self): | |
371 | import pickle | |
372 | s = pickle.dumps(instance) | |
373 | instance.scale=0.8 | |
374 | i = pickle.loads(s) | |
375 | self.assertEqual(i(),(0.3,18,[10,20,30])) | |
376 | ||
377 | ||
378 | @nottest | |
379 | class TestPO1(param.Parameterized): | |
380 | x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1)) | |
381 | y = param.Number(default=1,bounds=(-1,1)) | |
382 | ||
383 | @istest | |
384 | class TestNumberParameter(API1TestCase): | |
385 | ||
386 | def test_outside_bounds(self): | |
387 | t1 = TestPO1() | |
388 | # Test bounds (non-dynamic number) | |
389 | try: | |
390 | t1.y = 10 | |
391 | except ValueError: | |
392 | pass | |
393 | else: | |
394 | assert False, "Should raise ValueError." | |
395 | ||
396 | def test_outside_bounds_numbergen(self): | |
397 | t1 = TestPO1() | |
398 | # Test bounds (dynamic number) | |
399 | t1.x = numbergen.UniformRandom(lbound=2,ubound=3) # bounds not checked on set | |
400 | try: | |
401 | t1.x | |
402 | except ValueError: | |
403 | pass | |
404 | else: | |
405 | assert False, "Should raise ValueError." | |
406 | ||
407 | ||
408 | @istest | |
409 | class TestStringParameter(API1TestCase): | |
410 | ||
411 | def setUp(self): | |
412 | super(TestStringParameter, self).setUp() | |
413 | ||
414 | class TestString(param.Parameterized): | |
415 | a = param.String() | |
416 | b = param.String(default='',allow_None=True) | |
417 | c = param.String(default=None) | |
418 | ||
419 | self._TestString = TestString | |
420 | ||
421 | def test_handling_of_None(self): | |
422 | t = self._TestString() | |
423 | ||
424 | with self.assertRaises(ValueError): | |
425 | t.a = None | |
426 | ||
427 | t.b = None | |
428 | ||
429 | assert t.c is None | |
430 | ||
431 | ||
432 | @istest | |
433 | class TestParameterizedUtilities(API1TestCase): | |
434 | ||
435 | def setUp(self): | |
436 | super(TestParameterizedUtilities, self).setUp() | |
437 | ||
438 | ||
439 | def test_default_label_formatter(self): | |
440 | assert default_label_formatter('a_b_C') == 'A b C' | |
441 | ||
442 | ||
443 | def test_default_label_formatter_not_capitalized(self): | |
444 | assert default_label_formatter.instance(capitalize=False)('a_b_C') == 'a b C' | |
445 | ||
446 | ||
447 | def test_default_label_formatter_not_replace_underscores(self): | |
448 | assert default_label_formatter.instance(replace_underscores=False)('a_b_C') == 'A_b_C' | |
449 | def test_default_label_formatter_overrides(self): | |
450 | assert default_label_formatter.instance(overrides={'a': 'b'})('a') == 'b' | |
451 | ||
452 | @istest | |
453 | class TestParamOverrides(API1TestCase): | |
454 | ||
455 | def setUp(self): | |
456 | super(TestParamOverrides, self).setUp() | |
457 | self.po = param.Parameterized(name='A',print_level=0) | |
458 | ||
459 | def test_init_name(self): | |
460 | self.assertEqual(self.po.name, 'A') | |
461 | ||
462 | def test_simple_override(self): | |
463 | overrides = ParamOverrides(self.po,{'name':'B'}) | |
464 | self.assertEqual(overrides['name'], 'B') | |
465 | self.assertEqual(overrides['print_level'], 0) | |
466 | ||
467 | # CEBALERT: missing test for allow_extra_keywords (e.g. getting a | |
468 | # warning on attempting to override non-existent parameter when | |
469 | # allow_extra_keywords is False) | |
470 | ||
471 | def test_missing_key(self): | |
472 | overrides = ParamOverrides(self.po,{'name':'B'}) | |
473 | with self.assertRaises(AttributeError): | |
474 | overrides['doesnotexist'] | |
475 | ||
476 | ||
477 | class TestSharedParameters(API1TestCase): | |
478 | ||
479 | def setUp(self): | |
480 | super(TestSharedParameters, self).setUp() | |
481 | with shared_parameters(): | |
482 | self.p1 = TestPO(name='A', print_level=0) | |
483 | self.p2 = TestPO(name='B', print_level=0) | |
484 | self.ap1 = AnotherTestPO(name='A', print_level=0) | |
485 | self.ap2 = AnotherTestPO(name='B', print_level=0) | |
486 | ||
487 | def test_shared_object(self): | |
488 | self.assertTrue(self.ap1.instPO is self.ap2.instPO) | |
489 | self.assertTrue(self.ap1.param.params('instPO').default is not self.ap2.instPO) | |
490 | ||
491 | def test_shared_list(self): | |
492 | self.assertTrue(self.p1.inst is self.p2.inst) | |
493 | self.assertTrue(self.p1.param.params('inst').default is not self.p2.inst) | |
494 | ||
495 | ||
496 | ||
497 | if __name__ == "__main__": | |
498 | import nose | |
499 | nose.runmodule() |
0 | """ | |
1 | Unit test for the repr and pprint of parameterized objects. | |
2 | """ | |
3 | ||
4 | import param | |
5 | from . import API1TestCase | |
6 | ||
7 | ||
8 | class TestParameterizedRepr(API1TestCase): | |
9 | ||
10 | def setUp(self): | |
11 | super(TestParameterizedRepr, self).setUp() | |
12 | # initialize a parameterized class | |
13 | class A(param.Parameterized): | |
14 | a = param.Number(4, precedence=-5) | |
15 | b = param.String('B', precedence=-4) | |
16 | c = param.Number(4, precedence=0) | |
17 | d = param.Integer(-22, precedence=1) | |
18 | ||
19 | x = param.Number(1, precedence=2) | |
20 | y = param.Number(2, precedence=-1) | |
21 | z = param.Number(3, precedence=-2) | |
22 | def __init__(self, a, b, c=4, d=-22, **kwargs): | |
23 | super(A, self).__init__(a=a, b=b, c=c, **kwargs) | |
24 | ||
25 | self.A = A | |
26 | ||
27 | class B(param.Parameterized): # Similar to A but no **kwargs | |
28 | a = param.Number(4, precedence=-5) | |
29 | b = param.String('B', precedence=-4) | |
30 | c = param.Number(4, precedence=0) | |
31 | d = param.Integer(-22, precedence=1) | |
32 | ||
33 | x = param.Number(1, precedence=2) | |
34 | def __init__(self, a, b, c=4, d=-22): | |
35 | super(B, self).__init__(a=a, b=b, c=c, name='ClassB') | |
36 | ||
37 | self.B = B | |
38 | ||
39 | class C(param.Parameterized): # Similar to A but with *varargs | |
40 | a = param.Number(4, precedence=-5) | |
41 | b = param.String('B', precedence=-4) | |
42 | c = param.Number(4, precedence=0) | |
43 | d = param.Integer(-22, precedence=1) | |
44 | ||
45 | x = param.Number(1, precedence=2) | |
46 | y = param.Number(2, precedence=-1) | |
47 | z = param.Number(3, precedence=-2) | |
48 | ||
49 | def __init__(self, a, b, c=4, d=-22, *varargs, **kwargs): | |
50 | super(C, self).__init__(a=a, b=b, c=c, **kwargs) | |
51 | ||
52 | self.C = C | |
53 | ||
54 | ||
55 | class D(param.Parameterized): # Similar to A but with missing parameters | |
56 | a = param.Number(4, precedence=-5) | |
57 | b = param.String('B', precedence=-4) | |
58 | ||
59 | def __init__(self, a, b, c=4, d=-22, **kwargs): | |
60 | super(D, self).__init__(a=a, b=b, **kwargs) | |
61 | ||
62 | self.D = D | |
63 | ||
64 | ||
65 | # More realistically, positional args are not params | |
66 | class E(param.Parameterized): | |
67 | a = param.Number(4, precedence=-5) | |
68 | ||
69 | def __init__(self, p, q=4, **params): # (plus non-param kw too) | |
70 | super(E, self).__init__(**params) | |
71 | ||
72 | self.E = E | |
73 | ||
74 | ||
75 | def testparameterizedrepr(self): | |
76 | obj = self.A(4,'B', name='test1') | |
77 | self.assertEqual(repr(obj), | |
78 | "A(a=4, b='B', c=4, d=-22, name='test1', x=1, y=2, z=3)") | |
79 | ||
80 | def testparameterizedscriptrepr1(self): | |
81 | obj = self.A(4,'B', name='test') | |
82 | self.assertEqual(obj.pprint(), | |
83 | "A(4, 'B', name='test')") | |
84 | ||
85 | def testparameterizedscriptrepr2(self): | |
86 | obj = self.A(4,'B', c=5, name='test') | |
87 | self.assertEqual(obj.pprint(), | |
88 | "A(4, 'B', c=5, name='test')") | |
89 | ||
90 | def testparameterizedscriptrepr3(self): | |
91 | obj = self.A(4,'B', c=5, x=True, name='test') | |
92 | self.assertEqual(obj.pprint(), | |
93 | "A(4, 'B', c=5, name='test')") | |
94 | ||
95 | def testparameterizedscriptrepr4(self): | |
96 | obj = self.A(4,'B', c=5, x=10, name='test') | |
97 | self.assertEqual(obj.pprint(), | |
98 | "A(4, 'B', c=5, name='test', x=10)") | |
99 | ||
100 | ||
101 | def testparameterizedscriptrepr5(self): | |
102 | obj = self.A(4,'B', x=10, y=11, z=12, name='test') | |
103 | self.assertEqual(obj.pprint(), | |
104 | "A(4, 'B', name='test', z=12, y=11, x=10)") | |
105 | ||
106 | def testparameterizedscriptrepr_nokwargs(self): | |
107 | obj = self.B(4,'B', c=99) | |
108 | obj.x = 10 # Modified but not passable through constructor | |
109 | self.assertEqual(obj.pprint(), | |
110 | "B(4, 'B', c=99)") | |
111 | ||
112 | def testparameterizedscriptrepr_varags(self): | |
113 | obj = self.C(4,'C', c=99) | |
114 | self.assertEqual(obj.pprint(), | |
115 | "C(4, 'C', c=99, **varargs)") | |
116 | ||
117 | def testparameterizedscriptrepr_varags_kwargs(self): | |
118 | obj = self.C(4,'C', c=99, x=10, y=11, z=12) | |
119 | self.assertEqual(obj.pprint(), | |
120 | "C(4, 'C', c=99, z=12, y=11, x=10, **varargs)") | |
121 | ||
122 | def testparameterizedscriptrepr_missing_values(self): | |
123 | obj = self.D(4,'D', c=99) | |
124 | self.assertEqual(obj.pprint(), | |
125 | "D(4, 'D', c=<?>, d=<?>)") | |
126 | ||
127 | def testparameterizedscriptrepr_nonparams(self): | |
128 | obj = self.E(10,q='hi', a=99) | |
129 | self.assertEqual(obj.pprint(), | |
130 | "E(<?>, q=<?>, a=99)") | |
131 | ||
132 | def test_exceptions(self): | |
133 | obj = self.E(10,q='hi',a=99) | |
134 | try: | |
135 | obj.pprint(unknown_value=False) | |
136 | except Exception: | |
137 | pass | |
138 | else: | |
139 | raise AssertionError | |
140 | ||
141 | def test_suppression(self): | |
142 | obj = self.E(10,q='hi',a=99) | |
143 | self.assertEqual(obj.pprint(unknown_value=None), | |
144 | "E(a=99)") | |
145 | ||
146 | def test_imports_deduplication(self): | |
147 | obj = self.E(10,q='hi', a=99) | |
148 | imports = ['import me','import me'] | |
149 | obj.pprint(imports=imports) | |
150 | self.assertEqual(imports.count('import me'),1) | |
151 | ||
152 | def test_qualify(self): | |
153 | obj = self.E(10,q='hi', a=99) | |
154 | ||
155 | r = "E(<?>, q=<?>, a=99)" | |
156 | self.assertEqual(obj.pprint(qualify=False), | |
157 | r) | |
158 | ||
159 | self.assertEqual(obj.pprint(qualify=True), | |
160 | "tests.API1.testparameterizedrepr."+r) | |
161 | ||
162 | ||
163 | ||
164 | if __name__ == "__main__": | |
165 | import nose | |
166 | nose.runmodule() |
0 | """ | |
1 | Unit test for param.output. | |
2 | """ | |
3 | import sys | |
4 | ||
5 | from unittest import SkipTest | |
6 | ||
7 | import param | |
8 | ||
9 | from . import API1TestCase | |
10 | ||
11 | ||
12 | class TestParamDepends(API1TestCase): | |
13 | ||
14 | def test_simple_output(self): | |
15 | class P(param.Parameterized): | |
16 | ||
17 | @param.output() | |
18 | def single_output(self): | |
19 | return 1 | |
20 | ||
21 | p = P() | |
22 | outputs = p.param.outputs() | |
23 | self.assertEqual(list(outputs), ['single_output']) | |
24 | ||
25 | otype, method, idx = outputs['single_output'] | |
26 | self.assertIs(type(otype), param.Parameter) | |
27 | self.assertEqual(method, p.single_output) | |
28 | self.assertEqual(idx, None) | |
29 | ||
30 | def test_subclass_output(self): | |
31 | class A(param.Parameterized): | |
32 | ||
33 | @param.output() | |
34 | def single_output(self): | |
35 | return 1 | |
36 | ||
37 | class B(param.Parameterized): | |
38 | ||
39 | @param.output() | |
40 | def another_output(self): | |
41 | return 2 | |
42 | ||
43 | class C(A, B): | |
44 | pass | |
45 | ||
46 | p = C() | |
47 | outputs = p.param.outputs() | |
48 | self.assertEqual(sorted(outputs), ['another_output', 'single_output']) | |
49 | ||
50 | otype, method, idx = outputs['single_output'] | |
51 | self.assertIs(type(otype), param.Parameter) | |
52 | self.assertEqual(method, p.single_output) | |
53 | self.assertEqual(idx, None) | |
54 | ||
55 | otype, method, idx = outputs['another_output'] | |
56 | self.assertIs(type(otype), param.Parameter) | |
57 | self.assertEqual(method, p.another_output) | |
58 | self.assertEqual(idx, None) | |
59 | ||
60 | ||
61 | def test_named_kwarg_output(self): | |
62 | class P(param.Parameterized): | |
63 | ||
64 | @param.output(value=param.Integer) | |
65 | def single_output(self): | |
66 | return 1 | |
67 | ||
68 | p = P() | |
69 | outputs = p.param.outputs() | |
70 | self.assertEqual(list(outputs), ['value']) | |
71 | ||
72 | otype, method, idx = outputs['value'] | |
73 | self.assertIs(type(otype), param.Integer) | |
74 | self.assertEqual(method, p.single_output) | |
75 | self.assertEqual(idx, None) | |
76 | ||
77 | def test_named_and_typed_arg_output(self): | |
78 | class P(param.Parameterized): | |
79 | ||
80 | @param.output(('value', param.Integer)) | |
81 | def single_output(self): | |
82 | return 1 | |
83 | ||
84 | p = P() | |
85 | outputs = p.param.outputs() | |
86 | self.assertEqual(list(outputs), ['value']) | |
87 | ||
88 | otype, method, idx = outputs['value'] | |
89 | self.assertIs(type(otype), param.Integer) | |
90 | self.assertEqual(method, p.single_output) | |
91 | self.assertEqual(idx, None) | |
92 | ||
93 | def test_named_arg_output(self): | |
94 | class P(param.Parameterized): | |
95 | ||
96 | @param.output('value') | |
97 | def single_output(self): | |
98 | return 1 | |
99 | ||
100 | p = P() | |
101 | outputs = p.param.outputs() | |
102 | self.assertEqual(list(outputs), ['value']) | |
103 | ||
104 | otype, method, idx = outputs['value'] | |
105 | self.assertIs(type(otype), param.Parameter) | |
106 | self.assertEqual(method, p.single_output) | |
107 | self.assertEqual(idx, None) | |
108 | ||
109 | def test_typed_arg_output(self): | |
110 | class P(param.Parameterized): | |
111 | ||
112 | @param.output(int) | |
113 | def single_output(self): | |
114 | return 1 | |
115 | ||
116 | p = P() | |
117 | outputs = p.param.outputs() | |
118 | self.assertEqual(list(outputs), ['single_output']) | |
119 | ||
120 | otype, method, idx = outputs['single_output'] | |
121 | self.assertIs(type(otype), param.ClassSelector) | |
122 | self.assertIs(otype.class_, int) | |
123 | self.assertEqual(method, p.single_output) | |
124 | self.assertEqual(idx, None) | |
125 | ||
126 | def test_multiple_named_kwarg_output(self): | |
127 | py_major = sys.version_info.major | |
128 | py_minor = sys.version_info.minor | |
129 | if (py_major < 3 or (py_major == 3 and py_minor < 6)): | |
130 | raise SkipTest('Multiple keyword output declarations only ' | |
131 | 'supported in Python >= 3.6, skipping test.') | |
132 | ||
133 | class P(param.Parameterized): | |
134 | ||
135 | @param.output(value=param.Integer, value2=param.String) | |
136 | def multi_output(self): | |
137 | return (1, 'string') | |
138 | ||
139 | p = P() | |
140 | outputs = p.param.outputs() | |
141 | self.assertEqual(set(outputs), {'value', 'value2'}) | |
142 | ||
143 | otype, method, idx = outputs['value'] | |
144 | self.assertIs(type(otype), param.Integer) | |
145 | self.assertEqual(method, p.multi_output) | |
146 | self.assertEqual(idx, 0) | |
147 | ||
148 | otype, method, idx = outputs['value2'] | |
149 | self.assertIs(type(otype), param.String) | |
150 | self.assertEqual(method, p.multi_output) | |
151 | self.assertEqual(idx, 1) | |
152 | ||
153 | def test_multi_named_and_typed_arg_output(self): | |
154 | class P(param.Parameterized): | |
155 | ||
156 | @param.output(('value', param.Integer), ('value2', param.String)) | |
157 | def multi_output(self): | |
158 | return (1, 'string') | |
159 | ||
160 | p = P() | |
161 | outputs = p.param.outputs() | |
162 | self.assertEqual(set(outputs), {'value', 'value2'}) | |
163 | otype, method, idx = outputs['value'] | |
164 | self.assertIs(type(otype), param.Integer) | |
165 | self.assertEqual(method, p.multi_output) | |
166 | self.assertEqual(idx, 0) | |
167 | ||
168 | otype, method, idx = outputs['value2'] | |
169 | self.assertIs(type(otype), param.String) | |
170 | self.assertEqual(method, p.multi_output) | |
171 | self.assertEqual(idx, 1) | |
172 | ||
173 | def test_multi_named_arg_output(self): | |
174 | class P(param.Parameterized): | |
175 | ||
176 | @param.output('value', 'value2') | |
177 | def multi_output(self): | |
178 | return (1, 2) | |
179 | ||
180 | p = P() | |
181 | outputs = p.param.outputs() | |
182 | self.assertEqual(set(outputs), {'value', 'value2'}) | |
183 | ||
184 | otype, method, idx = outputs['value'] | |
185 | self.assertIs(type(otype), param.Parameter) | |
186 | self.assertEqual(method, p.multi_output) | |
187 | self.assertEqual(idx, 0) | |
188 | ||
189 | otype, method, idx = outputs['value2'] | |
190 | self.assertIs(type(otype), param.Parameter) | |
191 | self.assertEqual(method, p.multi_output) | |
192 | self.assertEqual(idx, 1) | |
193 | ||
194 | def test_multi_typed_arg_output(self): | |
195 | with self.assertRaises(ValueError): | |
196 | class P(param.Parameterized): | |
197 | ||
198 | @param.output(int, str) | |
199 | def single_output(self): | |
200 | return 1 | |
201 | ||
202 | def test_multi_method_named_and_typed_arg_output(self): | |
203 | class P(param.Parameterized): | |
204 | ||
205 | @param.output(('value', param.Integer), ('value2', str)) | |
206 | def multi_output(self): | |
207 | return (1, 'string') | |
208 | ||
209 | @param.output(('value3', param.Number)) | |
210 | def single_output(self): | |
211 | return 3.0 | |
212 | ||
213 | p = P() | |
214 | outputs = p.param.outputs() | |
215 | self.assertEqual(set(outputs), {'value', 'value2', 'value3'}) | |
216 | ||
217 | otype, method, idx = outputs['value'] | |
218 | self.assertIs(type(otype), param.Integer) | |
219 | self.assertEqual(method, p.multi_output) | |
220 | self.assertEqual(idx, 0) | |
221 | ||
222 | otype, method, idx = outputs['value2'] | |
223 | self.assertIs(type(otype), param.ClassSelector) | |
224 | self.assertIs(otype.class_, str) | |
225 | self.assertEqual(method, p.multi_output) | |
226 | self.assertEqual(idx, 1) | |
227 | ||
228 | otype, method, idx = outputs['value3'] | |
229 | self.assertIs(type(otype), param.Number) | |
230 | self.assertEqual(method, p.single_output) | |
231 | self.assertEqual(idx, None) |
0 | """ | |
1 | UnitTest for param_union helper | |
2 | """ | |
3 | ||
4 | import logging | |
5 | import param | |
6 | from . import API1TestCase | |
7 | ||
8 | class MyHandler(logging.StreamHandler): | |
9 | ||
10 | def __init__(self): | |
11 | super(MyHandler, self).__init__() | |
12 | self.records = [] | |
13 | ||
14 | def emit(self, record): | |
15 | self.records.append(record) | |
16 | ||
17 | class TestParamUnion(API1TestCase): | |
18 | ||
19 | def setUp(self): | |
20 | self.logger = param.get_logger() | |
21 | self.handler = MyHandler() | |
22 | self.logger.addHandler(self.handler) | |
23 | ||
24 | def tearDown(self): | |
25 | self.logger.removeHandler(self.handler) | |
26 | ||
27 | def test_param_union_values(self): | |
28 | class A(param.Parameterized): | |
29 | a = param.Number(1) | |
30 | class B(param.Parameterized): | |
31 | b = param.Number(2) | |
32 | class C(A, B): | |
33 | pass | |
34 | a = A() | |
35 | a.a = 10 | |
36 | b = B() | |
37 | b.b = 5 | |
38 | c_1 = C(**param.param_union(a)) | |
39 | self.assertTrue(c_1.a == 10 and c_1.b == 2) | |
40 | c_2 = C(**param.param_union(b)) | |
41 | self.assertTrue(c_2.a == 1 and c_2.b == 5) | |
42 | c_3 = C(**param.param_union(a, b)) | |
43 | self.assertTrue(c_3.a == 10 and c_3.b == 5) | |
44 | c_4 = C(**param.param_union()) | |
45 | self.assertTrue(c_4.a == 1 and c_4.b == 2) | |
46 | ||
47 | def test_param_union_warnings(self): | |
48 | class A(param.Parameterized): | |
49 | a = param.Number(1) | |
50 | a = A() | |
51 | A(**param.param_union(a)) | |
52 | self.assertFalse(self.handler.records) | |
53 | A(**param.param_union()) | |
54 | self.assertFalse(self.handler.records) | |
55 | A(**param.param_union(a, a)) | |
56 | self.assertTrue(self.handler.records) | |
57 | self.handler.records.pop() | |
58 | A(**param.param_union(a, a, warn=False)) | |
59 | self.assertFalse(self.handler.records) | |
60 | ||
61 | def test_param_union_raises_on_unexpected_kwarg(self): | |
62 | with self.assertRaises(TypeError): | |
63 | param.param_union(dumbdumbface=True) |
0 | """ | |
1 | Unit test for Range parameters. | |
2 | """ | |
3 | import param | |
4 | from . import API1TestCase | |
5 | ||
6 | ||
7 | class TestRangeParameters(API1TestCase): | |
8 | ||
9 | def test_initialization_out_of_bounds(self): | |
10 | try: | |
11 | class Q(param.Parameterized): | |
12 | q = param.Range((0, 2), bounds=(0, 1)) | |
13 | except ValueError: | |
14 | pass | |
15 | else: | |
16 | raise AssertionError("No exception raised on out-of-bounds date") | |
17 | ||
18 | def test_set_exclusive_out_of_bounds_upper(self): | |
19 | class Q(param.Parameterized): | |
20 | q = param.Range(bounds=(0, 10), inclusive_bounds=(True, False)) | |
21 | try: | |
22 | Q.q = (0, 10) | |
23 | except ValueError: | |
24 | pass | |
25 | else: | |
26 | raise AssertionError("No exception raised on out-of-bounds date") | |
27 | ||
28 | def test_set_exclusive_out_of_bounds_lower(self): | |
29 | class Q(param.Parameterized): | |
30 | q = param.Range(bounds=(0, 10), inclusive_bounds=(False, True)) | |
31 | try: | |
32 | Q.q = (0, 10) | |
33 | except ValueError: | |
34 | pass | |
35 | else: | |
36 | raise AssertionError("No exception raised on out-of-bounds date") | |
37 | ||
38 | def test_set_out_of_bounds(self): | |
39 | class Q(param.Parameterized): | |
40 | q = param.Range(bounds=(0, 10)) | |
41 | try: | |
42 | Q.q = (5, 11) | |
43 | except ValueError: | |
44 | pass | |
45 | else: | |
46 | raise AssertionError("No exception raised on out-of-bounds date") | |
47 | ||
48 | def test_get_soft_bounds(self): | |
49 | q = param.Range((1,3), bounds=(0, 10), softbounds=(1, 9)) | |
50 | self.assertEqual(q.get_soft_bounds(), (1, 9)) | |
51 |
0 | """ | |
1 | Unit test for object selector parameters. | |
2 | ||
3 | Originally implemented as doctests in Topographica in the file | |
4 | testEnumerationParameter.txt | |
5 | """ | |
6 | ||
7 | import param | |
8 | from . import API1TestCase | |
9 | from collections import OrderedDict | |
10 | ||
11 | ||
12 | opts=dict(A=[1,2],B=[3,4],C=dict(a=1,b=2)) | |
13 | ||
14 | ||
15 | class TestSelectorParameters(API1TestCase): | |
16 | ||
17 | def setUp(self): | |
18 | super(TestSelectorParameters, self).setUp() | |
19 | class P(param.Parameterized): | |
20 | e = param.Selector([5,6,7]) | |
21 | f = param.Selector(default=10) | |
22 | h = param.Selector(default=None) | |
23 | g = param.Selector([7,8]) | |
24 | i = param.Selector([9],default=7, check_on_set=False) | |
25 | s = param.Selector(OrderedDict(one=1,two=2,three=3), default=3) | |
26 | d = param.Selector(opts, default=opts['B']) | |
27 | ||
28 | self.P = P | |
29 | ||
30 | def test_set_object_constructor(self): | |
31 | p = self.P(e=6) | |
32 | self.assertEqual(p.e, 6) | |
33 | ||
34 | def test_get_range_list(self): | |
35 | r = self.P.param.params("g").get_range() | |
36 | self.assertEqual(r['7'],7) | |
37 | self.assertEqual(r['8'],8) | |
38 | ||
39 | def test_get_range_dict(self): | |
40 | r = self.P.param.params("s").get_range() | |
41 | self.assertEqual(r['one'],1) | |
42 | self.assertEqual(r['two'],2) | |
43 | ||
44 | def test_get_range_mutable(self): | |
45 | r = self.P.param.params("d").get_range() | |
46 | self.assertEqual(r['A'],opts['A']) | |
47 | self.assertEqual(r['C'],opts['C']) | |
48 | self.d=opts['A'] | |
49 | self.d=opts['C'] | |
50 | self.d=opts['B'] | |
51 | ||
52 | def test_set_object_outside_bounds(self): | |
53 | p = self.P(e=6) | |
54 | try: | |
55 | p.e = 9 | |
56 | except ValueError: | |
57 | pass | |
58 | else: | |
59 | raise AssertionError("Object set outside range.") | |
60 | ||
61 | def test_set_object_setattr(self): | |
62 | p = self.P(e=6) | |
63 | p.f = 9 | |
64 | self.assertEqual(p.f, 9) | |
65 | p.g = 7 | |
66 | self.assertEqual(p.g, 7) | |
67 | p.i = 12 | |
68 | self.assertEqual(p.i, 12) | |
69 | ||
70 | ||
71 | def test_set_object_not_None(self): | |
72 | p = self.P(e=6) | |
73 | p.g = 7 | |
74 | try: | |
75 | p.g = None | |
76 | except ValueError: | |
77 | pass | |
78 | else: | |
79 | raise AssertionError("Object set outside range.") | |
80 | ||
81 | def test_set_object_setattr_post_error(self): | |
82 | p = self.P(e=6) | |
83 | p.f = 9 | |
84 | self.assertEqual(p.f, 9) | |
85 | p.g = 7 | |
86 | try: | |
87 | p.g = None | |
88 | except ValueError: | |
89 | pass | |
90 | else: | |
91 | raise AssertionError("Object set outside range.") | |
92 | ||
93 | self.assertEqual(p.g, 7) | |
94 | p.i = 12 | |
95 | self.assertEqual(p.i, 12) | |
96 | ||
97 | def test_initialization_out_of_bounds(self): | |
98 | try: | |
99 | class Q(param.Parameterized): | |
100 | q = param.Selector([4], 5) | |
101 | except ValueError: | |
102 | pass | |
103 | else: | |
104 | raise AssertionError("Selector created outside range.") | |
105 | ||
106 | ||
107 | def test_initialization_no_bounds(self): | |
108 | try: | |
109 | class Q(param.Parameterized): | |
110 | q = param.Selector(10, default=5) | |
111 | except TypeError: | |
112 | pass | |
113 | else: | |
114 | raise AssertionError("Selector created without range.") | |
115 | ||
116 | ||
117 | if __name__ == "__main__": | |
118 | import nose | |
119 | nose.runmodule() |
0 | """ | |
1 | Unit test for String parameters | |
2 | """ | |
3 | from . import API1TestCase | |
4 | ||
5 | import param | |
6 | ||
7 | ||
8 | ip_regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' | |
9 | ||
10 | class TestStringParameters(API1TestCase): | |
11 | ||
12 | def test_regex_ok(self): | |
13 | class A(param.Parameterized): | |
14 | s = param.String('0.0.0.0', ip_regex) | |
15 | ||
16 | a = A() | |
17 | a.s = '123.123.0.1' | |
18 | ||
19 | def test_reject_none(self): | |
20 | class A(param.Parameterized): | |
21 | s = param.String('0.0.0.0', ip_regex) | |
22 | ||
23 | a = A() | |
24 | ||
25 | exception = "String 's' only takes a string value." | |
26 | with self.assertRaisesRegexp(ValueError, exception): | |
27 | a.s = None # because allow_None should be False | |
28 | ||
29 | def test_default_none(self): | |
30 | class A(param.Parameterized): | |
31 | s = param.String(None, ip_regex) | |
32 | ||
33 | a = A() | |
34 | a.s = '123.123.0.1' | |
35 | a.s = None # because allow_None should be True with default of None | |
36 | ||
37 | def test_regex_incorrect(self): | |
38 | ||
39 | class A(param.Parameterized): | |
40 | s = param.String('0.0.0.0', regex=ip_regex) | |
41 | ||
42 | a = A() | |
43 | ||
44 | exception = "String 's': '123.123.0.256' does not match regex" | |
45 | with self.assertRaisesRegexp(ValueError, exception): | |
46 | a.s = '123.123.0.256' | |
47 | ||
48 | def test_regex_incorrect_default(self): | |
49 | ||
50 | exception = "String 'None': '' does not match regex" | |
51 | with self.assertRaisesRegexp(ValueError, exception): | |
52 | class A(param.Parameterized): | |
53 | s = param.String(regex=ip_regex) # default value '' does not match regular expression | |
54 | ||
55 |
0 | """ | |
1 | Unit tests for the param.Time class, time dependent parameters and | |
2 | time-dependent numbergenerators. | |
3 | """ | |
4 | import param | |
5 | import numbergen | |
6 | import copy | |
7 | ||
8 | from . import API1TestCase | |
9 | from nose.plugins.skip import SkipTest | |
10 | import fractions | |
11 | ||
12 | try: | |
13 | import gmpy | |
14 | except: | |
15 | gmpy = None | |
16 | ||
17 | ||
18 | ||
19 | class TestTimeClass(API1TestCase): | |
20 | ||
21 | def test_time_init(self): | |
22 | param.Time() | |
23 | ||
24 | def test_time_init_int(self): | |
25 | t = param.Time(time_type=int) | |
26 | self.assertEqual(t(), 0) | |
27 | ||
28 | def test_time_int_iter(self): | |
29 | t = param.Time(time_type=int) | |
30 | self.assertEqual(next(t), 0) | |
31 | self.assertEqual(next(t), 1) | |
32 | ||
33 | def test_time_init_timestep(self): | |
34 | t = param.Time(time_type=int, timestep=2) | |
35 | self.assertEqual(next(t), 0) | |
36 | self.assertEqual(next(t), 2) | |
37 | ||
38 | def test_time_int_until(self): | |
39 | t = param.Time(time_type=int, until=3) | |
40 | self.assertEqual(next(t), 0) | |
41 | self.assertEqual(next(t), 1) | |
42 | self.assertEqual(next(t), 2) | |
43 | self.assertEqual(next(t), 3) | |
44 | try: | |
45 | self.assertEqual(next(t), 4) | |
46 | raise AssertionError("StopIteration should have been raised") | |
47 | except StopIteration: | |
48 | pass | |
49 | ||
50 | def test_time_int_eq(self): | |
51 | t = param.Time(time_type=int) | |
52 | s = param.Time(time_type=int) | |
53 | t(3); s(3) | |
54 | self.assertEqual(t == s, True) | |
55 | ||
56 | def test_time_int_context(self): | |
57 | t = param.Time(time_type=int) | |
58 | t(3) | |
59 | with t: | |
60 | self.assertEqual(t(), 3) | |
61 | t(5) | |
62 | self.assertEqual(t(), 5) | |
63 | self.assertEqual(t(), 3) | |
64 | ||
65 | def test_time_int_context_iadd(self): | |
66 | ||
67 | with param.Time(time_type=int) as t: | |
68 | self.assertEqual(t(), 0) | |
69 | t += 5 | |
70 | self.assertEqual(t(), 5) | |
71 | self.assertEqual(t(), 0) | |
72 | ||
73 | def test_time_int_change_type(self): | |
74 | t = param.Time(time_type=int) | |
75 | self.assertEqual(t(), 0) | |
76 | t(1, fractions.Fraction) | |
77 | self.assertEqual(t(), 1) | |
78 | self.assertEqual(t.time_type, fractions.Fraction) | |
79 | ||
80 | def test_time_init_gmpy(self): | |
81 | if gmpy is None: raise SkipTest | |
82 | ||
83 | t = param.Time(time_type=gmpy.mpq) | |
84 | self.assertEqual(t(), gmpy.mpq(0)) | |
85 | t.advance(gmpy.mpq(0.25)) | |
86 | self.assertEqual(t(), gmpy.mpq(1,4)) | |
87 | ||
88 | def test_time_init_gmpy_advanced(self): | |
89 | if gmpy is None: raise SkipTest | |
90 | t = param.Time(time_type=gmpy.mpq, | |
91 | timestep=gmpy.mpq(0.25), | |
92 | until=1.5) | |
93 | self.assertEqual(t(), gmpy.mpq(0,1)) | |
94 | t(0.5) | |
95 | self.assertEqual(t(), gmpy.mpq(1,2)) | |
96 | with t: | |
97 | t.advance(0.25) | |
98 | self.assertEqual(t(), gmpy.mpq(3,4)) | |
99 | self.assertEqual(t(), gmpy.mpq(1,2)) | |
100 | tvals = [tval for tval in t] | |
101 | self.assertEqual(tvals, [gmpy.mpq(1,2), | |
102 | gmpy.mpq(3,4), | |
103 | gmpy.mpq(1,1), | |
104 | gmpy.mpq(5,4), | |
105 | gmpy.mpq(3,2)]) | |
106 | ||
107 | ||
108 | class TestTimeDependentDynamic(API1TestCase): | |
109 | ||
110 | def setUp(self): | |
111 | super(TestTimeDependentDynamic, self).setUp() | |
112 | param.Dynamic.time_dependent=None | |
113 | self.time_fn= param.Time(time_type=int) | |
114 | ||
115 | class Incrementer(object): | |
116 | def __init__(self): | |
117 | self.i = -1 | |
118 | def __call__(self): | |
119 | self.i+=1 | |
120 | return self.i | |
121 | ||
122 | self.Incrementer = Incrementer | |
123 | ||
124 | class DynamicClass(param.Parameterized): | |
125 | a = param.Number(default = self.Incrementer()) | |
126 | ||
127 | self.DynamicClass = DynamicClass | |
128 | self._start_state = copy.copy([param.Dynamic.time_dependent, | |
129 | numbergen.TimeAware.time_dependent, | |
130 | param.Dynamic.time_fn, | |
131 | numbergen.TimeAware.time_fn, | |
132 | param.random_seed]) | |
133 | ||
134 | def tearDown(self): | |
135 | param.Dynamic.time_dependent = self._start_state[0] | |
136 | numbergen.TimeAware.time_dependent = self._start_state[1] | |
137 | param.Dynamic.time_fn = self._start_state[2] | |
138 | numbergen.TimeAware.time_fn = self._start_state[3] | |
139 | param.random_seed = self._start_state[4] | |
140 | ||
141 | def test_non_time_dependent(self): | |
142 | """ | |
143 | With param.Dynamic.time_dependent=None every call should | |
144 | increment. | |
145 | """ | |
146 | param.Dynamic.time_dependent=None | |
147 | param.Dynamic.time_fn = self.time_fn | |
148 | ||
149 | dynamic = self.DynamicClass() | |
150 | self.assertEqual(dynamic.a, 0) | |
151 | self.assertEqual(dynamic.a, 1) | |
152 | self.assertEqual(dynamic.a, 2) | |
153 | ||
154 | def test_time_fixed(self): | |
155 | """ | |
156 | With param.Dynamic.time_dependent=True the value should only | |
157 | increment when the time value changes. | |
158 | """ | |
159 | param.Dynamic.time_dependent=True | |
160 | param.Dynamic.time_fn = self.time_fn | |
161 | ||
162 | dynamic = self.DynamicClass() | |
163 | self.assertEqual(dynamic.a, 0) | |
164 | self.assertEqual(dynamic.a, 0) | |
165 | ||
166 | self.time_fn += 1 | |
167 | self.assertEqual(dynamic.a, 1) | |
168 | self.assertEqual(dynamic.a, 1) | |
169 | param.Dynamic.time_fn -= 5 | |
170 | self.assertEqual(dynamic.a, 2) | |
171 | self.assertEqual(dynamic.a, 2) | |
172 | ||
173 | ||
174 | def test_time_dependent(self): | |
175 | """ | |
176 | With param.Dynamic.time_dependent=True and param.Dynamic and | |
177 | numbergen.TimeDependent sharing a common time_fn, the value | |
178 | should be a function of time. | |
179 | """ | |
180 | param.Dynamic.time_dependent=True | |
181 | param.Dynamic.time_fn = self.time_fn | |
182 | numbergen.TimeDependent.time_fn = self.time_fn | |
183 | ||
184 | class DynamicClass(param.Parameterized): | |
185 | b = param.Number(default = numbergen.ScaledTime(factor=2)) | |
186 | ||
187 | dynamic = DynamicClass() | |
188 | self.time_fn(0) | |
189 | self.assertEqual(dynamic.b, 0.0) | |
190 | self.time_fn += 5 | |
191 | self.assertEqual(dynamic.b, 10.0) | |
192 | self.assertEqual(dynamic.b, 10.0) | |
193 | self.time_fn -= 2 | |
194 | self.assertEqual(dynamic.b, 6.0) | |
195 | self.assertEqual(dynamic.b, 6.0) | |
196 | self.time_fn -= 3 | |
197 | self.assertEqual(dynamic.b, 0.0) | |
198 | ||
199 | ||
200 | def test_time_dependent_random(self): | |
201 | """ | |
202 | When set to time_dependent=True, random number generators | |
203 | should also be a function of time. | |
204 | """ | |
205 | param.Dynamic.time_dependent=True | |
206 | numbergen.TimeAware.time_dependent=True | |
207 | param.Dynamic.time_fn = self.time_fn | |
208 | numbergen.TimeAware.time_fn = self.time_fn | |
209 | param.random_seed = 42 | |
210 | ||
211 | class DynamicClass(param.Parameterized): | |
212 | c = param.Number(default = numbergen.UniformRandom(name = 'test1')) | |
213 | d = param.Number(default = numbergen.UniformRandom(name = 'test2')) | |
214 | e = param.Number(default = numbergen.UniformRandom(name = 'test1')) | |
215 | ||
216 | dynamic = DynamicClass() | |
217 | ||
218 | test1_t1 = 0.23589388250988552 | |
219 | test2_t1 = 0.12576257837158122 | |
220 | test1_t2 = 0.14117586161849593 | |
221 | test2_t2 = 0.9134917395930359 | |
222 | ||
223 | self.time_fn(0) | |
224 | self.assertEqual(dynamic.c, test1_t1) | |
225 | self.assertEqual(dynamic.c, dynamic.e) | |
226 | self.assertNotEqual(dynamic.c, dynamic.d) | |
227 | self.assertEqual(dynamic.d, test2_t1) | |
228 | self.time_fn(1) | |
229 | self.assertEqual(dynamic.c, test1_t2) | |
230 | self.assertEqual(dynamic.c, test1_t2) | |
231 | self.assertEqual(dynamic.d, test2_t2) | |
232 | self.time_fn(0) | |
233 | self.assertEqual(dynamic.c, test1_t1) | |
234 | self.assertEqual(dynamic.d, test2_t1) | |
235 | ||
236 | ||
237 | def test_time_hashing_integers(self): | |
238 | """ | |
239 | Check that ints, fractions and strings hash to the same value | |
240 | for integer values. | |
241 | """ | |
242 | hashfn = numbergen.Hash("test", input_count=1) | |
243 | hash_1 = hashfn(1) | |
244 | hash_42 = hashfn(42) | |
245 | hash_200001 = hashfn(200001) | |
246 | ||
247 | self.assertEqual(hash_1, hashfn(fractions.Fraction(1))) | |
248 | self.assertEqual(hash_1, hashfn("1")) | |
249 | ||
250 | self.assertEqual(hash_42, hashfn(fractions.Fraction(42))) | |
251 | self.assertEqual(hash_42, hashfn("42")) | |
252 | ||
253 | self.assertEqual(hash_200001, hashfn(fractions.Fraction(200001))) | |
254 | self.assertEqual(hash_200001, hashfn("200001")) | |
255 | ||
256 | ||
257 | def test_time_hashing_rationals(self): | |
258 | """ | |
259 | Check that hashes fractions and strings match for some | |
260 | reasonable rational numbers. | |
261 | """ | |
262 | hashfn = numbergen.Hash("test", input_count=1) | |
263 | pi = "3.141592" | |
264 | half = fractions.Fraction(0.5) | |
265 | self.assertEqual(hashfn(0.5), hashfn(half)) | |
266 | self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi))) | |
267 | ||
268 | ||
269 | def test_time_hashing_integers_gmpy(self): | |
270 | """ | |
271 | Check that hashes for gmpy values at the integers also matches | |
272 | those of ints, fractions and strings. | |
273 | """ | |
274 | if gmpy is None: raise SkipTest | |
275 | hashfn = numbergen.Hash("test", input_count=1) | |
276 | hash_1 = hashfn(1) | |
277 | hash_42 = hashfn(42) | |
278 | ||
279 | self.assertEqual(hash_1, hashfn(gmpy.mpq(1))) | |
280 | self.assertEqual(hash_1, hashfn(1)) | |
281 | ||
282 | self.assertEqual(hash_42, hashfn(gmpy.mpq(42))) | |
283 | self.assertEqual(hash_42, hashfn(42)) | |
284 | ||
285 | def test_time_hashing_rationals_gmpy(self): | |
286 | """ | |
287 | Check that hashes of fractions and gmpy mpqs match for some | |
288 | reasonable rational numbers. | |
289 | """ | |
290 | if gmpy is None: raise SkipTest | |
291 | pi = "3.141592" | |
292 | hashfn = numbergen.Hash("test", input_count=1) | |
293 | self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5))) | |
294 | self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592))) | |
295 | ||
296 | ||
297 | ||
298 | ||
299 | if __name__ == "__main__": | |
300 | import nose | |
301 | nose.runmodule() |
0 | """ | |
1 | Unit test for watch mechanism | |
2 | """ | |
3 | from . import API1TestCase | |
4 | ||
5 | from .utils import MockLoggingHandler | |
6 | ||
7 | import param | |
8 | ||
9 | from param.parameterized import discard_events | |
10 | ||
11 | ||
12 | class Accumulator(object): | |
13 | ||
14 | def __init__(self): | |
15 | self.args = [] | |
16 | self.kwargs = [] | |
17 | ||
18 | def __call__(self, *args, **kwargs): | |
19 | self.args.append(args) | |
20 | self.kwargs.append(kwargs) | |
21 | ||
22 | def call_count(self): | |
23 | return max(len(self.args), len(self.kwargs)) | |
24 | ||
25 | def args_for_call(self, number): | |
26 | return self.args[number] | |
27 | ||
28 | def kwargs_for_call(self, number): | |
29 | return self.kwargs[number] | |
30 | ||
31 | ||
32 | ||
33 | class SimpleWatchExample(param.Parameterized): | |
34 | a = param.Parameter(default=0) | |
35 | b = param.Parameter(default=0) | |
36 | c = param.Parameter(default=0) | |
37 | d = param.Integer(default=0) | |
38 | ||
39 | ||
40 | class SimpleWatchSubclass(SimpleWatchExample): | |
41 | pass | |
42 | ||
43 | ||
44 | class WatchMethodExample(SimpleWatchSubclass): | |
45 | ||
46 | @param.depends('a', watch='queued') | |
47 | def _clip_a(self): | |
48 | if self.a > 3: | |
49 | self.a = 3 | |
50 | ||
51 | @param.depends('b', watch=True) | |
52 | def _clip_b(self): | |
53 | if self.b > 10: | |
54 | self.b = 10 | |
55 | ||
56 | @param.depends('b', watch=True) | |
57 | def _set_c(self): | |
58 | self.c = self.b*2 | |
59 | ||
60 | @param.depends('c', watch=True) | |
61 | def _set_d_bounds(self): | |
62 | self.param.d.bounds = (self.c, self.c*2) | |
63 | ||
64 | ||
65 | class WatchSubclassExample(WatchMethodExample): | |
66 | ||
67 | pass | |
68 | ||
69 | ||
70 | ||
71 | class TestWatch(API1TestCase): | |
72 | ||
73 | @classmethod | |
74 | def setUpClass(cls): | |
75 | super(TestWatch, cls).setUpClass() | |
76 | log = param.parameterized.get_logger() | |
77 | cls.log_handler = MockLoggingHandler(level='DEBUG') | |
78 | log.addHandler(cls.log_handler) | |
79 | ||
80 | ||
81 | def setUp(self): | |
82 | super(TestWatch, self).setUp() | |
83 | self.accumulator = 0 | |
84 | ||
85 | def test_triggered_when_changed(self): | |
86 | def accumulator(change): | |
87 | self.accumulator += change.new | |
88 | ||
89 | obj = SimpleWatchExample() | |
90 | obj.param.watch(accumulator, 'a') | |
91 | obj.a = 1 | |
92 | self.assertEqual(self.accumulator, 1) | |
93 | obj.a = 2 | |
94 | self.assertEqual(self.accumulator, 3) | |
95 | ||
96 | ||
97 | def test_discard_events_decorator(self): | |
98 | def accumulator(change): | |
99 | self.accumulator += change.new | |
100 | ||
101 | obj = SimpleWatchExample() | |
102 | obj.param.watch(accumulator, 'a') | |
103 | with discard_events(obj): | |
104 | obj.a = 1 | |
105 | self.assertEqual(self.accumulator, 0) | |
106 | obj.a = 2 | |
107 | self.assertEqual(self.accumulator, 3) | |
108 | ||
109 | ||
110 | def test_triggered_when_changed_iterator_type(self): | |
111 | def accumulator(change): | |
112 | self.accumulator = change.new | |
113 | ||
114 | obj = SimpleWatchExample() | |
115 | obj.param.watch(accumulator, 'a') | |
116 | obj.a = [] | |
117 | self.assertEqual(self.accumulator, []) | |
118 | obj.a = tuple() | |
119 | self.assertEqual(self.accumulator, tuple()) | |
120 | ||
121 | ||
122 | def test_triggered_when_changed_mapping_type(self): | |
123 | def accumulator(change): | |
124 | self.accumulator = change.new | |
125 | ||
126 | obj = SimpleWatchExample() | |
127 | obj.param.watch(accumulator, 'a') | |
128 | obj.a = [] | |
129 | self.assertEqual(self.accumulator, []) | |
130 | obj.a = {} | |
131 | self.assertEqual(self.accumulator, {}) | |
132 | ||
133 | ||
134 | def test_untriggered_when_unchanged(self): | |
135 | def accumulator(change): | |
136 | self.accumulator += change.new | |
137 | ||
138 | obj = SimpleWatchExample() | |
139 | obj.param.watch(accumulator, 'a') | |
140 | obj.a = 1 | |
141 | self.assertEqual(self.accumulator, 1) | |
142 | obj.a = 1 | |
143 | self.assertEqual(self.accumulator, 1) | |
144 | ||
145 | ||
146 | def test_triggered_when_unchanged_complex_type(self): | |
147 | def accumulator(change): | |
148 | self.accumulator += 1 | |
149 | ||
150 | obj = SimpleWatchExample() | |
151 | obj.param.watch(accumulator, 'a') | |
152 | subobj = object() | |
153 | obj.a = subobj | |
154 | self.assertEqual(self.accumulator, 1) | |
155 | obj.a = subobj | |
156 | self.assertEqual(self.accumulator, 2) | |
157 | ||
158 | ||
159 | def test_triggered_when_unchanged_if_not_onlychanged(self): | |
160 | accumulator = Accumulator() | |
161 | obj = SimpleWatchExample() | |
162 | obj.param.watch(accumulator, 'a', onlychanged=False) | |
163 | obj.a = 1 | |
164 | ||
165 | self.assertEqual(accumulator.call_count(), 1) | |
166 | args = accumulator.args_for_call(0) | |
167 | self.assertEqual(len(args), 1) | |
168 | self.assertEqual(args[0].name, 'a') | |
169 | self.assertEqual(args[0].old, 0) | |
170 | self.assertEqual(args[0].new, 1) | |
171 | self.assertEqual(args[0].type, 'set') | |
172 | ||
173 | obj.a = 1 | |
174 | args = accumulator.args_for_call(1) | |
175 | self.assertEqual(len(args), 1) | |
176 | self.assertEqual(args[0].name, 'a') | |
177 | self.assertEqual(args[0].old, 1) | |
178 | self.assertEqual(args[0].new, 1) | |
179 | self.assertEqual(args[0].type, 'set') | |
180 | ||
181 | ||
182 | ||
183 | def test_untriggered_when_unwatched(self): | |
184 | def accumulator(change): | |
185 | self.accumulator += change.new | |
186 | ||
187 | obj = SimpleWatchExample() | |
188 | watcher = obj.param.watch(accumulator, 'a') | |
189 | obj.a = 1 | |
190 | self.assertEqual(self.accumulator, 1) | |
191 | obj.param.unwatch(watcher) | |
192 | obj.a = 2 | |
193 | self.assertEqual(self.accumulator, 1) | |
194 | ||
195 | ||
196 | def test_warning_unwatching_when_unwatched(self): | |
197 | def accumulator(change): | |
198 | self.accumulator += change.new | |
199 | ||
200 | obj = SimpleWatchExample() | |
201 | watcher = obj.param.watch(accumulator, 'a') | |
202 | obj.param.unwatch(watcher) | |
203 | obj.param.unwatch(watcher) | |
204 | self.log_handler.assertEndsWith('WARNING', | |
205 | ' to remove.') | |
206 | ||
207 | def test_simple_batched_watch_setattr(self): | |
208 | ||
209 | accumulator = Accumulator() | |
210 | ||
211 | obj = SimpleWatchExample() | |
212 | obj.param.watch(accumulator, ['a','b']) | |
213 | ||
214 | obj.a = 2 | |
215 | self.assertEqual(accumulator.call_count(), 1) | |
216 | args = accumulator.args_for_call(0) | |
217 | ||
218 | self.assertEqual(len(args), 1) | |
219 | self.assertEqual(args[0].name, 'a') | |
220 | self.assertEqual(args[0].old, 0) | |
221 | self.assertEqual(args[0].new, 2) | |
222 | self.assertEqual(args[0].type, 'changed') | |
223 | ||
224 | obj.b = 3 | |
225 | self.assertEqual(accumulator.call_count(), 2) | |
226 | args = accumulator.args_for_call(1) | |
227 | ||
228 | self.assertEqual(len(args), 1) | |
229 | self.assertEqual(args[0].name, 'b') | |
230 | self.assertEqual(args[0].old, 0) | |
231 | self.assertEqual(args[0].new, 3) | |
232 | self.assertEqual(args[0].type, 'changed') | |
233 | ||
234 | def test_batched_watch_context_manager(self): | |
235 | ||
236 | accumulator = Accumulator() | |
237 | ||
238 | obj = SimpleWatchExample() | |
239 | obj.param.watch(accumulator, ['a','b']) | |
240 | ||
241 | with param.batch_watch(obj): | |
242 | obj.a = 2 | |
243 | obj.b = 3 | |
244 | ||
245 | self.assertEqual(accumulator.call_count(), 1) | |
246 | args = accumulator.args_for_call(0) | |
247 | ||
248 | self.assertEqual(len(args), 2) | |
249 | self.assertEqual(args[0].name, 'a') | |
250 | self.assertEqual(args[0].old, 0) | |
251 | self.assertEqual(args[0].new, 2) | |
252 | self.assertEqual(args[0].type, 'changed') | |
253 | self.assertEqual(args[1].name, 'b') | |
254 | self.assertEqual(args[1].old, 0) | |
255 | self.assertEqual(args[1].new, 3) | |
256 | self.assertEqual(args[1].type, 'changed') | |
257 | ||
258 | def test_nested_batched_watch_setattr(self): | |
259 | ||
260 | obj = SimpleWatchExample() | |
261 | ||
262 | accumulator = Accumulator() | |
263 | obj.param.watch(accumulator, ['a', 'c']) | |
264 | ||
265 | def set_c(*events): | |
266 | obj.c = 3 | |
267 | ||
268 | obj.param.watch(set_c, ['a', 'b']) | |
269 | ||
270 | obj.param.set_param(a=2) | |
271 | self.assertEqual(obj.c, 3) | |
272 | ||
273 | # Change inside watch callback should have triggered | |
274 | # second call to accumulator | |
275 | self.assertEqual(accumulator.call_count(), 2) | |
276 | ||
277 | ||
278 | def test_simple_batched_watch(self): | |
279 | ||
280 | accumulator = Accumulator() | |
281 | ||
282 | obj = SimpleWatchExample() | |
283 | obj.param.watch(accumulator, ['a','b']) | |
284 | obj.param.set_param(a=23, b=42) | |
285 | ||
286 | self.assertEqual(accumulator.call_count(), 1) | |
287 | args = accumulator.args_for_call(0) | |
288 | self.assertEqual(len(args), 2) | |
289 | ||
290 | self.assertEqual(args[0].name, 'a') | |
291 | self.assertEqual(args[0].old, 0) | |
292 | self.assertEqual(args[0].new, 23) | |
293 | self.assertEqual(args[0].type, 'changed') | |
294 | ||
295 | self.assertEqual(args[1].name, 'b') | |
296 | self.assertEqual(args[1].old, 0) | |
297 | self.assertEqual(args[1].new, 42) | |
298 | self.assertEqual(args[1].type, 'changed') | |
299 | ||
300 | ||
301 | def test_simple_class_batched_watch(self): | |
302 | ||
303 | accumulator = Accumulator() | |
304 | ||
305 | obj = SimpleWatchSubclass | |
306 | watcher = obj.param.watch(accumulator, ['a','b']) | |
307 | obj.param.set_param(a=23, b=42) | |
308 | ||
309 | self.assertEqual(accumulator.call_count(), 1) | |
310 | args = accumulator.args_for_call(0) | |
311 | self.assertEqual(len(args), 2) | |
312 | ||
313 | self.assertEqual(args[0].name, 'a') | |
314 | self.assertEqual(args[0].old, 0) | |
315 | self.assertEqual(args[0].new, 23) | |
316 | self.assertEqual(args[0].type, 'changed') | |
317 | ||
318 | self.assertEqual(args[1].name, 'b') | |
319 | self.assertEqual(args[1].old, 0) | |
320 | self.assertEqual(args[1].new, 42) | |
321 | self.assertEqual(args[1].type, 'changed') | |
322 | ||
323 | SimpleWatchExample.param.unwatch(watcher) | |
324 | obj.param.set_param(a=0, b=0) | |
325 | ||
326 | ||
327 | def test_simple_batched_watch_callback_reuse(self): | |
328 | ||
329 | accumulator = Accumulator() | |
330 | ||
331 | obj = SimpleWatchExample() | |
332 | obj.param.watch(accumulator, ['a','b']) | |
333 | obj.param.watch(accumulator, ['c']) | |
334 | ||
335 | obj.param.set_param(a=23, b=42, c=99) | |
336 | ||
337 | self.assertEqual(accumulator.call_count(), 2) | |
338 | # Order may be undefined for Python <3.6 | |
339 | for args in [accumulator.args_for_call(i) for i in [0,1]]: | |
340 | if len(args) == 1: # ['c'] | |
341 | self.assertEqual(args[0].name, 'c') | |
342 | self.assertEqual(args[0].old, 0) | |
343 | self.assertEqual(args[0].new, 99) | |
344 | self.assertEqual(args[0].type, 'changed') | |
345 | ||
346 | elif len(args) == 2: # ['a', 'b'] | |
347 | self.assertEqual(args[0].name, 'a') | |
348 | self.assertEqual(args[0].old, 0) | |
349 | self.assertEqual(args[0].new, 23) | |
350 | self.assertEqual(args[0].type, 'changed') | |
351 | ||
352 | self.assertEqual(args[1].name, 'b') | |
353 | self.assertEqual(args[1].old, 0) | |
354 | self.assertEqual(args[1].new, 42) | |
355 | self.assertEqual(args[0].type, 'changed') | |
356 | else: | |
357 | raise Exception('Invalid number of arguments') | |
358 | ||
359 | ||
360 | def test_subclass_batched_watch(self): | |
361 | ||
362 | accumulator = Accumulator() | |
363 | ||
364 | obj = SimpleWatchSubclass() | |
365 | ||
366 | obj.param.watch(accumulator, ['b','c']) | |
367 | obj.param.set_param(b=23, c=42) | |
368 | ||
369 | self.assertEqual(accumulator.call_count(), 1) | |
370 | args = accumulator.args_for_call(0) | |
371 | self.assertEqual(len(args), 2) | |
372 | ||
373 | self.assertEqual(args[0].name, 'b') | |
374 | self.assertEqual(args[0].old, 0) | |
375 | self.assertEqual(args[0].new, 23) | |
376 | self.assertEqual(args[0].type, 'changed') | |
377 | ||
378 | self.assertEqual(args[1].name, 'c') | |
379 | self.assertEqual(args[1].old, 0) | |
380 | self.assertEqual(args[1].new, 42) | |
381 | self.assertEqual(args[1].type, 'changed') | |
382 | ||
383 | ||
384 | def test_nested_batched_watch(self): | |
385 | ||
386 | accumulator = Accumulator() | |
387 | ||
388 | obj = SimpleWatchExample() | |
389 | ||
390 | def set_param(*changes): | |
391 | obj.param.set_param(a=10, d=12) | |
392 | ||
393 | obj.param.watch(accumulator, ['a', 'b','c', 'd']) | |
394 | obj.param.watch(set_param, ['b', 'c']) | |
395 | obj.param.set_param(b=23, c=42) | |
396 | ||
397 | self.assertEqual(accumulator.call_count(), 2) | |
398 | args = accumulator.args_for_call(0) | |
399 | self.assertEqual(len(args), 2) | |
400 | ||
401 | self.assertEqual(args[0].name, 'b') | |
402 | self.assertEqual(args[0].old, 0) | |
403 | self.assertEqual(args[0].new, 23) | |
404 | self.assertEqual(args[0].type, 'changed') | |
405 | ||
406 | self.assertEqual(args[1].name, 'c') | |
407 | self.assertEqual(args[1].old, 0) | |
408 | self.assertEqual(args[1].new, 42) | |
409 | self.assertEqual(args[1].type, 'changed') | |
410 | ||
411 | args = accumulator.args_for_call(1) | |
412 | self.assertEqual(len(args), 2) | |
413 | ||
414 | self.assertEqual(args[0].name, 'a') | |
415 | self.assertEqual(args[0].old, 0) | |
416 | self.assertEqual(args[0].new, 10) | |
417 | self.assertEqual(args[0].type, 'changed') | |
418 | ||
419 | self.assertEqual(args[1].name, 'd') | |
420 | self.assertEqual(args[1].old, 0) | |
421 | self.assertEqual(args[1].new, 12) | |
422 | self.assertEqual(args[1].type, 'changed') | |
423 | ||
424 | ||
425 | def test_nested_batched_watch_not_onlychanged(self): | |
426 | accumulator = Accumulator() | |
427 | ||
428 | obj = SimpleWatchSubclass() | |
429 | ||
430 | obj.param.watch(accumulator, ['b','c'], onlychanged=False) | |
431 | obj.param.set_param(b=0, c=0) | |
432 | ||
433 | self.assertEqual(accumulator.call_count(), 1) | |
434 | ||
435 | args = accumulator.args_for_call(0) | |
436 | self.assertEqual(len(args), 2) | |
437 | ||
438 | self.assertEqual(args[0].name, 'b') | |
439 | self.assertEqual(args[0].old, 0) | |
440 | self.assertEqual(args[0].new, 0) | |
441 | self.assertEqual(args[0].type, 'set') | |
442 | ||
443 | self.assertEqual(args[1].name, 'c') | |
444 | self.assertEqual(args[1].old, 0) | |
445 | self.assertEqual(args[1].new, 0) | |
446 | self.assertEqual(args[1].type, 'set') | |
447 | ||
448 | ||
449 | ||
450 | class TestWatchMethod(API1TestCase): | |
451 | ||
452 | def test_dependent_params(self): | |
453 | obj = WatchMethodExample() | |
454 | ||
455 | obj.b = 3 | |
456 | self.assertEqual(obj.c, 6) | |
457 | ||
458 | def test_multiple_watcher_dispatch_queued(self): | |
459 | obj = WatchMethodExample() | |
460 | obj2 = SimpleWatchExample() | |
461 | ||
462 | def link(event): | |
463 | obj2.a = event.new | |
464 | ||
465 | obj.param.watch(link, 'a', queued=True) | |
466 | obj.a = 4 | |
467 | self.assertEqual(obj.a, 3) | |
468 | self.assertEqual(obj2.a, 3) | |
469 | ||
470 | def test_multiple_watcher_dispatch(self): | |
471 | obj = WatchMethodExample() | |
472 | obj2 = SimpleWatchExample() | |
473 | ||
474 | def link(event): | |
475 | obj2.b = event.new | |
476 | ||
477 | obj.param.watch(link, 'b') | |
478 | obj.b = 11 | |
479 | self.assertEqual(obj.b, 10) | |
480 | self.assertEqual(obj2.b, 11) | |
481 | ||
482 | def test_multiple_watcher_dispatch_on_param_attribute(self): | |
483 | obj = WatchMethodExample() | |
484 | accumulator = Accumulator() | |
485 | ||
486 | obj.param.watch(accumulator, 'd', 'bounds') | |
487 | obj.c = 2 | |
488 | self.assertEqual(obj.param.d.bounds, (2, 4)) | |
489 | self.assertEqual(accumulator.call_count(), 1) | |
490 | ||
491 | def test_depends_with_watch_on_subclass(self): | |
492 | obj = WatchSubclassExample() | |
493 | ||
494 | obj.b = 3 | |
495 | self.assertEqual(obj.c, 6) | |
496 | ||
497 | ||
498 | ||
499 | ||
500 | class TestWatchValues(API1TestCase): | |
501 | ||
502 | def setUp(self): | |
503 | super(TestWatchValues, self).setUp() | |
504 | self.accumulator = 0 | |
505 | ||
506 | def test_triggered_when_values_changed(self): | |
507 | def accumulator(a): | |
508 | self.accumulator += a | |
509 | ||
510 | obj = SimpleWatchExample() | |
511 | obj.param.watch_values(accumulator, 'a') | |
512 | obj.a = 1 | |
513 | self.assertEqual(self.accumulator, 1) | |
514 | obj.a = 2 | |
515 | self.assertEqual(self.accumulator, 3) | |
516 | ||
517 | ||
518 | def test_untriggered_when_values_unchanged(self): | |
519 | def accumulator(a): | |
520 | self.accumulator += a | |
521 | ||
522 | obj = SimpleWatchExample() | |
523 | obj.param.watch_values(accumulator, 'a') | |
524 | obj.a = 1 | |
525 | self.assertEqual(self.accumulator, 1) | |
526 | obj.a = 1 | |
527 | self.assertEqual(self.accumulator, 1) | |
528 | ||
529 | ||
530 | def test_untriggered_when_values_unwatched(self): | |
531 | def accumulator(a): | |
532 | self.accumulator += a | |
533 | ||
534 | obj = SimpleWatchExample() | |
535 | watcher = obj.param.watch_values(accumulator, 'a') | |
536 | obj.a = 1 | |
537 | self.assertEqual(self.accumulator, 1) | |
538 | obj.param.unwatch(watcher) | |
539 | obj.a = 2 | |
540 | self.assertEqual(self.accumulator, 1) | |
541 | ||
542 | ||
543 | def test_simple_batched_watch_values_setattr(self): | |
544 | ||
545 | accumulator = Accumulator() | |
546 | ||
547 | obj = SimpleWatchExample() | |
548 | obj.param.watch_values(accumulator, ['a','b']) | |
549 | ||
550 | obj.a = 2 | |
551 | self.assertEqual(accumulator.call_count(), 1) | |
552 | kwargs = accumulator.kwargs_for_call(0) | |
553 | ||
554 | self.assertEqual(len(kwargs), 1) | |
555 | self.assertEqual(kwargs, {'a':2}) | |
556 | ||
557 | obj.b = 3 | |
558 | self.assertEqual(accumulator.call_count(), 2) | |
559 | kwargs = accumulator.kwargs_for_call(1) | |
560 | self.assertEqual(kwargs, {'b':3}) | |
561 | ||
562 | ||
563 | def test_simple_batched_watch_values(self): | |
564 | ||
565 | accumulator = Accumulator() | |
566 | ||
567 | obj = SimpleWatchExample() | |
568 | obj.param.watch_values(accumulator, ['a','b']) | |
569 | obj.param.set_param(a=23, b=42) | |
570 | ||
571 | self.assertEqual(accumulator.call_count(), 1) | |
572 | kwargs = accumulator.kwargs_for_call(0) | |
573 | self.assertEqual(kwargs, {'a':23, 'b':42}) | |
574 | ||
575 | ||
576 | def test_simple_batched_watch_values_callback_reuse(self): | |
577 | ||
578 | accumulator = Accumulator() | |
579 | ||
580 | obj = SimpleWatchExample() | |
581 | obj.param.watch_values(accumulator, ['a','b']) | |
582 | obj.param.watch_values(accumulator, ['c']) | |
583 | ||
584 | obj.param.set_param(a=23, b=42, c=99) | |
585 | ||
586 | self.assertEqual(accumulator.call_count(), 2) | |
587 | # Order may be undefined for Python <3.6 | |
588 | for kwargs in [accumulator.kwargs_for_call(i) for i in [0,1]]: | |
589 | if len(kwargs) == 1: # ['c'] | |
590 | self.assertEqual(kwargs, {'c':99}) | |
591 | elif len(kwargs) == 2: # ['a', 'b'] | |
592 | self.assertEqual(kwargs, {'a':23, 'b':42}) | |
593 | else: | |
594 | raise Exception('Invalid number of arguments') | |
595 | ||
596 | ||
597 | ||
598 | ||
599 | ||
600 | class TestWatchAttributes(API1TestCase): | |
601 | ||
602 | def setUp(self): | |
603 | super(TestWatchAttributes, self).setUp() | |
604 | self.accumulator = [] | |
605 | ||
606 | def tearDown(self): | |
607 | SimpleWatchExample.param['d'].bounds = None | |
608 | ||
609 | def test_watch_class_param_attribute(self): | |
610 | def accumulator(a): | |
611 | self.accumulator += [a.new] | |
612 | ||
613 | SimpleWatchExample.param.watch(accumulator, ['d'], 'bounds') | |
614 | SimpleWatchExample.param['d'].bounds = (0, 3) | |
615 | assert self.accumulator == [(0, 3)] | |
616 | ||
617 | def test_watch_instance_param_attribute(self): | |
618 | def accumulator(a): | |
619 | self.accumulator += [a.new] | |
620 | ||
621 | obj = SimpleWatchExample() | |
622 | obj.param.watch(accumulator, ['d'], 'bounds') | |
623 | ||
624 | # Ensure watching an instance parameter makes copy | |
625 | assert obj.param.objects('current')['d'] is not SimpleWatchExample.param['d'] | |
626 | ||
627 | obj.param['d'].bounds = (0, 3) | |
628 | assert SimpleWatchExample.param['d'].bounds is None | |
629 | assert self.accumulator == [(0, 3)] | |
630 | ||
631 | ||
632 | ||
633 | class TestTrigger(API1TestCase): | |
634 | ||
635 | def setUp(self): | |
636 | super(TestTrigger, self).setUp() | |
637 | self.accumulator = 0 | |
638 | ||
639 | def test_simple_trigger_one_param(self): | |
640 | accumulator = Accumulator() | |
641 | obj = SimpleWatchExample() | |
642 | obj.param.watch(accumulator, ['a']) | |
643 | obj.param.trigger('a') | |
644 | self.assertEqual(accumulator.call_count(), 1) | |
645 | ||
646 | args = accumulator.args_for_call(0) | |
647 | self.assertEqual(args[0].name, 'a') | |
648 | self.assertEqual(args[0].old, 0) | |
649 | self.assertEqual(args[0].new, 0) | |
650 | self.assertEqual(args[0].type, 'triggered') | |
651 | ||
652 | def test_simple_trigger_when_batched(self): | |
653 | accumulator = Accumulator() | |
654 | obj = SimpleWatchExample() | |
655 | obj.param.watch(accumulator, ['a']) | |
656 | with param.batch_watch(obj): | |
657 | obj.param.trigger('a') | |
658 | self.assertEqual(accumulator.call_count(), 1) | |
659 | ||
660 | args = accumulator.args_for_call(0) | |
661 | self.assertEqual(args[0].name, 'a') | |
662 | self.assertEqual(args[0].old, 0) | |
663 | self.assertEqual(args[0].new, 0) | |
664 | # Note: This is not strictly correct | |
665 | self.assertEqual(args[0].type, 'changed') | |
666 | ||
667 | def test_simple_trigger_one_param_change(self): | |
668 | accumulator = Accumulator() | |
669 | obj = SimpleWatchExample() | |
670 | obj.param.watch(accumulator, ['a']) | |
671 | obj.a = 42 | |
672 | self.assertEqual(accumulator.call_count(), 1) | |
673 | ||
674 | obj.param.trigger('a') | |
675 | self.assertEqual(accumulator.call_count(), 2) | |
676 | ||
677 | args = accumulator.args_for_call(0) | |
678 | self.assertEqual(args[0].name, 'a') | |
679 | self.assertEqual(args[0].old, 0) | |
680 | self.assertEqual(args[0].new, 42) | |
681 | self.assertEqual(args[0].type, 'changed') | |
682 | ||
683 | args = accumulator.args_for_call(1) | |
684 | self.assertEqual(args[0].name, 'a') | |
685 | self.assertEqual(args[0].old, 42) | |
686 | self.assertEqual(args[0].new, 42) | |
687 | self.assertEqual(args[0].type, 'triggered') | |
688 | ||
689 | def test_simple_trigger_two_params(self): | |
690 | accumulator = Accumulator() | |
691 | obj = SimpleWatchExample() | |
692 | obj.param.watch(accumulator, ['a','b']) | |
693 | obj.param.trigger('a','b') | |
694 | self.assertEqual(accumulator.call_count(), 1) | |
695 | ||
696 | args = accumulator.args_for_call(0) | |
697 | self.assertEqual(args[0].name, 'a') | |
698 | self.assertEqual(args[0].old, 0) | |
699 | self.assertEqual(args[0].new, 0) | |
700 | self.assertEqual(args[0].type, 'triggered') | |
701 | ||
702 | self.assertEqual(args[1].name, 'b') | |
703 | self.assertEqual(args[1].old, 0) | |
704 | self.assertEqual(args[1].new, 0) | |
705 | self.assertEqual(args[1].type, 'triggered') | |
706 | ||
707 | ||
708 | if __name__ == "__main__": | |
709 | import nose | |
710 | nose.runmodule() |
0 | import logging | |
1 | ||
2 | class MockLoggingHandler(logging.Handler): | |
3 | """Mock logging handler to check for expected logs. | |
4 | ||
5 | Messages are available from an instance's ``messages`` dict, in | |
6 | order, indexed by a lowercase log level string (e.g., 'debug', | |
7 | 'info', etc.). | |
8 | ||
9 | This is typically used by using a setUpClass classmethod and a setUp | |
10 | method on a test case. The setUpClass classmethod can be configured | |
11 | as follows after calling super (with cls): | |
12 | ||
13 | log = param.parameterized.get_logger() | |
14 | cls.log_handler = MockLoggingHandler(level='DEBUG') | |
15 | log.addHandler(cls.log_handler) | |
16 | ||
17 | The setUp method then just needs to call self.log_handler.reset() | |
18 | between tests (typically after invoking super). This is necessary to | |
19 | make the tests independent where the tests can use the | |
20 | self.log_handler.tail and self.log_handler.assertEndsWith methods. | |
21 | """ | |
22 | ||
23 | def __init__(self, *args, **kwargs): | |
24 | self.messages = {'DEBUG': [], 'INFO': [], 'WARNING': [], | |
25 | 'ERROR': [], 'CRITICAL': [], 'VERBOSE':[]} | |
26 | self.param_methods = {'WARNING':'param.warning()', 'INFO':'param.message()', | |
27 | 'VERBOSE':'param.verbose()', 'DEBUG':'param.debug()'} | |
28 | super(MockLoggingHandler, self).__init__(*args, **kwargs) | |
29 | ||
30 | def emit(self, record): | |
31 | "Store a message to the instance's messages dictionary" | |
32 | self.acquire() | |
33 | try: | |
34 | self.messages[record.levelname].append(record.getMessage()) | |
35 | finally: | |
36 | self.release() | |
37 | ||
38 | def reset(self): | |
39 | self.acquire() | |
40 | self.messages = {'DEBUG': [], 'INFO': [], 'WARNING': [], | |
41 | 'ERROR': [], 'CRITICAL': [], 'VERBOSE':[]} | |
42 | self.release() | |
43 | ||
44 | def tail(self, level, n=1): | |
45 | "Returns the last n lines captured at the given level" | |
46 | return [str(el) for el in self.messages[level][-n:]] | |
47 | ||
48 | def assertEndsWith(self, level, substring): | |
49 | """ | |
50 | Assert that the last line captured at the given level ends with | |
51 | a particular substring. | |
52 | """ | |
53 | msg='\n\n{method}: {last_line}\ndoes not end with:\n{substring}' | |
54 | last_line = self.tail(level, n=1) | |
55 | if len(last_line) == 0: | |
56 | raise AssertionError('Missing {method} output: {substring}'.format( | |
57 | method=self.param_methods[level], substring=repr(substring))) | |
58 | if not last_line[0].endswith(substring): | |
59 | raise AssertionError(msg.format(method=self.param_methods[level], | |
60 | last_line=repr(last_line[0]), | |
61 | substring=repr(substring))) | |
62 | ||
63 | def assertContains(self, level, substring): | |
64 | """ | |
65 | Assert that the last line captured at the given level contains a | |
66 | particular substring. | |
67 | """ | |
68 | msg='\n\n{method}: {last_line}\ndoes not contain:\n{substring}' | |
69 | last_line = self.tail(level, n=1) | |
70 | if len(last_line) == 0: | |
71 | raise AssertionError('Missing {method} output: {substring}'.format( | |
72 | method=self.param_methods[level], substring=repr(substring))) | |
73 | if substring not in last_line[0]: | |
74 | raise AssertionError(msg.format(method=self.param_methods[level], | |
75 | last_line=repr(last_line[0]), | |
76 | substring=repr(substring))) |
0 | import sys | |
1 | import unittest # noqa | |
2 | ||
3 | if sys.version_info[0]==2 and sys.version_info[1]<7: | |
4 | del sys.modules['unittest'] | |
5 | sys.modules['unittest'] = __import__('unittest2') |
0 | [tox] | |
1 | envlist = | |
2 | py37,py36,py35,py34,py27,pypy, | |
3 | {py27,py36}-flakes, | |
4 | {py27,py36}-with_numpy, | |
5 | {py27,py36}-with_ipython | |
6 | {py27,py35,py36,py37}-with_pandas | |
7 | ||
8 | [testenv] | |
9 | deps = .[tests] | |
10 | commands = nosetests | |
11 | ||
12 | [testenv:coverage] | |
13 | # remove develop install if https://github.com/ioam/param/issues/219 | |
14 | # implemented | |
15 | setdevelop = True | |
16 | passenv = TRAVIS TRAVIS_* | |
17 | deps = {[testenv]deps} | |
18 | coveralls | |
19 | commands = nosetests --with-coverage --cover-package=param | |
20 | coveralls | |
21 | # TODO missing numbergen | |
22 | ||
23 | [testenv:with_numpy] | |
24 | deps = {[testenv]deps} | |
25 | numpy | |
26 | setenv = PARAM_TEST_NUMPY = 1 | |
27 | ||
28 | [testenv:with_pandas] | |
29 | deps = {[testenv]deps} | |
30 | pandas | |
31 | setenv = PARAM_TEST_PANDAS = 1 | |
32 | ||
33 | ||
34 | [testenv:with_ipython] | |
35 | deps = {[testenv]deps} | |
36 | ipython | |
37 | setenv = PARAM_TEST_IPYTHON = 1 | |
38 | ||
39 | [testenv:flakes] | |
40 | skip_install = true | |
41 | commands = flake8 | |
42 | ||
43 | [flake8] | |
44 | ignore = E,W,W605 | |
45 | include = *.py | |
46 | exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,.ipynb_checkpoints,run_test.py |