Codebase list python-param / d77b76f
Import upstream version 1.12.0 Debian Janitor 2 years ago
98 changed file(s) with 11436 addition(s) and 2084 deletion(s). Raw diff Collapse all Expand all
+0
-15
.appveyor.yml less more
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 name: packages
1 on:
2 push:
3 tags:
4 - 'v[0-9]+.[0-9]+.[0-9]+'
5 - 'v[0-9]+.[0-9]+.[0-9]+a[0-9]+'
6 - 'v[0-9]+.[0-9]+.[0-9]+b[0-9]+'
7 - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
8
9 jobs:
10 conda_build:
11 name: Build Conda Packages
12 runs-on: 'ubuntu-latest'
13 defaults:
14 run:
15 shell: bash -l {0}
16 env:
17 CHANS_DEV: "-c pyviz/label/dev"
18 PKG_TEST_PYTHON: "--test-python=py37"
19 PYTHON_VERSION: "3.7"
20 CHANS: "-c pyviz"
21 CONDA_UPLOAD_TOKEN: ${{ secrets.CONDA_UPLOAD_TOKEN }}
22 steps:
23 - uses: actions/checkout@v2
24 - name: Fetch unshallow
25 run: git fetch --prune --tags --unshallow -f
26 - uses: actions/setup-python@v2
27 with:
28 python-version: "3.7"
29 - uses: conda-incubator/setup-miniconda@v2
30 with:
31 miniconda-version: "latest"
32 - name: Set output
33 id: vars
34 run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
35 - name: conda setup
36 run: |
37 eval "$(conda shell.bash hook)"
38 conda config --set always_yes yes --set changeps1 no
39 conda update conda
40 conda install anaconda-client conda-build
41 - name: conda build
42 run: |
43 eval "$(conda shell.bash hook)"
44 conda build conda.recipe/
45 - name: conda dev upload
46 if: (contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc'))
47 run: |
48 eval "$(conda shell.bash hook)"
49 anaconda --token $CONDA_UPLOAD_TOKEN upload --user pyviz --label=dev $(conda build --output conda.recipe)
50 - name: conda main upload
51 if: (!(contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc')))
52 run: |
53 eval "$(conda shell.bash hook)"
54 anaconda --token $CONDA_UPLOAD_TOKEN upload --user pyviz --label=dev --label=main $(conda build --output conda.recipe)
55 pip_build:
56 name: Build PyPI Packages
57 runs-on: 'ubuntu-latest'
58 defaults:
59 run:
60 shell: bash -l {0}
61 env:
62 TOX_ENV: "py3.7"
63 steps:
64 - uses: actions/checkout@v2
65 - name: Fetch unshallow
66 run: git fetch --prune --tags --unshallow -f
67 - uses: actions/setup-python@v2
68 with:
69 python-version: "3.7"
70 - name: env setup
71 run: |
72 python -m pip install --upgrade pip
73 python -m pip install setuptools wheel twine tox
74 - name: pip build
75 run: |
76 python setup.py sdist bdist_wheel
77 - name: Publish package to PyPI
78 uses: pypa/gh-action-pypi-publish@master
79 with:
80 user: ${{ secrets.PPU }}
81 password: ${{ secrets.PPP }}
82 packages_dir: dist/
0 name: docs
1 on:
2 push:
3 tags:
4 - 'v[0-9]+.[0-9]+.[0-9]+'
5 - 'v[0-9]+.[0-9]+.[0-9]+a[0-9]+'
6 - 'v[0-9]+.[0-9]+.[0-9]+b[0-9]+'
7 - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
8 workflow_dispatch:
9 inputs:
10 target:
11 description: 'dev or main'
12 required: true
13 default: 'dev'
14
15 jobs:
16 build_docs:
17 name: Documentation
18 runs-on: 'ubuntu-latest'
19 timeout-minutes: 120
20 defaults:
21 run:
22 shell: bash -l {0}
23 env:
24 DESC: "Documentation build"
25 steps:
26 - uses: actions/checkout@v2
27 - name: Fetch unshallow
28 run: git fetch --prune --tags --unshallow -f
29 - uses: actions/setup-python@v2
30 with:
31 python-version: 3.7
32 - name: Set output
33 id: vars
34 run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
35 - name: graphviz
36 run: sudo apt install libgraphviz-dev
37 - name: env setup
38 run: |
39 python -m pip install pip==21.0.0
40 python -m pip install nbsite --pre
41 python -m pip install -e .[doc] --use-feature=fast-deps --use-deprecated=legacy-resolver
42 - name: build docs
43 run: |
44 cp examples/user_guide/*.ipynb doc/user_guide/
45 python -m nbsite build --org holoviz --project-name param
46 - name: Deploy dev
47 uses: peaceiris/actions-gh-pages@v3
48 if: (github.event.inputs.target == 'dev' || contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc'))
49 with:
50 personal_token: ${{ secrets.ACCESS_TOKEN }}
51 external_repository: pyviz-dev/param
52 publish_dir: ./builtdocs
53 force_orphan: true
54 - name: Deploy main
55 if: (github.event.inputs.target == 'main' || !(contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc')))
56 uses: peaceiris/actions-gh-pages@v3
57 with:
58 github_token: ${{ secrets.GITHUB_TOKEN }}
59 publish_dir: ./builtdocs
60 cname: param.holoviz.org
61 force_orphan: true
0 # things not included
1 # language
2 # notifications - no email notifications set up
3
4 name: pytest
5 on:
6 push:
7 branches:
8 - master
9 pull_request:
10 branches:
11 - '*'
12
13 jobs:
14 test_suite:
15 name: Tox on ${{ matrix.python-version }}, ${{ matrix.os }}
16 runs-on: ${{ matrix.os }}
17 strategy:
18 fail-fast: false
19 matrix:
20 os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
21 python-version: [2.7, 3.6, 3.7, 3.8, 3.9, '3.10', pypy3]
22 timeout-minutes: 30
23 defaults:
24 run:
25 shell: bash -l {0}
26 env:
27 PYTHON_VERSION: ${{ matrix.python-version }}
28 CHANS_DEV: "-c pyviz/label/dev"
29 CHANS: "-c pyviz"
30 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 steps:
32 - uses: actions/checkout@v2
33 with:
34 fetch-depth: "100"
35 - uses: actions/setup-python@v2
36 with:
37 python-version: ${{ matrix.python-version }}
38 - name: Fetch
39 run: git fetch --prune --tags
40 - name: env setup
41 run: |
42 set -xe
43 python3 -VV
44 python3 -m site
45 python3 -m pip install --upgrade pip
46 python3 -m pip install tox flake8
47 - name: lint
48 run: |
49 flake8
50 - name: unit python
51 if: (!startsWith(matrix.python-version, 'py'))
52 run: |
53 pyver="${{ matrix.python-version }}"
54 tox_env="py${pyver//.}"
55 tox -e $tox_env
56 - name: unit pypy
57 if: startsWith(matrix.python-version, 'py')
58 run: |
59 pyver="${{ matrix.python-version }}"
60 tox_env="${pyver//.}"
61 tox -e $tox_env
62 - name: unit with_ipython
63 run: tox -e with_ipython
64 - name: unit with_numpy
65 if: (!startsWith(matrix.python-version, 'py'))
66 run: tox -e with_numpy
67 - name: unit with_pandas
68 if: (!startsWith(matrix.python-version, 'py') && !(contains(matrix.os, 'windows') && matrix.python-version == '3.10'))
69 run: tox -e with_pandas
70 - name: unit with_jsonschema
71 run: tox -e with_jsonschema
72 - name: unit with_gmpy
73 if: (contains(matrix.os, 'ubuntu') && !startsWith(matrix.python-version, 'py'))
74 run: tox -e with_gmpy
75 - name: unit all_deps
76 if: (contains(matrix.os, 'ubuntu') && !startsWith(matrix.python-version, 'py'))
77 run: tox -e with_all
78 - uses: codecov/codecov-action@v2
79 with:
80 fail_ci_if_error: true
77 *.so
88 *.o
99 *.out
10 *.lock
10 *.lock
11 .ipynb_checkpoints
12 .vscode
13 .tox
14 data.pickle
15
16 # Docs
17 builtdocs/
18 jupyter_execute/
19 doc/user_guide/*.ipynb
20 doc/Reference_Manual/
21
22 # Unit test / Coverage report
23 .coverage
24 coverage.xml
+0
-158
.travis.yml less more
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-2020, HoloViz team.
11 All rights reserved.
22
33 Redistribution and use in source and binary forms, with or without
1212 documentation and/or other materials provided with the
1313 distribution.
1414
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.
1818
1919 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2020 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
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
-35
README.rst less more
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 name: param
1 channels:
2 - defaults
3 dependencies:
4 - python=3.9.7
5 - aiohttp=3.7.4
6 - panel=0.12.2
3030 - param
3131 - numbergen
3232 commands:
33 # https://github.com/ioam/param/issues/219
34 - nosetests tests
33 # https://github.com/holoviz/param/issues/219
34 - pytest tests
3535
3636 about:
3737 home: {{ sdata['url'] }}
Binary diff not shown
Binary diff not shown
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:ooo="http://xml.openoffice.org/svg/export"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:export-ydpi="169.84"
11 inkscape:export-xdpi="169.84"
12 inkscape:export-filename="/Users/jbednar/param/doc/_static/icon.png"
13 version="1.1"
14 id="svg833"
15 width="127.80564"
16 height="130.11864"
17 viewBox="0 0 127.80564 130.11864"
18 sodipodi:docname="icon.svg"
19 inkscape:version="1.0 (4035a4f, 2020-05-01)">
20 <metadata
21 id="metadata839">
22 <rdf:RDF>
23 <cc:Work
24 rdf:about="">
25 <dc:format>image/svg+xml</dc:format>
26 <dc:type
27 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
28 <dc:title></dc:title>
29 </cc:Work>
30 </rdf:RDF>
31 </metadata>
32 <defs
33 id="defs837">
34 <clipPath
35 clipPathUnits="userSpaceOnUse"
36 id="presentation_clip_path">
37 <rect
38 id="rect1057"
39 height="1678"
40 width="4191"
41 y="8493"
42 x="7731" />
43 </clipPath>
44 <clipPath
45 clipPathUnits="userSpaceOnUse"
46 id="presentation_clip_path_shrink">
47 <rect
48 id="rect1060"
49 height="1675"
50 width="4183"
51 y="8494"
52 x="7735" />
53 </clipPath>
54 <font
55 horiz-adv-x="2048"
56 id="EmbeddedFont_1"
57 horiz-origin-x="0"
58 horiz-origin-y="0"
59 vert-origin-x="512"
60 vert-origin-y="768"
61 vert-adv-y="1024">
62 <font-face
63 id="font-face1065"
64 descent="389"
65 ascent="1918"
66 font-style="normal"
67 font-weight="bold"
68 units-per-em="2048"
69 font-family="Ubuntu embedded" />
70 <missing-glyph
71 id="missing-glyph1067"
72 d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"
73 horiz-adv-x="2048" />
74 <glyph
75 id="glyph1069"
76 d="M 793,807 C 765,814 733,821 696,828 659,836 620,840 578,840 558,840 536,838 509,835 482,831 462,827 449,823 L 449,0 143,0 143,1020 C 198,1039 262,1057 337,1074 411,1091 494,1100 586,1100 602,1100 622,1099 645,1097 668,1095 692,1092 715,1089 738,1085 761,1081 784,1076 808,1071 827,1066 844,1059 Z"
77 horiz-adv-x="726"
78 unicode="r" />
79 <glyph
80 id="glyph1071"
81 d="M 735,571 C 735,670 723,739 697,780 672,821 629,842 567,842 548,842 528,841 508,839 487,837 468,834 449,831 L 449,0 143,0 143,1040 C 169,1047 200,1054 234,1062 269,1069 306,1076 345,1082 384,1089 424,1093 465,1097 506,1100 546,1102 586,1102 664,1102 727,1092 775,1072 824,1052 864,1029 895,1001 939,1033 989,1057 1046,1075 1102,1093 1154,1102 1202,1102 1288,1102 1359,1090 1414,1066 1469,1042 1513,1008 1546,965 1579,921 1602,869 1614,809 1626,749 1632,682 1632,608 L 1632,0 1327,0 1327,571 C 1327,670 1314,739 1289,780 1264,821 1221,842 1159,842 1143,842 1120,838 1091,829 1061,821 1037,811 1018,799 1027,767 1034,734 1036,699 1039,665 1040,627 1040,588 L 1040,0 735,0 Z"
82 horiz-adv-x="1504"
83 unicode="m" />
84 <glyph
85 id="glyph1073"
86 d="M 555,213 C 585,213 614,214 641,215 668,216 690,218 707,221 L 707,453 C 694,455 676,458 651,461 627,464 604,465 584,465 555,465 528,463 503,460 478,456 455,450 436,440 417,431 402,418 391,401 380,385 375,365 375,340 375,292 391,259 423,241 455,222 499,213 555,213 Z M 530,1106 C 621,1106 696,1096 756,1075 816,1055 864,1025 900,987 936,949 962,902 977,848 992,793 999,733 999,666 L 999,31 C 956,21 895,10 817,-3 739,-16 645,-23 535,-23 465,-23 402,-16 345,-4 288,8 240,28 199,56 158,84 126,121 104,166 83,211 72,266 72,332 72,395 84,448 110,492 135,535 169,570 211,596 253,622 302,641 356,652 411,664 468,670 526,670 566,670 601,668 632,665 663,661 687,657 707,651 L 707,680 C 707,732 691,773 659,805 628,836 573,852 496,852 444,852 393,848 342,841 291,833 248,823 211,809 L 172,1055 C 190,1060 212,1066 239,1072 265,1078 294,1084 326,1089 357,1093 390,1097 425,1101 460,1104 495,1106 530,1106 Z"
87 horiz-adv-x="960"
88 unicode="a" />
89 <glyph
90 id="glyph1075"
91 d="M 590,1436 C 801,1436 964,1398 1077,1324 1191,1250 1247,1128 1247,958 1247,788 1190,665 1075,589 961,513 797,475 584,475 L 483,475 483,0 164,0 164,1399 C 233,1412 307,1422 385,1427 463,1433 531,1436 590,1436 Z M 610,1163 C 587,1163 564,1163 542,1161 519,1160 500,1158 483,1157 L 483,748 584,748 C 694,748 778,763 834,793 890,823 918,879 918,961 918,1000 910,1033 896,1059 882,1085 861,1106 835,1121 808,1137 776,1148 737,1154 699,1160 657,1163 610,1163 Z"
92 horiz-adv-x="1115"
93 unicode="P" />
94 </font>
95 <g
96 id="g1080"
97 ooo:id-list="id3"
98 ooo:slide="id1" />
99 <g
100 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
101 id="bullet-char-template-57356">
102 <path
103 id="path1084"
104 d="M 580,1141 1163,571 580,0 -4,571 Z" />
105 </g>
106 <g
107 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
108 id="bullet-char-template-57354">
109 <path
110 id="path1087"
111 d="M 8,1128 H 1137 V 0 H 8 Z" />
112 </g>
113 <g
114 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
115 id="bullet-char-template-10146">
116 <path
117 id="path1090"
118 d="M 174,0 602,739 174,1481 1456,739 Z M 1358,739 309,1346 659,739 Z" />
119 </g>
120 <g
121 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
122 id="bullet-char-template-10132">
123 <path
124 id="path1093"
125 d="M 2015,739 1276,0 H 717 l 543,543 H 174 v 393 h 1086 l -543,545 h 557 z" />
126 </g>
127 <g
128 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
129 id="bullet-char-template-10007">
130 <path
131 id="path1096"
132 d="m 0,-2 c -7,16 -16,29 -25,39 l 381,530 c -94,256 -141,385 -141,387 0,25 13,38 40,38 9,0 21,-2 34,-5 21,4 42,12 65,25 l 27,-13 111,-251 280,301 64,-25 24,25 c 21,-10 41,-24 62,-43 C 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 c 0,-27 -21,-55 -63,-84 l 16,-20 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 c -22,-34 -53,-51 -92,-51 -42,0 -63,17 -64,51 -7,9 -10,24 -10,44 0,9 1,19 2,30 z" />
133 </g>
134 <g
135 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
136 id="bullet-char-template-10004">
137 <path
138 id="path1099"
139 d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 c 0,78 14,145 41,201 34,71 87,106 158,106 53,0 88,-31 106,-94 l 23,-176 c 8,-64 28,-97 59,-98 l 735,706 c 11,11 33,17 66,17 42,0 63,-15 63,-46 V 965 c 0,-36 -10,-64 -30,-84 L 442,47 C 390,-6 338,-33 285,-33 Z" />
140 </g>
141 <g
142 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
143 id="bullet-char-template-9679">
144 <path
145 id="path1102"
146 d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 c 0,181 53,324 160,431 106,107 249,161 430,161 179,0 323,-54 432,-161 108,-107 162,-251 162,-431 0,-180 -54,-324 -162,-431 C 1136,54 992,0 813,0 Z" />
147 </g>
148 <g
149 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
150 id="bullet-char-template-8226">
151 <path
152 id="path1105"
153 d="m 346,457 c -73,0 -137,26 -191,78 -54,51 -81,114 -81,188 0,73 27,136 81,188 54,52 118,78 191,78 73,0 134,-26 185,-79 51,-51 77,-114 77,-187 0,-75 -25,-137 -76,-188 -50,-52 -112,-78 -186,-78 z" />
154 </g>
155 <g
156 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
157 id="bullet-char-template-8211">
158 <path
159 id="path1108"
160 d="M -4,459 H 1135 V 606 H -4 Z" />
161 </g>
162 <g
163 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)"
164 id="bullet-char-template-61548">
165 <path
166 id="path1111"
167 d="m 173,740 c 0,163 58,303 173,419 116,115 255,173 419,173 163,0 302,-58 418,-173 116,-116 174,-256 174,-419 0,-163 -58,-303 -174,-418 C 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z" />
168 </g>
169 </defs>
170 <sodipodi:namedview
171 inkscape:document-rotation="0"
172 pagecolor="#ffffff"
173 bordercolor="#666666"
174 borderopacity="1"
175 objecttolerance="10"
176 gridtolerance="10"
177 guidetolerance="10"
178 inkscape:pageopacity="0"
179 inkscape:pageshadow="2"
180 inkscape:window-width="1080"
181 inkscape:window-height="755"
182 id="namedview835"
183 showgrid="false"
184 fit-margin-top="0"
185 fit-margin-left="0"
186 fit-margin-right="0"
187 fit-margin-bottom="0"
188 inkscape:zoom="1.4551401"
189 inkscape:cx="51.956823"
190 inkscape:cy="114.5697"
191 inkscape:window-x="150"
192 inkscape:window-y="558"
193 inkscape:window-maximized="0"
194 inkscape:current-layer="g841" />
195 <g
196 inkscape:groupmode="layer"
197 inkscape:label="Image"
198 id="g841"
199 transform="translate(31.615473,2.1993251)">
200 <g
201 id="g1206"
202 transform="translate(25.586963)">
203 <g
204 style="fill:#92130b;fill-opacity:1"
205 transform="translate(-94.217432,-3.0924857)"
206 id="g872">
207 <circle
208 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
209 id="path845"
210 cx="76.717133"
211 cy="20.575476"
212 r="16" />
213 <circle
214 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
215 id="path847"
216 cy="20.250648"
217 cx="48.697315"
218 r="8" />
219 <rect
220 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
221 id="rect851"
222 width="104"
223 height="16"
224 x="49.013115"
225 y="12.250648" />
226 <circle
227 r="8"
228 cx="153.13832"
229 cy="20.250648"
230 id="path847-4"
231 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
232 </g>
233 <g
234 style="fill:#1177b8;fill-opacity:1"
235 transform="matrix(-1,0,0,1,107.6182,42.284511)"
236 id="g872-9">
237 <circle
238 r="16"
239 cy="20.575476"
240 cx="76.717133"
241 id="path845-1"
242 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
243 <circle
244 r="8"
245 cx="48.697315"
246 cy="20.250648"
247 id="path847-41"
248 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
249 <rect
250 y="12.250648"
251 x="49.013115"
252 height="16"
253 width="104"
254 id="rect851-2"
255 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
256 <circle
257 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
258 id="path847-4-1"
259 cy="20.250648"
260 cx="153.13832"
261 r="8" />
262 </g>
263 <g
264 style="fill:#128d15;fill-opacity:1"
265 transform="translate(-94.217432,87.661522)"
266 id="g872-4">
267 <circle
268 r="16"
269 cy="20.575476"
270 cx="76.717133"
271 id="path845-6"
272 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
273 <circle
274 r="8"
275 cx="48.697315"
276 cy="20.250648"
277 id="path847-7"
278 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
279 <rect
280 y="12.250648"
281 x="49.013115"
282 height="16"
283 width="104"
284 id="rect851-27"
285 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
286 <circle
287 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
288 id="path847-4-15"
289 cy="20.250648"
290 cx="153.13832"
291 r="8" />
292 </g>
293 </g>
294 </g>
295 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:ooo="http://xml.openoffice.org/svg/export"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:export-ydpi="169.84"
11 inkscape:export-xdpi="169.84"
12 inkscape:export-filename="/Users/jbednar/param/doc/_static/logo_horizontal.png"
13 inkscape:version="1.0 (4035a4f, 2020-05-01)"
14 sodipodi:docname="logo_horizontal.svg"
15 viewBox="0 0 255.42815 73.894227"
16 height="73.894226"
17 width="255.42815"
18 id="svg833"
19 version="1.1">
20 <metadata
21 id="metadata839">
22 <rdf:RDF>
23 <cc:Work
24 rdf:about="">
25 <dc:format>image/svg+xml</dc:format>
26 <dc:type
27 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
28 <dc:title></dc:title>
29 </cc:Work>
30 </rdf:RDF>
31 </metadata>
32 <defs
33 id="defs837">
34 <clipPath
35 id="presentation_clip_path"
36 clipPathUnits="userSpaceOnUse">
37 <rect
38 x="7731"
39 y="8493"
40 width="4191"
41 height="1678"
42 id="rect1057" />
43
44 </clipPath>
45 <clipPath
46 id="presentation_clip_path_shrink"
47 clipPathUnits="userSpaceOnUse">
48 <rect
49 x="7735"
50 y="8494"
51 width="4183"
52 height="1675"
53 id="rect1060" />
54
55 </clipPath>
56 <font
57 vert-adv-y="1024"
58 vert-origin-y="768"
59 vert-origin-x="512"
60 horiz-origin-y="0"
61 horiz-origin-x="0"
62 id="EmbeddedFont_1"
63 horiz-adv-x="2048">
64 <font-face
65 font-family="Ubuntu embedded"
66 units-per-em="2048"
67 font-weight="bold"
68 font-style="normal"
69 ascent="1918"
70 descent="389"
71 id="font-face1065" />
72
73 <missing-glyph
74 horiz-adv-x="2048"
75 d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"
76 id="missing-glyph1067" />
77
78 <glyph
79 unicode="r"
80 horiz-adv-x="726"
81 d="M 793,807 C 765,814 733,821 696,828 659,836 620,840 578,840 558,840 536,838 509,835 482,831 462,827 449,823 L 449,0 143,0 143,1020 C 198,1039 262,1057 337,1074 411,1091 494,1100 586,1100 602,1100 622,1099 645,1097 668,1095 692,1092 715,1089 738,1085 761,1081 784,1076 808,1071 827,1066 844,1059 Z"
82 id="glyph1069" />
83
84 <glyph
85 unicode="m"
86 horiz-adv-x="1504"
87 d="M 735,571 C 735,670 723,739 697,780 672,821 629,842 567,842 548,842 528,841 508,839 487,837 468,834 449,831 L 449,0 143,0 143,1040 C 169,1047 200,1054 234,1062 269,1069 306,1076 345,1082 384,1089 424,1093 465,1097 506,1100 546,1102 586,1102 664,1102 727,1092 775,1072 824,1052 864,1029 895,1001 939,1033 989,1057 1046,1075 1102,1093 1154,1102 1202,1102 1288,1102 1359,1090 1414,1066 1469,1042 1513,1008 1546,965 1579,921 1602,869 1614,809 1626,749 1632,682 1632,608 L 1632,0 1327,0 1327,571 C 1327,670 1314,739 1289,780 1264,821 1221,842 1159,842 1143,842 1120,838 1091,829 1061,821 1037,811 1018,799 1027,767 1034,734 1036,699 1039,665 1040,627 1040,588 L 1040,0 735,0 Z"
88 id="glyph1071" />
89
90 <glyph
91 unicode="a"
92 horiz-adv-x="960"
93 d="M 555,213 C 585,213 614,214 641,215 668,216 690,218 707,221 L 707,453 C 694,455 676,458 651,461 627,464 604,465 584,465 555,465 528,463 503,460 478,456 455,450 436,440 417,431 402,418 391,401 380,385 375,365 375,340 375,292 391,259 423,241 455,222 499,213 555,213 Z M 530,1106 C 621,1106 696,1096 756,1075 816,1055 864,1025 900,987 936,949 962,902 977,848 992,793 999,733 999,666 L 999,31 C 956,21 895,10 817,-3 739,-16 645,-23 535,-23 465,-23 402,-16 345,-4 288,8 240,28 199,56 158,84 126,121 104,166 83,211 72,266 72,332 72,395 84,448 110,492 135,535 169,570 211,596 253,622 302,641 356,652 411,664 468,670 526,670 566,670 601,668 632,665 663,661 687,657 707,651 L 707,680 C 707,732 691,773 659,805 628,836 573,852 496,852 444,852 393,848 342,841 291,833 248,823 211,809 L 172,1055 C 190,1060 212,1066 239,1072 265,1078 294,1084 326,1089 357,1093 390,1097 425,1101 460,1104 495,1106 530,1106 Z"
94 id="glyph1073" />
95
96 <glyph
97 unicode="P"
98 horiz-adv-x="1115"
99 d="M 590,1436 C 801,1436 964,1398 1077,1324 1191,1250 1247,1128 1247,958 1247,788 1190,665 1075,589 961,513 797,475 584,475 L 483,475 483,0 164,0 164,1399 C 233,1412 307,1422 385,1427 463,1433 531,1436 590,1436 Z M 610,1163 C 587,1163 564,1163 542,1161 519,1160 500,1158 483,1157 L 483,748 584,748 C 694,748 778,763 834,793 890,823 918,879 918,961 918,1000 910,1033 896,1059 882,1085 861,1106 835,1121 808,1137 776,1148 737,1154 699,1160 657,1163 610,1163 Z"
100 id="glyph1075" />
101
102 </font>
103 <g
104 ooo:slide="id1"
105 ooo:id-list="id3"
106 id="g1080" />
107 <g
108 id="bullet-char-template-57356"
109 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
110 <path
111 d="M 580,1141 1163,571 580,0 -4,571 Z"
112 id="path1084" />
113
114 </g>
115 <g
116 id="bullet-char-template-57354"
117 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
118 <path
119 d="M 8,1128 H 1137 V 0 H 8 Z"
120 id="path1087" />
121
122 </g>
123 <g
124 id="bullet-char-template-10146"
125 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
126 <path
127 d="M 174,0 602,739 174,1481 1456,739 Z M 1358,739 309,1346 659,739 Z"
128 id="path1090" />
129
130 </g>
131 <g
132 id="bullet-char-template-10132"
133 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
134 <path
135 d="M 2015,739 1276,0 H 717 l 543,543 H 174 v 393 h 1086 l -543,545 h 557 z"
136 id="path1093" />
137
138 </g>
139 <g
140 id="bullet-char-template-10007"
141 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
142 <path
143 d="m 0,-2 c -7,16 -16,29 -25,39 l 381,530 c -94,256 -141,385 -141,387 0,25 13,38 40,38 9,0 21,-2 34,-5 21,4 42,12 65,25 l 27,-13 111,-251 280,301 64,-25 24,25 c 21,-10 41,-24 62,-43 C 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 c 0,-27 -21,-55 -63,-84 l 16,-20 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 c -22,-34 -53,-51 -92,-51 -42,0 -63,17 -64,51 -7,9 -10,24 -10,44 0,9 1,19 2,30 z"
144 id="path1096" />
145
146 </g>
147 <g
148 id="bullet-char-template-10004"
149 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
150 <path
151 d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 c 0,78 14,145 41,201 34,71 87,106 158,106 53,0 88,-31 106,-94 l 23,-176 c 8,-64 28,-97 59,-98 l 735,706 c 11,11 33,17 66,17 42,0 63,-15 63,-46 V 965 c 0,-36 -10,-64 -30,-84 L 442,47 C 390,-6 338,-33 285,-33 Z"
152 id="path1099" />
153
154 </g>
155 <g
156 id="bullet-char-template-9679"
157 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
158 <path
159 d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 c 0,181 53,324 160,431 106,107 249,161 430,161 179,0 323,-54 432,-161 108,-107 162,-251 162,-431 0,-180 -54,-324 -162,-431 C 1136,54 992,0 813,0 Z"
160 id="path1102" />
161
162 </g>
163 <g
164 id="bullet-char-template-8226"
165 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
166 <path
167 d="m 346,457 c -73,0 -137,26 -191,78 -54,51 -81,114 -81,188 0,73 27,136 81,188 54,52 118,78 191,78 73,0 134,-26 185,-79 51,-51 77,-114 77,-187 0,-75 -25,-137 -76,-188 -50,-52 -112,-78 -186,-78 z"
168 id="path1105" />
169
170 </g>
171 <g
172 id="bullet-char-template-8211"
173 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
174 <path
175 d="M -4,459 H 1135 V 606 H -4 Z"
176 id="path1108" />
177
178 </g>
179 <g
180 id="bullet-char-template-61548"
181 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
182 <path
183 d="m 173,740 c 0,163 58,303 173,419 116,115 255,173 419,173 163,0 302,-58 418,-173 116,-116 174,-256 174,-419 0,-163 -58,-303 -174,-418 C 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"
184 id="path1111" />
185
186 </g>
187 </defs>
188 <sodipodi:namedview
189 inkscape:current-layer="g841"
190 inkscape:window-maximized="1"
191 inkscape:window-y="23"
192 inkscape:window-x="0"
193 inkscape:cy="-41.98869"
194 inkscape:cx="108.87227"
195 inkscape:zoom="1.4551401"
196 fit-margin-bottom="0"
197 fit-margin-right="0"
198 fit-margin-left="0"
199 fit-margin-top="0"
200 showgrid="false"
201 id="namedview835"
202 inkscape:window-height="1875"
203 inkscape:window-width="1080"
204 inkscape:pageshadow="2"
205 inkscape:pageopacity="0"
206 guidetolerance="10"
207 gridtolerance="10"
208 objecttolerance="10"
209 borderopacity="1"
210 bordercolor="#666666"
211 pagecolor="#ffffff" />
212 <g
213 transform="translate(154.84756,-152.29741)"
214 id="g841"
215 inkscape:label="Image"
216 inkscape:groupmode="layer">
217 <g
218 transform="matrix(0.56789885,0,0,0.56789885,-122.36236,153.5464)"
219 id="g1206">
220 <g
221 id="g872"
222 transform="translate(-94.217432,-3.0924857)"
223 style="fill:#92130b;fill-opacity:1">
224 <circle
225 r="16"
226 cy="20.575476"
227 cx="76.717133"
228 id="path845"
229 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
230 <circle
231 r="8"
232 cx="48.697315"
233 cy="20.250648"
234 id="path847"
235 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
236 <rect
237 y="12.250648"
238 x="49.013115"
239 height="16"
240 width="104"
241 id="rect851"
242 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
243 <circle
244 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
245 id="path847-4"
246 cy="20.250648"
247 cx="153.13832"
248 r="8" />
249 </g>
250 <g
251 id="g872-9"
252 transform="matrix(-1,0,0,1,107.6182,42.284511)"
253 style="fill:#1177b8;fill-opacity:1">
254 <circle
255 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
256 id="path845-1"
257 cx="76.717133"
258 cy="20.575476"
259 r="16" />
260 <circle
261 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
262 id="path847-41"
263 cy="20.250648"
264 cx="48.697315"
265 r="8" />
266 <rect
267 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
268 id="rect851-2"
269 width="104"
270 height="16"
271 x="49.013115"
272 y="12.250648" />
273 <circle
274 r="8"
275 cx="153.13832"
276 cy="20.250648"
277 id="path847-4-1"
278 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
279 </g>
280 <g
281 id="g872-4"
282 transform="translate(-94.217432,87.661522)"
283 style="fill:#128d15;fill-opacity:1">
284 <circle
285 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
286 id="path845-6"
287 cx="76.717133"
288 cy="20.575476"
289 r="16" />
290 <circle
291 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
292 id="path847-7"
293 cy="20.250648"
294 cx="48.697315"
295 r="8" />
296 <rect
297 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
298 id="rect851-27"
299 width="104"
300 height="16"
301 x="49.013115"
302 y="12.250648" />
303 <circle
304 r="8"
305 cx="153.13832"
306 cy="20.250648"
307 id="path847-4-15"
308 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
309 </g>
310 </g>
311 <text
312 id="text931"
313 y="211.32452"
314 x="-74.112411"
315 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-3.36px;word-spacing:0px;fill:#1177b8;fill-opacity:1"
316 xml:space="preserve"><tspan
317 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1177b8;fill-opacity:1"
318 y="211.32452"
319 x="-74.112411"
320 id="tspan929"
321 sodipodi:role="line">Param</tspan></text>
322 </g>
323 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:ooo="http://xml.openoffice.org/svg/export"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:export-ydpi="169.84"
11 inkscape:export-xdpi="169.84"
12 inkscape:export-filename="/Users/jbednar/param/doc/_static/logo_horizontal_white.png"
13 inkscape:version="1.0 (4035a4f, 2020-05-01)"
14 sodipodi:docname="logo_horizontal_white.svg"
15 viewBox="0 0 255.42815 73.894227"
16 height="73.894226"
17 width="255.42815"
18 id="svg833"
19 version="1.1">
20 <metadata
21 id="metadata839">
22 <rdf:RDF>
23 <cc:Work
24 rdf:about="">
25 <dc:format>image/svg+xml</dc:format>
26 <dc:type
27 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
28 <dc:title />
29 </cc:Work>
30 </rdf:RDF>
31 </metadata>
32 <defs
33 id="defs837">
34 <clipPath
35 id="presentation_clip_path"
36 clipPathUnits="userSpaceOnUse">
37 <rect
38 x="7731"
39 y="8493"
40 width="4191"
41 height="1678"
42 id="rect1057" />
43 </clipPath>
44 <clipPath
45 id="presentation_clip_path_shrink"
46 clipPathUnits="userSpaceOnUse">
47 <rect
48 x="7735"
49 y="8494"
50 width="4183"
51 height="1675"
52 id="rect1060" />
53 </clipPath>
54 <font
55 vert-adv-y="1024"
56 vert-origin-y="768"
57 vert-origin-x="512"
58 horiz-origin-y="0"
59 horiz-origin-x="0"
60 id="EmbeddedFont_1"
61 horiz-adv-x="2048">
62 <font-face
63 font-family="Ubuntu embedded"
64 units-per-em="2048"
65 font-weight="bold"
66 font-style="normal"
67 ascent="1918"
68 descent="389"
69 id="font-face1065" />
70 <missing-glyph
71 horiz-adv-x="2048"
72 d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"
73 id="missing-glyph1067" />
74 <glyph
75 unicode="r"
76 horiz-adv-x="726"
77 d="M 793,807 C 765,814 733,821 696,828 659,836 620,840 578,840 558,840 536,838 509,835 482,831 462,827 449,823 L 449,0 143,0 143,1020 C 198,1039 262,1057 337,1074 411,1091 494,1100 586,1100 602,1100 622,1099 645,1097 668,1095 692,1092 715,1089 738,1085 761,1081 784,1076 808,1071 827,1066 844,1059 Z"
78 id="glyph1069" />
79 <glyph
80 unicode="m"
81 horiz-adv-x="1504"
82 d="M 735,571 C 735,670 723,739 697,780 672,821 629,842 567,842 548,842 528,841 508,839 487,837 468,834 449,831 L 449,0 143,0 143,1040 C 169,1047 200,1054 234,1062 269,1069 306,1076 345,1082 384,1089 424,1093 465,1097 506,1100 546,1102 586,1102 664,1102 727,1092 775,1072 824,1052 864,1029 895,1001 939,1033 989,1057 1046,1075 1102,1093 1154,1102 1202,1102 1288,1102 1359,1090 1414,1066 1469,1042 1513,1008 1546,965 1579,921 1602,869 1614,809 1626,749 1632,682 1632,608 L 1632,0 1327,0 1327,571 C 1327,670 1314,739 1289,780 1264,821 1221,842 1159,842 1143,842 1120,838 1091,829 1061,821 1037,811 1018,799 1027,767 1034,734 1036,699 1039,665 1040,627 1040,588 L 1040,0 735,0 Z"
83 id="glyph1071" />
84 <glyph
85 unicode="a"
86 horiz-adv-x="960"
87 d="M 555,213 C 585,213 614,214 641,215 668,216 690,218 707,221 L 707,453 C 694,455 676,458 651,461 627,464 604,465 584,465 555,465 528,463 503,460 478,456 455,450 436,440 417,431 402,418 391,401 380,385 375,365 375,340 375,292 391,259 423,241 455,222 499,213 555,213 Z M 530,1106 C 621,1106 696,1096 756,1075 816,1055 864,1025 900,987 936,949 962,902 977,848 992,793 999,733 999,666 L 999,31 C 956,21 895,10 817,-3 739,-16 645,-23 535,-23 465,-23 402,-16 345,-4 288,8 240,28 199,56 158,84 126,121 104,166 83,211 72,266 72,332 72,395 84,448 110,492 135,535 169,570 211,596 253,622 302,641 356,652 411,664 468,670 526,670 566,670 601,668 632,665 663,661 687,657 707,651 L 707,680 C 707,732 691,773 659,805 628,836 573,852 496,852 444,852 393,848 342,841 291,833 248,823 211,809 L 172,1055 C 190,1060 212,1066 239,1072 265,1078 294,1084 326,1089 357,1093 390,1097 425,1101 460,1104 495,1106 530,1106 Z"
88 id="glyph1073" />
89 <glyph
90 unicode="P"
91 horiz-adv-x="1115"
92 d="M 590,1436 C 801,1436 964,1398 1077,1324 1191,1250 1247,1128 1247,958 1247,788 1190,665 1075,589 961,513 797,475 584,475 L 483,475 483,0 164,0 164,1399 C 233,1412 307,1422 385,1427 463,1433 531,1436 590,1436 Z M 610,1163 C 587,1163 564,1163 542,1161 519,1160 500,1158 483,1157 L 483,748 584,748 C 694,748 778,763 834,793 890,823 918,879 918,961 918,1000 910,1033 896,1059 882,1085 861,1106 835,1121 808,1137 776,1148 737,1154 699,1160 657,1163 610,1163 Z"
93 id="glyph1075" />
94 </font>
95 <g
96 ooo:slide="id1"
97 ooo:id-list="id3"
98 id="g1080" />
99 <g
100 id="bullet-char-template-57356"
101 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
102 <path
103 d="M 580,1141 1163,571 580,0 -4,571 Z"
104 id="path1084" />
105 </g>
106 <g
107 id="bullet-char-template-57354"
108 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
109 <path
110 d="M 8,1128 H 1137 V 0 H 8 Z"
111 id="path1087" />
112 </g>
113 <g
114 id="bullet-char-template-10146"
115 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
116 <path
117 d="M 174,0 602,739 174,1481 1456,739 Z M 1358,739 309,1346 659,739 Z"
118 id="path1090" />
119 </g>
120 <g
121 id="bullet-char-template-10132"
122 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
123 <path
124 d="M 2015,739 1276,0 H 717 l 543,543 H 174 v 393 h 1086 l -543,545 h 557 z"
125 id="path1093" />
126 </g>
127 <g
128 id="bullet-char-template-10007"
129 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
130 <path
131 d="m 0,-2 c -7,16 -16,29 -25,39 l 381,530 c -94,256 -141,385 -141,387 0,25 13,38 40,38 9,0 21,-2 34,-5 21,4 42,12 65,25 l 27,-13 111,-251 280,301 64,-25 24,25 c 21,-10 41,-24 62,-43 C 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 c 0,-27 -21,-55 -63,-84 l 16,-20 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 c -22,-34 -53,-51 -92,-51 -42,0 -63,17 -64,51 -7,9 -10,24 -10,44 0,9 1,19 2,30 z"
132 id="path1096" />
133 </g>
134 <g
135 id="bullet-char-template-10004"
136 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
137 <path
138 d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 c 0,78 14,145 41,201 34,71 87,106 158,106 53,0 88,-31 106,-94 l 23,-176 c 8,-64 28,-97 59,-98 l 735,706 c 11,11 33,17 66,17 42,0 63,-15 63,-46 V 965 c 0,-36 -10,-64 -30,-84 L 442,47 C 390,-6 338,-33 285,-33 Z"
139 id="path1099" />
140 </g>
141 <g
142 id="bullet-char-template-9679"
143 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
144 <path
145 d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 c 0,181 53,324 160,431 106,107 249,161 430,161 179,0 323,-54 432,-161 108,-107 162,-251 162,-431 0,-180 -54,-324 -162,-431 C 1136,54 992,0 813,0 Z"
146 id="path1102" />
147 </g>
148 <g
149 id="bullet-char-template-8226"
150 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
151 <path
152 d="m 346,457 c -73,0 -137,26 -191,78 -54,51 -81,114 -81,188 0,73 27,136 81,188 54,52 118,78 191,78 73,0 134,-26 185,-79 51,-51 77,-114 77,-187 0,-75 -25,-137 -76,-188 -50,-52 -112,-78 -186,-78 z"
153 id="path1105" />
154 </g>
155 <g
156 id="bullet-char-template-8211"
157 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
158 <path
159 d="M -4,459 H 1135 V 606 H -4 Z"
160 id="path1108" />
161 </g>
162 <g
163 id="bullet-char-template-61548"
164 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
165 <path
166 d="m 173,740 c 0,163 58,303 173,419 116,115 255,173 419,173 163,0 302,-58 418,-173 116,-116 174,-256 174,-419 0,-163 -58,-303 -174,-418 C 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"
167 id="path1111" />
168 </g>
169 </defs>
170 <sodipodi:namedview
171 inkscape:current-layer="g841"
172 inkscape:window-maximized="1"
173 inkscape:window-y="23"
174 inkscape:window-x="0"
175 inkscape:cy="-166.37534"
176 inkscape:cx="118.25941"
177 inkscape:zoom="1.4551401"
178 fit-margin-bottom="0"
179 fit-margin-right="0"
180 fit-margin-left="0"
181 fit-margin-top="0"
182 showgrid="false"
183 id="namedview835"
184 inkscape:window-height="1875"
185 inkscape:window-width="1080"
186 inkscape:pageshadow="2"
187 inkscape:pageopacity="0"
188 guidetolerance="10"
189 gridtolerance="10"
190 objecttolerance="10"
191 borderopacity="1"
192 bordercolor="#666666"
193 pagecolor="#ffffff"
194 inkscape:document-rotation="0" />
195 <g
196 transform="translate(154.84756,-152.29741)"
197 id="g841"
198 inkscape:label="Image"
199 inkscape:groupmode="layer">
200 <g
201 transform="matrix(0.56789885,0,0,0.56789885,-122.36236,153.5464)"
202 id="g1206">
203 <g
204 id="g872"
205 transform="translate(-94.217432,-3.0924857)"
206 style="fill:#92130b;fill-opacity:1">
207 <circle
208 r="16"
209 cy="20.575476"
210 cx="76.717133"
211 id="path845"
212 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
213 <circle
214 r="8"
215 cx="48.697315"
216 cy="20.250648"
217 id="path847"
218 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
219 <rect
220 y="12.250648"
221 x="49.013115"
222 height="16"
223 width="104"
224 id="rect851"
225 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
226 <circle
227 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
228 id="path847-4"
229 cy="20.250648"
230 cx="153.13832"
231 r="8" />
232 </g>
233 <g
234 id="g872-9"
235 transform="matrix(-1,0,0,1,107.6182,42.284511)"
236 style="fill:#1177b8;fill-opacity:1">
237 <circle
238 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
239 id="path845-1"
240 cx="76.717133"
241 cy="20.575476"
242 r="16" />
243 <circle
244 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
245 id="path847-41"
246 cy="20.250648"
247 cx="48.697315"
248 r="8" />
249 <rect
250 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
251 id="rect851-2"
252 width="104"
253 height="16"
254 x="49.013115"
255 y="12.250648" />
256 <circle
257 r="8"
258 cx="153.13832"
259 cy="20.250648"
260 id="path847-4-1"
261 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
262 </g>
263 <g
264 id="g872-4"
265 transform="translate(-94.217432,87.661522)"
266 style="fill:#128d15;fill-opacity:1">
267 <circle
268 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
269 id="path845-6"
270 cx="76.717133"
271 cy="20.575476"
272 r="16" />
273 <circle
274 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
275 id="path847-7"
276 cy="20.250648"
277 cx="48.697315"
278 r="8" />
279 <rect
280 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
281 id="rect851-27"
282 width="104"
283 height="16"
284 x="49.013115"
285 y="12.250648" />
286 <circle
287 r="8"
288 cx="153.13832"
289 cy="20.250648"
290 id="path847-4-15"
291 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
292 </g>
293 </g>
294 <text
295 inkscape:export-ydpi="169.84"
296 inkscape:export-xdpi="169.84"
297 id="text931"
298 y="211.32452"
299 x="-74.112411"
300 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-3.35999999999999988px;word-spacing:0px;fill:#f2f2f2;fill-opacity:1;"
301 xml:space="preserve"><tspan
302 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#f2f2f2;fill-opacity:1;"
303 y="211.32452"
304 x="-74.112411"
305 id="tspan929"
306 sodipodi:role="line">Param</tspan></text>
307 </g>
308 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:ooo="http://xml.openoffice.org/svg/export"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:version="1.0 (4035a4f, 2020-05-01)"
11 sodipodi:docname="logo_stacked.svg"
12 viewBox="0 0 169.573 196.6991"
13 height="196.6991"
14 width="169.573"
15 id="svg833"
16 version="1.1">
17 <metadata
18 id="metadata839">
19 <rdf:RDF>
20 <cc:Work
21 rdf:about="">
22 <dc:format>image/svg+xml</dc:format>
23 <dc:type
24 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25 <dc:title></dc:title>
26 </cc:Work>
27 </rdf:RDF>
28 </metadata>
29 <defs
30 id="defs837">
31 <clipPath
32 id="presentation_clip_path"
33 clipPathUnits="userSpaceOnUse">
34 <rect
35 x="7731"
36 y="8493"
37 width="4191"
38 height="1678"
39 id="rect1057" />
40
41 </clipPath>
42 <clipPath
43 id="presentation_clip_path_shrink"
44 clipPathUnits="userSpaceOnUse">
45 <rect
46 x="7735"
47 y="8494"
48 width="4183"
49 height="1675"
50 id="rect1060" />
51
52 </clipPath>
53 <font
54 vert-adv-y="1024"
55 vert-origin-y="768"
56 vert-origin-x="512"
57 horiz-origin-y="0"
58 horiz-origin-x="0"
59 id="EmbeddedFont_1"
60 horiz-adv-x="2048">
61 <font-face
62 font-family="Ubuntu embedded"
63 units-per-em="2048"
64 font-weight="bold"
65 font-style="normal"
66 ascent="1918"
67 descent="389"
68 id="font-face1065" />
69
70 <missing-glyph
71 horiz-adv-x="2048"
72 d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"
73 id="missing-glyph1067" />
74
75 <glyph
76 unicode="r"
77 horiz-adv-x="726"
78 d="M 793,807 C 765,814 733,821 696,828 659,836 620,840 578,840 558,840 536,838 509,835 482,831 462,827 449,823 L 449,0 143,0 143,1020 C 198,1039 262,1057 337,1074 411,1091 494,1100 586,1100 602,1100 622,1099 645,1097 668,1095 692,1092 715,1089 738,1085 761,1081 784,1076 808,1071 827,1066 844,1059 Z"
79 id="glyph1069" />
80
81 <glyph
82 unicode="m"
83 horiz-adv-x="1504"
84 d="M 735,571 C 735,670 723,739 697,780 672,821 629,842 567,842 548,842 528,841 508,839 487,837 468,834 449,831 L 449,0 143,0 143,1040 C 169,1047 200,1054 234,1062 269,1069 306,1076 345,1082 384,1089 424,1093 465,1097 506,1100 546,1102 586,1102 664,1102 727,1092 775,1072 824,1052 864,1029 895,1001 939,1033 989,1057 1046,1075 1102,1093 1154,1102 1202,1102 1288,1102 1359,1090 1414,1066 1469,1042 1513,1008 1546,965 1579,921 1602,869 1614,809 1626,749 1632,682 1632,608 L 1632,0 1327,0 1327,571 C 1327,670 1314,739 1289,780 1264,821 1221,842 1159,842 1143,842 1120,838 1091,829 1061,821 1037,811 1018,799 1027,767 1034,734 1036,699 1039,665 1040,627 1040,588 L 1040,0 735,0 Z"
85 id="glyph1071" />
86
87 <glyph
88 unicode="a"
89 horiz-adv-x="960"
90 d="M 555,213 C 585,213 614,214 641,215 668,216 690,218 707,221 L 707,453 C 694,455 676,458 651,461 627,464 604,465 584,465 555,465 528,463 503,460 478,456 455,450 436,440 417,431 402,418 391,401 380,385 375,365 375,340 375,292 391,259 423,241 455,222 499,213 555,213 Z M 530,1106 C 621,1106 696,1096 756,1075 816,1055 864,1025 900,987 936,949 962,902 977,848 992,793 999,733 999,666 L 999,31 C 956,21 895,10 817,-3 739,-16 645,-23 535,-23 465,-23 402,-16 345,-4 288,8 240,28 199,56 158,84 126,121 104,166 83,211 72,266 72,332 72,395 84,448 110,492 135,535 169,570 211,596 253,622 302,641 356,652 411,664 468,670 526,670 566,670 601,668 632,665 663,661 687,657 707,651 L 707,680 C 707,732 691,773 659,805 628,836 573,852 496,852 444,852 393,848 342,841 291,833 248,823 211,809 L 172,1055 C 190,1060 212,1066 239,1072 265,1078 294,1084 326,1089 357,1093 390,1097 425,1101 460,1104 495,1106 530,1106 Z"
91 id="glyph1073" />
92
93 <glyph
94 unicode="P"
95 horiz-adv-x="1115"
96 d="M 590,1436 C 801,1436 964,1398 1077,1324 1191,1250 1247,1128 1247,958 1247,788 1190,665 1075,589 961,513 797,475 584,475 L 483,475 483,0 164,0 164,1399 C 233,1412 307,1422 385,1427 463,1433 531,1436 590,1436 Z M 610,1163 C 587,1163 564,1163 542,1161 519,1160 500,1158 483,1157 L 483,748 584,748 C 694,748 778,763 834,793 890,823 918,879 918,961 918,1000 910,1033 896,1059 882,1085 861,1106 835,1121 808,1137 776,1148 737,1154 699,1160 657,1163 610,1163 Z"
97 id="glyph1075" />
98
99 </font>
100 <g
101 ooo:slide="id1"
102 ooo:id-list="id3"
103 id="g1080" />
104 <g
105 id="bullet-char-template-57356"
106 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
107 <path
108 d="M 580,1141 1163,571 580,0 -4,571 Z"
109 id="path1084" />
110
111 </g>
112 <g
113 id="bullet-char-template-57354"
114 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
115 <path
116 d="M 8,1128 H 1137 V 0 H 8 Z"
117 id="path1087" />
118
119 </g>
120 <g
121 id="bullet-char-template-10146"
122 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
123 <path
124 d="M 174,0 602,739 174,1481 1456,739 Z M 1358,739 309,1346 659,739 Z"
125 id="path1090" />
126
127 </g>
128 <g
129 id="bullet-char-template-10132"
130 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
131 <path
132 d="M 2015,739 1276,0 H 717 l 543,543 H 174 v 393 h 1086 l -543,545 h 557 z"
133 id="path1093" />
134
135 </g>
136 <g
137 id="bullet-char-template-10007"
138 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
139 <path
140 d="m 0,-2 c -7,16 -16,29 -25,39 l 381,530 c -94,256 -141,385 -141,387 0,25 13,38 40,38 9,0 21,-2 34,-5 21,4 42,12 65,25 l 27,-13 111,-251 280,301 64,-25 24,25 c 21,-10 41,-24 62,-43 C 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 c 0,-27 -21,-55 -63,-84 l 16,-20 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 c -22,-34 -53,-51 -92,-51 -42,0 -63,17 -64,51 -7,9 -10,24 -10,44 0,9 1,19 2,30 z"
141 id="path1096" />
142
143 </g>
144 <g
145 id="bullet-char-template-10004"
146 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
147 <path
148 d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 c 0,78 14,145 41,201 34,71 87,106 158,106 53,0 88,-31 106,-94 l 23,-176 c 8,-64 28,-97 59,-98 l 735,706 c 11,11 33,17 66,17 42,0 63,-15 63,-46 V 965 c 0,-36 -10,-64 -30,-84 L 442,47 C 390,-6 338,-33 285,-33 Z"
149 id="path1099" />
150
151 </g>
152 <g
153 id="bullet-char-template-9679"
154 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
155 <path
156 d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 c 0,181 53,324 160,431 106,107 249,161 430,161 179,0 323,-54 432,-161 108,-107 162,-251 162,-431 0,-180 -54,-324 -162,-431 C 1136,54 992,0 813,0 Z"
157 id="path1102" />
158
159 </g>
160 <g
161 id="bullet-char-template-8226"
162 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
163 <path
164 d="m 346,457 c -73,0 -137,26 -191,78 -54,51 -81,114 -81,188 0,73 27,136 81,188 54,52 118,78 191,78 73,0 134,-26 185,-79 51,-51 77,-114 77,-187 0,-75 -25,-137 -76,-188 -50,-52 -112,-78 -186,-78 z"
165 id="path1105" />
166
167 </g>
168 <g
169 id="bullet-char-template-8211"
170 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
171 <path
172 d="M -4,459 H 1135 V 606 H -4 Z"
173 id="path1108" />
174
175 </g>
176 <g
177 id="bullet-char-template-61548"
178 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
179 <path
180 d="m 173,740 c 0,163 58,303 173,419 116,115 255,173 419,173 163,0 302,-58 418,-173 116,-116 174,-256 174,-419 0,-163 -58,-303 -174,-418 C 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"
181 id="path1111" />
182
183 </g>
184 </defs>
185 <sodipodi:namedview
186 inkscape:current-layer="g841"
187 inkscape:window-maximized="0"
188 inkscape:window-y="726"
189 inkscape:window-x="-11"
190 inkscape:cy="107.69751"
191 inkscape:cx="73.871331"
192 inkscape:zoom="1.4551401"
193 fit-margin-bottom="0"
194 fit-margin-right="0"
195 fit-margin-left="0"
196 fit-margin-top="0"
197 showgrid="false"
198 id="namedview835"
199 inkscape:window-height="755"
200 inkscape:window-width="1245"
201 inkscape:pageshadow="2"
202 inkscape:pageopacity="0"
203 guidetolerance="10"
204 gridtolerance="10"
205 objecttolerance="10"
206 borderopacity="1"
207 bordercolor="#666666"
208 pagecolor="#ffffff" />
209 <g
210 transform="translate(52.499152,2.1993251)"
211 id="g841"
212 inkscape:label="Image"
213 inkscape:groupmode="layer">
214 <g
215 transform="translate(25.586963)"
216 id="g1206">
217 <g
218 id="g872"
219 transform="translate(-94.217432,-3.0924857)"
220 style="fill:#92130b;fill-opacity:1">
221 <circle
222 r="16"
223 cy="20.575476"
224 cx="76.717133"
225 id="path845"
226 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
227 <circle
228 r="8"
229 cx="48.697315"
230 cy="20.250648"
231 id="path847"
232 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
233 <rect
234 y="12.250648"
235 x="49.013115"
236 height="16"
237 width="104"
238 id="rect851"
239 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
240 <circle
241 style="fill:#92130b;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
242 id="path847-4"
243 cy="20.250648"
244 cx="153.13832"
245 r="8" />
246 </g>
247 <g
248 id="g872-9"
249 transform="matrix(-1,0,0,1,107.6182,42.284511)"
250 style="fill:#1177b8;fill-opacity:1">
251 <circle
252 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
253 id="path845-1"
254 cx="76.717133"
255 cy="20.575476"
256 r="16" />
257 <circle
258 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
259 id="path847-41"
260 cy="20.250648"
261 cx="48.697315"
262 r="8" />
263 <rect
264 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
265 id="rect851-2"
266 width="104"
267 height="16"
268 x="49.013115"
269 y="12.250648" />
270 <circle
271 r="8"
272 cx="153.13832"
273 cy="20.250648"
274 id="path847-4-1"
275 style="fill:#1177b8;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
276 </g>
277 <g
278 id="g872-4"
279 transform="translate(-94.217432,87.661522)"
280 style="fill:#128d15;fill-opacity:1">
281 <circle
282 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
283 id="path845-6"
284 cx="76.717133"
285 cy="20.575476"
286 r="16" />
287 <circle
288 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
289 id="path847-7"
290 cy="20.250648"
291 cx="48.697315"
292 r="8" />
293 <rect
294 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293"
295 id="rect851-27"
296 width="104"
297 height="16"
298 x="49.013115"
299 y="12.250648" />
300 <circle
301 r="8"
302 cx="153.13832"
303 cy="20.250648"
304 id="path847-4-15"
305 style="fill:#128d15;fill-opacity:1;stroke:#000000;stroke-width:7.36463;stroke-dasharray:0, 81.0109;stroke-dashoffset:14.7293" />
306 </g>
307 </g>
308 <text
309 id="text931"
310 y="193.79578"
311 x="-57.619152"
312 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-3.36px;word-spacing:0px;fill:#1177b8;fill-opacity:1"
313 xml:space="preserve"><tspan
314 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1177b8;fill-opacity:1"
315 y="193.79578"
316 x="-57.619152"
317 id="tspan929"
318 sodipodi:role="line">Param</tspan></text>
319 </g>
320 </svg>
0 .prompt {
1 display: none;
2 }
3
4 .cell_output {
5 padding-left: 0;
6 }
7
8 @media (min-width: 1200px) {
9 .container, .container-lg, .container-md, .container-sm, .container-xl {
10 max-width: 1600px;
11 }
12 }
Binary diff not shown
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:ooo="http://xml.openoffice.org/svg/export"
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:export-ydpi="169.84"
11 inkscape:export-xdpi="169.84"
12 inkscape:export-filename="/Users/jbednar/param/doc/_static/wordmark.png"
13 inkscape:version="1.0 (4035a4f, 2020-05-01)"
14 sodipodi:docname="wordmark.svg"
15 viewBox="0 0 169.573 45.568001"
16 height="45.568001"
17 width="169.573"
18 id="svg833"
19 version="1.1">
20 <metadata
21 id="metadata839">
22 <rdf:RDF>
23 <cc:Work
24 rdf:about="">
25 <dc:format>image/svg+xml</dc:format>
26 <dc:type
27 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
28 <dc:title></dc:title>
29 </cc:Work>
30 </rdf:RDF>
31 </metadata>
32 <defs
33 id="defs837">
34 <clipPath
35 id="presentation_clip_path"
36 clipPathUnits="userSpaceOnUse">
37 <rect
38 x="7731"
39 y="8493"
40 width="4191"
41 height="1678"
42 id="rect1057" />
43
44 </clipPath>
45 <clipPath
46 id="presentation_clip_path_shrink"
47 clipPathUnits="userSpaceOnUse">
48 <rect
49 x="7735"
50 y="8494"
51 width="4183"
52 height="1675"
53 id="rect1060" />
54
55 </clipPath>
56 <font
57 vert-adv-y="1024"
58 vert-origin-y="768"
59 vert-origin-x="512"
60 horiz-origin-y="0"
61 horiz-origin-x="0"
62 id="EmbeddedFont_1"
63 horiz-adv-x="2048">
64 <font-face
65 font-family="Ubuntu embedded"
66 units-per-em="2048"
67 font-weight="bold"
68 font-style="normal"
69 ascent="1918"
70 descent="389"
71 id="font-face1065" />
72
73 <missing-glyph
74 horiz-adv-x="2048"
75 d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"
76 id="missing-glyph1067" />
77
78 <glyph
79 unicode="r"
80 horiz-adv-x="726"
81 d="M 793,807 C 765,814 733,821 696,828 659,836 620,840 578,840 558,840 536,838 509,835 482,831 462,827 449,823 L 449,0 143,0 143,1020 C 198,1039 262,1057 337,1074 411,1091 494,1100 586,1100 602,1100 622,1099 645,1097 668,1095 692,1092 715,1089 738,1085 761,1081 784,1076 808,1071 827,1066 844,1059 Z"
82 id="glyph1069" />
83
84 <glyph
85 unicode="m"
86 horiz-adv-x="1504"
87 d="M 735,571 C 735,670 723,739 697,780 672,821 629,842 567,842 548,842 528,841 508,839 487,837 468,834 449,831 L 449,0 143,0 143,1040 C 169,1047 200,1054 234,1062 269,1069 306,1076 345,1082 384,1089 424,1093 465,1097 506,1100 546,1102 586,1102 664,1102 727,1092 775,1072 824,1052 864,1029 895,1001 939,1033 989,1057 1046,1075 1102,1093 1154,1102 1202,1102 1288,1102 1359,1090 1414,1066 1469,1042 1513,1008 1546,965 1579,921 1602,869 1614,809 1626,749 1632,682 1632,608 L 1632,0 1327,0 1327,571 C 1327,670 1314,739 1289,780 1264,821 1221,842 1159,842 1143,842 1120,838 1091,829 1061,821 1037,811 1018,799 1027,767 1034,734 1036,699 1039,665 1040,627 1040,588 L 1040,0 735,0 Z"
88 id="glyph1071" />
89
90 <glyph
91 unicode="a"
92 horiz-adv-x="960"
93 d="M 555,213 C 585,213 614,214 641,215 668,216 690,218 707,221 L 707,453 C 694,455 676,458 651,461 627,464 604,465 584,465 555,465 528,463 503,460 478,456 455,450 436,440 417,431 402,418 391,401 380,385 375,365 375,340 375,292 391,259 423,241 455,222 499,213 555,213 Z M 530,1106 C 621,1106 696,1096 756,1075 816,1055 864,1025 900,987 936,949 962,902 977,848 992,793 999,733 999,666 L 999,31 C 956,21 895,10 817,-3 739,-16 645,-23 535,-23 465,-23 402,-16 345,-4 288,8 240,28 199,56 158,84 126,121 104,166 83,211 72,266 72,332 72,395 84,448 110,492 135,535 169,570 211,596 253,622 302,641 356,652 411,664 468,670 526,670 566,670 601,668 632,665 663,661 687,657 707,651 L 707,680 C 707,732 691,773 659,805 628,836 573,852 496,852 444,852 393,848 342,841 291,833 248,823 211,809 L 172,1055 C 190,1060 212,1066 239,1072 265,1078 294,1084 326,1089 357,1093 390,1097 425,1101 460,1104 495,1106 530,1106 Z"
94 id="glyph1073" />
95
96 <glyph
97 unicode="P"
98 horiz-adv-x="1115"
99 d="M 590,1436 C 801,1436 964,1398 1077,1324 1191,1250 1247,1128 1247,958 1247,788 1190,665 1075,589 961,513 797,475 584,475 L 483,475 483,0 164,0 164,1399 C 233,1412 307,1422 385,1427 463,1433 531,1436 590,1436 Z M 610,1163 C 587,1163 564,1163 542,1161 519,1160 500,1158 483,1157 L 483,748 584,748 C 694,748 778,763 834,793 890,823 918,879 918,961 918,1000 910,1033 896,1059 882,1085 861,1106 835,1121 808,1137 776,1148 737,1154 699,1160 657,1163 610,1163 Z"
100 id="glyph1075" />
101
102 </font>
103 <g
104 ooo:slide="id1"
105 ooo:id-list="id3"
106 id="g1080" />
107 <g
108 id="bullet-char-template-57356"
109 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
110 <path
111 d="M 580,1141 1163,571 580,0 -4,571 Z"
112 id="path1084" />
113
114 </g>
115 <g
116 id="bullet-char-template-57354"
117 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
118 <path
119 d="M 8,1128 H 1137 V 0 H 8 Z"
120 id="path1087" />
121
122 </g>
123 <g
124 id="bullet-char-template-10146"
125 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
126 <path
127 d="M 174,0 602,739 174,1481 1456,739 Z M 1358,739 309,1346 659,739 Z"
128 id="path1090" />
129
130 </g>
131 <g
132 id="bullet-char-template-10132"
133 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
134 <path
135 d="M 2015,739 1276,0 H 717 l 543,543 H 174 v 393 h 1086 l -543,545 h 557 z"
136 id="path1093" />
137
138 </g>
139 <g
140 id="bullet-char-template-10007"
141 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
142 <path
143 d="m 0,-2 c -7,16 -16,29 -25,39 l 381,530 c -94,256 -141,385 -141,387 0,25 13,38 40,38 9,0 21,-2 34,-5 21,4 42,12 65,25 l 27,-13 111,-251 280,301 64,-25 24,25 c 21,-10 41,-24 62,-43 C 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 c 0,-27 -21,-55 -63,-84 l 16,-20 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 c -22,-34 -53,-51 -92,-51 -42,0 -63,17 -64,51 -7,9 -10,24 -10,44 0,9 1,19 2,30 z"
144 id="path1096" />
145
146 </g>
147 <g
148 id="bullet-char-template-10004"
149 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
150 <path
151 d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 c 0,78 14,145 41,201 34,71 87,106 158,106 53,0 88,-31 106,-94 l 23,-176 c 8,-64 28,-97 59,-98 l 735,706 c 11,11 33,17 66,17 42,0 63,-15 63,-46 V 965 c 0,-36 -10,-64 -30,-84 L 442,47 C 390,-6 338,-33 285,-33 Z"
152 id="path1099" />
153
154 </g>
155 <g
156 id="bullet-char-template-9679"
157 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
158 <path
159 d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 c 0,181 53,324 160,431 106,107 249,161 430,161 179,0 323,-54 432,-161 108,-107 162,-251 162,-431 0,-180 -54,-324 -162,-431 C 1136,54 992,0 813,0 Z"
160 id="path1102" />
161
162 </g>
163 <g
164 id="bullet-char-template-8226"
165 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
166 <path
167 d="m 346,457 c -73,0 -137,26 -191,78 -54,51 -81,114 -81,188 0,73 27,136 81,188 54,52 118,78 191,78 73,0 134,-26 185,-79 51,-51 77,-114 77,-187 0,-75 -25,-137 -76,-188 -50,-52 -112,-78 -186,-78 z"
168 id="path1105" />
169
170 </g>
171 <g
172 id="bullet-char-template-8211"
173 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
174 <path
175 d="M -4,459 H 1135 V 606 H -4 Z"
176 id="path1108" />
177
178 </g>
179 <g
180 id="bullet-char-template-61548"
181 transform="matrix(4.8828125e-4,0,0,-4.8828125e-4,0,0)">
182 <path
183 d="m 173,740 c 0,163 58,303 173,419 116,115 255,173 419,173 163,0 302,-58 418,-173 116,-116 174,-256 174,-419 0,-163 -58,-303 -174,-418 C 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"
184 id="path1111" />
185
186 </g>
187 </defs>
188 <sodipodi:namedview
189 inkscape:current-layer="g841"
190 inkscape:window-maximized="1"
191 inkscape:window-y="23"
192 inkscape:window-x="0"
193 inkscape:cy="-56.151804"
194 inkscape:cx="23.017123"
195 inkscape:zoom="1.4551401"
196 fit-margin-bottom="0"
197 fit-margin-right="0"
198 fit-margin-left="0"
199 fit-margin-top="0"
200 showgrid="false"
201 id="namedview835"
202 inkscape:window-height="1875"
203 inkscape:window-width="1080"
204 inkscape:pageshadow="2"
205 inkscape:pageopacity="0"
206 guidetolerance="10"
207 gridtolerance="10"
208 objecttolerance="10"
209 borderopacity="1"
210 bordercolor="#666666"
211 pagecolor="#ffffff" />
212 <g
213 transform="translate(68.992411,-166.46052)"
214 id="g841"
215 inkscape:label="Image"
216 inkscape:groupmode="layer">
217 <text
218 id="text931"
219 y="211.32452"
220 x="-74.112411"
221 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-3.36px;word-spacing:0px;fill:#1177b8;fill-opacity:1"
222 xml:space="preserve"><tspan
223 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1177b8;fill-opacity:1"
224 y="211.32452"
225 x="-74.112411"
226 id="tspan929"
227 sodipodi:role="line">Param</tspan></text>
228 </g>
229 </svg>
0 # About
1
2 Param is completely open source, available under a [BSD license](https://github.com/holoviz/param/blob/master/LICENSE.txt), freely for both commercial and non-commercial use. Param was originally developed at the University of Texas at Austin and the University of Edinburgh with funding from the US National Institutes of Health grant 1R01-MH66991. Param is now maintained by [Anaconda Inc.](https://anaconda.com) and by community contributors.
3
4 Param is maintained as part of the [HoloViz](https://holoviz.org) family of tools. The [holoviz.org](https://holoviz.org) website shows how to use Param together with other libraries to solve complex problems, with detailed tutorials and examples. Each of the HoloViz tools builds on Param, as do many of the example projects at [examples.pyviz.org](https://examples.pyviz.org).
5
6 If you have any questions or usage issues visit the [Param Discourse site](https://discourse.holoviz.org/c/param), and if you want to report bugs or request new features, first see if it's already in our list of [open issues](https://github.com/holoviz/param/issues) and then add to the issue or open a new one if needed.
7
8 If you like Param and have built something you want to share, tweet a link or screenshot of your latest creation at [@HoloViz_org](https://twitter.com/HoloViz_org). Thanks!
0 # Comparison to other approaches
1
2 Param was first developed in 2003 for Python 2.1 as part of a long-running brain simulation [project](https://topographica.org), and was made into a separate package on [Github](https://github.com/holoviz/param/graphs/contributors) in 2012. In the interim a variety of other libraries solving some of the same problems have been developed, including:
3
4 - [Traits](http://code.enthought.com/projects/traits)
5 - [Traitlets](https://github.com/ipython/traitlets)
6 - [attrs](https://github.com/python-attrs/attrs) (with optional [attrs-strict](https://github.com/bloomberg/attrs-strict))
7 - [Django models](https://docs.djangoproject.com/en/3.1/topics/db/models)
8 - [Pydantic](https://pydantic-docs.helpmanual.io)
9
10 Plus, Python itself has incorporated mechanisms addressing some of the same issues:
11
12 - [Python 3.6+ type annotations](https://www.python.org/dev/peps/pep-0526/)
13 - [Python 3.7+ data classes](https://docs.python.org/3/library/dataclasses.html)
14 - [Python 2.6+ namedtuples](https://docs.python.org/3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields)
15 - [Python 2.2+ properties](https://docs.python.org/3/library/functions.html#property)
16
17 Each of these approaches overlaps with some but by no means all of the functionality provided by Param, as described below. Also see the comparisons provided with [attrs](https://www.attrs.org/en/stable/why.html) and by an [attr user](https://glyph.twistedmatrix.com/2016/08/attrs.html), which were written about `attrs` but also apply just as well to Param (with Param differing in also providing e.g. GUI support as listed below). [Other info](https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/) comparing `attrs` to `pydantic` is also available.
18
19 Here we will use the word "parameter" as a generic term for a Python attribute, a Param Parameter, a Traitlets/HasTraits trait, or an attr `attr.ib`.
20
21
22 ## Brevity of code
23
24 Python properties can be used to express nearly anything Param or Traitlets can do, but they require at least an order of magnitude more code to do it. You can think of Param and Traitlets as a pre-written implementation of a Python property that implements a configurable parameter. Avoiding having to write that code each time is a big win, because configurable parameters are all over any Python codebase, and Parameter/attr.ib/pydantic/Traits-based approaches lead to much simpler and more maintainable codebases.
25
26 Specifically, where Param or Traitlets can express an automatically validated type and bounds on an attribute in a simple and localized one-line declaration like `a = param.Integer(5, bounds=(1,10))`, implementing the same functionality using properties requires changes to the constructor plus separate explicit `get` and `set` methods, each with at least a half-dozen lines of validation code. Though this get/set/validate code may seem easy to write, it is difficult to read, difficult to maintain, and difficult to make comprehensive or exhaustive.
27 In practice, most programmers simply skip validation or implement it only partially, leaving their code behaving in undefined ways for unexpected inputs. With Param or Traitlets, you don't have to choose between short/readable/maintainable code and heavily validated code; you can have both for far less work!
28
29 `pydantic` and `attrs` provide many of these same benefits, though `attrs` type and bounds validation is treated as an extra step that is more general but also typically much more verbose.
30
31 ## Runtime checking
32
33 Python 3 type annotations allow users to specify types for attributes and function returns, but these types are not normally checked at runtime, and so they do not have the same role of validating user input or programmer error as the type declarations in Params, Traits, Traitlets, pydantic, and attr. They also are limited to the type, so they cannot enforce constraints on range ('state' must be in the list ['Alabama', 'Alaska',...]). Thus even if type hinting is used, programmers still need to write code to actually validate the inputs to functions and methods, which is the role of packages like Param and Traitlets. Note that Pydantic focuses on [generating valid outputs at runtime](https://github.com/samuelcolvin/pydantic/issues/578focused) rather than detecting invalid inputs, so it may or may not be suitable for the same types of applications as the other libraries discussed here.
34
35
36 ## Generality and ease of integration with your project
37
38 The various Python features listed above are part of the standard library with the versions indicated above, and so do not add any dependencies at all to your build process, as long as you restrict yourself to the Python versions where that support was added.
39
40 Param, Traitlets, Pydantic, and attrs are all pure Python projects, with minimal dependencies, and so adding them to any project is generally straightforward. They also support a wide range of Python versions, making them usable in cases where the more recent Python-language features are not available.
41
42 Django models offer some of the same ways to declare parameters and generate web-based GUIs (below), but require the extensive Django web framework and normally rely on a database and web server, which in practice limit their usage to users building dedicated web sites, unlike the no-dependency Param and attrs libraries that can be added to Python projects of any type.
43
44 Traits is a very heavyweight solution, requiring installation and C compilation of a large suite of tools, which makes it difficult to include in separate projects.
45
46 ## GUI toolkits
47
48 Several of these packages support automatically mapping parameters/traits/attributes into GUI widgets. Although any of them could in principle be supported for any GUI toolkit, only certain GUI interfaces are currently available:
49
50 - Panel: Jupyter and Bokeh-server support for Param, mapping Parameters to widgets and ParameterizedObjects to sets of widgets
51 - ParamTk: (unsupported) TKinter support for Param
52 - IPywidgets: Jupyter support for Traitlets, but without automatic mapping from trait to widget
53 - TraitsUI: wxWidgets and Qt support for Traits
54
55 ## Dynamic values
56
57 Param, Traits, Traitlets, Pydantic, and attrs all allow any Python expression to be supplied for initializing parameters, allowing parameter default values to be computed at the time a module is first loaded. Pydantic, Traits, and Traitlets also allow a class author to add code for a given parameter to compute a default value on first access.
58
59 ```python
60 >>> from time import time, sleep
61 >>> import traitlets as tr
62 >>> class A(tr.HasTraits):
63 ... instantiation_time = tr.Float()
64 ... @tr.default('instantiation_time')
65 ... def _look_up_time(self):
66 ... return time()
67 ...
68 >>> a=A()
69 >>> time()
70 1634594159.2040331
71 >>> sleep(1)
72 >>> time()
73 1634594165.3485172
74 >>> a.instantiation_time
75 1634594167.812151
76 >>> a.instantiation_time
77 1634594167.812151
78 >>> sleep(1)
79 >>> b=A()
80 >>> b.instantiation_time
81 1634594178.427819
82 ```
83
84
85 Param's equivalent decorator `@param.depends(on_init=True)` will run a method when the Parameterized class is instantiated, not on first access.
86 On the other hand, Param does allow fully dynamic values for *any* access to a numeric Parameter instance, not just the original instantiation:
87
88 ```python
89 >>> from time import time
90 >>> import param
91 >>> class A(param.Parameterized):
92 ... val=param.Number(0)
93 ...
94 >>> a=A()
95 >>> a.val
96 0
97 >>> a.val=lambda:time()
98 >>> a.val
99 1475587455.437027
100 >>> a.val
101 1475587456.501314
102 ```
11
22 from nbsite.shared_conf import *
33
4 project = u'Param'
5 authors = u'PyViz authors'
6 copyright = u'\u00a9 2005-2018, ' + authors
7 description = 'Declarative Python programming using Parameters.'
4 project = u'param'
5 authors = u'HoloViz developers'
6 copyright = u'2003-2021 ' + authors
7 description = 'Declarative Python programming using Parameters'
88
99 import param
10 version = release = param.__version__
10
11 param.parameterized.docstring_signature = False
12 param.parameterized.docstring_describe_params = False
13
14 version = release = str(param.__version__)
15
16 nbbuild_cell_timeout = 600
1117
1218 html_static_path += ['_static']
13 html_theme = 'sphinx_ioam_theme'
19
20 html_theme = "pydata_sphinx_theme"
21
22 html_logo = "_static/logo_horizontal.png"
23
24 html_favicon = "_static/favicon.ico"
25
26 html_css_files = ['site.css']
27
28 exclude_patterns += ['historical_release_notes.rst']
29
1430 html_theme_options = {
15 'logo':'logo.png',
16 'favicon':'favicon.ico',
17 # 'css':'site.css'
31 "github_url": "https://github.com/holoviz/param",
32 "icon_links": [
33 {
34 "name": "Discourse",
35 "url": "https://discourse.holoviz.org/",
36 "icon": "fab fa-discourse",
37 },
38 ]
1839 }
19
20 _NAV = (
21 ('API', 'Reference_Manual/param'),
22 ('About', 'About'),
23 )
2440
2541 html_context.update({
2642 'PROJECT': project,
2743 'DESCRIPTION': description,
2844 'AUTHOR': authors,
29 # canonical URL (for search engines); can ignore for local builds
30 'WEBSITE_SERVER': 'https://param.pyviz.org',
3145 'VERSION': version,
32 'NAV': _NAV,
33 'LINKS': _NAV,
34 'SOCIAL': (
35 ('Gitter', '//gitter.im/pyviz/pyviz'),
36 ('Github', '//github.com/ioam/param'),
37 )
46 'theme_google_analytics_id': 'UA-154795830-6',
47 'theme_github_url': 'https://github.com/holoviz/param',
3848 })
49
50 extensions += ['sphinx_copybutton']
0 # Getting Started
1
2 ## Installation
3
4 Param has no required dependencies outside of Python's standard library, and so it is very easy to install.
5
6 Official releases of Param are available from [conda](https://anaconda.org/ioam/param) and [PyPI](http://pypi.python.org/pypi/param), and can be installed via:
7
8 ```
9 conda install -c pyviz param
10 ```
11
12 or
13
14 ```
15 pip install --user param
16 ```
17
18 or
19
20 ```
21 pip install param
22 ```
23
24 The very latest changes can be obtained via `conda install -c pyviz/label/dev param` or `pip install https://github.com/ioam/param/archive/master.zip`.
25
26 ## Using Param to get simple, robust code
27
28 The `param` library gives you Parameters, which are used in Parameterized classes.
29
30 A Parameter is a special type of Python class attribute extended to have various optional 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 or instance:
31
32 ```{code-block} python
33 import param
34
35 class A(param.Parameterized):
36 title = param.String(default="sum", doc="Title for the result")
37
38 class B(A):
39 a = param.Integer(2, bounds=(0,10), doc="First addend")
40 b = param.Integer(3, bounds=(0,10), doc="Second addend")
41
42 def __call__(self):
43 return self.title + ": " + str(self.a + self.b)
44 ```
45
46 ```{code-block} python
47 >> o1 = B(b=4, title="Sum")
48 >> o1.a = 5
49 >> o1()
50 'Sum: 9'
51 ```
52
53 ```{code-block} python
54 >> o1.b
55 4
56 ```
57
58 As you can see, the Parameters defined here work precisely like any other Python attributes in your code, so it's generally quite straightforward to migrate an existing class to use Param. Just inherit from `param.Parameterized`, then provide an optional `Parameter` declaration for each parameter the object accepts, including ranges and allowed values if appropriate. You only need to declare and document each parameter _once_, at the highest superclass where it applies, and its default value all the other metadata will be inherited by each subclass.
59
60 Once you've declared your parameters, a whole wealth of features and better behavior is now unlocked! For instance, what happens if a user tries to supply some inappropriate data? With Param, such errors will be caught immediately:
61
62 ```{code-block} python
63 >>> ParamClass(a="four")
64 ValueError: Parameter 'a' must be an integer.
65
66 >>> o2 = ParamClass()
67 >>> o2.b = -5
68 ValueError: Parameter 'b' must be at least 0
69 ```
70
71 Of course, you could always add more code to an ordinary Python class to check for errors like that, but as described in the [User Guide](user_guide/Simplifying_Codebases), that quickly gets unwieldy, with dozens of lines of exceptions, assertions, property definitions, and decorators that obscure what you actually wrote your code to do. Param lets you focus on the code you're writing, while letting your users know exactly what inputs they can supply.
72
73 The types in Param may remind you of the static types found in some languages, but here the validation is done at runtime and is checking not just types but also numeric ranges or for specific allowed values. Param thus helps you not just with programming correctness, as for static types, but also for validating user inputs. Validating user inputs is generally a large fraction of a program's code, because such inputs are a huge source of vulnerabilities and potential error conditions, and Param lets you avoid ever having to write nearly any of that code.
74
75 The [User Guide](user_guide/index) explains all the other Param features for simplifying your codebase, improving input validation, allowing flexible configuration, and supporting serialization.
76
77 ## Using Param for configuration
78
79 Once you have declared your Parameters, they are now fully accessible from Python in a way that helps users of your code configure it and control it if they wish. Without any extra work by the author of the class, a user can use Python to reconfigure any of the defaults that will be used when they use these objects:
80
81 ```{code-block} python
82 >>> A.title = "The sum is"
83 >>> B.a = 6
84
85 >>> o3 = B()
86 >>> o3()
87 'The sum is: 9'
88 ```
89
90 Because this configuration is all declarative, the underlying values can come from a YAML file, a JSON blob, URL parameters, CLI arguments, or just about any source, letting you provide users full control over configuration with very little effort. Once you write a Parameterized class, it's up to a user to choose how they want to work with it; your job is done!
91
92 ## Using Param to explore parameter spaces
93
94 Param is valuable for _any_ Python codebase, but it offers features that are particularly well suited for running models, simulations, machine learning pipelines, or other programs where the same code needs to be evaluated multiple times to see how it behaves with different parameter values. To facilitate such usage, numeric parameters in Param can be set to a callable value, which will be evaluated every time the parameter is accessed:
95
96 ```{code-block} python
97 >>> import random
98 >>> o2 = B(a = lambda: random.randint(0,5))
99
100 >>> o2(), o2(), o2(), o2()
101 ('The sum is: 6', 'The sum is: 7', 'The sum is: 3', 'The sum is: 3')
102 ```
103
104 The code for `B` doesn't have to have any special knowledge or processing of dynamic values, because accessing `a` always simply returns an integer, not the callable function:
105
106 ```{code-block} python
107 >>> o2.a
108 4
109 ```
110
111 Thus the author of a Parameterized class does not have to take such dynamic values into account; their code simply works with whatever value is returned by the attribute lookup, whether it's dynamic or not. This approach makes Parameterized code immediately ready for exploration across parameter values, whether or not the code's author specifically provided for such usage.
112
113 Param includes a separate and optional module `numbergen` that makes it simple to generate streams of numeric values for use as Parameter values. `numbergen` objects are picklable (unlike a `lambda` as above) and can be combined into expressions to build up parameter sweeps or Monte Carlo simulations:
114
115 ```{code-block} python
116 >>> import numbergen as ng
117
118 >>> o3 = B(a = ng.Choice(choices=[2,4,6]),
119 >>> b = 1+2*ng.UniformRandomInt(ubound=3))
120
121 >>> o3(), o3(), o3(), o3()
122 ('The sum is: 11', 'The sum is: 3', 'The sum is: 13', 'The sum is: 7')
123 ```
124
125 Numbergen objects support the usual arithmetic operations like +, -, *, /, //, %, **, and `abs()`, and so they can be freely combined with each other or with mathematical constants. They also optionally respect a global "time" (e.g. a simulation time or a logical counter), which lets you synchronize changes to dynamic values without any special coordination code.
126
127 ## Using Param to build GUIs
128
129 Param is useful for any sort of programming, but if you need a GUI with widgets, it turns out that the information captured by a Parameter is very often already what is needed to build such a GUI. For instance, we can use the separate [Panel](https://panel.holoviz.org) library to create widgets in a web browser and display the output from the above class automatically.
130
131 Panel and other GUI libraries can of course explicitly instantiate widgets, so why use Param in this way? Simply put, this approach lets you cleanly separate your domain-specific code, clearly declaring the parameters it requires and respects, from your GUI code. The GUI code controls GUI issues like layout and font size, but the fundamental declaration of what parameters are available is done at the level of the code that actually uses it (classes A and B in this case). With Param, you can _separately_ declare all your Parameters right where they are used, achieving robustness, type checking, and clear documentation, while avoiding having your GUI code be tightly bound up with your domain-specific details. This approach helps you build maintainable, general-purpose codebases that can easily be used with or without GUI interfaces, with unattended batch operation not needing any GUI support and GUIs not needing to be updated every time someone adds a new option or parameter to the underlying code.
132
133 ## Learning more
134
135 The [User Guide](user_guide/index) goes through the major features of Param and how to use them. If you are interested in GUI programming, also see the [Param guide](https://panel.holoviz.org/user_guide/Param.html) in Panel, and the rest of the [Panel](https://panel.holoviz.org) docs. Have fun making your life better with Param!
0 # Welcome to Param!
1
2 <h1><img src="_static/logo_stacked.png" width="125"></h1>
3
4 Are you a Python programmer? If so, you need Param, and check out our <a href="https://youtu.be/KP9bRmzinaY">5-minute intro video</a> to see why!
5
6 <div align="right" style="margin-right:10% margin-left:10%;">
7 <iframe width="100%" height="400" src="https://www.youtube.com/embed/KP9bRmzinaY" title="Param: Python Parameters" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
8 <a href="https://tinyurl.com/parampromo">(Download notebook)</a>
9 </div>
10
11 Param is a library for handling all the user-modifiable parameters, arguments, and attributes that control your code. It provides automatic, robust error-checking while dramatically reducing boilerplate code, letting you focus on what you want your code to do rather than on checking for all the possible ways users could supply inappropriate values to a function or class.
12
13 Param lets you program declaratively in Python, stating facts about each of your parameters up front. Once you have done that, Param can handle the rest (type checking, range validation, documentation, serialization, and more!).
14
15 Param-based programs tend to contain much less code than other Python programs, instead just having easily readable and maintainable manifests of Parameters for each object or function. This way your remaining code can be much simpler and clearer, while users can also easily see how to use it properly. Plus, Param doesn't require any code outside of the Python standard library, making it simple to add to any project.
16
17 Param is also useful as a way to keep your domain-specific code independent of any GUI or other user-interface code, letting you maintain a single codebase to support both GUI and non-GUI usage, with the GUI maintainable by UI experts and the domain-specific code maintained by domain experts.
18
19 To quickly see how Param works and can be used, jump straight into the [Getting Started Guide](getting_started), then check out the full functionality in the [User Guide.](user_guide/index)
20
21 ```{toctree}
22 ---
23 hidden: true
24 ---
25 Introduction <self>
26 Getting Started <getting_started>
27 User Guide <user_guide/index>
28 Comparisons <comparisons>
29 Roadmap <roadmap>
30 API <reference>
31 Github Source <https://github.com/holoviz/param>
32 About <about>
33 ```
+0
-20
doc/index.rst less more
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 API Reference Manual
2 ********************
3
4 The Param API Reference Manual provides a comprehensive reference for
5 all modules, functions, classes, and methods provided by Param. See the
6 `User Guide <../user_guide>`_ for a more readable overview and explanations;
7 this material duplicates what is available from `help(obj)` for each object.
8
9 `parameterized`_
10 Parameter, Parameterized, and other core classes and methods
11 `param`_
12 Optional predefined parameter types like Number, Selector, etc.
13 `ipython`_
14 Optional help functions tailored for Jupyter and IPython
15 `serializer`_
16 Optional JSON serialization
17 `version`_
18 Automatic generation of version strings using version control
19
20
21 parameterized Module
22 ====================
23
24 .. inheritance-diagram:: param.parameterized
25
26 ---------------------------
27
28 .. automodule:: param.parameterized
29 :members:
30 :inherited-members:
31 :show-inheritance:
32
33
34 param Module
35 ===============
36
37 .. inheritance-diagram:: param.__init__
38
39 ----------------------
40
41 .. automodule:: param.__init__
42 :members:
43 :inherited-members:
44 :show-inheritance:
45
46
47 ipython Module
48 ==============
49
50 .. inheritance-diagram:: param.ipython
51
52 ---------------------
53
54 .. automodule:: param.ipython
55 :members:
56 :inherited-members:
57 :show-inheritance:
58
59
60 serializer Module
61 =================
62
63 .. inheritance-diagram:: param.serializer
64
65 ------------------------
66
67 .. automodule:: param.serializer
68 :members:
69 :inherited-members:
70 :show-inheritance:
71
72
73 version Module
74 ==============
75
76 .. inheritance-diagram:: param.version
77
78 ---------------------
79
80 .. automodule:: param.version
81 :members:
82 :inherited-members:
83 :show-inheritance:
84
85
86 .. _parameterized: #parameterized-module
87 .. _param: #param-module
88 .. _ipython: #ipython-module
89 .. _serializer: #serializer-module
90 .. _version: #version-module
91
0 # Roadmap
1
2 Param is a mature library (originally from 2003) that changes very slowly and very rarely; it is fully ready for use in production applications. Major changes are undertaken only after significant discussions and with attention to how existing Param-based applications will be affected. Thus Param users should not expect only slow progress on these roadmap items, but they are listed here in the hopes that they will be useful.
3
4 Currently scheduled plans:
5
6 - Remove deprecated API and tag release 2.0. Deprecated API is noted in the source code and except in very rare cases has never been documented on the website, so removing these methods should only affect users of Param from 2020 or earlier.
7
8 - More powerful serialization (to JSON, YAML, and URLs) to make it simpler to persist the state of a Parameterized object. Some support already merged as https://github.com/holoviz/param/pull/414 , but still to be further developed as support for using Parameterized objects to build REST APIS (see https://github.com/holoviz/lumen for example usage).
9
10 Other items that are not yet scheduled but would be great to have:
11
12 - Integrate more fully with Python 3 language features like [type annotations](https://www.python.org/dev/peps/pep-0526) and [data classes](https://docs.python.org/3/library/dataclasses.html), e.g. to respect and validate against declared types without requiring an explicit `param.Parameter` declaration and potentially without inheriting from `param.Parameterized`, and to better support IDE type-checking features.
13
14 - Integrate and interoperate more fully with other frameworks like Django models, Traitlets, attrs, Django models, Pydantic, or swagger/OpenAPI, each of which capture or can use similar information about parameter names, values, and constraints and so in many cases can easily be converted from one to the other.
15
16 - Improve support for Param in editors, automatic formatting tools, linters, document generators, and other tools that process Python code and could be made to have special-purpose optimizations specifically for Parameterized objects.
17
18 - Follow PEP8 more strictly: PEP8 definitely wasn't written with Parameters in mind, and it typically results in badly formatted files when applied to Parameterized code. But PEP8 could be applied to Param's own code, e.g. using Black.
19
20 - Triaging open issues: The Param developer team consists of volunteers typically using Param on their projects but not explicity tasked with or funded to work on Param itself. It would thus be great if the more experienced Param users could help address some of the issues that have been raised but not yet solved.
21
22 - Improve test coverage
23
24 Other [issues](https://github.com/holoviz/param/issues) are collected on Github and will be addressed on various time scales as indicated by the issue's milestone (typically next minor release, next major release, or "wishlist" (not scheduled or assigned to any person but agreed to be desirable). Any contributor is encouraged to attempt to implement a "wishlist" item, though if it is particularly complex or time consuming it is useful to discuss it first with one of the core maintainers (e.g. by stating your intentions on the issue).
0 # User Guide
1
2 This user guide provides detailed information about how to use Param, assuming you have worked through the Getting Started guide.
3
4 - [Simplifying Codebases](./Simplifying_Codebases): How Param allows you to eliminate boilerplate and unsafe code
5 - [Parameters](./Parameters): Using parameters (Class vs. instance parameters, setting defaults, etc.)
6 - [Parameter Types](./Parameter_Types): Predefined Parameter classes available for your use
7 - [Dependencies and Watchers](./Dependencies_and_Watchers): Expressing relationships between parameters and parameters or code, and triggering events
8 - [Serialization and Persistence](./Serialization_and_Persistence): Saving the state of a Parameterized object to a text, script, or pickle file
9 - [Outputs](./Outputs): Output types and connecting output to Parameter inputs
10 - [Logging and Warnings](./Logging_and_Warnings): Logging, messaging, warning, and raising errors on Parameterized objects
11 - [ParameterizedFunctions](./ParameterizedFunctions): Parameterized function objects, for configurable callables
12 - [Dynamic Parameters](./Dynamic_Parameters): Using dynamic parameter values with and without Numbergen
13 - [How Param Works](./How_Param_Works): Internal details, for Param developers and power users
14 - [Using Param in GUIs](https://panel.holoviz.org/user_guide/Param.html): (external site) Using Param with Panel to make GUIs
15
16 ```{toctree}
17 ---
18 hidden: true
19 maxdepth: 2
20 ---
21 Overview <self>
22 Simplifying Codebases <Simplifying_Codebases>
23 Parameters <Parameters>
24 Parameter Types <Parameter_Types>
25 Dependencies and Watchers <Dependencies_and_Watchers>
26 Serialization and Persistence <Serialization_and_Persistence>
27 Outputs <Outputs>
28 Logging and Warnings <Logging_and_Warnings>
29 ParameterizedFunctions <ParameterizedFunctions>
30 Dynamic Parameters <Dynamic_Parameters>
31 How Param Works <How_Param_Works>
32 ```
+0
-19
examples/About.ipynb less more
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 "<img src=\"https://raw.githubusercontent.com/holoviz/param/master/doc/_static/logo_horizontal.png\" style=\"display:block;margin-left:auto;margin-right:auto;width:50%;max-width:500px\">"
7 ]
8 },
9 {
10 "cell_type": "markdown",
11 "metadata": {},
12 "source": [
13 "# Introduction"
14 ]
15 },
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "**Param is a library providing Parameters:**<br>\n",
21 "<div style=\"padding-left:1cm;\">\n",
22 "\n",
23 "Python attributes extended to have features such as\n",
24 "* type and range checking\n",
25 "* dynamically generated values\n",
26 "* documentation strings\n",
27 "* default values\n",
28 "* events\n",
29 "</div>\n",
30 "\n",
31 "**Param enables you to write robust and powerful applications in just a few lines of code**.\n",
32 "\n",
33 "**Param is free, open source, small, and has no external dependencies**, so that it can easily be included as part of other projects."
34 ]
35 },
36 {
37 "cell_type": "markdown",
38 "metadata": {},
39 "source": [
40 "# Example: A Parameterized Class"
41 ]
42 },
43 {
44 "cell_type": "markdown",
45 "metadata": {},
46 "source": [
47 "The [Pythagorean Theorem](https://en.wikipedia.org/wiki/Pythagorean_theorem) is one of the world's most famous equations.\n",
48 "<img src=\"https://miro.medium.com/max/658/1*SsN2DG__Z5DyOI0uf7hbwQ.png\" style=\"display:block;margin-top:0.5cm;margin-left:2cm;margin-right:auto;width:40%;max-width:700px;border: 2px solid black;\">\n",
49 "\n",
50 "<br>\n",
51 "\n",
52 "**We will illustrate how powerful Param is** by building a model of the Pythagorean Theorem."
53 ]
54 },
55 {
56 "cell_type": "markdown",
57 "metadata": {},
58 "source": [
59 "### **Pythagorean Theorem Class**"
60 ]
61 },
62 {
63 "cell_type": "code",
64 "execution_count": null,
65 "metadata": {},
66 "outputs": [],
67 "source": [
68 "import param, math, time"
69 ]
70 },
71 {
72 "cell_type": "code",
73 "execution_count": null,
74 "metadata": {},
75 "outputs": [],
76 "source": [
77 "class PythagoreanTheorem(param.Parameterized):\n",
78 " \"\"\"Model of the Pythagorean Theorem\"\"\"\n",
79 "\n",
80 " a = param.Number(default=0, bounds=(0,None), doc=\"Length of side a\")\n",
81 " b = param.Number(default=0, bounds=(0,None), doc=\"Length of side b\")\n",
82 " c = param.Number(default=0, bounds=(0,None), doc=\"Length of the hypotenuse c\",\n",
83 " constant=True)\n",
84 "\n",
85 "\n",
86 " def __init__(self, **params):\n",
87 " super().__init__(**params) # Sets values a and b if provided in the params\n",
88 " \n",
89 " self._update_hypotenuse() # Sets the value c\n",
90 "\n",
91 "\n",
92 " @param.depends(\"a\", \"b\", watch=True) # Triggers a run of the function whenever a or b is changed\n",
93 " def _update_hypotenuse(self):\n",
94 " \"\"\"Updates the length of the hypotenuse\"\"\"\n",
95 " with param.edit_constant(self):\n",
96 " self.c = math.sqrt(self.a**2+self.b**2)"
97 ]
98 },
99 {
100 "cell_type": "markdown",
101 "metadata": {},
102 "source": [
103 "### **Pythagorean Theorem Object**"
104 ]
105 },
106 {
107 "cell_type": "markdown",
108 "metadata": {},
109 "source": [
110 "**Lets try to use the model**"
111 ]
112 },
113 {
114 "cell_type": "code",
115 "execution_count": null,
116 "metadata": {},
117 "outputs": [],
118 "source": [
119 "pythagoras = PythagoreanTheorem(a=3, b=4) # create an object with initial values for the parameters a and b\n",
120 "pythagoras.c # print the result for c"
121 ]
122 },
123 {
124 "cell_type": "markdown",
125 "metadata": {},
126 "source": [
127 "### **Using the Parameters**"
128 ]
129 },
130 {
131 "cell_type": "markdown",
132 "metadata": {},
133 "source": [
134 "We will now **take a closer look** at what these few lines of code provide us:"
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "#### **Param Provides Parameter Validation**"
142 ]
143 },
144 {
145 "cell_type": "code",
146 "execution_count": null,
147 "metadata": {},
148 "outputs": [],
149 "source": [
150 "# check admissible parameter values\n",
151 "try:\n",
152 " pythagoras1 = PythagoreanTheorem(a=-1, b=4)\n",
153 "except Exception as ex:\n",
154 " print(ex)"
155 ]
156 },
157 {
158 "cell_type": "code",
159 "execution_count": null,
160 "metadata": {},
161 "outputs": [],
162 "source": [
163 "# check parameter types\n",
164 "try:\n",
165 " pythagoras2 = PythagoreanTheorem(a=\"length is 3\", b=4)\n",
166 "except Exception as ex:\n",
167 " print(ex)"
168 ]
169 },
170 {
171 "cell_type": "markdown",
172 "metadata": {},
173 "source": [
174 "Param contains a wide range of useful parameter types, including\n",
175 "* `String`\n",
176 "* `Integer`\n",
177 "* `Float`\n",
178 "* `Bool`\n",
179 "* `DataFrame`"
180 ]
181 },
182 {
183 "cell_type": "markdown",
184 "metadata": {},
185 "source": [
186 "#### **Param Provides Constant Parameters**"
187 ]
188 },
189 {
190 "cell_type": "code",
191 "execution_count": null,
192 "metadata": {},
193 "outputs": [],
194 "source": [
195 "# constant values cannot be changed\n",
196 "try:\n",
197 " pythagoras.c = 3\n",
198 "except Exception as ex:\n",
199 " print(ex)"
200 ]
201 },
202 {
203 "cell_type": "markdown",
204 "metadata": {},
205 "source": [
206 "#### **Param Provides Default Values**"
207 ]
208 },
209 {
210 "cell_type": "code",
211 "execution_count": null,
212 "metadata": {},
213 "outputs": [],
214 "source": [
215 "print( f\"{pythagoras.param.a.name} = {pythagoras.param.a.default}\")"
216 ]
217 },
218 {
219 "cell_type": "markdown",
220 "metadata": {},
221 "source": [
222 "#### **Param Provides Documentation**"
223 ]
224 },
225 {
226 "cell_type": "code",
227 "execution_count": null,
228 "metadata": {},
229 "outputs": [],
230 "source": [
231 "?pythagoras"
232 ]
233 },
234 {
235 "cell_type": "code",
236 "execution_count": null,
237 "metadata": {},
238 "outputs": [],
239 "source": [
240 "# more extensive documentation\n",
241 "help(pythagoras)"
242 ]
243 },
244 {
245 "cell_type": "markdown",
246 "metadata": {},
247 "source": [
248 "#### **Param Provides Events**"
249 ]
250 },
251 {
252 "cell_type": "markdown",
253 "metadata": {},
254 "source": [
255 "You can **use events to react to parameter changes.**"
256 ]
257 },
258 {
259 "cell_type": "markdown",
260 "metadata": {},
261 "source": [
262 "We have already reacted to events by using the `@param.depends(\"a\", \"b\", watch=True)` annotation<br>\n",
263 "$\\quad$ to react to `a` or `b` changing by updating the calculated hypotenuse.\n",
264 "\n",
265 "Here we will use the alternative **`param.watch`** to just watch for changes to the hypotenuse `c` and print the event raised."
266 ]
267 },
268 {
269 "cell_type": "code",
270 "execution_count": null,
271 "metadata": {},
272 "outputs": [],
273 "source": [
274 "def print_event(event):\n",
275 " print(event, end='\\n\\n')\n",
276 "\n",
277 "watcher = pythagoras.param.watch(print_event, \"c\")"
278 ]
279 },
280 {
281 "cell_type": "code",
282 "execution_count": null,
283 "metadata": {},
284 "outputs": [],
285 "source": [
286 "for _ in range(3):\n",
287 " pythagoras.b += 1\n",
288 " time.sleep(1)"
289 ]
290 },
291 {
292 "cell_type": "markdown",
293 "metadata": {},
294 "source": [
295 "We can also **stop watching** again:"
296 ]
297 },
298 {
299 "cell_type": "code",
300 "execution_count": null,
301 "metadata": {},
302 "outputs": [],
303 "source": [
304 "pythagoras.param.unwatch(watcher)"
305 ]
306 },
307 {
308 "cell_type": "markdown",
309 "metadata": {},
310 "source": [
311 "## **Param Makes it Easy to Create GUIs**"
312 ]
313 },
314 {
315 "cell_type": "markdown",
316 "metadata": {},
317 "source": [
318 "On top of param you can **quickly build interactive applications and graphical user interfaces.**"
319 ]
320 },
321 {
322 "cell_type": "markdown",
323 "metadata": {},
324 "source": [
325 "The whole [HoloViz](https://holoviz.org) ecosystem is built in this way! \n",
326 "\n",
327 "<table>\n",
328 "<tr style=\"background-color:transparent\">\n",
329 " <td> <a href=\"https://panel.pyviz.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/panel.png\"/></a></td>\n",
330 " <td> <a href=\"https://hvplot.pyviz.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/hvplot.png\"/></a></td>\n",
331 " <td> <a href=\"https://holoviews.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/holoviews.png\"/></a></td>\n",
332 " <td> <a href=\"http://geoviews.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/geoviews.png\"/></a></td>\n",
333 " <td> <a href=\"http://datashader.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/datashader.png\"/></a></td>\n",
334 " <td> <a href=\"https://param.pyviz.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/param.png\"/></a></td>\n",
335 " <td> <a href=\"https://colorcet.pyviz.org\"><img style=\"height:100px;margin-right:1em;margin-left: 1em\" src=\"https://holoviz.org/assets/colorcet.png\"/></a></td>\n",
336 "</tr>\n",
337 "</table>"
338 ]
339 },
340 {
341 "cell_type": "markdown",
342 "metadata": {},
343 "source": [
344 "Let's use **[Panel](https://panel.holoviz.org/) to illustrate how powerful this is.**"
345 ]
346 },
347 {
348 "cell_type": "code",
349 "execution_count": null,
350 "metadata": {},
351 "outputs": [],
352 "source": [
353 "import panel as pn\n",
354 "pn.extension()"
355 ]
356 },
357 {
358 "cell_type": "code",
359 "execution_count": null,
360 "metadata": {},
361 "outputs": [],
362 "source": [
363 "pn.Param(pythagoras)"
364 ]
365 },
366 {
367 "cell_type": "markdown",
368 "metadata": {},
369 "source": [
370 "# Visit the Param Website"
371 ]
372 },
373 {
374 "cell_type": "markdown",
375 "metadata": {},
376 "source": [
377 "**Please visit [Param's website](https://param.holoviz.org) for more information** like official releases, installation instructions, documentation, and examples.\n",
378 "\n",
379 "And **join the community** on the [HoloViz Discourse](https://discourse.holoviz.org/)."
380 ]
381 },
382 {
383 "cell_type": "markdown",
384 "metadata": {},
385 "source": [
386 "[<img src=\"assets/param-is-powerful.png\" style=\"display:block;margin-left:1cm;margin-right:auto;width:80%;max-width:1000px;border:2px solid black;\">](https://discourse.holoviz.org/)"
387 ]
388 }
389 ],
390 "metadata": {
391 "language_info": {
392 "name": "python",
393 "pygments_lexer": "ipython3"
394 }
395 },
396 "nbformat": 4,
397 "nbformat_minor": 5
398 }
+0
-300
examples/index.ipynb less more
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 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Dependencies and Watchers\n",
7 "\n",
8 "As outlined in the [Dynamic Parameters](Dynamic_Parameters.ipynb) guide, Param can be used in multiple ways, including as a static set of typed attributes, dynamic attributes that are computed when they are read (`param.Dynamic` parameters, a \"pull\" or \"get\" model), and using explicitly expressed chains of actions driven by events at the Parameter level (a \"push\" or \"set\" model described in this notebook). \n",
9 "\n",
10 "Unlike Dynamic Parameters, which calculate values when parameters are _accessed_, the dependency and watcher interface allows events to be triggered when parameters are _set_. With this interface, parameters and methods can declare that they should be updated or invoked when a given parameter is modified, spawning a cascading series of events that update settings to be consistent, adapt values as appropriate for a change, or invoke computations such as updating a displayed object when a value is modified. This approach is well suited to a GUI interface, where a user interacts with a single widget at a time but other widgets or displays need to be updated in response. The\n",
11 "[Dynamic Parameters](Dynamic_Parameters.ipynb) approach, in contrast, is well suited when Parameters update either on read or in response to a global clock or counter, such as in a simulation or machine-learning iteration.\n",
12 "\n",
13 "This user guide is structured as three main sections:\n",
14 "\n",
15 "- [Dependencies](#Dependencies): High-level dependency declaration via the `@param.depends()` decorator\n",
16 "- [Watchers](#Watchers): Low-level watching mechanism via `.param.watch()`.\n",
17 "- [Using dependencies and watchers](#Using-dependencies-and-watchers): Utilities and tools for working with events created using either dependencies or watchers."
18 ]
19 },
20 {
21 "cell_type": "markdown",
22 "metadata": {},
23 "source": [
24 "## Dependencies\n",
25 "\n",
26 "Param's `depends` decorator allows a programmer to express that a given computation \"depends\" on a certain set of parameters. For instance, if you have parameters whose values are interlinked, it's easy to express that relationship with `depends`:"
27 ]
28 },
29 {
30 "cell_type": "code",
31 "execution_count": null,
32 "metadata": {},
33 "outputs": [],
34 "source": [
35 "import param\n",
36 "\n",
37 "class C(param.Parameterized):\n",
38 " _countries = {'Africa': ['Ghana', 'Togo', 'South Africa'],\n",
39 " 'Asia' : ['China', 'Thailand', 'Japan', 'Singapore'],\n",
40 " 'Europe': ['Austria', 'Bulgaria', 'Greece', 'Switzerland']}\n",
41 " \n",
42 " continent = param.Selector(list(_countries.keys()), default='Asia')\n",
43 " country = param.Selector(_countries['Asia'])\n",
44 " \n",
45 " @param.depends('continent', watch=True)\n",
46 " def _update_countries(self):\n",
47 " countries = self._countries[self.continent]\n",
48 " self.param['country'].objects = countries\n",
49 " if self.country not in countries:\n",
50 " self.country = countries[0]\n",
51 "\n",
52 "c = C()\n",
53 "c.country, c.param.country.objects"
54 ]
55 },
56 {
57 "cell_type": "code",
58 "execution_count": null,
59 "metadata": {},
60 "outputs": [],
61 "source": [
62 "c.continent='Africa'\n",
63 "c.country, c.param.country.objects"
64 ]
65 },
66 {
67 "cell_type": "code",
68 "execution_count": null,
69 "metadata": {},
70 "outputs": [],
71 "source": [
72 "c"
73 ]
74 },
75 {
76 "cell_type": "markdown",
77 "metadata": {},
78 "source": [
79 "As you can see, here Param updates the allowed and current values for `country` whenever someone changes the `continent` parameter. This code relies on the dependency mechanism to make sure these parameters are kept appropriately synchronized:\n",
80 "\n",
81 "1. First, we set up the default continent but do not declare the `objects` and `default` for the `country` parameter. This is because this parameter is dependent on the `continent` and therefore it is easy to set up values that are inconsistent and makes it difficult to override the default continent since changes to both parameters need to be coordinated. \n",
82 "2. Next, if someone chooses a different continent, the list of countries allowed needs to be updated, so the method `_update_countries()` that (a) looks up the countries allowed for the current continent, (b) sets that list as the allowed objects for the `country` parameter, and (c) selects the first such country as the default country.\n",
83 "3. Finally, we expressed that the `_update_countries()` method depends on the `continent` parameter. We specified `watch=True`) to direct Param to invoke this method immediately, whenever the value of `continent` changes. We'll see [examples of watch=False](#watch=False-dependencies) later. Importantly we also set `on_init=True`, which means that when instance is created the `self._update_countries()` method is automatically called setting up the `country` parameter appropriately. This avoids having to declare a `__init__` method to manually call the method ourselves and the potentially brittle process of setting up consistent defaults."
84 ]
85 },
86 {
87 "cell_type": "markdown",
88 "metadata": {},
89 "source": [
90 "### Dependency specs\n",
91 "\n",
92 "The example above expressed a dependency of `_update_countries` on this object's `continent` parameter. A wide range of such dependency relationships can be specified:\n",
93 "\n",
94 "1. **Multiple dependencies**: Here we had only one parameter in the dependency list, but you can supply any number of dependencies (`@param.depends('continent', 'country', watch=True)`).\n",
95 "2. **Dependencies on nested parameters**: Parameters specified can either be on this class, or on nested Parameterized objects of this class. Parameters on this class are specified as the attribute name as a simple string (like `'continent'`). Nested parameters are specified as a dot-separated string (like `'handler.strategy.i'`, if this object has a parameter `handler`, whose value is an object `strategy`, which itself has a parameter `i`). If you want to depend on some arbitrary parameter elsewhere in Python, just create an `instantiate=False` (and typically read-only) parameter on this class to hold it, then here you can specify the path to it on _this_ object.\n",
96 "3. **Dependencies on metadata**: By default, dependencies are tied to a parameter's current value, but dependencies can also be on any of the declared metadata about the parameter (e.g. a method could depend on `country:constant`, triggering when someone changes whether that parameter is constant, or on `country:objects` (triggering when the objects list is replaced (not just changed in place as in appending). The available metadata is listed in the `__slots__` attribute of a Parameter object (e.g. \n",
97 "`p.param.continent.__slots__`). \n",
98 "4. **Dependencies on any nested param**: If you want to depend on _all_ the parameters of a nested object `n`, your method can depend on `'n.param'` (where parameter `n` has been set to a Parameterized object).\n",
99 "5. **Dependencies on a method name**: Often you will want to break up computation into multiple chunks, some of which are useful on their own and some which require other computations to have been done as prerequisites. In this case, your method can declare a dependency on another method (as a string name), which means that it will now watch everything that method watches, and will then get invoked after that method is invoked.\n",
100 "\n",
101 "We can see examples of all these dependency specifications in class `D` below:"
102 ]
103 },
104 {
105 "cell_type": "code",
106 "execution_count": null,
107 "metadata": {},
108 "outputs": [],
109 "source": [
110 "class D(param.Parameterized):\n",
111 " x = param.Number(7)\n",
112 " s = param.String(\"never\")\n",
113 " i = param.Integer(-5)\n",
114 " o = param.Selector(['red', 'green', 'blue'])\n",
115 " n = param.ClassSelector(param.Parameterized, c, instantiate=False) \n",
116 " \n",
117 " @param.depends('x', 's', 'n.country', 's:constant', watch=True)\n",
118 " def cb1(self):\n",
119 " print(f\"cb1 x={self.x} s={self.s} \"\n",
120 " f\"param.s.constant={self.param.s.constant} n.country={self.n.country}\")\n",
121 "\n",
122 " @param.depends('n.param', watch=True)\n",
123 " def cb2(self):\n",
124 " print(f\"cb2 n={self.n}\")\n",
125 "\n",
126 " @param.depends('x', 'i', watch=True)\n",
127 " def cb3(self):\n",
128 " print(f\"cb3 x={self.x} i={self.i}\")\n",
129 "\n",
130 " @param.depends('cb3', watch=True)\n",
131 " def cb4(self):\n",
132 " print(f\"cb4 x={self.x} i={self.i}\")\n",
133 "\n",
134 "d = D()\n",
135 "d"
136 ]
137 },
138 {
139 "cell_type": "markdown",
140 "metadata": {},
141 "source": [
142 "Here we have created an object `d` of type `D` with a unique ID like `D00003`. `d` has various parameters, including one nested Parameterized object in its parameter `n`. In this class, the nested parameter is set to our earlier object `c`, using `instantiate=False` to ensure that the value is precisely the same earlier object, not a copy of it. You can verify that it is the same object by comparing e.g. `name='C00002'` in the repr for the subobject in `d` to the name in the repr for `c` in the previous section; both should be e.g. `C00002`.\n",
143 "\n",
144 "Dependencies are stored declaratively so that they are accessible for other libraries to inspect and use. E.g. we can now examine the dependencies for the decorated callback method `cb1`:"
145 ]
146 },
147 {
148 "cell_type": "code",
149 "execution_count": null,
150 "metadata": {},
151 "outputs": [],
152 "source": [
153 "dependencies = d.param.method_dependencies('cb1')\n",
154 "[f\"{o.inst.name}.{o.pobj.name}:{o.what}\" for o in dependencies]"
155 ]
156 },
157 {
158 "cell_type": "markdown",
159 "metadata": {},
160 "source": [
161 "Here we can see that method `cb1` will be invoked for any value changes in `d`'s parameters `x` or `s`, for any value changes in `c`'s parameter `country`, and a change in the `constant` slot of `s`. These dependency relationships correspond to the specification `@param.depends('x', 's', 'n.country', 's:constant', watch=True)` above.\n",
162 "\n",
163 "Now, if we change `x`, we can see that Param invokes `cb1`:"
164 ]
165 },
166 {
167 "cell_type": "code",
168 "execution_count": null,
169 "metadata": {},
170 "outputs": [],
171 "source": [
172 "d.x = 5"
173 ]
174 },
175 {
176 "cell_type": "markdown",
177 "metadata": {},
178 "source": [
179 "`cb3` and `cb4` are also invoked, because `cb3` depends on `x` as well, plus `cb4` depends on `cb3`, inheriting all of `cb3`'s dependencies.\n",
180 "\n",
181 "If we now change `c.country`, `cb1` will be invoked since `cb1` depends on `n.country`, and `n` is currently set to `c`:"
182 ]
183 },
184 {
185 "cell_type": "code",
186 "execution_count": null,
187 "metadata": {},
188 "outputs": [],
189 "source": [
190 "c.country = 'Togo'"
191 ]
192 },
193 {
194 "cell_type": "markdown",
195 "metadata": {},
196 "source": [
197 "As you can see, `cb2` is also invoked, because `cb2` depends on _all_ parameters of the subobject in `n`. \n",
198 "\n",
199 "`continent` is also a parameter on `c`, so `cb2` will also be invoked if you change `c.continent`. Note that changing `c.continent` itself invokes `c._update_countries()`, so in that case `cb2` actually gets invoked _twice_ (once for each parameter changed on `c`), along with `cb1` (watching `n.country`):"
200 ]
201 },
202 {
203 "cell_type": "code",
204 "execution_count": null,
205 "metadata": {},
206 "outputs": [],
207 "source": [
208 "c.continent = 'Europe'"
209 ]
210 },
211 {
212 "cell_type": "markdown",
213 "metadata": {},
214 "source": [
215 "Changing metadata works just the same as changing values. Because `cb1` depends on the `constant` slot of `s`, it is invoked when that slot changes:"
216 ]
217 },
218 {
219 "cell_type": "code",
220 "execution_count": null,
221 "metadata": {},
222 "outputs": [],
223 "source": [
224 "d.param.s.constant = True"
225 ]
226 },
227 {
228 "cell_type": "markdown",
229 "metadata": {},
230 "source": [
231 "Importantly, if we replace a sub-object on which we have declared dependencies, Param automatically rebinds the dependencies to the new object:"
232 ]
233 },
234 {
235 "cell_type": "code",
236 "execution_count": null,
237 "metadata": {},
238 "outputs": [],
239 "source": [
240 "d.n = C()"
241 ]
242 },
243 {
244 "cell_type": "markdown",
245 "metadata": {},
246 "source": [
247 "Note that if the values of the dependencies on the old and new object are the same, no event is fired. \n",
248 "\n",
249 "Additionally the previously bound sub-object is now no longer connected:"
250 ]
251 },
252 {
253 "cell_type": "code",
254 "execution_count": null,
255 "metadata": {},
256 "outputs": [],
257 "source": [
258 "c.continent = 'Europe'"
259 ]
260 },
261 {
262 "cell_type": "markdown",
263 "metadata": {},
264 "source": [
265 "### `watch=False` dependencies\n",
266 "\n",
267 "The previous examples all supplied `watch=True`, indicating that Param itself should watch for changes in the dependency and invoke that method when a dependent parameter is set. If `watch=False` (the default), `@param.depends` declares that such a dependency exists, but does not automatically invoke it. `watch=False` is useful for setting up code for a separate library like [Panel](https://panel.holoviz.org) or [HoloViews](https://holoviews.org) to use, indicating which parameters the external library should watch so that it knows when to invoke the decorated method. Typically, you'll want to use `watch=False` when that external library needs to do something with the return value of the method (a functional approach), and use `watch=True` when the function is [side-effecty](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), i.e. having an effect just from running it, and not normally returning a value.\n",
268 "\n",
269 "For instance, consider this Param class with methods that return values to display:"
270 ]
271 },
272 {
273 "cell_type": "code",
274 "execution_count": null,
275 "metadata": {},
276 "outputs": [],
277 "source": [
278 "class Mul(param.Parameterized):\n",
279 " a = param.Number(5, bounds=(-100, 100))\n",
280 " b = param.Number(-2, bounds=(-100, 100))\n",
281 "\n",
282 " @param.depends('a', 'b')\n",
283 " def view(self):\n",
284 " return str(self.a*self.b)\n",
285 "\n",
286 " def view2(self):\n",
287 " return str(self.a*self.b)\n",
288 "\n",
289 "prod = Mul(name='Multiplier')"
290 ]
291 },
292 {
293 "cell_type": "markdown",
294 "metadata": {},
295 "source": [
296 "You could run this code manually:"
297 ]
298 },
299 {
300 "cell_type": "code",
301 "execution_count": null,
302 "metadata": {},
303 "outputs": [],
304 "source": [
305 "prod.a = 7\n",
306 "prod.b = 10\n",
307 "prod.view()"
308 ]
309 },
310 {
311 "cell_type": "markdown",
312 "metadata": {},
313 "source": [
314 "Or you could pass the parameters and the `view` method to Panel, and let Panel invoke it as needed by following the dependency chain:"
315 ]
316 },
317 {
318 "cell_type": "code",
319 "execution_count": null,
320 "metadata": {},
321 "outputs": [],
322 "source": [
323 "import panel as pn\n",
324 "pn.extension()"
325 ]
326 },
327 {
328 "cell_type": "code",
329 "execution_count": null,
330 "metadata": {},
331 "outputs": [],
332 "source": [
333 "pn.Row(prod.param, prod.view)"
334 ]
335 },
336 {
337 "cell_type": "markdown",
338 "metadata": {},
339 "source": [
340 "Panel creates widgets for the parameters, runs the `view` method with the default values of those parameters, and displays the result. As long as you have a live Python process running (not just a static HTML export of this page as on param.holoviz.org), Panel will then watch for changes in those parameters due to the widgets and will re-execute the `view` method to update the output whenever one of those parameters changes. Using the dependency declarations, Panel is able to do all this without ever having to be told separately which parameters there are or what dependency relationships there are. \n",
341 "\n",
342 "How does that work? A library like Panel can simply ask Param what dependency relationships have been declared for the method passed to it:"
343 ]
344 },
345 {
346 "cell_type": "code",
347 "execution_count": null,
348 "metadata": {},
349 "outputs": [],
350 "source": [
351 "[o.name for o in prod.param.method_dependencies('view')]"
352 ]
353 },
354 {
355 "cell_type": "markdown",
356 "metadata": {},
357 "source": [
358 "Note that in this particular case the `depends` decorator could have been omitted, because Param conservatively assumes that any method _could_ read the value of any parameter, and thus if it has no other declaration from the user, the dependencies are assumed to include _all_ parameters (including `name`, even though it is constant):"
359 ]
360 },
361 {
362 "cell_type": "code",
363 "execution_count": null,
364 "metadata": {},
365 "outputs": [],
366 "source": [
367 "[o.name for o in prod.param.method_dependencies('view2')]"
368 ]
369 },
370 {
371 "cell_type": "markdown",
372 "metadata": {},
373 "source": [
374 "Conversely, if you want to declare that a given method does not depend on any parameters at all, you can use `@param.depends()`. \n",
375 "\n",
376 "Be sure not to set `watch=True` for dependencies for any method you pass to an external library like Panel to handle, or else that method will get invoked _twice_, once by Param itself (discarding the output) and once by the external library (using the output). Typically you will want `watch=True` for a side-effecty function or method (typically not returning a value), and `watch=False` (the default) for a function or method with a return value, and you'll need an external library to do something with that return value."
377 ]
378 },
379 {
380 "cell_type": "markdown",
381 "metadata": {},
382 "source": [
383 "### `@param.depends` with function objects\n",
384 "\n",
385 "The `depends` decorator can also be used with bare functions, in which case the specification should be an actual Parameter object, not a string. The function will be called with the parameter(s)'s value(s) as positional arguments:"
386 ]
387 },
388 {
389 "cell_type": "code",
390 "execution_count": null,
391 "metadata": {},
392 "outputs": [],
393 "source": [
394 "@param.depends(c.param.country, d.param.i, watch=True)\n",
395 "def g(country, i):\n",
396 " print(f\"g country={country} i={i}\")\n",
397 "\n",
398 "c.country = 'Greece'"
399 ]
400 },
401 {
402 "cell_type": "code",
403 "execution_count": null,
404 "metadata": {},
405 "outputs": [],
406 "source": [
407 "d.i = 6"
408 ]
409 },
410 {
411 "cell_type": "markdown",
412 "metadata": {},
413 "source": [
414 "Here you can see that in addition to the classmethods starting with `cb` previously set up to depend on the country, setting `c`'s `country` parameter or `d`'s `i` parameter now also invokes function `g`, passing in the current values of the parameters it depends on whenever the function gets invoked. `g` can then make a side effect happen such as updating any other data structure it can access that needs to be changed when `country` or `i` changes. \n",
415 "\n",
416 "Using `@param.depends(..., watch=False)` with a function allows providing bound standalone functions to an external library for display, just as in the `.view` method above.\n",
417 "\n",
418 "Of course, you can still invoke `g` with your own explicit arguments, which does not invoke any watching mechanisms:"
419 ]
420 },
421 {
422 "cell_type": "code",
423 "execution_count": null,
424 "metadata": {},
425 "outputs": [],
426 "source": [
427 "g('USA', 7)"
428 ]
429 },
430 {
431 "cell_type": "markdown",
432 "metadata": {},
433 "source": [
434 "## Watchers\n",
435 "\n",
436 "The `depends` decorator is built on Param's lower-level `.param.watch` interface, registering the decorated method or function as a `Watcher` object associated with those parameter(s). If you're building or using a complex library like Panel, you can use the low-level Parameter watching interface to set up arbitrary chains of watchers to respond to parameter value or metadata setting:\n",
437 "\n",
438 "- `obj.param.watch(fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0)`: <br>Create and register a `Watcher` that will invoke the given callback `fn` when the `what` item (`value` or one of the Parameter's slots) is set (or more specifically, changed, if `onlychanged=True`). If `queued=True`, delays calling any events triggered during this callback's execution until all processing of the current events has been completed (breadth-first Event processing rather than the default depth-first processing). The `precedence` declares a precedence level for the Watcher that determines the priority with which the callback is executed. Lower precedence levels are executed earlier. Negative precedences are reserved for internal Watchers, i.e. those set up by `param.depends`. The `fn` will be invoked with one or more `Event` objects that have been triggered, as positional arguments. Returns a `Watcher` object, e.g. for use in `unwatch`.\n",
439 "\n",
440 "- `obj.param.watch_values(fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0)`: <br>Easier-to-use version of `obj.param.watch` specific to watching for changes in parameter values. Same as `watch`, but hard-codes `what='value'` and invokes the callback `fn` using keyword arguments _param_name_=_new_value_ rather than with a positional-argument list of `Event` objects.\n",
441 "\n",
442 "- `obj.param.unwatch(watcher)`: <br>Remove the given `Watcher` (typically obtained as the return value from `watch` or `watch_values`) from those registered on this `obj`.\n",
443 "\n",
444 "To see how to use `watch` and `watch_values`, let's make a class with parameters `a` and `b` and various watchers with corresponding callback methods:"
445 ]
446 },
447 {
448 "cell_type": "code",
449 "execution_count": null,
450 "metadata": {},
451 "outputs": [],
452 "source": [
453 "def e(e):\n",
454 " return f\"(event: {e.name} changed from {e.old} to {e.new})\"\n",
455 "\n",
456 "class P(param.Parameterized):\n",
457 " a = param.Integer(default=0)\n",
458 " b = param.Integer(default=0)\n",
459 " \n",
460 " def __init__(self, **params):\n",
461 " super().__init__(**params)\n",
462 " self.param.watch(self.run_a1, ['a'], queued=True, precedence=2)\n",
463 " self.param.watch(self.run_a2, ['a'], precedence=1)\n",
464 " self.param.watch(self.run_b, ['b'])\n",
465 "\n",
466 " def run_a1(self, event):\n",
467 " self.b += 1\n",
468 " print('a1', self.a, e(event))\n",
469 "\n",
470 " def run_a2(self, event):\n",
471 " print('a2', self.a, e(event))\n",
472 "\n",
473 " def run_b(self, event):\n",
474 " print('b', self.b, e(event))\n",
475 " \n",
476 "p = P()\n",
477 "\n",
478 "p.a = 1"
479 ]
480 },
481 {
482 "cell_type": "markdown",
483 "metadata": {},
484 "source": [
485 "Here, we have set up three Watchers, each invoking a method on `P` when either `a` or `b` changes. The first Watcher invokes `run_a1` when `a` changes, and in turn `run_a1` changes `b`. Since `queued=True` for `run_a1`, `run_b` is not invoked while `run_a1` executes, but only later once both `run_a1` and `run_a2` have completed (since both Watchers were triggered by the original event `p.a=1`).\n",
486 "\n",
487 "Additionally we have set a higher `precedence` value for `run_a1` which results in it being executed **after** `run_a2`.\n",
488 "\n",
489 "Here we're using data from the `Event` objects given to each callback to see what's changed; try `help(param.parameterized.Event)` for details of what is in these objects (and similarly try the help for `Watcher` (returned by `watch`) or `PInfo` (returned by `.param.method_dependencies`))."
490 ]
491 },
492 {
493 "cell_type": "code",
494 "execution_count": null,
495 "metadata": {},
496 "outputs": [],
497 "source": [
498 "#help(param.parameterized.Event)\n",
499 "#help(param.parameterized.Watcher)\n",
500 "#help(param.parameterized.PInfo)"
501 ]
502 },
503 {
504 "cell_type": "markdown",
505 "metadata": {},
506 "source": [
507 "## Using dependencies and watchers\n",
508 "\n",
509 "Whether you use the `watch` or the `depends` approach, Param will store a set of `Watcher` objects on each `Parameterized` object that let it manage and process `Event`s. Param provides various context managers, methods, and Parameters that help you work with Watchers and Events:\n",
510 "\n",
511 "- [`batch_call_watchers`](#batch_call_watchers): context manager accumulating and eliding multiple Events to be applied on exit from the context \n",
512 "- [`discard_events`](#discard_events): context manager silently discarding events generated while in the context\n",
513 "- [`.param.trigger`](#.param.trigger): method to force creation of an Event for this Parameter's Watchers without a corresponding change to the Parameter\n",
514 "- [Event Parameter](#Event-Parameter): Special Parameter type providing triggerable transient Events (like a momentary push button)\n",
515 "- [Async executor](#Async-executor): Support for asynchronous processing of Events, e.g. for interfacing to external servers\n",
516 "\n",
517 "Each of these will be described in the following sections."
518 ]
519 },
520 {
521 "cell_type": "markdown",
522 "metadata": {},
523 "source": [
524 "### `batch_call_watchers`\n",
525 "\n",
526 "Context manager that accumulates parameter changes on the supplied object and dispatches them all at once when the context is exited, to allow multiple changes to a given parameter to be accumulated and short-circuited, rather than prompting serial changes from a batch of parameter setting:"
527 ]
528 },
529 {
530 "cell_type": "code",
531 "execution_count": null,
532 "metadata": {},
533 "outputs": [],
534 "source": [
535 "with param.parameterized.batch_call_watchers(p):\n",
536 " p.a = 2\n",
537 " p.a = 3\n",
538 " p.a = 1\n",
539 " p.a = 5"
540 ]
541 },
542 {
543 "cell_type": "markdown",
544 "metadata": {},
545 "source": [
546 "Here, even though `p.a` is changed four times, each of the watchers of `a` is executed only once, with the final value. One of those events then changes `b`, so `b`'s watcher is also executed once.\n",
547 "\n",
548 "If we set `b` explicitly, `b`'s watcher will be invoked twice, once for the explicit setting of `b`, and once because of the code `self.b += 1`:"
549 ]
550 },
551 {
552 "cell_type": "code",
553 "execution_count": null,
554 "metadata": {},
555 "outputs": [],
556 "source": [
557 "with param.parameterized.batch_call_watchers(p):\n",
558 " p.a = 2\n",
559 " p.b = 8\n",
560 " p.a = 3\n",
561 " p.a = 1\n",
562 " p.a = 5"
563 ]
564 },
565 {
566 "cell_type": "markdown",
567 "metadata": {},
568 "source": [
569 "If all you need to do is set a batch of parameters, you can use `.update` instead of `batch_call_watchers`, which has the same underlying batching mechanism:"
570 ]
571 },
572 {
573 "cell_type": "code",
574 "execution_count": null,
575 "metadata": {},
576 "outputs": [],
577 "source": [
578 "p.param.update(a=9,b=2)"
579 ]
580 },
581 {
582 "cell_type": "markdown",
583 "metadata": {},
584 "source": [
585 "### `discard_events`\n",
586 "\n",
587 "Context manager that discards any events within its scope that are triggered on the supplied parameterized object. Useful for making silent changes to dependent parameters, e.g. in a setup phase. If your dependencies are meant to ensure consistency between parameters, be careful that your manual changes in this context don't put the object into an inconsistent state!"
588 ]
589 },
590 {
591 "cell_type": "code",
592 "execution_count": null,
593 "metadata": {},
594 "outputs": [],
595 "source": [
596 "with param.parameterized.discard_events(p):\n",
597 " p.a = 2\n",
598 " p.b = 9"
599 ]
600 },
601 {
602 "cell_type": "markdown",
603 "metadata": {},
604 "source": [
605 "(Notice that none of the callbacks is invoked, despite all the Watchers on `p.a` and `p.b`.)"
606 ]
607 },
608 {
609 "cell_type": "markdown",
610 "metadata": {},
611 "source": [
612 "### `.param.trigger`\n",
613 "\n",
614 "Usually, a Watcher will be invoked only when a parameter is set (and only if it is changed, by default). What if you want to trigger a Watcher in other cases? For instance, if a parameter value is a mutable container like a list and you add or change an item in that container, Param's `set` method will never be invoked, because in Python the container itself is not changed when the contents are changed. In such cases, you can trigger a watcher explicitly, using `.param.trigger(*param_names)`. Triggering does not affect parameter values, apart from the special parameters of type Event (see [below](#Event-Parameter:)).\n",
615 "\n",
616 "For instance, if you set `p.b` to the value it already has, no callback will normally be invoked:"
617 ]
618 },
619 {
620 "cell_type": "code",
621 "execution_count": null,
622 "metadata": {},
623 "outputs": [],
624 "source": [
625 "p.b = p.b"
626 ]
627 },
628 {
629 "cell_type": "markdown",
630 "metadata": {},
631 "source": [
632 "But if you explicitly trigger parameter `b` on `p`, `run_b` will be invoked, even though the value of `b` is not changing:"
633 ]
634 },
635 {
636 "cell_type": "code",
637 "execution_count": null,
638 "metadata": {},
639 "outputs": [],
640 "source": [
641 "p.param.trigger('b')"
642 ]
643 },
644 {
645 "cell_type": "markdown",
646 "metadata": {},
647 "source": [
648 "If you trigger `a`, the usual series of chained events will be triggered, including changing `b`:"
649 ]
650 },
651 {
652 "cell_type": "code",
653 "execution_count": null,
654 "metadata": {},
655 "outputs": [],
656 "source": [
657 "p.param.trigger('a')"
658 ]
659 },
660 {
661 "cell_type": "markdown",
662 "metadata": {},
663 "source": [
664 "### `Event` Parameter\n",
665 "\n",
666 "An Event Parameter is a special Parameter type whose value is intimately linked to the triggering of events for Watchers to consume. Event has a Boolean value, which when set to `True` triggers the associated watchers (as any Parameter does) but then is automatically set back to `False`. \n",
667 "\n",
668 "Conversely, if events are triggered directly on a `param.Event` via `.trigger`, the value is transiently set to True (so that it's clear which of many parameters being watched may have changed), then restored to False when the triggering completes. An Event parameter is thus like a momentary switch or pushbutton with a transient True value that normally serves only to launch some other action (e.g. via a `param.depends` decorator or a watcher), rather than encapsulating the action itself as `param.Action` does. "
669 ]
670 },
671 {
672 "cell_type": "code",
673 "execution_count": null,
674 "metadata": {},
675 "outputs": [],
676 "source": [
677 "class Q(param.Parameterized):\n",
678 " e = param.Event()\n",
679 " \n",
680 " @param.depends('e', watch=True)\n",
681 " def callback(self):\n",
682 " print(f'e=={self.e}')\n",
683 " \n",
684 "q = Q()\n",
685 "q.e = True"
686 ]
687 },
688 {
689 "cell_type": "code",
690 "execution_count": null,
691 "metadata": {},
692 "outputs": [],
693 "source": [
694 "q.e"
695 ]
696 },
697 {
698 "cell_type": "code",
699 "execution_count": null,
700 "metadata": {},
701 "outputs": [],
702 "source": [
703 "q.param.trigger('e')"
704 ]
705 },
706 {
707 "cell_type": "code",
708 "execution_count": null,
709 "metadata": {},
710 "outputs": [],
711 "source": [
712 "q.e"
713 ]
714 },
715 {
716 "cell_type": "markdown",
717 "metadata": {},
718 "source": [
719 "### Async executor\n",
720 "\n",
721 "Param's events and callbacks described above are all synchronous, happening in a clearly defined order where the processing of each function blocks all other processing until it is completed. Watchers can also be used with the Python3 asyncio [`async`/`await`](https://docs.python.org/3/library/asyncio-task.html) support to operate asynchronously. To do this, you can define `param.parameterized.async_executor` with an asynchronous executor that schedules tasks on an event loop from e.g. Tornado or the [asyncio](https://docs.python.org/3/library/asyncio.html) library, which will allow you to use coroutines and other asynchronous functions as `.param.watch` callbacks.\n",
722 "\n",
723 "As an example, you can use the Tornado IOLoop underlying this Jupyter Notebook by putting events on the event loop and watching for results to accumulate:"
724 ]
725 },
726 {
727 "cell_type": "code",
728 "execution_count": null,
729 "metadata": {},
730 "outputs": [],
731 "source": [
732 "import param, asyncio, aiohttp\n",
733 "from tornado.ioloop import IOLoop\n",
734 "\n",
735 "param.parameterized.async_executor = IOLoop.current().add_callback\n",
736 "\n",
737 "class Downloader(param.Parameterized):\n",
738 " url = param.String()\n",
739 " results = param.List()\n",
740 " \n",
741 " def __init__(self, **params):\n",
742 " super().__init__(**params)\n",
743 " self.param.watch(self.fetch, ['url'])\n",
744 "\n",
745 " async def fetch(self, event):\n",
746 " async with aiohttp.ClientSession() as session:\n",
747 " async with session.get(event.new) as response:\n",
748 " img = await response.read()\n",
749 " self.results.append((event.new, img))\n",
750 "\n",
751 "f = Downloader()\n",
752 "n = 7\n",
753 "for index in range(n):\n",
754 " f.url = f\"https://picsum.photos/800/300?image={index}\"\n",
755 "\n",
756 "f.results"
757 ]
758 },
759 {
760 "cell_type": "markdown",
761 "metadata": {},
762 "source": [
763 "When you execute the above cell, you will normally get `[]`, indicating that there are not yet any results available. \n",
764 "\n",
765 "What the code does is to request 10 different images from an image site by repeatedly setting the `url` parameter of `Downloader` to a new URL. Each time the `url` parameter is modified, because of the `self.param.watch` call, the `self.fetch` callback is invoked. Because it is marked `async` and uses `await` internally, the method call returns without waiting for results to be available. Once the `await`ed results are available, the method continues with its execution and a tuple (_image_name_, _image_data_) is added to the `results` parameter.\n",
766 "\n",
767 "If you need to have all the results available (and have an internet connection!), you can wait for them:"
768 ]
769 },
770 {
771 "cell_type": "code",
772 "execution_count": null,
773 "metadata": {},
774 "outputs": [],
775 "source": [
776 "print(\"Waiting: \", end=\"\")\n",
777 "while len(f.results)<n:\n",
778 " print(f\"{len(f.results)} \", end=\"\")\n",
779 " await asyncio.sleep(0.05)\n",
780 " \n",
781 "[t[0] for t in f.results]"
782 ]
783 },
784 {
785 "cell_type": "markdown",
786 "metadata": {},
787 "source": [
788 "This `while` loop iterates until all results are available, printing a count of results so far each time through the loop. Processing is done during the `asyncio.sleep` call, which returns control to the IOLoop for that length of time, and then the `while` loop checks to see if processing is done yet. Once it's done, the list of URLs downloaded is displayed, and you can see from the ordering (unlikely to be sequential) that the downloading was done asynchronously. You can find out more about programming asynchronously in the [asyncio](https://docs.python.org/3/library/asyncio.html) docs."
789 ]
790 },
791 {
792 "cell_type": "markdown",
793 "metadata": {},
794 "source": [
795 "### Applying these techniques to your own code\n",
796 "\n",
797 "As you can see, there is extensive support for watching for events on Parameters, whether you use the low-level Watcher interface or the high-level `@param.depends` interface. As usual when multiple APIs are provided, it's a good idea to start with the high-level interface, and only drop down to the low-level watching approach if you need the additional power and control and are able to accept the corresponding complexity. The asynchronous approach is then only needed for very specific applications where you want your code to be decoupled from an external system. Most people can simply use `@param.depends` to cover all their needs for interaction between Parameters and for computation that depends on Parameters."
798 ]
799 }
800 ],
801 "metadata": {
802 "language_info": {
803 "name": "python",
804 "pygments_lexer": "ipython3"
805 }
806 },
807 "nbformat": 4,
808 "nbformat_minor": 5
809 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Dynamic parameter values\n",
7 "\n",
8 "When developing your own Python code using Parameters, there are a variety of different programming models you can use:\n",
9 "\n",
10 "1. **Parameters as fancy Python attributes**: making use of Param's semantic type checking, inheritance of default values and docstrings, etc., but not using any dynamic or event-handling features of Param. When Parameter values need to change, users change them explicitly, using their own Python code in separate methods and functions invoked from outside of Param.\n",
11 "2. **\"Push\" model**: Using Param's [Dependencies and Watchers](Dependencies_and_Watchers.ipynb) so that Param invokes user-written code to change Parameter values based on events that Param detects (typically chaining from changes in some other parameter values). A \"push\" model is typical for event-driven GUI applications, where a user interacts with a GUI widget to change some Parameter value, prompting Param to execute chained dependencies in response.\n",
12 "3. **\"Pull\" model**: Using Dynamic parameter values (described here) where the value of each dynamic parameter is computed when the parameter is read, potentially computing it from some global state values. A \"pull\" model is typical for simulations with a global clock, making it easy to use value _a1_ at time 1, value _a2_ at times 2-100, value _a3_ for 100-, etc.\n",
13 "\n",
14 "Each of these models has advantages and disadvantages that make them appropriate for different situations, so it's important to understand all three models so that you can choose the right one(s) for your system. Here, we'll discuss the third model, using `Dynamic` parameters.\n",
15 "\n",
16 "## `param.Dynamic`\n",
17 "\n",
18 "A `Dynamic` parameter of type `t` is one that accepts _either_ a value of type `t`, _or_ a callable returning a value of type `t`. If a user passes a callable, the callable will be invoked to get the actual value when the parameter value is accessed. All of Param's numeric parameter types are `Dynamic` because their base class `param.Number` inherits from `param.Dynamic`. New non-numeric types can be defined and made dynamic by inheriting from `param.Dynamic`, and having dynamic string and selector parameters would be a nice addition to the current dynamic numeric parameter support. \n",
19 "\n",
20 "To see how it works, let's make a Parameterized class with some numeric Parameters:"
21 ]
22 },
23 {
24 "cell_type": "code",
25 "execution_count": null,
26 "metadata": {},
27 "outputs": [],
28 "source": [
29 "import param, random\n",
30 "\n",
31 "class P(param.Parameterized):\n",
32 " i = param.Integer(2)\n",
33 " j = param.Integer(5)\n",
34 " k = param.Integer(8)\n",
35 " x = param.Number(-13.6)\n",
36 "\n",
37 "P(i=6, x=9.8)"
38 ]
39 },
40 {
41 "cell_type": "markdown",
42 "metadata": {},
43 "source": [
44 "Here we can set `p.i` and `p.x` to any supported numeric values, illustrating programming model 1. But we can also set them to dynamic values, for model 3:"
45 ]
46 },
47 {
48 "cell_type": "code",
49 "execution_count": null,
50 "metadata": {},
51 "outputs": [],
52 "source": [
53 "p = P(i=lambda: random.randint(35,99), x=lambda: random.random())\n",
54 "p"
55 ]
56 },
57 {
58 "cell_type": "code",
59 "execution_count": null,
60 "metadata": {},
61 "outputs": [],
62 "source": [
63 "p.i, p.i, p.i"
64 ]
65 },
66 {
67 "cell_type": "code",
68 "execution_count": null,
69 "metadata": {},
70 "outputs": [],
71 "source": [
72 "p.x, p.x, p.x"
73 ]
74 },
75 {
76 "cell_type": "markdown",
77 "metadata": {},
78 "source": [
79 "As you can see, each time you access a parameter with a dynamic value, it computes a new value and returns it. If you want to inspect the current value without changing it, you can use a special method for that:"
80 ]
81 },
82 {
83 "cell_type": "code",
84 "execution_count": null,
85 "metadata": {},
86 "outputs": [],
87 "source": [
88 "p.param.inspect_value('x'), p.param.inspect_value('x'), p.param.inspect_value('x')"
89 ]
90 },
91 {
92 "cell_type": "markdown",
93 "metadata": {},
94 "source": [
95 "Of course, dynamic parameters don't have to be random; e.g. you can set a parameter to a counter:"
96 ]
97 },
98 {
99 "cell_type": "code",
100 "execution_count": null,
101 "metadata": {},
102 "outputs": [],
103 "source": [
104 "class Count:\n",
105 " def __init__(self, start=0):\n",
106 " self._count=start\n",
107 " \n",
108 " def __call__(self):\n",
109 " self._count += 1\n",
110 " return self._count\n",
111 " \n",
112 "c = Count()\n",
113 "\n",
114 "p.j = c\n",
115 "p.j, p.j, p.j"
116 ]
117 },
118 {
119 "cell_type": "markdown",
120 "metadata": {},
121 "source": [
122 "## Using a gobal time variable\n",
123 "\n",
124 "As you can see, dynamic parameters are _very_ dynamic by default, changing every single time they are accessed. What if you want a \"somewhat dynamic\" value that changes only in certain well-defined situations? For instance, what if you are running a simulation, and you want a new dynamic value whenever time `t` changes, but otherwise the value should be constant so that no matter how many times the parameter is read at that time `t`, the result is the same? Or you are running a training or annealing or sampling or similar process that has many different iterations or runs, and you want values to change only when the iteration or run number changes, and otherwise to have the same value for a given iteration or run? \n",
125 "\n",
126 "To support simulations and other applications controlled by a central counter or state value like this, `Dynamic` supports a `time_dependent` mode where new values will be generated only if `param.Dynamic.time_fn` has changed in value since a number was last generated:"
127 ]
128 },
129 {
130 "cell_type": "code",
131 "execution_count": null,
132 "metadata": {},
133 "outputs": [],
134 "source": [
135 "param.Dynamic.time_dependent = True\n",
136 "\n",
137 "p.i, p.i, p.i"
138 ]
139 },
140 {
141 "cell_type": "markdown",
142 "metadata": {},
143 "source": [
144 "`time_fn` is a callable object that will return the current value if called:"
145 ]
146 },
147 {
148 "cell_type": "code",
149 "execution_count": null,
150 "metadata": {},
151 "outputs": [],
152 "source": [
153 "param.Dynamic.time_fn()"
154 ]
155 },
156 {
157 "cell_type": "markdown",
158 "metadata": {},
159 "source": [
160 "`time_fn` can be incremented using `+=` or changed to a specific value by calling with that value:"
161 ]
162 },
163 {
164 "cell_type": "code",
165 "execution_count": null,
166 "metadata": {},
167 "outputs": [],
168 "source": [
169 "param.Dynamic.time_fn +=1\n",
170 "p.i, p.i, p.i"
171 ]
172 },
173 {
174 "cell_type": "code",
175 "execution_count": null,
176 "metadata": {},
177 "outputs": [],
178 "source": [
179 "param.Dynamic.time_fn +=10\n",
180 "p.i, p.i, p.i"
181 ]
182 },
183 {
184 "cell_type": "code",
185 "execution_count": null,
186 "metadata": {},
187 "outputs": [],
188 "source": [
189 "param.Dynamic.time_fn(6)\n",
190 "param.Dynamic.time_fn()"
191 ]
192 },
193 {
194 "cell_type": "code",
195 "execution_count": null,
196 "metadata": {},
197 "outputs": [],
198 "source": [
199 "p.i, p.i, p.i"
200 ]
201 },
202 {
203 "cell_type": "markdown",
204 "metadata": {},
205 "source": [
206 "The global `time_fn` provides a convenient way to compute values that are fixed functions of the time value:"
207 ]
208 },
209 {
210 "cell_type": "code",
211 "execution_count": null,
212 "metadata": {},
213 "outputs": [],
214 "source": [
215 "p.k = lambda: 100+param.Dynamic.time_fn()**2\n",
216 "p.k"
217 ]
218 },
219 {
220 "cell_type": "code",
221 "execution_count": null,
222 "metadata": {},
223 "outputs": [],
224 "source": [
225 "param.Dynamic.time_fn +=10\n",
226 "p.k"
227 ]
228 },
229 {
230 "cell_type": "code",
231 "execution_count": null,
232 "metadata": {},
233 "outputs": [],
234 "source": [
235 "# Reset to the default, to support out of order execution of this notebook\n",
236 "param.Dynamic.time_dependent = False "
237 ]
238 },
239 {
240 "cell_type": "markdown",
241 "metadata": {},
242 "source": [
243 "See `help(param.Time)` for detailed information about using the `time_fn`, including:\n",
244 "- how to use `time_fn` as a context manager to test results at different times without disrupting the current time\n",
245 "- how and why to use time types other than the integer default\n",
246 "- how to set an upper limit on the time to bound a simulation run\n",
247 "- how to declare the time units and time label\n",
248 "\n",
249 "The `time_fn` is not required to be of type `param.Time`, but a lot of the features here do depend on that particular model of time.\n",
250 "\n",
251 "If there are any Parameterized objects that should _not_ respect the global time value or should respect a different time value, you can call `obj.param.set_dynamic_time_fn()` to override the time on those objects and any of their subobjects.\n",
252 "\n",
253 "You can see [topographica](https://github.com/ioam/topographica) for an example of a complex simulator built on Param's time support, including a [general-purpose event-driven simulation engine](https://github.com/ioam/topographica/blob/master/topo/base/simulation.py) capable of simulating any phenomena that can be simulated by updating at discrete times (whether on a fixed global timebase or not)."
254 ]
255 },
256 {
257 "cell_type": "code",
258 "execution_count": null,
259 "metadata": {},
260 "outputs": [],
261 "source": [
262 "#help(param.Time)"
263 ]
264 },
265 {
266 "cell_type": "markdown",
267 "metadata": {},
268 "source": [
269 "## Numbergen\n",
270 "\n",
271 "As you can see above, you can pass any callable object to a Dynamic parameter, including unnamed functions (lambdas), named functions, and custom objects with a `__call__` method. However, each of those approaches has limitations:\n",
272 "\n",
273 "- lambdas cannot easily be pickled for saving and restoring state (though see [cloudpickle](https://github.com/cloudpipe/cloudpickle) for an alternative to pickle that does support lambdas)\n",
274 "- named functions don't support internal state and need to be stored in a named module somewhere for them to be picklable, potentially resulting in a large number of one-off functions to keep track of\n",
275 "- making a new object with a `__call__` method is verbose and error-prone, and again needs to be stored in a formal module if it is to be picklable.\n",
276 "\n",
277 "To make using Dynamic parameters more convenient, Param includes a separate module [Numbergen](https://github.com/holoviz/param/blob/master/numbergen/__init__.py) that provides ready-to-use, picklable, composable, and interchangeable callable objects producing numeric values. Numbergen relies only on Param and the Python standard library, so it should be easy to add to any project. \n",
278 "\n",
279 "Numbergen objects are designed to work seamlessly as Dynamic parameter values, providing easy access to various temporal distributions, along with tools for combining and configuring number generators without having to write custom functions or classes. Moreover, because all of these objects are Parameterized objects sharing the same usage interface (each provides a numeric value when called, regardless of how many or which parameters are required to configure that distribution), using them together with Param's Dynamic support provides a huge amount of power over the values parameters take over time, without requiring any extra complexity in your program. Without Dynamic support and numbergen, your Parameterized classes could of course provide their own support for e.g. a normal random distribution by accepting a mean and variance, but it would then be limited to that specific random distribution, whereas Dynamic parameters can accept _any_ current or future number generator object as configured by a user for their own purposes, neatly separating your Parameterized's requirements (\"a positive integer value\") from the user's requirements (\"let's see what happens when the value starts at 1 and doubles every iteration\").\n",
280 "\n",
281 "Numbergen objects all inherit from `ng.NumberGenerator`, which defines the callable interface and adds [operator support](#Operations-on-number-generators) as described below. Each type of object then further inherits from either TimeAware (having basic time support) or TimeDependent (TimeAware objects having values that are a strict function of time)."
282 ]
283 },
284 {
285 "cell_type": "markdown",
286 "metadata": {},
287 "source": [
288 "### TimeDependent number generators\n",
289 "\n",
290 "If you have a global clock, TimeDependent number generators are easy to reason about: their value is a strict function of the time value returned by their `time_fn` parameter. If the generator has a value `v` at time `t`, then if time is advanced by 10 units to `t+10`, rolled back 5 units to `t+5`, and rolled back 5 more units to `t`, the value of the generator will again be `v`. These generators typically calculate their values directly from the `time_fn` value. TimeDependent objects provide a `time_dependent` parameter that is always True; the only mode they support is to be dependent on the global time. TimeDependent number generators include:\n",
291 "\n",
292 "- `ng.ScaledTime(factor=1.0)`: Simple multiplicative function of the global time value.\n",
293 "- `ng.ExponentialDecay(starting_value=1.0, ending_value=0.0, time_constant=10000, base=e)`: Returns `starting_value*base^(-time_fn()/time_constant)`.\n",
294 "- `ng.BoxCar(onset=0.0, duration=None)`: 1.0 in the exclusive interval (onset, onset+duration); zero at all other times. Default is a step function with no offset.\n",
295 "- `ng.SquareWave(onset=0.0, duration=1.0, off_duration=None)`: Alternating between 1.0 and 0.0 starting at the onset with a frequency (duration+off_duration) with a duty cycle determined by duration:off_duration (50% by default; off_duration defaults to the initial on duration).\n",
296 "\n",
297 "To demonstrate these objects, let's write a helper function that samples the distribution at different time values:"
298 ]
299 },
300 {
301 "cell_type": "code",
302 "execution_count": null,
303 "metadata": {},
304 "outputs": [],
305 "source": [
306 "import numbergen as ng\n",
307 "import pandas as pd\n",
308 "\n",
309 "param.Dynamic.time_dependent = True\n",
310 "pd.options.display.precision=3\n",
311 "\n",
312 "def timesample(ng, ts=range(0,10), time_fn = param.Dynamic.time_fn):\n",
313 " ss = []\n",
314 " for t in ts:\n",
315 " time_fn(t)\n",
316 " s = ng()\n",
317 " ss += [(t,s)]\n",
318 " df = pd.DataFrame(ss, columns=['t','s']).T\n",
319 " return df.style.set_caption(ng.param.pprint(unknown_value=None))"
320 ]
321 },
322 {
323 "cell_type": "markdown",
324 "metadata": {},
325 "source": [
326 "Now we can see how each of these objects behaves as `t` changes:"
327 ]
328 },
329 {
330 "cell_type": "code",
331 "execution_count": null,
332 "metadata": {},
333 "outputs": [],
334 "source": [
335 "timesample(ng.ScaledTime(factor=2.0))"
336 ]
337 },
338 {
339 "cell_type": "code",
340 "execution_count": null,
341 "metadata": {},
342 "outputs": [],
343 "source": [
344 "timesample(ng.ExponentialDecay(time_constant=2, base=4))"
345 ]
346 },
347 {
348 "cell_type": "code",
349 "execution_count": null,
350 "metadata": {},
351 "outputs": [],
352 "source": [
353 "timesample(ng.BoxCar(onset=0.0, duration=3))"
354 ]
355 },
356 {
357 "cell_type": "code",
358 "execution_count": null,
359 "metadata": {},
360 "outputs": [],
361 "source": [
362 "timesample(ng.SquareWave(onset=0.0, duration=1.0, off_duration=2.0))"
363 ]
364 },
365 {
366 "cell_type": "markdown",
367 "metadata": {},
368 "source": [
369 "### TimeAware random number generators\n",
370 "\n",
371 "A TimeAware object also has access to a `time_fn` and has a `time_dependent` parameter, but either sets `time_dependent=False` (indicating that values are never a strict function of time) or allows either True or False (switching into and out of a time dependent mode). All current `TimeAware` NumberGenerator objects are random number generators that support both possible values of `time_dependent`. For `time_dependent=False` (the default), they return a new value on each call, while for `time_dependent=True`, they return pseudorandom values that follow the indicated distribution but are also a strict function of the time, in that the same number will be returned for a given time value even if time skips ahead or backwards. \n",
372 "\n",
373 "These random values are thus very tightly controlled to allow reproducible, repeatable results, with values determined by both a seed value (to choose the overall set of random values) and by the current time. Effectively, when `time_dependent=True`, these numbers provide a random value seeded by the generator's `name` parameter, the global `param.random_seed`, the `seed` parameter of the NumberGenerator, _and_ the NumberGenerator's current `time_fn()` value. The resulting generated values should be the same for a given object and a given `time_fn` value, even across platforms and machine-word sizes (see the [Hash](https://github.com/holoviz/param/blob/master/numbergen/__init__.py#L176), TimeAwareRandomState, and RandomDistribution classes for more details). \n",
374 "\n",
375 "For best results, you should provide an explicit unique name to any such generator and preserve that name over time, so that results will be reproducible across program runs. By default, the underlying random numbers are generated using Python's [random](https://docs.python.org/3/library/random.html) module (which see for details of the number generation), but you can substitute an instance of `numpy.random.RandomState` or similar compatible object for `self.random_generator` for higher performance or to generate time-dependent array values.\n",
376 "\n",
377 "RandomDistributions (all TimeAware and supporting `time_dependent`) include:\n",
378 "\n",
379 "- `ng.UniformRandom(lbound=0.0, ubound=1.0)`: Uniform random float in the range [lbound, ubound).\n",
380 "- `ng.UniformRandomOffset(mean=0, range=1.0)`: Same as UniformRandom, but returns a random float in the range [mean - range/2, mean + range/2).\n",
381 "- `ng.UniformRandomInt(lbound=0, ubound=1000)`: Uniform random integer in the (inclusive) range [lbound, ubound].\n",
382 "- `ng.Choice(choices=[0,1])`: Random value from a provided list of choices.\n",
383 "- `ng.NormalRandom(mu=0.0, sigma=1.0)`: Normally distributed (Gaussian) random number with mean mu and standard deviation sigma.\n",
384 "- `ng.VonMisesRandom(mu=0,kappa=1)`: Circularly normal distributed random number centered around `mu` with inverse variance `kappa`; for `kappa=0` the result is uniformly distributed from 0 to 2*pi, and for narrow kappa it approaches the normal distribution with variance 1/kappa."
385 ]
386 },
387 {
388 "cell_type": "code",
389 "execution_count": null,
390 "metadata": {},
391 "outputs": [],
392 "source": [
393 "timesample(ng.UniformRandom(lbound=0.0, ubound=10.0))"
394 ]
395 },
396 {
397 "cell_type": "code",
398 "execution_count": null,
399 "metadata": {},
400 "outputs": [],
401 "source": [
402 "timesample(ng.UniformRandomOffset(mean=100, range=3))"
403 ]
404 },
405 {
406 "cell_type": "code",
407 "execution_count": null,
408 "metadata": {},
409 "outputs": [],
410 "source": [
411 "timesample(ng.UniformRandomInt(lbound=0, ubound=1000))"
412 ]
413 },
414 {
415 "cell_type": "code",
416 "execution_count": null,
417 "metadata": {},
418 "outputs": [],
419 "source": [
420 "timesample(ng.Choice(choices=[3.1, -95, 7]))"
421 ]
422 },
423 {
424 "cell_type": "code",
425 "execution_count": null,
426 "metadata": {},
427 "outputs": [],
428 "source": [
429 "timesample(ng.NormalRandom(mu=50.0, sigma=5.0))"
430 ]
431 },
432 {
433 "cell_type": "code",
434 "execution_count": null,
435 "metadata": {},
436 "outputs": [],
437 "source": [
438 "timesample(ng.VonMisesRandom(mu=0, kappa=500)) # small variance around 0 (aka 2pi)"
439 ]
440 },
441 {
442 "cell_type": "markdown",
443 "metadata": {},
444 "source": [
445 "### Operations on number generators\n",
446 "\n",
447 "Numbergen also provides a couple of NumberGenerators that accept other NumberGenerator objects and filter or modify their values:\n",
448 "\n",
449 "- `ng.TimeSampledFn(period=1.0, offset=1.0, fn=None)`: Discretizes the given time-dependent NumberGenerator to give discrete values held constant over the given period, changing a continuous function of time into a series of discrete steps starting at the indicated offset and changing at the indicated period.\n",
450 "- `ng.BoundedNumber(generator=None, bounds=(None,None))`: Wrapper around another number generator (any callable returning a number) that silently crops the result to the given bounds.\n",
451 "\n",
452 "It also provides a set of unary (`- + abs()`) and binary (`+ - * % ** / //`) mathematical operators that make it simple to adapt the output for usage in practice without having to define one-off functions. For instance:"
453 ]
454 },
455 {
456 "cell_type": "code",
457 "execution_count": null,
458 "metadata": {},
459 "outputs": [],
460 "source": [
461 "timesample(-abs(ng.ScaledTime(factor=2.0)+1)//4)"
462 ]
463 },
464 {
465 "cell_type": "code",
466 "execution_count": null,
467 "metadata": {},
468 "outputs": [],
469 "source": [
470 "timesample(ng.UniformRandom()%0.2)"
471 ]
472 },
473 {
474 "cell_type": "code",
475 "execution_count": null,
476 "metadata": {},
477 "outputs": [],
478 "source": [
479 "timesample(2*ng.SquareWave()-1)"
480 ]
481 },
482 {
483 "cell_type": "markdown",
484 "metadata": {},
485 "source": [
486 "## Using numbergen objects for parameter values\n",
487 "\n",
488 "Any of the above objects can be supplied for any `param.Number` Parameter type. For instance, instead of the lambdas in the first examples in this guide, you can use Numbergen objects:"
489 ]
490 },
491 {
492 "cell_type": "code",
493 "execution_count": null,
494 "metadata": {},
495 "outputs": [],
496 "source": [
497 "param.Dynamic.time_dependent = False\n",
498 "\n",
499 "p = P(i=ng.UniformRandomInt(lbound=35, ubound=99), \n",
500 " x=ng.UniformRandom())\n",
501 "\n",
502 "p.i, p.i, p.i"
503 ]
504 },
505 {
506 "cell_type": "code",
507 "execution_count": null,
508 "metadata": {},
509 "outputs": [],
510 "source": [
511 "p.x, p.x, p.x"
512 ]
513 },
514 {
515 "cell_type": "markdown",
516 "metadata": {},
517 "source": [
518 "Notice that the decision to use a particular distribution is up to the _user_ of class `P`, not the author of `P`. The author of `P` just needs to know that `i` will be an integer and that `x` will be a float (with bounds if specified); the user is then free to set those values to be static or any type of dynamic value as needed. Using Param with this \"pull\" model thus provides users with easy ways to control how parameters change their value over time (for some model of time), without additional work by the Parameterized class author. You can see extensive examples of this approach at the [imagen](https://imagen.holoviz.org) website, which shows how Numbergen objects can be used to create flexible streams of generated image objects without needing any special support for such streams in the Parameterized objects in that library."
519 ]
520 }
521 ],
522 "metadata": {
523 "language_info": {
524 "name": "python",
525 "pygments_lexer": "ipython3"
526 }
527 },
528 "nbformat": 4,
529 "nbformat_minor": 5
530 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# How Param Works\n",
7 "\n",
8 "Param seamlessly makes Python attributes have a much richer set of behaviors than they would otherwise, at once both more powerful (with automatic dynamic behavior) and more tightly controlled by the class author. It is natural to wonder how Param achieves this, especially given that it is a normal pure-Python library, not an alternative implementation of Python or a pre-processor. The answer is that Param makes extensive use of Python language features that allow tailoring the behavior of attribute getting and setting in sophisticated ways. You don't need to read any of the material on this page to use Param successfully, but it might help you understand what's going on \"under the hood\" for debugging or optimizing complex situations or for extending Param.\n",
9 "\n",
10 "## Descriptors\n",
11 "\n",
12 "A `Parameter` object is a type of Python \"descriptor\", i.e., an object that implements custom `__get__` and/or `__set__` behavior. When a descriptor is an attribute of a class, Python will invoke those custom methods instead of simply getting and setting the actual value of the attribute (i.e., the `Parameter` object). The [Python descriptor docs](https://docs.python.org/3/howto/descriptor.html) explain this process in detail, but briefly, let's consider a simple descriptor that returns how many times it has been accessed:"
13 ]
14 },
15 {
16 "cell_type": "code",
17 "execution_count": null,
18 "metadata": {},
19 "outputs": [],
20 "source": [
21 "class Count:\n",
22 " def __init__(self, start=0):\n",
23 " self._count=start\n",
24 " \n",
25 " def __get__(self, obj, objtype=None):\n",
26 " self._count += 1\n",
27 " return self._count"
28 ]
29 },
30 {
31 "cell_type": "code",
32 "execution_count": null,
33 "metadata": {},
34 "outputs": [],
35 "source": [
36 "class C:\n",
37 " x = 5\n",
38 " y = Count(0)\n",
39 "\n",
40 "c = C()\n",
41 "\n",
42 "c.x, c.x, c.x, c.y, c.y, c.y"
43 ]
44 },
45 {
46 "cell_type": "markdown",
47 "metadata": {},
48 "source": [
49 "As you can see, class attributes `x` and `y` here can both be used the same way, but `x` is a normal Python attribute, returning the fixed value `5` that was set on the class, while `y` is a descriptor and returns a computed value when accessed (rather than returning itself as you might think from the syntax), and thus gives a different value each time. Parameters are much more complex than the above example, but this descriptor support provides the underlying mechanism for having full-featured attribute behavior like dynamic values, bounds checking, and so on."
50 ]
51 },
52 {
53 "cell_type": "markdown",
54 "metadata": {},
55 "source": [
56 "## Slots\n",
57 "\n",
58 "As described in the [Parameters docs](Parameters.ipynb), Parameters can store a rich collection of metadata about each parameter. Storing a full object and associated dictionary of metadata for each class and instance attribute could get expensive (i.e., slow and using a lot of memory), so Parameters are implemented using [slots](https://docs.python.org/3/reference/datamodel.html#slots). A slot is like a normal Python attribute, but instead of being stored in the convenient and flexible but slow `__dict__` attribute of the object, slots are stored in a fixed-size data structure `__slots__` that works like a C `struct`. `__slots__` reserves just enough space to store these attributes, which can be accessed instantaneously rather than requiring a dictionary lookup (hash table search).\n",
59 "\n",
60 "Using `__slots__` requires special support for operations to copy and restore Parameters (e.g. for Python persistent storage pickling); see `__getstate__` and `__setstate__`. A Parameter defines at least these slots, with additional slots added for each subclass:\n",
61 "\n",
62 "```\n",
63 "__slots__ = ['name', '_internal_name', 'default', 'doc', 'precedence', \n",
64 " 'instantiate', 'constant', 'readonly', 'pickle_default_value',\n",
65 " 'allow_None', 'per_instance', 'watchers', 'owner', '_label']\n",
66 "```\n",
67 "\n",
68 "In most cases, you can just treat a Parameter's existing slots like attributes of the Parameter class; they work just the same as regular attributes except for speed and storage space. However, if you add a _new_ attribute to a Parameter class, you have to make sure that you also add it to the `__slots__` defined for that Parameter class, or you'll either get an error or else the Parameter will get an unnecessary full `__dict__` object just to hold the one new attribute. "
69 ]
70 },
71 {
72 "cell_type": "markdown",
73 "metadata": {},
74 "source": [
75 "## Metaclasses\n",
76 "\n",
77 "Another way `Parameter` and `Parameterized` differ from ordinary Python classes is that they specify a special [metaclass](https://docs.python.org/3/reference/datamodel.html#metaclasses) that determines how they behave. Just like you instantiate a Python class to get a Python object, you instantiate a Python metaclass to get a Python class. Most classes are instances of the default metaclass named `type`, but with a custom metaclass, you can define how every Python class with that metaclass will behave, at a fundamental level. \n",
78 "\n",
79 "The `ParameterMetaclass` is fairly simple, mainly overriding docstrings so that `help(someparam)` gives the declared documentation for the Parameter instance, rather than the less-useful docstring for the underlying class that it would otherwise display. This behavior is convenient, but not essential to the operation of Param. \n",
80 "\n",
81 "`ParameterizedMetaclass`, on the other hand, defines a lot of the behavior behind Param's features. In particular, the metaclass implements the behavior for getting and setting parameter values at the class level, similar to how a descriptor controls such behavior at the instance level. Without the metaclass, setting the value of a class attribute to a scalar like `5` would wipe out the `Parameter` object rather than updating the default value. The metaclass thus performs the same role at the class level as descriptors do at the instance level. Descriptors allow setting the value of an instance attribute without overriding the `Parameter` object on that instance, and the metaclass allows setting the value of a class attribute without overridding the `Parameter` object on the class. All told, the ParameterizedMetaclass handles: \n",
82 "\n",
83 "- allowing Parameter default values to be set at the class level (as just described),\n",
84 "- supporting inheriting Parameter objects from superclasses, \n",
85 "- instantiating parameter default values into the class's dictionary (if needed)\n",
86 "- populating the `name` slot of each Parameter by its attribute name in the class,\n",
87 "- reporting whether a class has been declared to be abstract (useful for ignoring it in selectors),\n",
88 "- various bookkeeping about dependencies and watchers, \n",
89 "- generating docstrings at the class level for each Parameter in the class so that `help(parameterizedclass)` displays not just the class docstring but also information about the Parameters in it (or in superclasses)\n",
90 "\n",
91 "Thus much of how Param works depends on ParameterizedMetaclass. "
92 ]
93 },
94 {
95 "cell_type": "markdown",
96 "metadata": {},
97 "source": [
98 "## Custom attribute access\n",
99 "\n",
100 "The above mechanisms let Param [customize attribute access](https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access) for dynamic behavior and control over user settings. As an example of how this all fits together, consider the following code:"
101 ]
102 },
103 {
104 "cell_type": "code",
105 "execution_count": null,
106 "metadata": {},
107 "outputs": [],
108 "source": [
109 "from param import Parameterized, Parameter\n",
110 "\n",
111 "class A(Parameterized):\n",
112 " p = Parameter(default=1, per_instance=False, instantiate=False)\n",
113 "\n",
114 "a1 = A()\n",
115 "a2 = A()"
116 ]
117 },
118 {
119 "cell_type": "markdown",
120 "metadata": {},
121 "source": [
122 "Here, a1 and a2 share one Parameter object (`A.__dict__['p']`), because `per_instance` is `False`:"
123 ]
124 },
125 {
126 "cell_type": "code",
127 "execution_count": null,
128 "metadata": {},
129 "outputs": [],
130 "source": [
131 "A.__dict__['p'] is a1.param.p is a2.param.p"
132 ]
133 },
134 {
135 "cell_type": "markdown",
136 "metadata": {},
137 "source": [
138 "The default (class-attribute) value of p is stored in this Parameter object (`A.__dict__['p'].default`), but is accessible as `A.p` due to the Parameter being a descriptor:"
139 ]
140 },
141 {
142 "cell_type": "code",
143 "execution_count": null,
144 "metadata": {},
145 "outputs": [],
146 "source": [
147 "A.__dict__['p'].default"
148 ]
149 },
150 {
151 "cell_type": "code",
152 "execution_count": null,
153 "metadata": {},
154 "outputs": [],
155 "source": [
156 "A.p"
157 ]
158 },
159 {
160 "cell_type": "markdown",
161 "metadata": {},
162 "source": [
163 "If the value of `p` is set on `a1`, `a1`'s value of `p` is stored in the `a1` instance itself, under a specially mangled attribute name. The mangled name is called the `_internal_name` of the parameter, and is constructed from the \"attrib name\" of the parameter (i.e. `p` in this case) but modified so that it will not be confused with the underlying `Parameter` object `p`:"
164 ]
165 },
166 {
167 "cell_type": "code",
168 "execution_count": null,
169 "metadata": {},
170 "outputs": [],
171 "source": [
172 "a1.p=2\n",
173 "a1.__dict__['_p_param_value']"
174 ]
175 },
176 {
177 "cell_type": "markdown",
178 "metadata": {},
179 "source": [
180 "When `a1.p` is requested, `a1.__dict__['_p_param_value']` is returned. When `a2.p` is requested, `_p_param_value` is not found in `a2.__dict__`, so `A.__dict__['p'].default` (i.e. `A.p`) is returned instead:"
181 ]
182 },
183 {
184 "cell_type": "code",
185 "execution_count": null,
186 "metadata": {},
187 "outputs": [],
188 "source": [
189 "a2.p"
190 ]
191 },
192 {
193 "cell_type": "markdown",
194 "metadata": {},
195 "source": [
196 "Because the value for `a2.p` is returned from `A.p`, changing `A.p` will affect `a2.p`, but not `a1.p` since it has its own independent value:"
197 ]
198 },
199 {
200 "cell_type": "code",
201 "execution_count": null,
202 "metadata": {},
203 "outputs": [],
204 "source": [
205 "A.p=3\n",
206 "a2.p, a1.p"
207 ]
208 },
209 {
210 "cell_type": "markdown",
211 "metadata": {},
212 "source": [
213 "If `p` was not defined in `A` but was defined in a superclass, the value found in that superclass would be returned instead. \n",
214 "\n",
215 "You can re-execute the above code changing to `per_instance=True` and/or `instantiate=True` on Parameter `p` and see how the behavior differs. With `per_instance=True` (which would normally be the default), `a1` and `a2` would each have independent copies of the `Parameter` object, and with `instantiate=True`, each instance would get its own copy of the class's default value, making it immune to later changes at the class level."
216 ]
217 },
218 {
219 "cell_type": "markdown",
220 "metadata": {},
221 "source": [
222 "# Other notes\n",
223 "\n",
224 "Once we have Parameter descriptors and the metaclasses, there is relatively little left for the Parameterized class itself to do:\n",
225 "\n",
226 "- implementing the rest of [dependencies and watchers](Dependencies_and_Watchers.ipynb)\n",
227 "- providing a constructor that lets you set instance parameters\n",
228 "- instantiating and providing the `.param` accessor for invoking methods and accessing the Parameter objects\n",
229 "- handling state saving and restoring (pickling)\n",
230 "\n",
231 "And that's it for the core of Param! There are other behaviors implemented at the level of specific Parameters, but those are typically localized and can be understood by reading the class docstring for that Parameter type."
232 ]
233 }
234 ],
235 "metadata": {
236 "language_info": {
237 "name": "python",
238 "pygments_lexer": "ipython3"
239 }
240 },
241 "nbformat": 4,
242 "nbformat_minor": 5
243 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Logging and Warnings\n",
7 "\n",
8 "`Parameterized` objects provide methods for displaying logging messages and warnings in a way that can be controlled and redirected globally using the standard Python [logging](https://docs.python.org/3/library/logging.html) module (see the [logging cookbook](https://docs.python.org/3/howto/logging.html)). Compared to using a Python logger directly, using these methods inside your Parameterized class helps users by making the messages consistent, each prepending with information about the Parameterized object where the call was made so that your users can understand what the message indicates.\n",
9 "\n",
10 "By default, a Python logger named `param` will be instantiated to do the logging, but another logger can be supplied by setting `param.parameterized.logger` to it after importing `param.parameterized`.\n",
11 "\n",
12 "\n",
13 "## Writing log messages\n",
14 "\n",
15 "Each logging message has an associated logging `level` that indicates how severe a condition is being described (DEBUG, VERBOSE, INFO (aka \"message\"), WARNING, ERROR, or CRITICAL). These levels are as defined by the logging module, except for the addition of VERBOSE as a level intermediate between DEBUG (internal debugging information) and INFO (user-relevant non-error messages).\n",
16 "\n",
17 "The typical way to print a message is to call `.param.log()` with one of the following logging levels:\n",
18 "\n",
19 "- `.param.log(param.DEBUG, ...)`: Detailed debugging information, not displayed onscreen by default.\n",
20 "- `.param.log(param.VERBOSE, ...)`: Additional sometimes-useful information, not displayed onscreen by default.\n",
21 "- `.param.log(param.INFO, ...)`: Informative message, displayed onscreen by default.\n",
22 "- `.param.log(param.WARNING, ...)`: Warning of an unexpected or inappropriate but non-fatal condition, displayed onscreen by default.\n",
23 "\n",
24 "For convenience and to allow eventual integration with the Python [warnings](https://docs.python.org/3/library/warnings.html) module, `.param.warning(` is available as an alias for `.param.log(param.WARNING,`:\n",
25 "\n",
26 "- `.param.warning(...)`: Warning of an unexpected or inappropriate but non-fatal condition, displayed onscreen by default.\n",
27 "\n",
28 "The arguments accepted in each case are the same as those of [logging.debug()](https://docs.python.org/3/library/logging.html#logging.debug). Specifically, each call is like `.param.debug(msg, *args, **kw)`, where `msg` is an [old-style ('%') format string](https://wiki.python.org/moin/StringFormatting) and the `args` and `kwargs` will be merged with that format string. E.g.:"
29 ]
30 },
31 {
32 "cell_type": "code",
33 "execution_count": null,
34 "metadata": {},
35 "outputs": [],
36 "source": [
37 "import param\n",
38 "\n",
39 "desired = 1\n",
40 "actual = 5\n",
41 "\n",
42 "param.main.param.log(param.INFO, \"Welcome!\")\n",
43 "param.main.param.log(param.VERBOSE, \"Local variables: %s\", locals())\n",
44 "param.main.param.log(param.WARNING, \"Value %02d is not %d\", actual, desired)\n",
45 "param.main.param.warning(\"Value %02d is not %d\", actual, desired)"
46 ]
47 },
48 {
49 "cell_type": "markdown",
50 "metadata": {},
51 "source": [
52 "Here we've used the default global Parameterized object `param.main`, useful for generic module-level messages, but more often you would make a call like `self.param.log()` _inside_ a Parameterized class instead. You can see that the messages are each prefixed by the logging level, `param` (the name of the default logger), and the name of this object (`main` in this case). You can also see that, by default, verbose messages are not actually printed.\n",
53 "\n",
54 "You may wonder (a) why the formatting string is \"old style\" , and (b) why the formatting values \"actual, desired\" are not combined directly with the formatting string. I.e., why not just use a Python3 f-string, like:"
55 ]
56 },
57 {
58 "cell_type": "code",
59 "execution_count": null,
60 "metadata": {},
61 "outputs": [],
62 "source": [
63 "param.main.param.log(param.WARNING, f\"Value {actual:02} is not {desired}\") # Discouraged!"
64 ]
65 },
66 {
67 "cell_type": "markdown",
68 "metadata": {},
69 "source": [
70 "The answer is that particularly for `debug` and `verbose` messages that could occur inside frequently executed code, we want logging to be \"lazy\", in that we do not want to render a string representation for `actual`, `desired`, etc. unless we are actually printing the message. If we use an f-string or any other locally formatted string, the string formatting will be done _whether_ _or_ _not_ the message will be displayed, potentially causing drastic slowdowns in your code. For instance, in the code above, the entire `locals()` dictionary would be iterated over, printed to strings. Of course, since the message isn't being printed in that case, the entire format string would then be discarded, greatly slowing down the code without producing any output. To avoid that, the logging module has to defer the string substitution and handle that itself, and it was written before Python3, so it only supports old-style substitution. So, even though it is more awkward, it is highly recommended to use this old-style, lazy string formatting support."
71 ]
72 },
73 {
74 "cell_type": "markdown",
75 "metadata": {},
76 "source": [
77 "## Controlling the logging level\n",
78 "\n",
79 "You can use the `param.parameterized.logging_level` context manager to temporarily reduce or elevate the logging level while you execute code:"
80 ]
81 },
82 {
83 "cell_type": "code",
84 "execution_count": null,
85 "metadata": {},
86 "outputs": [],
87 "source": [
88 "with param.logging_level('CRITICAL'):\n",
89 " param.main.param.log(param.INFO, \"Message 1\")\n",
90 " param.main.param.log(param.VERBOSE, \"Verbose 1\")"
91 ]
92 },
93 {
94 "cell_type": "code",
95 "execution_count": null,
96 "metadata": {},
97 "outputs": [],
98 "source": [
99 "with param.logging_level('DEBUG'):\n",
100 " param.main.param.log(param.INFO, \"Message 2\")\n",
101 " param.main.param.log(param.VERBOSE, \"Verbose 2\")"
102 ]
103 },
104 {
105 "cell_type": "markdown",
106 "metadata": {},
107 "source": [
108 "You can also set the value more globally (and permanently) on the logger object:"
109 ]
110 },
111 {
112 "cell_type": "code",
113 "execution_count": null,
114 "metadata": {},
115 "outputs": [],
116 "source": [
117 "param.get_logger().setLevel(param.DEBUG)"
118 ]
119 },
120 {
121 "cell_type": "code",
122 "execution_count": null,
123 "metadata": {},
124 "outputs": [],
125 "source": [
126 "param.main.param.log(param.INFO, \"Message 2\")\n",
127 "param.main.param.log(param.VERBOSE, \"Verbose 2\")"
128 ]
129 },
130 {
131 "cell_type": "markdown",
132 "metadata": {},
133 "source": [
134 "For testing, continuous integration (CI), or other specific applications, you can also set `param.parameterized.warnings_as_exceptions = True`, which will cause your program to raise an exception the first time it encounters any warning."
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "# Controlling the formatting of log messages\n",
142 "\n",
143 "The Python logging module provides many options for configuring how the log messages are generated. For complete control, you can instantiate your own logger and set `param.parameterized.logger` to it after importing `param.parameterized`.\n",
144 "\n",
145 "A hook is provided for the relatively common case when you want to prefix each message with the time of day, a progress indication, a simulator time, or some other indication of a global state. Specifically, you can set `param.parameterized.dbprint_prefix` to a callable object returning a string. The object will be called when constructing each message:"
146 ]
147 },
148 {
149 "cell_type": "code",
150 "execution_count": null,
151 "metadata": {},
152 "outputs": [],
153 "source": [
154 "from datetime import datetime\n",
155 "#param.parameterized.warnings_as_exceptions = True\n",
156 "\n",
157 "param.parameterized.dbprint_prefix=lambda: str(datetime.now())\n",
158 "\n",
159 "param.main.param.warning(\"Message 4\")\n",
160 "param.main.param.warning(\"Message 5\")"
161 ]
162 },
163 {
164 "cell_type": "markdown",
165 "metadata": {},
166 "source": [
167 "## Counting warnings\n",
168 "\n",
169 "Typically, a program will abort if an error is encountered, making such a condition hard to miss, but warning messages might be lost in a sea of informational, verbose, or debug messages. Param keeps track of how many times `.param.warning(...)` (or its alias `.param.log(param.WARNING, ...)`) has been called, and it is often useful to print out that value at the end of a program run:"
170 ]
171 },
172 {
173 "cell_type": "code",
174 "execution_count": null,
175 "metadata": {},
176 "outputs": [],
177 "source": [
178 "print(f\"Run completed. {param.parameterized.warning_count} warnings were encountered.\")"
179 ]
180 }
181 ],
182 "metadata": {
183 "language_info": {
184 "name": "python",
185 "pygments_lexer": "ipython3"
186 }
187 },
188 "nbformat": 4,
189 "nbformat_minor": 5
190 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Outputs\n",
7 "\n",
8 "Parameters are typically used for the _inputs_ to a Parameterized objects, declaring as precisely as possible which inputs are allowed. You can also declare what _outputs_ are generated by a Parameterized, using `@param.output`. At present, Param itself does not use output declarations internally, but they can be queried by Param-based programs to allow safe chaining of Parameterized objects into pipelines, such as for the [boxflow](https://github.com/ioam/boxflow) dataflow programming system or the multi-page pipelines in [Panel](https://panel.holoviz.org/user_guide/Pipelines.html). \n",
9 "\n",
10 "`@param.output` annotates a method on a Parameterized class to declare that it returns one or more outputs of a specified type. As a simple example, this declaration indicates that the given function returns a number:"
11 ]
12 },
13 {
14 "cell_type": "code",
15 "execution_count": null,
16 "metadata": {},
17 "outputs": [],
18 "source": [
19 "import param\n",
20 "\n",
21 "class P(param.Parameterized):\n",
22 " a = param.Number(default=5, bounds=(0, 10))\n",
23 " b = param.Number(default=5, bounds=(0, 10))\n",
24 "\n",
25 " @param.output(param.Number)\n",
26 " def product(self):\n",
27 " return self.a * self.b\n",
28 " \n",
29 "p = P(a=2, b=3)\n",
30 "p.product()"
31 ]
32 },
33 {
34 "cell_type": "markdown",
35 "metadata": {},
36 "source": [
37 "If a program wants to know if the output from object `p` is suitable for connecting to an input of some other object `q`, it can query this output type:"
38 ]
39 },
40 {
41 "cell_type": "code",
42 "execution_count": null,
43 "metadata": {},
44 "outputs": [],
45 "source": [
46 "p.param.outputs()"
47 ]
48 },
49 {
50 "cell_type": "markdown",
51 "metadata": {},
52 "source": [
53 "This return value is of the form _name_: (_type_, _method_, _index_), which here indicates that:\n",
54 "- object `p` generates one output called `product` \n",
55 "- `product` is of type `param.Number`\n",
56 "- `product` can be generated by invoking method `p.product()` \n",
57 "- `product` will be returned directly as a single value by the method (indicated by an index of `None`; otherwise the index would indicate the position of this particular output in a tuple returned by the method)\n",
58 "\n",
59 "An automated program could use this information to decide whether the output from one Parameterized is suitable for connecting to the input of another.\n",
60 "\n",
61 "The above example is typical, but let's review the other output declarations accepted by `@param.output`. The simplest declaration declares the method returns an object of the same name as the method, without any type guarantees:\n",
62 "\n",
63 "```python\n",
64 "@param.output()\n",
65 "def product(self): return self.a * self.b\n",
66 "```\n",
67 "\n",
68 "More commonly, a parameter type will be specified as above, indicating that this method returns a value of that type, again defaulting to the method name:\n",
69 "\n",
70 "```python\n",
71 "@param.output(param.Number())\n",
72 "def product2(self): return self.a * self.b\n",
73 "```\n",
74 "\n",
75 "The output name can be declared explicitly as a keyword argument if desired, e.g. if the method name is not a suitable output name:\n",
76 "\n",
77 "```python\n",
78 "@param.output(result=param.Number())\n",
79 "def __call__(self): return self.a * self.b\n",
80 "```\n",
81 "\n",
82 "Multiple outputs may be declared using keywords mapping from output name to the type (Python >= 3.6 only):\n",
83 "\n",
84 "```python\n",
85 "@param.output(prod_num=param.Number(), prod_str=param.String())\n",
86 "def products(self): \n",
87 " prod = self.a * self.b\n",
88 " return prod, str(prod)\n",
89 "```\n",
90 "\n",
91 "`@param.output` also accepts Python object types, which will be upgraded to a ClassSelector:\n",
92 "\n",
93 "```python\n",
94 "@param.output(int)\n",
95 "def int_product(self): return int(self.a * self.b)\n",
96 "```\n",
97 "\n",
98 "We can see these various options in action:"
99 ]
100 },
101 {
102 "cell_type": "code",
103 "execution_count": null,
104 "metadata": {},
105 "outputs": [],
106 "source": [
107 "class Q(param.Parameterized):\n",
108 " a = param.Number(default=5, bounds=(0, 10))\n",
109 " b = param.Number(default=5, bounds=(0, 10))\n",
110 "\n",
111 " @param.output()\n",
112 " def product(self): return self.a * self.b\n",
113 "\n",
114 " @param.output(param.Number())\n",
115 " def product2(self): return self.a * self.b\n",
116 "\n",
117 " @param.output(result=param.Number())\n",
118 " def __call__(self): return self.a * self.b\n",
119 "\n",
120 " @param.output(prod_num=param.Number(), prod_str=param.String())\n",
121 " def products(self): \n",
122 " prod = self.a * self.b\n",
123 " return prod, str(prod)\n",
124 "\n",
125 " @param.output(int)\n",
126 " def int_product(self): return int(self.a * self.b)\n",
127 "\n",
128 "q=Q()\n",
129 "q"
130 ]
131 },
132 {
133 "cell_type": "code",
134 "execution_count": null,
135 "metadata": {},
136 "outputs": [],
137 "source": [
138 "q.param.outputs()"
139 ]
140 },
141 {
142 "cell_type": "markdown",
143 "metadata": {},
144 "source": [
145 "Here, you can see that there are _two_ outputs from `prod_str()`, one of type Number and one of type String, and that they are in the order (number, string) in the tuple. The other outputs are all a single result returned directly from that method, with the indicated types (defaulting to `Parameter`) and names. Annotating outputs in this way can help you build large, flexible systems for connecting Parameterized objects together into larger data or computational structures."
146 ]
147 }
148 ],
149 "metadata": {
150 "language_info": {
151 "name": "python",
152 "pygments_lexer": "ipython3"
153 }
154 },
155 "nbformat": 4,
156 "nbformat_minor": 5
157 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Parameter types\n",
7 "\n",
8 "You can get some of the benefit of Param from Parameter and Parameterized alone, such as having constant or readonly parameters, parameter value inheritance, and parameter docstrings. However, you'll typically want a more specialized Parameter type so that you can enforce type and bounds restrictions and enable suitable behavior specialized for that type of parameter. Param ships with a large set of pre-defined more-specific Parameter types, and additional custom parameters can and should be added for any domain-specific parameter types needed.\n",
9 "\n",
10 "The predefined types are organized into a class hierarchy where each type is a subclass of Parameter:\n",
11 "\n",
12 "- [String](#strings): String value, optionally constrained by a regular expression\n",
13 "- [Color](#colors): A hex string specifying an RGB color, or a standard named color\n",
14 "- [Boolean](#booleans): True or False (or None, if allowed)\n",
15 " * [Event](#invocations): True/False parameter used to trigger actions (see [Dependencies and watchers](Dependencies_and_Watchers.ipynb)).\n",
16 "- [Dynamic](#numbers): Base class for values that can be set to a callable that returns a concrete value\n",
17 " * [Number](#numbers): Any type of numeric value\n",
18 " - [Integer](#numbers): Positive or negative integer value\n",
19 " - [Magnitude](#numbers): Positive value in the inclusive range 0.0,1.0\n",
20 " - [Date](#numbers): Date or datetime type\n",
21 " - [CalendarDate](#numbers): Date (not datetime)\n",
22 "- [Tuple](#tuples): Python tuple of a fixed length and optional fixed type\n",
23 " * [NumericTuple](#tuples): Python tuple of a fixed length and a numeric type\n",
24 " - [XYCoordinates](#tuples): Position in an x,y plane\n",
25 " - [Range](#tuples): A numeric range or interval\n",
26 " * [DateRange](#tuples): A range of dates or datetimes\n",
27 " * [CalendarDateRange](#tuples): A range of dates (not datetimes)\n",
28 "- [List](#lists): A list of objects, potentially of a fixed, min, or max length\n",
29 " * [HookList](#lists): A list of callables, for calling user-defined code at a processing stage\n",
30 "- [Path](#paths): A POSIX-style string specifying the location of a local file or folder\n",
31 " * [Filename](#paths): A POSIX-style string specifying the location of a local file\n",
32 " * [Foldername](#paths): A POSIX-style string specifying the location of a local folder\n",
33 "- [SelectorBase](#selectors): Abstract superclass covering various selector parameter types\n",
34 " * [Selector](#selectors): One object selected out of a provided ordered list of objects\n",
35 " - [FileSelector](#selectors): One filename selected out of those matching a provided glob\n",
36 " - [ListSelector](#selectors): Multiple objects selected out of a provided list of objects\n",
37 " - [MultiFileSelector](#selectors): Multiple filenames selected out of those matching a provided glob\n",
38 " * [ClassSelector](#classSelectors): An instance or class of a specified Python class or superclass\n",
39 " - [Dict](#classSelectors): A Python dictionary\n",
40 " - [Array](#classSelectors): NumPy array\n",
41 " - [Series](#classSelectors): A Pandas Series\n",
42 " - [DataFrame](#classSelectors): A Pandas DataFrame\n",
43 "- [Callable](#invocations): A callable object, such as a function.\n",
44 " * [Action](#invocations): A callable with no arguments, ready to invoke\n",
45 "- [Composite](#invocations): A list of other attributes or parameters of this class, settable and readable as a group"
46 ]
47 },
48 {
49 "cell_type": "markdown",
50 "metadata": {},
51 "source": [
52 "The full behavior of these types is covered in the [Reference Manual](https://param.holoviz.org/Reference_Manual/param.html). Here we will discuss the major categories of Parameter type and how to use them, including examples of what each type does _not_ allow (labeled `with param.exceptions_summarized():`). Each of these classes is also suitable for subclassing to create more specialized types enforcing your own specific constraints. After reading about Parameters in general, feel free to skip around in this page and only look at the Parameter types of interest to you!"
53 ]
54 },
55 {
56 "cell_type": "markdown",
57 "metadata": {},
58 "source": [
59 "## Strings\n",
60 "\n",
61 "- `param.String`: String value, with optional regular expression (regex) constraints\n",
62 "\n",
63 "A `param.String` may be set to any Python string value by default, or it may be constrained to match a provided regular expression `regex`. Like all other Parameters, it can optionally also `allow_None`, which will be true by default if the default value is None."
64 ]
65 },
66 {
67 "cell_type": "code",
68 "execution_count": null,
69 "metadata": {},
70 "outputs": [],
71 "source": [
72 "import param\n",
73 "\n",
74 "class S(param.Parameterized):\n",
75 " s = param.String('Four score', regex='[A-Z][a-z][a-z][a-z ]*')\n",
76 "\n",
77 "s = S()\n",
78 "s.s"
79 ]
80 },
81 {
82 "cell_type": "code",
83 "execution_count": null,
84 "metadata": {},
85 "outputs": [],
86 "source": [
87 "with param.exceptions_summarized():\n",
88 " s.s = 5"
89 ]
90 },
91 {
92 "cell_type": "code",
93 "execution_count": null,
94 "metadata": {},
95 "outputs": [],
96 "source": [
97 "s.s = 'Forever after'"
98 ]
99 },
100 {
101 "cell_type": "code",
102 "execution_count": null,
103 "metadata": {},
104 "outputs": [],
105 "source": [
106 "with param.exceptions_summarized():\n",
107 " s.s = 'four of spades'"
108 ]
109 },
110 {
111 "cell_type": "code",
112 "execution_count": null,
113 "metadata": {},
114 "outputs": [],
115 "source": [
116 "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]?)$'\n",
117 "email_regex = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n",
118 "\n",
119 "class I(param.Parameterized):\n",
120 " ip_address = param.String('192.1.0.1', regex=ip_regex)\n",
121 " email = param.String('example@me.com', regex=email_regex)\n",
122 "i = I()\n",
123 "i.ip_address"
124 ]
125 },
126 {
127 "cell_type": "code",
128 "execution_count": null,
129 "metadata": {},
130 "outputs": [],
131 "source": [
132 "i.ip_address=\"10.0.0.2\"\n",
133 "i.email = \"user@gmail.com\""
134 ]
135 },
136 {
137 "cell_type": "code",
138 "execution_count": null,
139 "metadata": {},
140 "outputs": [],
141 "source": [
142 "with param.exceptions_summarized():\n",
143 " i.ip_address='192.x.1.x'"
144 ]
145 },
146 {
147 "cell_type": "markdown",
148 "metadata": {},
149 "source": [
150 "## Colors\n",
151 "\n",
152 "- `param.Color`: Named color or hex RGB string (with or without a # prefix)\n",
153 "\n",
154 "\n",
155 "A Color parameter specifies one of the standard [web color names](https://www.w3.org/TR/css-color-3/#svg-color) or an arbitrary hex RGB string. To support only hex RGB strings, specify `allow_named=False`.\n",
156 "\n",
157 "lemonchiffon"
158 ]
159 },
160 {
161 "cell_type": "code",
162 "execution_count": null,
163 "metadata": {},
164 "outputs": [],
165 "source": [
166 "class C(param.Parameterized):\n",
167 " c = param.Color('#EEFF00')\n",
168 "\n",
169 "c = C()\n",
170 "c.c"
171 ]
172 },
173 {
174 "cell_type": "code",
175 "execution_count": null,
176 "metadata": {},
177 "outputs": [],
178 "source": [
179 "c.c = 'lemonchiffon'\n",
180 "c.c"
181 ]
182 },
183 {
184 "cell_type": "code",
185 "execution_count": null,
186 "metadata": {},
187 "outputs": [],
188 "source": [
189 "with param.exceptions_summarized():\n",
190 " c.c = 'puce'"
191 ]
192 },
193 {
194 "cell_type": "code",
195 "execution_count": null,
196 "metadata": {},
197 "outputs": [],
198 "source": [
199 "with param.exceptions_summarized():\n",
200 " c.c = '#abcdefg'"
201 ]
202 },
203 {
204 "cell_type": "markdown",
205 "metadata": {},
206 "source": [
207 "## Booleans\n",
208 "\n",
209 "- `param.Boolean`: A True or False value (or None, if allow_None is true)\n",
210 "\n",
211 "A Boolean may be True or False. Like all other Parameters, it can optionally also `allow_None`, which will be true by default if the default value is None."
212 ]
213 },
214 {
215 "cell_type": "code",
216 "execution_count": null,
217 "metadata": {},
218 "outputs": [],
219 "source": [
220 "class B(param.Parameterized):\n",
221 " b = param.Boolean(True)\n",
222 " n = param.Boolean(None)\n",
223 "\n",
224 "b = B()\n",
225 "b.b"
226 ]
227 },
228 {
229 "cell_type": "code",
230 "execution_count": null,
231 "metadata": {},
232 "outputs": [],
233 "source": [
234 "with param.exceptions_summarized():\n",
235 " b.b=1"
236 ]
237 },
238 {
239 "cell_type": "code",
240 "execution_count": null,
241 "metadata": {},
242 "outputs": [],
243 "source": [
244 "with param.exceptions_summarized():\n",
245 " b.b=None"
246 ]
247 },
248 {
249 "cell_type": "code",
250 "execution_count": null,
251 "metadata": {},
252 "outputs": [],
253 "source": [
254 "b.n = True\n",
255 "b.n = None"
256 ]
257 },
258 {
259 "cell_type": "markdown",
260 "metadata": {},
261 "source": [
262 "## Numbers\n",
263 "\n",
264 "- `param.Number`: Python floats, int, and bignum values.\n",
265 "- `param.Integer`: Integer values.\n",
266 "- `param.Magnitude`: Same as `param.Number(..., bounds=(0.0,1.0))`.\n",
267 "- `param.Date`: Date or datetime value of type `datetime.datetime`, `datetime.date`, or `numpy.datetime64`.\n",
268 "- `param.CalendarDate`: Date value of type `datetime.date`.\n",
269 "\n",
270 "A Number is the most common type of Parameter. All Numbers in param are of class Dynamic, which allows them to be set not just to a single value but to a value that can repeatedly be drawn from a distribution or a sequence. (See [Dynamic Parameters](Dynamic_Parameters.ipynb) for more information about using these dynamic features, which will not be further discussed here.) Any Number has a default value (potentially None if allowed) and optional bounds.\n",
271 "\n",
272 "There are two types of bounds: ``bounds`` and ``softbounds``. ``bounds`` are hard bounds: the parameter must have a value within the specified range. The default bounds are (None,None), meaning there are actually no hard bounds. One or both bounds can be set by specifying a value (e.g. `bounds=(None,10)` means there is no lower bound, and an upper bound of 10). Bounds are inclusive by default, but exclusivity can be specified for each bound by setting inclusive_bounds (e.g. `inclusive_bounds=(True,False)` specifies an exclusive upper bound). \n",
273 "\n",
274 "When not being dynamically generated, `bounds` are checked whenever a Number is created or set. Using a default value outside the hard bounds, or one that is not numeric, results in an exception. When being dynamically generated, bounds are checked when the value of a Number is _requested_ (since it has no specific numeric value when first set). A generated value that is not numeric, or is outside the hard bounds, results in an exception. \n",
275 "\n",
276 "A separate set of ``softbounds`` is present to indicate the _typical_ range of the parameter, but these bounds are not enforced by Param. Setting the soft bounds allows a user to know what ranges of values are likely to be useful and allows a GUI to know what values to display on sliders for the Number; `softbounds` are thus suggestions or hints rather than enforced limits. \n",
277 "\n",
278 "Similarly, an optional ``step`` value can be provided to indicate the granularity of this parameter. As for `softbounds`, Param does not force values to conform to the provided step value, but (if provided) the step can be queried by user code and used for parameter sweeps (starting at the `softbounds` low and incrementing in value by `step` until the `softbounds` high), or by GUI code to determine steps on a settings dial.\n",
279 "\n",
280 "Several convenience methods for working with bounds are provided:\n",
281 "- `get_soft_bounds()`: return the soft bounds (or hard bounds, if no soft bounds), for code that needs to know the typical range for this Parameter.\n",
282 "- `crop_to_bounds(val)`: crop the provided value to fit into the hard bounds.\n",
283 "- `set_in_bounds(obj,val)`: silently crop the given value into the legal range and set to the result, for building an API or user interface that accepts free-form input. \n",
284 "\n",
285 "Numbers support a `set_hook` that can be set to a function like `def logging_set_hook(obj,val): log_value(val) return val`, which will be called whenever the value is set.\n",
286 "\n",
287 "Using Number parameters:"
288 ]
289 },
290 {
291 "cell_type": "code",
292 "execution_count": null,
293 "metadata": {},
294 "outputs": [],
295 "source": [
296 "import param\n",
297 "\n",
298 "class N(param.Parameterized):\n",
299 " n = param.Number(5.6, bounds=(0,None), softbounds=(None,50))\n",
300 " i = param.Integer(5, bounds=(0,50))\n",
301 " \n",
302 "a = N()\n",
303 "a.n=2\n",
304 "a.n"
305 ]
306 },
307 {
308 "cell_type": "code",
309 "execution_count": null,
310 "metadata": {},
311 "outputs": [],
312 "source": [
313 "N.param.n.set_in_bounds(a,-10)\n",
314 "a.n"
315 ]
316 },
317 {
318 "cell_type": "code",
319 "execution_count": null,
320 "metadata": {},
321 "outputs": [],
322 "source": [
323 "a.param.n.set_in_bounds(a,-5)\n",
324 "a.n"
325 ]
326 },
327 {
328 "cell_type": "code",
329 "execution_count": null,
330 "metadata": {},
331 "outputs": [],
332 "source": [
333 "a.param.n.set_in_bounds(a,75)\n",
334 "a.n"
335 ]
336 },
337 {
338 "cell_type": "code",
339 "execution_count": null,
340 "metadata": {},
341 "outputs": [],
342 "source": [
343 "a.param.n.get_soft_bounds()"
344 ]
345 },
346 {
347 "cell_type": "code",
348 "execution_count": null,
349 "metadata": {},
350 "outputs": [],
351 "source": [
352 "with param.exceptions_summarized():\n",
353 " a.n = -5"
354 ]
355 },
356 {
357 "cell_type": "code",
358 "execution_count": null,
359 "metadata": {},
360 "outputs": [],
361 "source": [
362 "a.n = 500\n",
363 "a.n"
364 ]
365 },
366 {
367 "cell_type": "code",
368 "execution_count": null,
369 "metadata": {},
370 "outputs": [],
371 "source": [
372 "with param.exceptions_summarized():\n",
373 " a.i=5.7"
374 ]
375 },
376 {
377 "cell_type": "code",
378 "execution_count": null,
379 "metadata": {},
380 "outputs": [],
381 "source": [
382 "import datetime\n",
383 "\n",
384 "class D(param.Parameterized):\n",
385 " d = param.CalendarDate(datetime.date(1900, 1, 1))\n",
386 " t = param.Date(datetime.datetime.fromisoformat('2002-12-25T00:00'))\n",
387 "\n",
388 "d = D()\n",
389 "d.d = datetime.date.fromisoformat('2000-01-01')\n",
390 "d.d"
391 ]
392 },
393 {
394 "cell_type": "code",
395 "execution_count": null,
396 "metadata": {},
397 "outputs": [],
398 "source": [
399 "with param.exceptions_summarized():\n",
400 " d.d = 2022"
401 ]
402 },
403 {
404 "cell_type": "code",
405 "execution_count": null,
406 "metadata": {},
407 "outputs": [],
408 "source": [
409 "d.t = datetime.date(1900, 1, 1)\n",
410 "d.t"
411 ]
412 },
413 {
414 "cell_type": "code",
415 "execution_count": null,
416 "metadata": {},
417 "outputs": [],
418 "source": [
419 "with param.exceptions_summarized():\n",
420 " d.d = datetime.datetime.fromisoformat('2002-12-25T00:00')"
421 ]
422 },
423 {
424 "cell_type": "markdown",
425 "metadata": {},
426 "source": [
427 "## Tuples"
428 ]
429 },
430 {
431 "cell_type": "markdown",
432 "metadata": {},
433 "source": [
434 "- `param.Tuple`: Python tuple of a fixed length.\n",
435 "- `param.NumericTuple`: Python tuple of a fixed length, with numeric values.\n",
436 "- `param.XYCoordinates`: Python pair (2-tuple) of numeric values. Same as `param.NumericTuple(..., length=2)`, but semantically representing a 2D coordinate in a plane (e.g. for drawing programs or GUIs)\n",
437 "- `param.Range`: `NumericTuple` representing a numeric range with optional bounds and softbounds.\n",
438 "- `param.DateRange`: `Range` where the numeric type is a date or datetime (using same date types as `param.Date`).\n",
439 "- `param.CalendarDateRange`: `Range` where the numeric type is a `datetime.date`. \n",
440 "\n",
441 "A tuple Parameter accepts a Python tuple for the value. Tuple parameters have a fixed length, typically set by the default value of the parameter but settable as the `length` if the default value is None. Only a tuple of the specified length will be accepted when a value is set.\n",
442 "\n",
443 "There are many tuple types as listed above, accepting either any type, numeric types, datetimes, dates, etc. `Range` types support `bounds`, `softbounds`, `inclusive_bounds`, and `step` on the numeric values in the tuple, similar to [Number](#Numbers) types."
444 ]
445 },
446 {
447 "cell_type": "code",
448 "execution_count": null,
449 "metadata": {},
450 "outputs": [],
451 "source": [
452 "class T(param.Parameterized):\n",
453 " t = param.Range((-10,10), bounds=(-100,None), softbounds=(None,100))\n",
454 "b = T()\n",
455 "b.t"
456 ]
457 },
458 {
459 "cell_type": "code",
460 "execution_count": null,
461 "metadata": {},
462 "outputs": [],
463 "source": [
464 "b.t = (50.2,50.3)\n",
465 "b.t"
466 ]
467 },
468 {
469 "cell_type": "code",
470 "execution_count": null,
471 "metadata": {},
472 "outputs": [],
473 "source": [
474 "with param.exceptions_summarized():\n",
475 " b.t = 5"
476 ]
477 },
478 {
479 "cell_type": "code",
480 "execution_count": null,
481 "metadata": {},
482 "outputs": [],
483 "source": [
484 "with param.exceptions_summarized():\n",
485 " b.t = (5,5,5)"
486 ]
487 },
488 {
489 "cell_type": "code",
490 "execution_count": null,
491 "metadata": {},
492 "outputs": [],
493 "source": [
494 "with param.exceptions_summarized():\n",
495 " b.t = (5,\"5\")"
496 ]
497 },
498 {
499 "cell_type": "code",
500 "execution_count": null,
501 "metadata": {},
502 "outputs": [],
503 "source": [
504 "with param.exceptions_summarized():\n",
505 " b.t = (-500,500)"
506 ]
507 },
508 {
509 "cell_type": "code",
510 "execution_count": null,
511 "metadata": {},
512 "outputs": [],
513 "source": [
514 "import datetime\n",
515 "class D(param.Parameterized):\n",
516 " d = param.CalendarDateRange((datetime.date.fromisoformat('1900-01-01'),\n",
517 " datetime.date.fromisoformat('1910-12-31')))\n",
518 "c = D()\n",
519 "c.d"
520 ]
521 },
522 {
523 "cell_type": "code",
524 "execution_count": null,
525 "metadata": {},
526 "outputs": [],
527 "source": [
528 "with param.exceptions_summarized():\n",
529 " c.d=(1905, 1907)"
530 ]
531 },
532 {
533 "cell_type": "markdown",
534 "metadata": {},
535 "source": [
536 "## Lists"
537 ]
538 },
539 {
540 "cell_type": "markdown",
541 "metadata": {},
542 "source": [
543 "- `param.List`: A Python list of objects, usually of a specified type.\n",
544 "- `param.HookList`: A list of callable objects, for executing user-defined code at some processing stage\n",
545 "\n",
546 "List Parameters accept a Python list of objects. Typically the `item_type` will be specified for those objects, so that the rest of the code does not have to further check types when it refers to those values. Where appropriate, the `bounds` of the list can be set as (_min_length_, _max_length_), defaulting to `(0,None)`. Because List parameters already have an empty value ([]), they do not support `allow_None`.\n",
547 "\n",
548 "A `param.HookList` is a list whose elements are callable objects (typically either functions or objects with a `__call__` method). A `HookList` is intended for providing user configurability at various stages of some processing algorithm or pipeline. At present, there is no validation that the provided callable takes any particular number or type of arguments."
549 ]
550 },
551 {
552 "cell_type": "code",
553 "execution_count": null,
554 "metadata": {},
555 "outputs": [],
556 "source": [
557 "import param\n",
558 "class L(param.Parameterized):\n",
559 " ls = param.List([\"red\",\"green\",\"blue\"], item_type=str, bounds=(0,10))\n",
560 "\n",
561 "e = L()\n",
562 "e.ls"
563 ]
564 },
565 {
566 "cell_type": "code",
567 "execution_count": null,
568 "metadata": {},
569 "outputs": [],
570 "source": [
571 "with param.exceptions_summarized():\n",
572 " e.ls = [1,2]"
573 ]
574 },
575 {
576 "cell_type": "code",
577 "execution_count": null,
578 "metadata": {},
579 "outputs": [],
580 "source": [
581 "with param.exceptions_summarized():\n",
582 " e.ls = [str(i) for i in range(20)]"
583 ]
584 },
585 {
586 "cell_type": "code",
587 "execution_count": null,
588 "metadata": {},
589 "outputs": [],
590 "source": [
591 "class multi_stage_example(param.Parameterized):\n",
592 " before = param.HookList()\n",
593 " during = param.HookList()\n",
594 " after = param.HookList()\n",
595 " \n",
596 " values = param.List([1.5,-8.1,6.9,100.0], item_type=float)\n",
597 " \n",
598 " def __call__(self):\n",
599 " for h in self.before: h(self)\n",
600 " s = 0\n",
601 " for v in self.values:\n",
602 " v_ = v\n",
603 " for h in self.during: v_ = h(v_)\n",
604 " s += v_\n",
605 " for h in self.after: h()\n",
606 " return s\n",
607 "\n",
608 "ex = multi_stage_example()\n",
609 "ex()"
610 ]
611 },
612 {
613 "cell_type": "code",
614 "execution_count": null,
615 "metadata": {},
616 "outputs": [],
617 "source": [
618 "def validate(obj):\n",
619 " for i in obj.values:\n",
620 " if i<0:\n",
621 " print(\"Negative value found in argument\")\n",
622 "\n",
623 "m = multi_stage_example(before=[validate])\n",
624 "\n",
625 "m()"
626 ]
627 },
628 {
629 "cell_type": "code",
630 "execution_count": null,
631 "metadata": {},
632 "outputs": [],
633 "source": [
634 "from math import fabs\n",
635 "\n",
636 "ex.during=[abs]\n",
637 "ex()"
638 ]
639 },
640 {
641 "cell_type": "markdown",
642 "metadata": {},
643 "source": [
644 "## Paths\n",
645 "\n",
646 "- `param.Path`: A POSIX-style string specifying the location of a local file or folder\n",
647 "- `param.Filename`: A POSIX-style string specifying the location of a local file\n",
648 "- `param.Foldername`: A POSIX-style string specifying the location of a local folder\n",
649 "\n",
650 "A Path can be set to a string specifying the path of a file or folder. In code, the string should be specified in POSIX (UNIX) style, using forward slashes / and starting from / if absolute or some other character if relative. When retrieved, the string will be in the format of the user's operating system. \n",
651 "\n",
652 "Relative paths are converted to absolute paths by searching for a matching filename on the filesystem. If `search_paths` is provided and not empty, the folders in that list are searched for the given filename, in order, returning the absolute path for the first match found by appending the provided path to the search path. An IOError is raised if the file or folder is not found. If `search_paths` is empty (the default), the file or folder is expected to be in the current working directory.\n",
653 "\n",
654 "Either a file or a folder name is accepted by `param.Path`, while `param.Filename` accepts only file names and `param.Foldername` accepts only folder names."
655 ]
656 },
657 {
658 "cell_type": "code",
659 "execution_count": null,
660 "metadata": {},
661 "outputs": [],
662 "source": [
663 "class P(param.Parameterized):\n",
664 " p = param.Path('Parameter_Types.ipynb')\n",
665 " f = param.Filename('Parameter_Types.ipynb')\n",
666 " d = param.Foldername('lib', search_paths=['/','/usr','/share'])\n",
667 " \n",
668 "p = P()\n",
669 "p.p"
670 ]
671 },
672 {
673 "cell_type": "code",
674 "execution_count": null,
675 "metadata": {},
676 "outputs": [],
677 "source": [
678 "p.p='/usr/lib'\n",
679 "p.p"
680 ]
681 },
682 {
683 "cell_type": "code",
684 "execution_count": null,
685 "metadata": {},
686 "outputs": [],
687 "source": [
688 "p.f"
689 ]
690 },
691 {
692 "cell_type": "code",
693 "execution_count": null,
694 "metadata": {},
695 "outputs": [],
696 "source": [
697 "with param.exceptions_summarized():\n",
698 " p.f='/usr/lib'"
699 ]
700 },
701 {
702 "cell_type": "code",
703 "execution_count": null,
704 "metadata": {},
705 "outputs": [],
706 "source": [
707 "p.d"
708 ]
709 },
710 {
711 "cell_type": "code",
712 "execution_count": null,
713 "metadata": {},
714 "outputs": [],
715 "source": [
716 "with param.exceptions_summarized():\n",
717 " p.d='Parameter_Types.ipynb'"
718 ]
719 },
720 {
721 "cell_type": "markdown",
722 "metadata": {},
723 "source": [
724 "## Selectors\n",
725 "\n",
726 "- `param.Selector`: One object selected out of a provided ordered list of objects\n",
727 "- `param.ListSelector`: Multiple objects selected out of a provided list of objects\n",
728 "- `param.FileSelector`: One filename selected out of those matching a provided glob\n",
729 "- `param.MultiFileSelector`: Multiple filenames selected out of those matching a provided glob\n",
730 "\n",
731 "The value of a Selector is one or more items from a set of allowed values. All Selector types must implement `get_range()`, providing a concrete list of available options for the value.\n",
732 "\n",
733 "A `param.Selector` accepts a list or dictionary of `objects`, and has a single default (current) value that must be one of those objects. If not otherwise specified, the default will be the first item from the list or dictionary. \n",
734 "\n",
735 "Providing the objects as a list is appropriate for selecting among a set of strings, or among a set of Parameterized objects that each have a \"name\" parameter. That way, a UI that lets users select by string will have a suitable string available for each object to let the user make a choice between them.\n",
736 "\n",
737 "Otherwise, the objects should be provided as a _name_:_value_ dictionary, where the string name will be stored for use in such a UI, but is not otherwise accessed by Param. The values from setting and getting the parameter are always the actual underlying object, not the string names. Because the string name will need to be looked up from the value if this parameter is used in a UI, all objects need to be hashable via the `param.hashable()` function, which accepts Python literals plus list and dictionary types (treating them like tuples).\n",
738 "\n",
739 "If the list of available objects is not meant be exhaustive, you can specify `check_on_set=False` (which automatically applies if the initial list is empty). Objects will then be added to the `objects` list whenever they are set, including as the initial default. `check_on_set=False` can be useful when the predefined set of objects is not exhaustive, letting a user select from the existing list for convenience while also being able to supply any other suitable object they can construct. When `check_on_set=True`, the initial value (and all subsequent values) must be in the `objects` list.\n",
740 "\n",
741 "Because `Selector` is usually used to allow selection from a list of existing (instantiated) objects, `instantiate` is False by default, but you can specify `instantiate=True` if you want each copy of this Parameter value to be independent of other instances and superclasses.\n",
742 "\n",
743 "In cases where the objects in the list cannot be known when writing the Parameterized class but can be calculated at runtime, you can supply a callable (of no arguments) to `compute_default_fn`, and then ensure that at runtime you call `compute_default` on that Parameter to initialize the value.\n",
744 "\n",
745 "A `param.ListSelector` works just the same as a regular Selector, but the value is a _list_ of valid objects from the available objects, rather than just one. Each item in the list is checked against the `objects`, and thus the current value is thus a _subset_ of the `objects`, rather than just one of the objects.\n",
746 "\n",
747 "A `param.FileSelector` works like a regular Selector with the value being a filename and the `objects` being computed from files on a file system. The files are specified as a `path` [glob](https://docs.python.org/3/library/glob.html), and all filenames matching the glob are valid `objects` for the parameter.\n",
748 "\n",
749 "A `param.MultiFileSelector` is the analog of ListSelector but for files, i.e., again supporting a path glob but allowing the user to select a list of filenames rather than a single filename. The default value in this case is _all_ of the matched files, not just the first one."
750 ]
751 },
752 {
753 "cell_type": "code",
754 "execution_count": null,
755 "metadata": {},
756 "outputs": [],
757 "source": [
758 "colors = [\"red\",\"green\",\"blue\"]\n",
759 "\n",
760 "class S(param.Parameterized):\n",
761 " o = param.Selector(colors)\n",
762 " ls = param.ListSelector(colors[0:2], objects=colors)\n",
763 " \n",
764 "s = S()\n",
765 "s.o"
766 ]
767 },
768 {
769 "cell_type": "code",
770 "execution_count": null,
771 "metadata": {},
772 "outputs": [],
773 "source": [
774 "s.o = \"green\"\n",
775 "s.o"
776 ]
777 },
778 {
779 "cell_type": "code",
780 "execution_count": null,
781 "metadata": {},
782 "outputs": [],
783 "source": [
784 "with param.exceptions_summarized():\n",
785 " s.o = \"yellow\""
786 ]
787 },
788 {
789 "cell_type": "code",
790 "execution_count": null,
791 "metadata": {},
792 "outputs": [],
793 "source": [
794 "with param.exceptions_summarized():\n",
795 " s.o = 42"
796 ]
797 },
798 {
799 "cell_type": "code",
800 "execution_count": null,
801 "metadata": {},
802 "outputs": [],
803 "source": [
804 "s.ls"
805 ]
806 },
807 {
808 "cell_type": "code",
809 "execution_count": null,
810 "metadata": {},
811 "outputs": [],
812 "source": [
813 "s.ls=['blue']\n",
814 "s.ls"
815 ]
816 },
817 {
818 "cell_type": "code",
819 "execution_count": null,
820 "metadata": {},
821 "outputs": [],
822 "source": [
823 "with param.exceptions_summarized():\n",
824 " s.ls=['red','yellow']\n",
825 " s.ls"
826 ]
827 },
828 {
829 "cell_type": "code",
830 "execution_count": null,
831 "metadata": {},
832 "outputs": [],
833 "source": [
834 "class F(param.Parameterized):\n",
835 " f = param.FileSelector(path='/usr/share/*')\n",
836 " fs = param.MultiFileSelector(path='/usr/share/*')\n",
837 " \n",
838 "f = F()\n",
839 "f.f"
840 ]
841 },
842 {
843 "cell_type": "code",
844 "execution_count": null,
845 "metadata": {},
846 "outputs": [],
847 "source": [
848 "f.param.f.objects[0:3]"
849 ]
850 },
851 {
852 "cell_type": "code",
853 "execution_count": null,
854 "metadata": {},
855 "outputs": [],
856 "source": [
857 "f.fs = f.param.fs.objects[0:2]\n",
858 "f.fs"
859 ]
860 },
861 {
862 "cell_type": "markdown",
863 "metadata": {},
864 "source": [
865 "## ClassSelectors\n",
866 "\n",
867 "- `param.ClassSelector`: An instance or class of a specified Python class or superclass\n",
868 "- `param.Dict`: A Python dictionary\n",
869 "- `param.Array`: NumPy array\n",
870 "- `param.Series`: A Pandas Series\n",
871 "- `param.DataFrame`: A Pandas DataFrame\n",
872 "\n",
873 "A ClassSelector has a value that is either an instance or a subclass of a specified Python `class_`. By default, requires an instance of that class, but specifying `is_instance=False` means that a subclass must be selected instead. \n",
874 "\n",
875 "Like Selector types, all ClassSelector types implement `get_range()`, in this case providing an introspected list of all the concrete (not abstract) subclasses available for the given class. If you want a class to be treated as abstract so that it does not show up in such a list, you can have it declare `__abstract=True` as a class attribute. In a GUI, the range list allows a user to select a type of object they want to create, and they can then separately edit the new object's parameters (if any) to configure it appropriately."
876 ]
877 },
878 {
879 "cell_type": "code",
880 "execution_count": null,
881 "metadata": {},
882 "outputs": [],
883 "source": [
884 "class C(param.Parameterized):\n",
885 " e_instance = param.ClassSelector(class_=ArithmeticError, default=ZeroDivisionError(\"1/0\"))\n",
886 " e_class = param.ClassSelector(class_=ArithmeticError, default=ZeroDivisionError, is_instance=False)\n",
887 " \n",
888 "c = C(e_class=OverflowError)\n",
889 "c.e_class, c.e_instance"
890 ]
891 },
892 {
893 "cell_type": "code",
894 "execution_count": null,
895 "metadata": {},
896 "outputs": [],
897 "source": [
898 "c.param.e_instance.get_range()"
899 ]
900 },
901 {
902 "cell_type": "code",
903 "execution_count": null,
904 "metadata": {},
905 "outputs": [],
906 "source": [
907 "c.param.e_class.get_range()"
908 ]
909 },
910 {
911 "cell_type": "code",
912 "execution_count": null,
913 "metadata": {},
914 "outputs": [],
915 "source": [
916 "with param.exceptions_summarized():\n",
917 " c.e_class = Exception"
918 ]
919 },
920 {
921 "cell_type": "code",
922 "execution_count": null,
923 "metadata": {},
924 "outputs": [],
925 "source": [
926 "with param.exceptions_summarized():\n",
927 " c.e_instance = ArithmeticError"
928 ]
929 },
930 {
931 "cell_type": "code",
932 "execution_count": null,
933 "metadata": {},
934 "outputs": [],
935 "source": [
936 "c.e_instance = ArithmeticError()\n",
937 "c.e_instance"
938 ]
939 },
940 {
941 "cell_type": "markdown",
942 "metadata": {},
943 "source": [
944 "Various types of ClassSelector are provided for specific data types:\n",
945 "\n",
946 "- `param.Dict`: `class_=dict`, accepting a Python dictionary\n",
947 "- `param.Array`: `class=numpy.ndarray`, accepting a NumPy array\n",
948 "- `param.Series`: `class_=pandas.Series`, a Pandas Series. Accepts constraints on the number of `rows`, either as an integer length (e.g. `rows=10`) or a range tuple `rows=(2,4)`).\n",
949 "- `param.DataFrame`: `class_=pandas.DataFrame`, a Pandas DataFrame. Accepts constraints on the number of `rows` (as for `param.Series`) or `columns` (with numerical or range values as for `rows` or as a list of column names (which must appear in that order) or as a set of column names (which can appear in any order))."
950 ]
951 },
952 {
953 "cell_type": "code",
954 "execution_count": null,
955 "metadata": {},
956 "outputs": [],
957 "source": [
958 "import numpy as np, pandas as pd\n",
959 "\n",
960 "class D(param.Parameterized):\n",
961 " d = param.Dict(dict(a=1, b=\"not set\", c=2.0))\n",
962 " a = param.Array(np.array([1,-1]))\n",
963 " s = param.Series(pd.Series([1,-1]))\n",
964 " f = param.DataFrame(pd.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}), rows=(2,None), columns=set(['a','b']))\n",
965 "\n",
966 "d = D()\n",
967 "d.d = {5:np.nan}\n",
968 "d.d"
969 ]
970 },
971 {
972 "cell_type": "code",
973 "execution_count": null,
974 "metadata": {},
975 "outputs": [],
976 "source": [
977 "with param.exceptions_summarized():\n",
978 " d.d=[(\"a\",1)]"
979 ]
980 },
981 {
982 "cell_type": "code",
983 "execution_count": null,
984 "metadata": {},
985 "outputs": [],
986 "source": [
987 "df = pd.DataFrame({'b':[-2,-3], 'a':[-1,-2]})\n",
988 "d.f = df\n",
989 "d.f"
990 ]
991 },
992 {
993 "cell_type": "code",
994 "execution_count": null,
995 "metadata": {},
996 "outputs": [],
997 "source": [
998 "with param.exceptions_summarized():\n",
999 " df = pd.DataFrame({'a':[-2,-3], 'c':[-1,-2]})\n",
1000 " d.f = df"
1001 ]
1002 },
1003 {
1004 "cell_type": "code",
1005 "execution_count": null,
1006 "metadata": {},
1007 "outputs": [],
1008 "source": [
1009 "with param.exceptions_summarized():\n",
1010 " df = pd.DataFrame({'a':[-2], 'b':[-1]})\n",
1011 " d.f = df"
1012 ]
1013 },
1014 {
1015 "cell_type": "markdown",
1016 "metadata": {},
1017 "source": [
1018 "### `classlist` and `param.descendents`\n",
1019 "\n",
1020 "If you are building a GUI or some other mechanism allowing the user to choose a class or an instance of a specified class in a ClassSelector, you may want to construct a list of all subclasses or superclasses of the given class. To make it easy to traverse upwards or downwards in the inheritance hierarchy in this way, param provides the `classlist` and `param.descendents` functions. `classlist` provides a list of the superclasses of the provided object, including itself, in order from least to most specific:"
1021 ]
1022 },
1023 {
1024 "cell_type": "code",
1025 "execution_count": null,
1026 "metadata": {},
1027 "outputs": [],
1028 "source": [
1029 "from param.parameterized import classlist\n",
1030 "\n",
1031 "classlist(D)"
1032 ]
1033 },
1034 {
1035 "cell_type": "markdown",
1036 "metadata": {},
1037 "source": [
1038 "As you can see, `D` is a type of `Parameterized`, and a `Parameterized` is a type of Python object. Conversely (and typically more usefully), `param.descendents` provides a list of the subclasses of the provided object, including itself:"
1039 ]
1040 },
1041 {
1042 "cell_type": "code",
1043 "execution_count": null,
1044 "metadata": {},
1045 "outputs": [],
1046 "source": [
1047 "param.descendents(param.SelectorBase)"
1048 ]
1049 },
1050 {
1051 "cell_type": "markdown",
1052 "metadata": {},
1053 "source": [
1054 "As you can see, there are many subtypes of SelectorBase. This list is calculated from whatever subtypes are currently defined in this Python session. If you derive an additional subtype or load code that defines an additional subtype, this list will get longer, so you need to make sure that all such code has been executed before letting the user make a selection of a subtype."
1055 ]
1056 },
1057 {
1058 "cell_type": "markdown",
1059 "metadata": {},
1060 "source": [
1061 "## Invocations\n",
1062 "\n",
1063 "- `param.Callable`: A callable object, such as a function\n",
1064 "- `param.Action`: A callable with no arguments, ready to invoke\n",
1065 "- `param.Event`: Empty action, for use in triggering events for watchers\n",
1066 "- `param.Composite`: Wrapper around other parameters, letting them be set as a tuple\n",
1067 "\n",
1068 "Invocation parameters are a loose group of types that either contain an executable (callable) object, are invoked to execute some other code, or are set to change the value of some other parameter(s) or attribute(s).\n",
1069 "\n",
1070 "A Callable may be set to any callable object, typically either a function or else an instance of a class that provides a `__call__` method. At present, there is no validation that the provided callable takes any particular number or type of arguments. Lambdas can be provided, but note that the resulting parameter value will no longer be picklable, so if you need to use pickling (`setstate` and `getstate`), be sure to use a named function instead.\n",
1071 "\n",
1072 "An Action is the same as a Callable, but is expected to have no arguments. In a GUI an Action is typically mapped to a button whose name or label is the name of this parameter. \n",
1073 "\n",
1074 "An Event Parameter has a Boolean value but is primarily intended for triggering events on its watchers. See [Dependencies and Watchers](Dependencies_and_Watchers.ipynb) for the details.\n",
1075 "\n",
1076 "A Composite Parameter has a value that is looked up from the value of a list of attributes of this class (which may or may not be parameters) and that when set changes the values of those other attributes or parameters. This type of Parameter can be useful for treating a set of related values as a group for setting purposes, but as individual parameters for code that reads from them. As of Param 1.10, Composite parameters have not been tested with watchers and dependencies and may not behave appropriately for such uses."
1077 ]
1078 },
1079 {
1080 "cell_type": "code",
1081 "execution_count": null,
1082 "metadata": {},
1083 "outputs": [],
1084 "source": [
1085 "def identity(x): return x\n",
1086 " \n",
1087 "def print_time_of_day():\n",
1088 " print(datetime.date.today())\n",
1089 "\n",
1090 "class A(param.Parameterized):\n",
1091 " transformer = param.Callable(identity)\n",
1092 " a = param.Action(print_time_of_day)\n",
1093 " \n",
1094 " def __call__(self, x):\n",
1095 " return self.transformer(x)\n",
1096 " \n",
1097 "a = A()\n",
1098 "a(5)"
1099 ]
1100 },
1101 {
1102 "cell_type": "code",
1103 "execution_count": null,
1104 "metadata": {},
1105 "outputs": [],
1106 "source": [
1107 "def double(x):\n",
1108 " return 2*x\n",
1109 " \n",
1110 "d = A(transformer=double)\n",
1111 "d(5)"
1112 ]
1113 },
1114 {
1115 "cell_type": "code",
1116 "execution_count": null,
1117 "metadata": {},
1118 "outputs": [],
1119 "source": [
1120 "d.a()"
1121 ]
1122 },
1123 {
1124 "cell_type": "code",
1125 "execution_count": null,
1126 "metadata": {},
1127 "outputs": [],
1128 "source": [
1129 "with param.exceptions_summarized():\n",
1130 " d.a = 5"
1131 ]
1132 }
1133 ],
1134 "metadata": {
1135 "language_info": {
1136 "name": "python",
1137 "pygments_lexer": "ipython3"
1138 }
1139 },
1140 "nbformat": 4,
1141 "nbformat_minor": 5
1142 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# ParameterizedFunctions\n",
7 "\n",
8 "`Parameterized` classes and objects are full-featured substitues for Python objects, providing rich support and control for how attributes behave. What if you need similar features, but for functions rather than objects?\n",
9 "\n",
10 "Python functions don't directly support the various [language features like descriptors that Param builds on](How_Param_Works.ipynb), but you can instead make a Python class or object that _behaves_ like a function, while still supporting Parameters. To make that easier, Param provides an abstract class `ParameterizedFunction` that you can use as a superclass for any function-like object you want to write. A ParameterizedFunction automatically invokes its `__call__` method whenever it is instantiated. So, all you need to do is implement the `__call__` method with the implementation of your function. For example:"
11 ]
12 },
13 {
14 "cell_type": "code",
15 "execution_count": null,
16 "metadata": {},
17 "outputs": [],
18 "source": [
19 "from param import Parameter, ParameterizedFunction, ParamOverrides\n",
20 "\n",
21 "class multiply(ParameterizedFunction):\n",
22 " \"Function to multiply two arguments.\"\n",
23 "\n",
24 " left = Parameter(2, doc=\"Left-hand-side argument\")\n",
25 " right = Parameter(4, doc=\"Right-hand-side argument\")\n",
26 "\n",
27 " def __call__(self, **params):\n",
28 " p = ParamOverrides(self, params)\n",
29 " return p.left * p.right\n",
30 " \n",
31 "multiply()"
32 ]
33 },
34 {
35 "cell_type": "code",
36 "execution_count": null,
37 "metadata": {},
38 "outputs": [],
39 "source": [
40 "multiply(left=3, right=7)"
41 ]
42 },
43 {
44 "cell_type": "code",
45 "execution_count": null,
46 "metadata": {},
47 "outputs": [],
48 "source": [
49 "multiply.left = 7\n",
50 "multiply(right = 10)"
51 ]
52 },
53 {
54 "cell_type": "markdown",
55 "metadata": {},
56 "source": [
57 "Here you can see that multiply acts like any other function that takes keyword arguments, but the arguments are now documented, potentially type checked, and have default values. \n",
58 "\n",
59 "This implementation depends on the separate object {py:class}`param.ParamOverrides`, which provides two-level lookup of parameter values: first on the arguments provided to the call, and then (if not provided) on the ParameterizedFunction instance. This way a user can choose to provide any or none of the arguments when the function (really, function object) is invoked. \n",
60 "\n",
61 "The `__call__` method can also take positional arguments, but in that case the class author would need to handle any mapping from those arguments to parameters there might be. `__call__` can also take extra keyword arguments beyond parameter values, but if so, you'll need to construct ParamOverrides as `p = ParamOverrides(self, params, allow_extra_keywords=True)`, then access the extra (non-Parameter) keywords in `p.extra_keywords` and process those explicitly."
62 ]
63 },
64 {
65 "cell_type": "markdown",
66 "metadata": {},
67 "source": [
68 "## .instance()\n",
69 "\n",
70 "Usually, with a Parameterized object, you can modify values on the instance level, in addition to the class level shown above. Here, however, there is no instance to grab, because the ParameterizedFunction is called and evaluated, returning a value rather than the function object. If you want to grab an instance where you can set a value and then call the instance, you can use the `.instance()` method of a ParameterizedFunction:"
71 ]
72 },
73 {
74 "cell_type": "code",
75 "execution_count": null,
76 "metadata": {},
77 "outputs": [],
78 "source": [
79 "multiply_by_10 = multiply.instance(right=10)\n",
80 "multiply_by_10(left=8)"
81 ]
82 }
83 ],
84 "metadata": {
85 "language_info": {
86 "name": "python",
87 "pygments_lexer": "ipython3"
88 }
89 },
90 "nbformat": 4,
91 "nbformat_minor": 5
92 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Parameters and Parameterized objects\n",
7 "\n",
8 "Fundamentally, what Param does is allow you to control how certain user-visible attributes (\"parameters\") of a Python class or instance will behave when their value is get or set. A user of that class can set those attributes to control the class, but only if the mechanisms provided by Param and configured by the programmer allow it. In this way, Param allows the author of a class to implement and constrain what a user can do with that class or an instance of it, setting up a clear contract of what is and is not allowed, and how that attribute will behave. To do this, Param provides two main new types of Python object: `Parameter` objects, and `Parameterized` objects.\n",
9 "\n",
10 "A parameter is a special kind of Python class attribute. Setting a `Parameterized` class attribute to be a Parameter instance causes that attribute of the class (and the class's instances) to be treated as a parameter, not just an ordinary attribute. Parameters support special behavior, including dynamically generated parameter values, documentation strings, constant and read-only parameters, type or range checking at assignment time, and values dependent on those of other parameters.\n",
11 "\n",
12 "More concretely, a Python `Parameter` object inherits from `param.Parameter` and stores various metadata attributes describing how a corresponding Python attribute of a `Parameterized` object should behave. By convention, we will use a capital 'P' Parameter to refer to the Parameter object itself, and a lower-case 'p' parameter to refer to the Python attribute it controls (i.e., the Parameter's \"value\"). \n",
13 "\n",
14 "A `Parameterized` class is a Python class that inherits from `param.Parameterized` and can accept `Parameter` objects as class attributes. A `Parameterized` class or instance uses the `Parameter` objects to determine how the corresponding attribute should behave.\n",
15 "\n",
16 "There are many specific types of `Parameter` with different behavior, discussed in [Parameter Types](Parameter_Types.ipynb), but here we will cover the common behavior between _all_ Parameter types when used in a `Parameterized` object."
17 ]
18 },
19 {
20 "cell_type": "markdown",
21 "metadata": {},
22 "source": [
23 "## Parameter metadata\n",
24 "\n",
25 "Each Parameter type can define additional behavior and associated metadata, but the metadata supported for all Parameter types includes:\n",
26 "\n",
27 "- **default**: Default value for this parameter at the class level, which will also be the value at the Parameterized instance level if it hasn't been set separately on the instance.\n",
28 "- **name**: String **name** of this parameter, which is typically determined by the attribute name of this Parameter in the owning Parameterized object, and is not set directly by a programmer.\n",
29 "- **label**: Optional long name used for a verbose listing; defaults to the **name**.\n",
30 "- **allow_None**: Whether this parameter accepts None as an allowed value, in addition to whatever other types it accepts. Automatically set to True if the default value of this Parameter is None.\n",
31 "- **doc**: Docstring describing this parameter, which can be used by automatic documentation systems.\n",
32 "- **constant**: Parameter whose value can only be changed at the class level or in a Parameterized constructor. Once the Parameterized instance has been created, the value is constant except in the context of `with param.edit_constant(obj)` (see below).\n",
33 "- **readonly**: Parameter whose value cannot be set by a user either on an instance or at the class level. Can still be changed inside a codebase by temporarily overriding this value, e.g. to report internal state\n",
34 "- **instantiate**: Whether to deepcopy the default value into a Parameterized instance when it is created. \n",
35 "- **per_instance**: whether a separate Parameter instance will be created for every Parameterized instance created.\n",
36 "- **precedence**: Optional numeric value controlling whether this parameter is visible in a listing and if so in what order.\n",
37 "\n",
38 "Most of these settings (apart from **name**) are accepted as keyword arguments to the Parameter's constructor, with `default` also accepted as a positional argument:"
39 ]
40 },
41 {
42 "cell_type": "code",
43 "execution_count": null,
44 "metadata": {},
45 "outputs": [],
46 "source": [
47 "import param\n",
48 "from param import Parameter, Parameterized\n",
49 "\n",
50 "p = Parameter(42, doc=\"The answer\", constant=True)\n",
51 "p.default"
52 ]
53 },
54 {
55 "cell_type": "code",
56 "execution_count": null,
57 "metadata": {},
58 "outputs": [],
59 "source": [
60 "p.allow_None"
61 ]
62 },
63 {
64 "cell_type": "code",
65 "execution_count": null,
66 "metadata": {},
67 "outputs": [],
68 "source": [
69 "p.doc"
70 ]
71 },
72 {
73 "cell_type": "markdown",
74 "metadata": {},
75 "source": [
76 "## Parameter objects and instances\n",
77 "\n",
78 "In most cases, a Parameter will not be declared on its own as above; the Parameter object by itself is little more than a container for the metadata above. Until it is put into a class, most of those declarations are not meaningful, because what the Parameter object does is to specify how the corresponding Python attribute of that class should be handled. For example, we can define a Parameterized class with a couple of Parameter objects, and we'll then be able to access the corresponding attributes of that class:"
79 ]
80 },
81 {
82 "cell_type": "code",
83 "execution_count": null,
84 "metadata": {},
85 "outputs": [],
86 "source": [
87 "class A(Parameterized):\n",
88 " question = Parameter(\"What is it?\", doc=\"The question\")\n",
89 " answer = Parameter(2, constant=True, doc=\"The answer\")\n",
90 " ultimate_answer = Parameter(42, readonly=True, doc=\"The real answer\")\n",
91 "\n",
92 "a = A(question=\"How is it?\", answer=\"6\")"
93 ]
94 },
95 {
96 "cell_type": "markdown",
97 "metadata": {},
98 "source": [
99 "Here, we created a Parameterized class `A`, with parameters `question` and `answer`, each with default values. We then instantiated a Python object `a` of type `A`. Without having to write a constructor for `A`, we were able to provide our own values for `question` and `answer`, while inheriting the default value of `ultimate_answer`. This approach gives a lot of (but not too much!) configurability to the user of this class, without much effort by the class author. Any values we provide at instantiation need to be allowed by the `Parameter` declaration; e.g. here we could not provide a value for `ultimate_answer` when declaring `a`, because that parameter is read only."
100 ]
101 },
102 {
103 "cell_type": "code",
104 "execution_count": null,
105 "metadata": {},
106 "outputs": [],
107 "source": [
108 "with param.exceptions_summarized():\n",
109 " A(ultimate_answer=\"no\")"
110 ]
111 },
112 {
113 "cell_type": "markdown",
114 "metadata": {},
115 "source": [
116 "Now that we have a Parameterized instance `a`, we can access the attributes we defined just as if they were normal Python instance attributes, and we'll get the values we provided:"
117 ]
118 },
119 {
120 "cell_type": "code",
121 "execution_count": null,
122 "metadata": {},
123 "outputs": [],
124 "source": [
125 "a.question"
126 ]
127 },
128 {
129 "cell_type": "code",
130 "execution_count": null,
131 "metadata": {},
132 "outputs": [],
133 "source": [
134 "a.answer"
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "Meanwhile, the `Parameterized` _class_ `A` (not the instance `a`) still has the default values, accessible as class attributes and used for any future objects instantiated of type `A`:"
142 ]
143 },
144 {
145 "cell_type": "code",
146 "execution_count": null,
147 "metadata": {},
148 "outputs": [],
149 "source": [
150 "A.question"
151 ]
152 },
153 {
154 "cell_type": "code",
155 "execution_count": null,
156 "metadata": {},
157 "outputs": [],
158 "source": [
159 "A.answer"
160 ]
161 },
162 {
163 "cell_type": "code",
164 "execution_count": null,
165 "metadata": {},
166 "outputs": [],
167 "source": [
168 "b=A()\n",
169 "b.answer"
170 ]
171 },
172 {
173 "cell_type": "markdown",
174 "metadata": {},
175 "source": [
176 "If accessing the attribute always gives us a value whether on the instance or the class, what happened to the `Parameter` objects? They are stored on the Parameterized instance or class, and are accessible via a `param` accessor object at either the instance or class levels:"
177 ]
178 },
179 {
180 "cell_type": "code",
181 "execution_count": null,
182 "metadata": {},
183 "outputs": [],
184 "source": [
185 "a.param.question"
186 ]
187 },
188 {
189 "cell_type": "code",
190 "execution_count": null,
191 "metadata": {},
192 "outputs": [],
193 "source": [
194 "a.param.question.name"
195 ]
196 },
197 {
198 "cell_type": "code",
199 "execution_count": null,
200 "metadata": {},
201 "outputs": [],
202 "source": [
203 "a.param.question.default"
204 ]
205 },
206 {
207 "cell_type": "code",
208 "execution_count": null,
209 "metadata": {},
210 "outputs": [],
211 "source": [
212 "A.param.question.default"
213 ]
214 },
215 {
216 "cell_type": "markdown",
217 "metadata": {},
218 "source": [
219 "Once the Parameterized instance is created, the attributes can continue to be modified on it as often as you like, as long as the value is allowed by the `Parameter` object involved. E.g. `question` can still be changed, while `answer` is constant and cannot be changed after the `Parameterized` object has been instantiated:"
220 ]
221 },
222 {
223 "cell_type": "code",
224 "execution_count": null,
225 "metadata": {},
226 "outputs": [],
227 "source": [
228 "with param.exceptions_summarized():\n",
229 " a.question=True\n",
230 " a.answer=5"
231 ]
232 },
233 {
234 "cell_type": "code",
235 "execution_count": null,
236 "metadata": {},
237 "outputs": [],
238 "source": [
239 "a.question"
240 ]
241 },
242 {
243 "cell_type": "markdown",
244 "metadata": {},
245 "source": [
246 "Note that if you do need to change the value of a constant parameter (typically inside of your Parameterized object's own code), you can do so using the `param.edit_constant` context manager:"
247 ]
248 },
249 {
250 "cell_type": "code",
251 "execution_count": null,
252 "metadata": {},
253 "outputs": [],
254 "source": [
255 "with param.edit_constant(a):\n",
256 " a.answer=30\n",
257 "a.answer"
258 ]
259 },
260 {
261 "cell_type": "markdown",
262 "metadata": {},
263 "source": [
264 "In most cases, the only time you need to worry about the difference between a Parameter and a regular Python attribute is when you first declare it; after that it will sit there happily behaving as instructed, noticeable only when a user attempts something the declarer of that Parameter has not allowed. You can safely leave the various metadata items at their defaults most of the time, but they are all there for when your particular application requires a certain behavior. "
265 ]
266 },
267 {
268 "cell_type": "markdown",
269 "metadata": {},
270 "source": [
271 "## Parameter inheritance and instantiation\n",
272 "\n",
273 "Much of the parameter metadata is there to help you control whether and how the parameter value is instantiated on Parameterized objects as they are created or new Parameterized subclasses as they are defined. Depending on how you want to use that Parameter and what values it might take, controlling instantiation can be very important when mutable values are involved. First, let's look at the default behavior, which is appropriate for immutable attributes:"
274 ]
275 },
276 {
277 "cell_type": "code",
278 "execution_count": null,
279 "metadata": {},
280 "outputs": [],
281 "source": [
282 "class B(A):\n",
283 " ultimate_answer = Parameter(84, readonly=True)\n",
284 "\n",
285 "b = B()\n",
286 "b.question"
287 ]
288 },
289 {
290 "cell_type": "code",
291 "execution_count": null,
292 "metadata": {},
293 "outputs": [],
294 "source": [
295 "A.question=\"How are you?\""
296 ]
297 },
298 {
299 "cell_type": "code",
300 "execution_count": null,
301 "metadata": {},
302 "outputs": [],
303 "source": [
304 "b.question"
305 ]
306 },
307 {
308 "cell_type": "markdown",
309 "metadata": {},
310 "source": [
311 "Here you can see that B inherits the `question` parameter from A, and as long as `question` has not been set explicitly on `b`, `b.question` will report the value from where that Parameter was defined, i.e. A in this case. If `question` is subsequently set on `b`, `b.question` will no longer be affected by the value in `A`:"
312 ]
313 },
314 {
315 "cell_type": "code",
316 "execution_count": null,
317 "metadata": {},
318 "outputs": [],
319 "source": [
320 "b.question=\"Why?\"\n",
321 "A.question=\"Who?\"\n",
322 "b.question"
323 ]
324 },
325 {
326 "cell_type": "markdown",
327 "metadata": {},
328 "source": [
329 "As you can see, parameters not specified in B are still fully usable in it, if they were declared in a superclass. Metadata associated with that parameter is also inherited if not explicitly overidden in `B`. E.g. `help(b)` or `help(B)` will list all parameters:"
330 ]
331 },
332 {
333 "cell_type": "markdown",
334 "metadata": {},
335 "source": [
336 "<img src=\"../assets/param_help.png\" alt=\"Param help\"></img>"
337 ]
338 },
339 {
340 "cell_type": "markdown",
341 "metadata": {},
342 "source": [
343 "Parameter inheritance like this lets you (a) define a Parameter only once, no matter how many subclasses it might be used in, and (b) control the value of that parameter conveniently across the entire set of subclasses and instances, as long as that attribute has not been set on those objects already. Using inheritance in this way is a very convenient mechanism for setting default values and other \"global\" parameters, whether before a program starts executing or during it.\n",
344 "\n",
345 "However, what happens if the value (unlike Python strings) is mutable? Things get a lot more complex:"
346 ]
347 },
348 {
349 "cell_type": "code",
350 "execution_count": null,
351 "metadata": {},
352 "outputs": [],
353 "source": [
354 "s = [1,2,3]\n",
355 "\n",
356 "class C(Parameterized):\n",
357 " s1 = param.Parameter(s, doc=\"A sequence\")\n",
358 " s2 = param.Parameter(s, doc=\"Another sequence\")\n",
359 "\n",
360 "c = C()"
361 ]
362 },
363 {
364 "cell_type": "markdown",
365 "metadata": {},
366 "source": [
367 "Here, both parameters `s1` and `s2` effectively point to the same underlying sequence `s`:"
368 ]
369 },
370 {
371 "cell_type": "code",
372 "execution_count": null,
373 "metadata": {},
374 "outputs": [],
375 "source": [
376 "c.s1 is c.s2"
377 ]
378 },
379 {
380 "cell_type": "code",
381 "execution_count": null,
382 "metadata": {},
383 "outputs": [],
384 "source": [
385 "s[1]*=5"
386 ]
387 },
388 {
389 "cell_type": "code",
390 "execution_count": null,
391 "metadata": {},
392 "outputs": [],
393 "source": [
394 "s"
395 ]
396 },
397 {
398 "cell_type": "code",
399 "execution_count": null,
400 "metadata": {},
401 "outputs": [],
402 "source": [
403 "c.s1"
404 ]
405 },
406 {
407 "cell_type": "code",
408 "execution_count": null,
409 "metadata": {},
410 "outputs": [],
411 "source": [
412 "c.s1[2]='a'"
413 ]
414 },
415 {
416 "cell_type": "code",
417 "execution_count": null,
418 "metadata": {},
419 "outputs": [],
420 "source": [
421 "c.s1"
422 ]
423 },
424 {
425 "cell_type": "code",
426 "execution_count": null,
427 "metadata": {},
428 "outputs": [],
429 "source": [
430 "c.s2"
431 ]
432 },
433 {
434 "cell_type": "markdown",
435 "metadata": {},
436 "source": [
437 "As you can see, there is only one actual sequence here, and `s`, `s1`, and `s2` all point to it. In some cases such behavior is desirable, e.g. if the mutable object is a specific global list (e.g. a set of search paths) with a unique identity and all of the parameters are meant to point to that specific item. In other cases, it's the contents of the mutable item that are important, and no sharing of contents is intended. Luckily, Param supports that case as well, if you provide `instantiate=True`:"
438 ]
439 },
440 {
441 "cell_type": "code",
442 "execution_count": null,
443 "metadata": {},
444 "outputs": [],
445 "source": [
446 "s = [1,2,3]\n",
447 "\n",
448 "class D(Parameterized):\n",
449 " s1 = Parameter(s, doc=\"A sequence\", instantiate=True)\n",
450 " s2 = Parameter(s, doc=\"Another sequence\", instantiate=True)\n",
451 "\n",
452 "d = D()"
453 ]
454 },
455 {
456 "cell_type": "markdown",
457 "metadata": {},
458 "source": [
459 "Now, parameters `s1` and `s2` point to their own copies of the sequence, independent of each other and of the original argument `s`:"
460 ]
461 },
462 {
463 "cell_type": "code",
464 "execution_count": null,
465 "metadata": {},
466 "outputs": [],
467 "source": [
468 "d.s1 is d.s2"
469 ]
470 },
471 {
472 "cell_type": "code",
473 "execution_count": null,
474 "metadata": {},
475 "outputs": [],
476 "source": [
477 "s*=2"
478 ]
479 },
480 {
481 "cell_type": "code",
482 "execution_count": null,
483 "metadata": {},
484 "outputs": [],
485 "source": [
486 "s"
487 ]
488 },
489 {
490 "cell_type": "code",
491 "execution_count": null,
492 "metadata": {},
493 "outputs": [],
494 "source": [
495 "d.s1"
496 ]
497 },
498 {
499 "cell_type": "code",
500 "execution_count": null,
501 "metadata": {},
502 "outputs": [],
503 "source": [
504 "d.s1[2]='a'"
505 ]
506 },
507 {
508 "cell_type": "code",
509 "execution_count": null,
510 "metadata": {},
511 "outputs": [],
512 "source": [
513 "d.s2"
514 ]
515 },
516 {
517 "cell_type": "markdown",
518 "metadata": {},
519 "source": [
520 "Of course, copying the data into each instance like that costs memory, and moreover prevents controlling all instances at once by setting a class attribute as we saw earlier, which is why `instantiate` is not True by default. As a rule of thumb, set `instantiate=True` if and only if (a) your Parameter can take mutable values, and (b) you want those values to be independent between Parameterized instances."
521 ]
522 },
523 {
524 "cell_type": "markdown",
525 "metadata": {},
526 "source": [
527 "## Parameter metadata inheritance and instantiation\n",
528 "\n",
529 "`instantiate` controls how parameter values behave, but similar issues arise for Parameter objects, which offer similar control via the `per_instance` metadata declaration. `per_instance` (True by default) provides a logically distinct Parameter object for every Parameterized instance, allowing each such instance to have different metadata for that parameter. For example, we can set the label separately for each instance without clobbering each other:"
530 ]
531 },
532 {
533 "cell_type": "code",
534 "execution_count": null,
535 "metadata": {},
536 "outputs": [],
537 "source": [
538 "d1 = D()\n",
539 "d2 = D()\n",
540 "d1.param.s1.label=\"sequence 1\"\n",
541 "d2.param.s1.label=\"Sequence 1\"\n",
542 "d2.param.s1.label"
543 ]
544 },
545 {
546 "cell_type": "code",
547 "execution_count": null,
548 "metadata": {},
549 "outputs": [],
550 "source": [
551 "d1.param.s1.label"
552 ]
553 },
554 {
555 "cell_type": "markdown",
556 "metadata": {},
557 "source": [
558 "This capability is useful for situations with dynamically updated metadata, e.g. if you need setting one parameter's value (e.g. 'Continent') to change the allowed values of another parameter (e.g. 'Country'). The underlying Parameter objects are copied lazily (only when actually changed), so that objects are not actually multiplied unless necessary. If you do want parameters to share a single Parameter object so that you can control its behavior globally, you can achieve that with `per_instance=False`, though the effects are confusing in the same way as `instantiate=True` for mutable objects (above):"
559 ]
560 },
561 {
562 "cell_type": "code",
563 "execution_count": null,
564 "metadata": {},
565 "outputs": [],
566 "source": [
567 "class E(Parameterized):\n",
568 " a = param.Parameter(3.14, label=\"pi\", per_instance=False)\n",
569 "\n",
570 "e1 = E()\n",
571 "e2 = E()\n",
572 "e2.param.a.label=\"Pie\"\n",
573 "e1.param.a.label"
574 ]
575 },
576 {
577 "cell_type": "markdown",
578 "metadata": {},
579 "source": [
580 "## Instantiating with shared parameters\n",
581 "\n",
582 "When creating a large collection of Parameterized objects of the same type, the overhead of having separate parameters for each object can be significant. If you want, you can create the objects to share parameter instances for efficiency, and also so that you can easily change a value on all such objects at the same time. \n",
583 "\n",
584 "As an example, the default behavior of a set of objects will be to have independent parameter values, such that changing one of them will not affect the others:"
585 ]
586 },
587 {
588 "cell_type": "code",
589 "execution_count": null,
590 "metadata": {},
591 "outputs": [],
592 "source": [
593 "class S(param.Parameterized):\n",
594 " l = param.Parameter([1,2,3])\n",
595 "\n",
596 "ss = [S() for i in range(10)]\n",
597 "ss[0].l[2]=5\n",
598 "ss[1].l"
599 ]
600 },
601 {
602 "cell_type": "markdown",
603 "metadata": {},
604 "source": [
605 "If you use the context manager `shared_parameters`, any Parameterized objects created within that context will share parameter instances, such that changing one of them will affect all of them:"
606 ]
607 },
608 {
609 "cell_type": "code",
610 "execution_count": null,
611 "metadata": {},
612 "outputs": [],
613 "source": [
614 "with param.shared_parameters():\n",
615 " ps = [S() for i in range(10)]\n",
616 " \n",
617 "ps[0].l[2]=5\n",
618 "ps[1].l"
619 ]
620 },
621 {
622 "cell_type": "markdown",
623 "metadata": {},
624 "source": [
625 "This approach can provide significant speedup and memory savings in certain cases."
626 ]
627 },
628 {
629 "cell_type": "markdown",
630 "metadata": {},
631 "source": [
632 "## Displaying Parameterized objects\n",
633 "\n",
634 "Most of the important behavior of Parameterized is to do with instantiation, getting, and setting, as described above. Parameterized also provides a few public methods for creating a string representation of the Parameterized object and its parameters:\n",
635 "\n",
636 "- `Parameterized.__str__()`: A concise, non-executable representation of the name and class of this object\n",
637 "- `Parameterized.__repr__()`: A representation of this object and its parameter values as if it were Python code calling the constructor (`classname(parameter1=x,parameter2=y,...)`)\n",
638 "- `Parameterized.param.pprint()`: Customizable, hierarchical pretty-printed representation of this Parameterized and (recursively) any of its parameters that are Parameterized objects. See [Serialization and Persistence](Serialization_and_Persistence.ipynb) for details on customizing `pprint`."
639 ]
640 },
641 {
642 "cell_type": "code",
643 "execution_count": null,
644 "metadata": {},
645 "outputs": [],
646 "source": [
647 "import param\n",
648 "\n",
649 "class Q(param.Parameterized):\n",
650 " a = param.Number(39, bounds=(0,50))\n",
651 " b = param.String(\"str\")\n",
652 "\n",
653 "class P(Q):\n",
654 " c = param.ClassSelector(Q, Q())\n",
655 " e = param.ClassSelector(param.Parameterized, param.Parameterized())\n",
656 " f = param.Range((0,1))\n",
657 "\n",
658 "p = P(f=(2,3), c=P(f=(42,43)), name=\"demo\")"
659 ]
660 },
661 {
662 "cell_type": "code",
663 "execution_count": null,
664 "metadata": {},
665 "outputs": [],
666 "source": [
667 "p.__str__()"
668 ]
669 },
670 {
671 "cell_type": "code",
672 "execution_count": null,
673 "metadata": {},
674 "outputs": [],
675 "source": [
676 "p.__repr__()"
677 ]
678 },
679 {
680 "cell_type": "code",
681 "execution_count": null,
682 "metadata": {},
683 "outputs": [],
684 "source": [
685 "p.param.pprint(separator=\"\\n\")"
686 ]
687 },
688 {
689 "cell_type": "markdown",
690 "metadata": {},
691 "source": [
692 "Notice that in the case of a circular reference (`p.c = P(c=p)`) the representation will show an ellipsis (`...`) rather than recursively printing the subobject:"
693 ]
694 },
695 {
696 "cell_type": "code",
697 "execution_count": null,
698 "metadata": {},
699 "outputs": [],
700 "source": [
701 "p.c=P(c=p)\n",
702 "p.param.pprint()"
703 ]
704 },
705 {
706 "cell_type": "markdown",
707 "metadata": {},
708 "source": [
709 "# Other Parameterized methods\n",
710 "\n",
711 "Like `.param.pprint`, the remaining \"utility\" or convenience methods available for a `Parameterized` class or object are provided via a subobject called `param` that helps keep the namespace clean and disambiguate between Parameter objects and parameter values:\n",
712 "\n",
713 "- `.param.add_parameter(param_name,param_obj)`: Dynamically add a new Parameter to this object's class\n",
714 "- `.param.update(**kwargs)`: Set parameter values from the given `param=value` keyword arguments (or a dict or iterable), delaying watching and dependency handling until all have been updated\n",
715 "- `.param.values(onlychanged=False)`: A dict of name,value pairs for all parameters of this object\n",
716 "- `.param.objects(instance=True)`: Parameter objects of this instance or class\n",
717 "- `.param.get_value_generator(name)`: Returns the underlying value-generating callable for this parameter, or the underlying static value if none\n",
718 "- `.param.force_new_dynamic_value(name)`: For a Dynamic parameter, generate a new value and return it\n",
719 "- `.param.inspect_value(name)`: For a Dynamic parameter, return the current value of the named attribute without modifying it.\n"
720 ]
721 },
722 {
723 "cell_type": "markdown",
724 "metadata": {},
725 "source": [
726 "## Specialized Parameter types\n",
727 "\n",
728 "As you can see above, a `Parameter` provides a lot of power already on its own, but in practice you will want to use much more specific parameter types that reject invalid inputs and keep your code clean and simple. A specialized Parameter acts as a \"contract\" with the users of the code you write, declaring and defending precisely what configuration is allowed and how to achieve it. If you need to accept specific inputs like that but don't add an appropriate Parameter type, you'll be stuck adding exceptions and validation code throughout your codebase, whereas anything you can express at the Parameter level will be enforced automatically without any further checks or code.\n",
729 "\n",
730 "For instance, what if you want to accept a numeric parameter, but (for some reason) can only accept numbers that are integer powers of 2? You'll need a custom Parameter class to express a restriction like that:"
731 ]
732 },
733 {
734 "cell_type": "code",
735 "execution_count": null,
736 "metadata": {},
737 "outputs": [],
738 "source": [
739 "import numbers\n",
740 "\n",
741 "class BinaryPower(param.Parameter):\n",
742 " \"\"\"Integer Parameter required to be a power of 2\"\"\"\n",
743 "\n",
744 " def _validate_value(self, val, allow_None):\n",
745 " super(BinaryPower, self)._validate_value(val, allow_None)\n",
746 " if not isinstance(val, numbers.Number):\n",
747 " raise ValueError(\"BinaryPower parameter %r must be a number, \"\n",
748 " \"not %r.\" % (self.name, val))\n",
749 " \n",
750 " if not (val % 2 == 0):\n",
751 " raise ValueError(\"BinaryPower parameter %r must be a power of 2, \"\n",
752 " \"not %r.\" % (self.name, val))\n",
753 "\n",
754 "class P(param.Parameterized):\n",
755 " n = param.Number()\n",
756 " b = BinaryPower()\n",
757 " \n",
758 "p=P()\n",
759 "P(n=5, b=4)\n",
760 "P(b=4, n=5, name='P00003')"
761 ]
762 },
763 {
764 "cell_type": "code",
765 "execution_count": null,
766 "metadata": {},
767 "outputs": [],
768 "source": [
769 "with param.exceptions_summarized():\n",
770 " P(n=5, b=\"four\")"
771 ]
772 },
773 {
774 "cell_type": "code",
775 "execution_count": null,
776 "metadata": {},
777 "outputs": [],
778 "source": [
779 "with param.exceptions_summarized():\n",
780 " P(n=5, b=5)"
781 ]
782 },
783 {
784 "cell_type": "markdown",
785 "metadata": {},
786 "source": [
787 "Luckily, you don't often need to write a custom Parameter class like this, because the most common cases are already provided in Param, as listed in the [Parameter Types](Parameter_Types.ipynb) manual. If you need something more specific than the existing types, start with the one that comes closest to restricting its value to the desired set of values without excluding any allowable values. In this case all integer powers of 2 are also integers, so you'd start with `param.Integer` rather than `param.Parameterized` as above. You can then make a new subclass and add validation as above to further restrict the values to precisely what you allow. Here if you inherited from `param.Integer` you would no longer need to check if the input is a number, as `param.Integer` already does that as long as you call `super` as above. Your custom type can override any aspects of the Parameter if needed, e.g. to accept different items in the constructor, store additional data, add additional constraints, and so on. The existing Parameter types in `param/__init__.py` act as a rich source of examples for you to start with and crib from."
788 ]
789 }
790 ],
791 "metadata": {
792 "language_info": {
793 "name": "python",
794 "pygments_lexer": "ipython3"
795 }
796 },
797 "nbformat": 4,
798 "nbformat_minor": 5
799 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Serialization and Persistence\n",
7 "\n",
8 "Parameterized objects are declarative, explicitly defining a set of values for their parameters. This set of values constitutes the (parameter) state of the object, and this state can be saved (\"serialized\"), transmitted (if appropriate), and restored (\"deserialized\") in various ways, so that object state can be sent from one Python session to another, restored from disk, configured using a text file, and so on.\n",
9 "\n",
10 "Param offers several independent serialization mechanisms for a Parameterized object, each used for very different purposes:\n",
11 "- **Pickle**: creates a Python [pickle](https://docs.python.org/3/library/pickle.html) file containing not just the Parameters, but potentially any other state of the object. A pickle file is not human readable, and is not always portable between different python versions, but it is highly complete, capturing both parameter values and also non-Parameter attributes of an object. Useful for saving the entire state of a complex object and restoring it. All objects used in pickling need to be restorable, which puts some restrictions on Parameter values (e.g. requiring named functions, not lambdas).\n",
12 "- **JSON**: captures the state as a JSON text string. Currently and probably always limited in what can be represented, but human readable and easily exchanged with other languages. Useful for sending over a network connection, saving simple state to disk for restoring later, etc.\n",
13 "- **script_repr**: generates a string representation in the form of Python code that, when executed, will instantiate Parameterized objects having similar state. Useful for capturing the current state in a compact, human-readable form suitable for manual editing to create a Python file. Not all Parameters will have values representable in this way (e.g. functions defined in the current namespace will not show their function definition), but this representation is generally a reasonable human-readable starting point for hand editing. "
14 ]
15 },
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "## Pickling Parameterized objects\n",
21 "\n",
22 "Param supports Python's native [pickle](https://docs.python.org/3/library/pickle.html) serialization format. Pickling converts a Python object into a binary stream of bytes that can be stored on disk, and unpickling converts a previously pickled byte stream into an instantiated Python object in the same or a new Python session. Pickling does not capture the actual Python source code or bytecode for functions or classes; instead, it assumes you will have the same Python source tree available for importing those definitions during unpickling and only stores the fully qualified path to those definitions. Thus pickling requires that you use named functions defined in separate importable modules rather than lambdas (unnamed functions) or other objects whose code is defined only in the main namespace or in a non-importable python script. \n",
23 "\n",
24 "Apart from such limitations, pickling is the most rich and _fully featured_ serialization option, capable of capturing the full state of an object even beyond its Parameter values. Pickling is also inherently the _least portable_ option, because it does include all the details of this internal state. The resulting .pkl files are not human readable and are not normally usable outside of Python or even across Python versions in some cases. Pickling is thus most useful for \"snapshots\" (e.g. for checkpoint-and-restore support) for a particular software installation, rather than for exporting, archiving, or configuration. See the [comparison with JSON](https://docs.python.org/3/library/pickle.html#comparison-with-json) to help understand some of the tradeoffs involved in using pickles. \n",
25 "\n",
26 "### Using pickling\n",
27 "\n",
28 "Let's look at an example of pickling and unpickling a Parameterized object:"
29 ]
30 },
31 {
32 "cell_type": "code",
33 "execution_count": null,
34 "metadata": {},
35 "outputs": [],
36 "source": [
37 "import param, pickle, time\n",
38 "from param.parameterized import default_label_formatter\n",
39 "\n",
40 "class A(param.Parameterized):\n",
41 " n = param.Number(39)\n",
42 " l = param.List([\"a\",\"b\"])\n",
43 " o = param.ClassSelector(class_=param.Parameterized)\n",
44 " \n",
45 " def __init__(self, **params):\n",
46 " super(A,self).__init__(**params)\n",
47 " self.timestamp = time.time()\n",
48 " \n",
49 "a = A(n=5, l=[1,\"e\",[2]], o=default_label_formatter.instance())\n",
50 "a, a.timestamp"
51 ]
52 },
53 {
54 "cell_type": "markdown",
55 "metadata": {},
56 "source": [
57 "Here we created a Parameterized object `a` containing another Parameterized object nested in parameter `o`, with state in `self.timestamp` and not just in the Parameter values. To save this state to a file on disk, we can do a pickle \"dump\" and then delete the object so that we are sure it's no longer around:"
58 ]
59 },
60 {
61 "cell_type": "code",
62 "execution_count": null,
63 "metadata": {},
64 "outputs": [],
65 "source": [
66 "with open('data.pickle', 'wb') as f:\n",
67 " pickle.dump(a, f)\n",
68 " \n",
69 "del a"
70 ]
71 },
72 {
73 "cell_type": "markdown",
74 "metadata": {},
75 "source": [
76 "To reload the state of `a` from disk, we do a pickle \"load\":"
77 ]
78 },
79 {
80 "cell_type": "code",
81 "execution_count": null,
82 "metadata": {},
83 "outputs": [],
84 "source": [
85 "import pickle\n",
86 "\n",
87 "with open('data.pickle', 'rb') as f:\n",
88 " a = pickle.load(f)\n",
89 " \n",
90 "a, a.timestamp"
91 ]
92 },
93 {
94 "cell_type": "markdown",
95 "metadata": {},
96 "source": [
97 "As you can see, it restored not just the Parameter values, but the timestamp (stored in the object's dictionary) as well. \n",
98 "\n",
99 "Here we are depending on the class definition of `A` actually being in memory. If we delete that definition and try to unpickle the object again, it will fail:"
100 ]
101 },
102 {
103 "cell_type": "code",
104 "execution_count": null,
105 "metadata": {},
106 "outputs": [],
107 "source": [
108 "del A\n",
109 "\n",
110 "with param.exceptions_summarized():\n",
111 " with open('data.pickle', 'rb') as f:\n",
112 " a = pickle.load(f)"
113 ]
114 },
115 {
116 "cell_type": "markdown",
117 "metadata": {},
118 "source": [
119 "Notice how the pickle has stored the fact that class `A` is defined in the main namespace, but because `__main__` is not an importable module, unpickling fails. Had `A` been defined in a module available for importing, unpickling would have succeeded here even if A had never previously been loaded.\n",
120 "\n",
121 "To use pickling in practice, you'll need to ensure that all functions and classes are named (not lambdas) and defined in some importable module, not just inline here in a notebook or script or command prompt. Even so, pickling can be very useful as a way to save and restore state of complex Parameterized objects."
122 ]
123 },
124 {
125 "cell_type": "markdown",
126 "metadata": {},
127 "source": [
128 "### Pickling limitations and workarounds\n",
129 "\n",
130 "As you develop a module using Param, you'll need to pay attention to a few technical issues if you want to support pickling:\n",
131 "\n",
132 "1. **Callable parameter values**: If you provide any `param.Callable`, `param.Hooklist`, or other parameters that can accept callable objects to your users, you will need to warn them that none of those can be set to unnamed (lambda) functions or to one-off functions defined in the main namespace if they want to use pickling. Of course, you can accept such values during initial development when you may not care about pickling, but once things are working, move the one-off function to a proper importable module and then it will be safe to use as a picklable value. One way to make this work smoothly is to create `param.ParameterizedFunction` objects or other \"function object\" classes (classes whose instances are callable like functions but which may have state and are fully picklable); see e.g. the `numbergen` module for examples.\n",
133 "\n",
134 "2. **Skipping Parameters that should not be pickled**: In some cases, you may not _want_ the value of a given Parameter to be pickled and restored even while other state is being serialized. For instance, a Parameter whose value is set to a particular file path might cause errors if that path is restored when the pickle is loaded on a different system or once the file no longer exists. To cover such rare but potentially important cases, the Parameter can be defined with `pickle_default_value=False` (normally `True`), so that the instantaneous value is usable but won't be saved and restored with pickle.\n",
135 "\n",
136 "3. **Customizing settting and getting state**: You may find that your Parameter or Parameterized objects have other state that you need to handle specially, whether that's to save and restore data that isn't otherwise picklable, or to ignore state that should _not_ be pickled. For instance, if your object's dictionary contains some object that doesn't support pickling, then you can add code to omit that or to serialize it in some special way that allows it to be restored, e.g. by extracting a state dictionary fom it and then restoring it from the dictionary later. See the [pickle docs](https://docs.python.org/3/library/pickle.html#pickle-state) for the `__getstate__` and `__setstate__` methods that you can implement on your Parameter or Parameterized objects to override this behavior. Be sure to call `super(YourClass,self).__setstate__(state)` or the getstate equivalent so that you also store parameters and dictionary values as usual, if desired.\n",
137 "\n",
138 "4. **Loading old pickle files**: If you use pickles extensively, you may find yourself wanting to support pickle files generated by an older version of your own code, even though your code has since changed (with renamed modules, classes, or parameters, or options that are no longer supported, etc.). By default, unpickling will raise an exception if it finds information in your pickle file that does not match the current Python source code, but it is possible to add custom handling to translate old definitions to match current code, discard no-longer-used options, map from a previous approach into the current approach, etc. You can use `__getstate__` and `__setstate__` on your top-level object or on specific other classes to do just about anything like this, though it can get complicated to reason about. Best practice is to store the module version number or other suitable identifier as an attribute or Parameter on the top-level object to declare what version of the code was used to create the file, and you can then read this identifier later to determine whether you need to apply such conversions on reloading."
139 ]
140 },
141 {
142 "cell_type": "markdown",
143 "metadata": {},
144 "source": [
145 "## Serializing with JSON\n",
146 "\n",
147 "JSON is a human-readable string representation for nested dictionaries of key-value pairs. Compared to pickle, JSON is a much more limited representation, using a fixed set of types mapped to string values, and not natively supporting Python-specific types like tuples or custom Python objects. However, it is widely accepted across computer languages, and because it is human readable and editable and omits the detailed internal state of objects (unlike pickle), JSON works well as an interchange or configuration format.\n",
148 "\n",
149 "Param's JSON support is currently fairly limited, with support for serializing and deserializing individual (not nested) Parameterized objects. It is currently primarily used for synchronizing state \"across the wire\", e.g. between multiple apps running on different machines that communicate changes to shared state (e.g. for a remote GUI), but as proposed in [issue#520](https://github.com/holoviz/param/issues/520) it could be extended to be a general configuration and specification mechanism by adding conventions for specifying a Parameterized type for an object and its nested objects.\n",
150 "\n",
151 "To see how it currently works, let's start with a Parameterized object containing Parameters of different types:"
152 ]
153 },
154 {
155 "cell_type": "code",
156 "execution_count": null,
157 "metadata": {},
158 "outputs": [],
159 "source": [
160 "import param, datetime, numpy as np, pandas as pd\n",
161 "\n",
162 "ndarray = np.array([[1,2,3],[4,5,6]])\n",
163 "df = pd.DataFrame({'A':[1,2,3], 'B':[1.1,2.2,3.3]})\n",
164 "\n",
165 "simple_list = [1]\n",
166 "\n",
167 "class P(param.Parameterized):\n",
168 " a = param.Integer(default=5, doc='Int', bounds=(2,30), inclusive_bounds=(True, False))\n",
169 " e = param.List([1,2,3], class_=int)\n",
170 " g = param.Date(default=datetime.datetime.now())\n",
171 " l = param.Range(default=(1.1,2.3), bounds=(1,3))\n",
172 " m = param.String(default='baz', allow_None=True)\n",
173 " s = param.DataFrame(default=df, columns=(1,4), rows=(2,5))\n",
174 "\n",
175 "p = P(a=29)\n",
176 "p"
177 ]
178 },
179 {
180 "cell_type": "markdown",
181 "metadata": {},
182 "source": [
183 "To serialize this Parameterized object to a JSON string, call `.serialize_parameters()` on it:"
184 ]
185 },
186 {
187 "cell_type": "code",
188 "execution_count": null,
189 "metadata": {},
190 "outputs": [],
191 "source": [
192 "s = p.param.serialize_parameters()\n",
193 "s"
194 ]
195 },
196 {
197 "cell_type": "markdown",
198 "metadata": {},
199 "source": [
200 "Notice that the serialization includes not just the values set specifically on this instance (`a=29`), but also all the default values inherited from the class definition.\n",
201 "\n",
202 "You can easily select only a subset to serialize, if you wish:"
203 ]
204 },
205 {
206 "cell_type": "code",
207 "execution_count": null,
208 "metadata": {},
209 "outputs": [],
210 "source": [
211 "p.param.serialize_parameters(subset=['a','m'])"
212 ]
213 },
214 {
215 "cell_type": "markdown",
216 "metadata": {},
217 "source": [
218 "The JSON string can be saved to disk, sent via a network connection, stored in a database, or for any other usage suitable for a string.\n",
219 "\n",
220 "Once you are ready to deserialize the string into a Parameterized object, you'll need to know the class it came from (here `P`) and can then call its `deserialize_parameters` method to get parameter values to use in `P`'s constructor:"
221 ]
222 },
223 {
224 "cell_type": "code",
225 "execution_count": null,
226 "metadata": {},
227 "outputs": [],
228 "source": [
229 "p2 = P(**P.param.deserialize_parameters(s))\n",
230 "p2"
231 ]
232 },
233 {
234 "cell_type": "markdown",
235 "metadata": {},
236 "source": [
237 "As you can see, we have successfully serialized our original object `p` into a new object `p2`, which could be in a different Python process on a different machine or at a different date.\n",
238 "\n",
239 "### JSON limitations and workarounds\n",
240 "\n",
241 "To see the limitations on Param's JSON support, let's look at how it works in more detail. Because the result of serialization (`s` above) is a valid JSON string, we can use the `json` library to unpack it without any knowledge of what Parameterized class it came from:"
242 ]
243 },
244 {
245 "cell_type": "code",
246 "execution_count": null,
247 "metadata": {},
248 "outputs": [],
249 "source": [
250 "import json\n",
251 "dj = json.loads(s)\n",
252 "dj"
253 ]
254 },
255 {
256 "cell_type": "markdown",
257 "metadata": {},
258 "source": [
259 "The result is a Python dictionary of name:value pairs, some of which you can recognize as the original type (e.g. `a=29`), others that have changed type (e.g. `l=(1.1,2.3)` or `s=pd.DataFrame({'A':[1,2,3], 'B':[1.1,2.2,3.3]})`), and others that are still a string encoding of that type (e.g. `g=datetime.datetime(...)`)). If you try to pass this dictionary to your Parameterized constructor, any such value will be rejected as invalid by the corresponding Parameter:"
260 ]
261 },
262 {
263 "cell_type": "code",
264 "execution_count": null,
265 "metadata": {},
266 "outputs": [],
267 "source": [
268 "with param.exceptions_summarized():\n",
269 " P(**dj)"
270 ]
271 },
272 {
273 "cell_type": "markdown",
274 "metadata": {},
275 "source": [
276 "That's why instead of simply `json.loads(s)`, we do `P.param.deserialize_parameters(s)`, which uses the knowledge that `P.l` is a tuple parameter to convert the resulting list `[1.1, 2.3]` into a Python tuple `(1.1, 2.3)` as required for such a parameter:"
277 ]
278 },
279 {
280 "cell_type": "code",
281 "execution_count": null,
282 "metadata": {},
283 "outputs": [],
284 "source": [
285 "print(dj['l'])\n",
286 "print(p2.l)"
287 ]
288 },
289 {
290 "cell_type": "markdown",
291 "metadata": {},
292 "source": [
293 "Similarly, parameters of type `param.Array` will unpack the list representation into a NumPy array, `param.DataFrame` unpacks the list of dicts of list into a Pandas DataFrame, etc. So, the encoding for your Parameterized object will always be standard JSON, but to _deserialize_ it fully into a Parameterized, you'll need to know the class it came from, or Param will not know that the list it finds was originally a tuple, dataframe, etc. \n",
294 "\n",
295 "For this reason, any Parameter that itself contains a Parameterized object will not be able to be JSON deserialized, since even if we knew what class it was (e.g. for `param.ClassSelector(class_=param.Number)`, it could be some subclass of that class. Because the class name is not currently stored in the JSON serialization, there is no way to restore it. Thus there is currently no support for JSON serializing or deserializing nested Parameterized objects.\n",
296 "\n",
297 "We do expect to add support for nested objects using something like the convention for datetime objects; see [issue#520](https://github.com/holoviz/param/issues/520)."
298 ]
299 },
300 {
301 "cell_type": "markdown",
302 "metadata": {},
303 "source": [
304 "### JSON Schemas \n",
305 "\n",
306 "If you want to use your JSON representation in a separate process where Param is not available or perhaps in a different language altogether, Param can provide a [JSON schema](https://json-schema.org/) that specifies what type you are expecting for each Parameter. The schema for a given Parameterized can be obtained using the `schema` method:"
307 ]
308 },
309 {
310 "cell_type": "code",
311 "execution_count": null,
312 "metadata": {},
313 "outputs": [],
314 "source": [
315 "p.param.schema()"
316 ]
317 },
318 {
319 "cell_type": "markdown",
320 "metadata": {},
321 "source": [
322 "Once you have the schema, you can validate that a given JSON string matches the schema, i.e. that all values included therein match the constraints listed in the schema:"
323 ]
324 },
325 {
326 "cell_type": "code",
327 "execution_count": null,
328 "metadata": {},
329 "outputs": [],
330 "source": [
331 "from jsonschema import validate\n",
332 "d = json.loads(s)\n",
333 "full_schema = {\"type\" : \"object\", \"properties\" : p.param.schema()}\n",
334 "validate(instance=d, schema=full_schema)"
335 ]
336 },
337 {
338 "cell_type": "markdown",
339 "metadata": {},
340 "source": [
341 "If one of the parameter values fails to match the provided schema, you'll get an exception:"
342 ]
343 },
344 {
345 "cell_type": "code",
346 "execution_count": null,
347 "metadata": {},
348 "outputs": [],
349 "source": [
350 "d2 = d.copy()\n",
351 "d2['a']='astring'\n",
352 "\n",
353 "with param.exceptions_summarized():\n",
354 " validate(instance=d2, schema=full_schema)"
355 ]
356 },
357 {
358 "cell_type": "markdown",
359 "metadata": {},
360 "source": [
361 "The `param.schema()` call accepts the same `subset` argument as `.param.serialize_parameters()`, letting you serialize and check only a subset of the parameters if appropriate. \n",
362 "\n",
363 "You can also supply a `safe=True` argument that checks that all parameter values are _guaranteed_ to be serializable and follow the given schema. This lets you detect if there are any containers or parameters whose type is not fully specified:"
364 ]
365 },
366 {
367 "cell_type": "code",
368 "execution_count": null,
369 "metadata": {},
370 "outputs": [],
371 "source": [
372 "with param.exceptions_summarized():\n",
373 " full2 = {\"type\" : \"object\", \"properties\" : p.param.schema(safe=True)}\n",
374 " validate(instance=d, schema=full2)"
375 ]
376 },
377 {
378 "cell_type": "markdown",
379 "metadata": {},
380 "source": [
381 "## script_repr\n",
382 "\n",
383 "Parameterized objects can be constructed through a series of interactive actions, either in a GUI or command line, or as the result of automated scripts and object-construction functions. Any parameter values can also be changed at any moment once that object has been created. If you want to capture the resulting Parameterized object with any such additions and changes, you can use the `param.script_repr()` function. `script_repr` returns a representation of that object and all nested Parameterized or other supported objects as Python code that can recreate the full object later. This approach lets you go flexibly from an interactive or indirect way of creating or modifying objects, to being able to recreate that specific object again for later use. Programs with a GUI interface can use `script_repr()` as a way of exporting a runnable version of what a user created interactively in the GUI.\n",
384 "\n",
385 "For example, let's construct a Parameterized object `p` containing Parameters whose values are themselves Parameterized objects with their own Parameters:"
386 ]
387 },
388 {
389 "cell_type": "code",
390 "execution_count": null,
391 "metadata": {},
392 "outputs": [],
393 "source": [
394 "import param\n",
395 "\n",
396 "class Q(param.Parameterized):\n",
397 " a = param.Number(39, bounds=(0,50))\n",
398 " b = param.String(\"str\")\n",
399 "\n",
400 "class P(param.Parameterized):\n",
401 " c = param.ClassSelector(Q, Q())\n",
402 " d = param.ClassSelector(param.Parameterized, param.Parameterized())\n",
403 " e = param.Range((0,1))\n",
404 " \n",
405 "q = Q(b=\"new\")\n",
406 "p=P(c=q, e=(2,3))\n",
407 "p"
408 ]
409 },
410 {
411 "cell_type": "markdown",
412 "metadata": {},
413 "source": [
414 "We can get a script representation for this object by calling `script_repr(p)`:"
415 ]
416 },
417 {
418 "cell_type": "code",
419 "execution_count": null,
420 "metadata": {},
421 "outputs": [],
422 "source": [
423 "print(param.script_repr(p))"
424 ]
425 },
426 {
427 "cell_type": "markdown",
428 "metadata": {},
429 "source": [
430 "As you can see, this representation encodes the fact that `P` was defined in the main namespace, generated inside this notebook. As you might expect, this representation has the same limitation as for `pickle` -- only classes that are in importable modules will be runnable; you'll need to save the source code to your classes in a proper Python module if you want the resulting script to be runnable. But once you have done that, you can use the `script_repr` to get a runnable version of your Parameterized object no matter how you created it, whether it was by selecting options in a GUI, adding items via a loop in a script, and so on."
431 ]
432 },
433 {
434 "cell_type": "markdown",
435 "metadata": {},
436 "source": [
437 "### script_repr limitations and workarounds\n",
438 "\n",
439 "Apart from making sure your functions and classes are all defined in their own importable modules, there are various considerations and limitations to keep in mind if you want to support using `script_repr`. "
440 ]
441 },
442 {
443 "cell_type": "markdown",
444 "metadata": {},
445 "source": [
446 "Normally, script_repr prints only parameter values that have changed from their defaults; it is designed to generate a script as close as is practical to one that a user would have typed to create the given object. If you want a record of the _complete_ set of parameter values, including all defaults, you can enable that behavior:"
447 ]
448 },
449 {
450 "cell_type": "code",
451 "execution_count": null,
452 "metadata": {},
453 "outputs": [],
454 "source": [
455 "import param.parameterized\n",
456 "param.parameterized.script_repr_suppress_defaults=True"
457 ]
458 },
459 {
460 "cell_type": "markdown",
461 "metadata": {},
462 "source": [
463 "The resulting output is then suitable for archiving the full parameter state of that object, even if some default later gets changed in the source code. Note that Param is not able to detect all cases where a default value is unchanged, e.g. for Parameters with `instantiate=True`, which will always be treated as changed since each instance has a copy of that Parameter value independent of the original default value.\n",
464 "\n",
465 "You can control `script_repr` with keyword arguments:\n",
466 "\n",
467 "- `imports=[]`: If desired, a list of imports that can be built up over multiple script_repr calls to collect a full set of imports required for a script. Useful with `show_imports=False` except on the last script_repr call. Can be an empty list or a list containing some hard-coded imports needed.\n",
468 "- `prefix=\"\\n \"`: Optional prefix to use before a nested object.\n",
469 "- `qualify=True`: Whether the class's path will be included (e.g. \"a.b.C()\"), otherwise only the class will appear (\"C()\").\n",
470 "- `unknown_value=None`: determines what to do where a representation cannot be generated for something required to recreate the object. Such things include non-parameter positional and keyword arguments, and certain values of parameters (e.g. some random state objects). Supplying an `unknown_value` of `None` causes unrepresentable things to be silently ignored. If `unknown_value` is a string, that string will appear in place of any unrepresentable things. If `unknown_value` is `False`, an Exception will be raised if an unrepresentable value is encountered. \n",
471 "- `separator=\"\\n\"`: Separator to use between parameters.\n",
472 "- `show_imports=True`: Whether to include import statements in the output.\n",
473 "\n",
474 "\n",
475 "The `script_repr` behavior for a particular type, whether it's a Parameterized object or not, can be overridden to provide any functionality needed. Such overrides are stored in `param.parameterized.script_repr_reg`, which already contains handling for list and tuple containers, various objects with random state, functions, and modules. See examples in \n",
476 "`param.parameterized`."
477 ]
478 }
479 ],
480 "metadata": {
481 "language_info": {
482 "name": "python",
483 "pygments_lexer": "ipython3"
484 }
485 },
486 "nbformat": 4,
487 "nbformat_minor": 5
488 }
0 {
1 "cells": [
2 {
3 "cell_type": "markdown",
4 "metadata": {},
5 "source": [
6 "# Simplifying Codebases\n",
7 "\n",
8 "Param's just a Python library, and so anything you can do with Param you can do \"manually\". So, why use Param?\n",
9 "\n",
10 "The most immediate benefit to using Param is that it allows you to greatly simplify your codebases, making them much more clear, readable, and maintainable, while simultaneously providing robust handling against error conditions.\n",
11 "\n",
12 "Param does this by letting a programmer explicitly declare the types and values of parameters accepted by the code. Param then ensures that only suitable values of those parameters ever make it through to the underlying code, removing the need to handle any of those conditions explicitly.\n",
13 "\n",
14 "To see how this works, let's create a Python class with some attributes without using Param:"
15 ]
16 },
17 {
18 "cell_type": "code",
19 "execution_count": null,
20 "metadata": {},
21 "outputs": [],
22 "source": [
23 "class OrdinaryClass(object):\n",
24 " def __init__(self, a=2, b=3, title=\"sum\"):\n",
25 " self.a = a\n",
26 " self.b = b\n",
27 " self.title = title\n",
28 " \n",
29 " def __call__(self):\n",
30 " return self.title + \": \" + str(self.a + self.b)"
31 ]
32 },
33 {
34 "cell_type": "markdown",
35 "metadata": {},
36 "source": [
37 "As this is just standard Python, we can of course instantiate this class, modify its variables, and call it:"
38 ]
39 },
40 {
41 "cell_type": "code",
42 "execution_count": null,
43 "metadata": {},
44 "outputs": [],
45 "source": [
46 "o1 = OrdinaryClass(b=4, title=\"Sum\")\n",
47 "o1.a=4\n",
48 "o1()"
49 ]
50 },
51 {
52 "cell_type": "markdown",
53 "metadata": {},
54 "source": [
55 "The same code written using Param would look like:"
56 ]
57 },
58 {
59 "cell_type": "code",
60 "execution_count": null,
61 "metadata": {},
62 "outputs": [],
63 "source": [
64 "import param\n",
65 " \n",
66 "class ParamClass(param.Parameterized):\n",
67 " a = param.Integer(2, bounds=(0,1000), doc=\"First addend\")\n",
68 " b = param.Integer(3, bounds=(0,1000), doc=\"Second addend\")\n",
69 " title = param.String(default=\"sum\", doc=\"Title for the result\")\n",
70 " \n",
71 " def __call__(self):\n",
72 " return self.title + \": \" + str(self.a + self.b)"
73 ]
74 },
75 {
76 "cell_type": "code",
77 "execution_count": null,
78 "metadata": {},
79 "outputs": [],
80 "source": [
81 "o2 = ParamClass(b=4, title=\"Sum\")\n",
82 "o2()"
83 ]
84 },
85 {
86 "cell_type": "markdown",
87 "metadata": {},
88 "source": [
89 "As you can see, the Parameters here are used precisely like normal attributes once they are defined, so the code for `__call__` and for invoking the constructor are the same in both cases. It's thus generally quite straightforward to migrate an existing class into Param. So, why do that?\n",
90 "\n",
91 "Well, with fewer lines of code than the ordinary class, you've now unlocked a whole wealth of features and better behavior! For instance, what happens if a user tries to supply some inappropriate data? With Param, such errors will be caught immediately:"
92 ]
93 },
94 {
95 "cell_type": "code",
96 "execution_count": null,
97 "metadata": {},
98 "outputs": [],
99 "source": [
100 "with param.exceptions_summarized(): \n",
101 " o3 = ParamClass()\n",
102 " o3.b = -5"
103 ]
104 },
105 {
106 "cell_type": "markdown",
107 "metadata": {},
108 "source": [
109 "Of course, you could always add more code to an ordinary Python class to check for errors like that, but it quickly gets unwieldy:"
110 ]
111 },
112 {
113 "cell_type": "code",
114 "execution_count": null,
115 "metadata": {},
116 "outputs": [],
117 "source": [
118 "class OrdinaryClass2(object):\n",
119 " def __init__(self, a=2, b=3, title=\"sum\"):\n",
120 " if type(a) is not int:\n",
121 " raise ValueError(\"'a' must be an integer\")\n",
122 " if type(b) is not int:\n",
123 " raise ValueError(\"'b' must be an integer\")\n",
124 " if a<0:\n",
125 " raise ValueError(\"'a' must be at least `0`\")\n",
126 " if b<0:\n",
127 " raise ValueError(\"'b' must be at least `0`\")\n",
128 " if type(title) is not str:\n",
129 " raise ValueError(\"'title' must be a string\") \n",
130 " \n",
131 " self.a = a\n",
132 " self.b = b\n",
133 " self.title = title\n",
134 " \n",
135 " def __call__(self):\n",
136 " return self.title + \": \" + str(self.a + self.b)"
137 ]
138 },
139 {
140 "cell_type": "code",
141 "execution_count": null,
142 "metadata": {},
143 "outputs": [],
144 "source": [
145 "with param.exceptions_summarized(): \n",
146 " OrdinaryClass2(a=\"f\")"
147 ]
148 },
149 {
150 "cell_type": "markdown",
151 "metadata": {},
152 "source": [
153 "Unfortunately, catching errors in the constructor like that won't help if someone modifies the attribute directly, which won't be detected as an error:"
154 ]
155 },
156 {
157 "cell_type": "code",
158 "execution_count": null,
159 "metadata": {},
160 "outputs": [],
161 "source": [
162 "o4 = OrdinaryClass2()\n",
163 "o4.a = \"four\""
164 ]
165 },
166 {
167 "cell_type": "markdown",
168 "metadata": {},
169 "source": [
170 "Python will happily accept this incorrect value and will continue processing. It may only be much later, in a very different part of your code, that you see a mysterious error message that's then very difficult to relate back to the actual problem you need to fix:"
171 ]
172 },
173 {
174 "cell_type": "code",
175 "execution_count": null,
176 "metadata": {},
177 "outputs": [],
178 "source": [
179 "with param.exceptions_summarized(): \n",
180 " o4()"
181 ]
182 },
183 {
184 "cell_type": "markdown",
185 "metadata": {},
186 "source": [
187 "Here there's no problem with the code in the cell above; `o4()` is fully valid Python; the real problem is in the preceding cell, which could have been in a completely different file or library. The error message is also obscure and confusing at this level, because the user of `o4` may have no idea why strings and integers are getting concatenated.\n",
188 "\n",
189 "To get a better error message, you _could_ move those checks into the `__call__` method, which would make sure that errors are always eventually detected:"
190 ]
191 },
192 {
193 "cell_type": "code",
194 "execution_count": null,
195 "metadata": {},
196 "outputs": [],
197 "source": [
198 "class OrdinaryClass3(object):\n",
199 " def __init__(self, a=2, b=3, title=\"sum\"): \n",
200 " self.a = a\n",
201 " self.b = b\n",
202 " self.title = title\n",
203 " \n",
204 " def __call__(self):\n",
205 " if type(self.a) is not int:\n",
206 " raise ValueError(\"'a' must be an integer\")\n",
207 " if type(self.b) is not int:\n",
208 " raise ValueError(\"'b' must be an integer\")\n",
209 " if self.a<0:\n",
210 " raise ValueError(\"'a' must be at least `0`\")\n",
211 " if self.b<0:\n",
212 " raise ValueError(\"'b' must be at least `0`\")\n",
213 " if type(self.title) is not str:\n",
214 " raise ValueError(\"'title' must be a string\") \n",
215 "\n",
216 " return self.title + \": \" + str(self.a + self.b)"
217 ]
218 },
219 {
220 "cell_type": "code",
221 "execution_count": null,
222 "metadata": {},
223 "outputs": [],
224 "source": [
225 "o5 = OrdinaryClass3()\n",
226 "o5.a = \"four\""
227 ]
228 },
229 {
230 "cell_type": "code",
231 "execution_count": null,
232 "metadata": {},
233 "outputs": [],
234 "source": [
235 "with param.exceptions_summarized(): \n",
236 " o5()"
237 ]
238 },
239 {
240 "cell_type": "markdown",
241 "metadata": {},
242 "source": [
243 "But you'd now have to check for errors in _every_ _single_ _method_ that might use those parameters. Worse, you still only detect the problem very late, far from where it was first introduced. Any distance between the error and the error report makes it much more difficult to address, as the user then has to track down where in the code `a` might have gotten set to a non-integer.\n",
244 "\n",
245 "With Param you can catch such problems at their start, as soon as an incorrect value is provided, when it is still simple to detect and correct it. To get those same features in hand-written Python code, you would need to provide explicit getters and setters, which is made easier with Python properties and decorators, but is still quite unwieldy:"
246 ]
247 },
248 {
249 "cell_type": "code",
250 "execution_count": null,
251 "metadata": {},
252 "outputs": [],
253 "source": [
254 "class OrdinaryClass4(object):\n",
255 " def __init__(self, a=2, b=3, title=\"sum\"):\n",
256 " self.a = a\n",
257 " self.b = b\n",
258 " self.title = title\n",
259 " \n",
260 " @property\n",
261 " def a(self): return self.__a\n",
262 " @a.setter\n",
263 " def a(self, a):\n",
264 " if type(a) is not int:\n",
265 " raise ValueError(\"'a' must be an integer\")\n",
266 " if a < 0:\n",
267 " raise ValueError(\"'a' must be at least `0`\")\n",
268 " self.__a = a\n",
269 " \n",
270 " @property\n",
271 " def b(self): return self.__b\n",
272 " @b.setter\n",
273 " def b(self, b):\n",
274 " if type(b) is not int:\n",
275 " raise ValueError(\"'a' must be an integer\")\n",
276 " if b < 0:\n",
277 " raise ValueError(\"'a' must be at least `0`\")\n",
278 " self.__b = b\n",
279 "\n",
280 " @property\n",
281 " def title(self): return self.__title\n",
282 " def title(self, b):\n",
283 " if type(title) is not string:\n",
284 " raise ValueError(\"'title' must be a string\")\n",
285 " self.__title = title\n",
286 "\n",
287 " def __call__(self):\n",
288 " return self.title + \": \" + str(self.a + self.b)"
289 ]
290 },
291 {
292 "cell_type": "code",
293 "execution_count": null,
294 "metadata": {},
295 "outputs": [],
296 "source": [
297 "o5=OrdinaryClass4()\n",
298 "o5()"
299 ]
300 },
301 {
302 "cell_type": "code",
303 "execution_count": null,
304 "metadata": {},
305 "outputs": [],
306 "source": [
307 "with param.exceptions_summarized(): \n",
308 " o5=OrdinaryClass4()\n",
309 " o5.b=-6"
310 ]
311 },
312 {
313 "cell_type": "markdown",
314 "metadata": {},
315 "source": [
316 "Note that this code has an easily overlooked mistake in it, reporting `a` rather than `b` as the problem. This sort of error is extremely common in copy-pasted validation code of this type, because tests rarely exercise all of the error conditions involved.\n",
317 "\n",
318 "As you can see, even getting close to the automatic validation already provided by Param requires 8 methods and >30 highly repetitive lines of code, even when using relatively esoteric Python features like properties and decorators, and still doesn't yet implement other Param features like automatic documentation, attribute inheritance, or dynamic values. With Param, the corresponding `ParamClass` code only requires 6 lines and no fancy techniques beyond Python classes. Most importantly, the Param version lets readers and program authors focus directly on what this code actually does, which is to compute a function from three provided parameters:\n",
319 "\n",
320 "```\n",
321 "class ParamClass(param.Parameterized):\n",
322 " a = param.Integer(2, bounds=(0,1000), doc=\"First addend\")\n",
323 " b = param.Integer(3, bounds=(0,1000), doc=\"Second addend\")\n",
324 " title = param.String(default=\"sum\", doc=\"Title for the result\")\n",
325 " \n",
326 " def __call__(self):\n",
327 " return self.title + \": \" + str(self.a + self.b)\n",
328 "```\n",
329 "\n",
330 "Even a quick skim of this code reveals what parameters are available, what values they will accept, what the default values are, and how those parameters will be used in the method. Plus the actual code of the method stands out immediately, as all the code is either parameters or actual functionality. In contrast, users of OrdinaryClass3 will have to read through dozens of lines of code to discern even basic information about usage, or else authors of the code will need to create and maintain docstrings that may or may not match the actual code over time and will further increase the amount of text to write and maintain."
331 ]
332 },
333 {
334 "cell_type": "markdown",
335 "metadata": {},
336 "source": [
337 "## Programming contracts\n",
338 "\n",
339 "If you think about the examples above, you can see how Param makes it simple for programmers to make a contract with their users, being explicit and clear what will be accepted and rejected, while also allowing programmers to make safe assumptions about what inputs the code may ever receive. There is no need for `__call__` _ever_ to check for the type of one of its parameters, whether it's in the range allowed, or any other property that can be enforced by Param. Your custom code can then be much more linear and straightforward, getting right to work with the actual task at hand, without having to have reams of `if` statements and `asserts()` that disrupt the flow of the source file and make the reader get sidetracked in error-handling code. Param lets you once and for all declare what this code accepts, which is both clear documentation to the user and a guarantee that the programmer can forget about any other possible value a user might someday supply.\n",
340 "\n",
341 "Crucially, these contracts apply not just between the user and a given piece of code, but also between components of the system itself. When validation code is expensive, as in ordinary Python, programmers will typically do it only at the edges of the system, where input from the user is accepted. But expressing types and ranges is so easy in Param, it can be done for any major component in the system. The Parameter list declares very clearly what that component accepts, which lets the code for that component ignore all potential inputs that are disallowed by the Parameter specifications, while correctly advertising to the rest of the codebase what inputs are allowed. Programmers can thus focus on their particular components of interest, knowing precisely what inputs will ever be let through, without having to reason about the flow of configuration and data throughout the whole system.\n",
342 "\n",
343 "Without Param, you should expect Python code to be full of confusing error checking and handling of different input types, while still only catching a small fraction of the possible incorrect inputs that could be provided. But Param-based code should be dramatically easier to read, easier to maintain, easier to develop, and nearly bulletproof against mistaken or even malicious usage. "
344 ]
345 }
346 ],
347 "metadata": {
348 "language_info": {
349 "name": "python",
350 "pygments_lexer": "ipython3"
351 }
352 },
353 "nbformat": 4,
354 "nbformat_minor": 4
355 }
119119 def __abs__ (self): return UnaryOperator(self,operator.abs)
120120
121121
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
122139
123140 class BinaryOperator(NumberGenerator):
124141 """Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator."""
147164 return self.operator(self.lhs() if callable(self.lhs) else self.lhs,
148165 self.rhs() if callable(self.rhs) else self.rhs, **self.args)
149166
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))
150171
151172
152173 class UnaryOperator(NumberGenerator):
170191 def __call__(self):
171192 return self.operator(self.operand(),**self.args)
172193
194 def pprint(self, *args, **kwargs):
195 return (pprint(self.operator, *args, **kwargs) + '(' +
196 pprint(self.operand, *args, **kwargs) + ')')
173197
174198
175199 class Hash(object):
201225
202226 I32 = 4294967296 # Maximum 32 bit unsigned int (i.e. 'I') value
203227 if isinstance(val, int):
204 numer, denom = val, 1
228 numer, denom = val, 1
205229 elif isinstance(val, fractions.Fraction):
206230 numer, denom = val.numerator, val.denominator
207231 elif hasattr(val, 'numer'):
208232 (numer, denom) = (int(val.numer()), int(val.denom()))
209233 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"
211235 % type(val).__name__)
212236 frac = fractions.Fraction(str(val))
213237 numer, denom = frac.numerator, frac.denominator
341365 """
342366 Warn if the object name is not explicitly set.
343367 """
344 changed_params = dict(self.param.get_param_values(onlychanged=True))
368 changed_params = self.param.values(onlychanged=True)
345369 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.")
348372
349373 def _hash_and_seed(self):
350374 """
414438 self._hash_and_seed()
415439
416440
417
418441 class UniformRandom(RandomDistribution):
419442 """
420443 Specified with lbound and ubound; when called, return a random
0 from __future__ import print_function
01 """
12 Parameters are a kind of class attribute allowing special behavior,
23 including dynamically generated parameter values, documentation
2728 Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides,
2829 descendents, get_logger, instance_descriptor, basestring)
2930
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
3234 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
3436
3537 from collections import OrderedDict
3638 from numbers import Real
4042 # only two required files.
4143 try:
4244 from .version import Version
43 __version__ = str(Version(fpath=__file__, archive_commit="9123ba0", reponame="param"))
45 __version__ = str(Version(fpath=__file__, archive_commit="d490aa95", reponame="param"))
4446 except:
4547 __version__ = "0.0.0+unknown"
4648
6870
6971
7072 # 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.
7274 random_seed = 42
7375
7476
220222 supplied parameters, inheriting from the specified base(s).
221223 """
222224 if not (isinstance(bases, list) or isinstance(bases, tuple)):
223 bases=[bases]
225 bases=[bases]
224226 return type(name, tuple(bases), params)
225227
226228
467469 raise StopIteration
468470 return self._time
469471
470 # For Python 2 compatibility; can be removed for Python 3.
472 # PARAM2_DEPRECATION: For Python 2 compatibility; can be removed for Python 3.
471473 next = __next__
472474
473475 def __call__(self, val=None, time_type=None):
563565 time_fn = Time()
564566 time_dependent = False
565567
566 # CBENHANCEMENT: Add an 'epsilon' slot.
567 # See email 'Re: simulation-time-controlled Dynamic parameters'
568 # Dec 22, 2007 CB->JAB
569
570568 def __init__(self,**params):
571569 """
572570 Call the superclass's __init__ and set instantiate=True if the
583581 """
584582 Add 'last time' and 'last value' attributes to the generator.
585583 """
586 # CEBALERT: use a dictionary to hold these things.
584 # Could use a dictionary to hold these things.
587585 if hasattr(obj,"_Dynamic_time_fn"):
588586 gen._Dynamic_time_fn = obj._Dynamic_time_fn
589587
590588 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
592590 # number with None (e.g. 1>None but FixedPoint(1)>None can't be done)
593591 gen._Dynamic_time = -1
594592
702700
703701 def identity_hook(obj,val): return val
704702
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)
705731
706732
707733 class Number(Dynamic):
749775
750776 """
751777
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,
755781 inclusive_bounds=(True,True), step=None, **params):
756782 """
757783 Initialize this parameter object and store the bounds.
758784
759785 Non-dynamic default values are checked against the bounds.
760786 """
761 super(Number,self).__init__(default=default,**params)
787 super(Number,self).__init__(default=default, **params)
762788
763789 self.set_hook = identity_hook
764790 self.bounds = bounds
765791 self.inclusive_bounds = inclusive_bounds
766 self._softbounds = softbounds
792 self.softbounds = softbounds
767793 self.step = step
768794 self._validate(default)
769795
770
771 def __get__(self,obj,objtype):
796 def __get__(self, obj, objtype):
772797 """
773798 Same as the superclass's __get__, but if the value was
774799 dynamically generated, check the bounds.
775800 """
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)
781807 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
791808
792809 def set_in_bounds(self,obj,val):
793810 """
799816 bounded_val = self.crop_to_bounds(val)
800817 else:
801818 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):
808822 """
809823 Return the given value cropped to be within the hard bounds
810824 for this parameter.
815829 returned value could be None. If a non-numeric value is passed
816830 in, set to be the default value (which could be None). In no
817831 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.
822840 if _is_number(val):
823841 if self.bounds is None:
824842 return val
836854
837855 else:
838856 # non-numeric value sent in: reverts to default value
839 return self.default
857 return self.default
840858
841859 return val
842860
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 parameter can only be None or a "
897 "numeric value, not type %r." % type(step))
868898
869899 def _validate(self, val):
870900 """
871901 Checks that the value is numeric and that it is within the hard
872902 bounds; if not, an exception is raised.
873903 """
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)
888907
889908 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)
913910
914911 def __setstate__(self,state):
915912 if 'step' not in state:
916913 state['step'] = None
917914
918 super(Number,self).__setstate__(state)
915 super(Number, self).__setstate__(state)
919916
920917
921918
922919 class Integer(Number):
923920 """Numeric Parameter required to be an Integer"""
924921
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 parameter can only be None or an "
939 "integer value, not type %r" % type(step))
942940
943941
944942
945943 class Magnitude(Number):
946944 """Numeric Parameter required to be in the range [0.0-1.0]."""
947945
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)
950948
951949
952950
955953
956954 __slots__ = ['bounds']
957955
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):
960959 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))
978971
979972
980973
983976
984977 __slots__ = ['length']
985978
986 def __init__(self,default=(0,0),length=None,**params):
979 def __init__(self, default=(0,0), length=None, **params):
987980 """
988981 Initialize a tuple parameter with a fixed length (number of
989982 elements). The length is determined by the initial default
990983 value, if any, and must be supplied explicitly otherwise. The
991984 length is not allowed to change after instantiation.
992985 """
993 super(Tuple,self).__init__(default=default,**params)
986 super(Tuple,self).__init__(default=default, **params)
994987 if length is None and default is not None:
995988 self.length = len(default)
996989 elif length is None and default is None:
1000993 self.length = length
1001994 self._validate(default)
1002995
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))
10031012
10041013 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 return list(value) # As JSON has no tuple representation
1020
1021 @classmethod
1022 def deserialize(cls, value):
1023 return tuple(value) # As JSON has no tuple representation
10151024
10161025
10171026 class NumericTuple(Tuple):
10181027 """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length."""
10191028
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
1029 def _validate_value(self, val, allow_None):
1030 super(NumericTuple, self)._validate_value(val, allow_None)
1031 if allow_None and val is None:
1032 return
1033 for n in val:
1034 if _is_number(n):
1035 continue
1036 raise ValueError("NumericTuple parameter %r only takes numeric "
1037 "values, not type %r." % (self.name, type(n)))
10281038
10291039
10301040 class XYCoordinates(NumericTuple):
10311041 """A NumericTuple for an X,Y coordinate."""
10321042
1033 def __init__(self,default=(0.0,0.0),**params):
1034 super(XYCoordinates,self).__init__(default=default,length=2,**params)
1035
1043 def __init__(self, default=(0.0, 0.0), **params):
1044 super(XYCoordinates,self).__init__(default=default, length=2, **params)
10361045
10371046
10381047 class Callable(Parameter):
10451054 2.4, so instantiate must be False for those values.
10461055 """
10471056
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
1057 def _validate_value(self, val, allow_None):
1058 if (allow_None and val is None) or callable(val):
1059 return
1060
1061 raise ValueError("Callable parameter %r only takes a callable object, "
1062 "not objects of type %r." % (self.name, type(val)))
10531063
10541064
10551065 class Action(Callable):
10671077 return False
10681078
10691079
1070
1071 # CEBALERT: this should be a method of ClassSelector.
1080 # Could be a method of ClassSelector.
10721081 def concrete_descendents(parentclass):
10731082 """
10741083 Return a dictionary containing all subclasses of the specified
10811090 """
10821091 return dict((c.__name__,c) for c in descendents(parentclass)
10831092 if not _is_abstract(c))
1084
10851093
10861094
10871095 class Composite(Parameter):
10941102 in the order specified. Likewise, setting the parameter takes a
10951103 sequence of values and sets the value of the constituent
10961104 attributes.
1097 """
1098
1099 __slots__=['attribs','objtype']
1100
1101 def __init__(self,attribs=None,**kw):
1105
1106 This Parameter type has not been tested with watchers and
1107 dependencies, and may not support them properly.
1108 """
1109
1110 __slots__ = ['attribs', 'objtype']
1111
1112 def __init__(self, attribs=None, **kw):
11021113 if attribs is None:
11031114 attribs = []
1104 super(Composite,self).__init__(default=None,**kw)
1115 super(Composite, self).__init__(default=None, **kw)
11051116 self.attribs = attribs
11061117
1107 def __get__(self,obj,objtype):
1118 def __get__(self, obj, objtype):
11081119 """
11091120 Return the values of all the attribs, as a list.
11101121 """
11111122 if obj is None:
1112 return [getattr(objtype,a) for a in self.attribs]
1123 return [getattr(objtype, a) for a in self.attribs]
11131124 else:
1114 return [getattr(obj,a) for a in self.attribs]
1125 return [getattr(obj, a) for a in self.attribs]
1126
1127 def _validate_attribs(self, val, attribs):
1128 if len(val) == len(attribs):
1129 return
1130 raise ValueError("Compound parameter %r got the wrong number "
1131 "of values (needed %d, but got %d)." %
1132 (self.name, len(attribs), len(val)))
11151133
11161134 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))
1135 self._validate_attribs(val, self.attribs)
11181136
11191137 def _post_setter(self, obj, val):
11201138 if obj is None:
1121 for a,v in zip(self.attribs,val):
1122 setattr(self.objtype,a,v)
1139 for a, v in zip(self.attribs, val):
1140 setattr(self.objtype, a, v)
11231141 else:
1124 for a,v in zip(self.attribs,val):
1125 setattr(obj,a,v)
1142 for a, v in zip(self.attribs, val):
1143 setattr(obj, a, v)
11261144
11271145
11281146 class SelectorBase(Parameter):
11381156 raise NotImplementedError("get_range() must be implemented in subclasses.")
11391157
11401158
1141 class ObjectSelector(SelectorBase):
1159 class Selector(SelectorBase):
11421160 """
11431161 Parameter whose value must be one object from a list of possible objects.
1162
1163 By default, if no default is specified, picks the first object from
1164 the provided set of objects, as long as the objects are in an
1165 ordered data collection.
11441166
11451167 check_on_set restricts the value to be among the current list of
11461168 objects. By default, if objects are initially supplied,
11611183 up from the object value.
11621184 """
11631185
1164 __slots__ = ['objects','compute_default_fn','check_on_set','names']
1165
1166 # ObjectSelector is usually used to allow selection from a list of
1186 __slots__ = ['objects', 'compute_default_fn', 'check_on_set', 'names']
1187
1188 # Selector is usually used to allow selection from a list of
11671189 # 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):
1190 def __init__(self, objects=None, default=None, instantiate=False,
1191 compute_default_fn=None, check_on_set=None,
1192 allow_None=None, empty_default=False, **params):
1193
1194 autodefault = None
1195 if objects:
1196 if is_ordered_dict(objects):
1197 autodefault = list(objects.values())[0]
1198 elif isinstance(objects, dict):
1199 main.param.warning("Parameter default value is arbitrary due to "
1200 "dictionaries prior to Python 3.6 not being "
1201 "ordered; should use an ordered dict or "
1202 "supply an explicit default value.")
1203 autodefault = list(objects.values())[0]
1204 elif isinstance(objects, list):
1205 autodefault = objects[0]
1206
1207 default = autodefault if (not empty_default and default is None) else default
1208
11701209 if objects is None:
11711210 objects = []
11721211 if isinstance(objects, collections_abc.Mapping):
11781217 self.compute_default_fn = compute_default_fn
11791218
11801219 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
1220 self.check_on_set = check_on_set
1221 elif len(objects) == 0:
1222 self.check_on_set = False
11841223 else:
1185 self.check_on_set=True
1186
1187 super(ObjectSelector,self).__init__(default=default,instantiate=instantiate,
1188 **params)
1224 self.check_on_set = True
1225
1226 super(Selector,self).__init__(
1227 default=default, instantiate=instantiate, **params)
11891228 # Required as Parameter sets allow_None=True if default is None
11901229 self.allow_None = allow_None
11911230 if default is not None and self.check_on_set is True:
11921231 self._validate(default)
11931232
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.
1233 # Note that if the list of objects is changed, the current value for
1234 # this parameter in existing POs could be outside of the new range.
11971235
11981236 def compute_default(self):
11991237 """
12041242 no longer None).
12051243 """
12061244 if self.default is None and callable(self.compute_default_fn):
1207 self.default=self.compute_default_fn()
1245 self.default = self.compute_default_fn()
12081246 if self.default not in self.objects:
12091247 self.objects.append(self.default)
12101248
1211
12121249 def _validate(self, val):
12131250 """
12141251 val must be None or one of the objects in self.objects.
12181255 return
12191256
12201257 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:
1258 # This method can be called before __init__ has called
1259 # super's __init__, so there may not be any name set yet.
1260 if (hasattr(self, "name") and self.name):
1261 attrib_name = " " + self.name
1262 else:
12261263 attrib_name = ""
12271264
12281265 items = []
12371274 limiter = ', ...]'
12381275 break
12391276 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))
1277 raise ValueError("%s not in parameter%s's list of possible objects, "
1278 "valid options include %s" % (val, attrib_name, items))
12421279
12431280 def _ensure_value_is_in_objects(self,val):
12441281 """
12471284 to check each item instead.
12481285 """
12491286 if not (val in self.objects):
1250 self.objects.append(val)
1287 self.objects.append(val)
12511288
12521289 def get_range(self):
12531290 """
12581295 return named_objs(self.objects, self.names)
12591296
12601297
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)
1298 class ObjectSelector(Selector):
1299 """
1300 Deprecated. Same as Selector, but with a different constructor for
1301 historical reasons.
1302 """
1303 def __init__(self, default=None, objects=None, **kwargs):
1304 super(ObjectSelector,self).__init__(objects=objects, default=default,
1305 empty_default=True, **kwargs)
1306
12911307
12921308 class ClassSelector(SelectorBase):
12931309 """
12971313 for is_instance=True.
12981314 """
12991315
1300 __slots__ = ['class_','is_instance']
1316 __slots__ = ['class_', 'is_instance']
13011317
13021318 def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params):
13031319 self.class_ = class_
13051321 super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params)
13061322 self._validate(default)
13071323
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_))
1324 def _validate(self, val):
1325 super(ClassSelector, self)._validate(val)
1326 self._validate_class_(val, self.class_, self.is_instance)
1327
1328 def _validate_class_(self, val, class_, is_instance):
1329 if (val is None and self.allow_None):
1330 return
1331 if isinstance(class_, tuple):
1332 class_name = ('(%s)' % ', '.join(cl.__name__ for cl in class_))
13131333 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):
1334 class_name = class_.__name__
1335 param_cls = self.__class__.__name__
1336 if is_instance:
1337 if not (isinstance(val, class_)):
13171338 raise ValueError(
1318 "Parameter '%s' value must be an instance of %s, not '%s'" %
1319 (self.name, class_name, val))
1339 "%s parameter %r value must be an instance of %s, not %r." %
1340 (param_cls, self.name, class_name, val))
13201341 else:
1321 if not (val is None and self.allow_None) and not (issubclass(val,self.class_)):
1342 if not (issubclass(val, class_)):
13221343 raise ValueError(
1323 "Parameter '%s' must be a subclass of %s, not '%s'" %
1324 (val.__name__, class_name, val.__class__.__name__))
1325
1344 "%s parameter %r must be a subclass of %s, not %r." %
1345 (param_cls, self.name, class_name, val.__name__))
13261346
13271347 def get_range(self):
13281348 """
13291349 Return the possible types for this parameter's value.
13301350
1331 (I.e. return {name: <class>} for all classes that are
1332 concrete_descendents() of self.class_.)
1351 (I.e. return `{name: <class>}` for all classes that are
1352 concrete_descendents() of `self.class_`.)
13331353
13341354 Only classes from modules that have been imported are added
13351355 (see concrete_descendents()).
13381358 all_classes = {}
13391359 for cls in classes:
13401360 all_classes.update(concrete_descendents(cls))
1341 d=OrderedDict((name,class_) for name,class_ in all_classes.items())
1361 d = OrderedDict((name, class_) for name,class_ in all_classes.items())
13421362 if self.allow_None:
1343 d['None']=None
1363 d['None'] = None
13441364 return d
13451365
13461366
13491369 Parameter whose value is a list of objects, usually of a specified type.
13501370
13511371 The bounds allow a minimum and/or maximum length of
1352 list to be enforced. If the class is non-None, all
1372 list to be enforced. If the item_type is non-None, all
13531373 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_
1374
1375 `class_` is accepted as an alias for `item_type`, but is
1376 deprecated due to conflict with how the `class_` slot is
1377 used in Selector classes.
1378 """
1379
1380 __slots__ = ['bounds', 'item_type', 'class_']
1381
1382 def __init__(self, default=[], class_=None, item_type=None,
1383 instantiate=True, bounds=(0, None), **params):
1384 self.item_type = item_type or class_
1385 self.class_ = self.item_type
13611386 self.bounds = bounds
1362 Parameter.__init__(self,default=default,instantiate=instantiate,
1387 Parameter.__init__(self, default=default, instantiate=instantiate,
13631388 **params)
13641389 self._validate(default)
13651390
13661391 def _validate(self, val):
13671392 """
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
1393 Checks that the value is numeric and that it is within the hard
1394 bounds; if not, an exception is raised.
1395 """
1396 self._validate_value(val, self.allow_None)
1397 self._validate_bounds(val, self.bounds)
1398 self._validate_item_type(val, self.item_type)
1399
1400 def _validate_bounds(self, val, bounds):
1401 "Checks that the list is of the right length and has the right contents."
1402 if bounds is None or (val is None and self.allow_None):
1403 return
1404 min_length, max_length = bounds
1405 l = len(val)
1406 if min_length is not None and max_length is not None:
1407 if not (min_length <= l <= max_length):
1408 raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length))
1409 elif min_length is not None:
1410 if not min_length <= l:
1411 raise ValueError("%s: list length must be at least %s."
1412 % (self.name, min_length))
1413 elif max_length is not None:
1414 if not l <= max_length:
1415 raise ValueError("%s: list length must be at most %s."
1416 % (self.name, max_length))
1417
1418 def _validate_value(self, val, allow_None):
1419 if allow_None and val is None:
1420 return
13741421 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
1422 raise ValueError("List parameter %r must be a list, not an object of type %s."
1423 % (self.name, type(val)))
1424
1425 def _validate_item_type(self, val, item_type):
1426 if item_type is None or (self.allow_None and val is None):
1427 return
1428 for v in val:
1429 if isinstance(v, item_type):
1430 continue
1431 raise TypeError("List parameter %r items must be instances "
1432 "of type %r, not %r." % (self.name, item_type, val))
13971433
13981434
13991435 class HookList(List):
14041440 for users to register a set of commands to be called at a
14051441 specified place in some sequence of processing steps.
14061442 """
1407 __slots__ = ['class_','bounds']
1408
1409 def _check_type(self,val):
1443 __slots__ = ['class_', 'bounds']
1444
1445 def _validate_value(self, val, allow_None):
1446 super(HookList, self)._validate_value(val, allow_None)
1447 if allow_None and val is None:
1448 return
14101449 for v in val:
1411 assert callable(v),repr(self.name)+": "+repr(v)+" is not callable."
1412
1450 if callable(v):
1451 continue
1452 raise ValueError("HookList parameter %r items must be callable, "
1453 "not %r." % (self.name, v))
14131454
14141455
14151456 class Dict(ClassSelector):
14161457 """
14171458 Parameter whose value is a dictionary.
14181459 """
1460
14191461 def __init__(self, default=None, **params):
1420 super(Dict,self).__init__(dict, default=default, **params)
1462 super(Dict, self).__init__(dict, default=default, **params)
14211463
14221464
14231465 class Array(ClassSelector):
14261468 """
14271469
14281470 def __init__(self, default=None, **params):
1429 # CEBALERT: instead use python array as default?
14301471 from numpy import ndarray
1431 super(Array,self).__init__(ndarray, allow_None=True, default=default, **params)
1472 super(Array, self).__init__(ndarray, allow_None=True, default=default, **params)
1473
1474 @classmethod
1475 def serialize(cls, value):
1476 return value.tolist()
1477
1478 @classmethod
1479 def deserialize(cls, value):
1480 from numpy import asarray
1481 return asarray(value)
14321482
14331483
14341484 class DataFrame(ClassSelector):
14491499 if a list is given, the supplied DataFrame must contain exactly the
14501500 same columns and in the same order and no other columns.
14511501 """
1452 __slots__ = ['rows','columns', 'ordered']
1502
1503 __slots__ = ['rows', 'columns', 'ordered']
14531504
14541505 def __init__(self, default=None, rows=None, columns=None, ordered=None, **params):
14551506 from pandas import DataFrame as pdDFrame
14561507 self.rows = rows
14571508 self.columns = columns
14581509 self.ordered = ordered
1459 super(DataFrame,self).__init__(pdDFrame, default=default, allow_None=True, **params)
1510 super(DataFrame,self).__init__(pdDFrame, default=default, **params)
14601511 self._validate(self.default)
1461
14621512
14631513 def _length_bounds_check(self, bounds, length, name):
14641514 message = '{name} length {length} does not match declared bounds of {bounds}'
14781528
14791529 if isinstance(self.columns, set) and self.ordered is True:
14801530 raise ValueError('Columns cannot be ordered when specified as a set')
1531
1532 if self.allow_None and val is None:
1533 return
14811534
14821535 if self.columns is None:
14831536 pass
15011554 if self.rows is not None:
15021555 self._length_bounds_check(self.rows, len(val), 'Row')
15031556
1557 @classmethod
1558 def serialize(cls, value):
1559 return value.to_dict('records')
1560
1561 @classmethod
1562 def deserialize(cls, value):
1563 from pandas import DataFrame as pdDFrame
1564 return pdDFrame(value)
1565
15041566
15051567 class Series(ClassSelector):
15061568 """
15101572 which may be a number or an integer bounds tuple to constrain the
15111573 allowable number of rows.
15121574 """
1575
15131576 __slots__ = ['rows']
1577
1578 def __init__(self, default=None, rows=None, allow_None=False, **params):
1579 from pandas import Series as pdSeries
1580 self.rows = rows
1581 super(Series,self).__init__(pdSeries, default=default, allow_None=allow_None,
1582 **params)
1583 self._validate(self.default)
15141584
15151585 def _length_bounds_check(self, bounds, length, name):
15161586 message = '{name} length {length} does not match declared bounds of {bounds}'
15251595 if failure:
15261596 raise ValueError(message.format(name=name,length=length, bounds=bounds))
15271597
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
15341598 def _validate(self, val):
15351599 super(Series, self)._validate(val)
15361600
1601 if self.allow_None and val is None:
1602 return
1603
15371604 if self.rows is not None:
15381605 self._length_bounds_check(self.rows, len(val), 'Row')
15391606
15411608
15421609 # For portable code:
15431610 # - 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,
1611 # - use resolve_path(path_to_file=True) for paths to existing files to be read,
1612 # - use resolve_path(path_to_file=False) for paths to existing folders to be read,
15461613 # and normalize_path() for paths to new files to be written.
15471614
15481615 class resolve_path(ParameterizedFunction):
15661633 Prepended to a non-relative path, in order, until a file is
15671634 found.""")
15681635
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'.""")
1636 path_to_file = Boolean(default=True, pickle_default_value=False,
1637 allow_None=True, doc="""
1638 String specifying whether the path refers to a 'File' or a
1639 'Folder'. If None, the path may point to *either* a 'File' *or*
1640 a 'Folder'.""")
15711641
15721642 def __call__(self, path, **params):
15731643 p = ParamOverrides(self, params)
1574
15751644 path = os.path.normpath(path)
1645 ftype = "File" if p.path_to_file is True \
1646 else "Folder" if p.path_to_file is False else "Path"
1647
1648 if not p.search_paths:
1649 p.search_paths = [os.getcwd()]
15761650
15771651 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)
1652 if ((p.path_to_file is None and os.path.exists(path)) or
1653 (p.path_to_file is True and os.path.isfile(path)) or
1654 (p.path_to_file is False and os.path.isdir( path))):
1655 return path
1656 raise IOError("%s '%s' not found." % (ftype,path))
15901657
15911658 else:
15921659 paths_tried = []
15931660 for prefix in p.search_paths:
15941661 try_path = os.path.join(os.path.normpath(prefix), path)
15951662
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)
1663 if ((p.path_to_file is None and os.path.exists(try_path)) or
1664 (p.path_to_file is True and os.path.isfile(try_path)) or
1665 (p.path_to_file is False and os.path.isdir( try_path))):
1666 return try_path
16041667
16051668 paths_tried.append(try_path)
16061669
1607 raise IOError(os.path.split(path)[1] + " was not found in the following place(s): " + str(paths_tried) + ".")
1670 raise IOError(ftype + " " + os.path.split(path)[1] + " was not found in the following place(s): " + str(paths_tried) + ".")
16081671
16091672
16101673 class normalize_path(ParameterizedFunction):
16451708 The specified path can be absolute, or relative to either:
16461709
16471710 * any of the paths specified in the search_paths attribute (if
1648 search_paths is not None);
1711 search_paths is not None);
16491712
16501713 or
16511714
16631726 super(Path,self).__init__(default,**params)
16641727
16651728 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)
1729 return resolve_path(path, path_to_file=None, search_paths=self.search_paths)
16701730
16711731 def _validate(self, val):
16721732 if val is None:
16731733 if not self.allow_None:
1674 Parameterized(name="%s.%s"%(self.owner.name,self.name)).warning('None is not allowed')
1734 Parameterized(name="%s.%s"%(self.owner.name,self.name)).param.warning('None is not allowed')
16751735 else:
16761736 try:
16771737 self._resolve(val)
16781738 except IOError as e:
1679 Parameterized(name="%s.%s"%(self.owner.name,self.name)).warning('%s',e.args[0])
1739 Parameterized(name="%s.%s"%(self.owner.name,self.name)).param.warning('%s',e.args[0])
16801740
16811741 def __get__(self, obj, objtype):
16821742 """
17071767
17081768 * any of the paths specified in the search_paths attribute (if
17091769 search_paths is not None);
1770
17101771 or
17111772
17121773 * any of the paths searched by resolve_path() (if search_paths
17141775 """
17151776
17161777 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)
1778 return resolve_path(path, path_to_file=True, search_paths=self.search_paths)
17211779
17221780
17231781 class Foldername(Path):
17311789
17321790 * any of the paths specified in the search_paths attribute (if
17331791 search_paths is not None);
1792
17341793 or
17351794
17361795 * any of the paths searched by resolve_dir_path() (if search_paths
17381797 """
17391798
17401799 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)
1800 return resolve_path(path, path_to_file=False, search_paths=self.search_paths)
17451801
17461802
17471803
17571813
17581814
17591815
1760 class FileSelector(ObjectSelector):
1816 class FileSelector(Selector):
17611817 """
17621818 Given a path glob, allows one file to be selected from those matching.
17631819 """
17641820 __slots__ = ['path']
17651821
17661822 def __init__(self, default=None, path="", **kwargs):
1767 super(FileSelector, self).__init__(default, **kwargs)
1823 self.default = default
17681824 self.path = path
17691825 self.update()
1826 super(FileSelector, self).__init__(default=default, objects=self.objects,
1827 empty_default=True, **kwargs)
1828
1829 def _on_set(self, attribute, old, new):
1830 super(FileSelector, self)._on_set(attribute, new, old)
1831 if attribute == 'path':
1832 self.update()
17701833
17711834 def update(self):
17721835 self.objects = sorted(glob.glob(self.path))
17781841 return abbreviate_paths(self.path,super(FileSelector, self).get_range())
17791842
17801843
1781 class ListSelector(ObjectSelector):
1782 """
1783 Variant of ObjectSelector where the value can be multiple objects from
1844 class ListSelector(Selector):
1845 """
1846 Variant of Selector where the value can be multiple objects from
17841847 a list of possible objects.
17851848 """
1849
1850 def __init__(self, default=None, objects=None, **kwargs):
1851 super(ListSelector,self).__init__(
1852 objects=objects, default=default, empty_default=True, **kwargs)
17861853
17871854 def compute_default(self):
17881855 if self.default is None and callable(self.compute_default_fn):
17921859 self.objects.append(o)
17931860
17941861 def _validate(self, val):
1862 if (val is None and self.allow_None):
1863 return
17951864 for o in val:
17961865 super(ListSelector, self)._validate(o)
17971866
18041873 __slots__ = ['path']
18051874
18061875 def __init__(self, default=None, path="", **kwargs):
1807 super(MultiFileSelector, self).__init__(default, **kwargs)
1876 self.default = default
18081877 self.path = path
18091878 self.update()
1879 super(MultiFileSelector, self).__init__(default=default, objects=self.objects, **kwargs)
1880
1881 def _on_set(self, attribute, old, new):
1882 super(MultiFileSelector, self)._on_set(attribute, new, old)
1883 if attribute == 'path':
1884 self.update()
18101885
18111886 def update(self):
18121887 self.objects = sorted(glob.glob(self.path))
18261901 def __init__(self, default=None, **kwargs):
18271902 super(Date, self).__init__(default=default, **kwargs)
18281903
1829 def _validate(self, val):
1904 def _validate_value(self, val, allow_None):
18301905 """
18311906 Checks that the value is numeric and that it is within the hard
18321907 bounds; if not, an exception is raised.
18341909 if self.allow_None and val is None:
18351910 return
18361911
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):
1912 if not isinstance(val, dt_types) and not (allow_None and val is None):
1913 raise ValueError("Date parameter %r only takes datetime and date types." % self.name)
1914
1915 def _validate_step(self, val, step):
1916 if step is not None and not isinstance(step, dt_types):
18411917 raise ValueError("Step parameter can only be None, a datetime or datetime type")
18421918
1843 self._checkBounds(val)
1919 @classmethod
1920 def serialize(cls, value):
1921 if not isinstance(value, (dt.datetime, dt.date)): # i.e np.datetime64
1922 value = value.astype(dt.datetime)
1923 return value.strftime("%Y-%m-%dT%H:%M:%S.%f")
1924
1925 @classmethod
1926 def deserialize(cls, value):
1927 return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
18441928
18451929
18461930 class CalendarDate(Number):
18511935 def __init__(self, default=None, **kwargs):
18521936 super(CalendarDate, self).__init__(default=default, **kwargs)
18531937
1854 def _validate(self, val):
1938 def _validate_value(self, val, allow_None):
18551939 """
18561940 Checks that the value is numeric and that it is within the hard
18571941 bounds; if not, an exception is raised.
18591943 if self.allow_None and val is None:
18601944 return
18611945
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)
1946 if (not isinstance(val, dt.date) or isinstance(val, dt.datetime)) and not (allow_None and val is None):
1947 raise ValueError("CalendarDate parameter %r only takes date types." % self.name)
1948
1949 def _validate_step(self, val, step):
1950 if step is not None and not isinstance(step, dt.date):
1951 raise ValueError("Step parameter can only be None or a date type.")
1952
1953 @classmethod
1954 def serialize(cls, value):
1955 return value.strftime("%Y-%m-%d")
1956
1957 @classmethod
1958 def deserialize(cls, value):
1959 return dt.datetime.strptime(value, "%Y-%m-%d").date()
18691960
18701961
18711962 class Color(Parameter):
18721963 """
18731964 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):
1965 prefix or (optionally) as a CSS3 color name.
1966 """
1967
1968 # CSS3 color specification https://www.w3.org/TR/css-color-3/#svg-color
1969 _named_colors = [ 'aliceblue', 'antiquewhite', 'aqua',
1970 'aquamarine', 'azure', 'beige', 'bisque', 'black',
1971 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood',
1972 'cadetblue', 'chartreuse', 'chocolate', 'coral',
1973 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue',
1974 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey',
1975 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen',
1976 'darkorange', 'darkorchid', 'darkred', 'darksalmon',
1977 'darkseagreen', 'darkslateblue', 'darkslategray',
1978 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
1979 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue',
1980 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',
1981 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray',
1982 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink',
1983 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
1984 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue',
1985 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
1986 'lightgray', 'lightgrey', 'lightgreen', 'lightpink',
1987 'lightsalmon', 'lightseagreen', 'lightskyblue',
1988 'lightslategray', 'lightslategrey', 'lightsteelblue',
1989 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta',
1990 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid',
1991 'mediumpurple', 'mediumseagreen', 'mediumslateblue',
1992 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred',
1993 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
1994 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab',
1995 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen',
1996 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff',
1997 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
1998 'rosybrown', 'royalblue', 'saddlebrown', 'salmon',
1999 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver',
2000 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow',
2001 'springgreen', 'steelblue', 'tan', 'teal', 'thistle',
2002 'tomato', 'turquoise', 'violet', 'wheat', 'white',
2003 'whitesmoke', 'yellow', 'yellowgreen']
2004
2005 __slots__ = ['allow_named']
2006
2007 def __init__(self, default=None, allow_named=True, **kwargs):
18782008 super(Color, self).__init__(default=default, **kwargs)
2009 self.allow_named = allow_named
18792010 self._validate(default)
18802011
18812012 def _validate(self, val):
1882 if (self.allow_None and val is None):
2013 self._validate_value(val, self.allow_None)
2014 self._validate_allow_named(val, self.allow_named)
2015
2016 def _validate_value(self, val, allow_None):
2017 if (allow_None and val is None):
18832018 return
18842019 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
2020 raise ValueError("Color parameter %r expects a string value, "
2021 "not an object of type %s." % (self.name, type(val)))
2022
2023 def _validate_allow_named(self, val, allow_named):
2024 if (val is None and self.allow_None):
2025 return
2026 is_hex = re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val)
2027 if self.allow_named:
2028 if not is_hex and val not in self._named_colors:
2029 raise ValueError("Color '%s' only takes RGB hex codes "
2030 "or named colors, received '%s'." % (self.name, val))
2031 elif not is_hex:
2032 raise ValueError("Color '%s' only accepts valid RGB hex "
2033 "codes, received '%s'." % (self.name, val))
18902034
18912035
18922036 class Range(NumericTuple):
1893 "A numeric range with optional bounds and softbounds"
1894
1895 __slots__ = ['bounds', 'inclusive_bounds', 'softbounds']
1896
2037 """
2038 A numeric range with optional bounds and softbounds.
2039 """
2040
2041 __slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step']
18972042
18982043 def __init__(self,default=None, bounds=None, softbounds=None,
1899 inclusive_bounds=(True,True), **params):
2044 inclusive_bounds=(True,True), step=None, **params):
19002045 self.bounds = bounds
19012046 self.inclusive_bounds = inclusive_bounds
19022047 self.softbounds = softbounds
2048 self.step = step
19032049 super(Range,self).__init__(default=default,length=2,**params)
19042050
1905
19062051 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
19132052 super(Range, self)._validate(val)
1914
1915 self._checkBounds(val)
2053 self._validate_bounds(val, self.bounds, self.inclusive_bounds)
2054
2055 def _validate_bounds(self, val, bounds, inclusive_bounds):
2056 if bounds is None or (val is None and self.allow_None):
2057 return
2058 vmin, vmax = bounds
2059 incmin, incmax = inclusive_bounds
2060 for bound, v in zip(['lower', 'upper'], val):
2061 too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin)
2062 too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax)
2063 if too_low or too_high:
2064 raise ValueError("Range parameter %r's %s bound must be in range %s."
2065 % (self.name, bound, self.rangestr()))
19162066
19172067
19182068 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)
2069 return get_soft_bounds(self.bounds, self.softbounds)
19412070
19422071
19432072 def rangestr(self):
19482077 return '%s%s, %s%s' % (incmin, vmin, vmax, incmax)
19492078
19502079
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
19632080 class DateRange(Range):
19642081 """
19652082 A datetime or date range specified as (start, end).
19662083
19672084 Bounds must be specified as datetime or date types (see param.dt_types).
19682085 """
1969 def _validate(self, val):
1970 if self.allow_None and val is None:
2086
2087 def _validate_value(self, val, allow_None):
2088 if allow_None and val is None:
19712089 return
19722090
19732091 for n in val:
1974 if not isinstance(n, dt_types):
1975 raise ValueError("DateRange '%s' only takes datetime types: %s"%(self.name,val))
2092 if isinstance(n, dt_types):
2093 continue
2094 raise ValueError("DateRange parameter %r only takes datetime "
2095 "types, not %s." % (self.name, val))
19762096
19772097 start, end = val
19782098 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)
2099 raise ValueError("DateRange parameter %r's end datetime %s "
2100 "is before start datetime %s." %
2101 (self.name,val[1],val[0]))
2102
19852103
19862104
19872105 class CalendarDateRange(Range):
19882106 """
19892107 A date range specified as (start_date, end_date).
19902108 """
1991 def _validate(self, val):
1992 if self.allow_None and val is None:
2109 def _validate_value(self, val, allow_None):
2110 if allow_None and val is None:
19932111 return
19942112
19952113 for n in val:
19962114 if not isinstance(n, dt.date):
1997 raise ValueError("CalendarDateRange '%s' only takes date types: %s"%(self.name,val))
2115 raise ValueError("CalendarDateRange parameter %r only "
2116 "takes date types, not %s." % (self.name, val))
19982117
19992118 start, end = val
20002119 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)
2120 raise ValueError("CalendarDateRange parameter %r's end date "
2121 "%s is before start date %s." %
2122 (self.name, val[1], val[0]))
2123
2124
2125 class Event(Boolean):
2126 """
2127 An Event Parameter is one whose value is intimately linked to the
2128 triggering of events for watchers to consume. Event has a Boolean
2129 value, which when set to True triggers the associated watchers (as
2130 any Parameter does) and then is automatically set back to
2131 False. Conversely, if events are triggered directly via `.trigger`,
2132 the value is transiently set to True (so that it's clear which of
2133 many parameters being watched may have changed), then restored to
2134 False when the triggering completes. An Event parameter is thus like
2135 a momentary switch or pushbutton with a transient True value that
2136 serves only to launch some other action (e.g. via a param.depends
2137 decorator), rather than encapsulating the action itself as
2138 param.Action does.
2139 """
2140
2141 # _autotrigger_value specifies the value used to set the parameter
2142 # to when the parameter is supplied to the trigger method. This
2143 # value change is then what triggers the watcher callbacks.
2144 __slots__ = ['_autotrigger_value', '_mode', '_autotrigger_reset_value']
2145
2146 def __init__(self,default=False,bounds=(0,1),**params):
2147 self._autotrigger_value = True
2148 self._autotrigger_reset_value = False
2149 self._mode = 'set-reset'
2150 # Mode can be one of 'set', 'set-reset' or 'reset'
2151
2152 # 'set' is normal Boolean parameter behavior when set with a value.
2153 # 'set-reset' temporarily sets the parameter (which triggers
2154 # watching callbacks) but immediately resets the value back to
2155 # False.
2156 # 'reset' applies the reset from True to False without
2157 # triggering watched callbacks
2158
2159 # This _mode attribute is one of the few places where a specific
2160 # parameter has a special behavior that is relied upon by the
2161 # core functionality implemented in
2162 # parameterized.py. Specifically, the set_param method
2163 # temporarily sets this attribute in order to disable resetting
2164 # back to False while triggered callbacks are executing
2165 super(Event, self).__init__(default=default,**params)
2166
2167 def _reset_event(self, obj, val):
2168 val = False
2169 if obj is None:
2170 self.default = val
2171 else:
2172 obj.__dict__[self._internal_name] = val
2173 self._post_setter(obj, val)
2174
2175 @instance_descriptor
2176 def __set__(self, obj, val):
2177 if self._mode in ['set-reset', 'set']:
2178 super(Event, self).__set__(obj, val)
2179 if self._mode in ['set-reset', 'reset']:
2180 self._reset_event(obj, val)
2181
2182
2183 from contextlib import contextmanager
2184 @contextmanager
2185 def exceptions_summarized():
2186 """Useful utility for writing docs that need to show expected errors.
2187 Shows exception only, concisely, without a traceback.
2188 """
2189 try:
2190 yield
2191 except Exception:
2192 import sys
2193 etype, value, tb = sys.exc_info()
2194 print("{}: {}".format(etype.__name__,value), file=sys.stderr)
1919 __author__ = "Jean-Luc Stevens"
2020
2121 import re
22 import sys
23 import itertools
2224 import textwrap
2325 import param
2426
5456 def get_param_info(self, obj, include_super=True):
5557 """
5658 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
5860 True, parameters are also collected from the super classes.
5961 """
6062
6466 val_dict = dict((k,p.default) for (k,p) in params.items())
6567 self_class = obj
6668 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()
6971 self_class = obj.__class__
7072
7173 if not include_super:
8587
8688 (params, val_dict, changed) = info
8789 contents = []
88 displayed_params = {}
89 for name, p in params.items():
90 displayed_params = []
91 for name in self.sort_by_precedence(params):
9092 if only_changed and not (name in changed):
9193 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):
9899 heading = "%s: " % name
99100 unindented = textwrap.dedent("< No docstring available >" if p.doc is None else p.doc)
100101
123124 return "\n".join(contents)
124125
125126
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
126150 def _build_table(self, info, order, max_col_len=40, only_changed=False):
127151 """
128152 Collect the information about parameters needed to build a
129153 properly formatted table and then tabulate it.
130154 """
131155
132 info_dict, bounds_dict = {}, {}
156 info_list, bounds_dict = [], {}
133157 (params, val_dict, changed) = info
134158 col_widths = dict((k,0) for k in order)
135159
136 for name, p in params.items():
160 ordering = self.sort_by_precedence(params)
161 for name in ordering:
162 p = params[name]
137163 if only_changed and not (name in changed):
138164 continue
139165
142168 allow_None = ' AN' if hasattr(p, 'allow_None') and p.allow_None else ''
143169
144170 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}
147178
148179 if hasattr(p, 'bounds'):
149180 lbound, ubound = (None,None) if p.bounds is None else p.bounds
161192
162193 if (lbound, ubound) != (None,None):
163194 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])])
173199 col_widths[col] = max_width
174200
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):
179207 """
180208 Returns the supplied information as a table suitable for
181209 printing or paging.
182210
183 info_dict: Dictionary of the parameters name, type and mode.
211 info_list: List of the parameters name, type and mode.
184212 col_widths: Dictionary of column widths in characters
185213 changed: List of parameters modified from their defaults.
186214 order: The order of the table columns
188216 """
189217
190218 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)
192220 columns = [col for col in order if col in column_set]
193221
194222 title_row = []
201229 contents.append(blue % ''.join(title_row)+"\n")
202230
203231 # Format the table rows
204 for row in sorted(info_dict):
232 for row, info in info_list:
205233 row_list = []
206 info = info_dict[row]
207234 for i,col in enumerate(columns):
208235 width = col_widths[col]+2
209236 val = info[col] if (col in info) else ''
257284 heading_text = "%s\n%s\n" % (title, heading_line)
258285
259286 param_info = self.get_param_info(param_obj, include_super=True)
260
261287 if not param_info[0]:
262288 return "%s\n%s" % ((green % heading_text), "Object has no parameters.")
263289
265291 only_changed=False)
266292
267293 docstrings = self.param_docstrings(param_info, max_col_len=100, only_changed=False)
268
269294 dflt_msg = "Parameters changed from their default values are marked in red."
270295 top_heading = (green % heading_text)
271296 top_heading += "\n%s" % (red % dflt_msg)
278303 return "%s\n\n%s\n\n%s\n\n%s" % (top_heading, table, docstring_heading, docstrings)
279304
280305
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/)"""
282307 message += '\nAvailable magics: %params'
283308
284309 _loaded = False
00 """
11 Generic support for objects with full-featured Parameters and
22 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).
38 """
49
510 import copy
1015 import numbers
1116 import operator
1217
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
1427 from operator import itemgetter,attrgetter
1528 from types import FunctionType
16 from functools import partial, wraps, reduce
1729
1830 import logging
1931 from contextlib import contextmanager
2537 param_pager = ParamPager(metaclass=True) # Generates param description
2638 except:
2739 param_pager = None
40
41 try:
42 from inspect import getfullargspec
43 except:
44 from inspect import getargspec as getfullargspec # python2
2845
2946 basestring = basestring if sys.version_info[0]==2 else str # noqa: it is defined
3047
6481 object_count = 0
6582 warning_count = 0
6683
84 class _Undefined:
85 """
86 Dummy value to signal completely undefined values rather than
87 simple None values.
88 """
6789
6890 @contextmanager
6991 def logging_level(level):
87109
88110
89111 @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.
97117 """
98118 BATCH_WATCH = parameterized.param._BATCH_WATCH
99119 parameterized.param._BATCH_WATCH = enable or parameterized.param._BATCH_WATCH
102122 finally:
103123 parameterized.param._BATCH_WATCH = BATCH_WATCH
104124 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:
105145 parameterized.param._batch_call_watchers()
106146
107147
132172 """
133173 batch_watch = parameterized.param._BATCH_WATCH
134174 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))
136177 try:
137178 yield
138179 except:
143184 parameterized.param._events = events
144185
145186
187 # External components can register an async executor which will run
188 # async functions
189 async_executor = None
190
191
146192 def classlist(class_):
147193 """
148194 Return a list of the class hierarchy above (and including) the given class.
149195
150 Same as inspect.getmro(class_)[::-1]
196 Same as `inspect.getmro(class_)[::-1]`
151197 """
152198 return inspect.getmro(class_)[::-1]
153199
173219
174220 def get_all_slots(class_):
175221 """
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
177223 superclasses.
178224 """
179225 # A subclass's __slots__ attribute does not contain slots defined
217263 return arg1==arg2
218264
219265
220
221 # For Python 2 compatibility.
266 # PARAM2_DEPRECATION: For Python 2 compatibility only; can be removed in param2.
222267 #
223268 # The syntax to use a metaclass changed incompatibly between 2 and
224269 # 3. The add_metaclass() class decorator below creates a class using a
239284 return wrapper
240285
241286
287
242288 class bothmethod(object): # pylint: disable-msg=R0903
243289 """
244290 'optional @classmethod'
269315 return reduce(_getattr, [obj] + attr.split('.'))
270316
271317
272 # (thought I was going to have a few decorators following this pattern)
273318 def accept_arguments(f):
319 """
320 Decorator for decorators that accept arguments
321 """
274322 @wraps(f)
275323 def _f(*args, **kwargs):
276324 return lambda actual_f: f(actual_f, *args, **kwargs)
285333 return cls
286334
287335
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 return inspect.isasyncgenfunction(function) or inspect.iscoroutinefunction(function)
343
344
288345 def instance_descriptor(f):
289 # If parameter has an instance Parameter delegate setting
346 # If parameter has an instance Parameter, delegate setting
290347 def _f(self, obj, val):
291348 instance_param = getattr(obj, '_instance__params', {}).get(self.name)
292349 if instance_param is not None and self is not instance_param:
296353 return _f
297354
298355
356 def get_method_owner(method):
357 """
358 Gets the instance that owns the supplied method
359 """
360 if not inspect.ismethod(method):
361 return None
362 if isinstance(method, partial):
363 method = method.func
364 return method.__self__ if sys.version_info.major >= 3 else method.im_self
365
366
299367 @accept_arguments
300368 def depends(func, *dependencies, **kw):
301369 """
308376 on Parameter values, or on other metadata about the Parameter.
309377 """
310378
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)
379 # PARAM2_DEPRECATION: python2 workaround; python3 allows kw-only args
380 # (i.e. "func, *dependencies, watch=False" rather than **kw and the check below)
381 watch = kw.pop("watch", False)
382 on_init = kw.pop("on_init", False)
314383
315384 @wraps(func)
316 def _depends(*args,**kw):
317 return func(*args,**kw)
385 def _depends(*args, **kw):
386 return func(*args, **kw)
318387
319388 deps = list(dependencies)+list(kw.values())
320389 string_specs = False
345414 'or function is not supported when referencing '
346415 'parameters by name.')
347416
348 if not string_specs and watch:
349 def cb(event):
417 if not string_specs and watch: # string_specs case handled elsewhere (later), in Parameterized.__init__
418 def cb(*events):
350419 args = (getattr(dep.owner, dep.name) for dep in dependencies)
351420 dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
352421 return func(*args, **dep_kwargs)
353422
423 grouped = defaultdict(list)
354424 for dep in deps:
355 dep.owner.param.watch(cb, dep.name)
425 grouped[id(dep.owner)].append(dep)
426 for group in grouped.values():
427 group[0].owner.param.watch(cb, [dep.name for dep in group])
356428
357429 _dinfo = getattr(func, '_dinfo', {})
358430 _dinfo.update({'dependencies': dependencies,
359 'kw': kw, 'watch': watch})
431 'kw': kw, 'watch': watch, 'on_init': on_init})
360432
361433 _depends._dinfo = _dinfo
362434
458530 return _output
459531
460532
461 def _params_depended_on(minfo):
462 params = []
463 dinfo = getattr(minfo.method,"_dinfo", {})
533 def _parse_dependency_spec(spec):
534 """
535 Parses param.depends specifications into three components:
536
537 1. The dotted path to the sub-object
538 2. The attribute being depended on, i.e. either a parameter or method
539 3. The parameter attribute being depended on
540 """
541 assert spec.count(":")<=1
542 spec = spec.strip()
543 m = re.match("(?P<path>[^:]*):?(?P<what>.*)", spec)
544 what = m.group('what')
545 path = "."+m.group('path')
546 m = re.match(r"(?P<obj>.*)(\.)(?P<attr>.*)", path)
547 obj = m.group('obj')
548 attr = m.group("attr")
549 return obj or None, attr, what or 'value'
550
551
552 def _params_depended_on(minfo, dynamic=True, intermediate=True):
553 """
554 Resolves dependencies declared on a Parameterized method.
555 Dynamic dependencies, i.e. dependencies on sub-objects which may
556 or may not yet be available, are only resolved if dynamic=True.
557 By default intermediate dependencies, i.e. dependencies on the
558 path to a sub-object are returned. For example for a dependency
559 on 'a.b.c' dependencies on 'a' and 'b' are returned as long as
560 intermediate=True.
561
562 Returns lists of concrete dependencies on available parameters
563 and dynamic dependencies specifications which have to resolved
564 if the referenced sub-objects are defined.
565 """
566 deps, dynamic_deps = [], []
567 dinfo = getattr(minfo.method, "_dinfo", {})
464568 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)
569 ddeps, ddynamic_deps = (minfo.inst or minfo.cls).param._spec_to_obj(d, dynamic, intermediate)
570 dynamic_deps += ddynamic_deps
571 for dep in ddeps:
572 if isinstance(dep, PInfo):
573 deps.append(dep)
469574 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")
575 method_deps, method_dynamic_deps = _params_depended_on(dep, dynamic, intermediate)
576 deps += method_deps
577 dynamic_deps += method_dynamic_deps
578 return deps, dynamic_deps
579
580
581 def _resolve_mcs_deps(obj, resolved, dynamic, intermediate=True):
582 """
583 Resolves constant and dynamic parameter dependencies previously
584 obtained using the _params_depended_on function. Existing resolved
585 dependencies are updated with a supplied parameter instance while
586 dynamic dependencies are resolved if possible.
587 """
588 dependencies = []
589 for dep in resolved:
590 if not issubclass(type(obj), dep.cls):
591 dependencies.append(dep)
592 continue
593 inst = obj if dep.inst is None else dep.inst
594 dep = PInfo(inst=inst, cls=dep.cls, name=dep.name,
595 pobj=inst.param[dep.name], what=dep.what)
596 dependencies.append(dep)
597 for dep in dynamic:
598 subresolved, _ = obj.param._spec_to_obj(dep.spec, intermediate=intermediate)
599 for subdep in subresolved:
600 if isinstance(subdep, PInfo):
601 dependencies.append(subdep)
602 else:
603 dependencies += _params_depended_on(subdep, intermediate=intermediate)[0]
604 return dependencies
605
606
607 def _skip_event(*events, **kwargs):
608 """
609 Checks whether a subobject event should be skipped.
610 Returns True if all the values on the new subobject
611 match the values on the previous subobject.
612 """
613 what = kwargs.get('what', 'value')
614 changed = kwargs.get('changed')
615 if changed is None:
616 return False
617 for e in events:
618 for p in changed:
619 if what == 'value':
620 old = _Undefined if e.old is None else _getattrr(e.old, p, None)
621 new = _Undefined if e.new is None else _getattrr(e.new, p, None)
622 else:
623 old = _Undefined if e.old is None else _getattrr(e.old.param[p], what, None)
624 new = _Undefined if e.new is None else _getattrr(e.new.param[p], what, None)
625 if not Comparator.is_equal(old, new):
626 return False
627 return True
628
629
630 def _m_caller(self, method_name, what='value', changed=None, callback=None):
631 """
632 Wraps a method call adding support for scheduling a callback
633 before it is executed and skipping events if a subobject has
634 changed but its values have not.
635 """
636 function = getattr(self, method_name)
637 if iscoroutinefunction(function):
638 import asyncio
639 @asyncio.coroutine
640 def caller(*events):
641 if callback: callback(*events)
642 if not _skip_event(*events, what=what, changed=changed):
643 yield function()
644 else:
645 def caller(*events):
646 if callback: callback(*events)
647 if not _skip_event(*events, what=what, changed=changed):
648 return function()
649 caller._watcher_name = method_name
650 return caller
651
652
653 def _add_doc(obj, docstring):
654 """Add a docstring to a namedtuple, if on python3 where that's allowed"""
655 if sys.version_info[0]>2:
656 obj.__doc__ = docstring
657
658
659 PInfo = namedtuple("PInfo", "inst cls name pobj what"); _add_doc(PInfo,
660 """
661 Object describing something being watched about a Parameter.
662
663 `inst`: Parameterized instance owning the Parameter, or None
664
665 `cls`: Parameterized class owning the Parameter
666
667 `name`: Name of the Parameter being watched
668
669 `pobj`: Parameter object being watched
670
671 `what`: What is being watched on the Parameter (either 'value' or a slot name)
672 """)
673
674 MInfo = namedtuple("MInfo", "inst cls name method"); _add_doc(MInfo,
675 """
676 Object describing a Parameterized method being watched.
677
678 `inst`: Parameterized instance owning the method, or None
679
680 `cls`: Parameterized class owning the method
681
682 `name`: Name of the method being watched
683
684 `method`: bound method of the object being watched
685 """)
686
687 DInfo = namedtuple("DInfo", "spec"); _add_doc(DInfo,
688 """
689 Object describing dynamic dependencies.
690 `spec`: Dependency specification to resolve
691 """)
692
693 Event = namedtuple("Event", "what name obj cls old new type"); _add_doc(Event,
694 """
695 Object representing an event that triggers a Watcher.
696
697 `what`: What is being watched on the Parameter (either value or a slot name)
698
699 `name`: Name of the Parameter that was set or triggered
700
701 `obj`: Parameterized instance owning the watched Parameter, or None
702
703 `cls`: Parameterized class owning the watched Parameter
704
705 `old`: Previous value of the item being watched
706
707 `new`: New value of the item being watched
708
709 `type`: `triggered` if this event was triggered explicitly), `changed` if
710 the item was set and watching for `onlychanged`, `set` if the item was set,
711 or None if type not yet known
712 """)
713
714 _Watcher = namedtuple("Watcher", "inst cls fn mode onlychanged parameter_names what queued precedence")
715
716 class Watcher(_Watcher):
717 """
718 Object declaring a callback function to invoke when an Event is
719 triggered on a watched item.
720
721 `inst`: Parameterized instance owning the watched Parameter, or
722 None
723
724 `cls`: Parameterized class owning the watched Parameter
725
726 `fn`: Callback function to invoke when triggered by a watched
727 Parameter
728
729 `mode`: 'args' for param.watch (call `fn` with PInfo object
730 positional args), or 'kwargs' for param.watch_values (call `fn`
731 with <param_name>:<new_value> keywords)
732
733 `onlychanged`: If True, only trigger for actual changes, not
734 setting to the current value
735
736 `parameter_names`: List of Parameters to watch, by name
737
738 `what`: What to watch on the Parameters (either 'value' or a slot
739 name)
740
741 `queued`: Immediately invoke callbacks triggered during processing
742 of an Event (if False), or queue them up for processing
743 later, after this event has been handled (if True)
744
745 `precedence`: A numeric value which determines the precedence of
746 the watcher. Lower precedence values are executed
747 with higher priority.
748 """
749
750 def __new__(cls_, *args, **kwargs):
751 """
752 Allows creating Watcher without explicit precedence value.
753 """
754 values = dict(zip(cls_._fields, args))
755 values.update(kwargs)
756 if 'precedence' not in values:
757 values['precedence'] = 0
758 return super(Watcher, cls_).__new__(cls_, **values)
759
760 def __iter__(self):
761 """
762 Backward compatibility layer to allow tuple unpacking without
763 the precedence value. Important for Panel which creates a
764 custom Watcher and uses tuple unpacking. Will be dropped in
765 Param 3.x.
766 """
767 return iter(self[:-1])
768
769 def __str__(self):
770 cls = type(self)
771 attrs = ', '.join(['%s=%r' % (f, getattr(self, f)) for f in cls._fields])
772 return "{cls}({attrs})".format(cls=cls.__name__, attrs=attrs)
773
774
775
482776
483777 class ParameterMetaclass(type):
484778 """
485779 Metaclass allowing control over creation of Parameter classes.
486780 """
487781 def __new__(mcs,classname,bases,classdict):
782
488783 # store the class's docstring in __classdoc
489784 if '__doc__' in classdict:
490785 classdict['__classdoc']=classdict['__doc__']
491 # when asking for help on Parameter *object*, return the doc
492 # slot
786
787 # when asking for help on Parameter *object*, return the doc slot
493788 classdict['__doc__']=property(attrgetter('doc'))
494789
495790 # To get the benefit of slots, subclasses must themselves define
498793 # a __dict__ unless it also defines __slots__.
499794 if '__slots__' not in classdict:
500795 classdict['__slots__']=[]
796
797 # No special handling for a __dict__ slot; should there be?
501798
502799 return type.__new__(mcs,classname,bases,classdict)
503800
511808
512809
513810
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.
527811 @add_metaclass(ParameterMetaclass)
528812 class Parameter(object):
529813 """
541825 objects Foo and Bar, such that Bar has a parameter delta, Foo is a
542826 subclass of Bar, and Foo has parameters alpha, sigma, and gamma
543827 (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 ...
828 definitions with something like this::
829
830 class Bar(Parameterized):
831 delta = Parameter(default=0.6, doc='The difference between steps.')
832 ...
833 class Foo(Bar):
834 alpha = Parameter(default=0.1, doc='The starting value.')
835 sigma = Parameter(default=0.5, doc='The standard deviation.',
836 constant=True)
837 gamma = Parameter(default=1.0, doc='The ending value.')
838 ...
556839
557840 Class Foo would then have four parameters, with delta defaulting
558841 to 0.6.
563846 constructed: The default constructor for Foo (and Bar) will
564847 accept arbitrary keyword arguments, each of which can be used
565848 to specify the value of a Parameter of Foo (or any of Foo's
566 superclasses). E.g., if a script does this:
849 superclasses). E.g., if a script does this::
567850
568851 myfoo = Foo(alpha=0.5)
569852
580863 2. A Parameterized class need specify only the attributes of a
581864 Parameter whose values differ from those declared in
582865 superclasses; the other values will be inherited. E.g. if Foo
583 declares
866 declares::
584867
585868 delta = Parameter(default=0.2)
586869
659942 # attributes. Using __slots__ requires special support for
660943 # operations to copy and restore Parameters (e.g. for Python
661944 # 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',
945 __slots__ = ['name', '_internal_name', 'default', 'doc',
946 'precedence', 'instantiate', 'constant', 'readonly',
947 'pickle_default_value', 'allow_None', 'per_instance',
665948 'watchers', 'owner', '_label']
666949
667950 # Note: When initially created, a Parameter does not know which
670953 # class is created, owner, name, and _internal_name are
671954 # set.
672955
673 def __init__(self,default=None,doc=None,label=None,precedence=None, # pylint: disable-msg=R0913
674 instantiate=False,constant=False,readonly=False,
956 _serializers = {'json': serializer.JSONSerialization}
957
958 def __init__(self,default=None, doc=None, label=None, precedence=None, # pylint: disable-msg=R0913
959 instantiate=False, constant=False, readonly=False,
675960 pickle_default_value=True, allow_None=False,
676961 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
962
963 """Initialize a new Parameter object and store the supplied attributes:
964
965 default: the owning class's value for the attribute represented
966 by this Parameter, which can be overridden in an instance.
967
968 doc: docstring explaining what this parameter represents.
969
970 label: optional text label to be used when this Parameter is
971 shown in a listing. If no label is supplied, the attribute name
972 for this parameter in the owning Parameterized object is used.
973
974 precedence: a numeric value, usually in the range 0.0 to 1.0,
975 which allows the order of Parameters in a class to be defined in
976 a listing or e.g. in GUI menus. A negative precedence indicates
977 a parameter that should be hidden in such listings.
978
979 instantiate: controls whether the value of this Parameter will
980 be deepcopied when a Parameterized object is instantiated (if
981 True), or if the single default value will be shared by all
982 Parameterized instances (if False). For an immutable Parameter
983 value, it is best to leave instantiate at the default of
984 False, so that a user can choose to change the value at the
985 Parameterized instance level (affecting only that instance) or
986 at the Parameterized class or superclass level (affecting all
987 existing and future instances of that class or superclass). For
988 a mutable Parameter value, the default of False is also appropriate
989 if you want all instances to share the same value state, e.g. if
990 they are each simply referring to a single global object like
991 a singleton. If instead each Parameterized should have its own
992 independently mutable value, instantiate should be set to
993 True, but note that there is then no simple way to change the
994 value of this Parameter at the class or superclass level,
995 because each instance, once created, will then have an
996 independently instantiated value.
997
998 constant: if true, the Parameter value can be changed only at
999 the class level or in a Parameterized constructor call. The
1000 value is otherwise constant on the Parameterized instance,
1001 once it has been constructed.
1002
1003 readonly: if true, the Parameter value cannot ordinarily be
1004 changed by setting the attribute at the class or instance
1005 levels at all. The value can still be changed in code by
1006 temporarily overriding the value of this slot and then
1007 restoring it, which is useful for reporting values that the
1008 _user_ should never change but which do change during code
1009 execution.
1010
1011 pickle_default_value: whether the default value should be
1012 pickled. Usually, you would want the default value to be pickled,
1013 but there are rare cases where that would not be the case (e.g.
1014 for file search paths that are specific to a certain system).
1015
1016 per_instance: whether a separate Parameter instance will be
1017 created for every Parameterized instance. True by default.
1018 If False, all instances of a Parameterized class will share
1019 the same Parameter object, including all validation
1020 attributes (bounds, etc.). See also instantiate, which is
1021 conceptually similar but affects the Parameter value rather
1022 than the Parameter object.
1023
1024 allow_None: if True, None is accepted as a valid value for
1025 this Parameter, in addition to any other values that are
1026 allowed. If the default value is defined as None, allow_None
1027 is set to True automatically.
1028
1029 default, doc, and precedence all default to None, which allows
6891030 inheritance of Parameter slots (attributes) from the owning-class'
6901031 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 """
1032 """
1033
7011034 self.name = None
702 self._internal_name = None
7031035 self.owner = None
704 self._label = label
7051036 self.precedence = precedence
7061037 self.default = default
7071038 self.doc = doc
7081039 self.constant = constant or readonly # readonly => constant
7091040 self.readonly = readonly
1041 self._label = label
1042 self._internal_name = None
7101043 self._set_instantiate(instantiate)
7111044 self.pickle_default_value = pickle_default_value
7121045 self.allow_None = (default is None or allow_None)
7131046 self.watchers = {}
7141047 self.per_instance = per_instance
7151048
1049 @classmethod
1050 def serialize(cls, value):
1051 "Given the parameter value, return a Python value suitable for serialization"
1052 return value
1053
1054 @classmethod
1055 def deserialize(cls, value):
1056 "Given a serializable Python value, return a value that the parameter can be set to"
1057 return value
1058
1059 def schema(self, safe=False, subset=None, mode='json'):
1060 if serializer is None:
1061 raise ImportError('Cannot import serializer.py needed to generate schema')
1062 if mode not in self._serializers:
1063 raise KeyError('Mode %r not in available serialization formats %r'
1064 % (mode, list(self._serializers.keys())))
1065 return self._serializers[mode].param_schema(self.__class__.__name__, self,
1066 safe=safe, subset=subset)
7161067
7171068 @property
7181069 def label(self):
7271078
7281079 def _set_instantiate(self,instantiate):
7291080 """Constant parameters must be instantiated."""
730 # CB: instantiate doesn't actually matter for read-only
1081 # instantiate doesn't actually matter for read-only
7311082 # parameters, since they can't be set even on a class. But
732 # this avoids needless instantiation.
1083 # having this code avoids needless instantiation.
7331084 if self.readonly:
7341085 self.instantiate = False
7351086 else:
7361087 self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201
7371088
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)
1089 def __setattr__(self, attribute, value):
1090 if attribute == 'name' and getattr(self, 'name', None) and value != self.name:
1091 raise AttributeError("Parameter name cannot be modified after "
1092 "it has been bound to a Parameterized.")
1093
1094 implemented = (attribute != "default" and hasattr(self, 'watchers') and attribute in self.watchers)
1095 slot_attribute = attribute in self.__slots__
7471096 try:
748 old = getattr(self,attribute) if implemented else NotImplemented
1097 old = getattr(self, attribute) if implemented else NotImplemented
1098 if slot_attribute:
1099 self._on_set(attribute, old, value)
7491100 except AttributeError as e:
750 if attribute in self.__slots__:
1101 if slot_attribute:
7511102 # If Parameter slot is defined but an AttributeError was raised
7521103 # we are in __setstate__ and watchers should not be triggered
7531104 old = NotImplemented
7561107
7571108 super(Parameter, self).__setattr__(attribute, value)
7581109
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
1110 if old is NotImplemented:
1111 return
1112
1113 event = Event(what=attribute, name=self.name, obj=None, cls=self.owner,
1114 old=old, new=value, type=None)
1115 for watcher in self.watchers[attribute]:
1116 self.owner.param._call_watcher(watcher, event)
1117 if not self.owner.param._BATCH_WATCH:
1118 self.owner.param._batch_call_watchers()
1119
1120 def _on_set(self, attribute, old, value):
1121 """
1122 Can be overridden on subclasses to handle changes when parameter
1123 attribute is set.
1124 """
1125
1126 def __get__(self, obj, objtype): # pylint: disable-msg=W0613
7681127 """
7691128 Return the value for this Parameter.
7701129
7761135 instance's value, if one has been set - otherwise produce the
7771136 class's value (default).
7781137 """
779 # NB: obj can be None (when __get__ called for a
780 # Parameterized class); objtype is never None
781
782 if obj is None:
1138 if obj is None: # e.g. when __get__ called for a Parameterized class
7831139 result = self.default
7841140 else:
7851141 result = obj.__dict__.get(self._internal_name,self.default)
7861142 return result
7871143
788
7891144 @instance_descriptor
790 def __set__(self,obj,val):
1145 def __set__(self, obj, val):
7911146 """
7921147 Set the value for this Parameter.
7931148
7971152 If called for a Parameterized instance, set the value of
7981153 this Parameter on that instance (i.e. in the instance's
7991154 __dict__, under the parameter's internal_name).
800
8011155
8021156 If the Parameter's constant attribute is True, only allows
8031157 the value to be set for a Parameterized class or on
8101164
8111165 Note that until we support some form of read-only
8121166 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
1167 object stored in a constant or read-only Parameter (e.g. one
1168 item in a list).
1169 """
1170
1171 # PARAM2_DEPRECATION: For Python 2 compatibility only;
1172 # Deprecated Number set_hook called here to avoid duplicating setter
8191173 if hasattr(self, 'set_hook'):
8201174 val = self.set_hook(obj,val)
8211175
8221176 self._validate(val)
8231177
8241178 _old = NotImplemented
825 # NB: obj can be None (when __set__ called for a
826 # Parameterized class)
1179 # obj can be None if __set__ is called for a Parameterized class
8271180 if self.constant or self.readonly:
8281181 if self.readonly:
829 raise TypeError("Read-only parameter '%s' cannot be modified"%self.name)
830 elif obj is None: #not obj
1182 raise TypeError("Read-only parameter '%s' cannot be modified" % self.name)
1183 elif obj is None:
8311184 _old = self.default
8321185 self.default = val
8331186 elif not obj.initialized:
834 _old = obj.__dict__.get(self._internal_name,self.default)
1187 _old = obj.__dict__.get(self._internal_name, self.default)
8351188 obj.__dict__[self._internal_name] = val
8361189 else:
837 raise TypeError("Constant parameter '%s' cannot be modified"%self.name)
838
1190 _old = obj.__dict__.get(self._internal_name, self.default)
1191 if val is not _old:
1192 raise TypeError("Constant parameter '%s' cannot be modified"%self.name)
8391193 else:
8401194 if obj is None:
8411195 _old = self.default
8421196 self.default = val
8431197 else:
844 _old = obj.__dict__.get(self._internal_name,self.default)
1198 _old = obj.__dict__.get(self._internal_name, self.default)
8451199 obj.__dict__[self._internal_name] = val
8461200
8471201 self._post_setter(obj, val)
8481202
1203 if obj is not None:
1204 if not getattr(obj, 'initialized', False):
1205 return
1206 obj.param._update_deps(self.name)
1207
8491208 if obj is None:
850 watchers = self.watchers.get("value",[])
1209 watchers = self.watchers.get("value")
1210 elif hasattr(obj, '_param_watchers') and self.name in obj._param_watchers:
1211 watchers = obj._param_watchers[self.name].get('value')
1212 if watchers is None:
1213 watchers = self.watchers.get("value")
8511214 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)
1215 watchers = None
1216
8551217 obj = self.owner if obj is None else obj
856 if obj is None:
1218
1219 if obj is None or not watchers:
8571220 return
8581221
859 for watcher in watchers:
1222 event = Event(what='value', name=self.name, obj=obj, cls=self.owner,
1223 old=_old, new=val, type=None)
1224
1225 # Copy watchers here since they may be modified inplace during iteration
1226 for watcher in sorted(watchers, key=lambda w: w.precedence):
8601227 obj.param._call_watcher(watcher, event)
8611228 if not obj.param._BATCH_WATCH:
8621229 obj.param._batch_call_watchers()
8631230
1231 def _validate_value(self, value, allow_None):
1232 """Implements validation for parameter value"""
8641233
8651234 def _validate(self, val):
866 """Implements validation for the parameter"""
867
1235 """Implements validation for the parameter value and attributes"""
1236 self._validate_value(val, self.allow_None)
8681237
8691238 def _post_setter(self, obj, val):
8701239 """Called after the parameter value has been validated and set"""
8711240
872
8731241 def __delete__(self,obj):
8741242 raise TypeError("Cannot delete '%s': Parameters deletion not allowed." % self.name)
875
8761243
8771244 def _set_names(self, attrib_name):
8781245 if None not in (self.owner, self.name) and attrib_name != self.name:
8851252 % (type(self).__name__, self.name,
8861253 self.owner.name, attrib_name))
8871254 self.name = attrib_name
888
889 self._internal_name = "_%s_param_value"%attrib_name
890
1255 self._internal_name = "_%s_param_value" % attrib_name
8911256
8921257 def __getstate__(self):
8931258 """
9201285
9211286 # Define one particular type of Parameter that is used in this file
9221287 class String(Parameter):
923 """
1288 r"""
9241289 A String Parameter, with a default value and optional regular expression (regex) matching.
9251290
9261291 Example of using a regex to implement IPv4 address matching::
9281293 class IPAddress(String):
9291294 '''IPv4 address as a string (dotted decimal notation)'''
9301295 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]?)$'
1296 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]?)$'
9321297 super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs)
933
9341298
9351299 """
9361300
9421306 self.allow_None = (default is None or allow_None)
9431307 self._validate(default)
9441308
1309 def _validate_regex(self, val, regex):
1310 if (val is None and self.allow_None):
1311 return
1312 if regex is not None and re.match(regex, val) is None:
1313 raise ValueError("String parameter %r value %r does not match regex %r."
1314 % (self.name, val, regex))
1315
1316 def _validate_value(self, val, allow_None):
1317 if allow_None and val is None:
1318 return
1319 if not isinstance(val, basestring):
1320 raise ValueError("String parameter %r only takes a string value, "
1321 "not value of type %s." % (self.name, type(val)))
1322
9451323 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))
1324 self._validate_value(val, self.allow_None)
1325 self._validate_regex(val, self.regex)
9541326
9551327
9561328 class shared_parameters(object):
9851357 @wraps(fn)
9861358 def override_initialization(self_,*args,**kw):
9871359 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
1360 original_initialized = parameterized_instance.initialized
1361 parameterized_instance.initialized = False
1362 fn(parameterized_instance, *args, **kw)
1363 parameterized_instance.initialized = original_initialized
9921364 return override_initialization
9931365
9941366
10611433 class or the instance as necessary.
10621434 """
10631435
1064 _disable_stubs = None # Flag used to disable stubs in the API1 tests
1436 _disable_stubs = False # Flag used to disable stubs in the API1 tests
10651437 # None for no action, True to raise and False to warn.
10661438
10671439 def __init__(self_, cls, self=None):
10711443 """
10721444 self_.cls = cls
10731445 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
1446
1447 @property
1448 def _BATCH_WATCH(self_):
1449 return self_.self_or_cls._parameters_state['BATCH_WATCH']
1450
1451 @_BATCH_WATCH.setter
1452 def _BATCH_WATCH(self_, value):
1453 self_.self_or_cls._parameters_state['BATCH_WATCH'] = value
1454
1455 @property
1456 def _TRIGGER(self_):
1457 return self_.self_or_cls._parameters_state['TRIGGER']
1458
1459 @_TRIGGER.setter
1460 def _TRIGGER(self_, value):
1461 self_.self_or_cls._parameters_state['TRIGGER'] = value
1462
1463 @property
1464 def _events(self_):
1465 return self_.self_or_cls._parameters_state['events']
1466
1467 @_events.setter
1468 def _events(self_, value):
1469 self_.self_or_cls._parameters_state['events'] = value
1470
1471 @property
1472 def _watchers(self_):
1473 return self_.self_or_cls._parameters_state['watchers']
1474
1475 @_watchers.setter
1476 def _watchers(self_, value):
1477 self_.self_or_cls._parameters_state['watchers'] = value
1478
1479 @property
1480 def watchers(self):
1481 """Read-only list of watchers on this Parameterized"""
1482 return self._watchers
10781483
10791484 @property
10801485 def self_or_cls(self_):
10811486 return self_.cls if self_.self is None else self_.self
10821487
1488 def __setstate__(self, state):
1489 # Set old parameters state on Parameterized._parameters_state
1490 self_or_cls = state.get('self', state.get('cls'))
1491 for k in self_or_cls._parameters_state:
1492 key = '_'+k
1493 if key in state:
1494 self_or_cls._parameters_state[k] = state.pop(key)
1495 for k, v in state.items():
1496 setattr(self, k, v)
10831497
10841498 def __getitem__(self_, key):
10851499 """
10881502 inst = self_.self
10891503 parameters = self_.objects(False) if inst is None else inst.param.objects(False)
10901504 p = parameters[key]
1091 if (inst is not None and p.per_instance and
1505 if (inst is not None and getattr(inst, 'initialized', False) and p.per_instance and
10921506 not getattr(inst, '_disable_instance__params', False)):
10931507 if key not in inst._instance__params:
10941508 try:
10991513 except:
11001514 raise
11011515 finally:
1102 p.watchers = watchers
1516 p.watchers = {k: list(v) for k, v in watchers.items()}
11031517 p.owner = inst
11041518 inst._instance__params[key] = p
11051519 else:
11701584 First, ensures that all Parameters with 'instantiate=True'
11711585 (typically used for mutable Parameters) are copied directly
11721586 into each object, to ensure that there is an independent copy
1173 (to avoid suprising aliasing errors). Then sets each of the
1587 (to avoid surprising aliasing errors). Then sets each of the
11741588 keyword arguments, warning when any of them are not defined as
11751589 parameters.
11761590
11781592 """
11791593 self = self_.param.self
11801594 ## Deepcopy all 'instantiate=True' parameters
1181 # (build a set of names first to avoid redundantly instantiating
1182 # a later-overridden parent class's parameter)
1595 # (building a set of names first to avoid redundantly
1596 # instantiating a later-overridden parent class's parameter)
11831597 params_to_instantiate = {}
11841598 for class_ in classlist(type(self)):
11851599 if not issubclass(class_, Parameterized):
11861600 continue
1187 for (k,v) in class_.__dict__.items():
1601 for (k, v) in class_.param._parameters.items():
11881602 # (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
1603 if v.instantiate and k != "name":
1604 params_to_instantiate[k] = v
11911605
11921606 for p in params_to_instantiate.values():
11931607 self.param._instantiate_param(p)
11941608
11951609 ## keyword arg setting
1196 for name,val in params.items():
1610 for name, val in params.items():
11971611 desc = self.__class__.get_param_descriptor(name)[0] # pylint: disable-msg=E1101
11981612 if not desc:
1199 self.param.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters",name,val)
1613 self.param.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters", name, val)
12001614 # i.e. if not desc it's setting an attribute in __dict__, not a Parameter
1201 setattr(self,name,val)
1202
1615 setattr(self, name, val)
1616
1617 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
12031618 @classmethod
12041619 def deprecate(cls, fn):
12051620 """
12301645 return not Comparator.is_equal(event.old, event.new)
12311646
12321647
1233 # CEBALERT: this is a bit ugly
1234 def _instantiate_param(self_,param_obj,dict_=None,key=None):
1648 def _instantiate_param(self_, param_obj, dict_=None, key=None):
12351649 # deepcopy param_obj.default into self.__dict__ (or dict_ if supplied)
12361650 # under the parameter's _internal_name (or key if supplied)
12371651 self = self_.self
12381652 dict_ = dict_ or self.__dict__
12391653 key = key or param_obj._internal_name
1240 param_key = (str(type(self)), param_obj.name)
12411654 if shared_parameters._share:
1655 param_key = (str(type(self)), param_obj.name)
12421656 if param_key in shared_parameters._shared_cache:
12431657 new_object = shared_parameters._shared_cache[param_key]
12441658 else:
12461660 shared_parameters._shared_cache[param_key] = new_object
12471661 else:
12481662 new_object = copy.deepcopy(param_obj.default)
1249 dict_[key]=new_object
1250
1251 if isinstance(new_object,Parameterized):
1663
1664 dict_[key] = new_object
1665
1666 if isinstance(new_object, Parameterized):
12521667 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?
1668 object_count += 1
1669 # Writes over name given to the original object;
1670 # could instead have kept the same name
12561671 new_object.param._generate_name()
12571672
1673 def _update_deps(self_, attribute=None, init=False):
1674 obj = self_.self
1675 init_methods = []
1676 for method, queued, on_init, constant, dynamic in type(obj).param._depends['watch']:
1677 # On initialization set up constant watchers; otherwise
1678 # clean up previous dynamic watchers for the updated attribute
1679 dynamic = [d for d in dynamic if attribute is None or d.spec.startswith(attribute)]
1680 if init:
1681 constant_grouped = defaultdict(list)
1682 for dep in _resolve_mcs_deps(obj, constant, []):
1683 constant_grouped[(id(dep.inst), id(dep.cls), dep.what)].append((None, dep))
1684 for group in constant_grouped.values():
1685 self_._watch_group(obj, method, queued, group)
1686 m = getattr(self_.self, method)
1687 if on_init and m not in init_methods:
1688 init_methods.append(m)
1689 elif dynamic:
1690 for w in obj._dynamic_watchers.pop(method, []):
1691 (w.inst or w.cls).param.unwatch(w)
1692 else:
1693 continue
1694
1695 # Resolve dynamic dependencies one-by-one to be able to trace their watchers
1696 grouped = defaultdict(list)
1697 for ddep in dynamic:
1698 for dep in _resolve_mcs_deps(obj, [], [ddep]):
1699 grouped[(id(dep.inst), id(dep.cls), dep.what)].append((ddep, dep))
1700
1701 for group in grouped.values():
1702 watcher = self_._watch_group(obj, method, queued, group, attribute)
1703 obj._dynamic_watchers[method].append(watcher)
1704 for m in init_methods:
1705 m()
1706
1707 def _resolve_dynamic_deps(self, obj, dynamic_dep, param_dep, attribute):
1708 """
1709 If a subobject whose parameters are being depended on changes
1710 we should only trigger events if the actual parameter values
1711 of the new object differ from those on the old subobject,
1712 therefore we accumulate parameters to compare on a subobject
1713 change event.
1714
1715 Additionally we need to make sure to notify the parent object
1716 if a subobject changes so the dependencies can be
1717 reinitialized so we return a callback which updates the
1718 dependencies.
1719 """
1720 subobj = obj
1721 subobjs = [obj]
1722 for subpath in dynamic_dep.spec.split('.')[:-1]:
1723 subobj = getattr(subobj, subpath.split(':')[0], None)
1724 subobjs.append(subobj)
1725
1726 dep_obj = (param_dep.inst or param_dep.cls)
1727 if dep_obj not in subobjs[:-1]:
1728 return None, None, param_dep.what
1729
1730 depth = subobjs.index(dep_obj)
1731 callback = None
1732 if depth > 0:
1733 def callback(*events):
1734 """
1735 If a subobject changes, we need to notify the main
1736 object to update the dependencies.
1737 """
1738 obj.param._update_deps(attribute)
1739
1740 p = '.'.join(dynamic_dep.spec.split(':')[0].split('.')[depth+1:])
1741 if p == 'param':
1742 subparams = [sp for sp in list(subobjs[-1].param)]
1743 else:
1744 subparams = [p]
1745
1746 if ':' in dynamic_dep.spec:
1747 what = dynamic_dep.spec.split(':')[-1]
1748 else:
1749 what = param_dep.what
1750
1751 return subparams, callback, what
1752
1753 def _watch_group(self_, obj, name, queued, group, attribute=None):
1754 """
1755 Sets up a watcher for a group of dependencies. Ensures that
1756 if the dependency was dynamically generated we check whether
1757 a subobject change event actually causes a value change and
1758 that we update the existing watchers, i.e. clean up watchers
1759 on the old subobject and create watchers on the new subobject.
1760 """
1761 dynamic_dep, param_dep = group[0]
1762 dep_obj = (param_dep.inst or param_dep.cls)
1763 params = []
1764 for _, g in group:
1765 if g.name not in params:
1766 params.append(g.name)
1767
1768 if dynamic_dep is None:
1769 subparams, callback, what = None, None, param_dep.what
1770 else:
1771 subparams, callback, what = self_._resolve_dynamic_deps(
1772 obj, dynamic_dep, param_dep, attribute)
1773
1774 mcaller = _m_caller(obj, name, what, subparams, callback)
1775 return dep_obj.param._watch(
1776 mcaller, params, param_dep.what, queued=queued, precedence=-1)
1777
12581778 # Classmethods
12591779
1780 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
12601781 def print_param_defaults(self_):
12611782 """Print the default values of all cls's Parameters."""
12621783 cls = self_.cls
12651786 print(cls.__name__+'.'+key+ '='+ repr(val.default))
12661787
12671788
1789 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
12681790 def set_default(self_,param_name,value):
12691791 """
12701792 Set the default value of param_name.
12751797 setattr(cls,param_name,value)
12761798
12771799
1278 def _add_parameter(self_, param_name,param_obj):
1800 def add_parameter(self_, param_name, param_obj):
12791801 """
12801802 Add a new Parameter object into this object's class.
12811803
1282 Supposed to result in a Parameter equivalent to one declared
1804 Should result in a Parameter equivalent to one declared
12831805 in the class's source code.
12841806 """
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.)
1807 # Could have just done setattr(cls,param_name,param_obj),
1808 # which is supported by the metaclass's __setattr__ , but
1809 # would need to handle the params() cache as well
1810 # (which is tricky but important for startup speed).
12931811 cls = self_.cls
12941812 type.__setattr__(cls,param_name,param_obj)
12951813 ParameterizedMetaclass._initialize_parameter(cls,param_name,param_obj)
12991817 except AttributeError:
13001818 pass
13011819
1302
1820 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
1821 _add_parameter = add_parameter
1822
1823
1824 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
13031825 def params(self_, parameter_name=None):
13041826 """
13051827 Return the Parameters of this class as the
13081830 Includes Parameters from this class and its
13091831 superclasses.
13101832 """
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
13231833 pdict = self_.objects(instance='existing')
13241834 if parameter_name is None:
13251835 return pdict
13281838
13291839 # Bothmethods
13301840
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.
1841 def update(self_, *args, **kwargs):
1842 """
1843 For the given dictionary or iterable or set of param=value keyword arguments,
1844 sets the corresponding parameter of this object or class to the given value.
13401845 """
13411846 BATCH_WATCH = self_.self_or_cls.param._BATCH_WATCH
13421847 self_.self_or_cls.param._BATCH_WATCH = True
13431848 self_or_cls = self_.self_or_cls
13441849 if args:
1345 if len(args) == 2 and not args[0] in kwargs and not kwargs:
1346 kwargs[args[0]] = args[1]
1850 if len(args) == 1 and not kwargs:
1851 kwargs = args[0]
13471852 else:
13481853 self_.self_or_cls.param._BATCH_WATCH = False
1349 raise ValueError("Invalid positional arguments for %s.set_param" %
1854 raise ValueError("%s.update accepts *either* an iterable or key=value pairs, not both" %
13501855 (self_or_cls.name))
1856
1857 trigger_params = [k for k in kwargs
1858 if ((k in self_.self_or_cls.param) and
1859 hasattr(self_.self_or_cls.param[k], '_autotrigger_value'))]
1860
1861 for tp in trigger_params:
1862 self_.self_or_cls.param[tp]._mode = 'set'
13511863
13521864 for (k, v) in kwargs.items():
13531865 if k not in self_or_cls.param:
13631875 if not BATCH_WATCH:
13641876 self_._batch_call_watchers()
13651877
1878 for tp in trigger_params:
1879 p = self_.self_or_cls.param[tp]
1880 p._mode = 'reset'
1881 setattr(self_or_cls, tp, p._autotrigger_reset_value)
1882 p._mode = 'set-reset'
1883
1884
1885 # PARAM2_DEPRECATION: Could be removed post param 2.0; use update() instead.
1886 def set_param(self_, *args,**kwargs):
1887 """
1888 For each param=value keyword argument, sets the corresponding
1889 parameter of this object or class to the given value.
1890
1891 For backwards compatibility, also accepts
1892 set_param("param",value) for a single parameter value using
1893 positional arguments, but the keyword interface is preferred
1894 because it is more compact and can set multiple values.
1895 """
1896 self_or_cls = self_.self_or_cls
1897 if args:
1898 if len(args) == 2 and not args[0] in kwargs and not kwargs:
1899 kwargs[args[0]] = args[1]
1900 else:
1901 raise ValueError("Invalid positional arguments for %s.set_param" %
1902 (self_or_cls.name))
1903 return self_.update(kwargs)
1904
13661905
13671906 def objects(self_, instance=True):
13681907 """
13771916 instance='existing'.
13781917 """
13791918 cls = self_.cls
1380 # CB: we cache the parameters because this method is called often,
1919 # We cache the parameters because this method is called often,
13811920 # and parameters are rarely added (and cannot be deleted)
13821921 try:
13831922 pdict = getattr(cls, '_%s__params' % cls.__name__)
13971936
13981937 if instance and self_.self is not None:
13991938 if instance == 'existing':
1400 if self_.self._instance__params:
1939 if getattr(self_.self, 'initialized', False) and self_.self._instance__params:
14011940 return dict(pdict, **self_.self._instance__params)
14021941 return pdict
14031942 else:
14091948 """
14101949 Trigger watchers for the given set of parameter names. Watchers
14111950 will be triggered whether or not the parameter values have
1412 actually changed.
1413 """
1951 actually changed. As a special case, the value will actually be
1952 changed for a Parameter of type Event, setting it to True so
1953 that it is clear which Event parameter has been triggered.
1954 """
1955 trigger_params = [p for p in self_.self_or_cls.param
1956 if hasattr(self_.self_or_cls.param[p], '_autotrigger_value')]
1957 triggers = {p:self_.self_or_cls.param[p]._autotrigger_value
1958 for p in trigger_params if p in param_names}
1959
14141960 events = self_.self_or_cls.param._events
14151961 watchers = self_.self_or_cls.param._watchers
14161962 self_.self_or_cls.param._events = []
14171963 self_.self_or_cls.param._watchers = []
1418 param_values = dict(self_.get_param_values())
1964 param_values = self_.values()
14191965 params = {name: param_values[name] for name in param_names}
14201966 self_.self_or_cls.param._TRIGGER = True
1421 self_.set_param(**params)
1967 self_.set_param(**dict(params, **triggers))
14221968 self_.self_or_cls.param._TRIGGER = False
14231969 self_.self_or_cls.param._events += events
14241970 self_.self_or_cls.param._watchers += watchers
14351981 return Event(what=event.what, name=event.name, obj=event.obj, cls=event.cls,
14361982 old=event.old, new=event.new, type=event_type)
14371983
1984 def _execute_watcher(self, watcher, events):
1985 if watcher.mode == 'args':
1986 args, kwargs = events, {}
1987 else:
1988 args, kwargs = (), {event.name: event.new for event in events}
1989
1990 if iscoroutinefunction(watcher.fn):
1991 if async_executor is None:
1992 raise RuntimeError("Could not execute %s coroutine function. "
1993 "Please register a asynchronous executor on "
1994 "param.parameterized.async_executor, which "
1995 "schedules the function on an event loop." %
1996 watcher.fn)
1997 async_executor(partial(watcher.fn, *args, **kwargs))
1998 else:
1999 watcher.fn(*args, **kwargs)
2000
14382001 def _call_watcher(self_, watcher, event):
14392002 """
1440 Invoke the given the watcher appropriately given a Event object.
2003 Invoke the given watcher appropriately given an Event object.
14412004 """
14422005 if self_.self_or_cls.param._TRIGGER:
14432006 pass
14502013 self_._watchers.append(watcher)
14512014 else:
14522015 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
2016 with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False):
2017 self_._execute_watcher(watcher, (event,))
14592018
14602019 def _batch_call_watchers(self_):
14612020 """
14692028 self_.self_or_cls.param._events = []
14702029 self_.self_or_cls.param._watchers = []
14712030
1472 for watcher in watchers:
2031 for watcher in sorted(watchers, key=lambda w: w.precedence):
14732032 events = [self_._update_event_type(watcher, event_dict[(name, watcher.what)],
14742033 self_.self_or_cls.param._TRIGGER)
14752034 for name in watcher.parameter_names
14762035 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
2036 with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False):
2037 self_._execute_watcher(watcher, events)
14832038
14842039 def set_dynamic_time_fn(self_,time_fn,sublistattr=None):
14852040 """
15222077 for obj in sublist:
15232078 obj.param.set_dynamic_time_fn(time_fn,sublistattr)
15242079
1525 def get_param_values(self_,onlychanged=False):
1526 """
2080 def serialize_parameters(self_, subset=None, mode='json'):
2081 self_or_cls = self_.self_or_cls
2082 if mode not in Parameter._serializers:
2083 raise ValueError('Mode %r not in available serialization formats %r'
2084 % (mode, list(Parameter._serializers.keys())))
2085 serializer = Parameter._serializers[mode]
2086 return serializer.serialize_parameters(self_or_cls, subset=subset)
2087
2088 def serialize_value(self_, pname, mode='json'):
2089 self_or_cls = self_.self_or_cls
2090 if mode not in Parameter._serializers:
2091 raise ValueError('Mode %r not in available serialization formats %r'
2092 % (mode, list(Parameter._serializers.keys())))
2093 serializer = Parameter._serializers[mode]
2094 return serializer.serialize_parameter_value(self_or_cls, pname)
2095
2096 def deserialize_parameters(self_, serialization, subset=None, mode='json'):
2097 self_or_cls = self_.self_or_cls
2098 serializer = Parameter._serializers[mode]
2099 return serializer.deserialize_parameters(self_or_cls, serialization, subset=subset)
2100
2101 def deserialize_value(self_, pname, value, 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.deserialize_parameter_value(self_or_cls, pname, value)
2108
2109 def schema(self_, safe=False, subset=None, mode='json'):
2110 """
2111 Returns a schema for the parameters on this Parameterized object.
2112 """
2113 self_or_cls = self_.self_or_cls
2114 if mode not in Parameter._serializers:
2115 raise ValueError('Mode %r not in available serialization formats %r'
2116 % (mode, list(Parameter._serializers.keys())))
2117 serializer = Parameter._serializers[mode]
2118 return serializer.schema(self_or_cls, safe=safe, subset=subset)
2119
2120 # PARAM2_DEPRECATION: Could be removed post param 2.0; same as values() but returns list, not dict
2121 def get_param_values(self_, onlychanged=False):
2122 """
2123 (Deprecated; use .values() instead.)
2124
15272125 Return a list of name,value pairs for all Parameters of this
15282126 object.
15292127
15322130 (onlychanged has no effect when called on a class).
15332131 """
15342132 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).
15392133 vals = []
1540 for name,val in self_or_cls.param.objects('existing').items():
2134 for name, val in self_or_cls.param.objects('existing').items():
15412135 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))
2136 if not onlychanged or not all_equal(value, val.default):
2137 vals.append((name, value))
15452138
15462139 vals.sort(key=itemgetter(0))
15472140 return vals
15482141
2142 def values(self_, onlychanged=False):
2143 """
2144 Return a dictionary of name,value pairs for the Parameters of this
2145 object.
2146
2147 When called on an instance with onlychanged set to True, will
2148 only return values that are not equal to the default value
2149 (onlychanged has no effect when called on a class).
2150 """
2151 # Defined in terms of get_param_values() to avoid ordering
2152 # issues in python2, but can be inverted if get_param_values
2153 # is removed when python2 support is dropped
2154 return dict(self_.get_param_values(onlychanged))
15492155
15502156 def force_new_dynamic_value(self_, name): # pylint: disable-msg=E0213
15512157 """
15712177 return param_obj.__get__(slf, cls)
15722178 else:
15732179 return param_obj._force(slf, cls)
1574
15752180
15762181 def get_value_generator(self_,name): # pylint: disable-msg=E0213
15772182 """
16322237
16332238 return value
16342239
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
2240 def method_dependencies(self_, name, intermediate=False):
2241 """
2242 Given the name of a method, returns a PInfo object for each dependency
2243 of this method. See help(PInfo) for the contents of these objects.
2244
2245 By default intermediate dependencies on sub-objects are not
2246 returned as these are primarily useful for internal use to
2247 determine when a sub-object dependency has to be updated.
2248 """
2249 method = getattr(self_.self_or_cls, name)
2250 minfo = MInfo(cls=self_.cls, inst=self_.self, name=name,
2251 method=method)
2252 deps, dynamic = _params_depended_on(
2253 minfo, dynamic=False, intermediate=intermediate)
2254 if self_.self is None:
2255 return deps
2256 return _resolve_mcs_deps(
2257 self_.self, deps, dynamic, intermediate=intermediate)
2258
2259 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
2260 params_depended_on = method_dependencies
16392261
16402262 def outputs(self_):
16412263 """
16562278 outputs[name] = (otype, method, idx)
16572279 return outputs
16582280
1659
1660 def _spec_to_obj(self_,spec):
1661 # TODO: when we decide on spec, this method should be
1662 # rewritten
1663
2281 def _spec_to_obj(self_, spec, dynamic=True, intermediate=True):
2282 """
2283 Resolves a dependency specification into lists of explicit
2284 parameter dependencies and dynamic dependencies.
2285
2286 Dynamic dependencies are specifications to be resolved when
2287 the sub-object whose parameters are being depended on is
2288 defined.
2289
2290 During class creation dynamic=False which means sub-object
2291 dependencies are not resolved. At instance creation and
2292 whenever a sub-object is set on an object this method will be
2293 invoked to determine whether the dependency is available.
2294
2295 For sub-object dependencies we also return dependencies for
2296 every part of the path, e.g. for a dependency specification
2297 like "a.b.c" we return dependencies for sub-object "a" and the
2298 sub-sub-object "b" in addition to the dependency on the actual
2299 parameter "c" on object "b". This is to ensure that if a
2300 sub-object is swapped out we are notified and can update the
2301 dynamic dependency to the new object. Even if a sub-object
2302 dependency can only partially resolved, e.g. if object "a"
2303 does not yet have a sub-object "b" we must watch for changes
2304 to "b" on sub-object "a" in case such a subobject is put in "b".
2305 """
16642306 if isinstance(spec, Parameter):
16652307 inst = spec.owner if isinstance(spec.owner, Parameterized) else None
16662308 cls = spec.owner if inst is None else type(inst)
16672309 info = PInfo(inst=inst, cls=cls, name=spec.name,
16682310 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
2311 return [] if intermediate == 'only' else [info], []
2312
2313 obj, attr, what = _parse_dependency_spec(spec)
2314 if obj is None:
2315 src = self_.self_or_cls
2316 elif not dynamic:
2317 return [], [DInfo(spec=spec)]
2318 else:
2319 src = _getattrr(self_.self_or_cls, obj[1::], None)
2320 if src is None:
2321 path = obj[1:].split('.')
2322 deps = []
2323 # Attempt to partially resolve subobject path to ensure
2324 # that if a subobject is later updated making the full
2325 # subobject path available we have to be notified and
2326 # set up watchers
2327 if len(path) >= 1 and intermediate:
2328 sub_src = None
2329 subpath = path
2330 while sub_src is None and subpath:
2331 subpath = subpath[:-1]
2332 sub_src = _getattrr(self_.self_or_cls, '.'.join(subpath), None)
2333 if subpath:
2334 subdeps, _ = self_._spec_to_obj(
2335 '.'.join(path[:len(subpath)+1]), dynamic, intermediate)
2336 deps += subdeps
2337 return deps, [] if intermediate == 'only' else [DInfo(spec=spec)]
2338
2339 cls, inst = (src, None) if isinstance(src, type) else (type(src), src)
16842340 if attr == 'param':
1685 dependencies = self_._spec_to_obj(obj[1:])
2341 deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate)
16862342 for p in src.param:
1687 dependencies += src.param._spec_to_obj(p)
1688 return dependencies
2343 param_deps, param_dynamic_deps = src.param._spec_to_obj(p, dynamic, intermediate)
2344 deps += param_deps
2345 dynamic_deps += param_dynamic_deps
2346 return deps, dynamic_deps
16892347 elif attr in src.param:
1690 what = what if what != '' else 'value'
16912348 info = PInfo(inst=inst, cls=cls, name=attr,
16922349 pobj=src.param[attr], what=what)
2350 elif hasattr(src, attr):
2351 info = MInfo(inst=inst, cls=cls, name=attr,
2352 method=getattr(src, attr))
2353 elif src.abstract:
2354 return [], [] if intermediate == 'only' else [DInfo(spec=spec)]
16932355 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'
2356 raise AttributeError("Attribute %r could not be resolved on %s."
2357 % (attr, src))
2358
2359 if obj is None or not intermediate:
2360 return [info], []
2361 deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate)
2362 if intermediate != 'only':
2363 deps.append(info)
2364 return deps, dynamic_deps
2365
2366 def _register_watcher(self_, action, watcher, what='value'):
17002367 parameter_names = watcher.parameter_names
17012368 for parameter_name in parameter_names:
17022369 if parameter_name not in self_.cls.param:
17172384 watchers[what] = []
17182385 getattr(watchers[what], action)(watcher)
17192386
1720 def watch(self_,fn,parameter_names, what='value', onlychanged=True, queued=False):
2387 def watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0):
2388 """
2389 Register the given callback function `fn` to be invoked for
2390 events on the indicated parameters.
2391
2392 `what`: What to watch on each parameter; either the value (by
2393 default) or else the indicated slot (e.g. 'constant').
2394
2395 `onlychanged`: By default, only invokes the function when the
2396 watched item changes, but if `onlychanged=False` also invokes
2397 it when the `what` item is set to its current value again.
2398
2399 `queued`: By default, additional watcher events generated
2400 inside the callback fn are dispatched immediately, effectively
2401 doing depth-first processing of Watcher events. However, in
2402 certain scenarios, it is helpful to wait to dispatch such
2403 downstream events until all events that triggered this watcher
2404 have been processed. In such cases setting `queued=True` on
2405 this Watcher will queue up new downstream events generated
2406 during `fn` until `fn` completes and all other watchers
2407 invoked by that same event have finished executing),
2408 effectively doing breadth-first processing of Watcher events.
2409
2410 `precedence`: Declares a precedence level for the Watcher that
2411 determines the priority with which the callback is executed.
2412 Lower precedence levels are executed earlier. Negative
2413 precedences are reserved for internal Watchers, i.e. those
2414 set up by param.depends.
2415
2416 When the `fn` is called, it will be provided the relevant
2417 Event objects as positional arguments, which allows it to
2418 determine which of the possible triggering events occurred.
2419
2420 Returns a Watcher object.
2421
2422 See help(Watcher) and help(Event) for the contents of those objects.
2423 """
2424 if precedence < 0:
2425 raise ValueError("User-defined watch callbacks must declare "
2426 "a positive precedence. Negative precedences "
2427 "are reserved for internal Watchers.")
2428 return self_._watch(fn, parameter_names, what, onlychanged, queued, precedence)
2429
2430 def _watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=-1):
17212431 parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,)
17222432 watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, mode='args',
17232433 onlychanged=onlychanged, parameter_names=parameter_names,
1724 what=what, queued=queued)
1725 self_._watch('append', watcher, what)
2434 what=what, queued=queued, precedence=precedence)
2435 self_._register_watcher('append', watcher, what)
17262436 return watcher
17272437
1728 def unwatch(self_,watcher):
1729 """
1730 Unwatch watchers set either with watch or watch_values.
2438 def unwatch(self_, watcher):
2439 """
2440 Remove the given Watcher object (from `watch` or `watch_values`) from this object's list.
17312441 """
17322442 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,)
2443 self_._register_watcher('remove', watcher, what=watcher.what)
2444 except Exception:
2445 self_.warning('No such watcher {watcher} to remove.'.format(watcher=str(watcher)))
2446
2447 def watch_values(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0):
2448 """
2449 Easier-to-use version of `watch` specific to watching for changes in parameter values.
2450
2451 Only allows `what` to be 'value', and invokes the callback `fn` using keyword
2452 arguments <param_name>=<new_value> rather than with a list of Event objects.
2453 """
2454 if precedence < 0:
2455 raise ValueError("User-defined watch callbacks must declare "
2456 "a positive precedence. Negative precedences "
2457 "are reserved for internal Watchers.")
2458 assert what == 'value'
2459 if isinstance(parameter_names, list):
2460 parameter_names = tuple(parameter_names)
2461 else:
2462 parameter_names = (parameter_names,)
17402463 watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn,
17412464 mode='kwargs', onlychanged=onlychanged,
1742 parameter_names=parameter_names, what='value',
1743 queued=queued)
1744 self_._watch('append', watcher, what)
2465 parameter_names=parameter_names, what=what,
2466 queued=queued, precedence=precedence)
2467 self_._register_watcher('append', watcher, what)
17452468 return watcher
17462469
1747
17482470 # Instance methods
17492471
1750
2472 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
17512473 def defaults(self_):
17522474 """
17532475 Return {parameter_name:parameter.default} for all non-constant
17582480 """
17592481 self = self_.self
17602482 d = {}
1761 for param_name,param in self.param.objects('existing').items():
2483 for param_name, param in self.param.objects('existing').items():
17622484 if param.constant:
17632485 pass
1764 elif param.instantiate:
1765 self.param._instantiate_param(param,dict_=d,key=param_name)
1766 else:
1767 d[param_name]=param.default
2486 if param.instantiate:
2487 self.param._instantiate_param(param, dict_=d, key=param_name)
2488 d[param_name] = param.default
17682489 return d
17692490
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.
2491 # Designed to avoid any processing unless the print
2492 # level is high enough, though not all callers of message(),
2493 # verbose(), debug(), etc are taking advantage of this.
17742494 def __db_print(self_,level,msg,*args,**kw):
17752495 """
17762496 Calls the logger returned by the get_logger() function,
17862506
17872507 get_logger(name=self_or_cls.name).log(level, msg, *args, **kw)
17882508
2509 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
17892510 def print_param_values(self_):
17902511 """Print the values of all this object's Parameters."""
17912512 self = self_.self
1792 for name,val in self.param.get_param_values():
2513 for name, val in self.param.values().items():
17932514 print('%s.%s = %s' % (self.name,name,val))
17942515
17952516 def warning(self_, msg,*args,**kw):
18002521
18012522 See Python's logging module for details of message formatting.
18022523 """
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
2524 self_.log(WARNING, msg, *args, **kw)
2525
2526 # PARAM2_DEPRECATION: Could be removed post param 2.0
18102527 def message(self_,msg,*args,**kw):
18112528 """
18122529 Print msg merged with args as a message.
18152532 """
18162533 self_.__db_print(INFO,msg,*args,**kw)
18172534
2535 # PARAM2_DEPRECATION: Could be removed post param 2.0
18182536 def verbose(self_,msg,*args,**kw):
18192537 """
18202538 Print msg merged with args as a verbose message.
18232541 """
18242542 self_.__db_print(VERBOSE,msg,*args,**kw)
18252543
2544 # PARAM2_DEPRECATION: Could be removed post param 2.0
18262545 def debug(self_,msg,*args,**kw):
18272546 """
18282547 Print msg merged with args as a debugging statement.
18312550 """
18322551 self_.__db_print(DEBUG,msg,*args,**kw)
18332552
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.
2553 def log(self_, level, msg, *args, **kw):
2554 """
2555 Print msg merged with args as a message at the indicated logging level.
2556
2557 Logging levels include those provided by the Python logging module
2558 plus VERBOSE, either obtained directly from the logging module like
2559 `logging.INFO`, or from parameterized like `param.parameterized.INFO`.
2560
2561 Supported logging levels include (in order of severity)
2562 DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL
2563
2564 See Python's logging module for details of message formatting.
2565 """
2566 if level is WARNING:
2567 if warnings_as_exceptions:
2568 raise Exception("Warning: " + msg % args)
2569 else:
2570 global warning_count
2571 warning_count+=1
2572 self_.__db_print(level, msg, *args, **kw)
2573
2574
2575 def pprint(self_, imports=None, prefix=" ", unknown_value='<?>',
2576 qualify=False, separator=""):
2577 """See Parameterized.pprint"""
2578 self = self_.self
2579 return self._pprint(imports, prefix, unknown_value, qualify, separator)
18442580
18452581
18462582
18672603 attribute __abstract set to True. The 'abstract' attribute can be
18682604 used to find out if a class is abstract or not.
18692605 """
1870 def __init__(mcs,name,bases,dict_):
2606 def __init__(mcs, name, bases, dict_):
18712607 """
18722608 Initialize the class object (not an instance of the class, but
18732609 the class itself).
18762612 default values (see __param_inheritance()) and setting
18772613 attrib_names (see _set_names()).
18782614 """
1879 type.__init__(mcs,name,bases,dict_)
2615 type.__init__(mcs, name, bases, dict_)
18802616
18812617 # Give Parameterized classes a useful 'name' attribute.
1882 # (Could instead consider changing the instance Parameter
1883 # 'name' to '__name__'?)
18842618 mcs.name = name
18852619
1886 mcs.param = Parameters(mcs)
2620 mcs._parameters_state = {
2621 "BATCH_WATCH": False, # If true, Event and watcher objects are queued.
2622 "TRIGGER": False,
2623 "events": [], # Queue of batched events
2624 "watchers": [] # Queue of batched watchers
2625 }
2626 mcs._param = Parameters(mcs)
18872627
18882628 # All objects (with their names) of type Parameter that are
18892629 # defined in this class
1890 parameters = [(n,o) for (n,o) in dict_.items()
1891 if isinstance(o,Parameter)]
2630 parameters = [(n, o) for (n, o) in dict_.items()
2631 if isinstance(o, Parameter)]
2632
2633 mcs._param._parameters = dict(parameters)
18922634
18932635 for param_name,param in parameters:
1894 mcs._initialize_parameter(param_name,param)
2636 mcs._initialize_parameter(param_name, param)
18952637
18962638 # 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')]
2639 dependers = [(n, m, m._dinfo) for (n, m) in dict_.items()
2640 if hasattr(m, '_dinfo')]
18992641
19002642 _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:
2643 for name, method, dinfo in dependers:
19052644 watch = dinfo.get('watch', False)
1906 if watch:
1907 _watch.append((n, watch == 'queued'))
2645 on_init = dinfo.get('on_init', False)
2646 if not watch:
2647 continue
2648 minfo = MInfo(cls=mcs, inst=None, name=name,
2649 method=method)
2650 deps, dynamic_deps = _params_depended_on(minfo, dynamic=False)
2651 _watch.append((name, watch == 'queued', on_init, deps, dynamic_deps))
2652
2653 # Resolve other dependencies in remainder of class hierarchy
2654 for cls in classlist(mcs)[:-1][::-1]:
2655 if not hasattr(cls, '_param'):
2656 continue
2657 for dep in cls.param._depends['watch']:
2658 method = getattr(mcs, dep[0], None)
2659 dinfo = getattr(method, '_dinfo', {'watch': False})
2660 if (not any(dep[0] == w[0] for w in _watch)
2661 and dinfo.get('watch')):
2662 _watch.append(dep)
19082663
19092664 mcs.param._depends = {'watch': _watch}
19102665
19382693
19392694
19402695 def _initialize_parameter(mcs,param_name,param):
1941 # parameter has no way to find out the name a
2696 # A Parameter has no way to find out the name a
19422697 # Parameterized class has for it
19432698 param._set_names(param_name)
19442699 mcs.__param_inheritance(param_name,param)
19452700
19462701
1947 # Python 2.6 added abstract base classes; see
1948 # https://github.com/ioam/param/issues/84
2702 # Should use the official Python 2.6+ abstract base classes; see
2703 # https://github.com/holoviz/param/issues/84
19492704 def __is_abstract(mcs):
19502705 """
19512706 Return True if the class has an attribute __abstract set to True.
19602715 # _ParameterizedMetaclass__abstract before running, but
19612716 # the actual class object will have an attribute
19622717 # _ClassName__abstract. So, we have to mangle it ourselves at
1963 # runtime.
2718 # runtime. Mangling follows description in
2719 # https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
19642720 try:
1965 return getattr(mcs,'_%s__abstract'%mcs.__name__)
2721 return getattr(mcs,'_%s__abstract'%mcs.__name__.lstrip("_"))
19662722 except AttributeError:
19672723 return False
19682724
19692725 abstract = property(__is_abstract)
19702726
1971
1972
1973 def __setattr__(mcs,attribute_name,value):
2727 def _get_param(mcs):
2728 return mcs._param
2729
2730 param = property(_get_param)
2731
2732 def __setattr__(mcs, attribute_name, value):
19742733 """
19752734 Implements 'self.attribute_name=value' in a way that also supports Parameters.
19762735
20122771 # (For instance, python's own pickling mechanism
20132772 # caches __slotnames__ on the class:
20142773 # 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.
2774 # This warning bypasses the usual mechanisms, which
2775 # has have consequences for warning counts, warnings
2776 # as exceptions, etc.
20182777 if not attribute_name.startswith('_'):
20192778 get_logger().log(WARNING,
20202779 "Setting non-Parameter class attribute %s.%s = %s ",
20482807 Note that instantiate is handled differently: if there is a
20492808 parameter with the same name in one of the superclasses with
20502809 instantiate set to True, this parameter will inherit
2051 instatiate=True.
2810 instantiate=True.
20522811 """
20532812 # get all relevant slots (i.e. slots defined in all
20542813 # superclasses of this parameter)
21122871
21132872
21142873
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
21192874 # Whether script_repr should avoid reporting the values of parameters
21202875 # that are just inheriting their values from the class defaults.
2876 # Because deepcopying creates a new object, cannot detect such
2877 # inheritance when instantiate = True, so such values will be printed
2878 # even if they are just being copied from the default.
21212879 script_repr_suppress_defaults=True
21222880
21232881
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=[],
2882 def script_repr(val, imports=None, prefix="\n ", settings=[],
2883 qualify=True, unknown_value=None, separator="\n",
2884 show_imports=True):
2885 """
2886 Variant of pprint() designed for generating a (nearly) runnable script.
2887
2888 The output of script_repr(parameterized_obj) is meant to be a
2889 string suitable for running using `python file.py`. Not every
2890 object is guaranteed to have a runnable script_repr
2891 representation, but it is meant to be a good starting point for
2892 generating a Python script that (after minor edits) can be
2893 evaluated to get a newly initialized object similar to the one
2894 provided.
2895
2896 The new object will only have the same parameter state, not the
2897 same internal (attribute) state; the script_repr captures only
2898 the state of the Parameters of that object and not any other
2899 attributes it may have.
2900
2901 If show_imports is True (default), includes import statements
2902 for each of the modules required for the objects being
2903 instantiated. This list may not be complete, as it typically
2904 includes only the imports needed for the Parameterized object
2905 itself, not for values that may have been supplied to Parameters.
2906
2907 Apart from show_imports, accepts the same arguments as pprint(),
2908 so see pprint() for explanations of the arguments accepted. The
2909 default values of each of these arguments differ from pprint() in
2910 ways that are more suitable for saving as a separate script than
2911 for e.g. pretty-printing at the Python prompt.
2912 """
2913
2914 if imports is None:
2915 imports = []
2916
2917 rep = pprint(val, imports, prefix, settings, unknown_value,
2918 qualify, separator)
2919
2920 imports = list(set(imports))
2921 imports_str = ("\n".join(imports) + "\n\n") if show_imports else ""
2922
2923 return imports_str + rep
2924
2925
2926 # PARAM2_DEPRECATION: Remove entirely unused settings argument
2927 def pprint(val,imports=None, prefix="\n ", settings=[],
21482928 unknown_value='<?>', qualify=False, separator=''):
21492929 """
2150 (Experimental) Pretty printed representation of a parameterized
2930 Pretty printed representation of a parameterized
21512931 object that may be evaluated with eval.
21522932
21532933 Similar to repr except introspection of the constructor (__init__)
21812961 (e.g. a newline could be supplied to have each Parameter appear on a
21822962 separate line).
21832963
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.
2964 Instances of types that require special handling can use the
2965 script_repr_reg dictionary. Using the type as a key, add a
2966 function that returns a suitable representation of instances of
2967 that type, and adds the required import statement. The repr of a
2968 parameter can be suppressed by returning None from the appropriate
2969 hook in script_repr_reg.
2970 """
2971
2972 if imports is None:
2973 imports = []
2974
21912975 if isinstance(val,type):
21922976 rep = type_script_repr(val,imports,prefix,settings)
21932977
21942978 elif type(val) in script_repr_reg:
21952979 rep = script_repr_reg[type(val)](val,imports,prefix,settings)
21962980
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
2981 elif hasattr(val,'_pprint'):
2982 rep=val._pprint(imports=imports, prefix=prefix+" ",
2983 qualify=qualify, unknown_value=unknown_value,
2984 separator=separator)
22062985 else:
22072986 rep=repr(val)
22082987
22092988 return rep
22102989
22112990
2212 #: see script_repr()
2991 # Registry for special handling for certain types in script_repr and pprint
22132992 script_repr_reg = {}
22142993
22152994
22463025 pass # Support added only if those libraries are available
22473026
22483027
2249 # why I have to type prefix and settings?
22503028 def function_script_repr(fn,imports,prefix,settings):
22513029 name = fn.__name__
22523030 module = fn.__module__
22713049 dbprint_prefix=None
22723050
22733051
2274
2275
2276
3052 # Copy of Python 3.2 reprlib's recursive_repr but allowing extra arguments
3053 if sys.version_info.major >= 3:
3054 from threading import get_ident
3055 def recursive_repr(fillvalue='...'):
3056 'Decorator to make a repr function return fillvalue for a recursive call'
3057
3058 def decorating_function(user_function):
3059 repr_running = set()
3060
3061 def wrapper(self, *args, **kwargs):
3062 key = id(self), get_ident()
3063 if key in repr_running:
3064 return fillvalue
3065 repr_running.add(key)
3066 try:
3067 result = user_function(self, *args, **kwargs)
3068 finally:
3069 repr_running.discard(key)
3070 return result
3071 return wrapper
3072
3073 return decorating_function
3074 else:
3075 def recursive_repr(fillvalue='...'):
3076 def decorating_function(user_function):
3077 return user_function
3078 return decorating_function
22773079
22783080
22793081 @add_metaclass(ParameterizedMetaclass)
23203122 see documentation for the 'logging' module.
23213123 """
23223124
2323 name = String(default=None,constant=True,doc="""
2324 String identifier for this object.""")
2325
2326
2327 def __init__(self,**params):
3125 name = String(default=None, constant=True, doc="""
3126 String identifier for this object.""")
3127
3128 def __init__(self, **params):
23283129 global object_count
23293130
23303131 # Flag that can be tested to see if e.g. constant Parameters
23313132 # 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)
3133 self.initialized = False
3134 self._parameters_state = {
3135 "BATCH_WATCH": False, # If true, Event and watcher objects are queued.
3136 "TRIGGER": False,
3137 "events": [], # Queue of batched events
3138 "watchers": [] # Queue of batched watchers
3139 }
23353140 self._instance__params = {}
23363141 self._param_watchers = {}
3142 self._dynamic_watchers = defaultdict(list)
23373143
23383144 self.param._generate_name()
23393145 self.param._setup_params(**params)
23403146 object_count += 1
23413147
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
3148 self.param._update_deps(init=True)
3149
3150 self.initialized = True
3151
3152 @property
3153 def param(self):
3154 return Parameters(self.__class__, self=self)
23563155
23573156 # 'Special' methods
23583157
23623161 copy of the object's __dict__ and that also includes the
23633162 object's __slots__ (if it has any).
23643163 """
2365 # remind me, why is it a copy? why not just state.update(self.__dict__)?
3164 # Unclear why this is a copy and not simply state.update(self.__dict__)
23663165 state = self.__dict__.copy()
2367
23683166 for slot in get_occupied_slots(self):
23693167 state[slot] = getattr(self,slot)
23703168
23853183 """
23863184 self.initialized=False
23873185
3186 # When making a copy the internal watchers have to be
3187 # recreated and point to the new instance
3188 if '_param_watchers' in state:
3189 param_watchers = state['_param_watchers']
3190 for p, attrs in param_watchers.items():
3191 for attr, watchers in attrs.items():
3192 new_watchers = []
3193 for watcher in watchers:
3194 watcher_args = list(watcher)
3195 if watcher.inst is not None:
3196 watcher_args[0] = self
3197 fn = watcher.fn
3198 if hasattr(fn, '_watcher_name'):
3199 watcher_args[2] = _m_caller(self, fn._watcher_name)
3200 elif get_method_owner(fn) is watcher.inst:
3201 watcher_args[2] = getattr(self, fn.__name__)
3202 new_watchers.append(Watcher(*watcher_args))
3203 param_watchers[p][attr] = new_watchers
3204
23883205 if '_instance__params' not in state:
23893206 state['_instance__params'] = {}
23903207 if '_param_watchers' not in state:
23913208 state['_param_watchers'] = {}
3209 state.pop('param', None)
23923210
23933211 for name,value in state.items():
23943212 setattr(self,name,value)
23953213 self.initialized=True
23963214
3215 @recursive_repr()
23973216 def __repr__(self):
23983217 """
23993218 Provide a nearly valid Python representation that could be used to recreate
24043223 """
24053224 try:
24063225 settings = ['%s=%s' % (name, repr(val))
2407 for name,val in self.param.get_param_values()]
3226 # PARAM2_DEPRECATION: Update to self.param.values.items()
3227 # (once python2 support is dropped)
3228 for name, val in self.param.get_param_values()]
24083229 except RuntimeError: # Handle recursion in parameter depth
24093230 settings = []
24103231 return self.__class__.__name__ + "(" + ", ".join(settings) + ")"
24143235 return "<%s %s>" % (self.__class__.__name__,self.name)
24153236
24163237
3238 # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead
24173239 def script_repr(self,imports=[],prefix=" "):
24183240 """
2419 Variant of __repr__ designed for generating a runnable script.
3241 Deprecated variant of __repr__ designed for generating a runnable script.
24203242 """
24213243 return self.pprint(imports,prefix, unknown_value=None, qualify=True,
24223244 separator="\n")
24233245
2424 # CEBALERT: not yet properly documented
2425 def pprint(self, imports=None, prefix=" ", unknown_value='<?>',
3246 @recursive_repr()
3247 def _pprint(self, imports=None, prefix=" ", unknown_value='<?>',
24263248 qualify=False, separator=""):
24273249 """
24283250 (Experimental) Pretty printed representation that may be
24293251 evaluated with eval. See pprint() function for more details.
24303252 """
24313253 if imports is None:
2432 imports = []
2433
2434 # CEBALERT: imports should just be a set rather than a list;
2435 # change in next release?
3254 imports = [] # would have been simpler to use a set from the start
24363255 imports[:] = list(set(imports))
3256
24373257 # Generate import statement
24383258 mod = self.__module__
24393259 bits = mod.split('.')
24403260 imports.append("import %s"%mod)
24413261 imports.append("import %s"%bits[0])
24423262
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__)
3263 changed_params = self.param.values(onlychanged=script_repr_suppress_defaults)
3264 values = self.param.values()
3265 spec = getfullargspec(self.__init__)
24463266 args = spec.args[1:] if spec.args[0] == 'self' else spec.args
24473267
24483268 if spec.defaults is not None:
24863306 if k in posargs:
24873307 # value will be unknown_value unless k is a parameter
24883308 arglist.append(value)
2489 elif k in kwargs or (spec.keywords is not None):
3309 elif (k in kwargs or
3310 (hasattr(spec, 'varkw') and (spec.varkw is not None)) or
3311 (hasattr(spec, 'keywords') and (spec.keywords is not None))):
24903312 # Explicit modified keywords or parameters in
24913313 # precendence order (if **kwargs present)
24923314 keywords.append('%s=%s' % (k, value))
24973319 arguments = arglist + keywords + (['**%s' % spec.varargs] if spec.varargs else [])
24983320 return qualifier + '%s(%s)' % (self.__class__.__name__, (','+separator+prefix).join(arguments))
24993321
2500
2501 # CEBALERT: note there's no state_push method on the class, so
3322 # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
3323 pprint = _pprint
3324
3325 # Note that there's no state_push method on the class, so
25023326 # dynamic parameters set on a class can't have state saved. This
25033327 # is because, to do this, state_push() would need to be a
25043328 # @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.)
3329 # already have a state_push() method.
3330 # (isinstance(g,Parameterized) below is used to exclude classes.)
25073331
25083332 def state_push(self):
25093333 """
26443468
26453469
26463470
2647 # Note that with Python 2.6, a fn's **args no longer has to be a
3471 # As of Python 2.6+, a fn's **args no longer has to be a
26483472 # dictionary. This might allow us to use a decorator to simplify using
26493473 # ParamOverrides (if that does indeed make them simpler to use).
26503474 # http://docs.python.org/whatsnew/2.6.html
26713495 supplied dict_ that are not also parameters of the overridden
26723496 object will be available via the extra_keywords() method.
26733497 """
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
3498 # This method should be fast because it's going to be
3499 # called a lot. This _might_ be faster (not tested):
26773500 # def __init__(self,overridden,**kw):
26783501 # ...
26793502 # dict.__init__(self,**kw)
2680 # be faster/easier to use?
26813503 self._overridden = overridden
26823504 dict.__init__(self,dict_)
26833505
26893511 def extra_keywords(self):
26903512 """
26913513 Return a dictionary containing items from the originally
2692 supplied dict_ whose names are not parameters of the
3514 supplied `dict_` whose names are not parameters of the
26933515 overridden object.
26943516 """
26953517 return self._extra_keywords
26973519 def param_keywords(self):
26983520 """
26993521 Return a dictionary containing items from the originally
2700 supplied dict_ whose names are parameters of the
3522 supplied `dict_` whose names are parameters of the
27013523 overridden object (i.e. not extra keywords/parameters).
27023524 """
27033525 return dict((key, self[key]) for key in self if key not in self.extra_keywords())
27543576 for name, val in params.items():
27553577 if name not in overridden_object_params:
27563578 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()?
3579 # Could remove name from params (i.e. del params[name])
3580 # so that it's only available via extra_keywords()
27603581 return extra_keywords
27613582
27623583
27773598 """
27783599 __abstract = True
27793600
2780 # CEBALERT: shouldn't this have come from a parent class
2781 # somewhere?
27823601 def __str__(self):
27833602 return self.__class__.__name__+"()"
27843603
27933612 cls = self_or_cls
27943613 else:
27953614 p = params
2796 params = dict(self_or_cls.get_param_values())
3615 params = self_or_cls.param.values()
27973616 params.update(p)
27983617 params.pop('name')
27993618 cls = self_or_cls.__class__
28173636 # Control reconstruction (during unpickling and copying):
28183637 # ensure that ParameterizedFunction.__new__ is skipped
28193638 state = ParameterizedFunction.__getstate__(self)
2820 # CB: here it's necessary to use a function defined at the
3639 # Here it's necessary to use a function defined at the
28213640 # module level rather than Parameterized.__new__ directly
28223641 # because otherwise pickle will find .__new__'s module to be
2823 # __main__. Pretty obscure aspect of pickle.py, or a bug?
3642 # __main__. Pretty obscure aspect of pickle.py...
28243643 return (_new_parameterized,(self.__class__,),state)
28253644
3645 # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead
28263646 def script_repr(self,imports=[],prefix=" "):
28273647 """
28283648 Same as Parameterized.script_repr, except that X.classname(Y
28323652 separator="\n")
28333653
28343654
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
3655 def _pprint(self, imports=None, prefix="\n ",unknown_value='<?>',
3656 qualify=False, separator=""):
3657 """
3658 Same as Parameterized._pprint, except that X.classname(Y
28393659 is replaced with X.classname.instance(Y
28403660 """
2841 r = Parameterized.pprint(self,imports,prefix,
2842 unknown_value=unknown_value,
2843 qualify=qualify,separator=separator)
3661 r = Parameterized._pprint(self,imports,prefix,
3662 unknown_value=unknown_value,
3663 qualify=qualify,separator=separator)
28443664 classname=self.__class__.__name__
28453665 return r.replace(".%s("%classname,".%s.instance("%classname)
28463666
28733693 label_formatter = default_label_formatter
28743694
28753695
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
3696 # PARAM2_DEPRECATION: Should be able to remove this; was originally
3697 # adapted from OProperty from
28843698 # infinitesque.net/articles/2005/enhancing%20Python's%20property.xhtml
3699 # but since python 2.6 the getter, setter, and deleter attributes of
3700 # a property should provide similar functionality already.
28853701 class overridable_property(object):
28863702 """
28873703 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
11 Provide consistent and up-to-date ``__version__`` strings for
22 Python packages.
33
4 See https://github.com/pyviz/autover for more information.
4 See https://github.com/holoviz/autover for more information.
55 """
66
77 # The Version class is a copy of autover.version.Version v0.2.5,
2525 cwd=cwd)
2626 output, error = (str(s.decode()).strip() for s in proc.communicate())
2727
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:
2932 raise Exception(proc.returncode, error)
3033 return output
3134
173176 # Verify this is the correct repository (since fpath could
174177 # be an unrelated git repository, and autover could just have
175178 # 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' ,
180182 # A remote 'server:reponame.git' can also be referred
181183 # to (i.e. cloned) as `server:reponame`.
182184 '/' + 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))
189196 if as_string: return output
190197 except Exception as e1:
191198 try:
11 universal = 1
22
33 [flake8]
4 # TODO tests (one day)
4 # TODO tests should not be excluded (one day...)
55 include = setup.py param numbergen
6 exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,tests
7 ignore = E1,
6 exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,tests,.ipynb_checkpoints,run_test.py
7 ignore = E114,
8 E116,
9 E126,
10 E128,
11 E129,
812 E2,
913 E3,
1014 E4,
1115 E5,
16 E731,
1217 E701,
1318 E702,
1419 E703,
15 E704,
20 E704,
1621 E722,
1722 E741,
1823 E742,
1924 E743,
20 W503
25 W503,
26 W504,
2127
22 [nosetests]
23 verbosity = 2
24 with-doctest = 1
25 nologcapture = 1
28 [tool:pytest]
29 python_files = test*.py
1818 # pip doesn't support tests_require
1919 # (https://github.com/pypa/pip/issues/1197)
2020 'tests': [
21 'nose',
22 'flake8'
21 'pytest',
22 'pytest-cov',
23 'flake8',
24 ],
25 'doc': [
26 'pygraphviz',
27 'nbsite >=0.6.1',
28 'pydata-sphinx-theme',
29 'myst-parser',
30 'nbconvert <6.0',
31 'graphviz',
32 'myst_nb ==0.12.2',
33 'sphinx-copybutton',
34 'aiohttp',
35 'panel',
36 'pandas',
2337 ]
2438 }
2539
3145 setup_args = dict(
3246 name='param',
3347 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",
48 description='Make your Python code clearer and more reliable by declaring Parameters.',
49 long_description=open('README.md').read() if os.path.isfile('README.md') else 'Consult README.md',
50 long_description_content_type="text/markdown",
51 author="HoloViz",
52 author_email="developers@holoviz.org",
53 maintainer="HoloViz",
54 maintainer_email="developers@holoviz.org",
4055 platforms=['Windows', 'Mac OS X', 'Linux'],
4156 license='BSD',
42 url='http://ioam.github.com/param/',
57 url='http://param.holoviz.org/',
4358 packages=["param","numbergen"],
4459 provides=["param","numbergen"],
4560 include_package_data = True,
4762 install_requires=[],
4863 extras_require=extras_require,
4964 tests_require=extras_require['tests'],
65 project_urls={
66 "Documentation": "https://param.holoviz.org/",
67 "Releases": "https://github.com/holoviz/param/releases",
68 "Bug Tracker": "https://github.com/holoviz/param/issues",
69 "Source Code": "https://github.com/holoviz/param",
70 "Panel Examples": "https://panel.holoviz.org/user_guide/Param.html",
71 },
5072 classifiers=[
5173 "License :: OSI Approved :: BSD License",
5274 "Development Status :: 5 - Production/Stable",
5375 "Programming Language :: Python :: 2",
5476 "Programming Language :: Python :: 2.7",
5577 "Programming Language :: Python :: 3",
56 "Programming Language :: Python :: 3.4",
57 "Programming Language :: Python :: 3.5",
5878 "Programming Language :: Python :: 3.6",
79 "Programming Language :: Python :: 3.7",
80 "Programming Language :: Python :: 3.8",
81 "Programming Language :: Python :: 3.9",
82 "Programming Language :: Python :: 3.10",
5983 "Operating System :: OS Independent",
6084 "Intended Audience :: Science/Research",
6185 "Intended Audience :: Developers",
22 """
33
44 import unittest
5 import pytest
56 import datetime as dt
67 import param
78
5051 dt.date(2017,2,25)))
5152 self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1),
5253 dt.date(2017,2,25)))
54
55 def test_datetime_not_accepted(self):
56 with pytest.raises(ValueError):
57 param.CalendarDate(dt.datetime(2021, 8, 16, 10))
66
77 import param
88
9 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
10 unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
911
1012 class TestClassSelectorParameters(unittest.TestCase):
1113
2426 self.assertEqual(p.e, 6)
2527
2628 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 exception = "ClassSelector parameter 'e' value must be an instance of int, not 'a'."
30 with self.assertRaisesRegex(ValueError, exception):
2931 self.P(e='a')
3032
3133 def test_single_class_type_constructor(self):
3335 self.assertEqual(p.f, float)
3436
3537 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 exception = "ClassSelector parameter 'f' must be a subclass of Number, not 'str'."
39 with self.assertRaisesRegex(ValueError, exception):
3840 self.P(f=str)
3941
4042 def test_multiple_class_instance_constructor1(self):
4648 self.assertEqual(p.g, 'A')
4749
4850 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 exception = r"ClassSelector parameter 'g' value must be an instance of \(int, str\), not 3.0."
52 with self.assertRaisesRegex(ValueError, exception):
5153 self.P(g=3.0)
5254
5355 def test_multiple_class_type_constructor1(self):
5961 self.assertEqual(p.h, str)
6062
6163 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 exception = r"ClassSelector parameter 'h' must be a subclass of \(int, str\), not 'float'."
65 with self.assertRaisesRegex(ValueError, exception):
6466 self.P(h=float)
1010 def test_initialization_invalid_string(self):
1111 try:
1212 class Q(param.Parameterized):
13 q = param.Color('red')
13 q = param.Color('red', allow_named=False)
1414 except ValueError:
1515 pass
1616 else:
17 raise AssertionError("No exception raised on out-of-bounds date")
17 raise AssertionError("No exception raised on invalid color")
1818
1919 def test_set_invalid_string(self):
2020 class Q(param.Parameterized):
21 q = param.Color()
21 q = param.Color(allow_named=False)
2222 try:
2323 Q.q = 'red'
2424 except ValueError:
2525 pass
2626 else:
27 raise AssertionError("No exception raised on out-of-bounds date")
27 raise AssertionError("No exception raised on invalid color")
28
29 def test_set_invalid_named_color(self):
30 class Q(param.Parameterized):
31 q = param.Color(allow_named=True)
32 try:
33 Q.q = 'razzmatazz'
34 except ValueError:
35 pass
36 else:
37 raise AssertionError("No exception raised on invalid color")
38
39 def test_invalid_long_hex(self):
40 class Q(param.Parameterized):
41 q = param.Color()
42 try:
43 Q.q = '#gfffff'
44 except ValueError:
45 pass
46 else:
47 raise AssertionError("No exception raised on invalid color")
2848
2949 def test_valid_long_hex(self):
3050 class Q(param.Parameterized):
3858 Q.q = '#fff'
3959 self.assertEqual(Q.q, '#fff')
4060
61 def test_valid_named_color(self):
62 class Q(param.Parameterized):
63 q = param.Color(allow_named=True)
64 Q.q = 'indianred'
65 self.assertEqual(Q.q, 'indianred')
9191 # get_value_generator() should give the objects
9292 self.assertEqual(ix(), 2)
9393 self.assertEqual(iy(), 5)
94
95
96 if __name__ == "__main__":
97 import nose
98 nose.runmodule()
22 """
33
44 import unittest
5
6 import pytest
57
68 from param.parameterized import add_metaclass
79 from param import concrete_descendents, Parameter
1214
1315
1416 positional_args = {
15 ClassSelector: (object,)
17 # ClassSelector: (object,)
1618 }
1719
18 skip = []
20 skip = ['ClassSelector']
1921
2022 try:
2123 import numpy # noqa
2931 skip.append('Series')
3032
3133
32 class TestDefaultsMetaclass(type):
34 class DefaultsMetaclassTest(type):
3335 def __new__(mcs, name, bases, dict_):
3436
3537 def test_skip(*args,**kw):
36 from nose.exc import SkipTest
37 raise SkipTest
38 pytest.skip()
3839
3940 def add_test(p):
4041 def test(self):
4950 return type.__new__(mcs, name, bases, dict_)
5051
5152
52 @add_metaclass(TestDefaultsMetaclass)
53 @add_metaclass(DefaultsMetaclassTest)
5354 class TestDefaults(unittest.TestCase):
5455 pass
55
56
57 if __name__ == "__main__":
58 import nose
59 nose.runmodule()
246246 self.assertNotEqual(call_1, t12.x)
247247
248248
249
250 if __name__ == "__main__":
251 import nose
252 nose.runmodule()
253
254
255249 # Commented out block in the original doctest version.
256250 # Maybe these are features originally planned but never implemented
257251
1919 # SkipTest will be raised if IPython unavailable
2020 from param.ipython import ParamPager
2121
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"""
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 \nw 4 Number C RO \nv 4 Number C RW \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;31mw: < No docstring available >\x1b[0m\n\x1b[1;34mv: < 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"""
2323
2424
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"""
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 \nw 4 Number C RO \nv 4 Number C RW \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;31mw: < No docstring available >\x1b[0m\n\x1b[1;34mv: < 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"""
2626
2727 class TestParamPager(unittest.TestCase):
2828
2929 def setUp(self):
3030 self.maxDiff = None
31
3132 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))
33 u = param.Number(4, precedence=0)
34 w = param.Number(4, readonly=True, precedence=1)
35 v = param.Number(4, constant=True, precedence=2)
36 x = param.String(None, allow_None=True, precedence=3)
37 y = param.Number(4, bounds=(-1, None), precedence=4)
38 z = param.Number(4, bounds=(-1, 100), softbounds=(-100, -200), precedence=5)
3839
3940 self.TestClass = TestClass
4041 self.pager = ParamPager()
4243 def test_parameterized_class(self):
4344 page_string = self.pager(self.TestClass)
4445 # 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)
46 page_string = re.sub(r'TestClass(\d+)', 'TestClass', page_string)
47 ref_string = re.sub(r'TestClass(\d+)', 'TestClass', test1_repr)
4748
4849 try:
4950 self.assertEqual(page_string, ref_string)
5556 def test_parameterized_instance(self):
5657 page_string = self.pager(self.TestClass())
5758 # 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)
59 page_string = re.sub(r'TestClass(\d+)', 'TestClass', page_string)
60 ref_string = re.sub(r'TestClass(\d+)', 'TestClass', test2_repr)
6061
6162 try:
6263 self.assertEqual(page_string, ref_string)
154154
155155 with self.assertRaises(TypeError):
156156 Q.params('r').compute_default()
157
158 if __name__ == "__main__":
159 import nose
160 nose.runmodule()
3232 for _ in range(_iterations):
3333 value = gen()
3434 self.assertTrue(lbound <= value < ubound)
35
36 if __name__ == "__main__":
37 import nose
38 nose.runmodule()
3232
3333 z = Z(z=numpy.array([1,2]))
3434 _is_array_and_equal(z.z,[1,2])
35
36
37 if __name__ == "__main__":
38 import nose
39 nose.runmodule()
100100 pass
101101 else:
102102 raise AssertionError("ObjectSelector created without range.")
103
104
105 if __name__ == "__main__":
106 import nose
107 nose.runmodule()
99
1010
1111 import random
12 from nose.tools import istest, nottest
13
1412
1513 from param.parameterized import ParamOverrides, shared_parameters
1614
17 @nottest
1815 class _SomeRandomNumbers(object):
1916 def __call__(self):
2017 return random.random()
2118
22 @nottest
2319 class TestPO(param.Parameterized):
20 __test__ = False
21
2422 inst = param.Parameter(default=[1,2,3],instantiate=True)
2523 notinst = param.Parameter(default=[1,2,3],instantiate=False)
2624 const = param.Parameter(default=1,constant=True)
2927
3028 dyn = param.Dynamic(default=1)
3129
32 @nottest
3330 class AnotherTestPO(param.Parameterized):
3431 instPO = param.Parameter(default=TestPO(),instantiate=True)
3532 notinstPO = param.Parameter(default=TestPO(),instantiate=False)
3633
37 @nottest
3834 class TestAbstractPO(param.Parameterized):
35 __test__ = False
36
3937 __abstract = True
4038
41 @nottest
39 class _AnotherAbstractPO(param.Parameterized):
40 __abstract = True
41
42
4243 class TestParamInstantiation(AnotherTestPO):
44 __test__ = False
45
4346 instPO = param.Parameter(default=AnotherTestPO(),instantiate=False)
4447
45 @istest
4648 class TestParameterized(unittest.TestCase):
4749
4850 def test_constant_parameter(self):
117119 def test_abstract_class(self):
118120 """Check that a class declared abstract actually shows up as abstract."""
119121 self.assertEqual(TestAbstractPO.abstract,True)
122 self.assertEqual(_AnotherAbstractPO.abstract,True)
120123 self.assertEqual(TestPO.abstract,False)
121124
122125
154157
155158 from param import parameterized
156159
157 @nottest
158160 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
161 __test__ = False
162
163 num_phase = param.Number(18)
164 frequencies = param.List([99])
165 scale = param.Number(0.3)
166
167 def __call__(self,**params_to_override):
168 params = parameterized.ParamOverrides(self,params_to_override)
169 num_phase = params['num_phase']
170 frequencies = params['frequencies']
171 scale = params['scale']
172 return scale,num_phase,frequencies
169173
170174 instance = some_fn.instance()
171175
172 @istest
173176 class TestParameterizedFunction(unittest.TestCase):
174177
175178 def _basic_tests(self,fn):
195198 self.assertEqual(i(),(0.3,18,[10,20,30]))
196199
197200
198 @nottest
199201 class TestPO1(param.Parameterized):
202 __test__ = False
203
200204 x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1))
201205 y = param.Number(default=1,bounds=(-1,1))
202206
203 @istest
204207 class TestNumberParameter(unittest.TestCase):
205208
206209 def test_outside_bounds(self):
225228 assert False, "Should raise ValueError."
226229
227230
228 @istest
229231 class TestStringParameter(unittest.TestCase):
230232
231233 def setUp(self):
250252
251253
252254
253 @istest
254255 class TestParamOverrides(unittest.TestCase):
255256
256257 def setUp(self):
291292 def test_shared_list(self):
292293 self.assertTrue(self.p1.inst is self.p2.inst)
293294 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()
158158
159159 self.assertEqual(obj.pprint(qualify=True),
160160 "tests.API0.testparameterizedrepr."+r)
161
162
163
164 if __name__ == "__main__":
165 import nose
166 nose.runmodule()
00 """
11 Unit test for String parameters
22 """
3
3 import sys
44 import unittest
55
66 import param
77
88
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]?)$'
9 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]?)$'
10
11 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
12 unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
1013
1114 class TestStringParameters(unittest.TestCase):
1215
2326
2427 a = A()
2528
26 exception = "String 's' only takes a string value."
27 with self.assertRaisesRegexp(ValueError, exception):
29 cls = 'class' if sys.version_info.major > 2 else 'type'
30 exception = "String parameter 's' only takes a string value, not value of type <%s 'NoneType'>." % cls
31 with self.assertRaisesRegex(ValueError, exception):
2832 a.s = None # because allow_None should be False
2933
3034 def test_default_none(self):
3640 a.s = None # because allow_None should be True with default of None
3741
3842 def test_regex_incorrect(self):
39
4043 class A(param.Parameterized):
4144 s = param.String('0.0.0.0', regex=ip_regex)
4245
4346 a = A()
4447
45 exception = "String 's': '123.123.0.256' does not match regex"
46 with self.assertRaisesRegexp(ValueError, exception):
48 exception = "String parameter 's' value '123.123.0.256' does not match regex '%s'." % ip_regex
49 with self.assertRaises(ValueError) as e:
4750 a.s = '123.123.0.256'
51 self.assertEqual(str(e.exception), exception.replace('\\', '\\\\'))
4852
4953 def test_regex_incorrect_default(self):
50
51 exception = "String 'None': '' does not match regex"
52 with self.assertRaisesRegexp(ValueError, exception):
54 exception = "String parameter None value '' does not match regex '%s'." % ip_regex
55 with self.assertRaises(ValueError) as e:
5356 class A(param.Parameterized):
5457 s = param.String(regex=ip_regex) # default value '' does not match regular expression
55
56
58 self.assertEqual(str(e.exception), exception.replace('\\', '\\\\'))
66 import numbergen
77 import copy
88
9 from nose.plugins.skip import SkipTest
9 import pytest
1010 import fractions
1111
1212 try:
1313 import gmpy
14 except:
15 gmpy = None
16
14 except ImportError:
15 import os
16 if os.getenv('PARAM_TEST_GMPY','0') == '1':
17 raise ImportError("PARAM_TEST_GMPY=1 but gmpy not available.")
18 else:
19 gmpy = None
1720
1821
1922 class TestTimeClass(unittest.TestCase):
7780 self.assertEqual(t(), 1)
7881 self.assertEqual(t.time_type, fractions.Fraction)
7982
83 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
8084 def test_time_init_gmpy(self):
81 if gmpy is None: raise SkipTest
82
8385 t = param.Time(time_type=gmpy.mpq)
8486 self.assertEqual(t(), gmpy.mpq(0))
8587 t.advance(gmpy.mpq(0.25))
8688 self.assertEqual(t(), gmpy.mpq(1,4))
8789
90 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
8891 def test_time_init_gmpy_advanced(self):
89 if gmpy is None: raise SkipTest
9092 t = param.Time(time_type=gmpy.mpq,
9193 timestep=gmpy.mpq(0.25),
9294 until=1.5)
266268 self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi)))
267269
268270
271 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
269272 def test_time_hashing_integers_gmpy(self):
270273 """
271274 Check that hashes for gmpy values at the integers also matches
272275 those of ints, fractions and strings.
273276 """
274 if gmpy is None: raise SkipTest
275277 hashfn = numbergen.Hash("test", input_count=1)
276278 hash_1 = hashfn(1)
277279 hash_42 = hashfn(42)
282284 self.assertEqual(hash_42, hashfn(gmpy.mpq(42)))
283285 self.assertEqual(hash_42, hashfn(42))
284286
287 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
285288 def test_time_hashing_rationals_gmpy(self):
286289 """
287290 Check that hashes of fractions and gmpy mpqs match for some
288291 reasonable rational numbers.
289292 """
290 if gmpy is None: raise SkipTest
291293 pi = "3.141592"
292294 hashfn = numbergen.Hash("test", input_count=1)
293295 self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5)))
294296 self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592)))
295
296
297
298
299 if __name__ == "__main__":
300 import nose
301 nose.runmodule()
77
88 def tearDown(self):
99 param.parameterized.Parameters._disable_stubs = False
10
11
12 # Python 2 compatibility
13 if not hasattr(API1TestCase, 'assertRaisesRegex'):
14 API1TestCase.assertRaisesRegex = API1TestCase.assertRaisesRegexp
15 if not hasattr(API1TestCase, 'assertEquals'):
16 API1TestCase.assertEquals = API1TestCase.assertEqual
33
44
55 import datetime as dt
6 import pytest
67 import param
78 from . import API1TestCase
89
5152 dt.date(2017,2,25)))
5253 self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1),
5354 dt.date(2017,2,25)))
55
56 def test_datetime_not_accepted(self):
57 with pytest.raises(ValueError):
58 param.CalendarDate(dt.datetime(2021, 8, 16, 10))
66
77 import param
88 from . import API1TestCase
9
109
1110 class TestClassSelectorParameters(API1TestCase):
1211
2524 self.assertEqual(p.e, 6)
2625
2726 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):
27 exception = "ClassSelector parameter 'e' value must be an instance of int, not 'a'."
28 with self.assertRaisesRegex(ValueError, exception):
3029 self.P(e='a')
3130
3231 def test_single_class_type_constructor(self):
3433 self.assertEqual(p.f, float)
3534
3635 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):
36 exception = "ClassSelector parameter 'f' must be a subclass of Number, not 'str'."
37 with self.assertRaisesRegex(ValueError, exception):
3938 self.P(f=str)
4039
4140 def test_multiple_class_instance_constructor1(self):
4746 self.assertEqual(p.g, 'A')
4847
4948 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):
49 exception = r"ClassSelector parameter 'g' value must be an instance of \(int, str\), not 3.0."
50 with self.assertRaisesRegex(ValueError, exception):
5251 self.P(g=3.0)
5352
5453 def test_multiple_class_type_constructor1(self):
6665 self.assertIn('str', classes)
6766
6867 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):
68 exception = r"ClassSelector parameter 'h' must be a subclass of \(int, str\), not 'float'."
69 with self.assertRaisesRegex(ValueError, exception):
7170 self.P(h=float)
7271
7372
9190 items = param.Dict(valid_dict)
9291
9392 test = Test()
94 exception = "Parameter 'items' value must be an instance of dict, not '3'"
95 with self.assertRaisesRegexp(ValueError, exception):
93 exception = "Dict parameter 'items' value must be an instance of dict, not 3."
94 with self.assertRaisesRegex(ValueError, exception):
9695 test.items = 3
88 def test_initialization_invalid_string(self):
99 try:
1010 class Q(param.Parameterized):
11 q = param.Color('red')
11 q = param.Color('red', allow_named=False)
1212 except ValueError:
1313 pass
1414 else:
15 raise AssertionError("No exception raised on out-of-bounds date")
15 raise AssertionError("No exception raised on invalid color")
1616
1717 def test_set_invalid_string(self):
1818 class Q(param.Parameterized):
19 q = param.Color()
19 q = param.Color(allow_named=False)
2020 try:
2121 Q.q = 'red'
2222 except ValueError:
2323 pass
2424 else:
25 raise AssertionError("No exception raised on out-of-bounds date")
25 raise AssertionError("No exception raised on invalid color")
26
27 def test_set_invalid_named_color(self):
28 class Q(param.Parameterized):
29 q = param.Color(allow_named=True)
30 try:
31 Q.q = 'razzmatazz'
32 except ValueError:
33 pass
34 else:
35 raise AssertionError("No exception raised on invalid color")
36
37 def test_invalid_long_hex(self):
38 class Q(param.Parameterized):
39 q = param.Color()
40 try:
41 Q.q = '#gfffff'
42 except ValueError:
43 pass
44 else:
45 raise AssertionError("No exception raised on invalid color")
2646
2747 def test_valid_long_hex(self):
2848 class Q(param.Parameterized):
3656 Q.q = '#fff'
3757 self.assertEqual(Q.q, '#fff')
3858
59 def test_valid_named_color(self):
60 class Q(param.Parameterized):
61 q = param.Color(allow_named=True)
62 Q.q = 'indianred'
63 self.assertEqual(Q.q, 'indianred')
9292 # get_value_generator() should give the objects
9393 self.assertEqual(ix(), 2)
9494 self.assertEqual(iy(), 5)
95
96
97 if __name__ == "__main__":
98 import nose
99 nose.runmodule()
00 """
11 Do all subclasses of Parameter supply a valid default?
22 """
3 import pytest
34
45 from param.parameterized import add_metaclass
56 from param import concrete_descendents, Parameter
1011 from . import API1TestCase
1112
1213 positional_args = {
13 ClassSelector: (object,)
14 # ClassSelector: (object,)
1415 }
1516
16 skip = []
17 skip = ['ClassSelector']
1718
1819 try:
1920 import numpy # noqa
2627 skip.append('Series')
2728
2829
29 class TestDefaultsMetaclass(type):
30 class DefaultsMetaclassTest(type):
3031 def __new__(mcs, name, bases, dict_):
3132
3233 def test_skip(*args,**kw):
33 from nose.exc import SkipTest
34 raise SkipTest
34 pytest.skip()
3535
3636 def add_test(p):
3737 def test(self):
4646 return type.__new__(mcs, name, bases, dict_)
4747
4848
49 @add_metaclass(TestDefaultsMetaclass)
49 @add_metaclass(DefaultsMetaclassTest)
5050 class TestDefaults(API1TestCase):
5151 pass
52
53
54 if __name__ == "__main__":
55 import nose
56 nose.runmodule()
247247 self.assertNotEqual(call_1, t12.x)
248248
249249
250
251 if __name__ == "__main__":
252 import nose
253 nose.runmodule()
254
255
256250 # Commented out block in the original doctest version.
257251 # Maybe these are features originally planned but never implemented
258252
1818 # SkipTest will be raised if IPython unavailable
1919 from param.ipython import ParamPager
2020
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"""
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 \nw 4 Number C RO \nv 4 Number C RW \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;31mw: < No docstring available >\x1b[0m\n\x1b[1;34mv: < 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"""
2222
2323
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"""
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 \nw 4 Number C RO \nv 4 Number C RW \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;31mw: < No docstring available >\x1b[0m\n\x1b[1;34mv: < 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"""
2525
2626 class TestParamPager(API1TestCase):
2727
2828 def setUp(self):
2929 super(TestParamPager, self).setUp()
3030 self.maxDiff = None
31
3132 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))
33 u = param.Number(4, precedence=0)
34 w = param.Number(4, readonly=True, precedence=1)
35 v = param.Number(4, constant=True, precedence=2)
36 x = param.String(None, allow_None=True, precedence=3)
37 y = param.Number(4, bounds=(-1, None), precedence=4)
38 z = param.Number(4, bounds=(-1, 100), softbounds=(-100, -200), precedence=5)
3839
3940 self.TestClass = TestClass
4041 self.pager = ParamPager()
4243 def test_parameterized_class(self):
4344 page_string = self.pager(self.TestClass)
4445 # 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)
46 page_string = re.sub(r'TestClass(\d+)', 'TestClass', page_string)
47 ref_string = re.sub(r'TestClass(\d+)', 'TestClass', test1_repr)
4748
4849 try:
4950 self.assertEqual(page_string, ref_string)
5556 def test_parameterized_instance(self):
5657 page_string = self.pager(self.TestClass())
5758 # 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)
59 page_string = re.sub(r'TestClass(\d+)', 'TestClass', page_string)
60 ref_string = re.sub(r'TestClass(\d+)', 'TestClass', test2_repr)
6061
6162 try:
6263 self.assertEqual(page_string, ref_string)
0 """
1 Testing JSON serialization of parameters and the corresponding schemas.
2 """
3
4 import datetime
5 import json
6
7 import param
8
9 from unittest import SkipTest, skipIf
10 from . import API1TestCase
11
12 try:
13 from jsonschema import validate, ValidationError
14 except ImportError:
15 import os
16 if os.getenv('PARAM_TEST_JSONSCHEMA','0') == '1':
17 raise ImportError("PARAM_TEST_JSONSCHEMA=1 but jsonschema not available.")
18 validate = None
19
20 try:
21 import numpy as np
22 ndarray = np.array([[1,2,3],[4,5,6]])
23 except:
24 np, ndarray = None, None
25
26 np_skip = skipIf(np is None, "NumPy is not available")
27
28 try:
29 import pandas as pd
30 df1 = pd.DataFrame({'A':[1,2,3], 'B':[1.1,2.2,3.3]})
31 df2 = pd.DataFrame({'A':[1.1,2.2,3.3], 'B':[1.1,2.2,3.3]})
32 except:
33 pd, df1, df2 = None, None, None
34
35 pd_skip = skipIf(pd is None, "pandas is not available")
36
37 simple_list = [1]
38
39 class TestSet(param.Parameterized):
40
41 __test__ = False
42
43 numpy_params = ['r']
44 pandas_params = ['s','t','u']
45 conditionally_unsafe = ['f', 'o']
46
47 a = param.Integer(default=5, doc='Example doc', bounds=(2,30), inclusive_bounds=(True, False))
48 b = param.Number(default=4.3, allow_None=True)
49 c = param.String(default='foo')
50 d = param.Boolean(default=False)
51 e = param.List([1,2,3], class_=int)
52 f = param.List([1,2,3])
53 g = param.Date(default=datetime.datetime.now())
54 h = param.Tuple(default=(1,2,3), length=3)
55 i = param.NumericTuple(default=(1,2,3,4))
56 j = param.XYCoordinates(default=(32.1, 51.5))
57 k = param.Integer(default=1)
58 l = param.Range(default=(1.1,2.3), bounds=(1,3))
59 m = param.String(default='baz', allow_None=True)
60 n = param.ObjectSelector(default=3, objects=[3,'foo'], allow_None=False)
61 o = param.ObjectSelector(default=simple_list, objects=[simple_list], allow_None=False)
62 p = param.ListSelector(default=[1,4,5], objects=[1,2,3,4,5,6])
63 q = param.CalendarDate(default=datetime.date.today())
64 r = None if np is None else param.Array(default=ndarray)
65 s = None if pd is None else param.DataFrame(default=df1, columns=2)
66 t = None if pd is None else param.DataFrame(default=pd.DataFrame(
67 {'A':[1,2,3], 'B':[1.1,2.2,3.3]}), columns=(1,4), rows=(2,5))
68 u = None if pd is None else param.DataFrame(default=df2, columns=['A', 'B'])
69 v = param.Dict({'1':2})
70
71
72 test = TestSet(a=29)
73
74
75 class TestSerialization(API1TestCase):
76 """
77 Base class for testing serialization of Parameter values
78 """
79
80 mode = None
81
82 __test__ = False
83
84 def _test_serialize(self, obj, pname):
85 serialized = obj.param.serialize_value(pname, mode=self.mode)
86 deserialized = obj.param.deserialize_value(pname, serialized, mode=self.mode)
87 self.assertEqual(deserialized, getattr(obj, pname))
88
89 def test_serialize_integer_class(self):
90 self._test_serialize(TestSet, 'a')
91
92 def test_serialize_integer_instance(self):
93 self._test_serialize(test, 'a')
94
95 def test_serialize_number_class(self):
96 self._test_serialize(TestSet, 'b')
97
98 def test_serialize_number_instance(self):
99 self._test_serialize(test, 'b')
100
101 def test_serialize_string_class(self):
102 self._test_serialize(TestSet, 'c')
103
104 def test_serialize_string_instance(self):
105 self._test_serialize(test, 'c')
106
107 def test_serialize_boolean_class(self):
108 self._test_serialize(TestSet, 'd')
109
110 def test_serialize_boolean_instance(self):
111 self._test_serialize(test, 'd')
112
113 def test_serialize_list_class(self):
114 self._test_serialize(TestSet, 'e')
115
116 def test_serialize_list_instance(self):
117 self._test_serialize(test, 'e')
118
119 def test_serialize_date_class(self):
120 self._test_serialize(TestSet, 'g')
121
122 def test_serialize_date_instance(self):
123 self._test_serialize(test, 'g')
124
125 def test_serialize_tuple_class(self):
126 self._test_serialize(TestSet, 'h')
127
128 def test_serialize_tuple_instance(self):
129 self._test_serialize(test, 'h')
130
131 def test_serialize_calendar_date_class(self):
132 self._test_serialize(TestSet, 'q')
133
134 def test_serialize_calendar_date_instance(self):
135 self._test_serialize(test, 'q')
136
137 @np_skip
138 def test_serialize_array_class(self):
139 serialized = TestSet.param.serialize_value('r', mode=self.mode)
140 deserialized = TestSet.param.deserialize_value('r', serialized, mode=self.mode)
141 self.assertTrue(np.array_equal(deserialized, getattr(TestSet, 'r')))
142
143 @np_skip
144 def test_serialize_array_instance(self):
145 serialized = test.param.serialize_value('r', mode=self.mode)
146 deserialized = test.param.deserialize_value('r', serialized, mode=self.mode)
147 self.assertTrue(np.array_equal(deserialized, getattr(test, 'r')))
148
149 @pd_skip
150 def test_serialize_dataframe_class(self):
151 serialized = TestSet.param.serialize_value('s', mode=self.mode)
152 deserialized = TestSet.param.deserialize_value('s', serialized, mode=self.mode)
153 self.assertTrue(getattr(TestSet, 's').equals(deserialized))
154
155 @pd_skip
156 def test_serialize_dataframe_instance(self):
157 serialized = test.param.serialize_value('s', mode=self.mode)
158 deserialized = test.param.deserialize_value('s', serialized, mode=self.mode)
159 self.assertTrue(getattr(test, 's').equals(deserialized))
160
161 def test_serialize_dict_class(self):
162 self._test_serialize(TestSet, 'v')
163
164 def test_serialize_dict_instance(self):
165 self._test_serialize(test, 'v')
166
167 def test_instance_serialization(self):
168 parameters = [p for p in test.param if p not in test.numpy_params + test.pandas_params]
169 serialized = test.param.serialize_parameters(subset=parameters, mode=self.mode)
170 deserialized = TestSet.param.deserialize_parameters(serialized, mode=self.mode)
171 for pname in parameters:
172 self.assertEqual(deserialized[pname], getattr(test, pname))
173
174 @np_skip
175 def test_numpy_instance_serialization(self):
176 serialized = test.param.serialize_parameters(subset=test.numpy_params, mode=self.mode)
177 deserialized = TestSet.param.deserialize_parameters(serialized, mode=self.mode)
178 for pname in test.numpy_params:
179 self.assertTrue(np.array_equal(deserialized[pname], getattr(test, pname)))
180
181 @pd_skip
182 def test_pandas_instance_serialization(self):
183 serialized = test.param.serialize_parameters(subset=test.pandas_params, mode=self.mode)
184 deserialized = TestSet.param.deserialize_parameters(serialized, mode=self.mode)
185 for pname in test.pandas_params:
186 self.assertTrue(getattr(test, pname).equals(deserialized[pname]))
187
188
189
190 class TestJSONSerialization(TestSerialization):
191
192 mode = 'json'
193
194 __test__ = True
195
196
197 class TestJSONSchema(API1TestCase):
198
199 def test_serialize_integer_schema_class(self):
200 if validate is None:
201 raise SkipTest('jsonschema needed for schema validation testing')
202 param_schema = TestSet.param.schema(safe=True, subset=['a'], mode='json')
203 schema = {"type" : "object", "properties" : param_schema}
204 serialized = json.loads(TestSet.param.serialize_parameters(subset=['a']))
205 self.assertEqual({'a':
206 {'type': 'integer', 'minimum': 2, 'exclusiveMaximum': 30,
207 'description': 'Example doc', 'title': 'A'}},
208 param_schema)
209 validate(instance=serialized, schema=schema)
210
211 def test_serialize_integer_schema_class_invalid(self):
212 if validate is None:
213 raise SkipTest('jsonschema needed for schema validation testing')
214 param_schema = TestSet.param.schema(safe=True, subset=['a'], mode='json')
215 schema = {"type" : "object", "properties" : param_schema}
216 self.assertEqual({'a':
217 {'type': 'integer', 'minimum': 2, 'exclusiveMaximum': 30,
218 'description': 'Example doc', 'title': 'A'}},
219 param_schema)
220
221 exception = "1 is not of type 'object'"
222 with self.assertRaisesRegex(ValidationError, exception):
223 validate(instance=1, schema=schema)
224
225 def test_serialize_integer_schema_instance(self):
226 if validate is None:
227 raise SkipTest('jsonschema needed for schema validation testing')
228 param_schema = test.param.schema(safe=True, subset=['a'], mode='json')
229 schema = {"type" : "object", "properties" : param_schema}
230 serialized = json.loads(test.param.serialize_parameters(subset=['a']))
231 self.assertEqual({'a':
232 {'type': 'integer', 'minimum': 2, 'exclusiveMaximum': 30,
233 'description': 'Example doc', 'title': 'A'}},
234 param_schema)
235 validate(instance=serialized, schema=schema)
236
237 @np_skip
238 def test_numpy_schemas_always_unsafe(self):
239 for param_name in test.numpy_params:
240 with self.assertRaisesRegex(param.serializer.UnsafeserializableException,''):
241 test.param.schema(safe=True, subset=[param_name], mode='json')
242
243 @pd_skip
244 def test_pandas_schemas_always_unsafe(self):
245 for param_name in test.pandas_params:
246 with self.assertRaisesRegex(param.serializer.UnsafeserializableException,''):
247 test.param.schema(safe=True, subset=[param_name], mode='json')
248
249 def test_class_instance_schemas_match_and_validate_unsafe(self):
250 if validate is None:
251 raise SkipTest('jsonschema needed for schema validation testing')
252
253 for param_name in list(test.param):
254 class_schema = TestSet.param.schema(safe=False, subset=[param_name], mode='json')
255 instance_schema = test.param.schema(safe=False, subset=[param_name], mode='json')
256 self.assertEqual(class_schema, instance_schema)
257
258 instance_serialization_val = test.param.serialize_parameters(subset=[param_name])
259 validate(instance=instance_serialization_val, schema=class_schema)
260
261 class_serialization_val = TestSet.param.serialize_parameters(subset=[param_name])
262 validate(instance=class_serialization_val, schema=class_schema)
263
264 def test_conditionally_unsafe(self):
265 for param_name in test.conditionally_unsafe:
266 with self.assertRaisesRegex(param.serializer.UnsafeserializableException,''):
267 test.param.schema(safe=True, subset=[param_name], mode='json')
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 TestListParameters(API1TestCase):
10
11 def setUp(self):
12 super(TestListParameters, self).setUp()
13 class P(param.Parameterized):
14 e = param.List([5,6,7], item_type=int)
15 l = param.List(["red","green","blue"], item_type=str, bounds=(0,10))
16
17 self.P = P
18
19 def test_default_None(self):
20 class Q(param.Parameterized):
21 r = param.List(default=[]) # Also check None)
22
23 def test_set_object_constructor(self):
24 p = self.P(e=[6])
25 self.assertEqual(p.e, [6])
26
27 def test_set_object_outside_bounds(self):
28 p = self.P()
29 try:
30 p.l=[6]*11
31 except ValueError:
32 pass
33 else:
34 raise AssertionError("Object set outside range.")
35
36 def test_set_object_wrong_type(self):
37 p = self.P()
38 try:
39 p.e=['s']
40 except TypeError:
41 pass
42 else:
43 raise AssertionError("Object allowed of wrong type.")
44
45 def test_set_object_not_None(self):
46 p = self.P(e=[6])
47 try:
48 p.e = None
49 except ValueError:
50 pass
51 else:
52 raise AssertionError("Object set outside range.")
153153
154154 with self.assertRaises(TypeError):
155155 Q.param.params('r').compute_default()
156
157 if __name__ == "__main__":
158 import nose
159 nose.runmodule()
3030 for _ in range(_iterations):
3131 value = gen()
3232 self.assertTrue(lbound <= value < ubound)
33
34 if __name__ == "__main__":
35 import nose
36 nose.runmodule()
2323 class Q(param.Parameterized):
2424 q = param.Number(default=1)
2525
26 self.assertEqual(Q.param['q'].step, None)
26 qobj = Q()
27 self.assertEqual(qobj.param['q'].step, None)
2728
2829 def test_initialization_with_step_instance(self):
2930 class Q(param.Parameterized):
3435
3536 def test_step_invalid_type_number_parameter(self):
3637 exception = "Step parameter can only be None or a numeric value"
37 with self.assertRaisesRegexp(ValueError, exception):
38 with self.assertRaisesRegex(ValueError, exception):
3839 param.Number(step='invalid value')
3940
4041 def test_step_invalid_type_integer_parameter(self):
4142 exception = "Step parameter can only be None or an integer value"
42 with self.assertRaisesRegexp(ValueError, exception):
43 with self.assertRaisesRegex(ValueError, exception):
4344 param.Integer(step=3.4)
4445
4546 def test_step_invalid_type_datetime_parameter(self):
4647 exception = "Step parameter can only be None, a datetime or datetime type"
47 with self.assertRaisesRegexp(ValueError, exception):
48 with self.assertRaisesRegex(ValueError, exception):
4849 param.Date(dt.datetime(2017,2,27), step=3.2)
4950
5051 def test_step_invalid_type_date_parameter(self):
5152 exception = "Step parameter can only be None or a date type"
52 with self.assertRaisesRegexp(ValueError, exception):
53 with self.assertRaisesRegex(ValueError, exception):
5354 param.CalendarDate(dt.date(2017,2,27), step=3.2)
4040
4141 z = Z(z=numpy.array([1,2]))
4242 _is_array_and_equal(z.z,[1,2])
43
44 if __name__ == "__main__":
45 import nose
46 nose.runmodule()
1717 def setUp(self):
1818 super(TestObjectSelectorParameters, self).setUp()
1919 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)
20 e = param.Selector(default=5,objects=[5,6,7])
21 f = param.Selector(default=10)
22 h = param.Selector(default=None)
23 g = param.Selector(default=None,objects=[7,8])
24 i = param.Selector(default=7,objects=[9],check_on_set=False)
25 s = param.Selector(default=3,objects=OrderedDict(one=1,two=2,three=3))
26 d = param.Selector(default=opts['B'],objects=opts)
2727
2828 self.P = P
2929
9797 def test_initialization_out_of_bounds(self):
9898 try:
9999 class Q(param.Parameterized):
100 q = param.ObjectSelector(5,objects=[4])
100 q = param.Selector(default=5,objects=[4])
101101 except ValueError:
102102 pass
103103 else:
107107 def test_initialization_no_bounds(self):
108108 try:
109109 class Q(param.Parameterized):
110 q = param.ObjectSelector(5,objects=10)
110 q = param.Selector(default=5,objects=10)
111111 except TypeError:
112112 pass
113113 else:
114114 raise AssertionError("ObjectSelector created without range.")
115115
116116
117 if __name__ == "__main__":
118 import nose
119 nose.runmodule()
117 def test_initialization_out_of_bounds_objsel(self):
118 try:
119 class Q(param.Parameterized):
120 q = param.ObjectSelector(5,objects=[4])
121 except ValueError:
122 pass
123 else:
124 raise AssertionError("ObjectSelector created outside range.")
125
126
127 def test_initialization_no_bounds_objsel(self):
128 try:
129 class Q(param.Parameterized):
130 q = param.ObjectSelector(5,objects=10)
131 except TypeError:
132 pass
133 else:
134 raise AssertionError("ObjectSelector created without range.")
2323 class Test(param.Parameterized):
2424 df = param.DataFrame(valid_df)
2525
26 def test_dataframe_allow_none(self):
27 class Test(param.Parameterized):
28 df = param.DataFrame(default=None, rows=3)
29
30 test = Test()
31 self.assertIs(test.df, None)
32
33 def test_dataframe_allow_none_constructor(self):
34 class Test(param.Parameterized):
35 df = param.DataFrame(allow_None=True, rows=3)
36
37 test = Test(df=None)
38 self.assertIs(test.df, None)
39
40 def test_dataframe_allow_none_set_value(self):
41 class Test(param.Parameterized):
42 df = param.DataFrame(allow_None=True, rows=3)
43
44 test = Test()
45 test.df = None
46 self.assertIs(test.df, None)
47
2648 def test_empty_dataframe_param_invalid_set(self):
2749 empty = pandas.DataFrame()
2850 class Test(param.Parameterized):
2951 df = param.DataFrame(default=empty)
3052
3153 test = Test()
32 exception = "Parameter 'df' value must be an instance of DataFrame, not '3'"
33 with self.assertRaisesRegexp(ValueError, exception):
54 exception = "DataFrame parameter 'df' value must be an instance of DataFrame, not 3."
55 with self.assertRaisesRegex(ValueError, exception):
3456 test.df = 3
3557
3658 def test_dataframe_unordered_column_set_valid(self):
4870
4971
5072 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):
73 self.assertEqual(test.param.params('df').ordered, False)
74 exception = r"Provided DataFrame columns \['b', 'a', 'c'\] does not contain required columns \['a', 'd'\]"
75 with self.assertRaisesRegex(ValueError, exception):
5476 test.df = invalid_df
5577
5678 def test_dataframe_ordered_column_list_valid(self):
6789 df = param.DataFrame(default=valid_df, columns=['b', 'a', 'd'])
6890
6991 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):
92 self.assertEqual(test.param.params('df').ordered, True)
93
94 exception = r"Provided DataFrame columns \['a', 'b', 'd'\] must exactly match \['b', 'a', 'd'\]"
95 with self.assertRaisesRegex(ValueError, exception):
7496 test.df = invalid_df
7597
7698
86108 df = param.DataFrame(default=valid_df, columns=3)
87109
88110 test = Test()
89 self.assertEquals(test.param.params('df').ordered, None)
111 self.assertEqual(test.param.params('df').ordered, None)
90112
91113 exception = "Column length 2 does not match declared bounds of 3"
92 with self.assertRaisesRegexp(ValueError, exception):
114 with self.assertRaisesRegex(ValueError, exception):
93115 test.df = invalid_df
94116
95117
102124
103125 invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c'])
104126
105 exception = "Columns length 3 does not match declared bounds of \(None, 2\)"
106 with self.assertRaisesRegexp(ValueError, exception):
127 exception = r"Columns length 3 does not match declared bounds of \(None, 2\)"
128 with self.assertRaisesRegex(ValueError, exception):
107129 class Test(param.Parameterized):
108130 df = param.DataFrame(default=invalid_df, columns=(None,2))
109131
120142
121143 test = Test()
122144 exception = "Row length 3 does not match declared bounds of 2"
123 with self.assertRaisesRegexp(ValueError, exception):
145 with self.assertRaisesRegex(ValueError, exception):
124146 test.df = invalid_df
125147
126148 def test_dataframe_unordered_row_tuple_valid(self):
132154
133155 invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c'])
134156
135 exception = "Row length 2 does not match declared bounds of \(5, 7\)"
136 with self.assertRaisesRegexp(ValueError, exception):
157 exception = r"Row length 2 does not match declared bounds of \(5, 7\)"
158 with self.assertRaisesRegex(ValueError, exception):
137159 class Test(param.Parameterized):
138160 df = param.DataFrame(default=invalid_df, rows=(5,7))
139161
158180
159181 test = Test()
160182 exception = "Row length 3 does not match declared bounds of 2"
161 with self.assertRaisesRegexp(ValueError, exception):
183 with self.assertRaisesRegex(ValueError, exception):
162184 test.series = invalid_series
163185
164186 def test_series_unordered_row_tuple_valid(self):
170192
171193 invalid_series = pandas.Series([1,2])
172194
173 exception = "Row length 2 does not match declared bounds of \(5, 7\)"
174 with self.assertRaisesRegexp(ValueError, exception):
195 exception = r"Row length 2 does not match declared bounds of \(5, 7\)"
196 with self.assertRaisesRegex(ValueError, exception):
175197 class Test(param.Parameterized):
176198 series = param.Series(default=invalid_series, rows=(5,7))
177199
178 if __name__ == "__main__":
179 import nose
180 nose.runmodule()
200 def test_series_allow_none(self):
201 class Test(param.Parameterized):
202 series = param.Series(default=None, rows=3)
203
204 test = Test()
205 self.assertIs(test.series, None)
206
207 def test_series_allow_none_constructor(self):
208 class Test(param.Parameterized):
209 series = param.Series(allow_None=True, rows=3)
210
211 test = Test(series=None)
212 self.assertIs(test.series, None)
213
214 def test_series_allow_none_set_value(self):
215 class Test(param.Parameterized):
216 series = param.Series(allow_None=True, rows=3)
217
218 test = Test()
219 test.series = None
220 self.assertIs(test.series, None)
33
44
55 import param
6
7 from param.parameterized import _parse_dependency_spec
8
69 from . import API1TestCase
710
811
12 class TestDependencyParser(API1TestCase):
13
14 def test_parameter_value(self):
15 obj, attr, what = _parse_dependency_spec('parameter')
16 assert obj is None
17 assert attr == 'parameter'
18 assert what == 'value'
19
20 def test_parameter_attribute(self):
21 obj, attr, what = _parse_dependency_spec('parameter:constant')
22 assert obj is None
23 assert attr == 'parameter'
24 assert what == 'constant'
25
26 def test_subobject_parameter(self):
27 obj, attr, what = _parse_dependency_spec('subobject.parameter')
28 assert obj == '.subobject'
29 assert attr == 'parameter'
30 assert what == 'value'
31
32 def test_subobject_parameter_attribute(self):
33 obj, attr, what = _parse_dependency_spec('subobject.parameter:constant')
34 assert obj == '.subobject'
35 assert attr == 'parameter'
36 assert what == 'constant'
37
38 def test_sub_subobject_parameter(self):
39 obj, attr, what = _parse_dependency_spec('subobject.subsubobject.parameter')
40 assert obj == '.subobject.subsubobject'
41 assert attr == 'parameter'
42 assert what == 'value'
43
44 def test_sub_subobject_parameter_attribute(self):
45 obj, attr, what = _parse_dependency_spec('subobject.subsubobject.parameter:constant')
46 assert obj == '.subobject.subsubobject'
47 assert attr == 'parameter'
48 assert what == 'constant'
49
50
51 class TestParamDependsSubclassing(API1TestCase):
52
53 def test_param_depends_override_depends_subset(self):
54
55 class A(param.Parameterized):
56 a = param.Parameter()
57 b = param.Parameter()
58
59 @param.depends('a', 'b', watch=True)
60 def test(self):
61 pass
62
63 a = A()
64
65 assert len(a.param.params_depended_on('test')) == 2
66
67 class B(A):
68
69 @param.depends('b')
70 def test(self):
71 pass
72
73 b = B()
74
75 assert len(b.param.params_depended_on('test')) == 1
76 assert len(B.param._depends['watch']) == 0
77
78 def test_param_depends_override_depends_subset_watched(self):
79
80 class A(param.Parameterized):
81 a = param.Parameter()
82 b = param.Parameter()
83
84 @param.depends('a', 'b', watch=True)
85 def test(self):
86 pass
87
88 a = A()
89
90 assert len(a.param.params_depended_on('test')) == 2
91
92 class B(A):
93
94 @param.depends('b', watch=True)
95 def test(self):
96 pass
97
98 b = B()
99
100 assert len(b.param.params_depended_on('test')) == 1
101 assert len(B.param._depends['watch']) == 1
102 m, _, _, deps, _ = B.param._depends['watch'][0]
103 assert m == 'test'
104 assert len(deps) == 1
105 assert deps[0].name == 'b'
106
107 def test_param_depends_override_all_depends(self):
108
109 class A(param.Parameterized):
110 a = param.Parameter()
111 b = param.Parameter()
112
113 @param.depends('a', 'b', watch=True)
114 def test(self):
115 pass
116
117 a = A()
118 assert len(a.param.params_depended_on('test')) == 2
119
120 class B(A):
121
122 def test(self):
123 pass
124
125 b = B()
126 assert len(b.param.params_depended_on('test')) == 3
127 assert len(B.param._depends['watch']) == 0
128
129
130
9131 class TestParamDepends(API1TestCase):
132
133 def setUp(self):
134
135 class P(param.Parameterized):
136 a = param.Parameter()
137 b = param.Parameter()
138
139 single_count = param.Integer()
140 attr_count = param.Integer()
141 single_nested_count = param.Integer()
142 double_nested_count = param.Integer()
143 nested_attr_count = param.Integer()
144 nested_count = param.Integer()
145
146 @param.depends('a', watch=True)
147 def single_parameter(self):
148 self.single_count += 1
149
150 @param.depends('a:constant', watch=True)
151 def constant(self):
152 self.attr_count += 1
153
154 @param.depends('b.a', watch=True)
155 def single_nested(self):
156 self.single_nested_count += 1
157
158 @param.depends('b.b.a', watch=True)
159 def double_nested(self):
160 self.double_nested_count += 1
161
162 @param.depends('b.a:constant', watch=True)
163 def nested_attribute(self):
164 self.nested_attr_count += 1
165
166 @param.depends('b.param', watch=True)
167 def nested(self):
168 self.nested_count += 1
169
170 class P2(param.Parameterized):
171
172 @param.depends(P.param.a)
173 def external_param(self, a):
174 pass
175
176 self.P = P
177 self.P2 = P2
178
179 def test_param_depends_on_init(self):
180 class A(param.Parameterized):
181
182 a = param.Parameter()
183
184 value = param.Integer()
185
186 @param.depends('a', watch=True, on_init=True)
187 def callback(self):
188 self.value += 1
189
190 a = A()
191 assert a.value == 1
192
193 a.a = True
194 assert a.value == 2
195
196 def test_param_nested_depends_value_unchanged(self):
197 class A(param.Parameterized):
198
199 c = param.Parameter()
200
201 d = param.Parameter()
202
203 class B(param.Parameterized):
204
205 a = param.Parameter()
206
207 test_count = param.Integer()
208
209 @param.depends('a.c', 'a.d', watch=True)
210 def test(self):
211 self.test_count += 1
212
213 b = B(a=A(c=1))
214 b.a = A(c=1)
215 assert b.test_count == 0
216
217 def test_param_nested_at_class_definition(self):
218
219 class A(param.Parameterized):
220
221 c = param.Parameter()
222
223 d = param.Parameter()
224
225 class B(param.Parameterized):
226
227 a = param.Parameter(A())
228
229 test_count = param.Integer()
230
231 @param.depends('a.c', 'a.d', watch=True)
232 def test(self):
233 self.test_count += 1
234
235 b = B()
236
237 b.a.c = 1
238 assert b.test_count == 1
239
240 b.a.param.update(c=2, d=1)
241 assert b.test_count == 2
242
243 b.a = A()
244 assert b.test_count == 3
245
246 B.a.c = 5
247 assert b.test_count == 3
248
249 def test_param_nested_depends_expands(self):
250 class A(param.Parameterized):
251
252 c = param.Parameter()
253
254 d = param.Parameter()
255
256 class B(param.Parameterized):
257
258 a = param.Parameter()
259
260 test_count = param.Integer()
261
262 @param.depends('a.param', watch=True)
263 def test(self):
264 self.test_count += 1
265
266 b = B(a=A(c=1, name='A'))
267 b.a = A(c=1, name='A')
268 assert b.test_count == 0
269
270 def test_param_depends_class_default_dynamic(self):
271
272 class A(param.Parameterized):
273 c = param.Parameter()
274
275 class B(param.Parameterized):
276 a = param.Parameter(A())
277
278 nested_count = param.Integer()
279
280 @param.depends('a.c', watch=True)
281 def nested(self):
282 self.nested_count += 1
283
284 b = B()
285
286 b.a.c = 1
287 assert b.nested_count == 1
288
289 b.a = A()
290 assert b.nested_count == 2
291
292 def test_param_instance_depends_dynamic_single_nested(self):
293 inst = self.P()
294 pinfos = inst.param.params_depended_on('single_nested', intermediate=True)
295 self.assertEqual(len(pinfos), 0)
296
297 inst.b = self.P()
298 pinfos = inst.param.params_depended_on('single_nested', intermediate=True)
299 self.assertEqual(len(pinfos), 2)
300 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
301 pinfo = pinfos[(inst, 'b')]
302 self.assertIs(pinfo.cls, self.P)
303 self.assertIs(pinfo.inst, inst)
304 self.assertEqual(pinfo.name, 'b')
305 self.assertEqual(pinfo.what, 'value')
306 pinfo2 = pinfos[(inst.b, 'a')]
307 self.assertIs(pinfo2.cls, self.P)
308 self.assertIs(pinfo2.inst, inst.b)
309 self.assertEqual(pinfo2.name, 'a')
310 self.assertEqual(pinfo2.what, 'value')
311
312 assert inst.single_nested_count == 1
313
314 inst.b.a = 1
315 assert inst.single_nested_count == 2
316
317 def test_param_instance_depends_dynamic_single_nested_initialized_no_intermediates(self):
318 init_b = self.P()
319 inst = self.P(b=init_b)
320 pinfos = inst.param.params_depended_on('single_nested', intermediate=False)
321 self.assertEqual(len(pinfos), 1)
322
323 assert pinfos[0].inst is init_b
324 assert pinfos[0].name == 'a'
325
326 new_b = self.P()
327 inst.b = new_b
328
329 pinfos = inst.param.params_depended_on('single_nested', intermediate=False)
330 self.assertEqual(len(pinfos), 1)
331 assert pinfos[0].inst is new_b
332 assert pinfos[0].name == 'a'
333
334 def test_param_instance_depends_dynamic_single_nested_initialized_only_intermediates(self):
335 init_b = self.P()
336 inst = self.P(b=init_b)
337 pinfos = inst.param.params_depended_on('single_nested', intermediate='only')
338 self.assertEqual(len(pinfos), 1)
339
340 assert pinfos[0].inst is inst
341 assert pinfos[0].name == 'b'
342
343 def test_param_instance_depends_dynamic_single_nested_initialized(self):
344 init_b = self.P()
345 inst = self.P(b=init_b)
346 pinfos = inst.param.params_depended_on('single_nested', intermediate=True)
347 self.assertEqual(len(pinfos), 2)
348
349 inst.b = self.P()
350 pinfos = inst.param.params_depended_on('single_nested', intermediate=True)
351 self.assertEqual(len(pinfos), 2)
352 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
353 pinfo = pinfos[(inst, 'b')]
354 self.assertIs(pinfo.cls, self.P)
355 self.assertIs(pinfo.inst, inst)
356 self.assertEqual(pinfo.name, 'b')
357 self.assertEqual(pinfo.what, 'value')
358 pinfo2 = pinfos[(inst.b, 'a')]
359 self.assertIs(pinfo2.cls, self.P)
360 self.assertIs(pinfo2.inst, inst.b)
361 self.assertEqual(pinfo2.name, 'a')
362 self.assertEqual(pinfo2.what, 'value')
363
364 assert inst.single_nested_count == 0
365
366 inst.b.a = 1
367 assert inst.single_nested_count == 1
368
369 # Ensure watcher on initial value does not trigger event
370 init_b.a = 2
371 assert inst.single_nested_count == 1
372
373 def test_param_instance_depends_dynamic_double_nested(self):
374 inst = self.P()
375 pinfos = inst.param.params_depended_on('double_nested', intermediate=True)
376 self.assertEqual(len(pinfos), 0)
377
378 inst.b = self.P(b=self.P())
379 pinfos = inst.param.params_depended_on('double_nested', intermediate=True)
380 self.assertEqual(len(pinfos), 3)
381 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
382 pinfo = pinfos[(inst, 'b')]
383 self.assertIs(pinfo.cls, self.P)
384 self.assertIs(pinfo.inst, inst)
385 self.assertEqual(pinfo.name, 'b')
386 self.assertEqual(pinfo.what, 'value')
387 pinfo2 = pinfos[(inst.b, 'b')]
388 self.assertIs(pinfo2.cls, self.P)
389 self.assertIs(pinfo2.inst, inst.b)
390 self.assertEqual(pinfo2.name, 'b')
391 self.assertEqual(pinfo2.what, 'value')
392 pinfo3 = pinfos[(inst.b.b, 'a')]
393 self.assertIs(pinfo3.cls, self.P)
394 self.assertIs(pinfo3.inst, inst.b.b)
395 self.assertEqual(pinfo3.name, 'a')
396 self.assertEqual(pinfo3.what, 'value')
397
398 assert inst.double_nested_count == 1
399
400 inst.b.b.a = 1
401 assert inst.double_nested_count == 2
402
403 old_subobj = inst.b.b
404 inst.b.b = self.P(a=3)
405 assert inst.double_nested_count == 3
406
407 old_subobj.a = 4
408 assert inst.double_nested_count == 3
409
410 inst.b.b = self.P(a=3)
411 assert inst.double_nested_count == 3
412
413 inst.b.b.a = 4
414 assert inst.double_nested_count == 4
415
416 inst.b.b = self.P(a=3)
417 assert inst.double_nested_count == 5
418
419 def test_param_instance_depends_dynamic_double_nested_partially_initialized(self):
420 inst = self.P(b=self.P())
421 pinfos = inst.param.params_depended_on('double_nested', intermediate=True)
422 self.assertEqual(len(pinfos), 2)
423
424 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
425 pinfo = pinfos[(inst, 'b')]
426 self.assertIs(pinfo.cls, self.P)
427 self.assertIs(pinfo.inst, inst)
428 self.assertEqual(pinfo.name, 'b')
429 self.assertEqual(pinfo.what, 'value')
430 pinfo = pinfos[(inst.b, 'b')]
431 self.assertIs(pinfo.cls, self.P)
432 self.assertIs(pinfo.inst, inst.b)
433 self.assertEqual(pinfo.name, 'b')
434 self.assertEqual(pinfo.what, 'value')
435
436 inst.b.b = self.P()
437 assert inst.double_nested_count == 1
438
439 inst.b.b.a = 1
440 assert inst.double_nested_count == 2
441
442 def test_param_instance_depends_dynamic_nested_attribute(self):
443 inst = self.P()
444 pinfos = inst.param.params_depended_on('nested_attribute', intermediate=True)
445 self.assertEqual(len(pinfos), 0)
446
447 inst.b = self.P()
448 pinfos = inst.param.params_depended_on('nested_attribute', intermediate=True)
449 self.assertEqual(len(pinfos), 2)
450 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
451 pinfo = pinfos[(inst, 'b')]
452 self.assertIs(pinfo.cls, self.P)
453 self.assertIs(pinfo.inst, inst)
454 self.assertEqual(pinfo.name, 'b')
455 self.assertEqual(pinfo.what, 'value')
456 pinfo2 = pinfos[(inst.b, 'a')]
457 self.assertIs(pinfo2.cls, self.P)
458 self.assertIs(pinfo2.inst, inst.b)
459 self.assertEqual(pinfo2.name, 'a')
460 self.assertEqual(pinfo2.what, 'constant')
461
462 assert inst.nested_attr_count == 1
463
464 inst.b.param.a.constant = True
465 assert inst.nested_attr_count == 2
466
467 new_b = self.P()
468 new_b.param.a.constant = True
469 inst.b = new_b
470 assert inst.nested_attr_count == 2
471
472 def test_param_instance_depends_dynamic_nested_attribute_initialized(self):
473 inst = self.P(b=self.P())
474 pinfos = inst.param.params_depended_on('nested_attribute', intermediate=True)
475 self.assertEqual(len(pinfos), 2)
476
477 inst.b = self.P()
478 pinfos = inst.param.params_depended_on('nested_attribute', intermediate=True)
479 self.assertEqual(len(pinfos), 2)
480 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
481 pinfo = pinfos[(inst, 'b')]
482 self.assertIs(pinfo.cls, self.P)
483 self.assertIs(pinfo.inst, inst)
484 self.assertEqual(pinfo.name, 'b')
485 self.assertEqual(pinfo.what, 'value')
486 pinfo2 = pinfos[(inst.b, 'a')]
487 self.assertIs(pinfo2.cls, self.P)
488 self.assertIs(pinfo2.inst, inst.b)
489 self.assertEqual(pinfo2.name, 'a')
490 self.assertEqual(pinfo2.what, 'constant')
491
492 assert inst.nested_attr_count == 0
493
494 inst.b.param.a.constant = True
495 assert inst.nested_attr_count == 1
496
497 def test_param_instance_depends_dynamic_nested(self):
498 inst = self.P()
499 pinfos = inst.param.params_depended_on('nested')
500 self.assertEqual(len(pinfos), 0)
501
502 inst.b = self.P()
503 pinfos = inst.param.params_depended_on('nested')
504 self.assertEqual(len(pinfos), 10)
505 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
506 pinfo = pinfos[(inst, 'b')]
507 self.assertIs(pinfo.cls, self.P)
508 self.assertIs(pinfo.inst, inst)
509 self.assertEqual(pinfo.name, 'b')
510 self.assertEqual(pinfo.what, 'value')
511 for p in ['a', 'b', 'name', 'nested_count', 'single_count', 'attr_count']:
512 pinfo2 = pinfos[(inst.b, p)]
513 self.assertIs(pinfo2.cls, self.P)
514 self.assertIs(pinfo2.inst, inst.b)
515 self.assertEqual(pinfo2.name, p)
516 self.assertEqual(pinfo2.what, 'value')
517
518 assert inst.nested_count == 1
519
520 inst.b.a = 1
521 assert inst.nested_count == 3
522
523 def test_param_instance_depends_dynamic_nested_initialized(self):
524 init_b = self.P()
525 inst = self.P(b=init_b)
526 pinfos = inst.param.params_depended_on('nested')
527 self.assertEqual(len(pinfos), 10)
528
529 inst.b = self.P()
530 pinfos = inst.param.params_depended_on('nested')
531 self.assertEqual(len(pinfos), 10)
532 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
533 pinfo = pinfos[(inst, 'b')]
534 self.assertIs(pinfo.cls, self.P)
535 self.assertIs(pinfo.inst, inst)
536 self.assertEqual(pinfo.name, 'b')
537 self.assertEqual(pinfo.what, 'value')
538 for p in ['a', 'b', 'name', 'nested_count', 'single_count', 'attr_count']:
539 pinfo2 = pinfos[(inst.b, p)]
540 self.assertIs(pinfo2.cls, self.P)
541 self.assertIs(pinfo2.inst, inst.b)
542 self.assertEqual(pinfo2.name, p)
543 self.assertEqual(pinfo2.what, 'value')
544
545 assert inst.single_nested_count == 0
546
547 inst.b.a = 1
548 assert inst.single_nested_count == 1
549
550 # Ensure watcher on initial value does not trigger event
551 init_b.a = 2
552 assert inst.single_nested_count == 1
553
554 def test_param_instance_depends_dynamic_nested_changed_value(self):
555 init_b = self.P(a=1)
556 inst = self.P(b=init_b)
557 pinfos = inst.param.params_depended_on('nested')
558 self.assertEqual(len(pinfos), 10)
559
560 inst.b = self.P(a=2)
561 pinfos = inst.param.params_depended_on('nested')
562 self.assertEqual(len(pinfos), 10)
563 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
564 pinfo = pinfos[(inst, 'b')]
565 self.assertIs(pinfo.cls, self.P)
566 self.assertIs(pinfo.inst, inst)
567 self.assertEqual(pinfo.name, 'b')
568 self.assertEqual(pinfo.what, 'value')
569 for p in ['a', 'b', 'name', 'nested_count', 'single_count', 'attr_count']:
570 pinfo2 = pinfos[(inst.b, p)]
571 self.assertIs(pinfo2.cls, self.P)
572 self.assertIs(pinfo2.inst, inst.b)
573 self.assertEqual(pinfo2.name, p)
574 self.assertEqual(pinfo2.what, 'value')
575
576 assert inst.single_nested_count == 1
577
578 inst.b.a = 1
579 assert inst.single_nested_count == 2
580
581 # Ensure watcher on initial value does not trigger event
582 init_b.a = 2
583 assert inst.single_nested_count == 2
584
585 def test_param_instance_depends(self):
586 p = self.P()
587 pinfos = p.param.params_depended_on('single_parameter')
588 self.assertEqual(len(pinfos), 1)
589 pinfo = pinfos[0]
590 self.assertIs(pinfo.cls, self.P)
591 self.assertIs(pinfo.inst, p)
592 self.assertEqual(pinfo.name, 'a')
593 self.assertEqual(pinfo.what, 'value')
594
595 p.a = 1
596 assert p.single_count == 1
597
598 def test_param_class_depends(self):
599 pinfos = self.P.param.params_depended_on('single_parameter')
600 self.assertEqual(len(pinfos), 1)
601 pinfo = pinfos[0]
602 self.assertIs(pinfo.cls, self.P)
603 self.assertIs(pinfo.inst, None)
604 self.assertEqual(pinfo.name, 'a')
605 self.assertEqual(pinfo.what, 'value')
606
607 def test_param_class_depends_constant(self):
608 pinfos = self.P.param.params_depended_on('constant')
609 self.assertEqual(len(pinfos), 1)
610 pinfo = pinfos[0]
611 self.assertIs(pinfo.cls, self.P)
612 self.assertIs(pinfo.inst, None)
613 self.assertEqual(pinfo.name, 'a')
614 self.assertEqual(pinfo.what, 'constant')
615
616 def test_param_inst_depends_nested(self):
617 inst = self.P(b=self.P())
618 pinfos = inst.param.params_depended_on('nested')
619 self.assertEqual(len(pinfos), 10)
620 pinfos = {(pi.inst, pi.name): pi for pi in pinfos}
621 pinfo = pinfos[(inst, 'b')]
622 self.assertIs(pinfo.cls, self.P)
623 self.assertIs(pinfo.inst, inst)
624 self.assertEqual(pinfo.name, 'b')
625 self.assertEqual(pinfo.what, 'value')
626 for p in ['name', 'a', 'b']:
627 info = pinfos[(inst.b, p)]
628 self.assertEqual(info.name, p)
629 self.assertIs(info.inst, inst.b)
630
631 def test_param_external_param_instance(self):
632 inst = self.P2()
633 pinfos = inst.param.params_depended_on('external_param')
634 pinfo = pinfos[0]
635 self.assertIs(pinfo.cls, self.P)
636 self.assertIs(pinfo.inst, None)
637 self.assertEqual(pinfo.name, 'a')
638 self.assertEqual(pinfo.what, 'value')
639
640
641
642 class TestParamDependsFunction(API1TestCase):
10643
11644 def setUp(self):
12645 class P(param.Parameterized):
13646 a = param.Parameter()
14647 b = param.Parameter()
15648
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
98649
99650 self.P = P
100651
108659 dependencies = {
109660 'dependencies': (p.param.a,),
110661 'kw': {'c': p.param.b},
111 'watch': False
662 'watch': False,
663 'on_init': False
112664 }
113665 self.assertEqual(function._dinfo, dependencies)
114666
122674 dependencies = {
123675 'dependencies': (p.param.a,),
124676 'kw': {'c': p.param.b},
125 'watch': False
677 'watch': False,
678 'on_init': False
126679 }
127680 self.assertEqual(function._dinfo, dependencies)
128681
99
1010 # CEBALERT: not anything like a complete test of Parameterized!
1111
12 import pytest
1213
1314 import random
14 from nose.tools import istest, nottest
15
1615
1716 from param.parameterized import ParamOverrides, shared_parameters
1817 from param.parameterized import default_label_formatter, no_instance_params
1918
20 @nottest
2119 class _SomeRandomNumbers(object):
2220 def __call__(self):
2321 return random.random()
2422
25 @nottest
2623 class TestPO(param.Parameterized):
24 __test__ = False
25
2726 inst = param.Parameter(default=[1,2,3],instantiate=True)
2827 notinst = param.Parameter(default=[1,2,3],instantiate=False, per_instance=False)
2928 const = param.Parameter(default=1,constant=True)
3433
3534 dyn = param.Dynamic(default=1)
3635
37 @nottest
3836 class TestPOValidation(param.Parameterized):
37 __test__ = False
38
3939 value = param.Number(default=2, bounds=(0, 4))
4040
41 @nottest
4241 @no_instance_params
4342 class TestPONoInstance(TestPO):
43 __test__ = False
4444 pass
4545
46 @nottest
4746 class AnotherTestPO(param.Parameterized):
4847 instPO = param.Parameter(default=TestPO(),instantiate=True)
4948 notinstPO = param.Parameter(default=TestPO(),instantiate=False)
5049
51 @nottest
5250 class TestAbstractPO(param.Parameterized):
51 __test__ = False
52
5353 __abstract = True
5454
55 @nottest
55 class _AnotherAbstractPO(param.Parameterized):
56 __abstract = True
57
5658 class TestParamInstantiation(AnotherTestPO):
59 __test__ = False
60
5761 instPO = param.Parameter(default=AnotherTestPO(),instantiate=False)
5862
59 @istest
6063 class TestParameterized(API1TestCase):
6164
6265 @classmethod
6669 cls.log_handler = MockLoggingHandler(level='DEBUG')
6770 log.addHandler(cls.log_handler)
6871
72 def test_parameter_name_fixed(self):
73 testpo = TestPO()
74
75 with pytest.raises(AttributeError):
76 testpo.param.const.name = 'notconst'
6977
7078 def test_constant_parameter(self):
7179 """Test that you can't set a constant parameter after construction."""
139147 def test_abstract_class(self):
140148 """Check that a class declared abstract actually shows up as abstract."""
141149 self.assertEqual(TestAbstractPO.abstract,True)
150 self.assertEqual(_AnotherAbstractPO.abstract,True)
142151 self.assertEqual(TestPO.abstract,False)
143152
144153
232241 inst.param['inst']
233242
234243 inst.param.params()
235 self.log_handler.assertContains(
236 'WARNING', 'The Parameterized instance has instance parameters')
244 if param.parameterized.Parameters._disable_stubs is None:
245 self.log_handler.assertContains(
246 'WARNING', 'The Parameterized instance has instance parameters')
237247
238248
239249 def test_instance_param_getitem(self):
267277 assert obj is TestPO.param[p]
268278
269279
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)
280 def test_update_instance_params(self):
281 # Ensure update does not make instance parameter copies
282 test = TestPO()
283 test.param.update(inst=3)
274284 for p, obj in TestPO.param.objects('current').items():
275285 assert obj is TestPO.param[p]
276286
277287
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()
288 def test_values_instance_params(self):
289 # Ensure values does not make instance parameter copies
290 test = TestPO()
291 test.param.values()
282292 for p, obj in TestPO.param.objects('current').items():
283293 assert obj is TestPO.param[p]
284294
285295
286296 def test_defaults_instance_params(self):
287 # Ensure get_param_values does not make instance parameter copies
297 # Ensure defaults does not make instance parameter copies
288298 test = TestPO()
289299 test.param.defaults()
290300 for p, obj in TestPO.param.objects('current').items():
334344
335345 from param import parameterized
336346
337 @nottest
338347 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
348 __test__ = False
349
350 num_phase = param.Number(18)
351 frequencies = param.List([99])
352 scale = param.Number(0.3)
353
354 def __call__(self,**params_to_override):
355 params = parameterized.ParamOverrides(self,params_to_override)
356 num_phase = params['num_phase']
357 frequencies = params['frequencies']
358 scale = params['scale']
359 return scale,num_phase,frequencies
349360
350361 instance = some_fn.instance()
351362
352 @istest
353363 class TestParameterizedFunction(API1TestCase):
354364
355365 def _basic_tests(self,fn):
375385 self.assertEqual(i(),(0.3,18,[10,20,30]))
376386
377387
378 @nottest
379388 class TestPO1(param.Parameterized):
389 __test__ = False
390
380391 x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1))
381392 y = param.Number(default=1,bounds=(-1,1))
382393
383 @istest
384394 class TestNumberParameter(API1TestCase):
385395
386396 def test_outside_bounds(self):
405415 assert False, "Should raise ValueError."
406416
407417
408 @istest
409418 class TestStringParameter(API1TestCase):
410419
411420 def setUp(self):
429438 assert t.c is None
430439
431440
432 @istest
433441 class TestParameterizedUtilities(API1TestCase):
434442
435443 def setUp(self):
449457 def test_default_label_formatter_overrides(self):
450458 assert default_label_formatter.instance(overrides={'a': 'b'})('a') == 'b'
451459
452 @istest
453460 class TestParamOverrides(API1TestCase):
454461
455462 def setUp(self):
491498 def test_shared_list(self):
492499 self.assertTrue(self.p1.inst is self.p2.inst)
493500 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()
158158
159159 self.assertEqual(obj.pprint(qualify=True),
160160 "tests.API1.testparameterizedrepr."+r)
161
162
163
164 if __name__ == "__main__":
165 import nose
166 nose.runmodule()
112112 pass
113113 else:
114114 raise AssertionError("Selector created without range.")
115
116
117 if __name__ == "__main__":
118 import nose
119 nose.runmodule()
00 """
11 Unit test for String parameters
22 """
3 import sys
4
35 from . import API1TestCase
46
57 import param
68
79
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]?)$'
10 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]?)$'
911
1012 class TestStringParameters(API1TestCase):
1113
2224
2325 a = A()
2426
25 exception = "String 's' only takes a string value."
26 with self.assertRaisesRegexp(ValueError, exception):
27 cls = 'class' if sys.version_info.major > 2 else 'type'
28 exception = "String parameter 's' only takes a string value, not value of type <%s 'NoneType'>." % cls
29 with self.assertRaisesRegex(ValueError, exception):
2730 a.s = None # because allow_None should be False
2831
2932 def test_default_none(self):
3538 a.s = None # because allow_None should be True with default of None
3639
3740 def test_regex_incorrect(self):
38
3941 class A(param.Parameterized):
4042 s = param.String('0.0.0.0', regex=ip_regex)
4143
4244 a = A()
4345
44 exception = "String 's': '123.123.0.256' does not match regex"
45 with self.assertRaisesRegexp(ValueError, exception):
46 exception = "String parameter 's' value '123.123.0.256' does not match regex '%s'." % ip_regex
47 with self.assertRaises(ValueError) as e:
4648 a.s = '123.123.0.256'
49 self.assertEqual(str(e.exception), exception.replace('\\', '\\\\'))
4750
4851 def test_regex_incorrect_default(self):
49
50 exception = "String 'None': '' does not match regex"
51 with self.assertRaisesRegexp(ValueError, exception):
52 exception = "String parameter None value '' does not match regex '%s'." % ip_regex
53 with self.assertRaises(ValueError) as e:
5254 class A(param.Parameterized):
5355 s = param.String(regex=ip_regex) # default value '' does not match regular expression
54
55
56 self.assertEqual(str(e.exception), exception.replace('\\', '\\\\'))
66 import copy
77
88 from . import API1TestCase
9 from nose.plugins.skip import SkipTest
9 import pytest
1010 import fractions
1111
1212 try:
1313 import gmpy
14 except:
15 gmpy = None
16
14 except ImportError:
15 import os
16 if os.getenv('PARAM_TEST_GMPY','0') == '1':
17 raise ImportError("PARAM_TEST_GMPY=1 but gmpy not available.")
18 else:
19 gmpy = None
1720
1821
1922 class TestTimeClass(API1TestCase):
7780 self.assertEqual(t(), 1)
7881 self.assertEqual(t.time_type, fractions.Fraction)
7982
83 def test_time_integration(self):
84 # This used to be a doctest of param.Time; moved
85 # here not to have any doctest to run.
86 time = param.Time(until=20, timestep=1)
87 self.assertEqual(time(), 0)
88 self.assertEqual(time(5), 5)
89 time += 5
90 self.assertEqual(time(), 10)
91 with time as t:
92 self.assertEqual(t(), 10)
93 self.assertEqual(
94 [val for val in t],
95 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
96 )
97 self.assertEqual(t(), 20)
98 'Time after iteration: %s' % t()
99 t += 2
100 self.assertEqual(t(), 22)
101 self.assertEqual(time(), 10)
102
103 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
80104 def test_time_init_gmpy(self):
81 if gmpy is None: raise SkipTest
82
83105 t = param.Time(time_type=gmpy.mpq)
84106 self.assertEqual(t(), gmpy.mpq(0))
85107 t.advance(gmpy.mpq(0.25))
86108 self.assertEqual(t(), gmpy.mpq(1,4))
87109
110 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
88111 def test_time_init_gmpy_advanced(self):
89 if gmpy is None: raise SkipTest
90112 t = param.Time(time_type=gmpy.mpq,
91113 timestep=gmpy.mpq(0.25),
92114 until=1.5)
266288 self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi)))
267289
268290
291 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
269292 def test_time_hashing_integers_gmpy(self):
270293 """
271294 Check that hashes for gmpy values at the integers also matches
272295 those of ints, fractions and strings.
273296 """
274 if gmpy is None: raise SkipTest
275297 hashfn = numbergen.Hash("test", input_count=1)
276298 hash_1 = hashfn(1)
277299 hash_42 = hashfn(42)
282304 self.assertEqual(hash_42, hashfn(gmpy.mpq(42)))
283305 self.assertEqual(hash_42, hashfn(42))
284306
307 @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
285308 def test_time_hashing_rationals_gmpy(self):
286309 """
287310 Check that hashes of fractions and gmpy mpqs match for some
288311 reasonable rational numbers.
289312 """
290 if gmpy is None: raise SkipTest
291313 pi = "3.141592"
292314 hashfn = numbergen.Hash("test", input_count=1)
293315 self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5)))
294316 self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592)))
295
296
297
298
299 if __name__ == "__main__":
300 import nose
301 nose.runmodule()
00 """
11 Unit test for watch mechanism
22 """
3 import copy
4
5 import param
6
7 from param.parameterized import discard_events
8
39 from . import API1TestCase
4
510 from .utils import MockLoggingHandler
6
7 import param
8
9 from param.parameterized import discard_events
1011
1112
1213 class Accumulator(object):
3536 b = param.Parameter(default=0)
3637 c = param.Parameter(default=0)
3738 d = param.Integer(default=0)
39 e = param.Event()
40 f = param.Event()
41
42 def method(self, event):
43 self.b = self.a * 2
3844
3945
4046 class SimpleWatchSubclass(SimpleWatchExample):
6167 def _set_d_bounds(self):
6268 self.param.d.bounds = (self.c, self.c*2)
6369
70 @param.depends('e', watch=True)
71 def _e_event_triggered(self):
72 assert self.e is True
73 self.d = 30
74
75 @param.depends('f', watch=True)
76 def _f_event_triggered(self):
77 assert self.f is True
78 self.b = 420
6479
6580 class WatchSubclassExample(WatchMethodExample):
6681
6782 pass
68
6983
7084
7185 class TestWatch(API1TestCase):
7791 cls.log_handler = MockLoggingHandler(level='DEBUG')
7892 log.addHandler(cls.log_handler)
7993
80
8194 def setUp(self):
8295 super(TestWatch, self).setUp()
8396 self.accumulator = 0
97 self.list_accumulator = []
8498
8599 def test_triggered_when_changed(self):
86100 def accumulator(change):
92106 self.assertEqual(self.accumulator, 1)
93107 obj.a = 2
94108 self.assertEqual(self.accumulator, 3)
95
96109
97110 def test_discard_events_decorator(self):
98111 def accumulator(change):
104117 obj.a = 1
105118 self.assertEqual(self.accumulator, 0)
106119 obj.a = 2
107 self.assertEqual(self.accumulator, 3)
108
120 self.assertEqual(self.accumulator, 2)
121
122 def test_priority_levels(self):
123 def accumulator1(change):
124 self.list_accumulator.append('A')
125 def accumulator2(change):
126 self.list_accumulator.append('B')
127
128 obj = SimpleWatchExample()
129 obj.param.watch(accumulator1, 'a', precedence=2)
130 obj.param.watch(accumulator2, 'a', precedence=1)
131
132 obj.a = 1
133 assert self.list_accumulator == ['B', 'A']
134
135 def test_priority_levels_batched(self):
136 def accumulator1(change):
137 self.list_accumulator.append('A')
138 def accumulator2(change):
139 self.list_accumulator.append('B')
140
141 obj = SimpleWatchExample()
142 obj.param.watch(accumulator1, 'a', precedence=2)
143 obj.param.watch(accumulator2, 'b', precedence=1)
144
145 obj.param.update(a=1, b=2)
146 assert self.list_accumulator == ['B', 'A']
109147
110148 def test_triggered_when_changed_iterator_type(self):
111149 def accumulator(change):
118156 obj.a = tuple()
119157 self.assertEqual(self.accumulator, tuple())
120158
121
122159 def test_triggered_when_changed_mapping_type(self):
123160 def accumulator(change):
124161 self.accumulator = change.new
130167 obj.a = {}
131168 self.assertEqual(self.accumulator, {})
132169
133
134170 def test_untriggered_when_unchanged(self):
135171 def accumulator(change):
136172 self.accumulator += change.new
142178 obj.a = 1
143179 self.assertEqual(self.accumulator, 1)
144180
145
146181 def test_triggered_when_unchanged_complex_type(self):
147182 def accumulator(change):
148183 self.accumulator += 1
155190 obj.a = subobj
156191 self.assertEqual(self.accumulator, 2)
157192
158
159193 def test_triggered_when_unchanged_if_not_onlychanged(self):
160194 accumulator = Accumulator()
161195 obj = SimpleWatchExample()
166200 args = accumulator.args_for_call(0)
167201 self.assertEqual(len(args), 1)
168202 self.assertEqual(args[0].name, 'a')
203 self.assertEqual(args[0].what, 'value')
169204 self.assertEqual(args[0].old, 0)
170205 self.assertEqual(args[0].new, 1)
171206 self.assertEqual(args[0].type, 'set')
174209 args = accumulator.args_for_call(1)
175210 self.assertEqual(len(args), 1)
176211 self.assertEqual(args[0].name, 'a')
212 self.assertEqual(args[0].what, 'value')
177213 self.assertEqual(args[0].old, 1)
178214 self.assertEqual(args[0].new, 1)
179215 self.assertEqual(args[0].type, 'set')
180216
181
182
183217 def test_untriggered_when_unwatched(self):
184218 def accumulator(change):
185219 self.accumulator += change.new
191225 obj.param.unwatch(watcher)
192226 obj.a = 2
193227 self.assertEqual(self.accumulator, 1)
194
195228
196229 def test_warning_unwatching_when_unwatched(self):
197230 def accumulator(change):
209242 accumulator = Accumulator()
210243
211244 obj = SimpleWatchExample()
212 obj.param.watch(accumulator, ['a','b'])
245 obj.param.watch(accumulator, ['a', 'b'])
213246
214247 obj.a = 2
215248 self.assertEqual(accumulator.call_count(), 1)
267300
268301 obj.param.watch(set_c, ['a', 'b'])
269302
270 obj.param.set_param(a=2)
303 obj.param.update(a=2)
271304 self.assertEqual(obj.c, 3)
272305
273306 # Change inside watch callback should have triggered
274307 # second call to accumulator
275308 self.assertEqual(accumulator.call_count(), 2)
276309
277
278310 def test_simple_batched_watch(self):
279311
280312 accumulator = Accumulator()
281313
282314 obj = SimpleWatchExample()
283315 obj.param.watch(accumulator, ['a','b'])
284 obj.param.set_param(a=23, b=42)
316 obj.param.update(a=23, b=42)
285317
286318 self.assertEqual(accumulator.call_count(), 1)
287319 args = accumulator.args_for_call(0)
297329 self.assertEqual(args[1].new, 42)
298330 self.assertEqual(args[1].type, 'changed')
299331
300
301332 def test_simple_class_batched_watch(self):
302333
303334 accumulator = Accumulator()
304335
305336 obj = SimpleWatchSubclass
306337 watcher = obj.param.watch(accumulator, ['a','b'])
307 obj.param.set_param(a=23, b=42)
338 obj.param.update(a=23, b=42)
308339
309340 self.assertEqual(accumulator.call_count(), 1)
310341 args = accumulator.args_for_call(0)
321352 self.assertEqual(args[1].type, 'changed')
322353
323354 SimpleWatchExample.param.unwatch(watcher)
324 obj.param.set_param(a=0, b=0)
325
355 obj.param.update(a=0, b=0)
326356
327357 def test_simple_batched_watch_callback_reuse(self):
328358
332362 obj.param.watch(accumulator, ['a','b'])
333363 obj.param.watch(accumulator, ['c'])
334364
335 obj.param.set_param(a=23, b=42, c=99)
365 obj.param.update(a=23, b=42, c=99)
336366
337367 self.assertEqual(accumulator.call_count(), 2)
338368 # Order may be undefined for Python <3.6
356386 else:
357387 raise Exception('Invalid number of arguments')
358388
389 def test_context_manager_batched_watch_reuse(self):
390
391 accumulator = Accumulator()
392
393 obj = SimpleWatchExample()
394 obj.param.watch(accumulator, ['a','b'])
395 obj.param.watch(accumulator, ['c'])
396
397 with param.batch_watch(obj):
398 obj.a = 23
399 obj.b = 42
400 obj.c = 99
401
402 self.assertEqual(accumulator.call_count(), 2)
403 # Order may be undefined for Python <3.6
404 for args in [accumulator.args_for_call(i) for i in [0, 1]]:
405 if len(args) == 1: # ['c']
406 self.assertEqual(args[0].name, 'c')
407 self.assertEqual(args[0].old, 0)
408 self.assertEqual(args[0].new, 99)
409 self.assertEqual(args[0].type, 'changed')
410
411 elif len(args) == 2: # ['a', 'b']
412 self.assertEqual(args[0].name, 'a')
413 self.assertEqual(args[0].old, 0)
414 self.assertEqual(args[0].new, 23)
415 self.assertEqual(args[0].type, 'changed')
416
417 self.assertEqual(args[1].name, 'b')
418 self.assertEqual(args[1].old, 0)
419 self.assertEqual(args[1].new, 42)
420 self.assertEqual(args[0].type, 'changed')
421 else:
422 raise Exception('Invalid number of arguments')
359423
360424 def test_subclass_batched_watch(self):
361425
364428 obj = SimpleWatchSubclass()
365429
366430 obj.param.watch(accumulator, ['b','c'])
367 obj.param.set_param(b=23, c=42)
431 obj.param.update(b=23, c=42)
368432
369433 self.assertEqual(accumulator.call_count(), 1)
370434 args = accumulator.args_for_call(0)
380444 self.assertEqual(args[1].new, 42)
381445 self.assertEqual(args[1].type, 'changed')
382446
383
384447 def test_nested_batched_watch(self):
385448
386449 accumulator = Accumulator()
387450
388451 obj = SimpleWatchExample()
389452
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)
453 def update(*changes):
454 obj.param.update(a=10, d=12)
455
456 obj.param.watch(accumulator, ['a', 'b', 'c', 'd'])
457 obj.param.watch(update, ['b', 'c'])
458 obj.param.update(b=23, c=42)
396459
397460 self.assertEqual(accumulator.call_count(), 2)
398461 args = accumulator.args_for_call(0)
421484 self.assertEqual(args[1].new, 12)
422485 self.assertEqual(args[1].type, 'changed')
423486
424
425487 def test_nested_batched_watch_not_onlychanged(self):
426488 accumulator = Accumulator()
427489
428490 obj = SimpleWatchSubclass()
429491
430492 obj.param.watch(accumulator, ['b','c'], onlychanged=False)
431 obj.param.set_param(b=0, c=0)
493 obj.param.update(b=0, c=0)
432494
433495 self.assertEqual(accumulator.call_count(), 1)
434496
445507 self.assertEqual(args[1].new, 0)
446508 self.assertEqual(args[1].type, 'set')
447509
448
510 def test_watch_deepcopy(self):
511 obj = SimpleWatchExample()
512
513 obj.param.watch(obj.method, ['a'])
514
515 copied = copy.deepcopy(obj)
516
517 copied.a = 2
518
519 self.assertEqual(copied.b, 4)
520 self.assertEqual(obj.b, 0)
521
522 def test_watch_event_value_trigger(self):
523 obj = WatchMethodExample()
524 obj.e = True
525 self.assertEqual(obj.d, 30)
526 self.assertEqual(obj.e, False)
527
528 def test_watch_event_trigger_method(self):
529 obj = WatchMethodExample()
530 obj.param.trigger('e')
531 self.assertEqual(obj.d, 30)
532 self.assertEqual(obj.e, False)
533
534 def test_watch_event_batched_trigger_method(self):
535 obj = WatchMethodExample()
536 obj.param.trigger('e', 'f')
537 self.assertEqual(obj.d, 30)
538 self.b = 420
539 self.assertEqual(obj.e, False)
540 self.assertEqual(obj.f, False)
449541
450542 class TestWatchMethod(API1TestCase):
451543
488580 self.assertEqual(obj.param.d.bounds, (2, 4))
489581 self.assertEqual(accumulator.call_count(), 1)
490582
583 args = accumulator.args_for_call(0)
584 self.assertEqual(len(args), 1)
585
586 self.assertEqual(args[0].name, 'd')
587 self.assertEqual(args[0].what, 'bounds')
588 self.assertEqual(args[0].old, None)
589 self.assertEqual(args[0].new, (2, 4))
590 self.assertEqual(args[0].type, 'changed')
591
491592 def test_depends_with_watch_on_subclass(self):
492593 obj = WatchSubclassExample()
493594
494595 obj.b = 3
495596 self.assertEqual(obj.c, 6)
496597
497
598 def test_watcher_method_deepcopy(self):
599 obj = WatchMethodExample(b=5)
600
601 copied = copy.deepcopy(obj)
602
603 copied.b = 11
604 self.assertEqual(copied.b, 10)
605 self.assertEqual(obj.b, 5)
498606
499607
500608 class TestWatchValues(API1TestCase):
502610 def setUp(self):
503611 super(TestWatchValues, self).setUp()
504612 self.accumulator = 0
613 self.list_accumulator = []
505614
506615 def test_triggered_when_values_changed(self):
507616 def accumulator(a):
514623 obj.a = 2
515624 self.assertEqual(self.accumulator, 3)
516625
517
518626 def test_untriggered_when_values_unchanged(self):
519627 def accumulator(a):
520628 self.accumulator += a
526634 obj.a = 1
527635 self.assertEqual(self.accumulator, 1)
528636
529
530637 def test_untriggered_when_values_unwatched(self):
531638 def accumulator(a):
532639 self.accumulator += a
539646 obj.a = 2
540647 self.assertEqual(self.accumulator, 1)
541648
649 def test_priority_levels(self):
650 def accumulator1(**kwargs):
651 self.list_accumulator.append('A')
652 def accumulator2(**kwargs):
653 self.list_accumulator.append('B')
654
655 obj = SimpleWatchExample()
656 obj.param.watch_values(accumulator1, 'a', precedence=2)
657 obj.param.watch_values(accumulator2, 'a', precedence=1)
658
659 obj.a = 1
660 assert self.list_accumulator == ['B', 'A']
661
662 def test_priority_levels_batched(self):
663 def accumulator1(**kwargs):
664 self.list_accumulator.append('A')
665 def accumulator2(**kwargs):
666 self.list_accumulator.append('B')
667
668 obj = SimpleWatchExample()
669 obj.param.watch_values(accumulator1, 'a', precedence=2)
670 obj.param.watch_values(accumulator2, 'b', precedence=1)
671
672 obj.param.update(a=1, b=2)
673 assert self.list_accumulator == ['B', 'A']
542674
543675 def test_simple_batched_watch_values_setattr(self):
544676
559691 kwargs = accumulator.kwargs_for_call(1)
560692 self.assertEqual(kwargs, {'b':3})
561693
562
563694 def test_simple_batched_watch_values(self):
564695
565696 accumulator = Accumulator()
566697
567698 obj = SimpleWatchExample()
568699 obj.param.watch_values(accumulator, ['a','b'])
569 obj.param.set_param(a=23, b=42)
700 obj.param.update(a=23, b=42)
570701
571702 self.assertEqual(accumulator.call_count(), 1)
572703 kwargs = accumulator.kwargs_for_call(0)
573704 self.assertEqual(kwargs, {'a':23, 'b':42})
574705
575
576706 def test_simple_batched_watch_values_callback_reuse(self):
577707
578708 accumulator = Accumulator()
581711 obj.param.watch_values(accumulator, ['a','b'])
582712 obj.param.watch_values(accumulator, ['c'])
583713
584 obj.param.set_param(a=23, b=42, c=99)
714 obj.param.update(a=23, b=42, c=99)
585715
586716 self.assertEqual(accumulator.call_count(), 2)
587717 # Order may be undefined for Python <3.6
594724 raise Exception('Invalid number of arguments')
595725
596726
597
598
599
600727 class TestWatchAttributes(API1TestCase):
601728
602729 def setUp(self):
627754 obj.param['d'].bounds = (0, 3)
628755 assert SimpleWatchExample.param['d'].bounds is None
629756 assert self.accumulator == [(0, 3)]
630
631757
632758
633759 class TestTrigger(API1TestCase):
703829 self.assertEqual(args[1].old, 0)
704830 self.assertEqual(args[1].new, 0)
705831 self.assertEqual(args[1].type, 'triggered')
706
707
708 if __name__ == "__main__":
709 import nose
710 nose.runmodule()
2323 def __init__(self, *args, **kwargs):
2424 self.messages = {'DEBUG': [], 'INFO': [], 'WARNING': [],
2525 'ERROR': [], 'CRITICAL': [], 'VERBOSE':[]}
26 self.param_methods = {'WARNING':'param.warning()', 'INFO':'param.message()',
27 'VERBOSE':'param.verbose()', 'DEBUG':'param.debug()'}
2826 super(MockLoggingHandler, self).__init__(*args, **kwargs)
2927
3028 def emit(self, record):
5048 Assert that the last line captured at the given level ends with
5149 a particular substring.
5250 """
53 msg='\n\n{method}: {last_line}\ndoes not end with:\n{substring}'
51 msg='\n\nparam.log({level},...): {last_line}\ndoes not end with:\n{substring}'
5452 last_line = self.tail(level, n=1)
5553 if len(last_line) == 0:
56 raise AssertionError('Missing {method} output: {substring}'.format(
57 method=self.param_methods[level], substring=repr(substring)))
54 raise AssertionError('Missing param.log({level},...) output: {substring}'.format(
55 level=level, substring=repr(substring)))
5856 if not last_line[0].endswith(substring):
59 raise AssertionError(msg.format(method=self.param_methods[level],
57 raise AssertionError(msg.format(level=level,
6058 last_line=repr(last_line[0]),
6159 substring=repr(substring)))
6260
6563 Assert that the last line captured at the given level contains a
6664 particular substring.
6765 """
68 msg='\n\n{method}: {last_line}\ndoes not contain:\n{substring}'
66 msg='\n\nparam.log({level},...): {last_line}\ndoes not contain:\n{substring}'
6967 last_line = self.tail(level, n=1)
7068 if len(last_line) == 0:
7169 raise AssertionError('Missing {method} output: {substring}'.format(
72 method=self.param_methods[level], substring=repr(substring)))
70 level=level, substring=repr(substring)))
7371 if substring not in last_line[0]:
74 raise AssertionError(msg.format(method=self.param_methods[level],
72 raise AssertionError(msg.format(level=level,
7573 last_line=repr(last_line[0]),
7674 substring=repr(substring)))
0 In 2018, we moved most of `Parameterized` onto a `param` namespace
1 object, expecting this to be the public API of param 2.0, and cleaning
2 up the namespace of user classes.
3
4 The new API has been in use for a while within holoviz projects, but
5 we're still changing it. Meanwhile, the previous API remains
6 available.
7
8 The original API's tests were copied into an `API0` subdirectory,
9 while tests in `API1` use the new API.
10
11 (Probably not ideal to just copy everything, and cleaning up would be
12 great, but this explains the two directories you see here.)
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')
00 [tox]
11 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
2 py310,py39,py38,py37,py36,py35,py27,pypy2,pypy3
3 # below tests only really need to run for one python version;
4 # migrate to py38-x, py39-x etc as they become default/typical
5 py37-flakes,
6 py37-coverage,
7 py37-with_numpy,
8 py37-with_ipython,
9 py37-with_pandas,
10 py37-with_jsonsschema,
11 py37-with_gmpy,
12 py37-with_all
713
814 [testenv]
915 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
16 commands = pytest tests --cov=numbergen --cov=param --cov-append --cov-report xml
2217
2318 [testenv:with_numpy]
2419 deps = {[testenv]deps}
3025 pandas
3126 setenv = PARAM_TEST_PANDAS = 1
3227
33
3428 [testenv:with_ipython]
3529 deps = {[testenv]deps}
3630 ipython
3731 setenv = PARAM_TEST_IPYTHON = 1
3832
33 [testenv:with_jsonschema]
34 deps = {[testenv]deps}
35 jsonschema
36 setenv = PARAM_TEST_JSONSCHEMA = 1
37
38 [testenv:with_gmpy]
39 deps = {[testenv]deps}
40 gmpy
41 setenv = PARAM_TEST_GMPY = 1
42
43 [testenv:with_all]
44 deps = {[testenv:with_numpy]deps}
45 {[testenv:with_pandas]deps}
46 {[testenv:with_ipython]deps}
47 {[testenv:with_jsonschema]deps}
48 {[testenv:with_gmpy]deps}
49 setenv = {[testenv:with_numpy]setenv}
50 {[testenv:with_pandas]setenv}
51 {[testenv:with_ipython]setenv}
52 {[testenv:with_jsonschema]setenv}
53 {[testenv:with_gmpy]setenv}
54
3955 [testenv:flakes]
4056 skip_install = true
4157 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