Import upstream version 1.12.0
Debian Janitor
2 years ago
0 | environment: | |
1 | matrix: | |
2 | - TOXENV: py36 | |
3 | - TOXENV: py35 | |
4 | - TOXENV: py34 | |
5 | - TOXENV: py27 | |
6 | ||
7 | matrix: | |
8 | fast_finish: true | |
9 | ||
10 | build: false | |
11 | ||
12 | install: C:\Python36\python -m pip install -U tox | |
13 | ||
14 | test_script: C:\Python36\scripts\tox |
0 | 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 |
7 | 7 | *.so |
8 | 8 | *.o |
9 | 9 | *.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 | 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. | |
1 | 1 | All rights reserved. |
2 | 2 | |
3 | 3 | Redistribution and use in source and binary forms, with or without |
12 | 12 | documentation and/or other materials provided with the |
13 | 13 | distribution. |
14 | 14 | |
15 | * Neither the name of IOAM nor the names of its contributors | |
16 | may be used to endorse or promote products derived from this | |
17 | software without specific prior written permission. | |
15 | * Neither the name of the copyright holder nor the names of any | |
16 | contributors may be used to endorse or promote products derived | |
17 | from this software without specific prior written permission. | |
18 | 18 | |
19 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
0 | <img src="https://raw.githubusercontent.com/holoviz/param/master/doc/_static/logo_horizontal.png" width=250> | |
1 | ||
2 | | | | | |
3 | | --- | --- | | |
4 | | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml) | |
5 | | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/master/graph/badge.svg)](https://codecov.io/gh/holoviz/param) || | |
6 | | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/pyviz-dev.github.io/param.svg?label=dev%20website)](https://pyviz-dev.github.io/param/) | | |
7 | | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/param) | | |
8 | | Python | [![Python support](https://img.shields.io/pypi/pyversions/param.svg)](https://pypi.org/project/param/) | |
9 | | Docs | [![gh-pages](https://img.shields.io/github/last-commit/holoviz/param/gh-pages.svg)](https://github.com/holoviz/param/tree/gh-pages) [![site](https://img.shields.io/website-up-down-green-red/https/param.holoviz.org.svg)](https://param.holoviz.org) | | |
10 | | Binder | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/holoviz/param/master?labpath=examples) | | |
11 | | Support | [![Discourse](https://img.shields.io/discourse/status?server=https%3A%2F%2Fdiscourse.holoviz.org)](https://discourse.holoviz.org/) | | |
12 | ||
13 | Param is a library providing Parameters: Python attributes extended to have features such as type and range checking, dynamically generated values, documentation strings, default values, etc., each of which is inherited from parent classes if not specified in a subclass. | |
14 | ||
15 | Param contains only two required Python files, with no external dependencies, and is provided freely for both non-commercial and commercial use under a BSD license, so that it can easily be included as part of other projects. | |
16 | ||
17 | Please see [param's website](https://param.holoviz.org) for official releases, installation instructions, documentation, and examples. |
0 | |LinuxTests|_ |WinTests|_ |Coverage|_ |PyPIVersion|_ |PyVersion|_ |License|_ | |
1 | ||
2 | Param | |
3 | ===== | |
4 | ||
5 | Param is a library providing Parameters: Python attributes extended to | |
6 | have features such as type and range checking, dynamically generated | |
7 | values, documentation strings, default values, etc., each of which is | |
8 | inherited from parent classes if not specified in a subclass. | |
9 | ||
10 | Param contains only two required Python files, with no external | |
11 | dependencies, and is provided freely for both non-commercial and | |
12 | commercial use under a BSD license, so that it can easily be included | |
13 | as part of other projects. | |
14 | ||
15 | Please see `param's website <http://param.pyviz.org>`_ for | |
16 | official releases, installation instructions, documentation, and examples. | |
17 | ||
18 | .. |LinuxTests| image:: https://travis-ci.org/pyviz/param.svg?branch=master | |
19 | .. _LinuxTests: https://travis-ci.org/pyviz/param | |
20 | ||
21 | .. |WinTests| image:: https://ci.appveyor.com/api/projects/status/1p5aom8o0tfgok1r?svg=true | |
22 | .. _WinTests: https://ci.appveyor.com/project/pyviz/param/branch/master | |
23 | ||
24 | .. |Coverage| image:: https://img.shields.io/coveralls/pyviz/param.svg | |
25 | .. _Coverage: https://coveralls.io/r/pyviz/param?branch=master | |
26 | ||
27 | .. |PyPIVersion| image:: http://img.shields.io/pypi/v/param.svg | |
28 | .. _PyPIVersion: https://pypi.python.org/pypi/param | |
29 | ||
30 | .. |PyVersion| image:: https://img.shields.io/pypi/pyversions/param.svg | |
31 | .. _PyVersion: https://pypi.python.org/pypi/param | |
32 | ||
33 | .. |License| image:: https://img.shields.io/pypi/l/param.svg | |
34 | .. _License: https://pypi.python.org/pypi/param |
0 | name: param | |
1 | channels: | |
2 | - defaults | |
3 | dependencies: | |
4 | - python=3.9.7 | |
5 | - aiohttp=3.7.4 | |
6 | - panel=0.12.2 |
30 | 30 | - param |
31 | 31 | - numbergen |
32 | 32 | commands: |
33 | # https://github.com/ioam/param/issues/219 | |
34 | - nosetests tests | |
33 | # https://github.com/holoviz/param/issues/219 | |
34 | - pytest tests | |
35 | 35 | |
36 | 36 | about: |
37 | 37 | 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> |
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/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> |
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/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> |
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: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 | ``` |
1 | 1 | |
2 | 2 | from nbsite.shared_conf import * |
3 | 3 | |
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' | |
8 | 8 | |
9 | 9 | 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 | |
11 | 17 | |
12 | 18 | 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 | ||
14 | 30 | 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 | ] | |
18 | 39 | } |
19 | ||
20 | _NAV = ( | |
21 | ('API', 'Reference_Manual/param'), | |
22 | ('About', 'About'), | |
23 | ) | |
24 | 40 | |
25 | 41 | html_context.update({ |
26 | 42 | 'PROJECT': project, |
27 | 43 | 'DESCRIPTION': description, |
28 | 44 | 'AUTHOR': authors, |
29 | # canonical URL (for search engines); can ignore for local builds | |
30 | 'WEBSITE_SERVER': 'https://param.pyviz.org', | |
31 | 45 | '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', | |
38 | 48 | }) |
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 | .. | |
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 | { | |
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 | } |
Binary diff not shown
Binary diff not shown
Binary diff not shown
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 | } |
119 | 119 | def __abs__ (self): return UnaryOperator(self,operator.abs) |
120 | 120 | |
121 | 121 | |
122 | operator_symbols = { | |
123 | operator.add:'+', | |
124 | operator.sub:'-', | |
125 | operator.mul:'*', | |
126 | operator.mod:'%', | |
127 | operator.pow:'**', | |
128 | operator.truediv:'/', | |
129 | operator.floordiv:'//', | |
130 | operator.neg:'-', | |
131 | operator.pos:'+', | |
132 | operator.abs:'abs', | |
133 | } | |
134 | ||
135 | def pprint(x, *args, **kwargs): | |
136 | "Pretty-print the provided item, translating operators to their symbols" | |
137 | return x.pprint(*args, **kwargs) if hasattr(x,'pprint') else operator_symbols.get(x, repr(x)) | |
138 | ||
122 | 139 | |
123 | 140 | class BinaryOperator(NumberGenerator): |
124 | 141 | """Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator.""" |
147 | 164 | return self.operator(self.lhs() if callable(self.lhs) else self.lhs, |
148 | 165 | self.rhs() if callable(self.rhs) else self.rhs, **self.args) |
149 | 166 | |
167 | def pprint(self, *args, **kwargs): | |
168 | return (pprint(self.lhs, *args, **kwargs) + | |
169 | pprint(self.operator, *args, **kwargs) + | |
170 | pprint(self.rhs, *args, **kwargs)) | |
150 | 171 | |
151 | 172 | |
152 | 173 | class UnaryOperator(NumberGenerator): |
170 | 191 | def __call__(self): |
171 | 192 | return self.operator(self.operand(),**self.args) |
172 | 193 | |
194 | def pprint(self, *args, **kwargs): | |
195 | return (pprint(self.operator, *args, **kwargs) + '(' + | |
196 | pprint(self.operand, *args, **kwargs) + ')') | |
173 | 197 | |
174 | 198 | |
175 | 199 | class Hash(object): |
201 | 225 | |
202 | 226 | I32 = 4294967296 # Maximum 32 bit unsigned int (i.e. 'I') value |
203 | 227 | if isinstance(val, int): |
204 | numer, denom = val, 1 | |
228 | numer, denom = val, 1 | |
205 | 229 | elif isinstance(val, fractions.Fraction): |
206 | 230 | numer, denom = val.numerator, val.denominator |
207 | 231 | elif hasattr(val, 'numer'): |
208 | 232 | (numer, denom) = (int(val.numer()), int(val.denom())) |
209 | 233 | else: |
210 | param.main.param.warning("Casting type '%s' to Fraction.fraction" | |
234 | param.main.param.log(param.WARNING, "Casting type '%s' to Fraction.fraction" | |
211 | 235 | % type(val).__name__) |
212 | 236 | frac = fractions.Fraction(str(val)) |
213 | 237 | numer, denom = frac.numerator, frac.denominator |
341 | 365 | """ |
342 | 366 | Warn if the object name is not explicitly set. |
343 | 367 | """ |
344 | changed_params = dict(self.param.get_param_values(onlychanged=True)) | |
368 | changed_params = self.param.values(onlychanged=True) | |
345 | 369 | if self.time_dependent and ('name' not in changed_params): |
346 | self.param.warning("Default object name used to set the seed: " | |
347 | "random values conditional on object instantiation order.") | |
370 | self.param.log(param.WARNING, "Default object name used to set the seed: " | |
371 | "random values conditional on object instantiation order.") | |
348 | 372 | |
349 | 373 | def _hash_and_seed(self): |
350 | 374 | """ |
414 | 438 | self._hash_and_seed() |
415 | 439 | |
416 | 440 | |
417 | ||
418 | 441 | class UniformRandom(RandomDistribution): |
419 | 442 | """ |
420 | 443 | Specified with lbound and ubound; when called, return a random |
0 | from __future__ import print_function | |
0 | 1 | """ |
1 | 2 | Parameters are a kind of class attribute allowing special behavior, |
2 | 3 | including dynamically generated parameter values, documentation |
27 | 28 | Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides, |
28 | 29 | descendents, get_logger, instance_descriptor, basestring) |
29 | 30 | |
30 | from .parameterized import (batch_watch, depends, output, # noqa: api import | |
31 | discard_events, edit_constant) | |
31 | from .parameterized import (batch_watch, depends, output, script_repr, # noqa: api import | |
32 | discard_events, edit_constant, instance_descriptor) | |
33 | from .parameterized import shared_parameters # noqa: api import | |
32 | 34 | from .parameterized import logging_level # noqa: api import |
33 | from .parameterized import shared_parameters # noqa: api import | |
35 | from .parameterized import DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL # noqa: api import | |
34 | 36 | |
35 | 37 | from collections import OrderedDict |
36 | 38 | from numbers import Real |
40 | 42 | # only two required files. |
41 | 43 | try: |
42 | 44 | from .version import Version |
43 | __version__ = str(Version(fpath=__file__, archive_commit="9123ba0", reponame="param")) | |
45 | __version__ = str(Version(fpath=__file__, archive_commit="d490aa95", reponame="param")) | |
44 | 46 | except: |
45 | 47 | __version__ = "0.0.0+unknown" |
46 | 48 | |
68 | 70 | |
69 | 71 | |
70 | 72 | # A global random seed (integer or rational) available for controlling |
71 | # the behaviour of parameterized objects with random state. | |
73 | # the behaviour of Parameterized objects with random state. | |
72 | 74 | random_seed = 42 |
73 | 75 | |
74 | 76 | |
220 | 222 | supplied parameters, inheriting from the specified base(s). |
221 | 223 | """ |
222 | 224 | if not (isinstance(bases, list) or isinstance(bases, tuple)): |
223 | bases=[bases] | |
225 | bases=[bases] | |
224 | 226 | return type(name, tuple(bases), params) |
225 | 227 | |
226 | 228 | |
467 | 469 | raise StopIteration |
468 | 470 | return self._time |
469 | 471 | |
470 | # For Python 2 compatibility; can be removed for Python 3. | |
472 | # PARAM2_DEPRECATION: For Python 2 compatibility; can be removed for Python 3. | |
471 | 473 | next = __next__ |
472 | 474 | |
473 | 475 | def __call__(self, val=None, time_type=None): |
563 | 565 | time_fn = Time() |
564 | 566 | time_dependent = False |
565 | 567 | |
566 | # CBENHANCEMENT: Add an 'epsilon' slot. | |
567 | # See email 'Re: simulation-time-controlled Dynamic parameters' | |
568 | # Dec 22, 2007 CB->JAB | |
569 | ||
570 | 568 | def __init__(self,**params): |
571 | 569 | """ |
572 | 570 | Call the superclass's __init__ and set instantiate=True if the |
583 | 581 | """ |
584 | 582 | Add 'last time' and 'last value' attributes to the generator. |
585 | 583 | """ |
586 | # CEBALERT: use a dictionary to hold these things. | |
584 | # Could use a dictionary to hold these things. | |
587 | 585 | if hasattr(obj,"_Dynamic_time_fn"): |
588 | 586 | gen._Dynamic_time_fn = obj._Dynamic_time_fn |
589 | 587 | |
590 | 588 | gen._Dynamic_last = None |
591 | # CEB: I'd use None for this, except can't compare a fixedpoint | |
589 | # Would have usede None for this, but can't compare a fixedpoint | |
592 | 590 | # number with None (e.g. 1>None but FixedPoint(1)>None can't be done) |
593 | 591 | gen._Dynamic_time = -1 |
594 | 592 | |
702 | 700 | |
703 | 701 | def identity_hook(obj,val): return val |
704 | 702 | |
703 | def get_soft_bounds(bounds, softbounds): | |
704 | """ | |
705 | For each soft bound (upper and lower), if there is a defined bound | |
706 | (not equal to None) and does not exceed the hard bound, then it is | |
707 | returned. Otherwise it defaults to the hard bound. The hard bound | |
708 | could still be None. | |
709 | """ | |
710 | if bounds is None: | |
711 | hl, hu = (None, None) | |
712 | else: | |
713 | hl, hu = bounds | |
714 | ||
715 | if softbounds is None: | |
716 | sl, su = (None, None) | |
717 | else: | |
718 | sl, su = softbounds | |
719 | ||
720 | if sl is None or (hl is not None and sl<hl): | |
721 | l = hl | |
722 | else: | |
723 | l = sl | |
724 | ||
725 | if su is None or (hu is not None and su>hu): | |
726 | u = hu | |
727 | else: | |
728 | u = su | |
729 | ||
730 | return (l, u) | |
705 | 731 | |
706 | 732 | |
707 | 733 | class Number(Dynamic): |
749 | 775 | |
750 | 776 | """ |
751 | 777 | |
752 | __slots__ = ['bounds','_softbounds','inclusive_bounds','set_hook', 'step'] | |
753 | ||
754 | def __init__(self,default=0.0,bounds=None,softbounds=None, | |
778 | __slots__ = ['bounds', 'softbounds', 'inclusive_bounds', 'set_hook', 'step'] | |
779 | ||
780 | def __init__(self, default=0.0, bounds=None, softbounds=None, | |
755 | 781 | inclusive_bounds=(True,True), step=None, **params): |
756 | 782 | """ |
757 | 783 | Initialize this parameter object and store the bounds. |
758 | 784 | |
759 | 785 | Non-dynamic default values are checked against the bounds. |
760 | 786 | """ |
761 | super(Number,self).__init__(default=default,**params) | |
787 | super(Number,self).__init__(default=default, **params) | |
762 | 788 | |
763 | 789 | self.set_hook = identity_hook |
764 | 790 | self.bounds = bounds |
765 | 791 | self.inclusive_bounds = inclusive_bounds |
766 | self._softbounds = softbounds | |
792 | self.softbounds = softbounds | |
767 | 793 | self.step = step |
768 | 794 | self._validate(default) |
769 | 795 | |
770 | ||
771 | def __get__(self,obj,objtype): | |
796 | def __get__(self, obj, objtype): | |
772 | 797 | """ |
773 | 798 | Same as the superclass's __get__, but if the value was |
774 | 799 | dynamically generated, check the bounds. |
775 | 800 | """ |
776 | result = super(Number,self).__get__(obj,objtype) | |
777 | # CEBALERT: results in extra lookups (_value_is_dynamic() is | |
778 | # also looking up 'result' - should just pass it in). Note | |
779 | # that this method is called often. | |
780 | if self._value_is_dynamic(obj,objtype): self._validate(result) | |
801 | result = super(Number, self).__get__(obj, objtype) | |
802 | # Should be able to optimize this commonly used method by | |
803 | # avoiding extra lookups (e.g. _value_is_dynamic() is also | |
804 | # looking up 'result' - should just pass it in). | |
805 | if self._value_is_dynamic(obj, objtype): | |
806 | self._validate(result) | |
781 | 807 | return result |
782 | ||
783 | # Allow softbounds to be used like a normal attribute, as it | |
784 | # probably should have been already (not _softbounds) | |
785 | @property | |
786 | def softbounds(self): return self._softbounds | |
787 | ||
788 | @softbounds.setter | |
789 | def softbounds(self,value): self._softbounds = value | |
790 | ||
791 | 808 | |
792 | 809 | def set_in_bounds(self,obj,val): |
793 | 810 | """ |
799 | 816 | bounded_val = self.crop_to_bounds(val) |
800 | 817 | else: |
801 | 818 | bounded_val = val |
802 | super(Number,self).__set__(obj,bounded_val) | |
803 | ||
804 | ||
805 | # CEBERRORALERT: doesn't take account of exclusive bounds; see | |
806 | # https://github.com/ioam/param/issues/80. | |
807 | def crop_to_bounds(self,val): | |
819 | super(Number, self).__set__(obj, bounded_val) | |
820 | ||
821 | def crop_to_bounds(self, val): | |
808 | 822 | """ |
809 | 823 | Return the given value cropped to be within the hard bounds |
810 | 824 | for this parameter. |
815 | 829 | returned value could be None. If a non-numeric value is passed |
816 | 830 | in, set to be the default value (which could be None). In no |
817 | 831 | case is an exception raised; all values are accepted. |
818 | """ | |
819 | # Currently, values outside the bounds are silently cropped to | |
820 | # be inside the bounds; it may be appropriate to add a warning | |
821 | # in such cases. | |
832 | ||
833 | As documented in https://github.com/holoviz/param/issues/80, | |
834 | currently does not respect exclusive bounds, which would | |
835 | strictly require setting to one less for integer values or | |
836 | an epsilon less for floats. | |
837 | """ | |
838 | # Values outside the bounds are silently cropped to | |
839 | # be inside the bounds. | |
822 | 840 | if _is_number(val): |
823 | 841 | if self.bounds is None: |
824 | 842 | return val |
836 | 854 | |
837 | 855 | else: |
838 | 856 | # non-numeric value sent in: reverts to default value |
839 | return self.default | |
857 | return self.default | |
840 | 858 | |
841 | 859 | return val |
842 | 860 | |
843 | ||
844 | def _checkBounds(self, val): | |
845 | ||
846 | if self.bounds is not None: | |
847 | vmin,vmax = self.bounds | |
848 | incmin,incmax = self.inclusive_bounds | |
849 | ||
850 | # Could simplify: see https://github.com/ioam/param/issues/83 | |
851 | if vmax is not None: | |
852 | if incmax is True: | |
853 | if not val <= vmax: | |
854 | raise ValueError("Parameter '%s' must be at most %s"%(self.name,vmax)) | |
855 | else: | |
856 | if not val < vmax: | |
857 | raise ValueError("Parameter '%s' must be less than %s"%(self.name,vmax)) | |
858 | ||
859 | if vmin is not None: | |
860 | if incmin is True: | |
861 | if not val >= vmin: | |
862 | raise ValueError("Parameter '%s' must be at least %s"%(self.name,vmin)) | |
863 | else: | |
864 | if not val > vmin: | |
865 | raise ValueError("Parameter '%s' must be greater than %s"%(self.name,vmin)) | |
866 | ||
867 | ||
861 | def _validate_bounds(self, val, bounds, inclusive_bounds): | |
862 | if bounds is None or (val is None and self.allow_None) or callable(val): | |
863 | return | |
864 | vmin, vmax = bounds | |
865 | incmin, incmax = inclusive_bounds | |
866 | if vmax is not None: | |
867 | if incmax is True: | |
868 | if not val <= vmax: | |
869 | raise ValueError("Parameter %r must be at most %s, " | |
870 | "not %s." % (self.name, vmax, val)) | |
871 | else: | |
872 | if not val < vmax: | |
873 | raise ValueError("Parameter %r must be less than %s, " | |
874 | "not %s." % (self.name, vmax, val)) | |
875 | ||
876 | if vmin is not None: | |
877 | if incmin is True: | |
878 | if not val >= vmin: | |
879 | raise ValueError("Parameter %r must be at least %s, " | |
880 | "not %s." % (self.name, vmin, val)) | |
881 | else: | |
882 | if not val > vmin: | |
883 | raise ValueError("Parameter %r must be greater than %s, " | |
884 | "not %s." % (self.name, vmin, val)) | |
885 | ||
886 | def _validate_value(self, val, allow_None): | |
887 | if (allow_None and val is None) or callable(val): | |
888 | return | |
889 | ||
890 | if not _is_number(val): | |
891 | raise ValueError("Parameter %r only takes numeric values, " | |
892 | "not type %r." % (self.name, type(val))) | |
893 | ||
894 | def _validate_step(self, val, step): | |
895 | if step is not None and not _is_number(step): | |
896 | raise ValueError("Step parameter can only be None or a " | |
897 | "numeric value, not type %r." % type(step)) | |
868 | 898 | |
869 | 899 | def _validate(self, val): |
870 | 900 | """ |
871 | 901 | Checks that the value is numeric and that it is within the hard |
872 | 902 | bounds; if not, an exception is raised. |
873 | 903 | """ |
874 | if callable(val): | |
875 | return val | |
876 | ||
877 | if self.allow_None and val is None: | |
878 | return | |
879 | ||
880 | if not _is_number(val): | |
881 | raise ValueError("Parameter '%s' only takes numeric values"%(self.name)) | |
882 | ||
883 | if self.step is not None and not _is_number(self.step): | |
884 | raise ValueError("Step parameter can only be None or a numeric value") | |
885 | ||
886 | self._checkBounds(val) | |
887 | ||
904 | self._validate_value(val, self.allow_None) | |
905 | self._validate_step(val, self.step) | |
906 | self._validate_bounds(val, self.bounds, self.inclusive_bounds) | |
888 | 907 | |
889 | 908 | def get_soft_bounds(self): |
890 | """ | |
891 | For each soft bound (upper and lower), if there is a defined bound (not equal to None) | |
892 | then it is returned, otherwise it defaults to the hard bound. The hard bound could still be None. | |
893 | """ | |
894 | if self.bounds is None: | |
895 | hl,hu=(None,None) | |
896 | else: | |
897 | hl,hu=self.bounds | |
898 | ||
899 | if self._softbounds is None: | |
900 | sl,su=(None,None) | |
901 | else: | |
902 | sl,su=self._softbounds | |
903 | ||
904 | ||
905 | if sl is None: l = hl | |
906 | else: l = sl | |
907 | ||
908 | if su is None: u = hu | |
909 | else: u = su | |
910 | ||
911 | return (l,u) | |
912 | ||
909 | return get_soft_bounds(self.bounds, self.softbounds) | |
913 | 910 | |
914 | 911 | def __setstate__(self,state): |
915 | 912 | if 'step' not in state: |
916 | 913 | state['step'] = None |
917 | 914 | |
918 | super(Number,self).__setstate__(state) | |
915 | super(Number, self).__setstate__(state) | |
919 | 916 | |
920 | 917 | |
921 | 918 | |
922 | 919 | class Integer(Number): |
923 | 920 | """Numeric Parameter required to be an Integer""" |
924 | 921 | |
925 | def __init__(self,default=0,**params): | |
926 | Number.__init__(self,default=default,**params) | |
927 | ||
928 | def _validate(self, val): | |
929 | if callable(val): return | |
930 | ||
931 | if self.allow_None and val is None: | |
932 | return | |
933 | ||
934 | if not isinstance(val,int): | |
935 | raise ValueError("Parameter '%s' must be an integer."%self.name) | |
936 | ||
937 | if self.step is not None and not isinstance(self.step, int): | |
938 | raise ValueError("Step parameter can only be None or an integer value") | |
939 | ||
940 | ||
941 | self._checkBounds(val) | |
922 | def __init__(self, default=0, **params): | |
923 | Number.__init__(self, default=default, **params) | |
924 | ||
925 | def _validate_value(self, val, allow_None): | |
926 | if callable(val): | |
927 | return | |
928 | ||
929 | if allow_None and val is None: | |
930 | return | |
931 | ||
932 | if not isinstance(val, int): | |
933 | raise ValueError("Integer parameter %r must be an integer, " | |
934 | "not type %r." % (self.name, type(val))) | |
935 | ||
936 | def _validate_step(self, val, step): | |
937 | if step is not None and not isinstance(step, int): | |
938 | raise ValueError("Step parameter can only be None or an " | |
939 | "integer value, not type %r" % type(step)) | |
942 | 940 | |
943 | 941 | |
944 | 942 | |
945 | 943 | class Magnitude(Number): |
946 | 944 | """Numeric Parameter required to be in the range [0.0-1.0].""" |
947 | 945 | |
948 | def __init__(self,default=1.0,softbounds=None,**params): | |
949 | Number.__init__(self,default=default,bounds=(0.0,1.0),softbounds=softbounds,**params) | |
946 | def __init__(self, default=1.0, softbounds=None, **params): | |
947 | Number.__init__(self, default=default, bounds=(0.0,1.0), softbounds=softbounds, **params) | |
950 | 948 | |
951 | 949 | |
952 | 950 | |
955 | 953 | |
956 | 954 | __slots__ = ['bounds'] |
957 | 955 | |
958 | # CB: bounds have no effect; see https://github.com/ioam/param/issues/82 | |
959 | def __init__(self,default=False,bounds=(0,1),**params): | |
956 | # Bounds are set for consistency and are arguably accurate, but have | |
957 | # no effect since values are either False, True, or None (if allowed). | |
958 | def __init__(self, default=False, bounds=(0,1), **params): | |
960 | 959 | self.bounds = bounds |
961 | super(Boolean, self).__init__(default=default,**params) | |
962 | ||
963 | def _validate(self, val): | |
964 | if self.allow_None: | |
965 | if not isinstance(val,bool) and val is not None: | |
966 | raise ValueError("Boolean '%s' only takes a Boolean value or None." | |
967 | %self.name) | |
968 | ||
969 | if val is not True and val is not False and val is not None: | |
970 | raise ValueError("Boolean '%s' must be True, False, or None."%self.name) | |
971 | else: | |
972 | if not isinstance(val,bool): | |
973 | raise ValueError("Boolean '%s' only takes a Boolean value."%self.name) | |
974 | ||
975 | if val is not True and val is not False: | |
976 | raise ValueError("Boolean '%s' must be True or False."%self.name) | |
977 | super(Boolean, self)._validate(val) | |
960 | super(Boolean, self).__init__(default=default, **params) | |
961 | ||
962 | def _validate_value(self, val, allow_None): | |
963 | if allow_None: | |
964 | if not isinstance(val, bool) and val is not None: | |
965 | raise ValueError("Boolean parameter %r only takes a " | |
966 | "Boolean value or None, not %s." | |
967 | % (self.name, val)) | |
968 | elif not isinstance(val, bool): | |
969 | raise ValueError("Boolean parameter %r must be True or False, " | |
970 | "not %s." % (self.name, val)) | |
978 | 971 | |
979 | 972 | |
980 | 973 | |
983 | 976 | |
984 | 977 | __slots__ = ['length'] |
985 | 978 | |
986 | def __init__(self,default=(0,0),length=None,**params): | |
979 | def __init__(self, default=(0,0), length=None, **params): | |
987 | 980 | """ |
988 | 981 | Initialize a tuple parameter with a fixed length (number of |
989 | 982 | elements). The length is determined by the initial default |
990 | 983 | value, if any, and must be supplied explicitly otherwise. The |
991 | 984 | length is not allowed to change after instantiation. |
992 | 985 | """ |
993 | super(Tuple,self).__init__(default=default,**params) | |
986 | super(Tuple,self).__init__(default=default, **params) | |
994 | 987 | if length is None and default is not None: |
995 | 988 | self.length = len(default) |
996 | 989 | elif length is None and default is None: |
1000 | 993 | self.length = length |
1001 | 994 | self._validate(default) |
1002 | 995 | |
996 | def _validate_value(self, val, allow_None): | |
997 | if val is None and allow_None: | |
998 | return | |
999 | ||
1000 | if not isinstance(val, tuple): | |
1001 | raise ValueError("Tuple parameter %r only takes a tuple value, " | |
1002 | "not %r." % (self.name, type(val))) | |
1003 | ||
1004 | def _validate_length(self, val, length): | |
1005 | if val is None and self.allow_None: | |
1006 | return | |
1007 | ||
1008 | if not len(val) == length: | |
1009 | raise ValueError("Tuple parameter %r is not of the correct " | |
1010 | "length (%d instead of %d)." % | |
1011 | (self.name, len(val), length)) | |
1003 | 1012 | |
1004 | 1013 | def _validate(self, val): |
1005 | if val is None and self.allow_None: | |
1006 | return | |
1007 | ||
1008 | if not isinstance(val,tuple): | |
1009 | raise ValueError("Tuple '%s' only takes a tuple value."%self.name) | |
1010 | ||
1011 | if not len(val)==self.length: | |
1012 | raise ValueError("%s: tuple is not of the correct length (%d instead of %d)." % | |
1013 | (self.name,len(val),self.length)) | |
1014 | ||
1014 | self._validate_value(val, self.allow_None) | |
1015 | self._validate_length(val, self.length) | |
1016 | ||
1017 | @classmethod | |
1018 | def serialize(cls, value): | |
1019 | 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 | |
1015 | 1024 | |
1016 | 1025 | |
1017 | 1026 | class NumericTuple(Tuple): |
1018 | 1027 | """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length.""" |
1019 | 1028 | |
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))) | |
1028 | 1038 | |
1029 | 1039 | |
1030 | 1040 | class XYCoordinates(NumericTuple): |
1031 | 1041 | """A NumericTuple for an X,Y coordinate.""" |
1032 | 1042 | |
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) | |
1036 | 1045 | |
1037 | 1046 | |
1038 | 1047 | class Callable(Parameter): |
1045 | 1054 | 2.4, so instantiate must be False for those values. |
1046 | 1055 | """ |
1047 | 1056 | |
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))) | |
1053 | 1063 | |
1054 | 1064 | |
1055 | 1065 | class Action(Callable): |
1067 | 1077 | return False |
1068 | 1078 | |
1069 | 1079 | |
1070 | ||
1071 | # CEBALERT: this should be a method of ClassSelector. | |
1080 | # Could be a method of ClassSelector. | |
1072 | 1081 | def concrete_descendents(parentclass): |
1073 | 1082 | """ |
1074 | 1083 | Return a dictionary containing all subclasses of the specified |
1081 | 1090 | """ |
1082 | 1091 | return dict((c.__name__,c) for c in descendents(parentclass) |
1083 | 1092 | if not _is_abstract(c)) |
1084 | ||
1085 | 1093 | |
1086 | 1094 | |
1087 | 1095 | class Composite(Parameter): |
1094 | 1102 | in the order specified. Likewise, setting the parameter takes a |
1095 | 1103 | sequence of values and sets the value of the constituent |
1096 | 1104 | 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): | |
1102 | 1113 | if attribs is None: |
1103 | 1114 | attribs = [] |
1104 | super(Composite,self).__init__(default=None,**kw) | |
1115 | super(Composite, self).__init__(default=None, **kw) | |
1105 | 1116 | self.attribs = attribs |
1106 | 1117 | |
1107 | def __get__(self,obj,objtype): | |
1118 | def __get__(self, obj, objtype): | |
1108 | 1119 | """ |
1109 | 1120 | Return the values of all the attribs, as a list. |
1110 | 1121 | """ |
1111 | 1122 | if obj is None: |
1112 | return [getattr(objtype,a) for a in self.attribs] | |
1123 | return [getattr(objtype, a) for a in self.attribs] | |
1113 | 1124 | 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))) | |
1115 | 1133 | |
1116 | 1134 | 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) | |
1118 | 1136 | |
1119 | 1137 | def _post_setter(self, obj, val): |
1120 | 1138 | 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) | |
1123 | 1141 | 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) | |
1126 | 1144 | |
1127 | 1145 | |
1128 | 1146 | class SelectorBase(Parameter): |
1138 | 1156 | raise NotImplementedError("get_range() must be implemented in subclasses.") |
1139 | 1157 | |
1140 | 1158 | |
1141 | class ObjectSelector(SelectorBase): | |
1159 | class Selector(SelectorBase): | |
1142 | 1160 | """ |
1143 | 1161 | 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. | |
1144 | 1166 | |
1145 | 1167 | check_on_set restricts the value to be among the current list of |
1146 | 1168 | objects. By default, if objects are initially supplied, |
1161 | 1183 | up from the object value. |
1162 | 1184 | """ |
1163 | 1185 | |
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 | |
1167 | 1189 | # 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 | ||
1170 | 1209 | if objects is None: |
1171 | 1210 | objects = [] |
1172 | 1211 | if isinstance(objects, collections_abc.Mapping): |
1178 | 1217 | self.compute_default_fn = compute_default_fn |
1179 | 1218 | |
1180 | 1219 | 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 | |
1184 | 1223 | 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) | |
1189 | 1228 | # Required as Parameter sets allow_None=True if default is None |
1190 | 1229 | self.allow_None = allow_None |
1191 | 1230 | if default is not None and self.check_on_set is True: |
1192 | 1231 | self._validate(default) |
1193 | 1232 | |
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. | |
1197 | 1235 | |
1198 | 1236 | def compute_default(self): |
1199 | 1237 | """ |
1204 | 1242 | no longer None). |
1205 | 1243 | """ |
1206 | 1244 | 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() | |
1208 | 1246 | if self.default not in self.objects: |
1209 | 1247 | self.objects.append(self.default) |
1210 | 1248 | |
1211 | ||
1212 | 1249 | def _validate(self, val): |
1213 | 1250 | """ |
1214 | 1251 | val must be None or one of the objects in self.objects. |
1218 | 1255 | return |
1219 | 1256 | |
1220 | 1257 | 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: | |
1226 | 1263 | attrib_name = "" |
1227 | 1264 | |
1228 | 1265 | items = [] |
1237 | 1274 | limiter = ', ...]' |
1238 | 1275 | break |
1239 | 1276 | 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)) | |
1242 | 1279 | |
1243 | 1280 | def _ensure_value_is_in_objects(self,val): |
1244 | 1281 | """ |
1247 | 1284 | to check each item instead. |
1248 | 1285 | """ |
1249 | 1286 | if not (val in self.objects): |
1250 | self.objects.append(val) | |
1287 | self.objects.append(val) | |
1251 | 1288 | |
1252 | 1289 | def get_range(self): |
1253 | 1290 | """ |
1258 | 1295 | return named_objs(self.objects, self.names) |
1259 | 1296 | |
1260 | 1297 | |
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 | ||
1291 | 1307 | |
1292 | 1308 | class ClassSelector(SelectorBase): |
1293 | 1309 | """ |
1297 | 1313 | for is_instance=True. |
1298 | 1314 | """ |
1299 | 1315 | |
1300 | __slots__ = ['class_','is_instance'] | |
1316 | __slots__ = ['class_', 'is_instance'] | |
1301 | 1317 | |
1302 | 1318 | def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params): |
1303 | 1319 | self.class_ = class_ |
1305 | 1321 | super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params) |
1306 | 1322 | self._validate(default) |
1307 | 1323 | |
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_)) | |
1313 | 1333 | 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_)): | |
1317 | 1338 | 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)) | |
1320 | 1341 | else: |
1321 | if not (val is None and self.allow_None) and not (issubclass(val,self.class_)): | |
1342 | if not (issubclass(val, class_)): | |
1322 | 1343 | 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__)) | |
1326 | 1346 | |
1327 | 1347 | def get_range(self): |
1328 | 1348 | """ |
1329 | 1349 | Return the possible types for this parameter's value. |
1330 | 1350 | |
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_`.) | |
1333 | 1353 | |
1334 | 1354 | Only classes from modules that have been imported are added |
1335 | 1355 | (see concrete_descendents()). |
1338 | 1358 | all_classes = {} |
1339 | 1359 | for cls in classes: |
1340 | 1360 | 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()) | |
1342 | 1362 | if self.allow_None: |
1343 | d['None']=None | |
1363 | d['None'] = None | |
1344 | 1364 | return d |
1345 | 1365 | |
1346 | 1366 | |
1349 | 1369 | Parameter whose value is a list of objects, usually of a specified type. |
1350 | 1370 | |
1351 | 1371 | 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 | |
1353 | 1373 | 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 | |
1361 | 1386 | self.bounds = bounds |
1362 | Parameter.__init__(self,default=default,instantiate=instantiate, | |
1387 | Parameter.__init__(self, default=default, instantiate=instantiate, | |
1363 | 1388 | **params) |
1364 | 1389 | self._validate(default) |
1365 | 1390 | |
1366 | 1391 | def _validate(self, val): |
1367 | 1392 | """ |
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 | |
1374 | 1421 | 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)) | |
1397 | 1433 | |
1398 | 1434 | |
1399 | 1435 | class HookList(List): |
1404 | 1440 | for users to register a set of commands to be called at a |
1405 | 1441 | specified place in some sequence of processing steps. |
1406 | 1442 | """ |
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 | |
1410 | 1449 | 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)) | |
1413 | 1454 | |
1414 | 1455 | |
1415 | 1456 | class Dict(ClassSelector): |
1416 | 1457 | """ |
1417 | 1458 | Parameter whose value is a dictionary. |
1418 | 1459 | """ |
1460 | ||
1419 | 1461 | def __init__(self, default=None, **params): |
1420 | super(Dict,self).__init__(dict, default=default, **params) | |
1462 | super(Dict, self).__init__(dict, default=default, **params) | |
1421 | 1463 | |
1422 | 1464 | |
1423 | 1465 | class Array(ClassSelector): |
1426 | 1468 | """ |
1427 | 1469 | |
1428 | 1470 | def __init__(self, default=None, **params): |
1429 | # CEBALERT: instead use python array as default? | |
1430 | 1471 | 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) | |
1432 | 1482 | |
1433 | 1483 | |
1434 | 1484 | class DataFrame(ClassSelector): |
1449 | 1499 | if a list is given, the supplied DataFrame must contain exactly the |
1450 | 1500 | same columns and in the same order and no other columns. |
1451 | 1501 | """ |
1452 | __slots__ = ['rows','columns', 'ordered'] | |
1502 | ||
1503 | __slots__ = ['rows', 'columns', 'ordered'] | |
1453 | 1504 | |
1454 | 1505 | def __init__(self, default=None, rows=None, columns=None, ordered=None, **params): |
1455 | 1506 | from pandas import DataFrame as pdDFrame |
1456 | 1507 | self.rows = rows |
1457 | 1508 | self.columns = columns |
1458 | 1509 | self.ordered = ordered |
1459 | super(DataFrame,self).__init__(pdDFrame, default=default, allow_None=True, **params) | |
1510 | super(DataFrame,self).__init__(pdDFrame, default=default, **params) | |
1460 | 1511 | self._validate(self.default) |
1461 | ||
1462 | 1512 | |
1463 | 1513 | def _length_bounds_check(self, bounds, length, name): |
1464 | 1514 | message = '{name} length {length} does not match declared bounds of {bounds}' |
1478 | 1528 | |
1479 | 1529 | if isinstance(self.columns, set) and self.ordered is True: |
1480 | 1530 | raise ValueError('Columns cannot be ordered when specified as a set') |
1531 | ||
1532 | if self.allow_None and val is None: | |
1533 | return | |
1481 | 1534 | |
1482 | 1535 | if self.columns is None: |
1483 | 1536 | pass |
1501 | 1554 | if self.rows is not None: |
1502 | 1555 | self._length_bounds_check(self.rows, len(val), 'Row') |
1503 | 1556 | |
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 | ||
1504 | 1566 | |
1505 | 1567 | class Series(ClassSelector): |
1506 | 1568 | """ |
1510 | 1572 | which may be a number or an integer bounds tuple to constrain the |
1511 | 1573 | allowable number of rows. |
1512 | 1574 | """ |
1575 | ||
1513 | 1576 | __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) | |
1514 | 1584 | |
1515 | 1585 | def _length_bounds_check(self, bounds, length, name): |
1516 | 1586 | message = '{name} length {length} does not match declared bounds of {bounds}' |
1525 | 1595 | if failure: |
1526 | 1596 | raise ValueError(message.format(name=name,length=length, bounds=bounds)) |
1527 | 1597 | |
1528 | def __init__(self, default=None, rows=None, **params): | |
1529 | from pandas import Series as pdSeries | |
1530 | self.rows = rows | |
1531 | super(Series,self).__init__(pdSeries, allow_None=True, default=default, **params) | |
1532 | self._validate(self.default) | |
1533 | ||
1534 | 1598 | def _validate(self, val): |
1535 | 1599 | super(Series, self)._validate(val) |
1536 | 1600 | |
1601 | if self.allow_None and val is None: | |
1602 | return | |
1603 | ||
1537 | 1604 | if self.rows is not None: |
1538 | 1605 | self._length_bounds_check(self.rows, len(val), 'Row') |
1539 | 1606 | |
1541 | 1608 | |
1542 | 1609 | # For portable code: |
1543 | 1610 | # - 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, | |
1546 | 1613 | # and normalize_path() for paths to new files to be written. |
1547 | 1614 | |
1548 | 1615 | class resolve_path(ParameterizedFunction): |
1566 | 1633 | Prepended to a non-relative path, in order, until a file is |
1567 | 1634 | found.""") |
1568 | 1635 | |
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'.""") | |
1571 | 1641 | |
1572 | 1642 | def __call__(self, path, **params): |
1573 | 1643 | p = ParamOverrides(self, params) |
1574 | ||
1575 | 1644 | 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()] | |
1576 | 1650 | |
1577 | 1651 | 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)) | |
1590 | 1657 | |
1591 | 1658 | else: |
1592 | 1659 | paths_tried = [] |
1593 | 1660 | for prefix in p.search_paths: |
1594 | 1661 | try_path = os.path.join(os.path.normpath(prefix), path) |
1595 | 1662 | |
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 | |
1604 | 1667 | |
1605 | 1668 | paths_tried.append(try_path) |
1606 | 1669 | |
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) + ".") | |
1608 | 1671 | |
1609 | 1672 | |
1610 | 1673 | class normalize_path(ParameterizedFunction): |
1645 | 1708 | The specified path can be absolute, or relative to either: |
1646 | 1709 | |
1647 | 1710 | * any of the paths specified in the search_paths attribute (if |
1648 | search_paths is not None); | |
1711 | search_paths is not None); | |
1649 | 1712 | |
1650 | 1713 | or |
1651 | 1714 | |
1663 | 1726 | super(Path,self).__init__(default,**params) |
1664 | 1727 | |
1665 | 1728 | 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) | |
1670 | 1730 | |
1671 | 1731 | def _validate(self, val): |
1672 | 1732 | if val is None: |
1673 | 1733 | 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') | |
1675 | 1735 | else: |
1676 | 1736 | try: |
1677 | 1737 | self._resolve(val) |
1678 | 1738 | 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]) | |
1680 | 1740 | |
1681 | 1741 | def __get__(self, obj, objtype): |
1682 | 1742 | """ |
1707 | 1767 | |
1708 | 1768 | * any of the paths specified in the search_paths attribute (if |
1709 | 1769 | search_paths is not None); |
1770 | ||
1710 | 1771 | or |
1711 | 1772 | |
1712 | 1773 | * any of the paths searched by resolve_path() (if search_paths |
1714 | 1775 | """ |
1715 | 1776 | |
1716 | 1777 | 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) | |
1721 | 1779 | |
1722 | 1780 | |
1723 | 1781 | class Foldername(Path): |
1731 | 1789 | |
1732 | 1790 | * any of the paths specified in the search_paths attribute (if |
1733 | 1791 | search_paths is not None); |
1792 | ||
1734 | 1793 | or |
1735 | 1794 | |
1736 | 1795 | * any of the paths searched by resolve_dir_path() (if search_paths |
1738 | 1797 | """ |
1739 | 1798 | |
1740 | 1799 | 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) | |
1745 | 1801 | |
1746 | 1802 | |
1747 | 1803 | |
1757 | 1813 | |
1758 | 1814 | |
1759 | 1815 | |
1760 | class FileSelector(ObjectSelector): | |
1816 | class FileSelector(Selector): | |
1761 | 1817 | """ |
1762 | 1818 | Given a path glob, allows one file to be selected from those matching. |
1763 | 1819 | """ |
1764 | 1820 | __slots__ = ['path'] |
1765 | 1821 | |
1766 | 1822 | def __init__(self, default=None, path="", **kwargs): |
1767 | super(FileSelector, self).__init__(default, **kwargs) | |
1823 | self.default = default | |
1768 | 1824 | self.path = path |
1769 | 1825 | 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() | |
1770 | 1833 | |
1771 | 1834 | def update(self): |
1772 | 1835 | self.objects = sorted(glob.glob(self.path)) |
1778 | 1841 | return abbreviate_paths(self.path,super(FileSelector, self).get_range()) |
1779 | 1842 | |
1780 | 1843 | |
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 | |
1784 | 1847 | a list of possible objects. |
1785 | 1848 | """ |
1849 | ||
1850 | def __init__(self, default=None, objects=None, **kwargs): | |
1851 | super(ListSelector,self).__init__( | |
1852 | objects=objects, default=default, empty_default=True, **kwargs) | |
1786 | 1853 | |
1787 | 1854 | def compute_default(self): |
1788 | 1855 | if self.default is None and callable(self.compute_default_fn): |
1792 | 1859 | self.objects.append(o) |
1793 | 1860 | |
1794 | 1861 | def _validate(self, val): |
1862 | if (val is None and self.allow_None): | |
1863 | return | |
1795 | 1864 | for o in val: |
1796 | 1865 | super(ListSelector, self)._validate(o) |
1797 | 1866 | |
1804 | 1873 | __slots__ = ['path'] |
1805 | 1874 | |
1806 | 1875 | def __init__(self, default=None, path="", **kwargs): |
1807 | super(MultiFileSelector, self).__init__(default, **kwargs) | |
1876 | self.default = default | |
1808 | 1877 | self.path = path |
1809 | 1878 | 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() | |
1810 | 1885 | |
1811 | 1886 | def update(self): |
1812 | 1887 | self.objects = sorted(glob.glob(self.path)) |
1826 | 1901 | def __init__(self, default=None, **kwargs): |
1827 | 1902 | super(Date, self).__init__(default=default, **kwargs) |
1828 | 1903 | |
1829 | def _validate(self, val): | |
1904 | def _validate_value(self, val, allow_None): | |
1830 | 1905 | """ |
1831 | 1906 | Checks that the value is numeric and that it is within the hard |
1832 | 1907 | bounds; if not, an exception is raised. |
1834 | 1909 | if self.allow_None and val is None: |
1835 | 1910 | return |
1836 | 1911 | |
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): | |
1841 | 1917 | raise ValueError("Step parameter can only be None, a datetime or datetime type") |
1842 | 1918 | |
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") | |
1844 | 1928 | |
1845 | 1929 | |
1846 | 1930 | class CalendarDate(Number): |
1851 | 1935 | def __init__(self, default=None, **kwargs): |
1852 | 1936 | super(CalendarDate, self).__init__(default=default, **kwargs) |
1853 | 1937 | |
1854 | def _validate(self, val): | |
1938 | def _validate_value(self, val, allow_None): | |
1855 | 1939 | """ |
1856 | 1940 | Checks that the value is numeric and that it is within the hard |
1857 | 1941 | bounds; if not, an exception is raised. |
1859 | 1943 | if self.allow_None and val is None: |
1860 | 1944 | return |
1861 | 1945 | |
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() | |
1869 | 1960 | |
1870 | 1961 | |
1871 | 1962 | class Color(Parameter): |
1872 | 1963 | """ |
1873 | 1964 | 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): | |
1878 | 2008 | super(Color, self).__init__(default=default, **kwargs) |
2009 | self.allow_named = allow_named | |
1879 | 2010 | self._validate(default) |
1880 | 2011 | |
1881 | 2012 | 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): | |
1883 | 2018 | return |
1884 | 2019 | 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)) | |
1890 | 2034 | |
1891 | 2035 | |
1892 | 2036 | 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'] | |
1897 | 2042 | |
1898 | 2043 | def __init__(self,default=None, bounds=None, softbounds=None, |
1899 | inclusive_bounds=(True,True), **params): | |
2044 | inclusive_bounds=(True,True), step=None, **params): | |
1900 | 2045 | self.bounds = bounds |
1901 | 2046 | self.inclusive_bounds = inclusive_bounds |
1902 | 2047 | self.softbounds = softbounds |
2048 | self.step = step | |
1903 | 2049 | super(Range,self).__init__(default=default,length=2,**params) |
1904 | 2050 | |
1905 | ||
1906 | 2051 | def _validate(self, val): |
1907 | """ | |
1908 | Checks that the value is numeric and that it is within the hard | |
1909 | bounds; if not, an exception is raised. | |
1910 | """ | |
1911 | if self.allow_None and val is None: | |
1912 | return | |
1913 | 2052 | 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())) | |
1916 | 2066 | |
1917 | 2067 | |
1918 | 2068 | 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) | |
1941 | 2070 | |
1942 | 2071 | |
1943 | 2072 | def rangestr(self): |
1948 | 2077 | return '%s%s, %s%s' % (incmin, vmin, vmax, incmax) |
1949 | 2078 | |
1950 | 2079 | |
1951 | def _checkBounds(self, val): | |
1952 | if self.bounds is not None: | |
1953 | vmin,vmax = self.bounds | |
1954 | incmin,incmax = self.inclusive_bounds | |
1955 | for bound, v in zip(['lower', 'upper'], val): | |
1956 | too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin) | |
1957 | too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax) | |
1958 | if too_low or too_high: | |
1959 | raise ValueError("Parameter '%s' %s bound must be in range %s" | |
1960 | % (self.name, bound, self.rangestr())) | |
1961 | ||
1962 | ||
1963 | 2080 | class DateRange(Range): |
1964 | 2081 | """ |
1965 | 2082 | A datetime or date range specified as (start, end). |
1966 | 2083 | |
1967 | 2084 | Bounds must be specified as datetime or date types (see param.dt_types). |
1968 | 2085 | """ |
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: | |
1971 | 2089 | return |
1972 | 2090 | |
1973 | 2091 | 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)) | |
1976 | 2096 | |
1977 | 2097 | start, end = val |
1978 | 2098 | 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 | ||
1985 | 2103 | |
1986 | 2104 | |
1987 | 2105 | class CalendarDateRange(Range): |
1988 | 2106 | """ |
1989 | 2107 | A date range specified as (start_date, end_date). |
1990 | 2108 | """ |
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: | |
1993 | 2111 | return |
1994 | 2112 | |
1995 | 2113 | for n in val: |
1996 | 2114 | 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)) | |
1998 | 2117 | |
1999 | 2118 | start, end = val |
2000 | 2119 | 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) |
19 | 19 | __author__ = "Jean-Luc Stevens" |
20 | 20 | |
21 | 21 | import re |
22 | import sys | |
23 | import itertools | |
22 | 24 | import textwrap |
23 | 25 | import param |
24 | 26 | |
54 | 56 | def get_param_info(self, obj, include_super=True): |
55 | 57 | """ |
56 | 58 | Get the parameter dictionary, the list of modifed parameters |
57 | and the dictionary or parameter values. If include_super is | |
59 | and the dictionary of parameter values. If include_super is | |
58 | 60 | True, parameters are also collected from the super classes. |
59 | 61 | """ |
60 | 62 | |
64 | 66 | val_dict = dict((k,p.default) for (k,p) in params.items()) |
65 | 67 | self_class = obj |
66 | 68 | else: |
67 | changed = [name for (name,_) in obj.param.get_param_values(onlychanged=True)] | |
68 | val_dict = dict(obj.param.get_param_values()) | |
69 | changed = list(obj.param.values(onlychanged=True).keys()) | |
70 | val_dict = obj.param.values() | |
69 | 71 | self_class = obj.__class__ |
70 | 72 | |
71 | 73 | if not include_super: |
85 | 87 | |
86 | 88 | (params, val_dict, changed) = info |
87 | 89 | contents = [] |
88 | displayed_params = {} | |
89 | for name, p in params.items(): | |
90 | displayed_params = [] | |
91 | for name in self.sort_by_precedence(params): | |
90 | 92 | if only_changed and not (name in changed): |
91 | 93 | continue |
92 | displayed_params[name] = p | |
93 | ||
94 | right_shift = max(len(name) for name in displayed_params.keys())+2 | |
95 | ||
96 | for i, name in enumerate(sorted(displayed_params)): | |
97 | p = displayed_params[name] | |
94 | displayed_params.append((name, params[name])) | |
95 | ||
96 | right_shift = max(len(name) for name, _ in displayed_params)+2 | |
97 | ||
98 | for i, (name, p) in enumerate(displayed_params): | |
98 | 99 | heading = "%s: " % name |
99 | 100 | unindented = textwrap.dedent("< No docstring available >" if p.doc is None else p.doc) |
100 | 101 | |
123 | 124 | return "\n".join(contents) |
124 | 125 | |
125 | 126 | |
127 | def sort_by_precedence(self, parameters): | |
128 | """ | |
129 | Sort the provided dictionary of parameters by their precedence value. | |
130 | In Python 3, preserves the original ordering for parameters with the | |
131 | same precedence; for Python 2 sorts them lexicographically by name, | |
132 | unless explicit precedences are provided. | |
133 | """ | |
134 | params = [(p, pobj) for p, pobj in parameters.items()] | |
135 | key_fn = lambda x: x[1].precedence if x[1].precedence is not None else 1e-8 | |
136 | sorted_params = sorted(params, key=key_fn) | |
137 | groups = itertools.groupby(sorted_params, key=key_fn) | |
138 | # Params preserve definition order in Python 3.6+ | |
139 | dict_ordered = ( | |
140 | (sys.version_info.major == 3 and sys.version_info.minor >= 6) or | |
141 | (sys.version_info.major > 3) or | |
142 | all(p.precedence is not None for p in parameters.values()) | |
143 | ) | |
144 | ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups] | |
145 | ordered_params = [el[0] for group in ordered_groups for el in group | |
146 | if (el[0] != 'name' or el[0] in parameters)] | |
147 | return ordered_params | |
148 | ||
149 | ||
126 | 150 | def _build_table(self, info, order, max_col_len=40, only_changed=False): |
127 | 151 | """ |
128 | 152 | Collect the information about parameters needed to build a |
129 | 153 | properly formatted table and then tabulate it. |
130 | 154 | """ |
131 | 155 | |
132 | info_dict, bounds_dict = {}, {} | |
156 | info_list, bounds_dict = [], {} | |
133 | 157 | (params, val_dict, changed) = info |
134 | 158 | col_widths = dict((k,0) for k in order) |
135 | 159 | |
136 | for name, p in params.items(): | |
160 | ordering = self.sort_by_precedence(params) | |
161 | for name in ordering: | |
162 | p = params[name] | |
137 | 163 | if only_changed and not (name in changed): |
138 | 164 | continue |
139 | 165 | |
142 | 168 | allow_None = ' AN' if hasattr(p, 'allow_None') and p.allow_None else '' |
143 | 169 | |
144 | 170 | mode = '%s %s%s' % (constant, readonly, allow_None) |
145 | info_dict[name] = {'name': name, 'type':p.__class__.__name__, | |
146 | 'mode':mode} | |
171 | ||
172 | value = repr(val_dict[name]) | |
173 | if len(value) > (max_col_len - 3): | |
174 | value = value[:max_col_len-3] + '...' | |
175 | ||
176 | p_dict = {'name': name, 'type': p.__class__.__name__, | |
177 | 'mode': mode, 'value': value} | |
147 | 178 | |
148 | 179 | if hasattr(p, 'bounds'): |
149 | 180 | lbound, ubound = (None,None) if p.bounds is None else p.bounds |
161 | 192 | |
162 | 193 | if (lbound, ubound) != (None,None): |
163 | 194 | bounds_dict[name] = (mark_lbound, mark_ubound) |
164 | info_dict[name]['bounds'] = '(%s, %s)' % (lbound, ubound) | |
165 | ||
166 | value = repr(val_dict[name]) | |
167 | if len(value) > (max_col_len - 3): | |
168 | value = value[:max_col_len-3] + '...' | |
169 | info_dict[name]['value'] = value | |
170 | ||
171 | for col in info_dict[name]: | |
172 | max_width = max([col_widths[col], len(info_dict[name][col])]) | |
195 | p_dict['bounds'] = '(%s, %s)' % (lbound, ubound) | |
196 | ||
197 | for col in p_dict: | |
198 | max_width = max([col_widths[col], len(p_dict[col])]) | |
173 | 199 | col_widths[col] = max_width |
174 | 200 | |
175 | return self._tabulate(info_dict, col_widths, changed, order, bounds_dict) | |
176 | ||
177 | ||
178 | def _tabulate(self, info_dict, col_widths, changed, order, bounds_dict): | |
201 | info_list.append((name, p_dict)) | |
202 | ||
203 | return self._tabulate(info_list, col_widths, changed, order, bounds_dict) | |
204 | ||
205 | ||
206 | def _tabulate(self, info_list, col_widths, changed, order, bounds_dict): | |
179 | 207 | """ |
180 | 208 | Returns the supplied information as a table suitable for |
181 | 209 | printing or paging. |
182 | 210 | |
183 | info_dict: Dictionary of the parameters name, type and mode. | |
211 | info_list: List of the parameters name, type and mode. | |
184 | 212 | col_widths: Dictionary of column widths in characters |
185 | 213 | changed: List of parameters modified from their defaults. |
186 | 214 | order: The order of the table columns |
188 | 216 | """ |
189 | 217 | |
190 | 218 | contents, tail = [], [] |
191 | column_set = set(k for row in info_dict.values() for k in row) | |
219 | column_set = set(k for _, row in info_list for k in row) | |
192 | 220 | columns = [col for col in order if col in column_set] |
193 | 221 | |
194 | 222 | title_row = [] |
201 | 229 | contents.append(blue % ''.join(title_row)+"\n") |
202 | 230 | |
203 | 231 | # Format the table rows |
204 | for row in sorted(info_dict): | |
232 | for row, info in info_list: | |
205 | 233 | row_list = [] |
206 | info = info_dict[row] | |
207 | 234 | for i,col in enumerate(columns): |
208 | 235 | width = col_widths[col]+2 |
209 | 236 | val = info[col] if (col in info) else '' |
257 | 284 | heading_text = "%s\n%s\n" % (title, heading_line) |
258 | 285 | |
259 | 286 | param_info = self.get_param_info(param_obj, include_super=True) |
260 | ||
261 | 287 | if not param_info[0]: |
262 | 288 | return "%s\n%s" % ((green % heading_text), "Object has no parameters.") |
263 | 289 | |
265 | 291 | only_changed=False) |
266 | 292 | |
267 | 293 | docstrings = self.param_docstrings(param_info, max_col_len=100, only_changed=False) |
268 | ||
269 | 294 | dflt_msg = "Parameters changed from their default values are marked in red." |
270 | 295 | top_heading = (green % heading_text) |
271 | 296 | top_heading += "\n%s" % (red % dflt_msg) |
278 | 303 | return "%s\n\n%s\n\n%s\n\n%s" % (top_heading, table, docstring_heading, docstrings) |
279 | 304 | |
280 | 305 | |
281 | message = """Welcome to the param IPython extension! (http://ioam.github.io/param/)""" | |
306 | message = """Welcome to the param IPython extension! (https://param.holoviz.org/)""" | |
282 | 307 | message += '\nAvailable magics: %params' |
283 | 308 | |
284 | 309 | _loaded = False |
0 | 0 | """ |
1 | 1 | Generic support for objects with full-featured Parameters and |
2 | 2 | messaging. |
3 | ||
4 | This file comes from the Param library (https://github.com/holoviz/param) | |
5 | but can be taken out of the param module and used on its own if desired, | |
6 | either alone (providing basic Parameter support) or with param's | |
7 | __init__.py (providing specialized Parameter types). | |
3 | 8 | """ |
4 | 9 | |
5 | 10 | import copy |
10 | 15 | import numbers |
11 | 16 | import operator |
12 | 17 | |
13 | from collections import namedtuple, OrderedDict | |
18 | # Allow this file to be used standalone if desired, albeit without JSON serialization | |
19 | try: | |
20 | from . import serializer | |
21 | except ImportError: | |
22 | serializer = None | |
23 | ||
24 | ||
25 | from collections import defaultdict, namedtuple, OrderedDict | |
26 | from functools import partial, wraps, reduce | |
14 | 27 | from operator import itemgetter,attrgetter |
15 | 28 | from types import FunctionType |
16 | from functools import partial, wraps, reduce | |
17 | 29 | |
18 | 30 | import logging |
19 | 31 | from contextlib import contextmanager |
25 | 37 | param_pager = ParamPager(metaclass=True) # Generates param description |
26 | 38 | except: |
27 | 39 | param_pager = None |
40 | ||
41 | try: | |
42 | from inspect import getfullargspec | |
43 | except: | |
44 | from inspect import getargspec as getfullargspec # python2 | |
28 | 45 | |
29 | 46 | basestring = basestring if sys.version_info[0]==2 else str # noqa: it is defined |
30 | 47 | |
64 | 81 | object_count = 0 |
65 | 82 | warning_count = 0 |
66 | 83 | |
84 | class _Undefined: | |
85 | """ | |
86 | Dummy value to signal completely undefined values rather than | |
87 | simple None values. | |
88 | """ | |
67 | 89 | |
68 | 90 | @contextmanager |
69 | 91 | def logging_level(level): |
87 | 109 | |
88 | 110 | |
89 | 111 | @contextmanager |
90 | def batch_watch(parameterized, enable=True, run=True): | |
91 | """ | |
92 | Context manager to batch watcher events on a parameterized object. | |
93 | The context manager will queue any events triggered by setting a | |
94 | parameter on the supplied parameterized object and dispatch them | |
95 | all at once when the context manager exits. If run=False the | |
96 | queued events are not dispatched and should be processed manually. | |
112 | def _batch_call_watchers(parameterized, enable=True, run=True): | |
113 | """ | |
114 | Internal version of batch_call_watchers, adding control over queueing and running. | |
115 | Only actually batches events if enable=True; otherwise a no-op. Only actually | |
116 | calls the accumulated watchers on exit if run=True; otherwise they remain queued. | |
97 | 117 | """ |
98 | 118 | BATCH_WATCH = parameterized.param._BATCH_WATCH |
99 | 119 | parameterized.param._BATCH_WATCH = enable or parameterized.param._BATCH_WATCH |
102 | 122 | finally: |
103 | 123 | parameterized.param._BATCH_WATCH = BATCH_WATCH |
104 | 124 | if run and not BATCH_WATCH: |
125 | parameterized.param._batch_call_watchers() | |
126 | ||
127 | batch_watch = _batch_call_watchers # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later. | |
128 | ||
129 | @contextmanager | |
130 | def batch_call_watchers(parameterized): | |
131 | """ | |
132 | Context manager to batch events to provide to Watchers on a | |
133 | parameterized object. This context manager queues any events | |
134 | triggered by setting a parameter on the supplied parameterized | |
135 | object, saving them up to dispatch them all at once when the | |
136 | context manager exits. | |
137 | """ | |
138 | BATCH_WATCH = parameterized.param._BATCH_WATCH | |
139 | parameterized.param._BATCH_WATCH = True | |
140 | try: | |
141 | yield | |
142 | finally: | |
143 | parameterized.param._BATCH_WATCH = BATCH_WATCH | |
144 | if not BATCH_WATCH: | |
105 | 145 | parameterized.param._batch_call_watchers() |
106 | 146 | |
107 | 147 | |
132 | 172 | """ |
133 | 173 | batch_watch = parameterized.param._BATCH_WATCH |
134 | 174 | parameterized.param._BATCH_WATCH = True |
135 | watchers, events = parameterized.param._watchers, parameterized.param._events | |
175 | watchers, events = (list(parameterized.param._watchers), | |
176 | list(parameterized.param._events)) | |
136 | 177 | try: |
137 | 178 | yield |
138 | 179 | except: |
143 | 184 | parameterized.param._events = events |
144 | 185 | |
145 | 186 | |
187 | # External components can register an async executor which will run | |
188 | # async functions | |
189 | async_executor = None | |
190 | ||
191 | ||
146 | 192 | def classlist(class_): |
147 | 193 | """ |
148 | 194 | Return a list of the class hierarchy above (and including) the given class. |
149 | 195 | |
150 | Same as inspect.getmro(class_)[::-1] | |
196 | Same as `inspect.getmro(class_)[::-1]` | |
151 | 197 | """ |
152 | 198 | return inspect.getmro(class_)[::-1] |
153 | 199 | |
173 | 219 | |
174 | 220 | def get_all_slots(class_): |
175 | 221 | """ |
176 | Return a list of slot names for slots defined in class_ and its | |
222 | Return a list of slot names for slots defined in `class_` and its | |
177 | 223 | superclasses. |
178 | 224 | """ |
179 | 225 | # A subclass's __slots__ attribute does not contain slots defined |
217 | 263 | return arg1==arg2 |
218 | 264 | |
219 | 265 | |
220 | ||
221 | # For Python 2 compatibility. | |
266 | # PARAM2_DEPRECATION: For Python 2 compatibility only; can be removed in param2. | |
222 | 267 | # |
223 | 268 | # The syntax to use a metaclass changed incompatibly between 2 and |
224 | 269 | # 3. The add_metaclass() class decorator below creates a class using a |
239 | 284 | return wrapper |
240 | 285 | |
241 | 286 | |
287 | ||
242 | 288 | class bothmethod(object): # pylint: disable-msg=R0903 |
243 | 289 | """ |
244 | 290 | 'optional @classmethod' |
269 | 315 | return reduce(_getattr, [obj] + attr.split('.')) |
270 | 316 | |
271 | 317 | |
272 | # (thought I was going to have a few decorators following this pattern) | |
273 | 318 | def accept_arguments(f): |
319 | """ | |
320 | Decorator for decorators that accept arguments | |
321 | """ | |
274 | 322 | @wraps(f) |
275 | 323 | def _f(*args, **kwargs): |
276 | 324 | return lambda actual_f: f(actual_f, *args, **kwargs) |
285 | 333 | return cls |
286 | 334 | |
287 | 335 | |
336 | def iscoroutinefunction(function): | |
337 | """ | |
338 | Whether the function is an asynchronous coroutine function. | |
339 | """ | |
340 | if not hasattr(inspect, 'iscoroutinefunction'): | |
341 | return False | |
342 | return inspect.isasyncgenfunction(function) or inspect.iscoroutinefunction(function) | |
343 | ||
344 | ||
288 | 345 | def instance_descriptor(f): |
289 | # If parameter has an instance Parameter delegate setting | |
346 | # If parameter has an instance Parameter, delegate setting | |
290 | 347 | def _f(self, obj, val): |
291 | 348 | instance_param = getattr(obj, '_instance__params', {}).get(self.name) |
292 | 349 | if instance_param is not None and self is not instance_param: |
296 | 353 | return _f |
297 | 354 | |
298 | 355 | |
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 | ||
299 | 367 | @accept_arguments |
300 | 368 | def depends(func, *dependencies, **kw): |
301 | 369 | """ |
308 | 376 | on Parameter values, or on other metadata about the Parameter. |
309 | 377 | """ |
310 | 378 | |
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) | |
314 | 383 | |
315 | 384 | @wraps(func) |
316 | def _depends(*args,**kw): | |
317 | return func(*args,**kw) | |
385 | def _depends(*args, **kw): | |
386 | return func(*args, **kw) | |
318 | 387 | |
319 | 388 | deps = list(dependencies)+list(kw.values()) |
320 | 389 | string_specs = False |
345 | 414 | 'or function is not supported when referencing ' |
346 | 415 | 'parameters by name.') |
347 | 416 | |
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): | |
350 | 419 | args = (getattr(dep.owner, dep.name) for dep in dependencies) |
351 | 420 | dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()} |
352 | 421 | return func(*args, **dep_kwargs) |
353 | 422 | |
423 | grouped = defaultdict(list) | |
354 | 424 | 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]) | |
356 | 428 | |
357 | 429 | _dinfo = getattr(func, '_dinfo', {}) |
358 | 430 | _dinfo.update({'dependencies': dependencies, |
359 | 'kw': kw, 'watch': watch}) | |
431 | 'kw': kw, 'watch': watch, 'on_init': on_init}) | |
360 | 432 | |
361 | 433 | _depends._dinfo = _dinfo |
362 | 434 | |
458 | 530 | return _output |
459 | 531 | |
460 | 532 | |
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", {}) | |
464 | 568 | 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) | |
469 | 574 | 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 | ||
482 | 776 | |
483 | 777 | class ParameterMetaclass(type): |
484 | 778 | """ |
485 | 779 | Metaclass allowing control over creation of Parameter classes. |
486 | 780 | """ |
487 | 781 | def __new__(mcs,classname,bases,classdict): |
782 | ||
488 | 783 | # store the class's docstring in __classdoc |
489 | 784 | if '__doc__' in classdict: |
490 | 785 | 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 | |
493 | 788 | classdict['__doc__']=property(attrgetter('doc')) |
494 | 789 | |
495 | 790 | # To get the benefit of slots, subclasses must themselves define |
498 | 793 | # a __dict__ unless it also defines __slots__. |
499 | 794 | if '__slots__' not in classdict: |
500 | 795 | classdict['__slots__']=[] |
796 | ||
797 | # No special handling for a __dict__ slot; should there be? | |
501 | 798 | |
502 | 799 | return type.__new__(mcs,classname,bases,classdict) |
503 | 800 | |
511 | 808 | |
512 | 809 | |
513 | 810 | |
514 | # CEBALERT: we break some aspects of slot handling for Parameter and | |
515 | # Parameterized. The __new__ methods in the metaclasses for those two | |
516 | # classes omit to handle the case where __dict__ is passed in | |
517 | # __slots__ (and they possibly omit other things too). Additionally, | |
518 | # various bits of code in the Parameterized class assumes that all | |
519 | # Parameterized instances have a __dict__, but I'm not sure that's | |
520 | # guaranteed to be true (although it's true at the moment). | |
521 | ||
522 | ||
523 | # CB: we could maybe reduce the complexity by doing something to allow | |
524 | # a parameter to discover things about itself when created (would also | |
525 | # allow things like checking a Parameter is owned by a | |
526 | # Parameterized). I have some vague ideas about what to do. | |
527 | 811 | @add_metaclass(ParameterMetaclass) |
528 | 812 | class Parameter(object): |
529 | 813 | """ |
541 | 825 | objects Foo and Bar, such that Bar has a parameter delta, Foo is a |
542 | 826 | subclass of Bar, and Foo has parameters alpha, sigma, and gamma |
543 | 827 | (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 | ... | |
556 | 839 | |
557 | 840 | Class Foo would then have four parameters, with delta defaulting |
558 | 841 | to 0.6. |
563 | 846 | constructed: The default constructor for Foo (and Bar) will |
564 | 847 | accept arbitrary keyword arguments, each of which can be used |
565 | 848 | 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:: | |
567 | 850 | |
568 | 851 | myfoo = Foo(alpha=0.5) |
569 | 852 | |
580 | 863 | 2. A Parameterized class need specify only the attributes of a |
581 | 864 | Parameter whose values differ from those declared in |
582 | 865 | superclasses; the other values will be inherited. E.g. if Foo |
583 | declares | |
866 | declares:: | |
584 | 867 | |
585 | 868 | delta = Parameter(default=0.2) |
586 | 869 | |
659 | 942 | # attributes. Using __slots__ requires special support for |
660 | 943 | # operations to copy and restore Parameters (e.g. for Python |
661 | 944 | # 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', | |
665 | 948 | 'watchers', 'owner', '_label'] |
666 | 949 | |
667 | 950 | # Note: When initially created, a Parameter does not know which |
670 | 953 | # class is created, owner, name, and _internal_name are |
671 | 954 | # set. |
672 | 955 | |
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, | |
675 | 960 | pickle_default_value=True, allow_None=False, |
676 | 961 | 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 | |
689 | 1030 | inheritance of Parameter slots (attributes) from the owning-class' |
690 | 1031 | 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 | ||
701 | 1034 | self.name = None |
702 | self._internal_name = None | |
703 | 1035 | self.owner = None |
704 | self._label = label | |
705 | 1036 | self.precedence = precedence |
706 | 1037 | self.default = default |
707 | 1038 | self.doc = doc |
708 | 1039 | self.constant = constant or readonly # readonly => constant |
709 | 1040 | self.readonly = readonly |
1041 | self._label = label | |
1042 | self._internal_name = None | |
710 | 1043 | self._set_instantiate(instantiate) |
711 | 1044 | self.pickle_default_value = pickle_default_value |
712 | 1045 | self.allow_None = (default is None or allow_None) |
713 | 1046 | self.watchers = {} |
714 | 1047 | self.per_instance = per_instance |
715 | 1048 | |
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) | |
716 | 1067 | |
717 | 1068 | @property |
718 | 1069 | def label(self): |
727 | 1078 | |
728 | 1079 | def _set_instantiate(self,instantiate): |
729 | 1080 | """Constant parameters must be instantiated.""" |
730 | # CB: instantiate doesn't actually matter for read-only | |
1081 | # instantiate doesn't actually matter for read-only | |
731 | 1082 | # parameters, since they can't be set even on a class. But |
732 | # this avoids needless instantiation. | |
1083 | # having this code avoids needless instantiation. | |
733 | 1084 | if self.readonly: |
734 | 1085 | self.instantiate = False |
735 | 1086 | else: |
736 | 1087 | self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201 |
737 | 1088 | |
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__ | |
747 | 1096 | 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) | |
749 | 1100 | except AttributeError as e: |
750 | if attribute in self.__slots__: | |
1101 | if slot_attribute: | |
751 | 1102 | # If Parameter slot is defined but an AttributeError was raised |
752 | 1103 | # we are in __setstate__ and watchers should not be triggered |
753 | 1104 | old = NotImplemented |
756 | 1107 | |
757 | 1108 | super(Parameter, self).__setattr__(attribute, value) |
758 | 1109 | |
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 | |
768 | 1127 | """ |
769 | 1128 | Return the value for this Parameter. |
770 | 1129 | |
776 | 1135 | instance's value, if one has been set - otherwise produce the |
777 | 1136 | class's value (default). |
778 | 1137 | """ |
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 | |
783 | 1139 | result = self.default |
784 | 1140 | else: |
785 | 1141 | result = obj.__dict__.get(self._internal_name,self.default) |
786 | 1142 | return result |
787 | 1143 | |
788 | ||
789 | 1144 | @instance_descriptor |
790 | def __set__(self,obj,val): | |
1145 | def __set__(self, obj, val): | |
791 | 1146 | """ |
792 | 1147 | Set the value for this Parameter. |
793 | 1148 | |
797 | 1152 | If called for a Parameterized instance, set the value of |
798 | 1153 | this Parameter on that instance (i.e. in the instance's |
799 | 1154 | __dict__, under the parameter's internal_name). |
800 | ||
801 | 1155 | |
802 | 1156 | If the Parameter's constant attribute is True, only allows |
803 | 1157 | the value to be set for a Parameterized class or on |
810 | 1164 | |
811 | 1165 | Note that until we support some form of read-only |
812 | 1166 | 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 | |
819 | 1173 | if hasattr(self, 'set_hook'): |
820 | 1174 | val = self.set_hook(obj,val) |
821 | 1175 | |
822 | 1176 | self._validate(val) |
823 | 1177 | |
824 | 1178 | _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 | |
827 | 1180 | if self.constant or self.readonly: |
828 | 1181 | 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: | |
831 | 1184 | _old = self.default |
832 | 1185 | self.default = val |
833 | 1186 | elif not obj.initialized: |
834 | _old = obj.__dict__.get(self._internal_name,self.default) | |
1187 | _old = obj.__dict__.get(self._internal_name, self.default) | |
835 | 1188 | obj.__dict__[self._internal_name] = val |
836 | 1189 | 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) | |
839 | 1193 | else: |
840 | 1194 | if obj is None: |
841 | 1195 | _old = self.default |
842 | 1196 | self.default = val |
843 | 1197 | else: |
844 | _old = obj.__dict__.get(self._internal_name,self.default) | |
1198 | _old = obj.__dict__.get(self._internal_name, self.default) | |
845 | 1199 | obj.__dict__[self._internal_name] = val |
846 | 1200 | |
847 | 1201 | self._post_setter(obj, val) |
848 | 1202 | |
1203 | if obj is not None: | |
1204 | if not getattr(obj, 'initialized', False): | |
1205 | return | |
1206 | obj.param._update_deps(self.name) | |
1207 | ||
849 | 1208 | 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") | |
851 | 1214 | 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 | ||
855 | 1217 | obj = self.owner if obj is None else obj |
856 | if obj is None: | |
1218 | ||
1219 | if obj is None or not watchers: | |
857 | 1220 | return |
858 | 1221 | |
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): | |
860 | 1227 | obj.param._call_watcher(watcher, event) |
861 | 1228 | if not obj.param._BATCH_WATCH: |
862 | 1229 | obj.param._batch_call_watchers() |
863 | 1230 | |
1231 | def _validate_value(self, value, allow_None): | |
1232 | """Implements validation for parameter value""" | |
864 | 1233 | |
865 | 1234 | 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) | |
868 | 1237 | |
869 | 1238 | def _post_setter(self, obj, val): |
870 | 1239 | """Called after the parameter value has been validated and set""" |
871 | 1240 | |
872 | ||
873 | 1241 | def __delete__(self,obj): |
874 | 1242 | raise TypeError("Cannot delete '%s': Parameters deletion not allowed." % self.name) |
875 | ||
876 | 1243 | |
877 | 1244 | def _set_names(self, attrib_name): |
878 | 1245 | if None not in (self.owner, self.name) and attrib_name != self.name: |
885 | 1252 | % (type(self).__name__, self.name, |
886 | 1253 | self.owner.name, attrib_name)) |
887 | 1254 | self.name = attrib_name |
888 | ||
889 | self._internal_name = "_%s_param_value"%attrib_name | |
890 | ||
1255 | self._internal_name = "_%s_param_value" % attrib_name | |
891 | 1256 | |
892 | 1257 | def __getstate__(self): |
893 | 1258 | """ |
920 | 1285 | |
921 | 1286 | # Define one particular type of Parameter that is used in this file |
922 | 1287 | class String(Parameter): |
923 | """ | |
1288 | r""" | |
924 | 1289 | A String Parameter, with a default value and optional regular expression (regex) matching. |
925 | 1290 | |
926 | 1291 | Example of using a regex to implement IPv4 address matching:: |
928 | 1293 | class IPAddress(String): |
929 | 1294 | '''IPv4 address as a string (dotted decimal notation)''' |
930 | 1295 | 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]?)$' | |
932 | 1297 | super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs) |
933 | ||
934 | 1298 | |
935 | 1299 | """ |
936 | 1300 | |
942 | 1306 | self.allow_None = (default is None or allow_None) |
943 | 1307 | self._validate(default) |
944 | 1308 | |
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 | ||
945 | 1323 | 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) | |
954 | 1326 | |
955 | 1327 | |
956 | 1328 | class shared_parameters(object): |
985 | 1357 | @wraps(fn) |
986 | 1358 | def override_initialization(self_,*args,**kw): |
987 | 1359 | 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 | |
992 | 1364 | return override_initialization |
993 | 1365 | |
994 | 1366 | |
1061 | 1433 | class or the instance as necessary. |
1062 | 1434 | """ |
1063 | 1435 | |
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 | |
1065 | 1437 | # None for no action, True to raise and False to warn. |
1066 | 1438 | |
1067 | 1439 | def __init__(self_, cls, self=None): |
1071 | 1443 | """ |
1072 | 1444 | self_.cls = cls |
1073 | 1445 | 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 | |
1078 | 1483 | |
1079 | 1484 | @property |
1080 | 1485 | def self_or_cls(self_): |
1081 | 1486 | return self_.cls if self_.self is None else self_.self |
1082 | 1487 | |
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) | |
1083 | 1497 | |
1084 | 1498 | def __getitem__(self_, key): |
1085 | 1499 | """ |
1088 | 1502 | inst = self_.self |
1089 | 1503 | parameters = self_.objects(False) if inst is None else inst.param.objects(False) |
1090 | 1504 | 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 | |
1092 | 1506 | not getattr(inst, '_disable_instance__params', False)): |
1093 | 1507 | if key not in inst._instance__params: |
1094 | 1508 | try: |
1099 | 1513 | except: |
1100 | 1514 | raise |
1101 | 1515 | finally: |
1102 | p.watchers = watchers | |
1516 | p.watchers = {k: list(v) for k, v in watchers.items()} | |
1103 | 1517 | p.owner = inst |
1104 | 1518 | inst._instance__params[key] = p |
1105 | 1519 | else: |
1170 | 1584 | First, ensures that all Parameters with 'instantiate=True' |
1171 | 1585 | (typically used for mutable Parameters) are copied directly |
1172 | 1586 | 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 | |
1174 | 1588 | keyword arguments, warning when any of them are not defined as |
1175 | 1589 | parameters. |
1176 | 1590 | |
1178 | 1592 | """ |
1179 | 1593 | self = self_.param.self |
1180 | 1594 | ## 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) | |
1183 | 1597 | params_to_instantiate = {} |
1184 | 1598 | for class_ in classlist(type(self)): |
1185 | 1599 | if not issubclass(class_, Parameterized): |
1186 | 1600 | continue |
1187 | for (k,v) in class_.__dict__.items(): | |
1601 | for (k, v) in class_.param._parameters.items(): | |
1188 | 1602 | # (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 | |
1191 | 1605 | |
1192 | 1606 | for p in params_to_instantiate.values(): |
1193 | 1607 | self.param._instantiate_param(p) |
1194 | 1608 | |
1195 | 1609 | ## keyword arg setting |
1196 | for name,val in params.items(): | |
1610 | for name, val in params.items(): | |
1197 | 1611 | desc = self.__class__.get_param_descriptor(name)[0] # pylint: disable-msg=E1101 |
1198 | 1612 | 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) | |
1200 | 1614 | # 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 | |
1203 | 1618 | @classmethod |
1204 | 1619 | def deprecate(cls, fn): |
1205 | 1620 | """ |
1230 | 1645 | return not Comparator.is_equal(event.old, event.new) |
1231 | 1646 | |
1232 | 1647 | |
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): | |
1235 | 1649 | # deepcopy param_obj.default into self.__dict__ (or dict_ if supplied) |
1236 | 1650 | # under the parameter's _internal_name (or key if supplied) |
1237 | 1651 | self = self_.self |
1238 | 1652 | dict_ = dict_ or self.__dict__ |
1239 | 1653 | key = key or param_obj._internal_name |
1240 | param_key = (str(type(self)), param_obj.name) | |
1241 | 1654 | if shared_parameters._share: |
1655 | param_key = (str(type(self)), param_obj.name) | |
1242 | 1656 | if param_key in shared_parameters._shared_cache: |
1243 | 1657 | new_object = shared_parameters._shared_cache[param_key] |
1244 | 1658 | else: |
1246 | 1660 | shared_parameters._shared_cache[param_key] = new_object |
1247 | 1661 | else: |
1248 | 1662 | 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): | |
1252 | 1667 | 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 | |
1256 | 1671 | new_object.param._generate_name() |
1257 | 1672 | |
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 | ||
1258 | 1778 | # Classmethods |
1259 | 1779 | |
1780 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1260 | 1781 | def print_param_defaults(self_): |
1261 | 1782 | """Print the default values of all cls's Parameters.""" |
1262 | 1783 | cls = self_.cls |
1265 | 1786 | print(cls.__name__+'.'+key+ '='+ repr(val.default)) |
1266 | 1787 | |
1267 | 1788 | |
1789 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1268 | 1790 | def set_default(self_,param_name,value): |
1269 | 1791 | """ |
1270 | 1792 | Set the default value of param_name. |
1275 | 1797 | setattr(cls,param_name,value) |
1276 | 1798 | |
1277 | 1799 | |
1278 | def _add_parameter(self_, param_name,param_obj): | |
1800 | def add_parameter(self_, param_name, param_obj): | |
1279 | 1801 | """ |
1280 | 1802 | Add a new Parameter object into this object's class. |
1281 | 1803 | |
1282 | Supposed to result in a Parameter equivalent to one declared | |
1804 | Should result in a Parameter equivalent to one declared | |
1283 | 1805 | in the class's source code. |
1284 | 1806 | """ |
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). | |
1293 | 1811 | cls = self_.cls |
1294 | 1812 | type.__setattr__(cls,param_name,param_obj) |
1295 | 1813 | ParameterizedMetaclass._initialize_parameter(cls,param_name,param_obj) |
1299 | 1817 | except AttributeError: |
1300 | 1818 | pass |
1301 | 1819 | |
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 | |
1303 | 1825 | def params(self_, parameter_name=None): |
1304 | 1826 | """ |
1305 | 1827 | Return the Parameters of this class as the |
1308 | 1830 | Includes Parameters from this class and its |
1309 | 1831 | superclasses. |
1310 | 1832 | """ |
1311 | if self_.self is not None and self_.self._instance__params: | |
1312 | self_.warning('The Parameterized instance has instance ' | |
1313 | 'parameters created using new-style param ' | |
1314 | 'APIs, which are incompatible with .params. ' | |
1315 | 'Use the new more explicit APIs on the ' | |
1316 | '.param accessor to query parameter instances.' | |
1317 | 'To query all parameter instances use ' | |
1318 | '.param.objects with the option to return ' | |
1319 | 'either class or instance parameter objects. ' | |
1320 | 'Alternatively use .param[name] indexing to ' | |
1321 | 'access a specific parameter object by name.') | |
1322 | ||
1323 | 1833 | pdict = self_.objects(instance='existing') |
1324 | 1834 | if parameter_name is None: |
1325 | 1835 | return pdict |
1328 | 1838 | |
1329 | 1839 | # Bothmethods |
1330 | 1840 | |
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. | |
1340 | 1845 | """ |
1341 | 1846 | BATCH_WATCH = self_.self_or_cls.param._BATCH_WATCH |
1342 | 1847 | self_.self_or_cls.param._BATCH_WATCH = True |
1343 | 1848 | self_or_cls = self_.self_or_cls |
1344 | 1849 | 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] | |
1347 | 1852 | else: |
1348 | 1853 | 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" % | |
1350 | 1855 | (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' | |
1351 | 1863 | |
1352 | 1864 | for (k, v) in kwargs.items(): |
1353 | 1865 | if k not in self_or_cls.param: |
1363 | 1875 | if not BATCH_WATCH: |
1364 | 1876 | self_._batch_call_watchers() |
1365 | 1877 | |
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 | ||
1366 | 1905 | |
1367 | 1906 | def objects(self_, instance=True): |
1368 | 1907 | """ |
1377 | 1916 | instance='existing'. |
1378 | 1917 | """ |
1379 | 1918 | 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, | |
1381 | 1920 | # and parameters are rarely added (and cannot be deleted) |
1382 | 1921 | try: |
1383 | 1922 | pdict = getattr(cls, '_%s__params' % cls.__name__) |
1397 | 1936 | |
1398 | 1937 | if instance and self_.self is not None: |
1399 | 1938 | if instance == 'existing': |
1400 | if self_.self._instance__params: | |
1939 | if getattr(self_.self, 'initialized', False) and self_.self._instance__params: | |
1401 | 1940 | return dict(pdict, **self_.self._instance__params) |
1402 | 1941 | return pdict |
1403 | 1942 | else: |
1409 | 1948 | """ |
1410 | 1949 | Trigger watchers for the given set of parameter names. Watchers |
1411 | 1950 | 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 | ||
1414 | 1960 | events = self_.self_or_cls.param._events |
1415 | 1961 | watchers = self_.self_or_cls.param._watchers |
1416 | 1962 | self_.self_or_cls.param._events = [] |
1417 | 1963 | self_.self_or_cls.param._watchers = [] |
1418 | param_values = dict(self_.get_param_values()) | |
1964 | param_values = self_.values() | |
1419 | 1965 | params = {name: param_values[name] for name in param_names} |
1420 | 1966 | self_.self_or_cls.param._TRIGGER = True |
1421 | self_.set_param(**params) | |
1967 | self_.set_param(**dict(params, **triggers)) | |
1422 | 1968 | self_.self_or_cls.param._TRIGGER = False |
1423 | 1969 | self_.self_or_cls.param._events += events |
1424 | 1970 | self_.self_or_cls.param._watchers += watchers |
1435 | 1981 | return Event(what=event.what, name=event.name, obj=event.obj, cls=event.cls, |
1436 | 1982 | old=event.old, new=event.new, type=event_type) |
1437 | 1983 | |
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 | ||
1438 | 2001 | def _call_watcher(self_, watcher, event): |
1439 | 2002 | """ |
1440 | Invoke the given the watcher appropriately given a Event object. | |
2003 | Invoke the given watcher appropriately given an Event object. | |
1441 | 2004 | """ |
1442 | 2005 | if self_.self_or_cls.param._TRIGGER: |
1443 | 2006 | pass |
1450 | 2013 | self_._watchers.append(watcher) |
1451 | 2014 | else: |
1452 | 2015 | 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,)) | |
1459 | 2018 | |
1460 | 2019 | def _batch_call_watchers(self_): |
1461 | 2020 | """ |
1469 | 2028 | self_.self_or_cls.param._events = [] |
1470 | 2029 | self_.self_or_cls.param._watchers = [] |
1471 | 2030 | |
1472 | for watcher in watchers: | |
2031 | for watcher in sorted(watchers, key=lambda w: w.precedence): | |
1473 | 2032 | events = [self_._update_event_type(watcher, event_dict[(name, watcher.what)], |
1474 | 2033 | self_.self_or_cls.param._TRIGGER) |
1475 | 2034 | for name in watcher.parameter_names |
1476 | 2035 | 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) | |
1483 | 2038 | |
1484 | 2039 | def set_dynamic_time_fn(self_,time_fn,sublistattr=None): |
1485 | 2040 | """ |
1522 | 2077 | for obj in sublist: |
1523 | 2078 | obj.param.set_dynamic_time_fn(time_fn,sublistattr) |
1524 | 2079 | |
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 | ||
1527 | 2125 | Return a list of name,value pairs for all Parameters of this |
1528 | 2126 | object. |
1529 | 2127 | |
1532 | 2130 | (onlychanged has no effect when called on a class). |
1533 | 2131 | """ |
1534 | 2132 | self_or_cls = self_.self_or_cls |
1535 | # CEB: we'd actually like to know whether a value has been | |
1536 | # explicitly set on the instance, but I'm not sure that's easy | |
1537 | # (would need to distinguish instantiation of default from | |
1538 | # user setting of value). | |
1539 | 2133 | 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(): | |
1541 | 2135 | 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)) | |
1545 | 2138 | |
1546 | 2139 | vals.sort(key=itemgetter(0)) |
1547 | 2140 | return vals |
1548 | 2141 | |
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)) | |
1549 | 2155 | |
1550 | 2156 | def force_new_dynamic_value(self_, name): # pylint: disable-msg=E0213 |
1551 | 2157 | """ |
1571 | 2177 | return param_obj.__get__(slf, cls) |
1572 | 2178 | else: |
1573 | 2179 | return param_obj._force(slf, cls) |
1574 | ||
1575 | 2180 | |
1576 | 2181 | def get_value_generator(self_,name): # pylint: disable-msg=E0213 |
1577 | 2182 | """ |
1632 | 2237 | |
1633 | 2238 | return value |
1634 | 2239 | |
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 | |
1639 | 2261 | |
1640 | 2262 | def outputs(self_): |
1641 | 2263 | """ |
1656 | 2278 | outputs[name] = (otype, method, idx) |
1657 | 2279 | return outputs |
1658 | 2280 | |
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 | """ | |
1664 | 2306 | if isinstance(spec, Parameter): |
1665 | 2307 | inst = spec.owner if isinstance(spec.owner, Parameterized) else None |
1666 | 2308 | cls = spec.owner if inst is None else type(inst) |
1667 | 2309 | info = PInfo(inst=inst, cls=cls, name=spec.name, |
1668 | 2310 | 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) | |
1684 | 2340 | if attr == 'param': |
1685 | dependencies = self_._spec_to_obj(obj[1:]) | |
2341 | deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate) | |
1686 | 2342 | 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 | |
1689 | 2347 | elif attr in src.param: |
1690 | what = what if what != '' else 'value' | |
1691 | 2348 | info = PInfo(inst=inst, cls=cls, name=attr, |
1692 | 2349 | 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)] | |
1693 | 2355 | 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'): | |
1700 | 2367 | parameter_names = watcher.parameter_names |
1701 | 2368 | for parameter_name in parameter_names: |
1702 | 2369 | if parameter_name not in self_.cls.param: |
1717 | 2384 | watchers[what] = [] |
1718 | 2385 | getattr(watchers[what], action)(watcher) |
1719 | 2386 | |
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): | |
1721 | 2431 | parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,) |
1722 | 2432 | watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, mode='args', |
1723 | 2433 | 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) | |
1726 | 2436 | return watcher |
1727 | 2437 | |
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. | |
1731 | 2441 | """ |
1732 | 2442 | 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,) | |
1740 | 2463 | watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, |
1741 | 2464 | 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) | |
1745 | 2468 | return watcher |
1746 | 2469 | |
1747 | ||
1748 | 2470 | # Instance methods |
1749 | 2471 | |
1750 | ||
2472 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1751 | 2473 | def defaults(self_): |
1752 | 2474 | """ |
1753 | 2475 | Return {parameter_name:parameter.default} for all non-constant |
1758 | 2480 | """ |
1759 | 2481 | self = self_.self |
1760 | 2482 | d = {} |
1761 | for param_name,param in self.param.objects('existing').items(): | |
2483 | for param_name, param in self.param.objects('existing').items(): | |
1762 | 2484 | if param.constant: |
1763 | 2485 | 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 | |
1768 | 2489 | return d |
1769 | 2490 | |
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. | |
1774 | 2494 | def __db_print(self_,level,msg,*args,**kw): |
1775 | 2495 | """ |
1776 | 2496 | Calls the logger returned by the get_logger() function, |
1786 | 2506 | |
1787 | 2507 | get_logger(name=self_or_cls.name).log(level, msg, *args, **kw) |
1788 | 2508 | |
2509 | # PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12 | |
1789 | 2510 | def print_param_values(self_): |
1790 | 2511 | """Print the values of all this object's Parameters.""" |
1791 | 2512 | self = self_.self |
1792 | for name,val in self.param.get_param_values(): | |
2513 | for name, val in self.param.values().items(): | |
1793 | 2514 | print('%s.%s = %s' % (self.name,name,val)) |
1794 | 2515 | |
1795 | 2516 | def warning(self_, msg,*args,**kw): |
1800 | 2521 | |
1801 | 2522 | See Python's logging module for details of message formatting. |
1802 | 2523 | """ |
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 | |
1810 | 2527 | def message(self_,msg,*args,**kw): |
1811 | 2528 | """ |
1812 | 2529 | Print msg merged with args as a message. |
1815 | 2532 | """ |
1816 | 2533 | self_.__db_print(INFO,msg,*args,**kw) |
1817 | 2534 | |
2535 | # PARAM2_DEPRECATION: Could be removed post param 2.0 | |
1818 | 2536 | def verbose(self_,msg,*args,**kw): |
1819 | 2537 | """ |
1820 | 2538 | Print msg merged with args as a verbose message. |
1823 | 2541 | """ |
1824 | 2542 | self_.__db_print(VERBOSE,msg,*args,**kw) |
1825 | 2543 | |
2544 | # PARAM2_DEPRECATION: Could be removed post param 2.0 | |
1826 | 2545 | def debug(self_,msg,*args,**kw): |
1827 | 2546 | """ |
1828 | 2547 | Print msg merged with args as a debugging statement. |
1831 | 2550 | """ |
1832 | 2551 | self_.__db_print(DEBUG,msg,*args,**kw) |
1833 | 2552 | |
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) | |
1844 | 2580 | |
1845 | 2581 | |
1846 | 2582 | |
1867 | 2603 | attribute __abstract set to True. The 'abstract' attribute can be |
1868 | 2604 | used to find out if a class is abstract or not. |
1869 | 2605 | """ |
1870 | def __init__(mcs,name,bases,dict_): | |
2606 | def __init__(mcs, name, bases, dict_): | |
1871 | 2607 | """ |
1872 | 2608 | Initialize the class object (not an instance of the class, but |
1873 | 2609 | the class itself). |
1876 | 2612 | default values (see __param_inheritance()) and setting |
1877 | 2613 | attrib_names (see _set_names()). |
1878 | 2614 | """ |
1879 | type.__init__(mcs,name,bases,dict_) | |
2615 | type.__init__(mcs, name, bases, dict_) | |
1880 | 2616 | |
1881 | 2617 | # Give Parameterized classes a useful 'name' attribute. |
1882 | # (Could instead consider changing the instance Parameter | |
1883 | # 'name' to '__name__'?) | |
1884 | 2618 | mcs.name = name |
1885 | 2619 | |
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) | |
1887 | 2627 | |
1888 | 2628 | # All objects (with their names) of type Parameter that are |
1889 | 2629 | # 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) | |
1892 | 2634 | |
1893 | 2635 | for param_name,param in parameters: |
1894 | mcs._initialize_parameter(param_name,param) | |
2636 | mcs._initialize_parameter(param_name, param) | |
1895 | 2637 | |
1896 | 2638 | # 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')] | |
1899 | 2641 | |
1900 | 2642 | _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: | |
1905 | 2644 | 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) | |
1908 | 2663 | |
1909 | 2664 | mcs.param._depends = {'watch': _watch} |
1910 | 2665 | |
1938 | 2693 | |
1939 | 2694 | |
1940 | 2695 | 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 | |
1942 | 2697 | # Parameterized class has for it |
1943 | 2698 | param._set_names(param_name) |
1944 | 2699 | mcs.__param_inheritance(param_name,param) |
1945 | 2700 | |
1946 | 2701 | |
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 | |
1949 | 2704 | def __is_abstract(mcs): |
1950 | 2705 | """ |
1951 | 2706 | Return True if the class has an attribute __abstract set to True. |
1960 | 2715 | # _ParameterizedMetaclass__abstract before running, but |
1961 | 2716 | # the actual class object will have an attribute |
1962 | 2717 | # _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 | |
1964 | 2720 | try: |
1965 | return getattr(mcs,'_%s__abstract'%mcs.__name__) | |
2721 | return getattr(mcs,'_%s__abstract'%mcs.__name__.lstrip("_")) | |
1966 | 2722 | except AttributeError: |
1967 | 2723 | return False |
1968 | 2724 | |
1969 | 2725 | abstract = property(__is_abstract) |
1970 | 2726 | |
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): | |
1974 | 2733 | """ |
1975 | 2734 | Implements 'self.attribute_name=value' in a way that also supports Parameters. |
1976 | 2735 | |
2012 | 2771 | # (For instance, python's own pickling mechanism |
2013 | 2772 | # caches __slotnames__ on the class: |
2014 | 2773 | # 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. | |
2018 | 2777 | if not attribute_name.startswith('_'): |
2019 | 2778 | get_logger().log(WARNING, |
2020 | 2779 | "Setting non-Parameter class attribute %s.%s = %s ", |
2048 | 2807 | Note that instantiate is handled differently: if there is a |
2049 | 2808 | parameter with the same name in one of the superclasses with |
2050 | 2809 | instantiate set to True, this parameter will inherit |
2051 | instatiate=True. | |
2810 | instantiate=True. | |
2052 | 2811 | """ |
2053 | 2812 | # get all relevant slots (i.e. slots defined in all |
2054 | 2813 | # superclasses of this parameter) |
2112 | 2871 | |
2113 | 2872 | |
2114 | 2873 | |
2115 | # JABALERT: Only partially achieved so far -- objects of the same | |
2116 | # type and parameter values are treated as different, so anything | |
2117 | # for which instantiate == True is reported as being non-default. | |
2118 | ||
2119 | 2874 | # Whether script_repr should avoid reporting the values of parameters |
2120 | 2875 | # 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. | |
2121 | 2879 | script_repr_suppress_defaults=True |
2122 | 2880 | |
2123 | 2881 | |
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=[], | |
2148 | 2928 | unknown_value='<?>', qualify=False, separator=''): |
2149 | 2929 | """ |
2150 | (Experimental) Pretty printed representation of a parameterized | |
2930 | Pretty printed representation of a parameterized | |
2151 | 2931 | object that may be evaluated with eval. |
2152 | 2932 | |
2153 | 2933 | Similar to repr except introspection of the constructor (__init__) |
2181 | 2961 | (e.g. a newline could be supplied to have each Parameter appear on a |
2182 | 2962 | separate line). |
2183 | 2963 | |
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 | ||
2191 | 2975 | if isinstance(val,type): |
2192 | 2976 | rep = type_script_repr(val,imports,prefix,settings) |
2193 | 2977 | |
2194 | 2978 | elif type(val) in script_repr_reg: |
2195 | 2979 | rep = script_repr_reg[type(val)](val,imports,prefix,settings) |
2196 | 2980 | |
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) | |
2206 | 2985 | else: |
2207 | 2986 | rep=repr(val) |
2208 | 2987 | |
2209 | 2988 | return rep |
2210 | 2989 | |
2211 | 2990 | |
2212 | #: see script_repr() | |
2991 | # Registry for special handling for certain types in script_repr and pprint | |
2213 | 2992 | script_repr_reg = {} |
2214 | 2993 | |
2215 | 2994 | |
2246 | 3025 | pass # Support added only if those libraries are available |
2247 | 3026 | |
2248 | 3027 | |
2249 | # why I have to type prefix and settings? | |
2250 | 3028 | def function_script_repr(fn,imports,prefix,settings): |
2251 | 3029 | name = fn.__name__ |
2252 | 3030 | module = fn.__module__ |
2271 | 3049 | dbprint_prefix=None |
2272 | 3050 | |
2273 | 3051 | |
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 | |
2277 | 3079 | |
2278 | 3080 | |
2279 | 3081 | @add_metaclass(ParameterizedMetaclass) |
2320 | 3122 | see documentation for the 'logging' module. |
2321 | 3123 | """ |
2322 | 3124 | |
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): | |
2328 | 3129 | global object_count |
2329 | 3130 | |
2330 | 3131 | # Flag that can be tested to see if e.g. constant Parameters |
2331 | 3132 | # 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 | } | |
2335 | 3140 | self._instance__params = {} |
2336 | 3141 | self._param_watchers = {} |
3142 | self._dynamic_watchers = defaultdict(list) | |
2337 | 3143 | |
2338 | 3144 | self.param._generate_name() |
2339 | 3145 | self.param._setup_params(**params) |
2340 | 3146 | object_count += 1 |
2341 | 3147 | |
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) | |
2356 | 3155 | |
2357 | 3156 | # 'Special' methods |
2358 | 3157 | |
2362 | 3161 | copy of the object's __dict__ and that also includes the |
2363 | 3162 | object's __slots__ (if it has any). |
2364 | 3163 | """ |
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__) | |
2366 | 3165 | state = self.__dict__.copy() |
2367 | ||
2368 | 3166 | for slot in get_occupied_slots(self): |
2369 | 3167 | state[slot] = getattr(self,slot) |
2370 | 3168 | |
2385 | 3183 | """ |
2386 | 3184 | self.initialized=False |
2387 | 3185 | |
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 | ||
2388 | 3205 | if '_instance__params' not in state: |
2389 | 3206 | state['_instance__params'] = {} |
2390 | 3207 | if '_param_watchers' not in state: |
2391 | 3208 | state['_param_watchers'] = {} |
3209 | state.pop('param', None) | |
2392 | 3210 | |
2393 | 3211 | for name,value in state.items(): |
2394 | 3212 | setattr(self,name,value) |
2395 | 3213 | self.initialized=True |
2396 | 3214 | |
3215 | @recursive_repr() | |
2397 | 3216 | def __repr__(self): |
2398 | 3217 | """ |
2399 | 3218 | Provide a nearly valid Python representation that could be used to recreate |
2404 | 3223 | """ |
2405 | 3224 | try: |
2406 | 3225 | 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()] | |
2408 | 3229 | except RuntimeError: # Handle recursion in parameter depth |
2409 | 3230 | settings = [] |
2410 | 3231 | return self.__class__.__name__ + "(" + ", ".join(settings) + ")" |
2414 | 3235 | return "<%s %s>" % (self.__class__.__name__,self.name) |
2415 | 3236 | |
2416 | 3237 | |
3238 | # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead | |
2417 | 3239 | def script_repr(self,imports=[],prefix=" "): |
2418 | 3240 | """ |
2419 | Variant of __repr__ designed for generating a runnable script. | |
3241 | Deprecated variant of __repr__ designed for generating a runnable script. | |
2420 | 3242 | """ |
2421 | 3243 | return self.pprint(imports,prefix, unknown_value=None, qualify=True, |
2422 | 3244 | separator="\n") |
2423 | 3245 | |
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='<?>', | |
2426 | 3248 | qualify=False, separator=""): |
2427 | 3249 | """ |
2428 | 3250 | (Experimental) Pretty printed representation that may be |
2429 | 3251 | evaluated with eval. See pprint() function for more details. |
2430 | 3252 | """ |
2431 | 3253 | 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 | |
2436 | 3255 | imports[:] = list(set(imports)) |
3256 | ||
2437 | 3257 | # Generate import statement |
2438 | 3258 | mod = self.__module__ |
2439 | 3259 | bits = mod.split('.') |
2440 | 3260 | imports.append("import %s"%mod) |
2441 | 3261 | imports.append("import %s"%bits[0]) |
2442 | 3262 | |
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__) | |
2446 | 3266 | args = spec.args[1:] if spec.args[0] == 'self' else spec.args |
2447 | 3267 | |
2448 | 3268 | if spec.defaults is not None: |
2486 | 3306 | if k in posargs: |
2487 | 3307 | # value will be unknown_value unless k is a parameter |
2488 | 3308 | 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))): | |
2490 | 3312 | # Explicit modified keywords or parameters in |
2491 | 3313 | # precendence order (if **kwargs present) |
2492 | 3314 | keywords.append('%s=%s' % (k, value)) |
2497 | 3319 | arguments = arglist + keywords + (['**%s' % spec.varargs] if spec.varargs else []) |
2498 | 3320 | return qualifier + '%s(%s)' % (self.__class__.__name__, (','+separator+prefix).join(arguments)) |
2499 | 3321 | |
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 | |
2502 | 3326 | # dynamic parameters set on a class can't have state saved. This |
2503 | 3327 | # is because, to do this, state_push() would need to be a |
2504 | 3328 | # @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.) | |
2507 | 3331 | |
2508 | 3332 | def state_push(self): |
2509 | 3333 | """ |
2644 | 3468 | |
2645 | 3469 | |
2646 | 3470 | |
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 | |
2648 | 3472 | # dictionary. This might allow us to use a decorator to simplify using |
2649 | 3473 | # ParamOverrides (if that does indeed make them simpler to use). |
2650 | 3474 | # http://docs.python.org/whatsnew/2.6.html |
2671 | 3495 | supplied dict_ that are not also parameters of the overridden |
2672 | 3496 | object will be available via the extra_keywords() method. |
2673 | 3497 | """ |
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): | |
2677 | 3500 | # def __init__(self,overridden,**kw): |
2678 | 3501 | # ... |
2679 | 3502 | # dict.__init__(self,**kw) |
2680 | # be faster/easier to use? | |
2681 | 3503 | self._overridden = overridden |
2682 | 3504 | dict.__init__(self,dict_) |
2683 | 3505 | |
2689 | 3511 | def extra_keywords(self): |
2690 | 3512 | """ |
2691 | 3513 | 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 | |
2693 | 3515 | overridden object. |
2694 | 3516 | """ |
2695 | 3517 | return self._extra_keywords |
2697 | 3519 | def param_keywords(self): |
2698 | 3520 | """ |
2699 | 3521 | 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 | |
2701 | 3523 | overridden object (i.e. not extra keywords/parameters). |
2702 | 3524 | """ |
2703 | 3525 | return dict((key, self[key]) for key in self if key not in self.extra_keywords()) |
2754 | 3576 | for name, val in params.items(): |
2755 | 3577 | if name not in overridden_object_params: |
2756 | 3578 | 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() | |
2760 | 3581 | return extra_keywords |
2761 | 3582 | |
2762 | 3583 | |
2777 | 3598 | """ |
2778 | 3599 | __abstract = True |
2779 | 3600 | |
2780 | # CEBALERT: shouldn't this have come from a parent class | |
2781 | # somewhere? | |
2782 | 3601 | def __str__(self): |
2783 | 3602 | return self.__class__.__name__+"()" |
2784 | 3603 | |
2793 | 3612 | cls = self_or_cls |
2794 | 3613 | else: |
2795 | 3614 | p = params |
2796 | params = dict(self_or_cls.get_param_values()) | |
3615 | params = self_or_cls.param.values() | |
2797 | 3616 | params.update(p) |
2798 | 3617 | params.pop('name') |
2799 | 3618 | cls = self_or_cls.__class__ |
2817 | 3636 | # Control reconstruction (during unpickling and copying): |
2818 | 3637 | # ensure that ParameterizedFunction.__new__ is skipped |
2819 | 3638 | 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 | |
2821 | 3640 | # module level rather than Parameterized.__new__ directly |
2822 | 3641 | # 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... | |
2824 | 3643 | return (_new_parameterized,(self.__class__,),state) |
2825 | 3644 | |
3645 | # PARAM2_DEPRECATION: Remove this compatibility alias for param 2.0 and later; use self.param.pprint instead | |
2826 | 3646 | def script_repr(self,imports=[],prefix=" "): |
2827 | 3647 | """ |
2828 | 3648 | Same as Parameterized.script_repr, except that X.classname(Y |
2832 | 3652 | separator="\n") |
2833 | 3653 | |
2834 | 3654 | |
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 | |
2839 | 3659 | is replaced with X.classname.instance(Y |
2840 | 3660 | """ |
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) | |
2844 | 3664 | classname=self.__class__.__name__ |
2845 | 3665 | return r.replace(".%s("%classname,".%s.instance("%classname) |
2846 | 3666 | |
2873 | 3693 | label_formatter = default_label_formatter |
2874 | 3694 | |
2875 | 3695 | |
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 | |
2884 | 3698 | # 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. | |
2885 | 3701 | class overridable_property(object): |
2886 | 3702 | """ |
2887 | 3703 | The same as Python's "property" attribute, but allows the accessor |
0 | """ | |
1 | Classes used to support string serialization of Parameters and | |
2 | Parameterized objects. | |
3 | """ | |
4 | ||
5 | import json | |
6 | import textwrap | |
7 | ||
8 | class UnserializableException(Exception): | |
9 | pass | |
10 | ||
11 | class UnsafeserializableException(Exception): | |
12 | pass | |
13 | ||
14 | def JSONNullable(json_type): | |
15 | "Express a JSON schema type as nullable to easily support Parameters that allow_None" | |
16 | return {'anyOf': [ json_type, {'type': 'null'}] } | |
17 | ||
18 | ||
19 | ||
20 | class Serialization(object): | |
21 | """ | |
22 | Base class used to implement different types of serialization. | |
23 | """ | |
24 | ||
25 | @classmethod | |
26 | def schema(cls, pobj, subset=None): | |
27 | raise NotImplementedError # noqa: unimplemented method | |
28 | ||
29 | @classmethod | |
30 | def serialize_parameters(cls, pobj, subset=None): | |
31 | """ | |
32 | Serialize the parameters on a Parameterized object into a | |
33 | single serialized object, e.g. a JSON string. | |
34 | """ | |
35 | raise NotImplementedError # noqa: unimplemented method | |
36 | ||
37 | @classmethod | |
38 | def deserialize_parameters(cls, pobj, serialized, subset=None): | |
39 | """ | |
40 | Deserialize a serialized object representing one or | |
41 | more Parameters into a dictionary of parameter values. | |
42 | """ | |
43 | raise NotImplementedError # noqa: unimplemented method | |
44 | ||
45 | @classmethod | |
46 | def serialize_parameter_value(cls, pobj, pname): | |
47 | """ | |
48 | Serialize a single parameter value. | |
49 | """ | |
50 | raise NotImplementedError # noqa: unimplemented method | |
51 | ||
52 | @classmethod | |
53 | def deserialize_parameter_value(cls, pobj, pname, value): | |
54 | """ | |
55 | Deserialize a single parameter value. | |
56 | """ | |
57 | raise NotImplementedError # noqa: unimplemented method | |
58 | ||
59 | ||
60 | class JSONSerialization(Serialization): | |
61 | """ | |
62 | Class responsible for specifying JSON serialization, deserialization | |
63 | and JSON schemas for Parameters and Parameterized classes and | |
64 | objects. | |
65 | """ | |
66 | ||
67 | unserializable_parameter_types = ['Callable'] | |
68 | ||
69 | json_schema_literal_types = { | |
70 | int:'integer', float:'number', str:'string', | |
71 | type(None): 'null' | |
72 | } | |
73 | ||
74 | @classmethod | |
75 | def loads(cls, serialized): | |
76 | return json.loads(serialized) | |
77 | ||
78 | @classmethod | |
79 | def dumps(cls, obj): | |
80 | return json.dumps(obj) | |
81 | ||
82 | @classmethod | |
83 | def schema(cls, pobj, safe=False, subset=None): | |
84 | schema = {} | |
85 | for name, p in pobj.param.objects('existing').items(): | |
86 | if subset is not None and name not in subset: | |
87 | continue | |
88 | schema[name] = p.schema(safe=safe) | |
89 | if p.doc: | |
90 | schema[name]['description'] = textwrap.dedent(p.doc).replace('\n', ' ').strip() | |
91 | if p.label: | |
92 | schema[name]['title'] = p.label | |
93 | return schema | |
94 | ||
95 | @classmethod | |
96 | def serialize_parameters(cls, pobj, subset=None): | |
97 | components = {} | |
98 | for name, p in pobj.param.objects('existing').items(): | |
99 | if subset is not None and name not in subset: | |
100 | continue | |
101 | value = pobj.param.get_value_generator(name) | |
102 | components[name] = p.serialize(value) | |
103 | return cls.dumps(components) | |
104 | ||
105 | @classmethod | |
106 | def deserialize_parameters(cls, pobj, serialization, subset=None): | |
107 | deserialized = cls.loads(serialization) | |
108 | components = {} | |
109 | for name, value in deserialized.items(): | |
110 | if subset is not None and name not in subset: | |
111 | continue | |
112 | deserialized = pobj.param[name].deserialize(value) | |
113 | components[name] = deserialized | |
114 | return components | |
115 | ||
116 | # Parameter level methods | |
117 | ||
118 | @classmethod | |
119 | def _get_method(cls, ptype, suffix): | |
120 | "Returns specialized method if available, otherwise None" | |
121 | method_name = ptype.lower()+'_' + suffix | |
122 | return getattr(cls, method_name, None) | |
123 | ||
124 | @classmethod | |
125 | def param_schema(cls, ptype, p, safe=False, subset=None): | |
126 | if ptype in cls.unserializable_parameter_types: | |
127 | raise UnserializableException | |
128 | dispatch_method = cls._get_method(ptype, 'schema') | |
129 | if dispatch_method: | |
130 | schema = dispatch_method(p, safe=safe) | |
131 | else: | |
132 | schema = {'type': ptype.lower()} | |
133 | return JSONNullable(schema) if p.allow_None else schema | |
134 | ||
135 | @classmethod | |
136 | def serialize_parameter_value(cls, pobj, pname): | |
137 | value = pobj.param.get_value_generator(pname) | |
138 | return cls.dumps(pobj.param[pname].serialize(value)) | |
139 | ||
140 | @classmethod | |
141 | def deserialize_parameter_value(cls, pobj, pname, value): | |
142 | value = cls.loads(value) | |
143 | return pobj.param[pname].deserialize(value) | |
144 | ||
145 | # Custom Schemas | |
146 | ||
147 | @classmethod | |
148 | def class__schema(cls, class_, safe=False): | |
149 | from .parameterized import Parameterized | |
150 | if isinstance(class_, tuple): | |
151 | return {'anyOf': [cls.class__schema(cls_) for cls_ in class_]} | |
152 | elif class_ in cls.json_schema_literal_types: | |
153 | return {'type': cls.json_schema_literal_types[class_]} | |
154 | elif issubclass(class_, Parameterized): | |
155 | return {'type': 'object', 'properties': class_.param.schema(safe)} | |
156 | else: | |
157 | return {'type': 'object'} | |
158 | ||
159 | @classmethod | |
160 | def array_schema(cls, p, safe=False): | |
161 | if safe is True: | |
162 | msg = ('Array is not guaranteed to be safe for ' | |
163 | 'serialization as the dtype is unknown') | |
164 | raise UnsafeserializableException(msg) | |
165 | return {'type': 'array'} | |
166 | ||
167 | @classmethod | |
168 | def classselector_schema(cls, p, safe=False): | |
169 | return cls.class__schema(p.class_, safe=safe) | |
170 | ||
171 | @classmethod | |
172 | def dict_schema(cls, p, safe=False): | |
173 | if safe is True: | |
174 | msg = ('Dict is not guaranteed to be safe for ' | |
175 | 'serialization as the key and value types are unknown') | |
176 | raise UnsafeserializableException(msg) | |
177 | return {'type': 'object'} | |
178 | ||
179 | @classmethod | |
180 | def date_schema(cls, p, safe=False): | |
181 | return {'type': 'string', 'format': 'date-time'} | |
182 | ||
183 | @classmethod | |
184 | def calendardate_schema(cls, p, safe=False): | |
185 | return {'type': 'string', 'format': 'date'} | |
186 | ||
187 | @classmethod | |
188 | def tuple_schema(cls, p, safe=False): | |
189 | schema = {'type': 'array'} | |
190 | if p.length is not None: | |
191 | schema['minItems'] = p.length | |
192 | schema['maxItems'] = p.length | |
193 | return schema | |
194 | ||
195 | @classmethod | |
196 | def number_schema(cls, p, safe=False): | |
197 | schema = {'type': p.__class__.__name__.lower() } | |
198 | return cls.declare_numeric_bounds(schema, p.bounds, p.inclusive_bounds) | |
199 | ||
200 | @classmethod | |
201 | def declare_numeric_bounds(cls, schema, bounds, inclusive_bounds): | |
202 | "Given an applicable numeric schema, augment with bounds information" | |
203 | if bounds is not None: | |
204 | (low, high) = bounds | |
205 | if low is not None: | |
206 | key = 'minimum' if inclusive_bounds[0] else 'exclusiveMinimum' | |
207 | schema[key] = low | |
208 | if high is not None: | |
209 | key = 'maximum' if inclusive_bounds[1] else 'exclusiveMaximum' | |
210 | schema[key] = high | |
211 | return schema | |
212 | ||
213 | @classmethod | |
214 | def integer_schema(cls, p, safe=False): | |
215 | return cls.number_schema(p) | |
216 | ||
217 | @classmethod | |
218 | def numerictuple_schema(cls, p, safe=False): | |
219 | schema = cls.tuple_schema(p, safe=safe) | |
220 | schema['additionalItems'] = {'type': 'number'} | |
221 | return schema | |
222 | ||
223 | @classmethod | |
224 | def xycoordinates_schema(cls, p, safe=False): | |
225 | return cls.numerictuple_schema(p, safe=safe) | |
226 | ||
227 | @classmethod | |
228 | def range_schema(cls, p, safe=False): | |
229 | schema = cls.tuple_schema(p, safe=safe) | |
230 | bounded_number = cls.declare_numeric_bounds( | |
231 | {'type': 'number'}, p.bounds, p.inclusive_bounds) | |
232 | schema['additionalItems'] = bounded_number | |
233 | return schema | |
234 | ||
235 | @classmethod | |
236 | def list_schema(cls, p, safe=False): | |
237 | schema = {'type': 'array'} | |
238 | if safe is True and p.item_type is None: | |
239 | msg = ('List without a class specified cannot be guaranteed ' | |
240 | 'to be safe for serialization') | |
241 | raise UnsafeserializableException(msg) | |
242 | if p.class_ is not None: | |
243 | schema['items'] = cls.class__schema(p.item_type, safe=safe) | |
244 | return schema | |
245 | ||
246 | @classmethod | |
247 | def objectselector_schema(cls, p, safe=False): | |
248 | try: | |
249 | allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]} | |
250 | for obj in p.objects] | |
251 | schema = {'anyOf': allowed_types} | |
252 | schema['enum'] = p.objects | |
253 | return schema | |
254 | except: | |
255 | if safe is True: | |
256 | msg = ('ObjectSelector cannot be guaranteed to be safe for ' | |
257 | 'serialization due to unserializable type in objects') | |
258 | raise UnsafeserializableException(msg) | |
259 | return {} | |
260 | ||
261 | @classmethod | |
262 | def selector_schema(cls, p, safe=False): | |
263 | try: | |
264 | allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]} | |
265 | for obj in p.objects.values()] | |
266 | schema = {'anyOf': allowed_types} | |
267 | schema['enum'] = p.objects | |
268 | return schema | |
269 | except: | |
270 | if safe is True: | |
271 | msg = ('Selector cannot be guaranteed to be safe for ' | |
272 | 'serialization due to unserializable type in objects') | |
273 | raise UnsafeserializableException(msg) | |
274 | return {} | |
275 | ||
276 | @classmethod | |
277 | def listselector_schema(cls, p, safe=False): | |
278 | if p.objects is None: | |
279 | if safe is True: | |
280 | msg = ('ListSelector cannot be guaranteed to be safe for ' | |
281 | 'serialization as allowed objects unspecified') | |
282 | return {'type': 'array'} | |
283 | for obj in p.objects: | |
284 | if type(obj) not in cls.json_schema_literal_types: | |
285 | msg = 'ListSelector cannot serialize type %s' % type(obj) | |
286 | raise UnserializableException(msg) | |
287 | return {'type': 'array', 'items': {'enum': p.objects}} | |
288 | ||
289 | @classmethod | |
290 | def dataframe_schema(cls, p, safe=False): | |
291 | schema = {'type': 'array'} | |
292 | if safe is True: | |
293 | msg = ('DataFrame is not guaranteed to be safe for ' | |
294 | 'serialization as the column dtypes are unknown') | |
295 | raise UnsafeserializableException(msg) | |
296 | if p.columns is None: | |
297 | schema['items'] = {'type': 'object'} | |
298 | return schema | |
299 | ||
300 | mincols, maxcols = None, None | |
301 | if isinstance(p.columns, int): | |
302 | mincols, maxcols = p.columns, p.columns | |
303 | elif isinstance(p.columns, tuple): | |
304 | mincols, maxcols = p.columns | |
305 | ||
306 | if isinstance(p.columns, int) or isinstance(p.columns, tuple): | |
307 | schema['items'] = {'type': 'object', 'minItems': mincols, | |
308 | 'maxItems': maxcols} | |
309 | ||
310 | if isinstance(p.columns, list) or isinstance(p.columns, set): | |
311 | literal_types = [{'type':el} for el in cls.json_schema_literal_types.values()] | |
312 | allowable_types = {'anyOf': literal_types} | |
313 | properties = {name: allowable_types for name in p.columns} | |
314 | schema['items'] = {'type': 'object', 'properties': properties} | |
315 | ||
316 | minrows, maxrows = None, None | |
317 | if isinstance(p.rows, int): | |
318 | minrows, maxrows = p.rows, p.rows | |
319 | elif isinstance(p.rows, tuple): | |
320 | minrows, maxrows = p.rows | |
321 | ||
322 | if minrows is not None: | |
323 | schema['minItems'] = minrows | |
324 | if maxrows is not None: | |
325 | schema['maxItems'] = maxrows | |
326 | ||
327 | return schema |
1 | 1 | Provide consistent and up-to-date ``__version__`` strings for |
2 | 2 | Python packages. |
3 | 3 | |
4 | See https://github.com/pyviz/autover for more information. | |
4 | See https://github.com/holoviz/autover for more information. | |
5 | 5 | """ |
6 | 6 | |
7 | 7 | # The Version class is a copy of autover.version.Version v0.2.5, |
25 | 25 | cwd=cwd) |
26 | 26 | output, error = (str(s.decode()).strip() for s in proc.communicate()) |
27 | 27 | |
28 | if proc.returncode != 0: | |
28 | # Detects errors as _either_ a non-zero return code _or_ messages | |
29 | # printed to stderr, because the return code is erroneously fixed at | |
30 | # zero in some cases (see https://github.com/holoviz/param/pull/389). | |
31 | if proc.returncode != 0 or len(error) > 0: | |
29 | 32 | raise Exception(proc.returncode, error) |
30 | 33 | return output |
31 | 34 | |
173 | 176 | # Verify this is the correct repository (since fpath could |
174 | 177 | # be an unrelated git repository, and autover could just have |
175 | 178 | # been copied/installed into it). |
176 | output = run_cmd([cmd, 'remote', '-v'], | |
177 | cwd=os.path.dirname(self.fpath)) | |
178 | repo_matches = ['', # No remote set | |
179 | '/' + self.reponame + '.git' , | |
179 | remotes = run_cmd([cmd, 'remote', '-v'], | |
180 | cwd=os.path.dirname(self.fpath)) | |
181 | repo_matches = ['/' + self.reponame + '.git' , | |
180 | 182 | # A remote 'server:reponame.git' can also be referred |
181 | 183 | # to (i.e. cloned) as `server:reponame`. |
182 | 184 | '/' + self.reponame + ' '] |
183 | if not any(m in output for m in repo_matches): | |
184 | return self | |
185 | ||
186 | output = run_cmd([cmd, 'describe', '--long', '--match', | |
187 | "v[0-9]*.[0-9]*.[0-9]*", '--dirty'], | |
188 | cwd=os.path.dirname(self.fpath)) | |
185 | if not any(m in remotes for m in repo_matches): | |
186 | try: | |
187 | output = self._output_from_file() | |
188 | if output is not None: | |
189 | self._update_from_vcs(output) | |
190 | except: pass | |
191 | if output is None: | |
192 | # glob pattern (not regexp) matching vX.Y.Z* tags | |
193 | output = run_cmd([cmd, 'describe', '--long', '--match', | |
194 | "v[0-9]*.[0-9]*.[0-9]*", '--dirty'], | |
195 | cwd=os.path.dirname(self.fpath)) | |
189 | 196 | if as_string: return output |
190 | 197 | except Exception as e1: |
191 | 198 | try: |
1 | 1 | universal = 1 |
2 | 2 | |
3 | 3 | [flake8] |
4 | # TODO tests (one day) | |
4 | # TODO tests should not be excluded (one day...) | |
5 | 5 | 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, | |
8 | 12 | E2, |
9 | 13 | E3, |
10 | 14 | E4, |
11 | 15 | E5, |
16 | E731, | |
12 | 17 | E701, |
13 | 18 | E702, |
14 | 19 | E703, |
15 | E704, | |
20 | E704, | |
16 | 21 | E722, |
17 | 22 | E741, |
18 | 23 | E742, |
19 | 24 | E743, |
20 | W503 | |
25 | W503, | |
26 | W504, | |
21 | 27 | |
22 | [nosetests] | |
23 | verbosity = 2 | |
24 | with-doctest = 1 | |
25 | nologcapture = 1 | |
28 | [tool:pytest] | |
29 | python_files = test*.py |
18 | 18 | # pip doesn't support tests_require |
19 | 19 | # (https://github.com/pypa/pip/issues/1197) |
20 | 20 | 'tests': [ |
21 | 'nose', | |
22 | 'flake8' | |
21 | 'pytest', | |
22 | 'pytest-cov', | |
23 | 'flake8', | |
24 | ], | |
25 | 'doc': [ | |
26 | 'pygraphviz', | |
27 | 'nbsite >=0.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', | |
23 | 37 | ] |
24 | 38 | } |
25 | 39 | |
31 | 45 | setup_args = dict( |
32 | 46 | name='param', |
33 | 47 | 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", | |
40 | 55 | platforms=['Windows', 'Mac OS X', 'Linux'], |
41 | 56 | license='BSD', |
42 | url='http://ioam.github.com/param/', | |
57 | url='http://param.holoviz.org/', | |
43 | 58 | packages=["param","numbergen"], |
44 | 59 | provides=["param","numbergen"], |
45 | 60 | include_package_data = True, |
47 | 62 | install_requires=[], |
48 | 63 | extras_require=extras_require, |
49 | 64 | 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 | }, | |
50 | 72 | classifiers=[ |
51 | 73 | "License :: OSI Approved :: BSD License", |
52 | 74 | "Development Status :: 5 - Production/Stable", |
53 | 75 | "Programming Language :: Python :: 2", |
54 | 76 | "Programming Language :: Python :: 2.7", |
55 | 77 | "Programming Language :: Python :: 3", |
56 | "Programming Language :: Python :: 3.4", | |
57 | "Programming Language :: Python :: 3.5", | |
58 | 78 | "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", | |
59 | 83 | "Operating System :: OS Independent", |
60 | 84 | "Intended Audience :: Science/Research", |
61 | 85 | "Intended Audience :: Developers", |
2 | 2 | """ |
3 | 3 | |
4 | 4 | import unittest |
5 | import pytest | |
5 | 6 | import datetime as dt |
6 | 7 | import param |
7 | 8 | |
50 | 51 | dt.date(2017,2,25))) |
51 | 52 | self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1), |
52 | 53 | 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)) |
6 | 6 | |
7 | 7 | import param |
8 | 8 | |
9 | if not hasattr(unittest.TestCase, 'assertRaisesRegex'): | |
10 | unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp | |
9 | 11 | |
10 | 12 | class TestClassSelectorParameters(unittest.TestCase): |
11 | 13 | |
24 | 26 | self.assertEqual(p.e, 6) |
25 | 27 | |
26 | 28 | 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): | |
29 | 31 | self.P(e='a') |
30 | 32 | |
31 | 33 | def test_single_class_type_constructor(self): |
33 | 35 | self.assertEqual(p.f, float) |
34 | 36 | |
35 | 37 | 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): | |
38 | 40 | self.P(f=str) |
39 | 41 | |
40 | 42 | def test_multiple_class_instance_constructor1(self): |
46 | 48 | self.assertEqual(p.g, 'A') |
47 | 49 | |
48 | 50 | 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): | |
51 | 53 | self.P(g=3.0) |
52 | 54 | |
53 | 55 | def test_multiple_class_type_constructor1(self): |
59 | 61 | self.assertEqual(p.h, str) |
60 | 62 | |
61 | 63 | 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): | |
64 | 66 | self.P(h=float) |
10 | 10 | def test_initialization_invalid_string(self): |
11 | 11 | try: |
12 | 12 | class Q(param.Parameterized): |
13 | q = param.Color('red') | |
13 | q = param.Color('red', allow_named=False) | |
14 | 14 | except ValueError: |
15 | 15 | pass |
16 | 16 | else: |
17 | raise AssertionError("No exception raised on out-of-bounds date") | |
17 | raise AssertionError("No exception raised on invalid color") | |
18 | 18 | |
19 | 19 | def test_set_invalid_string(self): |
20 | 20 | class Q(param.Parameterized): |
21 | q = param.Color() | |
21 | q = param.Color(allow_named=False) | |
22 | 22 | try: |
23 | 23 | Q.q = 'red' |
24 | 24 | except ValueError: |
25 | 25 | pass |
26 | 26 | 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") | |
28 | 48 | |
29 | 49 | def test_valid_long_hex(self): |
30 | 50 | class Q(param.Parameterized): |
38 | 58 | Q.q = '#fff' |
39 | 59 | self.assertEqual(Q.q, '#fff') |
40 | 60 | |
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') |
91 | 91 | # get_value_generator() should give the objects |
92 | 92 | self.assertEqual(ix(), 2) |
93 | 93 | self.assertEqual(iy(), 5) |
94 | ||
95 | ||
96 | if __name__ == "__main__": | |
97 | import nose | |
98 | nose.runmodule() |
2 | 2 | """ |
3 | 3 | |
4 | 4 | import unittest |
5 | ||
6 | import pytest | |
5 | 7 | |
6 | 8 | from param.parameterized import add_metaclass |
7 | 9 | from param import concrete_descendents, Parameter |
12 | 14 | |
13 | 15 | |
14 | 16 | positional_args = { |
15 | ClassSelector: (object,) | |
17 | # ClassSelector: (object,) | |
16 | 18 | } |
17 | 19 | |
18 | skip = [] | |
20 | skip = ['ClassSelector'] | |
19 | 21 | |
20 | 22 | try: |
21 | 23 | import numpy # noqa |
29 | 31 | skip.append('Series') |
30 | 32 | |
31 | 33 | |
32 | class TestDefaultsMetaclass(type): | |
34 | class DefaultsMetaclassTest(type): | |
33 | 35 | def __new__(mcs, name, bases, dict_): |
34 | 36 | |
35 | 37 | def test_skip(*args,**kw): |
36 | from nose.exc import SkipTest | |
37 | raise SkipTest | |
38 | pytest.skip() | |
38 | 39 | |
39 | 40 | def add_test(p): |
40 | 41 | def test(self): |
49 | 50 | return type.__new__(mcs, name, bases, dict_) |
50 | 51 | |
51 | 52 | |
52 | @add_metaclass(TestDefaultsMetaclass) | |
53 | @add_metaclass(DefaultsMetaclassTest) | |
53 | 54 | class TestDefaults(unittest.TestCase): |
54 | 55 | pass |
55 | ||
56 | ||
57 | if __name__ == "__main__": | |
58 | import nose | |
59 | nose.runmodule() |
246 | 246 | self.assertNotEqual(call_1, t12.x) |
247 | 247 | |
248 | 248 | |
249 | ||
250 | if __name__ == "__main__": | |
251 | import nose | |
252 | nose.runmodule() | |
253 | ||
254 | ||
255 | 249 | # Commented out block in the original doctest version. |
256 | 250 | # Maybe these are features originally planned but never implemented |
257 | 251 |
19 | 19 | # SkipTest will be raised if IPython unavailable |
20 | 20 | from param.ipython import ParamPager |
21 | 21 | |
22 | test1_repr = """\x1b[1;32mParameters of 'TestClass'\n=========================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
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""" | |
23 | 23 | |
24 | 24 | |
25 | test2_repr = """\x1b[1;32mParameters of 'TestClass' instance\n==================================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
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""" | |
26 | 26 | |
27 | 27 | class TestParamPager(unittest.TestCase): |
28 | 28 | |
29 | 29 | def setUp(self): |
30 | 30 | self.maxDiff = None |
31 | ||
31 | 32 | 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) | |
38 | 39 | |
39 | 40 | self.TestClass = TestClass |
40 | 41 | self.pager = ParamPager() |
42 | 43 | def test_parameterized_class(self): |
43 | 44 | page_string = self.pager(self.TestClass) |
44 | 45 | # 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) | |
47 | 48 | |
48 | 49 | try: |
49 | 50 | self.assertEqual(page_string, ref_string) |
55 | 56 | def test_parameterized_instance(self): |
56 | 57 | page_string = self.pager(self.TestClass()) |
57 | 58 | # 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) | |
60 | 61 | |
61 | 62 | try: |
62 | 63 | self.assertEqual(page_string, ref_string) |
154 | 154 | |
155 | 155 | with self.assertRaises(TypeError): |
156 | 156 | Q.params('r').compute_default() |
157 | ||
158 | if __name__ == "__main__": | |
159 | import nose | |
160 | nose.runmodule() |
32 | 32 | for _ in range(_iterations): |
33 | 33 | value = gen() |
34 | 34 | self.assertTrue(lbound <= value < ubound) |
35 | ||
36 | if __name__ == "__main__": | |
37 | import nose | |
38 | nose.runmodule() |
32 | 32 | |
33 | 33 | z = Z(z=numpy.array([1,2])) |
34 | 34 | _is_array_and_equal(z.z,[1,2]) |
35 | ||
36 | ||
37 | if __name__ == "__main__": | |
38 | import nose | |
39 | nose.runmodule() |
100 | 100 | pass |
101 | 101 | else: |
102 | 102 | raise AssertionError("ObjectSelector created without range.") |
103 | ||
104 | ||
105 | if __name__ == "__main__": | |
106 | import nose | |
107 | nose.runmodule() |
9 | 9 | |
10 | 10 | |
11 | 11 | import random |
12 | from nose.tools import istest, nottest | |
13 | ||
14 | 12 | |
15 | 13 | from param.parameterized import ParamOverrides, shared_parameters |
16 | 14 | |
17 | @nottest | |
18 | 15 | class _SomeRandomNumbers(object): |
19 | 16 | def __call__(self): |
20 | 17 | return random.random() |
21 | 18 | |
22 | @nottest | |
23 | 19 | class TestPO(param.Parameterized): |
20 | __test__ = False | |
21 | ||
24 | 22 | inst = param.Parameter(default=[1,2,3],instantiate=True) |
25 | 23 | notinst = param.Parameter(default=[1,2,3],instantiate=False) |
26 | 24 | const = param.Parameter(default=1,constant=True) |
29 | 27 | |
30 | 28 | dyn = param.Dynamic(default=1) |
31 | 29 | |
32 | @nottest | |
33 | 30 | class AnotherTestPO(param.Parameterized): |
34 | 31 | instPO = param.Parameter(default=TestPO(),instantiate=True) |
35 | 32 | notinstPO = param.Parameter(default=TestPO(),instantiate=False) |
36 | 33 | |
37 | @nottest | |
38 | 34 | class TestAbstractPO(param.Parameterized): |
35 | __test__ = False | |
36 | ||
39 | 37 | __abstract = True |
40 | 38 | |
41 | @nottest | |
39 | class _AnotherAbstractPO(param.Parameterized): | |
40 | __abstract = True | |
41 | ||
42 | ||
42 | 43 | class TestParamInstantiation(AnotherTestPO): |
44 | __test__ = False | |
45 | ||
43 | 46 | instPO = param.Parameter(default=AnotherTestPO(),instantiate=False) |
44 | 47 | |
45 | @istest | |
46 | 48 | class TestParameterized(unittest.TestCase): |
47 | 49 | |
48 | 50 | def test_constant_parameter(self): |
117 | 119 | def test_abstract_class(self): |
118 | 120 | """Check that a class declared abstract actually shows up as abstract.""" |
119 | 121 | self.assertEqual(TestAbstractPO.abstract,True) |
122 | self.assertEqual(_AnotherAbstractPO.abstract,True) | |
120 | 123 | self.assertEqual(TestPO.abstract,False) |
121 | 124 | |
122 | 125 | |
154 | 157 | |
155 | 158 | from param import parameterized |
156 | 159 | |
157 | @nottest | |
158 | 160 | 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 | |
169 | 173 | |
170 | 174 | instance = some_fn.instance() |
171 | 175 | |
172 | @istest | |
173 | 176 | class TestParameterizedFunction(unittest.TestCase): |
174 | 177 | |
175 | 178 | def _basic_tests(self,fn): |
195 | 198 | self.assertEqual(i(),(0.3,18,[10,20,30])) |
196 | 199 | |
197 | 200 | |
198 | @nottest | |
199 | 201 | class TestPO1(param.Parameterized): |
202 | __test__ = False | |
203 | ||
200 | 204 | x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1)) |
201 | 205 | y = param.Number(default=1,bounds=(-1,1)) |
202 | 206 | |
203 | @istest | |
204 | 207 | class TestNumberParameter(unittest.TestCase): |
205 | 208 | |
206 | 209 | def test_outside_bounds(self): |
225 | 228 | assert False, "Should raise ValueError." |
226 | 229 | |
227 | 230 | |
228 | @istest | |
229 | 231 | class TestStringParameter(unittest.TestCase): |
230 | 232 | |
231 | 233 | def setUp(self): |
250 | 252 | |
251 | 253 | |
252 | 254 | |
253 | @istest | |
254 | 255 | class TestParamOverrides(unittest.TestCase): |
255 | 256 | |
256 | 257 | def setUp(self): |
291 | 292 | def test_shared_list(self): |
292 | 293 | self.assertTrue(self.p1.inst is self.p2.inst) |
293 | 294 | 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() |
158 | 158 | |
159 | 159 | self.assertEqual(obj.pprint(qualify=True), |
160 | 160 | "tests.API0.testparameterizedrepr."+r) |
161 | ||
162 | ||
163 | ||
164 | if __name__ == "__main__": | |
165 | import nose | |
166 | nose.runmodule() |
0 | 0 | """ |
1 | 1 | Unit test for String parameters |
2 | 2 | """ |
3 | ||
3 | import sys | |
4 | 4 | import unittest |
5 | 5 | |
6 | 6 | import param |
7 | 7 | |
8 | 8 | |
9 | ip_regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' | |
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 | |
10 | 13 | |
11 | 14 | class TestStringParameters(unittest.TestCase): |
12 | 15 | |
23 | 26 | |
24 | 27 | a = A() |
25 | 28 | |
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): | |
28 | 32 | a.s = None # because allow_None should be False |
29 | 33 | |
30 | 34 | def test_default_none(self): |
36 | 40 | a.s = None # because allow_None should be True with default of None |
37 | 41 | |
38 | 42 | def test_regex_incorrect(self): |
39 | ||
40 | 43 | class A(param.Parameterized): |
41 | 44 | s = param.String('0.0.0.0', regex=ip_regex) |
42 | 45 | |
43 | 46 | a = A() |
44 | 47 | |
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: | |
47 | 50 | a.s = '123.123.0.256' |
51 | self.assertEqual(str(e.exception), exception.replace('\\', '\\\\')) | |
48 | 52 | |
49 | 53 | 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: | |
53 | 56 | class A(param.Parameterized): |
54 | 57 | s = param.String(regex=ip_regex) # default value '' does not match regular expression |
55 | ||
56 | ||
58 | self.assertEqual(str(e.exception), exception.replace('\\', '\\\\')) |
6 | 6 | import numbergen |
7 | 7 | import copy |
8 | 8 | |
9 | from nose.plugins.skip import SkipTest | |
9 | import pytest | |
10 | 10 | import fractions |
11 | 11 | |
12 | 12 | try: |
13 | 13 | 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 | |
17 | 20 | |
18 | 21 | |
19 | 22 | class TestTimeClass(unittest.TestCase): |
77 | 80 | self.assertEqual(t(), 1) |
78 | 81 | self.assertEqual(t.time_type, fractions.Fraction) |
79 | 82 | |
83 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
80 | 84 | def test_time_init_gmpy(self): |
81 | if gmpy is None: raise SkipTest | |
82 | ||
83 | 85 | t = param.Time(time_type=gmpy.mpq) |
84 | 86 | self.assertEqual(t(), gmpy.mpq(0)) |
85 | 87 | t.advance(gmpy.mpq(0.25)) |
86 | 88 | self.assertEqual(t(), gmpy.mpq(1,4)) |
87 | 89 | |
90 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
88 | 91 | def test_time_init_gmpy_advanced(self): |
89 | if gmpy is None: raise SkipTest | |
90 | 92 | t = param.Time(time_type=gmpy.mpq, |
91 | 93 | timestep=gmpy.mpq(0.25), |
92 | 94 | until=1.5) |
266 | 268 | self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi))) |
267 | 269 | |
268 | 270 | |
271 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
269 | 272 | def test_time_hashing_integers_gmpy(self): |
270 | 273 | """ |
271 | 274 | Check that hashes for gmpy values at the integers also matches |
272 | 275 | those of ints, fractions and strings. |
273 | 276 | """ |
274 | if gmpy is None: raise SkipTest | |
275 | 277 | hashfn = numbergen.Hash("test", input_count=1) |
276 | 278 | hash_1 = hashfn(1) |
277 | 279 | hash_42 = hashfn(42) |
282 | 284 | self.assertEqual(hash_42, hashfn(gmpy.mpq(42))) |
283 | 285 | self.assertEqual(hash_42, hashfn(42)) |
284 | 286 | |
287 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
285 | 288 | def test_time_hashing_rationals_gmpy(self): |
286 | 289 | """ |
287 | 290 | Check that hashes of fractions and gmpy mpqs match for some |
288 | 291 | reasonable rational numbers. |
289 | 292 | """ |
290 | if gmpy is None: raise SkipTest | |
291 | 293 | pi = "3.141592" |
292 | 294 | hashfn = numbergen.Hash("test", input_count=1) |
293 | 295 | self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5))) |
294 | 296 | self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592))) |
295 | ||
296 | ||
297 | ||
298 | ||
299 | if __name__ == "__main__": | |
300 | import nose | |
301 | nose.runmodule() |
7 | 7 | |
8 | 8 | def tearDown(self): |
9 | 9 | 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 |
3 | 3 | |
4 | 4 | |
5 | 5 | import datetime as dt |
6 | import pytest | |
6 | 7 | import param |
7 | 8 | from . import API1TestCase |
8 | 9 | |
51 | 52 | dt.date(2017,2,25))) |
52 | 53 | self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1), |
53 | 54 | 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)) |
6 | 6 | |
7 | 7 | import param |
8 | 8 | from . import API1TestCase |
9 | ||
10 | 9 | |
11 | 10 | class TestClassSelectorParameters(API1TestCase): |
12 | 11 | |
25 | 24 | self.assertEqual(p.e, 6) |
26 | 25 | |
27 | 26 | 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): | |
30 | 29 | self.P(e='a') |
31 | 30 | |
32 | 31 | def test_single_class_type_constructor(self): |
34 | 33 | self.assertEqual(p.f, float) |
35 | 34 | |
36 | 35 | 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): | |
39 | 38 | self.P(f=str) |
40 | 39 | |
41 | 40 | def test_multiple_class_instance_constructor1(self): |
47 | 46 | self.assertEqual(p.g, 'A') |
48 | 47 | |
49 | 48 | 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): | |
52 | 51 | self.P(g=3.0) |
53 | 52 | |
54 | 53 | def test_multiple_class_type_constructor1(self): |
66 | 65 | self.assertIn('str', classes) |
67 | 66 | |
68 | 67 | 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): | |
71 | 70 | self.P(h=float) |
72 | 71 | |
73 | 72 | |
91 | 90 | items = param.Dict(valid_dict) |
92 | 91 | |
93 | 92 | 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): | |
96 | 95 | test.items = 3 |
8 | 8 | def test_initialization_invalid_string(self): |
9 | 9 | try: |
10 | 10 | class Q(param.Parameterized): |
11 | q = param.Color('red') | |
11 | q = param.Color('red', allow_named=False) | |
12 | 12 | except ValueError: |
13 | 13 | pass |
14 | 14 | else: |
15 | raise AssertionError("No exception raised on out-of-bounds date") | |
15 | raise AssertionError("No exception raised on invalid color") | |
16 | 16 | |
17 | 17 | def test_set_invalid_string(self): |
18 | 18 | class Q(param.Parameterized): |
19 | q = param.Color() | |
19 | q = param.Color(allow_named=False) | |
20 | 20 | try: |
21 | 21 | Q.q = 'red' |
22 | 22 | except ValueError: |
23 | 23 | pass |
24 | 24 | 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") | |
26 | 46 | |
27 | 47 | def test_valid_long_hex(self): |
28 | 48 | class Q(param.Parameterized): |
36 | 56 | Q.q = '#fff' |
37 | 57 | self.assertEqual(Q.q, '#fff') |
38 | 58 | |
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') |
92 | 92 | # get_value_generator() should give the objects |
93 | 93 | self.assertEqual(ix(), 2) |
94 | 94 | self.assertEqual(iy(), 5) |
95 | ||
96 | ||
97 | if __name__ == "__main__": | |
98 | import nose | |
99 | nose.runmodule() |
0 | 0 | """ |
1 | 1 | Do all subclasses of Parameter supply a valid default? |
2 | 2 | """ |
3 | import pytest | |
3 | 4 | |
4 | 5 | from param.parameterized import add_metaclass |
5 | 6 | from param import concrete_descendents, Parameter |
10 | 11 | from . import API1TestCase |
11 | 12 | |
12 | 13 | positional_args = { |
13 | ClassSelector: (object,) | |
14 | # ClassSelector: (object,) | |
14 | 15 | } |
15 | 16 | |
16 | skip = [] | |
17 | skip = ['ClassSelector'] | |
17 | 18 | |
18 | 19 | try: |
19 | 20 | import numpy # noqa |
26 | 27 | skip.append('Series') |
27 | 28 | |
28 | 29 | |
29 | class TestDefaultsMetaclass(type): | |
30 | class DefaultsMetaclassTest(type): | |
30 | 31 | def __new__(mcs, name, bases, dict_): |
31 | 32 | |
32 | 33 | def test_skip(*args,**kw): |
33 | from nose.exc import SkipTest | |
34 | raise SkipTest | |
34 | pytest.skip() | |
35 | 35 | |
36 | 36 | def add_test(p): |
37 | 37 | def test(self): |
46 | 46 | return type.__new__(mcs, name, bases, dict_) |
47 | 47 | |
48 | 48 | |
49 | @add_metaclass(TestDefaultsMetaclass) | |
49 | @add_metaclass(DefaultsMetaclassTest) | |
50 | 50 | class TestDefaults(API1TestCase): |
51 | 51 | pass |
52 | ||
53 | ||
54 | if __name__ == "__main__": | |
55 | import nose | |
56 | nose.runmodule() |
247 | 247 | self.assertNotEqual(call_1, t12.x) |
248 | 248 | |
249 | 249 | |
250 | ||
251 | if __name__ == "__main__": | |
252 | import nose | |
253 | nose.runmodule() | |
254 | ||
255 | ||
256 | 250 | # Commented out block in the original doctest version. |
257 | 251 | # Maybe these are features originally planned but never implemented |
258 | 252 |
18 | 18 | # SkipTest will be raised if IPython unavailable |
19 | 19 | from param.ipython import ParamPager |
20 | 20 | |
21 | test1_repr = """\x1b[1;32mParameters of 'TestClass'\n=========================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
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""" | |
22 | 22 | |
23 | 23 | |
24 | test2_repr = """\x1b[1;32mParameters of 'TestClass' instance\n==================================\n\x1b[0m\n\x1b[1;31mParameters changed from their default values are marked in red.\x1b[0m\n\x1b[1;36mSoft bound values are marked in cyan.\x1b[0m\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None\n\n\x1b[1;34mNameValue Type Bounds Mode \x1b[0m\n\nu 4 Number V RW \nv 4 Number C RW \nw 4 Number C RO \nx None String V RW AN \ny 4 Number (-1, None) V RW \nz 4 Number (-1, 100) V RW \n\n\x1b[1;32mParameter docstrings:\n=====================\x1b[0m\n\n\x1b[1;34mu: < No docstring available >\x1b[0m\n\x1b[1;31mv: < No docstring available >\x1b[0m\n\x1b[1;34mw: < No docstring available >\x1b[0m\n\x1b[1;31mx: < No docstring available >\x1b[0m\n\x1b[1;34my: < No docstring available >\x1b[0m\n\x1b[1;31mz: < No docstring available >\x1b[0m""" | |
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""" | |
25 | 25 | |
26 | 26 | class TestParamPager(API1TestCase): |
27 | 27 | |
28 | 28 | def setUp(self): |
29 | 29 | super(TestParamPager, self).setUp() |
30 | 30 | self.maxDiff = None |
31 | ||
31 | 32 | 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) | |
38 | 39 | |
39 | 40 | self.TestClass = TestClass |
40 | 41 | self.pager = ParamPager() |
42 | 43 | def test_parameterized_class(self): |
43 | 44 | page_string = self.pager(self.TestClass) |
44 | 45 | # 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) | |
47 | 48 | |
48 | 49 | try: |
49 | 50 | self.assertEqual(page_string, ref_string) |
55 | 56 | def test_parameterized_instance(self): |
56 | 57 | page_string = self.pager(self.TestClass()) |
57 | 58 | # 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) | |
60 | 61 | |
61 | 62 | try: |
62 | 63 | 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.") |
153 | 153 | |
154 | 154 | with self.assertRaises(TypeError): |
155 | 155 | Q.param.params('r').compute_default() |
156 | ||
157 | if __name__ == "__main__": | |
158 | import nose | |
159 | nose.runmodule() |
30 | 30 | for _ in range(_iterations): |
31 | 31 | value = gen() |
32 | 32 | self.assertTrue(lbound <= value < ubound) |
33 | ||
34 | if __name__ == "__main__": | |
35 | import nose | |
36 | nose.runmodule() |
23 | 23 | class Q(param.Parameterized): |
24 | 24 | q = param.Number(default=1) |
25 | 25 | |
26 | self.assertEqual(Q.param['q'].step, None) | |
26 | qobj = Q() | |
27 | self.assertEqual(qobj.param['q'].step, None) | |
27 | 28 | |
28 | 29 | def test_initialization_with_step_instance(self): |
29 | 30 | class Q(param.Parameterized): |
34 | 35 | |
35 | 36 | def test_step_invalid_type_number_parameter(self): |
36 | 37 | exception = "Step parameter can only be None or a numeric value" |
37 | with self.assertRaisesRegexp(ValueError, exception): | |
38 | with self.assertRaisesRegex(ValueError, exception): | |
38 | 39 | param.Number(step='invalid value') |
39 | 40 | |
40 | 41 | def test_step_invalid_type_integer_parameter(self): |
41 | 42 | exception = "Step parameter can only be None or an integer value" |
42 | with self.assertRaisesRegexp(ValueError, exception): | |
43 | with self.assertRaisesRegex(ValueError, exception): | |
43 | 44 | param.Integer(step=3.4) |
44 | 45 | |
45 | 46 | def test_step_invalid_type_datetime_parameter(self): |
46 | 47 | exception = "Step parameter can only be None, a datetime or datetime type" |
47 | with self.assertRaisesRegexp(ValueError, exception): | |
48 | with self.assertRaisesRegex(ValueError, exception): | |
48 | 49 | param.Date(dt.datetime(2017,2,27), step=3.2) |
49 | 50 | |
50 | 51 | def test_step_invalid_type_date_parameter(self): |
51 | 52 | exception = "Step parameter can only be None or a date type" |
52 | with self.assertRaisesRegexp(ValueError, exception): | |
53 | with self.assertRaisesRegex(ValueError, exception): | |
53 | 54 | param.CalendarDate(dt.date(2017,2,27), step=3.2) |
40 | 40 | |
41 | 41 | z = Z(z=numpy.array([1,2])) |
42 | 42 | _is_array_and_equal(z.z,[1,2]) |
43 | ||
44 | if __name__ == "__main__": | |
45 | import nose | |
46 | nose.runmodule() |
17 | 17 | def setUp(self): |
18 | 18 | super(TestObjectSelectorParameters, self).setUp() |
19 | 19 | class P(param.Parameterized): |
20 | e = param.ObjectSelector(default=5,objects=[5,6,7]) | |
21 | f = param.ObjectSelector(default=10) | |
22 | h = param.ObjectSelector(default=None) | |
23 | g = param.ObjectSelector(default=None,objects=[7,8]) | |
24 | i = param.ObjectSelector(default=7,objects=[9],check_on_set=False) | |
25 | s = param.ObjectSelector(default=3,objects=OrderedDict(one=1,two=2,three=3)) | |
26 | d = param.ObjectSelector(default=opts['B'],objects=opts) | |
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) | |
27 | 27 | |
28 | 28 | self.P = P |
29 | 29 | |
97 | 97 | def test_initialization_out_of_bounds(self): |
98 | 98 | try: |
99 | 99 | class Q(param.Parameterized): |
100 | q = param.ObjectSelector(5,objects=[4]) | |
100 | q = param.Selector(default=5,objects=[4]) | |
101 | 101 | except ValueError: |
102 | 102 | pass |
103 | 103 | else: |
107 | 107 | def test_initialization_no_bounds(self): |
108 | 108 | try: |
109 | 109 | class Q(param.Parameterized): |
110 | q = param.ObjectSelector(5,objects=10) | |
110 | q = param.Selector(default=5,objects=10) | |
111 | 111 | except TypeError: |
112 | 112 | pass |
113 | 113 | else: |
114 | 114 | raise AssertionError("ObjectSelector created without range.") |
115 | 115 | |
116 | 116 | |
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.") |
23 | 23 | class Test(param.Parameterized): |
24 | 24 | df = param.DataFrame(valid_df) |
25 | 25 | |
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 | ||
26 | 48 | def test_empty_dataframe_param_invalid_set(self): |
27 | 49 | empty = pandas.DataFrame() |
28 | 50 | class Test(param.Parameterized): |
29 | 51 | df = param.DataFrame(default=empty) |
30 | 52 | |
31 | 53 | 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): | |
34 | 56 | test.df = 3 |
35 | 57 | |
36 | 58 | def test_dataframe_unordered_column_set_valid(self): |
48 | 70 | |
49 | 71 | |
50 | 72 | 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): | |
54 | 76 | test.df = invalid_df |
55 | 77 | |
56 | 78 | def test_dataframe_ordered_column_list_valid(self): |
67 | 89 | df = param.DataFrame(default=valid_df, columns=['b', 'a', 'd']) |
68 | 90 | |
69 | 91 | 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): | |
74 | 96 | test.df = invalid_df |
75 | 97 | |
76 | 98 | |
86 | 108 | df = param.DataFrame(default=valid_df, columns=3) |
87 | 109 | |
88 | 110 | test = Test() |
89 | self.assertEquals(test.param.params('df').ordered, None) | |
111 | self.assertEqual(test.param.params('df').ordered, None) | |
90 | 112 | |
91 | 113 | exception = "Column length 2 does not match declared bounds of 3" |
92 | with self.assertRaisesRegexp(ValueError, exception): | |
114 | with self.assertRaisesRegex(ValueError, exception): | |
93 | 115 | test.df = invalid_df |
94 | 116 | |
95 | 117 | |
102 | 124 | |
103 | 125 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) |
104 | 126 | |
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): | |
107 | 129 | class Test(param.Parameterized): |
108 | 130 | df = param.DataFrame(default=invalid_df, columns=(None,2)) |
109 | 131 | |
120 | 142 | |
121 | 143 | test = Test() |
122 | 144 | exception = "Row length 3 does not match declared bounds of 2" |
123 | with self.assertRaisesRegexp(ValueError, exception): | |
145 | with self.assertRaisesRegex(ValueError, exception): | |
124 | 146 | test.df = invalid_df |
125 | 147 | |
126 | 148 | def test_dataframe_unordered_row_tuple_valid(self): |
132 | 154 | |
133 | 155 | invalid_df = pandas.DataFrame({'a':[1,2], 'b':[2,3], 'c':[4,5]}, columns=['b', 'a', 'c']) |
134 | 156 | |
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): | |
137 | 159 | class Test(param.Parameterized): |
138 | 160 | df = param.DataFrame(default=invalid_df, rows=(5,7)) |
139 | 161 | |
158 | 180 | |
159 | 181 | test = Test() |
160 | 182 | exception = "Row length 3 does not match declared bounds of 2" |
161 | with self.assertRaisesRegexp(ValueError, exception): | |
183 | with self.assertRaisesRegex(ValueError, exception): | |
162 | 184 | test.series = invalid_series |
163 | 185 | |
164 | 186 | def test_series_unordered_row_tuple_valid(self): |
170 | 192 | |
171 | 193 | invalid_series = pandas.Series([1,2]) |
172 | 194 | |
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): | |
175 | 197 | class Test(param.Parameterized): |
176 | 198 | series = param.Series(default=invalid_series, rows=(5,7)) |
177 | 199 | |
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) |
3 | 3 | |
4 | 4 | |
5 | 5 | import param |
6 | ||
7 | from param.parameterized import _parse_dependency_spec | |
8 | ||
6 | 9 | from . import API1TestCase |
7 | 10 | |
8 | 11 | |
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 | ||
9 | 131 | 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): | |
10 | 643 | |
11 | 644 | def setUp(self): |
12 | 645 | class P(param.Parameterized): |
13 | 646 | a = param.Parameter() |
14 | 647 | b = param.Parameter() |
15 | 648 | |
16 | @param.depends('a') | |
17 | def single_parameter(self): | |
18 | pass | |
19 | ||
20 | @param.depends('a:constant') | |
21 | def constant(self): | |
22 | pass | |
23 | ||
24 | @param.depends('a.param') | |
25 | def nested(self): | |
26 | pass | |
27 | ||
28 | class P2(param.Parameterized): | |
29 | ||
30 | @param.depends(P.param.a) | |
31 | def external_param(self, a): | |
32 | pass | |
33 | ||
34 | self.P = P | |
35 | self.P2 = P2 | |
36 | ||
37 | def test_param_depends_instance(self): | |
38 | p = self.P() | |
39 | pinfos = p.param.params_depended_on('single_parameter') | |
40 | self.assertEqual(len(pinfos), 1) | |
41 | pinfo = pinfos[0] | |
42 | self.assertIs(pinfo.cls, self.P) | |
43 | self.assertIs(pinfo.inst, p) | |
44 | self.assertEqual(pinfo.name, 'a') | |
45 | self.assertEqual(pinfo.what, 'value') | |
46 | ||
47 | def test_param_depends_class(self): | |
48 | pinfos = self.P.param.params_depended_on('single_parameter') | |
49 | self.assertEqual(len(pinfos), 1) | |
50 | pinfo = pinfos[0] | |
51 | self.assertIs(pinfo.cls, self.P) | |
52 | self.assertIs(pinfo.inst, None) | |
53 | self.assertEqual(pinfo.name, 'a') | |
54 | self.assertEqual(pinfo.what, 'value') | |
55 | ||
56 | def test_param_depends_constant(self): | |
57 | pinfos = self.P.param.params_depended_on('constant') | |
58 | self.assertEqual(len(pinfos), 1) | |
59 | pinfo = pinfos[0] | |
60 | self.assertIs(pinfo.cls, self.P) | |
61 | self.assertIs(pinfo.inst, None) | |
62 | self.assertEqual(pinfo.name, 'a') | |
63 | self.assertEqual(pinfo.what, 'constant') | |
64 | ||
65 | def test_param_depends_nested(self): | |
66 | inst = self.P(a=self.P()) | |
67 | pinfos = inst.param.params_depended_on('nested') | |
68 | self.assertEqual(len(pinfos), 4) | |
69 | pinfos = {(pi.inst, pi.name): pi for pi in pinfos} | |
70 | pinfo = pinfos[(inst, 'a')] | |
71 | self.assertIs(pinfo.cls, self.P) | |
72 | self.assertIs(pinfo.inst, inst) | |
73 | self.assertEqual(pinfo.name, 'a') | |
74 | self.assertEqual(pinfo.what, 'value') | |
75 | for p in ['name', 'a', 'b']: | |
76 | info = pinfos[(inst.a, p)] | |
77 | self.assertEqual(info.name, p) | |
78 | self.assertIs(info.inst, inst.a) | |
79 | ||
80 | def test_param_external_param_instance(self): | |
81 | inst = self.P2() | |
82 | pinfos = inst.param.params_depended_on('external_param') | |
83 | pinfo = pinfos[0] | |
84 | self.assertIs(pinfo.cls, self.P) | |
85 | self.assertIs(pinfo.inst, None) | |
86 | self.assertEqual(pinfo.name, 'a') | |
87 | self.assertEqual(pinfo.what, 'value') | |
88 | ||
89 | ||
90 | ||
91 | class TestParamDependsFunction(API1TestCase): | |
92 | ||
93 | def setUp(self): | |
94 | class P(param.Parameterized): | |
95 | a = param.Parameter() | |
96 | b = param.Parameter() | |
97 | ||
98 | 649 | |
99 | 650 | self.P = P |
100 | 651 | |
108 | 659 | dependencies = { |
109 | 660 | 'dependencies': (p.param.a,), |
110 | 661 | 'kw': {'c': p.param.b}, |
111 | 'watch': False | |
662 | 'watch': False, | |
663 | 'on_init': False | |
112 | 664 | } |
113 | 665 | self.assertEqual(function._dinfo, dependencies) |
114 | 666 | |
122 | 674 | dependencies = { |
123 | 675 | 'dependencies': (p.param.a,), |
124 | 676 | 'kw': {'c': p.param.b}, |
125 | 'watch': False | |
677 | 'watch': False, | |
678 | 'on_init': False | |
126 | 679 | } |
127 | 680 | self.assertEqual(function._dinfo, dependencies) |
128 | 681 |
9 | 9 | |
10 | 10 | # CEBALERT: not anything like a complete test of Parameterized! |
11 | 11 | |
12 | import pytest | |
12 | 13 | |
13 | 14 | import random |
14 | from nose.tools import istest, nottest | |
15 | ||
16 | 15 | |
17 | 16 | from param.parameterized import ParamOverrides, shared_parameters |
18 | 17 | from param.parameterized import default_label_formatter, no_instance_params |
19 | 18 | |
20 | @nottest | |
21 | 19 | class _SomeRandomNumbers(object): |
22 | 20 | def __call__(self): |
23 | 21 | return random.random() |
24 | 22 | |
25 | @nottest | |
26 | 23 | class TestPO(param.Parameterized): |
24 | __test__ = False | |
25 | ||
27 | 26 | inst = param.Parameter(default=[1,2,3],instantiate=True) |
28 | 27 | notinst = param.Parameter(default=[1,2,3],instantiate=False, per_instance=False) |
29 | 28 | const = param.Parameter(default=1,constant=True) |
34 | 33 | |
35 | 34 | dyn = param.Dynamic(default=1) |
36 | 35 | |
37 | @nottest | |
38 | 36 | class TestPOValidation(param.Parameterized): |
37 | __test__ = False | |
38 | ||
39 | 39 | value = param.Number(default=2, bounds=(0, 4)) |
40 | 40 | |
41 | @nottest | |
42 | 41 | @no_instance_params |
43 | 42 | class TestPONoInstance(TestPO): |
43 | __test__ = False | |
44 | 44 | pass |
45 | 45 | |
46 | @nottest | |
47 | 46 | class AnotherTestPO(param.Parameterized): |
48 | 47 | instPO = param.Parameter(default=TestPO(),instantiate=True) |
49 | 48 | notinstPO = param.Parameter(default=TestPO(),instantiate=False) |
50 | 49 | |
51 | @nottest | |
52 | 50 | class TestAbstractPO(param.Parameterized): |
51 | __test__ = False | |
52 | ||
53 | 53 | __abstract = True |
54 | 54 | |
55 | @nottest | |
55 | class _AnotherAbstractPO(param.Parameterized): | |
56 | __abstract = True | |
57 | ||
56 | 58 | class TestParamInstantiation(AnotherTestPO): |
59 | __test__ = False | |
60 | ||
57 | 61 | instPO = param.Parameter(default=AnotherTestPO(),instantiate=False) |
58 | 62 | |
59 | @istest | |
60 | 63 | class TestParameterized(API1TestCase): |
61 | 64 | |
62 | 65 | @classmethod |
66 | 69 | cls.log_handler = MockLoggingHandler(level='DEBUG') |
67 | 70 | log.addHandler(cls.log_handler) |
68 | 71 | |
72 | def test_parameter_name_fixed(self): | |
73 | testpo = TestPO() | |
74 | ||
75 | with pytest.raises(AttributeError): | |
76 | testpo.param.const.name = 'notconst' | |
69 | 77 | |
70 | 78 | def test_constant_parameter(self): |
71 | 79 | """Test that you can't set a constant parameter after construction.""" |
139 | 147 | def test_abstract_class(self): |
140 | 148 | """Check that a class declared abstract actually shows up as abstract.""" |
141 | 149 | self.assertEqual(TestAbstractPO.abstract,True) |
150 | self.assertEqual(_AnotherAbstractPO.abstract,True) | |
142 | 151 | self.assertEqual(TestPO.abstract,False) |
143 | 152 | |
144 | 153 | |
232 | 241 | inst.param['inst'] |
233 | 242 | |
234 | 243 | 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') | |
237 | 247 | |
238 | 248 | |
239 | 249 | def test_instance_param_getitem(self): |
267 | 277 | assert obj is TestPO.param[p] |
268 | 278 | |
269 | 279 | |
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) | |
274 | 284 | for p, obj in TestPO.param.objects('current').items(): |
275 | 285 | assert obj is TestPO.param[p] |
276 | 286 | |
277 | 287 | |
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() | |
282 | 292 | for p, obj in TestPO.param.objects('current').items(): |
283 | 293 | assert obj is TestPO.param[p] |
284 | 294 | |
285 | 295 | |
286 | 296 | 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 | |
288 | 298 | test = TestPO() |
289 | 299 | test.param.defaults() |
290 | 300 | for p, obj in TestPO.param.objects('current').items(): |
334 | 344 | |
335 | 345 | from param import parameterized |
336 | 346 | |
337 | @nottest | |
338 | 347 | 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 | |
349 | 360 | |
350 | 361 | instance = some_fn.instance() |
351 | 362 | |
352 | @istest | |
353 | 363 | class TestParameterizedFunction(API1TestCase): |
354 | 364 | |
355 | 365 | def _basic_tests(self,fn): |
375 | 385 | self.assertEqual(i(),(0.3,18,[10,20,30])) |
376 | 386 | |
377 | 387 | |
378 | @nottest | |
379 | 388 | class TestPO1(param.Parameterized): |
389 | __test__ = False | |
390 | ||
380 | 391 | x = param.Number(default=numbergen.UniformRandom(lbound=-1,ubound=1,seed=1),bounds=(-1,1)) |
381 | 392 | y = param.Number(default=1,bounds=(-1,1)) |
382 | 393 | |
383 | @istest | |
384 | 394 | class TestNumberParameter(API1TestCase): |
385 | 395 | |
386 | 396 | def test_outside_bounds(self): |
405 | 415 | assert False, "Should raise ValueError." |
406 | 416 | |
407 | 417 | |
408 | @istest | |
409 | 418 | class TestStringParameter(API1TestCase): |
410 | 419 | |
411 | 420 | def setUp(self): |
429 | 438 | assert t.c is None |
430 | 439 | |
431 | 440 | |
432 | @istest | |
433 | 441 | class TestParameterizedUtilities(API1TestCase): |
434 | 442 | |
435 | 443 | def setUp(self): |
449 | 457 | def test_default_label_formatter_overrides(self): |
450 | 458 | assert default_label_formatter.instance(overrides={'a': 'b'})('a') == 'b' |
451 | 459 | |
452 | @istest | |
453 | 460 | class TestParamOverrides(API1TestCase): |
454 | 461 | |
455 | 462 | def setUp(self): |
491 | 498 | def test_shared_list(self): |
492 | 499 | self.assertTrue(self.p1.inst is self.p2.inst) |
493 | 500 | 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() |
158 | 158 | |
159 | 159 | self.assertEqual(obj.pprint(qualify=True), |
160 | 160 | "tests.API1.testparameterizedrepr."+r) |
161 | ||
162 | ||
163 | ||
164 | if __name__ == "__main__": | |
165 | import nose | |
166 | nose.runmodule() |
112 | 112 | pass |
113 | 113 | else: |
114 | 114 | raise AssertionError("Selector created without range.") |
115 | ||
116 | ||
117 | if __name__ == "__main__": | |
118 | import nose | |
119 | nose.runmodule() |
0 | 0 | """ |
1 | 1 | Unit test for String parameters |
2 | 2 | """ |
3 | import sys | |
4 | ||
3 | 5 | from . import API1TestCase |
4 | 6 | |
5 | 7 | import param |
6 | 8 | |
7 | 9 | |
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]?)$' | |
9 | 11 | |
10 | 12 | class TestStringParameters(API1TestCase): |
11 | 13 | |
22 | 24 | |
23 | 25 | a = A() |
24 | 26 | |
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): | |
27 | 30 | a.s = None # because allow_None should be False |
28 | 31 | |
29 | 32 | def test_default_none(self): |
35 | 38 | a.s = None # because allow_None should be True with default of None |
36 | 39 | |
37 | 40 | def test_regex_incorrect(self): |
38 | ||
39 | 41 | class A(param.Parameterized): |
40 | 42 | s = param.String('0.0.0.0', regex=ip_regex) |
41 | 43 | |
42 | 44 | a = A() |
43 | 45 | |
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: | |
46 | 48 | a.s = '123.123.0.256' |
49 | self.assertEqual(str(e.exception), exception.replace('\\', '\\\\')) | |
47 | 50 | |
48 | 51 | 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: | |
52 | 54 | class A(param.Parameterized): |
53 | 55 | s = param.String(regex=ip_regex) # default value '' does not match regular expression |
54 | ||
55 | ||
56 | self.assertEqual(str(e.exception), exception.replace('\\', '\\\\')) |
6 | 6 | import copy |
7 | 7 | |
8 | 8 | from . import API1TestCase |
9 | from nose.plugins.skip import SkipTest | |
9 | import pytest | |
10 | 10 | import fractions |
11 | 11 | |
12 | 12 | try: |
13 | 13 | 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 | |
17 | 20 | |
18 | 21 | |
19 | 22 | class TestTimeClass(API1TestCase): |
77 | 80 | self.assertEqual(t(), 1) |
78 | 81 | self.assertEqual(t.time_type, fractions.Fraction) |
79 | 82 | |
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") | |
80 | 104 | def test_time_init_gmpy(self): |
81 | if gmpy is None: raise SkipTest | |
82 | ||
83 | 105 | t = param.Time(time_type=gmpy.mpq) |
84 | 106 | self.assertEqual(t(), gmpy.mpq(0)) |
85 | 107 | t.advance(gmpy.mpq(0.25)) |
86 | 108 | self.assertEqual(t(), gmpy.mpq(1,4)) |
87 | 109 | |
110 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
88 | 111 | def test_time_init_gmpy_advanced(self): |
89 | if gmpy is None: raise SkipTest | |
90 | 112 | t = param.Time(time_type=gmpy.mpq, |
91 | 113 | timestep=gmpy.mpq(0.25), |
92 | 114 | until=1.5) |
266 | 288 | self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi))) |
267 | 289 | |
268 | 290 | |
291 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
269 | 292 | def test_time_hashing_integers_gmpy(self): |
270 | 293 | """ |
271 | 294 | Check that hashes for gmpy values at the integers also matches |
272 | 295 | those of ints, fractions and strings. |
273 | 296 | """ |
274 | if gmpy is None: raise SkipTest | |
275 | 297 | hashfn = numbergen.Hash("test", input_count=1) |
276 | 298 | hash_1 = hashfn(1) |
277 | 299 | hash_42 = hashfn(42) |
282 | 304 | self.assertEqual(hash_42, hashfn(gmpy.mpq(42))) |
283 | 305 | self.assertEqual(hash_42, hashfn(42)) |
284 | 306 | |
307 | @pytest.mark.skipif(gmpy is None, reason="gmpy is not installed") | |
285 | 308 | def test_time_hashing_rationals_gmpy(self): |
286 | 309 | """ |
287 | 310 | Check that hashes of fractions and gmpy mpqs match for some |
288 | 311 | reasonable rational numbers. |
289 | 312 | """ |
290 | if gmpy is None: raise SkipTest | |
291 | 313 | pi = "3.141592" |
292 | 314 | hashfn = numbergen.Hash("test", input_count=1) |
293 | 315 | self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5))) |
294 | 316 | self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592))) |
295 | ||
296 | ||
297 | ||
298 | ||
299 | if __name__ == "__main__": | |
300 | import nose | |
301 | nose.runmodule() |
0 | 0 | """ |
1 | 1 | Unit test for watch mechanism |
2 | 2 | """ |
3 | import copy | |
4 | ||
5 | import param | |
6 | ||
7 | from param.parameterized import discard_events | |
8 | ||
3 | 9 | from . import API1TestCase |
4 | ||
5 | 10 | from .utils import MockLoggingHandler |
6 | ||
7 | import param | |
8 | ||
9 | from param.parameterized import discard_events | |
10 | 11 | |
11 | 12 | |
12 | 13 | class Accumulator(object): |
35 | 36 | b = param.Parameter(default=0) |
36 | 37 | c = param.Parameter(default=0) |
37 | 38 | 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 | |
38 | 44 | |
39 | 45 | |
40 | 46 | class SimpleWatchSubclass(SimpleWatchExample): |
61 | 67 | def _set_d_bounds(self): |
62 | 68 | self.param.d.bounds = (self.c, self.c*2) |
63 | 69 | |
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 | |
64 | 79 | |
65 | 80 | class WatchSubclassExample(WatchMethodExample): |
66 | 81 | |
67 | 82 | pass |
68 | ||
69 | 83 | |
70 | 84 | |
71 | 85 | class TestWatch(API1TestCase): |
77 | 91 | cls.log_handler = MockLoggingHandler(level='DEBUG') |
78 | 92 | log.addHandler(cls.log_handler) |
79 | 93 | |
80 | ||
81 | 94 | def setUp(self): |
82 | 95 | super(TestWatch, self).setUp() |
83 | 96 | self.accumulator = 0 |
97 | self.list_accumulator = [] | |
84 | 98 | |
85 | 99 | def test_triggered_when_changed(self): |
86 | 100 | def accumulator(change): |
92 | 106 | self.assertEqual(self.accumulator, 1) |
93 | 107 | obj.a = 2 |
94 | 108 | self.assertEqual(self.accumulator, 3) |
95 | ||
96 | 109 | |
97 | 110 | def test_discard_events_decorator(self): |
98 | 111 | def accumulator(change): |
104 | 117 | obj.a = 1 |
105 | 118 | self.assertEqual(self.accumulator, 0) |
106 | 119 | 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'] | |
109 | 147 | |
110 | 148 | def test_triggered_when_changed_iterator_type(self): |
111 | 149 | def accumulator(change): |
118 | 156 | obj.a = tuple() |
119 | 157 | self.assertEqual(self.accumulator, tuple()) |
120 | 158 | |
121 | ||
122 | 159 | def test_triggered_when_changed_mapping_type(self): |
123 | 160 | def accumulator(change): |
124 | 161 | self.accumulator = change.new |
130 | 167 | obj.a = {} |
131 | 168 | self.assertEqual(self.accumulator, {}) |
132 | 169 | |
133 | ||
134 | 170 | def test_untriggered_when_unchanged(self): |
135 | 171 | def accumulator(change): |
136 | 172 | self.accumulator += change.new |
142 | 178 | obj.a = 1 |
143 | 179 | self.assertEqual(self.accumulator, 1) |
144 | 180 | |
145 | ||
146 | 181 | def test_triggered_when_unchanged_complex_type(self): |
147 | 182 | def accumulator(change): |
148 | 183 | self.accumulator += 1 |
155 | 190 | obj.a = subobj |
156 | 191 | self.assertEqual(self.accumulator, 2) |
157 | 192 | |
158 | ||
159 | 193 | def test_triggered_when_unchanged_if_not_onlychanged(self): |
160 | 194 | accumulator = Accumulator() |
161 | 195 | obj = SimpleWatchExample() |
166 | 200 | args = accumulator.args_for_call(0) |
167 | 201 | self.assertEqual(len(args), 1) |
168 | 202 | self.assertEqual(args[0].name, 'a') |
203 | self.assertEqual(args[0].what, 'value') | |
169 | 204 | self.assertEqual(args[0].old, 0) |
170 | 205 | self.assertEqual(args[0].new, 1) |
171 | 206 | self.assertEqual(args[0].type, 'set') |
174 | 209 | args = accumulator.args_for_call(1) |
175 | 210 | self.assertEqual(len(args), 1) |
176 | 211 | self.assertEqual(args[0].name, 'a') |
212 | self.assertEqual(args[0].what, 'value') | |
177 | 213 | self.assertEqual(args[0].old, 1) |
178 | 214 | self.assertEqual(args[0].new, 1) |
179 | 215 | self.assertEqual(args[0].type, 'set') |
180 | 216 | |
181 | ||
182 | ||
183 | 217 | def test_untriggered_when_unwatched(self): |
184 | 218 | def accumulator(change): |
185 | 219 | self.accumulator += change.new |
191 | 225 | obj.param.unwatch(watcher) |
192 | 226 | obj.a = 2 |
193 | 227 | self.assertEqual(self.accumulator, 1) |
194 | ||
195 | 228 | |
196 | 229 | def test_warning_unwatching_when_unwatched(self): |
197 | 230 | def accumulator(change): |
209 | 242 | accumulator = Accumulator() |
210 | 243 | |
211 | 244 | obj = SimpleWatchExample() |
212 | obj.param.watch(accumulator, ['a','b']) | |
245 | obj.param.watch(accumulator, ['a', 'b']) | |
213 | 246 | |
214 | 247 | obj.a = 2 |
215 | 248 | self.assertEqual(accumulator.call_count(), 1) |
267 | 300 | |
268 | 301 | obj.param.watch(set_c, ['a', 'b']) |
269 | 302 | |
270 | obj.param.set_param(a=2) | |
303 | obj.param.update(a=2) | |
271 | 304 | self.assertEqual(obj.c, 3) |
272 | 305 | |
273 | 306 | # Change inside watch callback should have triggered |
274 | 307 | # second call to accumulator |
275 | 308 | self.assertEqual(accumulator.call_count(), 2) |
276 | 309 | |
277 | ||
278 | 310 | def test_simple_batched_watch(self): |
279 | 311 | |
280 | 312 | accumulator = Accumulator() |
281 | 313 | |
282 | 314 | obj = SimpleWatchExample() |
283 | 315 | obj.param.watch(accumulator, ['a','b']) |
284 | obj.param.set_param(a=23, b=42) | |
316 | obj.param.update(a=23, b=42) | |
285 | 317 | |
286 | 318 | self.assertEqual(accumulator.call_count(), 1) |
287 | 319 | args = accumulator.args_for_call(0) |
297 | 329 | self.assertEqual(args[1].new, 42) |
298 | 330 | self.assertEqual(args[1].type, 'changed') |
299 | 331 | |
300 | ||
301 | 332 | def test_simple_class_batched_watch(self): |
302 | 333 | |
303 | 334 | accumulator = Accumulator() |
304 | 335 | |
305 | 336 | obj = SimpleWatchSubclass |
306 | 337 | watcher = obj.param.watch(accumulator, ['a','b']) |
307 | obj.param.set_param(a=23, b=42) | |
338 | obj.param.update(a=23, b=42) | |
308 | 339 | |
309 | 340 | self.assertEqual(accumulator.call_count(), 1) |
310 | 341 | args = accumulator.args_for_call(0) |
321 | 352 | self.assertEqual(args[1].type, 'changed') |
322 | 353 | |
323 | 354 | SimpleWatchExample.param.unwatch(watcher) |
324 | obj.param.set_param(a=0, b=0) | |
325 | ||
355 | obj.param.update(a=0, b=0) | |
326 | 356 | |
327 | 357 | def test_simple_batched_watch_callback_reuse(self): |
328 | 358 | |
332 | 362 | obj.param.watch(accumulator, ['a','b']) |
333 | 363 | obj.param.watch(accumulator, ['c']) |
334 | 364 | |
335 | obj.param.set_param(a=23, b=42, c=99) | |
365 | obj.param.update(a=23, b=42, c=99) | |
336 | 366 | |
337 | 367 | self.assertEqual(accumulator.call_count(), 2) |
338 | 368 | # Order may be undefined for Python <3.6 |
356 | 386 | else: |
357 | 387 | raise Exception('Invalid number of arguments') |
358 | 388 | |
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') | |
359 | 423 | |
360 | 424 | def test_subclass_batched_watch(self): |
361 | 425 | |
364 | 428 | obj = SimpleWatchSubclass() |
365 | 429 | |
366 | 430 | obj.param.watch(accumulator, ['b','c']) |
367 | obj.param.set_param(b=23, c=42) | |
431 | obj.param.update(b=23, c=42) | |
368 | 432 | |
369 | 433 | self.assertEqual(accumulator.call_count(), 1) |
370 | 434 | args = accumulator.args_for_call(0) |
380 | 444 | self.assertEqual(args[1].new, 42) |
381 | 445 | self.assertEqual(args[1].type, 'changed') |
382 | 446 | |
383 | ||
384 | 447 | def test_nested_batched_watch(self): |
385 | 448 | |
386 | 449 | accumulator = Accumulator() |
387 | 450 | |
388 | 451 | obj = SimpleWatchExample() |
389 | 452 | |
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) | |
396 | 459 | |
397 | 460 | self.assertEqual(accumulator.call_count(), 2) |
398 | 461 | args = accumulator.args_for_call(0) |
421 | 484 | self.assertEqual(args[1].new, 12) |
422 | 485 | self.assertEqual(args[1].type, 'changed') |
423 | 486 | |
424 | ||
425 | 487 | def test_nested_batched_watch_not_onlychanged(self): |
426 | 488 | accumulator = Accumulator() |
427 | 489 | |
428 | 490 | obj = SimpleWatchSubclass() |
429 | 491 | |
430 | 492 | 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) | |
432 | 494 | |
433 | 495 | self.assertEqual(accumulator.call_count(), 1) |
434 | 496 | |
445 | 507 | self.assertEqual(args[1].new, 0) |
446 | 508 | self.assertEqual(args[1].type, 'set') |
447 | 509 | |
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) | |
449 | 541 | |
450 | 542 | class TestWatchMethod(API1TestCase): |
451 | 543 | |
488 | 580 | self.assertEqual(obj.param.d.bounds, (2, 4)) |
489 | 581 | self.assertEqual(accumulator.call_count(), 1) |
490 | 582 | |
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 | ||
491 | 592 | def test_depends_with_watch_on_subclass(self): |
492 | 593 | obj = WatchSubclassExample() |
493 | 594 | |
494 | 595 | obj.b = 3 |
495 | 596 | self.assertEqual(obj.c, 6) |
496 | 597 | |
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) | |
498 | 606 | |
499 | 607 | |
500 | 608 | class TestWatchValues(API1TestCase): |
502 | 610 | def setUp(self): |
503 | 611 | super(TestWatchValues, self).setUp() |
504 | 612 | self.accumulator = 0 |
613 | self.list_accumulator = [] | |
505 | 614 | |
506 | 615 | def test_triggered_when_values_changed(self): |
507 | 616 | def accumulator(a): |
514 | 623 | obj.a = 2 |
515 | 624 | self.assertEqual(self.accumulator, 3) |
516 | 625 | |
517 | ||
518 | 626 | def test_untriggered_when_values_unchanged(self): |
519 | 627 | def accumulator(a): |
520 | 628 | self.accumulator += a |
526 | 634 | obj.a = 1 |
527 | 635 | self.assertEqual(self.accumulator, 1) |
528 | 636 | |
529 | ||
530 | 637 | def test_untriggered_when_values_unwatched(self): |
531 | 638 | def accumulator(a): |
532 | 639 | self.accumulator += a |
539 | 646 | obj.a = 2 |
540 | 647 | self.assertEqual(self.accumulator, 1) |
541 | 648 | |
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'] | |
542 | 674 | |
543 | 675 | def test_simple_batched_watch_values_setattr(self): |
544 | 676 | |
559 | 691 | kwargs = accumulator.kwargs_for_call(1) |
560 | 692 | self.assertEqual(kwargs, {'b':3}) |
561 | 693 | |
562 | ||
563 | 694 | def test_simple_batched_watch_values(self): |
564 | 695 | |
565 | 696 | accumulator = Accumulator() |
566 | 697 | |
567 | 698 | obj = SimpleWatchExample() |
568 | 699 | obj.param.watch_values(accumulator, ['a','b']) |
569 | obj.param.set_param(a=23, b=42) | |
700 | obj.param.update(a=23, b=42) | |
570 | 701 | |
571 | 702 | self.assertEqual(accumulator.call_count(), 1) |
572 | 703 | kwargs = accumulator.kwargs_for_call(0) |
573 | 704 | self.assertEqual(kwargs, {'a':23, 'b':42}) |
574 | 705 | |
575 | ||
576 | 706 | def test_simple_batched_watch_values_callback_reuse(self): |
577 | 707 | |
578 | 708 | accumulator = Accumulator() |
581 | 711 | obj.param.watch_values(accumulator, ['a','b']) |
582 | 712 | obj.param.watch_values(accumulator, ['c']) |
583 | 713 | |
584 | obj.param.set_param(a=23, b=42, c=99) | |
714 | obj.param.update(a=23, b=42, c=99) | |
585 | 715 | |
586 | 716 | self.assertEqual(accumulator.call_count(), 2) |
587 | 717 | # Order may be undefined for Python <3.6 |
594 | 724 | raise Exception('Invalid number of arguments') |
595 | 725 | |
596 | 726 | |
597 | ||
598 | ||
599 | ||
600 | 727 | class TestWatchAttributes(API1TestCase): |
601 | 728 | |
602 | 729 | def setUp(self): |
627 | 754 | obj.param['d'].bounds = (0, 3) |
628 | 755 | assert SimpleWatchExample.param['d'].bounds is None |
629 | 756 | assert self.accumulator == [(0, 3)] |
630 | ||
631 | 757 | |
632 | 758 | |
633 | 759 | class TestTrigger(API1TestCase): |
703 | 829 | self.assertEqual(args[1].old, 0) |
704 | 830 | self.assertEqual(args[1].new, 0) |
705 | 831 | self.assertEqual(args[1].type, 'triggered') |
706 | ||
707 | ||
708 | if __name__ == "__main__": | |
709 | import nose | |
710 | nose.runmodule() |
23 | 23 | def __init__(self, *args, **kwargs): |
24 | 24 | self.messages = {'DEBUG': [], 'INFO': [], 'WARNING': [], |
25 | 25 | 'ERROR': [], 'CRITICAL': [], 'VERBOSE':[]} |
26 | self.param_methods = {'WARNING':'param.warning()', 'INFO':'param.message()', | |
27 | 'VERBOSE':'param.verbose()', 'DEBUG':'param.debug()'} | |
28 | 26 | super(MockLoggingHandler, self).__init__(*args, **kwargs) |
29 | 27 | |
30 | 28 | def emit(self, record): |
50 | 48 | Assert that the last line captured at the given level ends with |
51 | 49 | a particular substring. |
52 | 50 | """ |
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}' | |
54 | 52 | last_line = self.tail(level, n=1) |
55 | 53 | 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))) | |
58 | 56 | if not last_line[0].endswith(substring): |
59 | raise AssertionError(msg.format(method=self.param_methods[level], | |
57 | raise AssertionError(msg.format(level=level, | |
60 | 58 | last_line=repr(last_line[0]), |
61 | 59 | substring=repr(substring))) |
62 | 60 | |
65 | 63 | Assert that the last line captured at the given level contains a |
66 | 64 | particular substring. |
67 | 65 | """ |
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}' | |
69 | 67 | last_line = self.tail(level, n=1) |
70 | 68 | if len(last_line) == 0: |
71 | 69 | raise AssertionError('Missing {method} output: {substring}'.format( |
72 | method=self.param_methods[level], substring=repr(substring))) | |
70 | level=level, substring=repr(substring))) | |
73 | 71 | if substring not in last_line[0]: |
74 | raise AssertionError(msg.format(method=self.param_methods[level], | |
72 | raise AssertionError(msg.format(level=level, | |
75 | 73 | last_line=repr(last_line[0]), |
76 | 74 | 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') |
0 | 0 | [tox] |
1 | 1 | envlist = |
2 | py37,py36,py35,py34,py27,pypy, | |
3 | {py27,py36}-flakes, | |
4 | {py27,py36}-with_numpy, | |
5 | {py27,py36}-with_ipython | |
6 | {py27,py35,py36,py37}-with_pandas | |
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 | |
7 | 13 | |
8 | 14 | [testenv] |
9 | 15 | 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 | |
22 | 17 | |
23 | 18 | [testenv:with_numpy] |
24 | 19 | deps = {[testenv]deps} |
30 | 25 | pandas |
31 | 26 | setenv = PARAM_TEST_PANDAS = 1 |
32 | 27 | |
33 | ||
34 | 28 | [testenv:with_ipython] |
35 | 29 | deps = {[testenv]deps} |
36 | 30 | ipython |
37 | 31 | setenv = PARAM_TEST_IPYTHON = 1 |
38 | 32 | |
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 | ||
39 | 55 | [testenv:flakes] |
40 | 56 | skip_install = true |
41 | 57 | 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 |