diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 76a28301c2..11c0d4b9f9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 2.8.6 +current_version = 3.0.3 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml new file mode 100644 index 0000000000..4e6df42256 --- /dev/null +++ b/.github/workflows/publish_pypi.yml @@ -0,0 +1,29 @@ +name: Publish to PyPI + +on: + release: + types: [created] + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+*' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.gitlab-ci-alba.yml b/.gitlab-ci-alba.yml new file mode 100644 index 0000000000..90f768f8bc --- /dev/null +++ b/.gitlab-ci-alba.yml @@ -0,0 +1,8 @@ +# This file is for configuring the CI/CD for creating (unofficial) +# debian packages for sardana at ALBA +# It has no efect unless you configure your gitlab instance to use it. +# TODO: generalise this so that it does not depend on ALBA's infrastructure + +include: +- https://git.cells.es/ctpkg/ci/ctpipeline/raw/master/ctjobdefs-ci.yml +- https://git.cells.es/ctpkg/ci/ctpipeline/raw/master/ctpipeline.yml diff --git a/.travis.yml b/.travis.yml index 7649e07b87..00a476ccca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ services: - docker python: - - "3.6" + - "3.5" env: global: @@ -18,7 +18,6 @@ env: matrix: - TEST="flake8" - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test - - TEST="testsuite" DOCKER_IMG=reszelaz/sardana-test:taurus-support-3.x - TEST="doc" DOCKER_IMG=reszelaz/sardana-test @@ -26,8 +25,8 @@ before_install: # install flake8 to perform python code style check in the script part # install it using pip in order to get the newest version - if [ $TEST == "flake8" ]; then sudo apt-get update -qq ; fi - - if [ $TEST == "flake8" ]; then sudo apt-get install -qq python-pip; fi - - if [ $TEST == "flake8" ]; then sudo pip install flake8; fi + - if [ $TEST == "flake8" ]; then sudo apt-get install -qq python3-pip; fi + - if [ $TEST == "flake8" ]; then sudo pip3 install flake8; fi install: # run reszelaz/sardana-test docker container (Debian8 with sardana-deps) @@ -38,7 +37,7 @@ install: - if [ $TEST == "testsuite" ]; then sleep 10; fi # install sardana in order to create the launcher scripts for servers - - if [ $TEST == "testsuite" ]; then docker exec sardana-test bash -c "cd /sardana && python setup.py install"; fi + - if [ $TEST == "testsuite" ]; then docker exec sardana-test bash -c "cd /sardana && python3 setup.py install"; fi # start Pool and MacroServer necessary for macro tests - if [ $TEST == "testsuite" ]; then docker exec sardana-test supervisorctl start Pool; fi @@ -50,37 +49,7 @@ script: # run flake8 check on all python files in the project - if [ $TEST == "flake8" ]; then ci/flake8_diff.sh; fi # run the full testsuite - - if [ $TEST == "testsuite" ]; then docker exec sardana-test sardanatestsuite; fi + - if [ $TEST == "testsuite" ]; then docker exec sardana-test xvfb-run -s '-screen 0 1920x1080x24' /bin/bash -c "pytest /usr/local/lib/python3.5/dist-packages/sardana-*.egg/sardana"; fi # build docs - - if [ $TEST == "doc" ]; then - docker exec -t sardana-test /bin/bash -c "cd /sardana ; sphinx-build -W doc/source/ build/sphinx/html" ; - fi - - # deploy sphinx docs to sardana-doc repo if we are on upstream - - if [[ $TEST == "doc" && $TRAVIS_REPO_SLUG == "sardana-org/sardana" ]]; then - pip install doctr ; - docker exec sardana-test /bin/bash -c "touch /sardana/build/sphinx/html/.nojekyll" ; - if [[ $TRAVIS_BRANCH == "develop" ]]; then - doctr deploy . ; - else - doctr deploy "v-$TRAVIS_BRANCH" ; - fi; - fi - -doctr: - key-path : ci/github_deploy_key.enc - deploy-repo : sardana-org/sardana-doc - require-master: false - sync: true - built-docs: build/sphinx/html/ - -deploy: - # deploy to pypi when a version tag is pushed to the official repo - - provider: pypi - user: sardana_bot - password: - secure: "HvZGtw8qFlacssi7FE92+gFgQPRRPvurpPxi/Gq74TeKWU0X4EbWVT3XMdi7sb7yA7JQlOGIGtY3ofzEdrKgKcEsrxxKbeSW7foDf3+AlmMF7c31ePxkqBCGMSAxsaCjKJR2sVtBNiycp0I7LWYeKlzFNY2W8aZW9dnpkC9aD/oGdNRJlCVGq912xaTnXRxmUrh+2IeUqsXKqfih7E0Qw99VXOLFdHIHtoPGN5ka+tvLp+zNFMi1q2HUyix4P/aQ10BwE5t1onfdSBBh7bzZTINoUVuN1bstNXYcoqfVMAbOoeArIIr7z41eYd8G8WMTXJp2MFrO61AW6xK8htB07RX2eaEWq7KT4zazG5vP/Skayr7ofnB/d3Rs1BOre9ttScJIxwyQLhL60WeM9NyCoHVjNdKYK5gNHX4se/6FOzmHm1VgQgI9bzyfIIAoSSyUL/5KOGdOwhMPSij5AT1YIy8RSe7efm+xw3md+wcmEsbaMX9VEy2YgTL0/nmFHrEA+9HV0I5xkFBQ8BHuK0YFubQ9rG99B1GwF0Vl85M+Ylp5D1/p70sXCHEUk3SbOcg9Kz0TTisDMuDT2ajJYGylg7/OskI5OwOBbEndP8OUPesm62V1ciQcKjH2L81yWajRPSfd/OPjoMwG+XdaG5rR7m2FACXvyhEOIeK1Mt41MvM=" - on: - repo: sardana-org/sardana - tags: true - condition: "$TEST == testsuite && $DOCKER_IMG == reszelaz/sardana-test && $TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" + - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "cd /sardana ; sphinx-build -W doc/source/ build/sphinx/html" ; fi + - if [ $TEST == "doc" ]; then docker exec -t sardana-test /bin/bash -c "touch /sardana/build/sphinx/html/.nojekyll" ; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ffa235b65..29c271e292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,207 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This file follows the formats and conventions from [keepachangelog.com] +## [3.0.3] 2020-09-18 + +### Added + +* Support to Python >= 3.5 (#1089, #1173, #1201, #1313, #1336) +* Showscan online based on pyqtgraph (#1285) + * multiple plots in the same MultiPlot widget (as opposed to different panels before) + * option to group curves by x-axis or individual plot per curve + * new showscan console script + * support fast scans: update curves at a fix rate (5Hz) + * better curve colors and symbols +* Measurement group (Taurus extension) configuration API with methods to + set/get: enabled, output, plot type, plot axes, timer, monitor, synchronizer, + value ref enabled, value ref pattern parameters(#867, #1415, #1416) +* Experiment configuration (expconf) macros + * Measurement group configuration macros: `set_meas_conf` and `get_meas_conf` (#690) + * Active measurement group selection macros: `set_meas` and `get_meas` (#690) + * Pre-scan snapshot macros: `lssnap`, `defsnap` and `udefsnap` (#1199) +* Automatic scan statistics calculation with the `scanstats` macro as the `post-scan` + hook stored in the `ScanStats` environment variable (#880, #1402) +* `pic`, `cen` to move the scanned motor to the peak and center of FWHM values + respectively (#890) +* `where` macro to print the scanned motor position (#890) +* `plotselect` macro for configuring channels for online scan plotting (#824) +* `genv` macro for printing environment variable values (#888) +* QtSpock widget (`QtSpock` and `QtSpockWidget`) - *experimental* (#1109) +* Dump info on channels if MG acq fails in step scan, ct and uct (#1308) +* Add timestamp to element's dumped information (#1308) +* Quality to `SardanaAttribute` (#1353) +* Instruments creation and configuration in sar_demo (#1198) +* Allow _experimental channel acquisition_ with PoolChannelTaurusValue (PCTV) widget (#1203) +* Documentation to Taurus Extensions of Sardana Devices: MacroServer part + and the whole Sardana part of the Qt Taurus Extensions (#1228, #1233) +* Advertise newfile macro in case no ScanDir or ScanFile is set (#1254, #1258) +* Improve scans to detect if a ScanFile od ScanDir are set but empty (#1262) +* Possibility to view debug messages in `DoorOutput` widget - enable/disable + using context menu option (#1242) +* Improve user experience with PMTV: + * Store PMTV (motor widget) configurations: *expert view* and *write mode* + (relative or absolute) permanently as TaurusGUI settings (#1286) + * Do not create encoder widget in PMTV if the motod does not have encoder + in order to avoid errors comming from the polling (#209, #1288) + * Change limit switches indicators from buttons to labels (#210, #1290) +* Improve documentation (#1241) +* Better macro exception message and hint to use `www` (#1191) +* Stress tests (on the Taurus level) for measurements (#1353) +* Add basic information to "how to write custom recorder" to + the documentation (#1275) +* Register a TaurusValue factory for pool widgets (#1333) +* Direct links to Sardana-Taurus model API (#1335) +* Use GitHub workflows to upload to PyPI (#1253, #1166, #1408, #1189) + +### Fixed + +* Improve macro aborting in Spock (2nd, 3rd and eventual 4th Ctrl+C now act + on the macro). Also print additional information on what is happening while + stopping and aborting (#1256, #978, #34) +* Use `tango.EnsureOmnitThread` to protect Sardana threads + (Tango is not thread safe) (#1298) +* Avoid using Tango `AttributeProxy` in limits protection to not be affected + by bug tango-controls/pytango#315 (#1302) +* Avoid deadlock in Sardana-Taurus models e.g. `MeasurementGroup.count()` or + `Motor.move()` (#1348) + * Remove redundant protection in PoolElement.start() and waitFinish() +* Fast repetitions of single acquisition measurements (counts) on MeasurementGroup (#1353) +* Pre-mature returning to ON state of MeasurementGroup at the end of measurement (#1353) +* Default macro parameter values in macroexecutor (#1153) +* Executing RunMacro Door's command with string parameters containing spaces (#1240) +* `macroxecutor` and `sequencer` now react on added/removed macros #295 +* Avoid printing `None` in `wm` and `wa` macros for `DialPosition` attribute and print + the `Position` attribute twice for pseudo motors (#929, #953, #1411, #1412) +* Setting of environment variables in Python 3.7 (#1195) +* Use `taurus.external.qt.compat.PY_OBJECT` in singal signatures instead of `object` + to avoid problems when using `builtins` from `future` (#1082) +* Remove Taurus deprecated code what reduces deprecation warnings (#1206, #1252) +* Macro functions which define results now really report the result (#1238) +* Use of env and hints in `macro` function decorator (#1239) +* Fix several issues with PMTV: + * Reset pending operations of absolute movement on switching to relative movement (#1293) + * PMTV widget not updating the following attributes: limit switches, state + and status (#1244) +* Avoid Taurus GUI slowness on startup and changing of perspectives due to too + large macroexecutor history by limitting it to 100 - + configurable with `MACROEXECUTOR_MAX_HISTORY` (#1307) +* OutputBlock view option when macros produce outputs at high rate (#1245) +* `showscan online` shows only the online trend and not erroneously online and offline + (#1260, #1400) +* Fix fast operations (motion & acq) by propertly clearing operation context and + resetting of acq ctrls dicts (#1300) +* Premature end of acquisition on Windows (#1397) +* `timescan` with referable channels (#1399, #1401) +* Use proper python3 executable regardeless of installation (#1398) +* Environment variables validation before macro execution when these are defined + on door's or macro's level (#1390) +* Use more efficient way to get terminal size for better printing spock output (#1245, #1268) +* Measurement groups renaming with `renameelem` macro(#951) +* `macroexecutor` correctly loads macro combo box if it was started with server down and + server started afterwards (#599, #1278) +* `TaurusMacroExecutorWidget` does not use _parent model_ feature (#599, #1278) +* `TaurusSequencerWidget` does not use _parent model_ feature (#1284) +* Macro plotting in new versions of ipython and matplotlib require extra call to + `pyplot.draw()` to make sure that the plot is refreshed (#1280) +* Controller's `StateOne()` that returns only state (#621, #1342) +* Fix problems with non-timerable channels in expconf (#1409) +* Allow MacroButton widget to be smaller - minimum size to show the macro name (#1265) +* Remove TangoAttribute controllers from Sardana (#181, #1279) +* Remove deprecation warning revealed when running test suite (#1267) +* Remove event filtering in `DynamicPlotManager` (showscan online) (#1299) +* Avoid unnecessary creations of DeviceProxies in `ascanct` (#1281) +* Macro modules with annotated functions are properly interpreted by the MacroServer + (#1366, #1367) +* Adapt to new taurus behavior of `cmd_line_parser` kwarg of `TaurusApplication` (#1306) +* Fix dummy C/T and 2D controller classes in the case the start sequence was interrupted + (#1188, #1309) +* Fix dummy motor velocity so it respects steps_per_unit (#1310) +* Make handling of `Macro.sendRecordData()` with arbitrary data more robust in Spock + (#1320, #1319) +* Use `utf8_json` as default codec (in Tango) if `Macro.sendRecordData()` does not specify one + (#1320, #1319) +* Avoid repeating of positions when `regscan`, `reg2scan` and `reg3scan` pass through start + position(s) (#1326) +* `test_stop_meas_cont_acquisition_3` spurious failures (#1188, #1353) +* Build docs with Sphinx 3 (#1330) + +### Deprecated + +* `DoorDebug` widget - use `DoorOutput` with enabled debugging (#1242) +* Global measurement group timer/monitor on all levels (#867) +* `value_ref_enabled` and `value_ref_pattern` measurement group parameters + for non-referable channels (#867) + +### Changed + +* Avoid extra state readout at the end of acquisition (#1354) +* Renamed _synchronization_ to _synch description_ + * Tango MeasurementGroup `Synchronization` attribute to `SynchDescription` + * Core MeasurementGroup `synchronization` property to `synch_description` + * Sardana-Taurus Device MeasurementGroup `getSynchronizationObj()`, + `getSynchronization()` and `setSynchronization()` methods to + `getSynchDescriptionObj()`,`getSynchDescription()` + and `setSynchDescription()` (#1337) + * `SynchronizationDescription` helper class to `SynchDescription` +* Requirements are no longer checked when importing sardana (#1185) +* Measurement group (Taurus extension) configuration API methods, known in + the old sense for setting a global measurement group timer/monitor: + `getTimer()`, `setTimer()`, `getMonitor()` were moved to `MGConfiguration` + class and are deprecated (#867) +* `macroexecutor` and `sequencer` discard settings if the models passed + as command line arguments had changed with respect to the previous execution + (#1278, #1284) + +### Removed + +* Support to Python < 3.5 (#1089, #1173, #1201, #1263) +* `sardana.macroserver.macro.ParamRepeat` class (#1315, #1358) +* Backwards compatibility for measurement group start without preparation + (#1315, #1373) +* Controller API (#1315, #1361): + * `class_prop` + * `ctrl_extra_attributes` + * `inst_name` + * `SetPar()` and `GerPar()` + * `SetExtraAttributePar()` and `GetExtraAttributePar()` + * `ctrl_properties` with "Description" as `str` +* `CounterTimerController` controller API (#1315, #1362, #1403): + * `_trigger_type` + * `PreStartAllCT()`, `PreStartOneCT()`, `StartAllCT()` and `StartOneCT()` +* `PseudoMotorController` controller API (#1315, #1363) + * `calc_all_pseudo()`, `calc_all_physical()`, `calc_pseudo()` and `calc_physical()` +* `PseudoCounterController` controller API (#1315, #1364) + * `calc()` +* `IORegisterController` controller API (#1315, #1365): + * `predefined_values` +* `Loadable` backawards compatibility (without `repetitions` and `latency` arguments) + (#1315, #1370) +* `PoolMotorSlim` widget (#1315, #1380) +* Door's Tango device `Abort` command (#1315, #1376) +* Backwards compatibility in measurement group configuration (#1315, #1372) + * not complete names (without the scheme and PQDN) + * `trigger_type` +* `Label` and `Calibration` attributes of `DiscretePseudMotor` controller + (#1315, #1374) +* MacroButton's methods (#1315, #1379, #1405) + * `toggleProgress()` + * `updateMacroArgumentFromSignal()` + * `connectArgEditors()` +* `Controller`'s (Taurus extension) `getUsedAxis` method (#1315, #1377) +* `sardana.taurus.qt.qtgui.extra_macroexecutor.dooroutput.DoorAttrListener` class + (#1315, #1378) +* "Show/hide plots" button in `expconf` (#960, #1255, #1257) +* `plotsButton` argument in `ExpDescriptionEditor` constructor (#960, #1255, #1257) +* `showscan online_raw` magic command in spock (#1260) +* `online` kwarg in `SpockBaseDoor` constructor (#1260) +* `sardana.requirements` (#1185) +* `sardanatestsuite` and `sardana.test.testsuite.*` utility functions (#1347) +* Hook places: `hooks` and `pre-start` (#1315, #1359) +* `FileRecorder` macro hint (#1315, #1360) +* `sardana.release` attributes: `version_info` and `revision` (#1315, #1357) +* `sardana.spock.release` module (#1315, #1357) +* Support to IPython < 1 (#1315, #1375) + ## [2.8.6] 2020-08-10 ### Fixed @@ -746,6 +947,8 @@ Main improvements since sardana 1.5.0 (aka Jan15): [keepachangelog.com]: http://keepachangelog.com +[Unreleased]: https://github.com/sardana-org/sardana/compare/3.0.3...HEAD +[3.0.3]: https://github.com/sardana-org/sardana/compare/3.0.3...2.8.6 [2.8.6]: https://github.com/sardana-org/sardana/compare/2.8.6...2.8.5 [2.8.5]: https://github.com/sardana-org/sardana/compare/2.8.5...2.8.4 [2.8.4]: https://github.com/sardana-org/sardana/compare/2.8.4...2.8.3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b4ec3de88..35f86abd5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ In general, the contributions to Sardana should consider following: - The code must comply with the sardana coding conventions: - We try to follow the standard Python style conventions as described in [Style Guide for Python Code](http://www.python.org/peps/pep-0008.html) - - Code **must** be python 2.6 compatible + - Code **must** be python 3.5 compatible (python 2 is not supported) - Use 4 spaces for indentation - use ``lowercase`` for module names. If possible prefix module names with the word ``sardana`` (like :file:`sardanautil.py`) to avoid import mistakes. @@ -79,7 +79,6 @@ The following code can be used as a template for writing new python modules to Sardana: ```python - #!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## diff --git a/README.md b/README.md new file mode 100644 index 0000000000..db6b754f04 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +[![PyPI pyversions](https://img.shields.io/pypi/pyversions/sardana.svg)](https://pypi.python.org/pypi/sardana/) +[![PyPI license](https://img.shields.io/pypi/l/sardana.svg)](https://pypi.python.org/pypi/sardana/) +[![PyPI version](https://img.shields.io/pypi/v/sardana.svg)](https://pypi.python.org/pypi/sardana/) +[![GitHub tag](https://img.shields.io/github/tag/sardana-org/sardana.svg)](https://GitHub.com/sardana-org/sardana/tags/) +[![Travis status](https://travis-ci.org/sardana-org/sardana.svg?branch=develop)](https://travis-ci.org/sardana-org/sardana) +[![Appveyor status](https://ci.appveyor.com/api/projects/status/rxeo3hsycilnyn9k/branch/develop?svg=true)](https://ci.appveyor.com/project/taurusorg/sardana/branch/develop) + + +Sardana is a software suite for Supervision, Control and Data Acquisition in scientific installations. + +Projects related to Sardana +=========================== + +* Sardana uses Taurus for control system access and user interfaces +* Sardana is based on Tango +* The command line interface for Sardana (Spock) is based on IPython + +Main web site: http://sardana-controls.org diff --git a/ci/appveyor.yml b/ci/appveyor.yml index 8c1991a639..d871492322 100644 --- a/ci/appveyor.yml +++ b/ci/appveyor.yml @@ -7,28 +7,64 @@ environment: VENV_TEST_DIR: "venv_test" matrix: - # Python 2.7 (64) - - PYTHON_DIR: "C:\\Python27-x64" - PYTHON_VERSION: "2.7" + # Python 3.5 (64) + - PYTHON_DIR: "C:\\Python35-x64" + PYTHON_VERSION: "3.5" PYTHON_ARCH: "64" - CONDA_PY: "27" + CONDA_PY: "35" - # Python 2.7 - - PYTHON_DIR: "C:\\Python27" - PYTHON_VERSION: "2.7" + # Python 3.5 + - PYTHON_DIR: "C:\\Python35" + PYTHON_VERSION: "3.5" PYTHON_ARCH: "32" - CONDA_PY: "27" + CONDA_PY: "35" + + # Python 3.6 (64) + - PYTHON_DIR: "C:\\Python36-x64" + PYTHON_VERSION: "3.6" + PYTHON_ARCH: "64" + CONDA_PY: "36" + + # Python 3.6 + - PYTHON_DIR: "C:\\Python36" + PYTHON_VERSION: "3.6" + PYTHON_ARCH: "32" + CONDA_PY: "36" + + # Python 3.7 (64) + - PYTHON_DIR: "C:\\Python37-x64" + PYTHON_VERSION: "3.7" + PYTHON_ARCH: "64" + CONDA_PY: "37" + + # Python 3.7 + - PYTHON_DIR: "C:\\Python37" + PYTHON_VERSION: "3.7" + PYTHON_ARCH: "32" + CONDA_PY: "37" + + # Python 3.8 (64) + - PYTHON_DIR: "C:\\Python38-x64" + PYTHON_VERSION: "3.8" + PYTHON_ARCH: "64" + CONDA_PY: "38" + + # Python 3.8 + - PYTHON_DIR: "C:\\Python38" + PYTHON_VERSION: "3.8" + PYTHON_ARCH: "32" + CONDA_PY: "38" install: # Add Python to PATH - "SET PATH=%PYTHON_DIR%;%PYTHON_DIR%\\Scripts;%PATH%" # Upgrade/install distribution modules - - "pip install --upgrade setuptools" + - "pip3 install --upgrade setuptools" - "python -m pip install --upgrade pip" # Install virtualenv - - "pip install --upgrade virtualenv" + - "pip3 install --upgrade virtualenv" - "virtualenv --version" build_script: @@ -37,7 +73,7 @@ build_script: - "%VENV_BUILD_DIR%\\Scripts\\activate.bat" # Install wheel - - "pip install --upgrade wheel" + - "pip3 install --upgrade wheel" # Build sardana sdist, msi and wheel - "python setup.py sdist bdist_wheel bdist_msi" diff --git a/ci/github_deploy_key.enc b/ci/github_deploy_key.enc deleted file mode 100644 index ad82ffa5de..0000000000 --- a/ci/github_deploy_key.enc +++ /dev/null @@ -1 +0,0 @@ -gAAAAABbRIlov7GgCqYKPx7wnaU_-rtYRBbbucfQIPnjSsA7UUntuTcOiyzb2qEr7FCyUtZ9q2fWJf6NFG-c8RFJP1CfnviMDruCzAPPmBgJilSFbtC5sXBme8gesFUylVmjo-vUWHqgcSFmtwWv-lKef9Y1Uj1IHkw7ujEPEb5CzqbqeNYLX0b2vaFJzj1ySQoAAXv5_M6Y6It5kSg2ob3DJvql-w81PkyrnqpcQbEtnSiRgrUCIAQQDpJFy2f-NPWcMqYh9yLnjvwfEKO8S6eSy8SLk26MMtzFhQ6-BO8DZSSmMkzX9gZqcEdM89jpA_Js-Cg10bAc_78Hia45GTgpfTISYGOQFLhpGun1-a5wxhdVieAr0pg2VtQ7n0mjwWlH2g28uYJF-VYuE5nrEa1KLXRVkTENLcZJlRjVD_JhfgGr6zWJ_pcebixZq_cxcvXP8_uM8YottdqrzsRt1XdhmUxe9SbBfgxZRGfvYlV_7LNs_WGsURlxxogTMVrq1bAFOB3c6tDZpEznpR0Ytgz-GJLSJv9IutdSjmeLCMQOlyvsTN33DAJtuQGoiwyJmz0SLWTC7ZSyUCqP9Wcu8ytcCK6jVoA1lYSPnvyWxH6xmTac9T9KQngJTmqvni5fOb4LjWGU0ILfWAZijgg7YlKSrqlEsuJ3gOTx0q7vpG5Z6e7cXMtGFTR4t7IhSnyaCDv7ox9kKq3Xv7p4MM00M58XH25SaY0yPnGlMqn0HWZFgAXuPHqrSz2kxWEsL4q3YRtc944ZP89Mwi-HgcWBSTPwbDmvPlls-GxEu8QB5fT51oHNY4H0VGs7Tjp9xVjysZGa9x439a-mj8LsTPqrUFuHw9_EXOENL0iCiD9vqTAWodsnGX2nm3FVXAuRwwwzeSwagrtQ6aUP47pOaBmq9uKaZT6iEE8g8eSCBNnn7WwDrGy5C4Me3zMAuwjjoB0nlr60oSps7vF9Pi-89P0AN1DS15i5pWObQ-fqQ2aKaobRXrBIhFxL1eCijkNxVDfM1mkbz087J6qw2iQwzpkkjSawZ4rdyWrZyQeGjhiVfIT22R67KUvYrVEguzUA8Q3IgmvbKhkLSPX3FUQfiSM_rKdBy_iO02bJU4glsR_G1QM4RMOy0LIEOGdcaq36H4QCSX76FNekSFGLmi1b3NQXYH58QC8rPmqNTIRQ6c90ukL33AxuQ7-VPpLkyqhaOCR_WD-SVzPOcU4RiJ6h99_-BRcUG0-3pIswrool-FrJLVZdVFqkmRxDPhLutS_m0gpShNBZYUZAtvd7s1nBYzjcDg1Q_087r7PE6EeCMyuE2sngwlgAuA8Q2p7zxaWR-D8mkxYxvlKyXII4MADXGwLjP7S27uBWcRVAjLEvjZC12vVcKA7sB8XQWcpmAvXT3PX6bZAW94qygrAWZaSzY9hSmBt5p8OXeEzNv6rNDLlK40mFl5eILbwwJfKvfl9pwlKxU67NHSKk_FQBkEsHolpyQqEv5MBI0zoBXQp4GxXaa7oGfivqxhDikjAS7MhcKjVKtx1fyEOusBRsl_4DJMZf4Oe58ZjEeCFnN2icFFv7SVjt7MoWYiEDVvh3thabHJKMk3cjlYlFFmIsU4_2HZw5FEuTt0Be1cQ4hExpPp7qWfwveFjQEo5uAjCj-3yuAzcIEPIR3-zHLh5GrvFVzkjlaEyDxnmjSeNrZW4b92puM0LiU9mX4HtZMOVElLeMkGt104anXkPmb0r62zduLazKqqY0pJDwFCtpqz8ZZRJ8gCj2DVanjGUvwNA2fDrl5Ddo3HWgrY7xpt6J-i5EnYWDLfy5-oprR3ruYaUtmhHifnWr305Ok3Q_ufdQzzF8fStuZfM8F9pFFiKOmY1v3mpBazvUgZlWI3uGIWXMoC-A15eB7bnYstNK4i_XW8lG8J4pUzNvM5GpoHPgpsSR-vyonQ8c9igB3Rd3A7eOhbvdxde360F4PubKvMQdcBCq9mfjsng3h2rGE5y33LB3o9WuyojRQxA9elnIMwfttkwnbTjX1pNfPVq6cFso5mhz_PKHSpr9b8V31hHiLh6Pdd2ODJkM72gUfsAoBvdmOcgSweu4kWkzT3urHb0T4o6P9fAlad39ibLw0qrT_ZqRpXXllnXF5j2OU0-a74gZ7p-8fGUKxTrjS6ZXlaQSABj697j52m9BHI07hKY5p8fNA0G28vZHBh3UkbYT8aZzxQFFDVMn8DJKSDZ7_F9VWXk9WvpGGNU1CTxx-d6VdohDRyp0BKbC5Qo32DEwvBHH33nLkb8NX22ghaFDbH940VlzlLcUb8-a8WIcVrYpOf0PHW2vt0lRZHfuS6CriioKf27GnQvhaGjk1mz0dxbk4gSVW3iULUxBpJqgxJkAoz-F5J-NuQpSZu4qhcfV-BRVrsweVe8obsfmxSbCZVt3hekGfYdUXVKJ8dKLDuY9H93beDryU_1AUyQ6S-7V7J992bKEPRvHzaHVeUC4jV2ySHN02Pi10XbQKHGQeF6UsajKnapmOfSpswJ6jteVePciOSy28suwX2a3LFB-t8fhUiSkh_tftosUA5_GeHE5XD8fDZ7hOV3IgKfnCzFfuPmuJ6Q4p3SlLXXpqXio8_e6DIIv7FWN4rdAVVbJ_gBp8C3RcZliAMk20VdXbIuLLAAnG8VxAzlfc40qACEI1ENoc4EcEospcqjhYKlIqyyOthOpjmPJpmD888ByRz4QfN3JIUUowt2WwLOYEekM_qIlkG-FL0qNqaXM2UXqphOQrhbVv23vTMvd0ZAwF5capxx2LOLj-99LMozheoDUCCYWTou586AH_B7zKiRFvphTUwY36maORbcQQeaUtMaV0ud5hEhHhNo-80-mJlBBs0ZPSWBYCC1u6H0Dtq1GISIde_TYl0xRxqH24xYEpIIhrTzpibpJipAH4c06RQzf1tAn70Oq6YfyjJ_Y2aekx3tHY0CCcnAa5FI-aOhHSilKLEKIuUKJ0kx0WTBv7YVElN7yDFYnHZFVJBMNQ5KOmvAzBl1T-ISGrD8VlXg2c-vY3DN1IEvQDZ8WdWupuoEDaSByTx5MOx3taOo_zVGZYHkXDQ00oP3aTDsEPIqEGsAz7B9Y_uOvGQt2-T0WJxwDlTDPbGnuHnIYHq9U9c4RbVjLr0EOWm2_4kCZ63EX9z31gkwXfp5R97rSnpFpQG_LubMx33fXvz2mxhvTmSNJdAOgHNv6ygfSFsHumAkXl6HcjV0XTKOUhVd49QRNF3J32ILKL_X5jHJCMgWTZSOvN3TzOa6VToe_zB4ndYtnNwqDQUjrKYRAcP3Wg7t4yaAhhiOTmOO1B2EhKlemf6_El8k8aWYfZDS5VHHL0V6qUu5-KExQt6lqQZvSq2a5nmMfBC6KQlKo5jNzqGbVsaBznr32H6IG0cQU6M0jTbZUozFcCFi9vmXfcBaSmC-0ZUYicazGsU46OuJJFHi2F0_TT-N-p3epLHDbAU35ig1zxbV4prBLoPi8cUMIBqOaaQiC2eHxGVSuYy9s4MdnwkmnM3fwumQTFf4QCAaRE5l2Yg6PAh92uUJ9yB4cCYZgovsRhXq5vjHRk2auc0_gk9Mu_vGqqLnn0E5W01I9hJJnvYVng-sI77zRGr9CzjT-ZvqziNN1iBX1sjpKbi13vzxTt8dWUvPpK8o26bPdBFbbdbEL2kHoALkMl_CV2d72RDdzBG0auwfXVp2XpfWuyMDBgO8IYrUSDjFiW2puwctD__MMkBSiFlouxJo6gxrgbpsMiSdshGjqD3rajyqcGcSnBI0BlgbfME6-cZhRZLz708IV_ai02M2jMq3cioYfjl7cCSpoNgLR0vbrG6Sc5-SexhnGGMVwtq4ej95FnixkAfhykHSBbF-_-SAVCI2h6_grHfYOwIr7cm2QqXCj1PtZPS-bk-3huXKzUneCHDnKjUntlgBN7ntkH0d44fP1iguT_FgWVzJUKFB2-9GJ0k6I792ChiBIFqT0BxAbWCOeoN40EKS0Bh4djTsBXzAp9GAGmrnITBKsBDhHxfzCLlE3C2m1dyS8t7AtECFlxbWFm-4e9iYmuvwTyFGjPab98-G6XE28NL877r-Q9vhe8kx7R8HMF3Egp_slP2gLwhRL6CL_PTxQNfFSFXy7wJV1H3bOV8jWYgTmkFO-gcyk8QFzuLWFPZtiZAku3eY5eC7ta1a_BTJ3e8tdcad2UXm7jjw1eQzTrZSL8Ry5_ejvprgtioukZq29hWH9xv2tApQJHCKy2QlYTkZVBFAYLq6ScNky6y28YEzzR2lcMI3sEJX50JHILn8hf7snFQ1Ns8EQRVKTcxFCnKuUA-SAOBbQYfhpql2qC4sClLlb3ieMmxOmhLAaANtcKRS7_wOdRcG6HNeC4vMZBohlvZQ4pvNkeGe9PDNGQSKPMoeSJZ01S2a-49VHXGHc2IwrGLFfMxXWoLUy6w== \ No newline at end of file diff --git a/doc/how_to_release.md b/doc/how_to_release.md deleted file mode 100644 index 3879e82a8f..0000000000 --- a/doc/how_to_release.md +++ /dev/null @@ -1,173 +0,0 @@ -# How to release - -This is a guide for sardana release managers: it details the steps for making -an official release, including a checklist of stuff that should be manually -tested. - -## The release process - -1. During all the development, use the Milestones to keep track of the intended - release for each issue. -2. Previous to the release deadline, re-check the open issues/PR and update - the assignation issues/PR to the release milestone. Request feedback from - the devel community. -3. Work to close all the PR/issues remaining open in the milestone. This can - be either done in develop or in a release branch called `release-XXX` - (where `XXX` is the milestone name, e.g. `Jul17`). If a release branch is - used, `develop` is freed to continue with integrations that may not be - suitable for this release. On the other hand, it adds a bit more work - because the release-related PRs (which are done against the `release-XXX` - branch), may need to be also merged to develop. - Note: the `release-XXX` branch *can* live in the sardana-org repo or on a - personal fork (in which case you should do step 4.v **now** to allow other - integrators to push directly to it). -4. Create the release branch if it was not done already in the previous step - and: - 1. Review and update the CHANGELOG.md if necessary. See [this](http://keepachangelog.com). - 2. Bump version using `bumpversion && bumpversion release` - (use [semver](http://semver.org/) criteria to choose amongst `major`, - `minor` or `patch`. OPTIONAL: Sardana depends on Taurus, and the - required version of Taurus may need to be bumped as well. Taurus and - other dependencies are specified in: `setup.py` (requires list of - strings) and `src/sardana/requirements.py` (`__requires__` dictionary - and taurus.core value). - 3. The version numbers used in the man pages of the Sardana scripts are - bumped (you may use `taurus/doc/makeman` script executing it from the - doc directory e.g. `sardana/doc`) and committing the changes. - There is a known [problem with the spock version number](https://github.com/sardana-org/sardana/issues/518). - 4. In the code use version number instead of milestone in deprecation - warnings (if any) e.g. replace *Jul18* with *2.5.0*. - 5. Create a PR to merge the `release-XXX` against the **`master`** branch - of the sardana-org repo -5. Request reviews in the PR from at least one integrator from each - participating institute. The master branch is protected, so the reviews need - to be cleared (or dismissed with an explanation) before the release can be - merged. -6. Perform manual tests (see checklist below). You may use the CI artifacts - (e.g., from appveyor) and post the results in the comments of the PR. -7. Once all reviews are cleared, update the date of the release in the - CHANGELOG.md, merge the PR and tag in master. -8. Check that travis-ci correctly uploaded to PyPI (triggered by a tag push) - - Previously: - ~~Release to PyPI **from a clean checkout** and using [twine](https://github.com/pypa/twine):~~ - >``` - >cd /tmp - >git clone https://github.com/sardana-org/sardana.git -b - >cd sardana - >python setup.py sdist bdist_wheel - >twine upload dist/* - >``` -9. Merge also the `release-XXX` branch into develop, and bump the version of - develop with `bumpversion patch` -10. Complete GitHub release (upload artifacts, edit text) -11. Create news in www.tango-controls.org - 1. On the News page click on Submit a news and fill up the form (if it doesn't work, try opening in new tab): - * Title: New Release Of Sardana X.X.X (Jan|JulXX) - * Ilustration: sardana or official logo (use png) - * Summary: short summary of the news (do not include the whole changelog here..) - * Categories: Release - 2. After submitting click on Modify this content text of the area \<\\> and provide detailes of the release e.g. changelog. -12. Notify mailing lists (sardana-users@lists.sourceforge.net, sardana-devel@lists.sourceforge.net, info@tango-controls.org) -13. Close the milestone for current release, and open a new one (there should be milestones for next 2 releases open). - -## Manual test checklist - -This is a check-list of manual tests. It is just orientative. Expand it -at will. This list assumes a clean environment with all Sardana dependencies -already installed and access to a Tango system with the TangoTest DS running. - -Hint: this list can be used as a template to be copy-pasted on a release manual test issues - -### Installation -- [ ] Install Sardana (on Linux from the tar.gz : `pip install ` - and on Windows from MSI) - **Note:** On openSuse 11.1 there are problems with pip, try `python setup - .py install` - -### Create testing environment and run testsuite -- [ ] Start Pool demo2. In a console do `Pool demo2`. -- [ ] Start MacroServer demo2 and connect to the Pool demo2. - In another console do: `MacroServer demo2` -- [ ] Set MacroServer's MacroPath to point to the macro examples. - In another IPython console do: - `PyTango.DeviceProxy('macroserver/demo2/1').put_property({'MacroPath':'/macroserver/macros/examples'})` - **Note:** Remember to use OS path separator e.g. '/' on Linux and '\' on - Windows -- [ ] Restart MacroServer e.g. Ctrl+C in the MacroServer's console and - start it again. -- [ ] Create spock profile demo2. In another console do `spock --profile=demo2` -- [ ] In spock run `sar_demo` macro. -- [ ] Edit `/sardanacustomsettings.py` - to point to the demo2 door e.g. `UNITTEST_DOOR_NAME = "door/demo2/1"` -- [ ] Run testsuite. In another console do `sardanatestsuite` - **Note:** On openSuse 11.1 and Windows there are known problems with - testsuite. Check previous release comments. - -### Test Sardana using Spock and expconf -- [ ] Test interactive macros from spock e.g. `ask_for_moveable`, `ask_peak` - **Note**: On Windows there are known bugs. -- [ ] Execute `umvr` macro and verify that the position updates arrives. -- [ ] In expconf configure scan files by setting ScanDir to: `/tmp/` on Linux - `C:\Users\\tmp` on Windows and ScanFile to: `demo1.h5, demo1.dat`. -- [ ] Configure online plot to show counters: On expconf GUI select for all - the counter channels, Plot Type 'Spectrum' and Plot Axes '\' -- [ ] Configure snapshot group: with a motor and the `sys/tg_test/1/ampli` - attribute. -- [ ] Add the `sys/tg_test/1/double_scalar` attribute to the measurement - group. -- [ ] Open online plot (This should ask to enable JsonRecorder, set it to true. Otherwise enable it in spock: `senv JsonRecorder True`). -- [ ] Run step scan -- [ ] Verify that records appear in spock output. -- [ ] Verify that records were stored in scan files. -- [ ] Verify that records were plotted on the online plot -- [ ] Run `showscan` and access to the last scan data. -- [ ] With `edmac` modify existing macro: `ask_peak` and run it to verify that the change - was applied. -- [ ] With `edmac` create a new macro in a new macro library: - `edmac my_macro /macroserver/macros/examples/my_macros.py` - and run it. - **Note:** Remember to use OS path separator e.g. '/' on Linux and '\' on - Windows - -### Test Sardana with TaurusGUI - -- [ ] Create the GUI using this [guide](https://sourceforge.net/p/sardana/wiki/Howto-GUI_creation) - -#### PMTV (PoolMotorTaurusValue) -- [ ] Move motors from the slit panel in absolute and relative modes. -- [ ] Show expert view. -- [ ] Show compact mode. - -#### macroexecutor -- [ ] Execute `ascan` macro -- [ ] Pause it in the middel and resume -- [ ] Abort it -- [ ] Add it to favorites -- [ ] Run `lsm` macro -- [ ] Execute `ascan` from favorites -- [ ] Run `lsmac` macro -- [ ] Execute `ascan` from history -- [ ] Edit `dscan` macro in spock yellow line and run it -- [ ] Restart macroexecutor application -- [ ] Run `lsm` from history -- [ ] Run `ascan` from favorites - -#### sequencer -- [ ] Add `ascan` macro to the sequence -- [ ] Add `lsct` macro as a `post-acq` hook of `ascan` -- [ ] Add `dscan` macro to the sequence -- [ ] Run the sequence -- [ ] Save sequence to a file -- [ ] Start new sequence -- [ ] Load sequence from a files -- [ ] Run the loaded sequence - -#### sardanaeditor -**Note:** There are known bugs on CentOS and Windows -- [ ] Open sardanaeditor with macroserver name as argument. -- [ ] Browse macro libraries and open an existing macro. -- [ ] Edit existing macro and save & apply chaneges. -- [ ] Execute macro to see if changes were aplied. -- [ ] Create a new macro using template. -- [ ] Execute the newly created macro. diff --git a/doc/man/MacroServer.1 b/doc/man/MacroServer.1 index f54a4ec40f..3c76869d9f 100644 --- a/doc/man/MacroServer.1 +++ b/doc/man/MacroServer.1 @@ -1,38 +1,18 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH MACROSERVER "1" "November 2019" "MacroServer 2.8.6" "User Commands" +.TH MacroServer "1" .SH NAME -MacroServer \- manual page for MacroServer 2.8.6 +MacroServer \- Sardana MacroServer Tango device server .SH SYNOPSIS -.B usage: -\fI\,MacroServer instance_name \/\fR[\fI\,options\/\fR] -.SH OPTIONS -.TP -\fB\-\-version\fR -show program's version number and exit -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR -log output level. Possible values are (case -sensitive): critical (or 0), error (1), warning (2), -info (3) debug (4), trace (5) [default: warning] -.TP -\fB\-\-log\-file\-level\fR=\fI\,LOG_FILE_LEVEL\/\fR -log file level. Possible values are (case sensitive): -critical (or 0), error (1), warning (2), info (3) -debug (4), trace (5) [default: debug]. Ignored if -\fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-log\-file\-name\fR=\fI\,LOG_FILE_NAME\/\fR -log file name. When given, MUST be absolute file name. -[default: /tmp/tango///log.txt]. Ignored if \fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-without\-log\-file\fR=\fI\,WITHOUT_LOG_FILE\/\fR -When set to True disables logging into a file -[default: False] -.TP -\fB\-\-rconsole\-port\fR=\fI\,RCONSOLE_PORT\/\fR -rconsole port number. [default: 0 meaning rconsole NOT -active] +.B MacroServer +\fI\,[instance_name] \/ +.SH DESCRIPTION +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBMacroServer\fP command launches the Sardana MacroServer Tango +device server. + +The best source for information about the available options is +to run `MacroServer --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. \ No newline at end of file diff --git a/doc/man/Pool.1 b/doc/man/Pool.1 index fff1ca3890..1c5272c8ad 100644 --- a/doc/man/Pool.1 +++ b/doc/man/Pool.1 @@ -1,38 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH POOL "1" "November 2019" "Pool 2.8.6" "User Commands" +.TH POOL "1" .SH NAME -Pool \- manual page for Pool 2.8.6 +Pool \- Sardana Pool Tango device server .SH SYNOPSIS -.B usage: -\fI\,Pool instance_name \/\fR[\fI\,options\/\fR] -.SH OPTIONS -.TP -\fB\-\-version\fR -show program's version number and exit -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR -log output level. Possible values are (case -sensitive): critical (or 0), error (1), warning (2), -info (3) debug (4), trace (5) [default: warning] -.TP -\fB\-\-log\-file\-level\fR=\fI\,LOG_FILE_LEVEL\/\fR -log file level. Possible values are (case sensitive): -critical (or 0), error (1), warning (2), info (3) -debug (4), trace (5) [default: debug]. Ignored if -\fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-log\-file\-name\fR=\fI\,LOG_FILE_NAME\/\fR -log file name. When given, MUST be absolute file name. -[default: /tmp/tango///log.txt]. Ignored if \fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-without\-log\-file\fR=\fI\,WITHOUT_LOG_FILE\/\fR -When set to True disables logging into a file -[default: False] -.TP -\fB\-\-rconsole\-port\fR=\fI\,RCONSOLE_PORT\/\fR -rconsole port number. [default: 0 meaning rconsole NOT -active] +.B Pool +\fI\,[instance_name] \/ +.SH DESCRIPTION +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBPool\fP command launches the Sardana Pool Tango device server. + +The best source for information about the available options is +to run `Pool --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/Sardana.1 b/doc/man/Sardana.1 index d8fa6476c1..c42bc10658 100644 --- a/doc/man/Sardana.1 +++ b/doc/man/Sardana.1 @@ -1,38 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH SARDANA "1" "November 2019" "Sardana 2.8.6" "User Commands" +.TH SARDANA "1" .SH NAME -Sardana \- manual page for Sardana 2.8.6 +Sardana \- Sardana Tango device server .SH SYNOPSIS -.B usage: -\fI\,Sardana instance_name \/\fR[\fI\,options\/\fR] -.SH OPTIONS -.TP -\fB\-\-version\fR -show program's version number and exit -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR -log output level. Possible values are (case -sensitive): critical (or 0), error (1), warning (2), -info (3) debug (4), trace (5) [default: warning] -.TP -\fB\-\-log\-file\-level\fR=\fI\,LOG_FILE_LEVEL\/\fR -log file level. Possible values are (case sensitive): -critical (or 0), error (1), warning (2), info (3) -debug (4), trace (5) [default: debug]. Ignored if -\fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-log\-file\-name\fR=\fI\,LOG_FILE_NAME\/\fR -log file name. When given, MUST be absolute file name. -[default: /tmp/tango///log.txt]. Ignored if \fB\-\-without\-log\-file\fR is True -.TP -\fB\-\-without\-log\-file\fR=\fI\,WITHOUT_LOG_FILE\/\fR -When set to True disables logging into a file -[default: False] -.TP -\fB\-\-rconsole\-port\fR=\fI\,RCONSOLE_PORT\/\fR -rconsole port number. [default: 0 meaning rconsole NOT -active] +.B Sardana +\fI\,[instance_name] \/ +.SH DESCRIPTION +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBSardana\fP command launches the Sardana Tango device server. + +The best source for information about the available options is +to run `Sardana --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/diffractometeralignment.1 b/doc/man/diffractometeralignment.1 index 66137bfd80..1967c24e90 100644 --- a/doc/man/diffractometeralignment.1 +++ b/doc/man/diffractometeralignment.1 @@ -1,43 +1,18 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH DIFFRACTOMETERALIGNMENT "1" "November 2019" "diffractometeralignment 2.8.6" "User Commands" +.TH DIFFRACTOMETERALIGNMENT "1" .SH NAME -diffractometeralignment \- manual page for diffractometeralignment 2.8.6 +diffractometeralignment \- Sardana diffractometer alignment GUI .SH SYNOPSIS .B diffractometeralignment \fI\, \/\fR[\fI\,door_name\/\fR] .SH DESCRIPTION -a taurus application for diffractometer alignment: h, k, l movements and -scans, go to maximum, ... -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-version\fR -show program's version number and exit -.IP -Taurus Options: -.IP -Basic options present in any taurus application -.TP -\fB\-\-taurus\-log\-level\fR=\fI\,LEVEL\/\fR -taurus log level. Allowed values are (case -insensitive): critical, error, warning/warn, info, -debug, trace -.TP -\fB\-\-taurus\-polling\-period\fR=\fI\,MILLISEC\/\fR -taurus global polling period in milliseconds -.TP -\fB\-\-taurus\-serialization\-mode\fR=\fI\,SERIAL\/\fR -taurus serialization mode. Allowed values are (case -insensitive): serial, concurrent (default) -.TP -\fB\-\-tango\-host\fR=\fI\,TANGO_HOST\/\fR -Tango host name (either HOST:PORT or a Taurus URI, -e.g. tango://foo:1234) -.TP -\fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR -enables remote debugging using the given port -.TP -\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR -Override the default formatter +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBdiffractometeralignment\fP command is the GUI application +for diffractometer alignment: h, k, l movements and scans, go to maximum, ... + +The best source for information about the available options is +to run `diffractometeralignment --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/hklscan.1 b/doc/man/hklscan.1 index 5efa3f9121..50d5f4a0d8 100644 --- a/doc/man/hklscan.1 +++ b/doc/man/hklscan.1 @@ -1,42 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH HKLSCAN "1" "November 2019" "hklscan 2.8.6" "User Commands" +.TH HKLSCAN "1" .SH NAME -hklscan \- manual page for hklscan 2.8.6 +hklscan \- Sardana HKL scan GUI .SH SYNOPSIS .B hklscan \fI\, \/\fR[\fI\,door_name\/\fR] .SH DESCRIPTION -a taurus application for performing hkl scans -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-version\fR -show program's version number and exit -.IP -Taurus Options: -.IP -Basic options present in any taurus application -.TP -\fB\-\-taurus\-log\-level\fR=\fI\,LEVEL\/\fR -taurus log level. Allowed values are (case -insensitive): critical, error, warning/warn, info, -debug, trace -.TP -\fB\-\-taurus\-polling\-period\fR=\fI\,MILLISEC\/\fR -taurus global polling period in milliseconds -.TP -\fB\-\-taurus\-serialization\-mode\fR=\fI\,SERIAL\/\fR -taurus serialization mode. Allowed values are (case -insensitive): serial, concurrent (default) -.TP -\fB\-\-tango\-host\fR=\fI\,TANGO_HOST\/\fR -Tango host name (either HOST:PORT or a Taurus URI, -e.g. tango://foo:1234) -.TP -\fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR -enables remote debugging using the given port -.TP -\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR -Override the default formatter +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBhklscan\fP command is the GUI application for performing hkl scans. + +The best source for information about the available options is +to run `hklscan --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. \ No newline at end of file diff --git a/doc/man/macroexecutor.1 b/doc/man/macroexecutor.1 index 5b8e3d131d..12e6f5e3f0 100644 --- a/doc/man/macroexecutor.1 +++ b/doc/man/macroexecutor.1 @@ -1,40 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH MACROEXECUTOR "1" "November 2019" "macroexecutor 2.8.6" "User Commands" +.TH MACROEXECUTOR "1" .SH NAME -macroexecutor \- manual page for macroexecutor 2.8.6 +macroexecutor \- Sardana GUI application for executing macros .SH SYNOPSIS .B macroexecutor -[\fI\,options\/\fR] -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-version\fR -show program's version number and exit -.IP -Taurus Options: -.IP -Basic options present in any taurus application -.TP -\fB\-\-taurus\-log\-level\fR=\fI\,LEVEL\/\fR -taurus log level. Allowed values are (case -insensitive): critical, error, warning/warn, info, -debug, trace -.TP -\fB\-\-taurus\-polling\-period\fR=\fI\,MILLISEC\/\fR -taurus global polling period in milliseconds -.TP -\fB\-\-taurus\-serialization\-mode\fR=\fI\,SERIAL\/\fR -taurus serialization mode. Allowed values are (case -insensitive): serial, concurrent (default) -.TP -\fB\-\-tango\-host\fR=\fI\,TANGO_HOST\/\fR -Tango host name (either HOST:PORT or a Taurus URI, -e.g. tango://foo:1234) -.TP -\fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR -enables remote debugging using the given port -.TP -\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR -Override the default formatter +\fI\, \/\fR[\fI\,door_name\/\fR] +.SH DESCRIPTION +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBmacroexecutor\fP command is the GUI application for executing macros. + +The best source for information about the available options is +to run `macroexecutor --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/sardanatestsuite.1 b/doc/man/sardanatestsuite.1 index 3c099f16b1..9777120f26 100644 --- a/doc/man/sardanatestsuite.1 +++ b/doc/man/sardanatestsuite.1 @@ -1,19 +1,16 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH SARDANATESTSUITE "1" "November 2019" "sardanatestsuite 2.8.6" "User Commands" +.TH SARDANATESTSUITE "1" .SH NAME -sardanatestsuite \- manual page for sardanatestsuite 2.8.6 +sardanatestsuite \- Sardana test suite +.SH SYNOPSIS +.B sardanatestsuite .SH DESCRIPTION -usage: sardanatestsuite [\-h] [\-e EXCLUDE_PATTERN] [\-\-version] -.PP -Main test suite for Sardana -.SS "optional arguments:" -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-e\fR EXCLUDE_PATTERN, \fB\-\-exclude\-pattern\fR EXCLUDE_PATTERN -regexp pattern matching test ids to be excluded. (e.g. -\&'sardana\e.pool\e..*' would exclude sardana.pool tests) -.TP -\fB\-\-version\fR -show program's version number and exit +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBsardanatestsuite\fP command launches the Sardana test suite. + +The best source for information about the available options is +to run `sardanatestsuite --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/sequencer.1 b/doc/man/sequencer.1 index b222d4bdab..2e5dd048ac 100644 --- a/doc/man/sequencer.1 +++ b/doc/man/sequencer.1 @@ -1,46 +1,19 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH SEQUENCER "1" "November 2019" "sequencer 2.8.6" "User Commands" +.TH SEQUENCER "1" .SH NAME -sequencer \- manual page for sequencer 2.8.6 +sequencer \- Sardana GUI application for executing macro sequences .SH SYNOPSIS .B sequencer -[\fI\,options\/\fR] +\fI\, \/\fR[\fI\,door_name\/\fR] .SH DESCRIPTION -Sardana macro sequencer. It allows the creation of sequences of macros, -executed one after the other. The sequences can be stored under xml files -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-f\fR FILE, \fB\-\-file\fR=\fI\,FILE\/\fR -load macro sequence from a file(XML or spock syntax) -.TP -\fB\-\-version\fR -show program's version number and exit -.IP -Taurus Options: -.IP -Basic options present in any taurus application -.TP -\fB\-\-taurus\-log\-level\fR=\fI\,LEVEL\/\fR -taurus log level. Allowed values are (case -insensitive): critical, error, warning/warn, info, -debug, trace -.TP -\fB\-\-taurus\-polling\-period\fR=\fI\,MILLISEC\/\fR -taurus global polling period in milliseconds -.TP -\fB\-\-taurus\-serialization\-mode\fR=\fI\,SERIAL\/\fR -taurus serialization mode. Allowed values are (case -insensitive): serial, concurrent (default) -.TP -\fB\-\-tango\-host\fR=\fI\,TANGO_HOST\/\fR -Tango host name (either HOST:PORT or a Taurus URI, -e.g. tango://foo:1234) -.TP -\fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR -enables remote debugging using the given port -.TP -\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR -Override the default formatter +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBsequencer\fP command is the GUI application for executing macro +sequences. It allows creation of sequences of macros, +executed one after the other. The sequences can be stored under xml files. + +The best source for information about the available options is +to run `sequencer --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/spock.1 b/doc/man/spock.1 index 9b8f767e1e..a95b5b2d15 100644 --- a/doc/man/spock.1 +++ b/doc/man/spock.1 @@ -1,371 +1,16 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH SPOCK "1" "November 2019" "spock 2.8.6" "User Commands" +.TH SPOCK "1" .SH NAME -spock \- manual page for spock 2.8.6 +spock \- Sardana CLI application +.SH SYNOPSIS +.B spock .SH DESCRIPTION -========= -.IP -IPython -.PP -========= -.PP -Tools for Interactive Computing in Python -========================================= -.IP -A Python shell with automatic history (input and output), dynamic object -introspection, easier configuration, command completion, access to the -system shell and more. IPython can also be embedded in running programs. -.PP -Usage -.IP -ipython [subcommand] [options] [\-c cmd | \fB\-m\fR mod | file] [\-\-] [arg] ... -.IP -If invoked with no options, it executes the file and exits, passing the -remaining arguments to the script, just as if you had specified the same -command with python. You may need to specify `\-\-` before args to be passed -to the script, to prevent IPython from attempting to parse them. If you -specify the option `\-i` before the filename, it will enter an interactive -IPython session after running the script, rather than exiting. Files ending -in .py will be treated as normal Python, but files ending in .ipy can -contain special IPython syntax (magic commands, shell expansions, etc.). -.IP -Almost all configuration in IPython is available via the command\-line. Do -`ipython \fB\-\-help\-all\fR` to see all available options. For persistent -configuration, look into your `ipython_config.py` configuration file for -details. -.IP -This file is typically installed in the `IPYTHONDIR` directory, and there -is a separate configuration directory for each profile. The default profile -directory will be located in \fI\,$IPYTHONDIR/profile_default\/\fP. IPYTHONDIR -defaults to to `$HOME/.ipython`. For Windows users, $HOME resolves to -C:\eUsers\eYourUserName in most instances. -.IP -To initialize a profile with the default configuration file, do:: -.IP -$> ipython profile create -.IP -and start editing `IPYTHONDIR/profile_default/ipython_config.py` -.IP -In IPython's documentation, we will refer to this directory as -`IPYTHONDIR`, you can change its default location by creating an -environment variable with this name and setting it to the desired path. -.IP -For more information, see the manual available in HTML and PDF in your -installation, or online at http://ipython.org/documentation.html. -.PP -Subcommands -\fB\-\-\-\-\-\-\-\-\-\-\-\fR -.PP -Subcommands are launched as `ipython cmd [args]`. For information on using -subcommand 'cmd', do: `ipython cmd \fB\-h\fR`. -.PP -locate -.IP -print the path to the IPython dir -.PP -kernel -.IP -Start a kernel without an attached frontend. -.PP -console -.IP -DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter terminal\-based Console. -.PP -install\-nbextension -.IP -DEPRECATED, Will be removed in IPython 6.0 : Install Jupyter notebook extension files -.PP -kernelspec -.IP -DEPRECATED, Will be removed in IPython 6.0 : Manage Jupyter kernel specifications. -.PP -notebook -.IP -DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter HTML Notebook Server. -.PP -profile -.IP -Create and manage IPython profiles. -.PP -qtconsole -.IP -DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter Qt Console. -.PP -nbconvert -.IP -DEPRECATED, Will be removed in IPython 6.0 : Convert notebooks to/from other formats. -.PP -trust -.IP -DEPRECATED, Will be removed in IPython 6.0 : Sign notebooks to trust their potentially unsafe contents at load. -.PP -history -.IP -Manage the IPython history database. -.PP -Options -\fB\-\-\-\-\-\-\-\fR -.PP -Arguments that take values are actually convenience aliases to full -Configurables, whose aliases are listed on the help line. For more information -on full configurables, see '\-\-help\-all'. -.PP -\fB\-\-no\-autoindent\fR -.IP -Turn off autoindenting. -.PP -\fB\-\-autoedit\-syntax\fR -.IP -Turn on auto editing of files with syntax errors. -.PP -\fB\-\-pylab\fR -.IP -Pre\-load matplotlib and numpy for interactive use with -the default matplotlib backend. -.PP -\fB\-\-simple\-prompt\fR -.IP -Force simple minimal prompt using `raw_input` -.PP -\fB\-\-confirm\-exit\fR -.IP -Set to confirm when you try to exit IPython with an EOF (Control\-D -in Unix, Control\-Z/Enter in Windows). By typing 'exit' or 'quit', -you can force a direct exit without any confirmation. -.PP -\fB\-\-no\-autoedit\-syntax\fR -.IP -Turn off auto editing of files with syntax errors. -.PP -\fB\-\-matplotlib\fR -.IP -Configure matplotlib for interactive use with -the default matplotlib backend. -.PP -\fB\-\-term\-title\fR -.IP -Enable auto setting the terminal title. -.PP -\fB\-\-no\-confirm\-exit\fR -.IP -Don't prompt the user when exiting. -.PP -\fB\-\-autoindent\fR -.IP -Turn on autoindenting. -.PP -\fB\-\-no\-automagic\fR -.IP -Turn off the auto calling of magic commands. -.PP -\fB\-\-no\-simple\-prompt\fR -.IP -Use a rich interactive prompt with prompt_toolkit -.PP -\fB\-\-banner\fR -.IP -Display a banner upon starting IPython. -.PP -\fB\-\-automagic\fR -.IP -Turn on the auto calling of magic commands. Type %%magic at the -IPython prompt for more information. -.PP -\fB\-\-no\-term\-title\fR -.IP -Disable auto setting the terminal title. -.PP -\fB\-\-nosep\fR -.IP -Eliminate all spacing between prompts. -.PP -\fB\-i\fR -.IP -If running code from the command line, become interactive afterwards. -It is often useful to follow this with `\-\-` to treat remaining flags as -script arguments. -.PP -\fB\-\-debug\fR -.IP -set log level to logging.DEBUG (maximize logging output) -.PP -\fB\-\-classic\fR -.IP -Gives IPython a similar feel to the classic Python prompt. -.PP -\fB\-\-quiet\fR -.IP -set log level to logging.CRITICAL (minimize logging output) -.PP -\fB\-\-pprint\fR -.IP -Enable auto pretty printing of results. -.PP -\fB\-\-pdb\fR -.IP -Enable auto calling the pdb debugger after every exception. -.PP -\fB\-\-color\-info\fR -.IP -IPython can display information about objects via a set of functions, -and optionally can use colors for this, syntax highlighting -source code and various other elements. This is on by default, but can cause -problems with some pagers. If you see such problems, you can disable the -colours. -.PP -\fB\-\-init\fR -.TP -Initialize profile with default config files. -This is equivalent -.IP -to running `ipython profile create ` prior to startup. -.PP -\fB\-\-no\-pdb\fR -.IP -Disable auto calling the pdb debugger after every exception. -.PP -\fB\-\-quick\fR -.IP -Enable quick startup with no config files. -.PP -\fB\-\-no\-color\-info\fR -.IP -Disable using colors for info related things. -.PP -\fB\-\-no\-pprint\fR -.IP -Disable auto pretty printing of results. -.PP -\fB\-\-no\-banner\fR -.IP -Don't display a banner upon starting IPython. -.PP -\fB\-\-profile=\fR (BaseIPythonApplication.profile) -.IP -Default: u'default' -The IPython profile to use. -.PP -\fB\-\-pylab=\fR (InteractiveShellApp.pylab) -.IP -Default: None -Choices: [u'auto', u'gtk', u'gtk3', u'inline', u'nbagg', u'notebook', u'osx', u'qt', u'qt4', u'qt5', u'tk', u'wx'] -Pre\-load matplotlib and numpy for interactive use, selecting a particular -matplotlib backend and loop integration. -.PP -\fB\-\-matplotlib=\fR (InteractiveShellApp.matplotlib) -.IP -Default: None -Choices: [u'auto', u'gtk', u'gtk3', u'inline', u'nbagg', u'notebook', u'osx', u'qt', u'qt4', u'qt5', u'tk', u'wx'] -Configure matplotlib for interactive use with the default matplotlib -backend. -.PP -\fB\-\-colors=\fR (InteractiveShell.colors) -.IP -Default: 'Neutral' -Choices: [u'Neutral', u'NoColor', u'LightBG', u'Linux'] -Set the color scheme (NoColor, Neutral, Linux, or LightBG). -.PP -\fB\-\-cache\-size=\fR (InteractiveShell.cache_size) -.IP -Default: 1000 -Set the size of the output cache. The default is 1000, you can change it -permanently in your config file. Setting it to 0 completely disables the -caching system, and the minimum value accepted is 20 (if you provide a value -less than 20, it is reset to 0 and a warning is issued). This limit is -defined because otherwise you'll spend more time re\-flushing a too small -cache than working -.PP -\fB\-\-logfile=\fR (InteractiveShell.logfile) -.IP -Default: '' -The name of the logfile to use. -.PP -\fB\-\-profile\-dir=\fR (ProfileDir.location) -.IP -Default: u'' -Set the profile location directly. This overrides the logic used by the -`profile` option. -.PP -\fB\-c\fR (InteractiveShellApp.code_to_run) -.IP -Default: '' -Execute the given command string. -.PP -\fB\-\-autocall=\fR (InteractiveShell.autocall) -.IP -Default: 0 -Choices: (0, 1, 2) -Make IPython automatically call any callable object even if you didn't type -explicit parentheses. For example, 'str 43' becomes 'str(43)' automatically. -The value can be '0' to disable the feature, '1' for 'smart' autocall, where -it is not applied if there are no more arguments on the line, and '2' for -\&'full' autocall, where all callable objects are automatically called (even -if no arguments are present). -.PP -\fB\-\-ipython\-dir=\fR (BaseIPythonApplication.ipython_dir) -.IP -Default: u'' -The name of the IPython directory. This directory is used for logging -configuration (through profiles), history storage, etc. The default is -usually $HOME/.ipython. This option can also be specified through the -environment variable IPYTHONDIR. -.PP -\fB\-\-gui=\fR (InteractiveShellApp.gui) -.IP -Default: None -Choices: [u'glut', u'gtk', u'gtk2', u'gtk3', u'osx', u'pyglet', u'qt', u'qt4', u'qt5', u'tk', u'wx', u'gtk2', u'qt4'] -Enable GUI event loop integration with any of ('glut', 'gtk', 'gtk2', -\&'gtk3', 'osx', 'pyglet', 'qt', 'qt4', 'qt5', 'tk', 'wx', 'gtk2', 'qt4'). -.PP -\fB\-\-logappend=\fR (InteractiveShell.logappend) -.IP -Default: '' -Start logging to the given file in append mode. Use `logfile` to specify a -log file to **overwrite** logs to. -.PP -\fB\-m\fR (InteractiveShellApp.module_to_run) -.IP -Default: '' -Run the module as a script. -.PP -\fB\-\-log\-level=\fR (Application.log_level) -.IP -Default: 30 -Choices: (0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL') -Set the log level by value or name. -.PP -\fB\-\-ext=\fR (InteractiveShellApp.extra_extension) -.IP -Default: '' -dotted module name of an IPython extension to load. -.PP -\fB\-\-config=\fR (BaseIPythonApplication.extra_config_file) -.IP -Default: u'' -Path to an extra config file to load. -If specified, load this config file in addition to any other IPython config. -.PP -To see all available configurables, use `\-\-help\-all` -.PP -Examples -\fB\-\-\-\-\-\-\-\-\fR -.TP -ipython \fB\-\-matplotlib\fR -# enable matplotlib integration -.TP -ipython \fB\-\-matplotlib\fR=\fI\,qt\/\fR -# enable matplotlib integration with qt4 backend -.TP -ipython \fB\-\-log\-level\fR=\fI\,DEBUG\/\fR -# set logging to DEBUG -.TP -ipython \fB\-\-profile\fR=\fI\,foo\/\fR -# start with profile foo -.IP -ipython profile create foo # create profile foo w/ default config files -ipython help profile # show the help for the profile subcmd -.TP -ipython locate -# print the path to the IPython directory -.IP -ipython locate profile foo # print the path to the directory for profile `foo` +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBspock\fP command launches the Sardana CLI application based on IPython. + +The best source for information about the available options is +to visit the Spock documentation: http://sardana-controls.org/users/spock.html. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/man/ubmatrix.1 b/doc/man/ubmatrix.1 index cfe5f66f95..393a976323 100644 --- a/doc/man/ubmatrix.1 +++ b/doc/man/ubmatrix.1 @@ -1,43 +1,18 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH UBMATRIX "1" "November 2019" "ubmatrix 2.8.6" "User Commands" +.TH UBMATRIX "1" .SH NAME -ubmatrix \- manual page for ubmatrix 2.8.6 +ubmatrix \- Sardana GUI application for setting diffractomer parameters .SH SYNOPSIS .B ubmatrix -\fI\,\/\fR +\fI\, \/\fR[\fI\,door_name\/\fR] .SH DESCRIPTION -a taurus application for setting diffractometer parameters: ubmatrix, lattice, -reflections, ... -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-\-version\fR -show program's version number and exit -.IP -Taurus Options: -.IP -Basic options present in any taurus application -.TP -\fB\-\-taurus\-log\-level\fR=\fI\,LEVEL\/\fR -taurus log level. Allowed values are (case -insensitive): critical, error, warning/warn, info, -debug, trace -.TP -\fB\-\-taurus\-polling\-period\fR=\fI\,MILLISEC\/\fR -taurus global polling period in milliseconds -.TP -\fB\-\-taurus\-serialization\-mode\fR=\fI\,SERIAL\/\fR -taurus serialization mode. Allowed values are (case -insensitive): serial, concurrent (default) -.TP -\fB\-\-tango\-host\fR=\fI\,TANGO_HOST\/\fR -Tango host name (either HOST:PORT or a Taurus URI, -e.g. tango://foo:1234) -.TP -\fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR -enables remote debugging using the given port -.TP -\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR -Override the default formatter +Sardana is a software suite for Supervision, Control and Data Acquisition +in scientific installations. + +The \fBubmatrix\fP command is the GUI application for setting diffractometer +parameters: ubmatrix, lattice, reflections, ... + +The best source for information about the available options is +to run `ubmatrix --help`. + +For more information about the Sardana project, visit +http://sardana-controls.org. diff --git a/doc/source/_static/pctv.png b/doc/source/_static/pctv.png new file mode 100644 index 0000000000..3589205dba Binary files /dev/null and b/doc/source/_static/pctv.png differ diff --git a/doc/source/_static/pctv_attr_editor.png b/doc/source/_static/pctv_attr_editor.png new file mode 100644 index 0000000000..490e9b00cc Binary files /dev/null and b/doc/source/_static/pctv_attr_editor.png differ diff --git a/doc/source/_static/pmtv.png b/doc/source/_static/pmtv.png index 42e2d9b940..b358e4111f 100644 Binary files a/doc/source/_static/pmtv.png and b/doc/source/_static/pmtv.png differ diff --git a/doc/source/_static/qtspock.png b/doc/source/_static/qtspock.png new file mode 100644 index 0000000000..638fc8f210 Binary files /dev/null and b/doc/source/_static/qtspock.png differ diff --git a/doc/source/_static/showscan-online-multi.png b/doc/source/_static/showscan-online-multi.png new file mode 100644 index 0000000000..96a3bdfe43 Binary files /dev/null and b/doc/source/_static/showscan-online-multi.png differ diff --git a/doc/source/_static/showscan-online.png b/doc/source/_static/showscan-online.png index cf25cf9c1b..5b559d8e66 100644 Binary files a/doc/source/_static/showscan-online.png and b/doc/source/_static/showscan-online.png differ diff --git a/doc/source/conf.py b/doc/source/conf.py index b2aea89fb3..9392b1c373 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -99,9 +99,9 @@ def type_getattr(self, name): master_doc = 'index' # General information about the project. -project = u'sardana' -copyright = u'2012, ALBA - CELLS, Creative Commons Attribution-Share Alike 3.0' -copyright = u"""Except where otherwise noted, content on this site is +project = 'sardana' +copyright = '2012, ALBA - CELLS, Creative Commons Attribution-Share Alike 3.0' +copyright = """Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 3.0 License""" # Ideally we would like to put the following html code for copyright... @@ -135,7 +135,7 @@ def type_getattr(self, name): exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None +default_role = "obj" # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True @@ -243,8 +243,8 @@ def type_getattr(self, name): # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'sardana.tex', u'Sardana Documentation', - u'Sardana team', 'manual'), + ('index', 'sardana.tex', 'Sardana Documentation', + 'Sardana team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -289,13 +289,23 @@ def type_getattr(self, name): # -- Options for reference to other documentation ------------------------ intersphinx_mapping = { - 'https://docs.python.org/dev': None, - 'https://docs.scipy.org/doc/scipy/reference': None, - 'https://docs.scipy.org/doc/numpy': None, - 'http://ipython.org/ipython-doc/stable/': None, - 'http://pytango.readthedocs.io/en/stable/': None, - 'http://taurus-scada.org': None, - 'http://pyqt.sourceforge.net/Docs/PyQt4/': None, - 'https://matplotlib.org/': None, - 'https://pythonhosted.org/guiqwt/': None, + 'python': ('https://docs.python.org/3', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'ipython': ('http://ipython.org/ipython-doc/stable/', None), + 'pytango': ('https://pytango.readthedocs.io/en/stable/', None), + 'taurus': ('http://taurus-scada.org', None), + 'pyqt': ('https://www.riverbankcomputing.com/static/Docs/PyQt5/', None), + 'matplotlib': ('https://matplotlib.org/', None), + 'guiqwt': ('https://guiqwt.readthedocs.io/en/latest/', None), } + + +def _skip_read_attr_hardware(app, what, name, obj, skip, options): + if what == "class" and name == "read_attr_hardware": + return True + + +def setup(app): + # due to tango-controls/pytango#352 we need to exclude read_attr_hardware + app.connect('autodoc-skip-member', _skip_read_attr_hardware) diff --git a/doc/source/devel/api/api_measurementgroup.rst b/doc/source/devel/api/api_measurementgroup.rst index f1355afbb1..aa6cc743c5 100644 --- a/doc/source/devel/api/api_measurementgroup.rst +++ b/doc/source/devel/api/api_measurementgroup.rst @@ -42,7 +42,7 @@ latency time Latency time between two consecutive acquisitions in the same acquisition operation. -synchronization +synch description Describes the acquisition operation synchronization. It is composed from the group(s) of equidistant acquisitions described by the following parameters: diff --git a/doc/source/devel/api/api_sardana.rst b/doc/source/devel/api/api_sardana.rst index 77c8256348..14a418c31c 100644 --- a/doc/source/devel/api/api_sardana.rst +++ b/doc/source/devel/api/api_sardana.rst @@ -9,6 +9,8 @@ Sardana API Macro API Controller API + Sardana-Taurus model API + Sardana-Taurus Qt model API Motor API I/O register Counter/timer API diff --git a/doc/source/devel/api/api_test.rst b/doc/source/devel/api/api_test.rst index 9b6f677f8f..af9b63c182 100644 --- a/doc/source/devel/api/api_test.rst +++ b/doc/source/devel/api/api_test.rst @@ -24,9 +24,7 @@ Macro test API Decorator --------- -.. decorator:: macroTest - - .. autofunction:: macroTest +.. autodecorator:: macroTest diff --git a/doc/source/devel/api/sardana/macroserver/macros.rst b/doc/source/devel/api/sardana/macroserver/macros.rst index 8b30c787cf..5701f1f48f 100644 --- a/doc/source/devel/api/sardana/macroserver/macros.rst +++ b/doc/source/devel/api/sardana/macroserver/macros.rst @@ -14,6 +14,7 @@ communication demo env + expconf expert hkl ioregister diff --git a/doc/source/devel/api/sardana/macroserver/macros/expconf.rst b/doc/source/devel/api/sardana/macroserver/macros/expconf.rst new file mode 100644 index 0000000000..368632d947 --- /dev/null +++ b/doc/source/devel/api/sardana/macroserver/macros/expconf.rst @@ -0,0 +1,6 @@ +:mod:`~sardana.macroserver.macros.expconf` +========================================== + +.. automodule:: sardana.macroserver.macros.expconf + :imported-members: + :members: diff --git a/doc/source/devel/api/sardana/pool/poolsynchronization.rst b/doc/source/devel/api/sardana/pool/poolsynchronization.rst index 35e600de2f..b62c52aff7 100644 --- a/doc/source/devel/api/sardana/pool/poolsynchronization.rst +++ b/doc/source/devel/api/sardana/pool/poolsynchronization.rst @@ -10,7 +10,7 @@ .. hlist:: :columns: 3 - * :class:`SynchronizationDescription` + * :class:`SynchDescription` * :class:`PoolSynchronization` PoolSynchronization @@ -24,13 +24,13 @@ PoolSynchronization :members: :undoc-members: -SynchronizationDescription --------------------------- +SynchDescription +---------------- -.. inheritance-diagram:: SynchronizationDescription +.. inheritance-diagram:: SynchDescription :parts: 1 -.. autoclass:: SynchronizationDescription +.. autoclass:: SynchDescription :show-inheritance: :members: :undoc-members: diff --git a/doc/source/devel/api/sardana/pool/poolutil.rst b/doc/source/devel/api/sardana/pool/poolutil.rst index 8c9ddbd258..55c6049536 100644 --- a/doc/source/devel/api/sardana/pool/poolutil.rst +++ b/doc/source/devel/api/sardana/pool/poolutil.rst @@ -1,8 +1,20 @@ .. currentmodule:: sardana.pool.poolutil :mod:`~sardana.pool.poolutil` -========================================= +============================= .. automodule:: sardana.pool.poolutil -.. rubric:: Classes \ No newline at end of file +.. rubric:: Singletons + +.. autodata:: PoolUtil + +.. rubric:: Classes + +_PoolUtil +--------- + +.. autoclass:: _PoolUtil + :show-inheritance: + :members: + :undoc-members: diff --git a/doc/source/devel/api/sardana/sardanadefs.rst b/doc/source/devel/api/sardana/sardanadefs.rst index c188c70a37..937be60336 100644 --- a/doc/source/devel/api/sardana/sardanadefs.rst +++ b/doc/source/devel/api/sardana/sardanadefs.rst @@ -47,10 +47,6 @@ .. autodata:: sardana.sardanadefs.InterfacesExpanded -.. autodata:: sardana.sardanadefs.INTERFACES - -.. autodata:: sardana.sardanadefs.INTERFACES_EXPANDED - .. rubric:: Functions .. autofunction:: sardana.sardanadefs.from_dtype_str diff --git a/doc/source/devel/api/sardana/spock/magic.rst b/doc/source/devel/api/sardana/spock/magic.rst index 1ff09cb076..40f1ab5491 100644 --- a/doc/source/devel/api/sardana/spock/magic.rst +++ b/doc/source/devel/api/sardana/spock/magic.rst @@ -7,4 +7,5 @@ .. rubric:: Functions +.. autofunction:: sardana.spock.magic.edmac .. autofunction:: sardana.spock.magic.showscan \ No newline at end of file diff --git a/doc/source/devel/api/sardana/taurus.rst b/doc/source/devel/api/sardana/taurus.rst index 02a599fdc2..ef6e60d799 100644 --- a/doc/source/devel/api/sardana/taurus.rst +++ b/doc/source/devel/api/sardana/taurus.rst @@ -11,3 +11,4 @@ :maxdepth: 1 core + qt diff --git a/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst b/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst index 03382f26ab..9912a909b5 100644 --- a/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst +++ b/doc/source/devel/api/sardana/taurus/core/tango/sardana.rst @@ -7,10 +7,21 @@ .. automodule:: sardana.taurus.core.tango.sardana +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + * :func:`unregisterExtensions` + .. rubric:: Modules .. toctree:: :maxdepth: 1 pool + macroserver +.. autofunction:: registerExtensions +.. autofunction:: unregisterExtensions diff --git a/doc/source/devel/api/sardana/taurus/core/tango/sardana/macroserver.rst b/doc/source/devel/api/sardana/taurus/core/tango/sardana/macroserver.rst new file mode 100644 index 0000000000..06b5649146 --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/core/tango/sardana/macroserver.rst @@ -0,0 +1,46 @@ +.. currentmodule:: sardana.taurus.core.tango.sardana.macroserver + +================= +Taurus Extensions +================= + +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + * :func:`unregisterExtensions` + +.. rubric:: Classes + +.. hlist:: + :columns: 4 + + * :class:`BaseDoor` + * :class:`BaseMacroServer` + +.. autofunction:: registerExtensions +.. autofunction:: unregisterExtensions + +BaseDoor +-------- + +.. inheritance-diagram:: BaseDoor + :parts: 1 + +.. autoclass:: BaseDoor + :show-inheritance: + :members: + :undoc-members: + +BaseMacroServer +--------------- + +.. inheritance-diagram:: BaseMacroServer + :parts: 1 + +.. autoclass:: BaseMacroServer + :show-inheritance: + :members: + :undoc-members: diff --git a/doc/source/devel/api/sardana/taurus/core/tango/sardana/pool.rst b/doc/source/devel/api/sardana/taurus/core/tango/sardana/pool.rst index 0c23814c8d..d2a42938c2 100644 --- a/doc/source/devel/api/sardana/taurus/core/tango/sardana/pool.rst +++ b/doc/source/devel/api/sardana/taurus/core/tango/sardana/pool.rst @@ -4,6 +4,14 @@ Taurus Extensions ================= +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + * :func:`unregisterExtensions` + .. rubric:: Classes .. hlist:: @@ -28,6 +36,9 @@ Taurus Extensions * :class:`TwoDExpChannel` * :class:`ZeroDExpChannel` +.. autofunction:: registerExtensions +.. autofunction:: unregisterExtensions + BaseElement ----------- diff --git a/doc/source/devel/api/sardana/taurus/qt.rst b/doc/source/devel/api/sardana/taurus/qt.rst new file mode 100644 index 0000000000..c450ea690e --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt.rst @@ -0,0 +1,15 @@ +.. currentmodule:: sardana.taurus.qt + +:mod:`~sardana.taurus.qt` +========================= + +.. automodule:: sardana.taurus.qt + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + qtcore + qtgui + diff --git a/doc/source/devel/api/sardana/taurus/qt/qtcore.rst b/doc/source/devel/api/sardana/taurus/qt/qtcore.rst new file mode 100644 index 0000000000..ae97ad449d --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtcore.rst @@ -0,0 +1,14 @@ +.. currentmodule:: sardana.taurus.qt.qtcore + +:mod:`~sardana.taurus.qt.qtcore` +================================ + +.. automodule:: sardana.taurus.qt.qtcore + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + tango + diff --git a/doc/source/devel/api/sardana/taurus/qt/qtcore/tango.rst b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango.rst new file mode 100644 index 0000000000..5ff72f93b6 --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango.rst @@ -0,0 +1,14 @@ +.. currentmodule:: sardana.taurus.qt.qtcore.tango + +:mod:`~sardana.taurus.qt.qtcore.tango` +====================================== + +.. automodule:: sardana.taurus.qt.qtcore.tango + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + sardana + diff --git a/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana.rst b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana.rst new file mode 100644 index 0000000000..1621c45a3b --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana.rst @@ -0,0 +1,25 @@ +.. currentmodule:: sardana.taurus.qt.qtcore.tango.sardana + +.. _sardana-taurus-qt-api: + +:mod:`~sardana.taurus.qt.qtcore.tango.sardana` +============================================== + +.. automodule:: sardana.taurus.qt.qtcore.tango.sardana + +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + pool + macroserver + +.. autofunction:: registerExtensions diff --git a/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/macroserver.rst b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/macroserver.rst new file mode 100644 index 0000000000..515181a84c --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/macroserver.rst @@ -0,0 +1,44 @@ +.. currentmodule:: sardana.taurus.qt.qtcore.tango.sardana.macroserver + +================= +Taurus Extensions +================= + +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + +.. rubric:: Classes + +.. hlist:: + :columns: 4 + + * :class:`QDoor` + * :class:`QMacroServer` + +.. autofunction:: registerExtensions + +QDoor +----- + +.. inheritance-diagram:: QDoor + :parts: 1 + +.. autoclass:: QDoor + :show-inheritance: + :members: + :undoc-members: + +QMacroServer +------------ + +.. inheritance-diagram:: QMacroServer + :parts: 1 + +.. autoclass:: QMacroServer + :show-inheritance: + :members: + :undoc-members: diff --git a/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/pool.rst b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/pool.rst new file mode 100644 index 0000000000..73e05a967f --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtcore/tango/sardana/pool.rst @@ -0,0 +1,44 @@ +.. currentmodule:: sardana.taurus.qt.qtcore.tango.sardana.pool + +================= +Taurus Extensions +================= + +.. rubric:: Functions + +.. hlist:: + :columns: 3 + + * :func:`registerExtensions` + +.. rubric:: Classes + +.. hlist:: + :columns: 4 + + * :class:`QPool` + * :class:`QMeasurementGroup` + +.. autofunction:: registerExtensions + +QPool +----- + +.. inheritance-diagram:: QPool + :parts: 1 + +.. autoclass:: QPool + :show-inheritance: + :members: + :undoc-members: + +QMeasurementGroup +----------------- + +.. inheritance-diagram:: QMeasurementGroup + :parts: 1 + +.. autoclass:: QMeasurementGroup + :show-inheritance: + :members: + :undoc-members: diff --git a/doc/source/devel/api/sardana/taurus/qt/qtgui.rst b/doc/source/devel/api/sardana/taurus/qt/qtgui.rst new file mode 100644 index 0000000000..a5ff2b0b2d --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtgui.rst @@ -0,0 +1,13 @@ +.. currentmodule:: sardana.taurus.qt.qtgui + +:mod:`~sardana.taurus.qt.qtgui` +================================ + +.. automodule:: sardana.taurus.qt.qtgui + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + extra_sardana \ No newline at end of file diff --git a/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana.rst b/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana.rst new file mode 100644 index 0000000000..7e02d405ee --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana.rst @@ -0,0 +1,14 @@ +.. currentmodule:: sardana.taurus.qt.qtgui.extra_sardana + +:mod:`~sardana.taurus.qt.qtgui.extra_sardana` +============================================= + +.. automodule:: sardana.taurus.qt.qtgui.extra_sardana + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + qtspock + diff --git a/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana/qtspock.rst b/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana/qtspock.rst new file mode 100644 index 0000000000..229a5c7b2e --- /dev/null +++ b/doc/source/devel/api/sardana/taurus/qt/qtgui/extra_sardana/qtspock.rst @@ -0,0 +1,37 @@ +.. currentmodule:: sardana.taurus.qt.qtgui.extra_sardana.qtspock + + +:mod:`~sardana.taurus.qt.qtgui.extra_sardana.qtspock` +===================================================== + +.. automodule:: sardana.taurus.qt.qtgui.extra_sardana.qtspock + +.. rubric:: Classes + +.. hlist:: + :columns: 4 + + * :class:`QtSpockWidget` + * :class:`QtSpock` + +QtSpockWidget +------------- + +.. inheritance-diagram:: QtSpockWidget + :parts: 1 + +.. autoclass:: QtSpockWidget + :show-inheritance: + :members: + :undoc-members: + +QtSpock +------- + +.. inheritance-diagram:: QtSpock + :parts: 1 + +.. autoclass:: QtSpock + :show-inheritance: + :members: + :undoc-members: diff --git a/doc/source/devel/guide_migration.rst b/doc/source/devel/guide_migration/0to1.rst similarity index 91% rename from doc/source/devel/guide_migration.rst rename to doc/source/devel/guide_migration/0to1.rst index e85bcbb54a..61258c095f 100644 --- a/doc/source/devel/guide_migration.rst +++ b/doc/source/devel/guide_migration/0to1.rst @@ -1,20 +1,13 @@ .. currentmodule:: sardana.pool.controller -.. _sardana-migration-guide: +.. _0to1: - -=================================== -Sardana migration guide -=================================== - -This chapter describes how to migrate different sardana components between the -different API versions. +======== +v0 -> v1 +======== How to migrate your macro code -=================================== - -API v0 -> v1 -------------- +============================== This chapter describes the necessary steps to fully migrate your macros from *API v0* ( sardana 0.x ) to *API v1* ( sardana 1.x ) @@ -26,11 +19,11 @@ The following are the 2 necessary changes to make your macros work in sardana *API v1*: 1. from:: - + from macro import Macro, Type, Table, List - + to:: - + from sardana.macroserver.macro import Macro, Type, Table, List 2. Parameter type ``Type.Motor`` should be changed ``Type.Moveable``. @@ -39,8 +32,8 @@ sardana *API v1*: and `Moveable` means all moveable elements (including physical motor, pseudo motor). -New features in API v1 -""""""""""""""""""""""" +New features +"""""""""""" This chapter is a summary of all new features in *API v1*. @@ -49,9 +42,6 @@ This chapter is a summary of all new features in *API v1*. How to migrate your controller code =================================== -API v0 -> v1 -------------- - This chapter describes the necessary steps to fully migrate your controller from *API v0* ( sardana 0.x ) to *API v1* ( sardana 1.x ) @@ -62,23 +52,23 @@ The following are the 2 necessary changes to make your controller work in sardana *API v1*: 1. from:: - + import pool from pool import /PoolUtil - + to:: - + from sardana import pool from sardana.pool import PoolUtil from sardana.pool.controller import - + 2. change contructor from:: def __init__(self, inst, props): code - + to:: - + def __init__(self, inst, props, *args, **kwargs): MotorController.__init__(self, inst, props, *args, **kwargs) code @@ -89,31 +79,31 @@ sardana *API v1*: The following change is not mandatory but is necessary in order for your controller to be recognized by the pool to be a *API v1* controller: -3. _log member changed from :class:`logging.Logger` to +3. _log member changed from :class:`logging.Logger` to :class:`taurus.core.util.Logger`. This means that you need to change code from:: - + self._log.setLevel(logging.INFO) - + to:: - + self._log.setLogLevel(logging.INFO) - + or:: - + self._log.setLogLevel(taurus.Info) - + since taurus.Info == logging.INFO. - + Optional changes """""""""""""""" -The following changes are not necessary to make your controller work. The +The following changes are not necessary to make your controller work. The *API v1* supports the *API v0* on these matters. 1. **class members**: - + #. from: :attr:`~Controller.class_prop` to: :attr:`~Controller.ctrl_properties` #. from: :attr:`~Controller.ctrl_extra_attributes` to: :attr:`~Controller.axis_attributes` #. new feature in *API v1*: :attr:`~Controller.ctrl_attributes` @@ -121,7 +111,7 @@ The following changes are not necessary to make your controller work. The 3. **data types**: #. :meth:`~Controller.StateOne` **return type**: Previously - :meth:`~Controller.StateOne` had to return a member of + :meth:`~Controller.StateOne` had to return a member of :class:`PyTango.DevState`. Now it **can** instead return a member of :class:`~sardana.sardanadefs.State`. This eliminates the need to import :mod:`PyTango`. @@ -137,7 +127,7 @@ The following changes are not necessary to make your controller work. The #. from: :meth:`~Controller.GetExtraAttributePar` to: :meth:`~Controller.GetAxisExtraPar` #. from: :meth:`~Controller.SetExtraAttributePar` to: :meth:`~Controller.SetAxisExtraPar` #. new feature in *API v1*: :meth:`~Controller.GetCtrlPar`, :meth:`~Controller.SetCtrlPar` - #. new feature in *API v1*: :meth:`~Stopable.AbortAll` (has default + #. new feature in *API v1*: :meth:`~Stopable.AbortAll` (has default implementation which calls :meth:`~Stopable.AbortOne` for each axis) 5. **pseudo motor controller method names**: @@ -149,8 +139,8 @@ The following changes are not necessary to make your controller work. The #. new feature in *API v1*: :meth:`~PseudoMotorController.GetMotor` #. new feature in *API v1*: :meth:`~PseudoMotorController.GetPseudoMotor` -New features in API v1 -""""""""""""""""""""""" +New features +"""""""""""" This chapter is a summary of all new features in *API v1*. @@ -161,7 +151,7 @@ This chapter is a summary of all new features in *API v1*. :meth:`~Controller.GetCtrlPar`, :meth:`~Controller.SetCtrlPar`) 2. For :attr:`~Controller.ctrl_properties`, :attr:`~Controller.axis_attributes` and :attr:`~Controller.ctrl_extra_attributes`: - + - new (more pythonic) syntax. Old syntax is still supported: - can replace data type strings for python type ('PyTango.DevDouble' -> float) - Default behavior. Example: before data access needed to be described explicitly. @@ -170,7 +160,7 @@ This chapter is a summary of all new features in *API v1*. - new keys 'fget' and 'fset' override default method calls 3. no need to import :mod:`PyTango` (:meth:`~Controller.StateOne` can return sardana.State.On instead of PyTango.DevState.ON) -4. PseudoMotorController has new :meth:`~PseudoMotorController.GetMotor` and +4. PseudoMotorController has new :meth:`~PseudoMotorController.GetMotor` and :meth:`~PseudoMotorController.GetPseudoMotor` 5. new :meth:`~Stopable.AbortAll` (with default implementation which calls :meth:`~Stopable.AbortOne` for each axis) diff --git a/doc/source/devel/guide_migration/2to3.rst b/doc/source/devel/guide_migration/2to3.rst new file mode 100644 index 0000000000..8e4f3939c3 --- /dev/null +++ b/doc/source/devel/guide_migration/2to3.rst @@ -0,0 +1,52 @@ +.. _2to3: + +======== +v2 -> v3 +======== + +Sardana v3 is the first Sardana release which **only** supports Python 3.5 or +higher. Due to the dropping of support to Python 2 Sardana v3 is not backwards +compatible with the previous versions. Below you can find +the incompatibilities and the necessary migration steps. + +MacroServer environment +======================= + +Environments created with Python 2 needs to be ported to Python 3. +In order to do that you can use the +`upgrade_env.py `_ script. + +This requires a choice of the shelve backend (either gnu or dumb) +at the moment of migration. The same decision is also important for new +Sardana systems. In this last case it can be tuned with this +`~sardana.sardanacustomsettings.MS_ENV_SHELVE_BACKEND`. + +Plugins migration to Python 3 +============================= + +Sardana plugins (macros, controllers and recorders) and all their dependencies +e.g. external libraries, needs to be migrated to Python 3. You can find hints +on how to pass this process in this +`ALBA's internal presentation `_ +(slides 4 - 34). + +GUIs migration to Python 3 +========================== + +Sardana-Taurus GUIs which use the :ref:`MacroButton` and all +their dependencies e.g. external libraries, needs to be migrated to Python 3. +It has not been observed that other +:ref:`Sardana-Taurus widgets` require their GUIs to be migrated. + +Other (non Python 3 related) incompatibilities +============================================== + +Sardana v3 was reduced by long time ago deprecated features. You can +find a list of them together with the suggested substitutes in this +`table `_. + + + + + + diff --git a/doc/source/devel/guide_migration/index.rst b/doc/source/devel/guide_migration/index.rst new file mode 100644 index 0000000000..01e0012088 --- /dev/null +++ b/doc/source/devel/guide_migration/index.rst @@ -0,0 +1,16 @@ +.. _sardana-migration-guide: + + +======================= +Sardana migration guide +======================= + +This chapter describes how to migrate different sardana components between the +different API versions. + + +.. toctree:: + :maxdepth: 1 + + 2to3 + 0to1 \ No newline at end of file diff --git a/doc/source/devel/howto_controllers/howto_controller.rst b/doc/source/devel/howto_controllers/howto_controller.rst index b753b7abb6..eef0b5028f 100644 --- a/doc/source/devel/howto_controllers/howto_controller.rst +++ b/doc/source/devel/howto_controllers/howto_controller.rst @@ -538,6 +538,31 @@ Example:: except: pass +.. _sardana-controller-accessing-tango: + +Accessing Tango from your controllers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is a very common pattern that when integrating a new hardware (or +eventually a software component) in Sardana you start from a Tango Device +Server (either already existing one or you develop one as an intermediate +layer). In this case your controller will need to access Tango and there are +two ways of doing that, either with Taurus_ (using `taurus.Device`) or +with PyTango_ (using `tango.DeviceProxy`). Please consult a similar discussion +:ref:`sardana-macro-accessing-tango` on which one to use. + +For accessing Sardana elements e.g.: motors, experimental channels, etc. +currently there is no Sardana :term:`API` and you will need to use one of the +above methods. + +.. note:: + For a very simplified integration of Tango devices in Sardana you may + consider using + `sardana-tango controllers `_. + + + + .. rubric:: Footnotes .. [#f1] Pseudo controllers don't need to manage their individual axis. Therefore, diff --git a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst index 16d62f5270..6d4f751265 100644 --- a/doc/source/devel/howto_controllers/howto_countertimercontroller.rst +++ b/doc/source/devel/howto_controllers/howto_countertimercontroller.rst @@ -115,7 +115,7 @@ Here is an example of the possible implementation of class SpringfieldCounterTimerController(CounterTimerController): - def LoadOne(self, axis, value, repetitions): + def LoadOne(self, axis, value, repetitions, latency): self.springfield.LoadChannel(axis, value) .. _sardana-countertimercontroller-howto-value: @@ -474,7 +474,7 @@ Here is an example of the possible implementation of class SpringfieldCounterTimerController(CounterTimerController): - def LoadOne(self, axis, value, repetitions): + def LoadOne(self, axis, value, repetitions, latency): self.springfield.LoadChannel(axis, value) self.springfield.SetRepetitions(repetitions) return value diff --git a/doc/source/devel/howto_controllers/index.rst b/doc/source/devel/howto_controllers/index.rst index 34e093ebfb..8bf86fc5ea 100644 --- a/doc/source/devel/howto_controllers/index.rst +++ b/doc/source/devel/howto_controllers/index.rst @@ -9,8 +9,8 @@ Writing controllers This chapter provides the necessary information to write controllers in sardana. -Before writing a new controller you should check the `controller plugin -register `_. +Before writing a new controller you should check the `Sardana plugins +catalogue `_. There's a high chance that somebody already wrote the plugin for your hardware. An overview of the pool controller concept can be found diff --git a/doc/source/devel/howto_controllers/sf_ct_ctrl.py b/doc/source/devel/howto_controllers/sf_ct_ctrl.py index 5e3c64b469..7e5df8dd20 100644 --- a/doc/source/devel/howto_controllers/sf_ct_ctrl.py +++ b/doc/source/devel/howto_controllers/sf_ct_ctrl.py @@ -66,7 +66,7 @@ def StartOne(self, axis, value=None): """acquire the specified counter""" self.springfield.StartChannel(axis) - def LoadOne(self, axis, value, repetitions): + def LoadOne(self, axis, value, repetitions, latency): self.springfield.LoadChannel(axis, value) def StopOne(self, axis): @@ -112,7 +112,7 @@ def ReadOne(self, axis): value = self.springfield.getValue(axis) return value - def LoadOne(self, axis, value, repetitions): + def LoadOne(self, axis, value, repetitions, latency): self.springfield.LoadChannel(axis, value) def StartOne(self, axis, position): diff --git a/doc/source/devel/howto_controllers/springfieldlib.py b/doc/source/devel/howto_controllers/springfieldlib.py index 71dc8ff26e..d42cf7c5cf 100644 --- a/doc/source/devel/howto_controllers/springfieldlib.py +++ b/doc/source/devel/howto_controllers/springfieldlib.py @@ -125,7 +125,7 @@ def setMinVelocity(self, vi): """ Sets the minimum velocity in ms^-1. A.k.a. base rate""" vi = float(vi) if vi < 0: - raise "Minimum velocity must be >= 0" + raise Exception("Minimum velocity must be >= 0") self.min_vel = vi @@ -145,7 +145,7 @@ def setMaxVelocity(self, vf): """ Sets the maximum velocity in ms^-1.""" vf = float(vf) if vf <= 0: - raise "Maximum velocity must be > 0" + raise Exception("Maximum velocity must be > 0") self.max_vel = vf @@ -165,7 +165,7 @@ def setAccelerationTime(self, at): """Sets the time to go from minimum velocity to maximum velocity in seconds""" at = float(at) if at <= 0: - raise "Acceleration time must be > 0" + raise Exception("Acceleration time must be > 0") self.accel_time = at self.accel = (self.max_vel - self.min_vel) / at @@ -179,7 +179,7 @@ def setDecelerationTime(self, dt): """Sets the time to go from maximum velocity to minimum velocity in seconds""" dt = float(dt) if dt <= 0: - raise "Deceleration time must be > 0" + raise Exception("Deceleration time must be > 0") self.decel_time = dt self.decel = (self.min_vel - self.max_vel) / dt @@ -193,7 +193,7 @@ def setAcceleration(self, a): """Sets the acceleration in ms^-2""" a = float(a) if a < 0: - raise "Acceleration must be >= 0" + raise Exception("Acceleration must be >= 0") self.accel = float(a) @@ -208,7 +208,7 @@ def setDeceleration(self, d): """Sets the deceleration in ms^-2""" d = float(d) if d > 0: - raise "Deceleration must be <= 0" + raise Exception("Deceleration must be <= 0") self.decel = d @@ -485,23 +485,32 @@ def setPower(self, power): self.power = power def info(self): - print "Small movement =", self.small_motion - print "length =", self.dsplmnt - print "position where maximum velocity will be reached =", self.curr_max_vel_pos - print "necessary displacement to reach maximum velocity =", self.curr_dsplmnt_reach_max_vel - print "necessary displacement to stop from maximum velocity =", self.curr_dsplmnt_reach_min_vel - print "maximum velocity possible =", self.curr_max_vel - print "time at top velocity =", self.curr_at_max_vel_time - print "displacement at top velocity =", self.curr_at_max_vel_dsplmnt - print "time to reach maximum velocity =", self.curr_max_vel_time - print "time to reach minimum velocity =", self.curr_min_vel_time - print "time the motion will take =", self.duration - print "instant when maximum velocity should be reached =", self.curr_max_vel_instant - print "instant when should start decelerating =", self.curr_min_vel_instant - print "instant the motion will end", self.final_instant - print "" - print "For long movements (where top vel is possible), necessary displacement to reach maximum velocity =", self.dsplmnt_reach_max_vel - print "For long movements (where top vel is possible), necessary displacement to stop from maximum velocity =", self.dsplmnt_reach_min_vel + print("Small movement =", self.small_motion) + print("length =", self.dsplmnt) + print("position where maximum velocity will be reached =", + self.curr_max_vel_pos) + print("necessary displacement to reach maximum velocity =", + self.curr_dsplmnt_reach_max_vel) + print("necessary displacement to stop from maximum velocity =", + self.curr_dsplmnt_reach_min_vel) + print("maximum velocity possible =", self.curr_max_vel) + print("time at top velocity =", self.curr_at_max_vel_time) + print("displacement at top velocity =", self.curr_at_max_vel_dsplmnt) + print("time to reach maximum velocity =", self.curr_max_vel_time) + print("time to reach minimum velocity =", self.curr_min_vel_time) + print("time the motion will take =", self.duration) + print("instant when maximum velocity should be reached =", + self.curr_max_vel_instant) + print("instant when should start decelerating =", + self.curr_min_vel_instant) + print("instant the motion will end", self.final_instant) + print("") + print("For long movements (where top vel is possible), " + "necessary displacement to reach maximum velocity =", + self.dsplmnt_reach_max_vel) + print("For long movements (where top vel is possible), " + "necessary displacement to stop from maximum velocity =", + self.dsplmnt_reach_min_vel) class SpringfieldMotorHW(object): diff --git a/doc/source/devel/howto_macros/macros_general.rst b/doc/source/devel/howto_macros/macros_general.rst index 1be8dc0eec..f8e1cb08c7 100644 --- a/doc/source/devel/howto_macros/macros_general.rst +++ b/doc/source/devel/howto_macros/macros_general.rst @@ -212,7 +212,7 @@ the macro function version of *Hello, World!*:: .. note:: If you already know a little about Python_ your are probably wondering why - not use ``print "Hello, World!"``? + not use ``print("Hello, World!")``? Remember that your macro will be executed by a Sardana server which may be running in a different computer than the computer you are working on. @@ -225,9 +225,6 @@ the macro function version of *Hello, World!*:: function (it is a bit more powerful than :meth:`~sardana.macroserver.macro.Macro.output`\, and has a slightly different syntax) :: - - # mandatory first line in your code if you use Python < 3.0 - from __future__ import print_function from sardana.macroserver.macro import macro @@ -253,6 +250,8 @@ includes :keyword:`for` and :keyword:`while` loops, :keyword:`if` ... wave_fft = numpy.fft.fft(wave) +.. _sardana-macro-parameters: + Adding parameters to your macro ------------------------------- @@ -469,7 +468,7 @@ this parameter any name but usually, by convention it is called ``self``). your macro to do all kinds of things, among others, to obtain the sardana elements. The :ref:`Macro API reference ` describes all these functions and the -:ref:`Sardana-Taurus extensions API reference ` +:ref:`Sardana-Taurus model API reference ` describes the obtained sardana elements. Let's say you want to write a macro that explicitly moves a known *theta* motor @@ -582,7 +581,7 @@ messages with different levels: * :meth:`~Macro.output` As you've seen, the special :meth:`~Macro.output` function has the same effect -as a print statement (with slightly different arguments). +as a print function (with slightly different arguments). Log messages may have several destinations depending on how your sardana server is configured. At least, one destination of each log message is the client(s) @@ -739,7 +738,7 @@ using :meth:`~Macro.data`: # and the result of the Macro.prepare method my_scan, _ = ret self.runMacro(my_scan) - print len(my_scan.data) + self.print(len(my_scan.data)) A set of macro call examples can be found :ref:`here `. @@ -769,7 +768,7 @@ macro. So, without further delay, here is the *Hello, World!* example:: """Hello, World! macro""" def run(self): - print "Hello, World!" + self.print("Hello, World!") .. _sardana-macro-add-parameters: @@ -818,7 +817,7 @@ prepare HelloWorld to run only after year 1989: raise Exception("HelloWorld can only run after year 1989") def run(self): - print "Hello, World!" + self.print("Hello, World!") .. _sardana-macro-handling-macro-stop-and-abort: @@ -923,6 +922,49 @@ using the :meth:`~sardana.macroserver.macro.Hookable.appendHook` method:: the macro. Using the method appends just one hook but does not affect the general hooks eventually attached to the macro. +.. _sardana-macro-accessing-tango: + +Accessing Tango from your macros +-------------------------------- + +Your macros almost certainly will need to access Tango_ devices, either +external, for example, coming from the vacuum system or any other +arbitrary system integrated with Tango Device Server or internal to Sardana +(Sardana elements are currently exported as Tango devices) e.g.: motors, +measurement group, etc. + +There exists different :term:`API` to access to Tango_ devices. + +First, to access Sardana elements it is recommended to use the Sardana +:term:`API`: e.g.: `~sardana.macroserver.macro.Macro.getMoveable` to obtain +any moveable (motor or pseudo motor), +`~sardana.macroserver.macro.Macro.getMeasurementGroup` to obtain a measurement +group. + +.. note:: + By :ref:`adding parameters to your macro ` + you get the same objects as if you were using the above methods. + Their classes are documented in: + :ref:`Sardana-Taurus model API reference ` + +Any external Tango device could be accessed with Taurus_ (using +`taurus.Device`) or simply with PyTango_ (using `tango.DeviceProxy`). +Taurus gives you some benefits over PyTango: + +- seamless attribute configuration management e.g.: unit aware attribute + read and write, simplified way to read/write attribute configuration, etc. +- unique way to access different data sources e.g. Tango_, EPICS_, + `hdf5 `_, etc. +- simplified way to use Tango_ events + +However the above benefits are not for free and more precisely are for some +extra time overhead (in milliseconds range). + +As a rule of thumb, if you you don't mind the extra overhead and value the +simplified usage you should use Taurus. If you strive for very optimal access +to Tango and don't need these benefits then most probably PyTango will work +better for you. + .. _sardana-macro-using-external-libraries: Using external python libraries @@ -986,6 +1028,10 @@ example:: self.pyplot.title(r'Verify $J_0(x)=\frac{1}{\pi}\int_0^{\pi}\cos(x \sin\phi)\,d\phi$') self.pyplot.xlabel('$x$') self.pyplot.legend() + self.pyplot.draw() + +The last call to ``pyplot.draw()`` is important to ensure the client updates +the figure properly. Running this macro from spock will result in something like: @@ -1017,6 +1063,7 @@ Just for fun, the following macro computes a fractal and plots it as an image:: mask = (fractal == 255) & (abs(z) > 10) fractal[mask] = 254 * n / finteractions self.pyplot.imshow(fractal) + self.pyplot.draw() And the resulting image (interactions=20, density=512): @@ -1049,6 +1096,11 @@ Also consider that each time you plot the complete data to be plotted is sent from the server to the client... so please avoid plotting arrays of 10,000,000 points! +Clients like spock receive the requests to plot in a separate thread. +Matplotlib has a long history of issues concerning plot updates using different +threads. To mitigate these effects please be sure to call ``self.pyplot.draw()`` +on your macro every time you need to be sure the matplotlib figure is up to date. + .. _sardana-macro-input: Asking for user input @@ -1533,14 +1585,26 @@ it. The job begins to run when added. When the job finishes, it sets the The second method is to use standard Python threading_ library. +Adding your macros to Sardana +----------------------------- + +To add your macros to Sardana, you need to configure the macro plugin discovery +path (MacroPath property):: + + _MACRO_SERVER.put_property({"MacroPath":[""]}) + +.. note:: + You can add more than one path, but be careful! The path order is important. + Macros in the higher position paths will take precedence over the lower position paths. .. rubric:: Footnotes .. [#f1] To find the absolute path for sardana's source code type on the - command line ``python -c "import sys, sardana; sys.stdout.write(str(sardana.__path__))"`` + command line ``python3 -c "import sys, sardana; sys.stdout.write + (str(sardana.__path__))"`` .. [#f2] To check which version of Python_ you are using type on the command - line ``python -c "import sys; sys.stdout.write(sys.version)"`` + line ``python3 -c "import sys; sys.stdout.write(sys.version)"`` .. |input_integer| image:: ../../_static/macro_input_integer.png :align: middle diff --git a/doc/source/devel/howto_recorders.rst b/doc/source/devel/howto_recorders.rst index f1c7ec33e6..0b7c2d3748 100644 --- a/doc/source/devel/howto_recorders.rst +++ b/doc/source/devel/howto_recorders.rst @@ -48,15 +48,39 @@ the environment variables. Writing a custom recorder ------------------------- -.. todo:: document how to write custom recorders +.. todo:: finish chapter based on user's input + +This chapter provides the necessary information to write recorders in Sardana. + +Before writing a new recorder you should check the `Sardana plugins +catalogue `_. +There's a chance that somebody already wrote one that fits your needs. + +If finally you decide to write a new one, below you can find some useful +information: + +* Your recorder class would need to inherit from ``DataRecorder`` + (or ``BaseFileRecorder`` if you write data to a file) and + implement minimum the following methods: + + * ``_startRecordList`` + * ``_writeRecord`` + * ``_endRecordList`` + + You can find some examples in ``sardana.macroserver.recorders`` module. +* Recorder classes are used to instantiate recorder objects in a scan. + Every time a scan starts, in its preparation phase, a new recorder object + gets created. Reversely, at the end of the scan the recorder object gets + destroyed. If you need to keep a long lived objects in your recorder + you may consider using the :term:`singleton pattern` Configuration ------------- Custom recorders may be added to the Sardana system by placing the recorder library module in a directory which is specified by the MacroServer -*RecorderPath* property. RecorderPath property may contain an ordered, -colon-separated list of directories. +:ref:`RecorderPath ` property. + In case of overriding recorders by name or by file extension (in case of the file recorders), recorders located in the first paths are of higher priority than the ones from the last paths. @@ -81,12 +105,10 @@ Three types of overriding may occur: same directory, the system will assign a list of recorders to a given extension. Then the application is in charge of deciding which one to use. -As previously mentioned recorders are selectable by either the recorder name or -the extension. During the MacroServer startup the extension to recorder map is +As previously mentioned recorders are selectable by either the extension +(using the :ref:`ScanFile ` environment variable) or the recorder name +(using the :ref:`ScanRecorder ` environment variable). + +During the MacroServer startup the extension to recorder map is generated while loading the recorder libraries. This dynamically created map -may be overridden by the custom map defined in the *sardanacustomsettings* -module (SCAN_RECORDER_MAP variable with a dictionary where key is the scan file -extension e.g. ".h5" and value is the recorder name e.g. "MyCustomRecorder", -where both keys and values are of type string). The SCAN_RECORDER_MAP will make -an union with the dynamically created map taking precedence in case the -extensions repeats in both of them. +may be overridden by editing the :data:`~sardana.sardanacustomsettings.SCAN_RECORDER_MAP`. diff --git a/doc/source/devel/howto_test/test_general.rst b/doc/source/devel/howto_test/test_general.rst index 2d4f54f7ca..3c9f18c744 100644 --- a/doc/source/devel/howto_test/test_general.rst +++ b/doc/source/devel/howto_test/test_general.rst @@ -29,23 +29,19 @@ revealing the bug. The first tests implemented are focused on Unit Tests, but the same framework should be used for integration and system tests as well. -The sardana.test module includes testsuite.py. This file provides an -auto-discovering suite for all tests implemented in Sardana. - The following are some key points to keep in mind when using this framework: -- The Sardana Test Framework is based on :mod:`unittest` which should be - imported from :mod:`taurus.external` in order to be compatible with all - versions of python supported by Taurus. - -- all test-related code is contained in submodules named `test` which appear +* Most of the tests in the Sardana Test Framework use :mod:`unittest`. + Since Sardana 3.0.2 we decided to move to `pytest `_ + and we plan to gradually migrate the already existing tests. +* All test-related code is contained in submodules named ``test`` which appear in any module of Sardana. - -- test-related code falls in one of these three categories: - * Actual test code (classes that derive from unittest.TestCase) - * Utility classes/functions (code to simplify development of test code) - * Resources (accessory files required by some test). They are located in - subdirectories named `res` situated inside the folders named `test`. +* Test related code falls in one of these three categories: + + * Actual test code (classes that derive from unittest.TestCase) + * Utility classes/functions (code to simplify development of test code) + * Resources (accessory files required by some test). They are located in + subdirectories named ``res`` situated inside the folders named ``test``. For a more complete description of the conventions on how to write tests with the Sardana Testing Framework, please refer to the diff --git a/doc/source/devel/howto_test/test_run_commands.rst b/doc/source/devel/howto_test/test_run_commands.rst index 83f487a985..484e7b79f0 100644 --- a/doc/source/devel/howto_test/test_run_commands.rst +++ b/doc/source/devel/howto_test/test_run_commands.rst @@ -10,35 +10,13 @@ Run tests from command line Run test suite -------------- -Running the Sardana test suite from command line can be done in two -different ways: +We recommend using pytest to run Sardana tests. Please refer to +`pytest documentation `_ +on how to execute tests. -1) Sardana tests can be executed using the `setuptools` test command prior to - the installation by executing the following command from within the sardana - project directory: - - python setup.py test - - This will execute only a subset of all the sardana tests - the unit test suite. - The functional tests, that require the :ref:`sardana-test-sar_demo`, are - excluded on purpose. - -2) The complete Sardana test suite, that includes the unit and the functional tests - can be executed only after the Sardana installation by executing the - `sardanatestsuite` script. - -Run a single test ------------------ - -Executing a single test from command line is done by doing: - - python -m unittest test_name - -Where test_name is the test module that has to be run. - -That can be done with more verbosity by indicating the option -v. - - python -m unittest -v test_name +.. note:: + Currently the majority of the Sardana tests are written using unittest. + We plan to gradually migrate them to pytest. .. _sardana-test-sar_demo: @@ -46,7 +24,8 @@ sar_demo test environment ------------------------- Some of the Sardana tests e.g. the ones that test the macros, require a running Sardana -instance with the sar_demo macro executed previosly. By default the tests will try to +instance with the sar_demo macro executed previously. By default the tests will try to connect to the `door/demo1/1` door in order to run the macros there. The default door -name can be changed in the `sardanacustomsettings` module. +name can be changed with the `sardana.sardanacustomsettings.UNITTEST_DOOR_NAME` +module. diff --git a/doc/source/devel/index.rst b/doc/source/devel/index.rst index 7b1aee922f..1f04436aa1 100644 --- a/doc/source/devel/index.rst +++ b/doc/source/devel/index.rst @@ -13,6 +13,6 @@ Developer's Guide Writing recorders howto_test/index API - Migration guide + Migration guide Examples Development guidelines diff --git a/doc/source/devel/overview/overview_IOR.rst b/doc/source/devel/overview/overview_IOR.rst index 19a2c263c0..d641636a46 100644 --- a/doc/source/devel/overview/overview_IOR.rst +++ b/doc/source/devel/overview/overview_IOR.rst @@ -11,8 +11,12 @@ register a value. This value type may be one of: :class:`int`, :class:`float`, :class:`bool` but the hardware usually expects a fixed type for a given register. -The IOR has a very wide range of applications it can serve to control the -:term:`PLC` registers, a discrete motor, etc. +In contrary to the writing of the :ref:`motor's ` +position attribute the writing of the IOR's value attribute is an instant +operation. + +The IOR has a very wide range of applications, for example it can serve to +control the :term:`PLC` registers. .. seealso:: diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst index 485e463517..3e34bf868b 100644 --- a/doc/source/glossary.rst +++ b/doc/source/glossary.rst @@ -247,12 +247,12 @@ Glossary people unfamiliar with Python sometimes use a numerical counter instead:: for i in range(len(food)): - print food[i] + print(food[i]) As opposed to the cleaner, Pythonic method:: for piece in food: - print piece + print(piece) sequence An :term:`iterable` which supports efficient element access using integer @@ -264,6 +264,11 @@ Glossary mapping rather than a sequence because the lookups use arbitrary :term:`immutable` keys rather than integers. + singleton pattern + Singleton pattern is a software design pattern that restricts the + instantiation of a class to one "single" instance. This is useful when + exactly one object is needed to coordinate actions across the system. + slice An object usually containing a portion of a :term:`sequence`. A slice is created using the subscript notation, ``[]`` with colons between numbers diff --git a/doc/source/index.rst b/doc/source/index.rst index 1ae979c0df..890a58d0b9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -40,6 +40,7 @@ Projects related to Sardana Project Page Download from PyPI Documentation + Plugins Catalogue Wiki .. _ALBA: http://www.albasynchrotron.es @@ -49,7 +50,7 @@ Projects related to Sardana .. _ESRF: http://esrf.eu .. _LGPL: http://www.gnu.org/licenses/lgpl.html .. _PyPi: http://pypi.python.org/pypi/sardana -.. _Documentation: http://sardana.readthedocs.org +.. _Documentation: https://sardana-controls.org .. _Tango: http://www.tango-controls.org/ .. _Taurus: http://taurus-scada.org/ .. _IPython: http://ipython.org/ diff --git a/doc/source/other_versions.rst b/doc/source/other_versions.rst index 2887e86d2c..ecbe6c56ba 100644 --- a/doc/source/other_versions.rst +++ b/doc/source/other_versions.rst @@ -3,48 +3,23 @@ Docs for other Sardana versions =============================== -The `main sardana docs `_ are generated for the +The `main Sardana docs `_ are generated for the most recent development version. -But docs for other versions of sardana (previous releases or other branches in -`repository `_ can also be browsed here: +You can still generate the docs for other versions of Sardana. In order to do that +you first need to clone the `Sardana repository `_, +checkout the necessary version and build the docs (this may require you to install +some dependencies apart from the sardana ones, such as sphinx). +In continuation you can find an example of how to do it for version ``2.8.3``: -.. raw:: html - - -
-
-
- - -
-
-
-
- - - - +.. code-block:: console + git clone -b 2.8.3 https://github.com/sardana-org/sardana + cd sardana + python setup.py build_sphinx + firefox build/sphinx/html/index.html +.. note:: + Sardana versions >= 3 work only with Python 3. Then you will need to build + the documentation with ``python3`` instead of ``python``. diff --git a/doc/source/sep/SEP16.md b/doc/source/sep/SEP16.md new file mode 100644 index 0000000000..becdca9064 --- /dev/null +++ b/doc/source/sep/SEP16.md @@ -0,0 +1,188 @@ + Title: Plugins (controllers, macros, etc.) catalogue + SEP: 16 + State: ACCEPTED + Reason: + SEP15 migrated the Sardana repository from SourceForge to GitHub. + The third-party repositories are still at SourceForge and a decision + is pending on what to do with them. + Date: 2017-04-03 + Drivers: Zbigniew Reszela + URL: https://github.com/reszelaz/sardana/blob/sep16/doc/source/sep/SEP16.md + License: http://www.jclark.com/xml/copying.txt + Abstract: + SEP15 migrated the Sardana project from SourceForge to GitHub but + left the third-party repositories, controllers and macros, at their + original location. This SEP takes care about the third-party + repositories reorganization so the plugins maintenance will become + more developer friendly and plugins more accessible to the user. + This is achieved by simply transferring the project ownerships + from the Sardana administrators to the plugins developers at the same + time giving some advices on how to organize the projects based + on the current experience. + + +Current Situation +----------------- + +Controllers and macros repositories are two separate repositories which +host all the plugins, both the ones that are generic and the ones which +are specific to the systems like ALBA’s or DESY’s beamlines. + +The controllers repository has two top level directories which divide +the plugins into the ones written in Python and the ones written in C++ +(obsolete). Underneath, one directory per controller type exists e.g. motor, +pseudomotor, countertimer, etc. Finally, controller modules are either +directly placed in that directories e.g. AdlinkAICoTiCtrl.py, or +a dedicated directory is created for a given controller IcePAPCtrl or +a family of them PmacCtrl. + +In the macros repository, the macro modules are either grouped in +directories, usually per system, e.g. ALBA_BL22_CLAESS, DESY_P01 or placed +directly in the repository e.g. albaemmacros.py. + +No third party repository exists neither for recorders, nor for GUIs/widgets. + +Objectives +---------- + +1. Enable natural way of working using git, like for example, use of the +feature branches, pull-requests, tags, independent git history etc. +2. Let developers organize their repositories according to their preferences. +3. Let developers organize plugins in projects so they could have their own +issue trackers, wiki pages, etc. +4. Do not force the hosting platform. +5. Give visibility to the well maintained plugins. + +Design +------ + +* Sardana organization will no more manage the plugins repositories. + These will be managed directly by their developers. +* Sardana organization will advice on how to organize the plugin projects. +* Sardana organization will maintain a catalogue of the third party plugins. + +### Old third-party repositories + +The old third-party repositories([controllers](https://sourceforge.net/p/sardana/controllers.git/ci/master/tree/) +and [macros](https://sourceforge.net/p/sardana/macros.git/ci/master/tree/)) +will stay opened until we move the actively maintained plugins. This process +may continue after this SEP gets ACCEPTED. + +### Advices on how to organize plugins projects + +This SEP leaves to the plugin developer the decision on how to organize the +project. However based on years of experience of developing Sardana plugins +we have observed some common patterns and questions that emerge to the +developer on how to organize the project. The Community will maintain a +document with a set of advices, analysis of possible scenarios and lessons +learnt on how to organize plugins projects but won't give neither clear +answers nor rules. This document is out of the scope of this SEP is +we anticipate it to evolve in the future and be very dynamic. + +### Catalogue + +A new repository, called sardana-plugins, will be added to the sardana-org +GitHub organization. This repository will not contain any of the plugins +itself but will serve as a catalogue of all the plugins. The only role of this +catalogue will be to list all the plugins together with the information +on where to find more information about them e.g. links to the project pages +or artifacts. Github searches over this repository will be useful when looking +for a plugin. + +The catalogue will group plugins in categories. The categories may change in + the future and new categories may be added. Also plugins may change between + categories in order to facilitate the users the process of finding them. + +Initially the following categories will be created: + +* Hardware - lists plugins for specific hardware like, motor controllers, + counting cards, etc. Example: IcePAP, Pmac, NI6602. +* Instrument - lists plugins for a specific instrument like, tables, + monochromators, attenuators. Example: three-legged table, DCM. +* System - lists plugins for the complete systems e.g. beamlines, laboratories. +* Software/Interfaces - lists plugins for interacting with other control +systems, frameworks e.g. Lima, Tango, Taurus. +* Other - lists plugins that does not meet any other criteria. + +See Appendix 1 for alternative options that were evaluated. + +The sardana-plugins repository will be managed exactly the same +as the sardana repository (administrators, push permissions, etc.). In order +to add a new plugin to the catalogue, one would need to open a PR with the +necessary changes in the catalogue. Anyone interested in receiving updates +on new plugins in the catalogue will just need to subscribe to this GitHub +repository. + +Implementation +-------------- + +### Old third-party repositories + +* Whenever a plugin module is moved away from the old repository it is +necessary to delete this module from the old location. +* A README file gets added to the old repository with information about this +SEP and location of the plugins catalogue. + +### Advices on how to organize plugins projects + +Currently the advices on how to organize plugins projects are written in +this [wiki page](https://github.com/sardana-org/sardana/wiki/How-to-organize-your-plugin-project). +This location may change in the future without affecting this SEP + +### Catalogue + +1. Implement sardana-plugins catalogue using the markdown format with one file + per category. The plugin projects are listed alphabetically within the + category. This format and organization of files may change in the future + not affecting this SEP. +2. Start accepting PR to the sardana-plugins catalogue whenever this SEP gets + into the CANDIDATE state. + +Appendix 1 +---------- +**Alternative Option 1** + +(This option was discarded when discussing the SEP but is kept here for +reference) + +This is a conservative option. It simply reflects the organization of +the current repositories and adds two more categories: Recorders and GUIs. + +Catalogue will be divided in the following categories: +* Motor controllers +* Pseudomotor controllers +* Counter/timer controllers +* Pseudocounter controllers +* I/O register controllers +* 0D controllers +* 1D controllers +* 2D controllers +* Trigger/gate controllers +* Macros +* Recorders +* GUIs + +**Alternative Option 2** + +(This option was discarded when discussing the SEP but is kept here for +reference) + +Mix of selected categories and alternative option 1. The catalogue +will have all the categories from the alternative option 1 +and the System category from the selected categories. The extra System +category is because maintaining an up-to-date catalogue of plugins from a +system like a beamline is not realistic. + + + +Changes +------- + +- 2017-04-03 [reszelaz](https://github.com/reszelaz) Create SEP16 draft +- 2019-07-16 [reszelaz](https://github.com/reszelaz) Rename register to +catalogue +- 2019-07-16 [reszelaz](https://github.com/reszelaz) Move to CANDIDATE after +meeting with DESY, MAXIV and SOLARIS +- 2019-11-28 [reszelaz](https://github.com/reszelaz) Move to ACCEPTED after +meeting with DESY, MAXIV and SOLARIS + diff --git a/doc/source/sep/index.md b/doc/source/sep/index.md index 249c630033..7c40d204a5 100644 --- a/doc/source/sep/index.md +++ b/doc/source/sep/index.md @@ -26,9 +26,10 @@ Proposals list [SEP13][] | REJECTED (moved to [TEP13][]) | Unified plugins support in Taurus & Sardana [SEP14][] | DRAFT | MSENV taurus schema [SEP15][] | ACCEPTED | Moving Sardana to Github - [SEP16][] | DRAFT | Plugins (controllers, macros, etc.) register + [SEP16][] | ACCEPTED | Plugins (controllers, macros, etc.) catalogue [SEP17][] | DRAFT | Ongoing acquisition formalization and implementation [SEP18][] | ACCEPTED | Extend acquisition and synchronization concepts for SEP2 needs + [SEP19][] | DRAFT | Refactor plugin system @@ -49,10 +50,10 @@ Proposals list [SEP13]: http://www.sardana-controls.org/sep/?SEP13.md [SEP14]: http://www.sardana-controls.org/sep/?SEP14.md [SEP15]: http://www.sardana-controls.org/sep/?SEP15.md -[SEP16]: https://github.com/reszelaz/sardana/blob/sep16/doc/source/sep/SEP16.md +[SEP16]: http://www.sardana-controls.org/sep/?SEP16.md [SEP17]: https://github.com/reszelaz/sardana/blob/sep17/doc/source/sep/SEP17.md [SEP18]: http://www.sardana-controls.org/sep/?SEP18.md - +[SEP19]: https://github.com/reszelaz/sardana/blob/sep19/doc/source/sep/SEP19.md [TEP3]: http://www.taurus-scada.org/tep/?TEP3.md [TEP13]: http://www.taurus-scada.org/tep/?TEP13.md diff --git a/doc/source/sphinxext/ipython_console_highlighting.py b/doc/source/sphinxext/ipython_console_highlighting.py index 97bb6e0ed9..29ac0127b8 100644 --- a/doc/source/sphinxext/ipython_console_highlighting.py +++ b/doc/source/sphinxext/ipython_console_highlighting.py @@ -61,7 +61,7 @@ class IPythonConsoleLexer(Lexer): In [2]: a Out[2]: 'foo' - In [3]: print a + In [3]: print(a) foo In [4]: 1 / 0 diff --git a/doc/source/sphinxext/sardanaextension.py b/doc/source/sphinxext/sardanaextension.py index 5cc2837190..e767935c32 100644 --- a/doc/source/sphinxext/sardanaextension.py +++ b/doc/source/sphinxext/sardanaextension.py @@ -65,9 +65,10 @@ def process_param(line): new_lines.append('%s:type %s: %s' % (prefix, param_name, klass)) new_lines.append('%s:param %s: %s' % (prefix, param_name, desc)) - except Exception, e: - print "Sardana sphinx extension: Not able to process param: '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("Sardana sphinx extension: Not able to process param: '%s'" + % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines @@ -85,9 +86,10 @@ def process_return(line): desc = desc[pos + 1:] new_lines.append('%s:rtype: %s' % (prefix, klass)) new_lines.append('%s:return: %s' % (prefix, desc)) - except Exception, e: - print "Sardana sphinx extension: Not able to process 'return': '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("Sardana sphinx extension: Not able to process 'return': '%s'" + % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines @@ -105,9 +107,10 @@ def process_raise(line): klass = "(" + process_type(elem_type, obj_type='exc') + ")" desc = desc[pos + 1:] new_lines.append('%s:raise: %s %s' % (prefix, klass, desc)) - except Exception, e: - print "Sardana sphinx extension: Not able to process 'raise': '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("Sardana sphinx extension: Not able to process 'raise': '%s'" + % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines @@ -192,7 +195,8 @@ def process_signature(app, what, name, obj, options, signature, return_annotatio if hasattr(obj, "__wrapped__"): if what == "method": from taurus.core.util.wrap import wrapped - obj = wrapped(obj) + # import pdb; pdb.set_trace() + # obj = wrapped(obj) signature = _format_method_args(obj) return signature, return_annotation diff --git a/doc/source/sphinxext/spock_console_highlighting.py b/doc/source/sphinxext/spock_console_highlighting.py index 0ccfaaa898..d77adfaffd 100644 --- a/doc/source/sphinxext/spock_console_highlighting.py +++ b/doc/source/sphinxext/spock_console_highlighting.py @@ -67,7 +67,7 @@ class SpockConsoleLexer(Lexer): LAB-01 [2]: a Result [2]: 'foo' - LAB-01 [3]: print a + LAB-01 [3]: print(a) foo LAB-01 [4]: 1 / 0 diff --git a/doc/source/users/adding_elements.rst b/doc/source/users/adding_elements.rst index dc9aff1b4d..198d42732b 100644 --- a/doc/source/users/adding_elements.rst +++ b/doc/source/users/adding_elements.rst @@ -23,8 +23,14 @@ plugin class into the Sardana. To check if it is already loaded use the :class:`~sardana.macroserver.macros.lists.lsctrl` macro. If it is not, you will need to configure the :ref:`controller plugin discovery path ` (``PoolPath`` property) and either restart the Sardana server or call the -:class:`~sardana.macroserver.macros.expert.addctrllib` macro. After that -check again with the list macro if the controller class is present and if +:class:`~sardana.macroserver.macros.expert.addctrllib` macro:: + + Pool__.put_property({"PoolPath":[""]}) + + Example: + Pool_demo1_1.put_property({"PoolPath":["/home/vagrant/controllers"]}) + +After that check again with the list macro if the controller class is present and if yes let's continue... To create a controller instance you can use @@ -41,6 +47,9 @@ our IcePAP system:: defctrl IcepapController ipap01 Host 10.0.0.30 Port 5000 +.. note:: + In order to use the controller you must define also a motor and use the created controller as a parameter + .. hint:: You can use the :class:`~sardana.macroserver.macros.expert.sar_info` macro to see the roles and properties available for a controller class. diff --git a/doc/source/users/configuration/macroserver.rst b/doc/source/users/configuration/macroserver.rst index a7b25979c3..415929d600 100644 --- a/doc/source/users/configuration/macroserver.rst +++ b/doc/source/users/configuration/macroserver.rst @@ -22,13 +22,16 @@ configurable via :ref:`sardana-macroserver-api-macropath` and :ref:`sardana-macroserver-api-recorderpath` attributes. In case Sardana is used with Tango this configuration is accessible via the ``MacroPath`` and ``RecorderPath`` :class:`~sardana.tango.macroserver.MacroServer.MacroServer` -device properties. +device properties. Both ``MacroPath`` and ``RecorderPath`` properties may +contain an ordered, colon-separated list of directories. Your plugins may need to access to third party Python modules. One can configure the directory where to look for them via :ref:`sardana-macroserver-api-pythonpath` attribute. In case Sardana is used with Tango this configuration is accessible via the ``PythonPath`` :class:`~sardana.tango.macroserver.MacroServer.MacroServer` device property. +``PythonPath`` property may contain an ordered, colon-separated list of +directories. Multiple macros can run concurrently in the MacroServer and the maximum number of these threads is configurable via diff --git a/doc/source/users/environment_variable_catalog.rst b/doc/source/users/environment_variable_catalog.rst index e7f72174fb..e66214594d 100644 --- a/doc/source/users/environment_variable_catalog.rst +++ b/doc/source/users/environment_variable_catalog.rst @@ -334,6 +334,53 @@ Example 2: .. seealso:: More about the extension to recorder map in :ref:`sardana-writing-recorders`. +.. _scanstats: + +ScanStats +~~~~~~~~~ +*Not mandatory, set by* :class:`~sardana.macroserver.macros.scan.scanstats` *macro* + +Stores the last calculated scan statistics. Its value is a dictionary with +the following key - value: + +* Motor - motor name on which the statistics were calculated +* ScanID - scan ID +* Stats - dictionary with channel(s) name as key and value being a dictionary + with the channel's scan statistics: + + * cen - center of FWHM + * com - center of mass of channel data + * fwhm - full-width at half-max of channel data + * int - sum/integral of channel data + * max - maximum of channel data + * maxpos - motor position where the channel reached the maximum + * mean - average of channel data + * min - minimum of channel data + * minpos - motor position where the channel reached the minimum + +For example:: + + {'Motor': 'mot01', + 'ScanID': 288, + 'Stats': {'ct01': {'cen': 5.0, + 'com': 5.000000000000002, + 'fwhm': 10.0, + 'int': 10.099999999999998, + 'max': 0.1, + 'maxpos': 0.0, + 'mean': 0.09999999999999998, + 'min': 0.1, + 'minpos': 0.0}, + 'gct01': {'cen': 4.999999999585752, + 'com': 5.000000000000002, + 'fwhm': 1.9999999568277493, + 'int': 21.289340331309955, + 'max': 1.0, + 'maxpos': 5.0, + 'mean': 0.21078554783475204, + 'min': 2.9802322387695312e-08, + 'minpos': 0.0}}} + .. _sharedmemory: SharedMemory diff --git a/doc/source/users/faq.rst b/doc/source/users/faq.rst index ef3fb9d262..221d85564d 100644 --- a/doc/source/users/faq.rst +++ b/doc/source/users/faq.rst @@ -54,14 +54,13 @@ How to call procedures? The central idea of the Sardana SCADA_ system is to execute procedures centrally. The execution can be started from either: - * *spock* offers a command line interface with commands very similar to SPEC_. - It is documented :ref:`here `. - * Procedures can also be executed with from a :term:`GUI`. Taurus provides - `generic widgets for macro execution `__. + * :ref:`sardana-spock` offers a command line interface with commands very similar to SPEC_. + * Procedures can also be executed with a :term:`GUI`. Taurus provides + :ref:`generic widgets for macro execution `. * Procedures can also be executed in specific :term:`GUI` s and specific Taurus_ widgets. The :term:`API` to execute macros from python code is documented - here ****. + in :mod:`sardana.taurus.core.tango.sardana` and from PyQt code is documented + in :mod:`sardana.taurus.qt.qtcore.tango.sardana`. How to write procedures? ------------------------ @@ -93,6 +92,16 @@ write Sardana controllers and pseudo controllers can be found This documentation also includes the :term:`API` which can be used to interface to the specific hardware item. +How to access Tango from within macros or controllers +-------------------------------------------------------------------------------- +In your macros and controllers almost certainly you will need to access Tango +devices (including Sardana elements) to read or write their attributes, +execute commands, etc. There exist different ways of accessing them: Sardana, +Taurus or PyTango :term:`API`. See more on which to choose in this chapters: + +* :ref:`sardana-macro-accessing-tango` +* :ref:`sardana-controller-accessing-tango` + How to add your own file format? -------------------------------- Documentation how to add your own file format can be found here ****. diff --git a/doc/source/users/getting_started/installing.rst b/doc/source/users/getting_started/installing.rst index 76baeb6cb5..6e2108a799 100644 --- a/doc/source/users/getting_started/installing.rst +++ b/doc/source/users/getting_started/installing.rst @@ -10,13 +10,13 @@ Installing with pip [1]_ (platform-independent) Sardana can be installed using pip. The following command will automatically download and install the latest release of Sardana (see -pip --help for options):: +pip3 --help for options):: - pip install sardana + pip3 install sardana You can test the installation by running:: - python -c "import sardana; print sardana.Release.version" + python3 -c "import sardana; print(sardana.Release.version)" Installing from PyPI manually [2]_ (platform-independent) @@ -28,11 +28,11 @@ You may alternatively install from a downloaded release package: #. Extract the downloaded source into a temporary directory and change to it #. run:: - python setup.py install + python3 setup.py install You can test the installation by running:: - python -c "import sardana; print sardana.Release.version" + python3 -c "import sardana; print(sardana.Release.version)" Linux (Debian-based) -------------------- @@ -45,7 +45,7 @@ doing (as root):: You can test the installation by running:: - python -c "import sardana; print sardana.Release.version" + python3 -c "import sardana; print(sardana.Release.version)" (see more detailed instructions in `this step-by-step howto `__) @@ -58,7 +58,7 @@ Windows #. Run the installation executable #. test the installation:: - C:\Python27\python -c "import sardana; print sardana.Release.version" + C:\Python35\python3 -c "import sardana; print(sardana.Release.version)" Windows installation shortcut ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -90,7 +90,7 @@ You can clone sardana from the main git repository:: Then, to work in editable mode, just do:: - pip install -e ./sardana + pip3 install -e ./sardana Note that you can also fork the git repository in github to get your own github-hosted clone of the sardana repository to which you will have full @@ -107,39 +107,39 @@ Dependencies Sardana has dependencies on some python libraries: -- Sardana uses Tango as the middleware so you need PyTango_ 7 or later +- Sardana uses Tango as the middleware so you need PyTango_ 9.2.5 or later installed. You can check it by doing:: - python -c 'import PyTango; print PyTango.Release.version' + python3 -c 'import tango; print(tango.__version__)' -- Sardana clients are developed with Taurus so you need Taurus_ 3.6.0 or later +- Sardana clients are developed with Taurus so you need Taurus_ 4.5.4 or later installed. You can check it by doing:: - python -c 'import taurus; print taurus.Release.version' + python3 -c 'import taurus; print(taurus.Release.version)' -- Sardana operate some data in the XML format and requires lxml_ library 2.1 or +- Sardana operate some data in the XML format and requires lxml_ library 2.3 or later. You can check it by doing:: - python -c 'import lxml.etree; print lxml.etree.LXML_VERSION' + python3 -c 'import lxml.etree; print(lxml.etree.LXML_VERSION)' -- spock (Sardana CLI) requires itango 0.0.1 or later [3]_. +- spock (Sardana CLI) requires itango 0.1.6 or later [3]_. .. rubric:: Footnotes .. [1] This command requires super user previledges on linux systems. If your user has them you can usually prefix the command with *sudo*: - ``sudo pip -U sardana``. Alternatively, if you don't have + ``sudo pip3 -U sardana``. Alternatively, if you don't have administrator previledges, you can install locally in your user - directory with: ``pip --user sardana`` + directory with: ``pip3 --user sardana`` In this case the executables are located at /.local/bin. Make sure the PATH is pointing there or you execute from there. .. [2] *setup.py install* requires user previledges on linux systems. If your user has them you can usually prefix the command with *sudo*: - ``sudo python setup.py install``. Alternatively, if you don't have + ``sudo python3 setup.py install``. Alternatively, if you don't have administrator previledges, you can install locally in your user directory - with: ``python setup.py install --user`` + with: ``python3 setup.py install --user`` In this case the executables are located at /.local/bin. Make sure the PATH is pointing there or you execute from there. diff --git a/doc/source/users/overview.rst b/doc/source/users/overview.rst index 3f9a774ab8..ac5e88907c 100644 --- a/doc/source/users/overview.rst +++ b/doc/source/users/overview.rst @@ -43,8 +43,8 @@ application should serve as a reminder of this final goal. :align: center :width: 500 -Inside this application we have many features common to other beamline control -applications or w some accelerator applications. The following screen shot +Inside this application we have many features common to other beamline control +applications or to some accelerator applications. The following screen shot shows such a standard application which has been done without programming - just by configuring the application. This illustrates one of the design guidelines in Sardana: Always provide a generic interface which can be diff --git a/doc/source/users/scan.rst b/doc/source/users/scan.rst index 6a98d7f4f4..29c7236c12 100644 --- a/doc/source/users/scan.rst +++ b/doc/source/users/scan.rst @@ -206,5 +206,27 @@ The snapshot is saved only once during a scan, on the very beginning. The exact way the snapshot data is saved depends on the :ref:`recorder ` and scan file format being used. +Scan statistics +--------------- + +Sardana may automatically calculate some basic statistics over the scan +results e.g., max, mean, FWHM, etc. + +In order to enable the statistics calculation you just need to attach +the :class:`~sardana.macroserver.macros.scan.scanstats` macro to the +``post-scan`` hook place (see :ref:`hook documentation ` +for more info). + +Apart from printing the statistics by the scanstats macro these are stored in +the door's :ref:`scanstats` environment variable. This way some other macro +can use them e.g., + +* move the scanned motor to the position where a given channel + reached the maximum value (:class:`~sardana.macroserver.macros.standard.pic`) +* move the scanned motor to center position of FWHM + (:class:`~sardana.macroserver.macros.standard.cen`) + + + .. _SEP6: http://www.sardana-controls.org/sep/?SEP6.md .. _Tango: http://www.tango-controls.org diff --git a/doc/source/users/spock.rst b/doc/source/users/spock.rst index 487750e880..13652d1125 100644 --- a/doc/source/users/spock.rst +++ b/doc/source/users/spock.rst @@ -57,7 +57,9 @@ Afterward, spock :term:`CLI` will start normally: Spock's sardana extension 1.0 loaded with profile: spockdoor (linked to door 'LAB-01-D01') - LAB-01-D01 [1]: + LAB-01-D01 [1]: +.. note:: + If you want to connect to another gate you need to create a new spock profile. Starting spock with a custom profile ------------------------------------ @@ -188,11 +190,42 @@ Stopping macros --------------- Some macros may take a long time to execute. To stop a macro in the middle of -its execution type :kbd:`Control+c`. +its execution type :kbd:`Control+c`. If the stopping process last too long, +you may trigger the aborting process with a second :kbd:`Control+c`. +Here be patient, further issuing of :kbd:`Control+c` may leave your macro +in an uncontrolled way. Use them only if you are sure that the aborting +process will not bring your system to a safe state. Macros that move motors or acquire data from sensors will automatically stop all motion and/or all acquisition. +While stopping and aborting macros Spock reports you what happens behind the +scene with informative messages: + +.. sourcecode:: spock + + LAB-01-D01 [1]: ascan mot01 0 10 100 0.1 + Operation will be saved in /tmp/test.h5 (HDF5::NXscan from NXscanH5_FileRecorder) + Scan #342 started at Wed Sep 9 23:01:14 2020. It will take at least 0:00:10.174246 + tg_test + #Pt No mot01 ct01 gct01 double_scalar dt + 0 0 0.1 2.98023e-08 243.47 0.0967791 + 1 0.1 0.1 5.91929e-08 243.47 0.239136 + 2 0.2 0.1 1.1595e-07 243.47 0.384191 + ^C + Ctrl-C received: Stopping... + Stopping Motion(['mot01']) reserved by ascan + Motion(['mot01']) stopped + Stopping mntgrp_expconf reserved by ascan + mntgrp_expconf stopped + Stopping /monitor reserved by ascan + Stopping /mirror reserved by ascan + Stopping /slit reserved by ascan + Operation saved in /tmp/test.h5 (HDF5::NXscan) + Scan #342 ended at Wed Sep 9 23:01:15 2020, taking 0:00:01.055814. Dead time 33.7% (motion dead time 12.8%) + Executing ascan.on_stop method... + Stopping done! + Exiting spock ------------- @@ -388,10 +421,8 @@ of files: Viewing scan data ~~~~~~~~~~~~~~~~~ -You can show plots for the current scan (i.e. plotting the scan *online*) by: - -* launching the :func:`showscan online ` command -* using the :ref:`show/hide button from the expconf widget ` +You can show plots for the current scan (i.e. plotting the scan *online*) by +launching the :func:`showscan online ` command. Sardana provides also a scan data viewer for scans which were stored in a `NeXus`_ file: :ref:`showscan_ui`. It can be launched using :func:`showscan ` @@ -645,17 +676,17 @@ spock console: Using spock as a Tango_ console ------------------------------- -As metioned in the beggining of this chapter, the sardana spock automatically -activates the PyTango_ 's ipython console extension. Therefore all Tango_ +As mentioned in the beginning of this chapter, the sardana spock automatically +activates the PyTango_ 's ipython console extension [#]_. Therefore all Tango_ features are automatically available on the sardana spock console. For example, -creating a :class:`~PyTango.DeviceProxy` will work inside the sardana spock +creating a :class:`tango.DeviceProxy` will work inside the sardana spock console: .. sourcecode:: spock - LAB-01-D01 [1]: tgtest = PyTango.DeviceProxy("sys/tg_test/1") + LAB-01-D01 [1]: tgtest = Device("sys/tg_test/1") - LAB-01-D01 [2]: print( tgtest.state() ) + LAB-01-D01 [2]: print(tgtest.state()) RUNNING .. rubric:: Footnotes diff --git a/doc/source/users/standard_macro_catalog.rst b/doc/source/users/standard_macro_catalog.rst index 1d5a7207f7..bd9f1e2731 100644 --- a/doc/source/users/standard_macro_catalog.rst +++ b/doc/source/users/standard_macro_catalog.rst @@ -88,6 +88,7 @@ environment related macros :columns: 5 * :class:`~sardana.macroserver.macros.env.lsenv` + * :class:`~sardana.macroserver.macros.env.genv` * :class:`~sardana.macroserver.macros.env.senv` * :class:`~sardana.macroserver.macros.env.usenv` * :class:`~sardana.macroserver.macros.env.dumpenv` @@ -117,8 +118,9 @@ list related macros * :class:`~sardana.macroserver.macros.lists.lsmac` * :class:`~sardana.macroserver.macros.lists.lsmaclib` * :class:`~sardana.macroserver.macros.env.lsgh` + * :class:`~sardana.macroserver.macros.env.lssnap` -measurement configuration macros +experiment configuration macros -------------------------------- .. hlist:: @@ -126,6 +128,14 @@ measurement configuration macros * :class:`~sardana.macroserver.macros.expert.defmeas` * :class:`~sardana.macroserver.macros.expert.udefmeas` + * :class:`~sardana.macroserver.macros.expconf.set_meas` + * :class:`~sardana.macroserver.macros.expconf.get_meas` + * :class:`~sardana.macroserver.macros.expconf.set_meas_conf` + * :class:`~sardana.macroserver.macros.expconf.get_meas_conf` + * :class:`~sardana.macroserver.macros.expconf.defsnap` + * :class:`~sardana.macroserver.macros.expconf.udefsnap` + * :class:`~sardana.macroserver.macros.expconf.lssnap` + * :class:`~sardana.macroserver.macros.standard.plotselect` general hooks macros -------------------- @@ -210,3 +220,7 @@ scan related macros :columns: 5 * :class:`~sardana.macroserver.macros.standard.newfile` + * :class:`~sardana.macroserver.macros.scan.scanstats` + * :class:`~sardana.macroserver.macros.standard.where` + * :class:`~sardana.macroserver.macros.standard.pic` + * :class:`~sardana.macroserver.macros.standard.cen` diff --git a/doc/source/users/taurus/experimentconfiguration.rst b/doc/source/users/taurus/experimentconfiguration.rst index 5d75849511..25fe0a7308 100644 --- a/doc/source/users/taurus/experimentconfiguration.rst +++ b/doc/source/users/taurus/experimentconfiguration.rst @@ -102,25 +102,19 @@ a given channel or its controller: .. _expconf_ui_showplots: -Show / Hide current scan plot(s) +View current scan plot(s) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Experiment Configuration widget provides a button to show/hide plots of the -current scan. When this button is checked, the values of `plot type` and -`plot axes` :ref:`in the channel configuration ` -determine how many plot widgets will be spawned to show the channels involved -in a scan when the scan is run. +Plots need to be configured previously as explained in the +:ref:`channel configuration ` +(plot type and plot axes). +Running a scan will spawn a panel on taurusgui with the plot. The number of +panels that will spawn is defined in the +:ref:`channel configuration `. +If the configuration hasn't been changed, a new scan will overwrite the previous plots. -.. figure:: /_static/expconf-showplot.png - :width: 100% - :figwidth: 100% - :align: center - - Button for enabling/disabling plots of the current scan. - -.. note:: This button may in some contexts be disabled (e.g. by default on - sardana-aware TaurusGUIs) +Plots can also be seen with spock's command ``showscan online``. .. _expconf_ui_snapshot_group: diff --git a/doc/source/users/taurus/index.rst b/doc/source/users/taurus/index.rst index e642cee2fd..d34005f17f 100644 --- a/doc/source/users/taurus/index.rst +++ b/doc/source/users/taurus/index.rst @@ -1,7 +1,7 @@ .. _sardana-taurus: ================================ -Sardana Taurus Extension widgets +Sardana-Taurus Extension widgets ================================ @@ -17,3 +17,10 @@ Sardana provides several :mod:`taurus`-based widgets for being used in GUIs Sardana Editor Showscan PoolMotorTV + PoolChannelTV + QtSpock + +If you do not find a widget which meets your requirements and plan to develop +a new one consider using +:ref:`Sardana-Taurus Qt model classes `. + diff --git a/doc/source/users/taurus/macrobutton.rst b/doc/source/users/taurus/macrobutton.rst index f302876770..cb62e6211e 100644 --- a/doc/source/users/taurus/macrobutton.rst +++ b/doc/source/users/taurus/macrobutton.rst @@ -1,3 +1,5 @@ +.. _macrobutton: + MacroButton User’s Interface ----------------------------- diff --git a/doc/source/users/taurus/macroexecutor.rst b/doc/source/users/taurus/macroexecutor.rst index b3675dfa6c..d6bfa18b5c 100644 --- a/doc/source/users/taurus/macroexecutor.rst +++ b/doc/source/users/taurus/macroexecutor.rst @@ -53,8 +53,11 @@ Options:: The model list is optional and is a space-separated list of two device names: macro server and door. -If not provided at the application startup, models can be later on changed in configuration dialog. - +If not provided at the application startup, models can be later on changed in configuration dialog. + +If the macro server and door are changed in the configuration dialog, the favourites list and the history will +be discarded. + Extra functionalities: - Changing macro configuration @@ -143,10 +146,23 @@ with current macro and its current settings. To restore macro from favourites list just select it in the list and macro parameters editor will immediately populate with stored settings. -- modifying favouites list +- modifying favourites list First select favourite macro and buttons with arrows becomes enable (if it is feasible to change order) - removing a favourite -First select favourite macro, button with '-' sign appears enabled. After pressing this button, previously selected macro disappears from the list. \ No newline at end of file +First select favourite macro, button with '-' sign appears enabled. After pressing this button, previously selected macro disappears from the list. + +.. _using_history_viewer: + +Using history viewer +-------------------- + +Once a macro is used, it gets registered in the history viewer with the same +values it has been executed. + +To load a macro from the history viewer, click on the macro in the history +viewer. + +To remove all history, click on the *bin* button. \ No newline at end of file diff --git a/doc/source/users/taurus/poolchanneltv.rst b/doc/source/users/taurus/poolchanneltv.rst new file mode 100644 index 0000000000..848c042238 --- /dev/null +++ b/doc/source/users/taurus/poolchanneltv.rst @@ -0,0 +1,30 @@ +PoolChannelTV User’s Interface +------------------------------ + +Sardana provides a widget to display and operate any Sardana channel. +As all Taurus widget it needs at least a model, but several can be given +to this widget. The widget exports the Sardana channel models as Taurus attributes. + +The :class:`~sardana.taurus.qt.qtgui.extra_pool.poolchannel.PoolChannelTV` +allows: + + - Start :ref:`experimental channel acquisition` + after prior setting of integration time + - Stop the acquisition + - Monitor the channel's value while acquiring or after the acquisition + +.. image:: /_static/pctv.png + :align: center + +Moreover, this widget allows you to access to the channel's configuration via a +context menu (right-click over the channel name) - see the image bellow. + +.. image:: /_static/pctv_attr_editor.png + :align: center + + + + + + + diff --git a/doc/source/users/taurus/qtspock.rst b/doc/source/users/taurus/qtspock.rst new file mode 100644 index 0000000000..c5579cd2bc --- /dev/null +++ b/doc/source/users/taurus/qtspock.rst @@ -0,0 +1,37 @@ +.. _qtspock: + +QtSpock +------- + +.. note:: + The QtSpock widget has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including its removal) may occur if + deemed necessary by the core developers. + +Sardana provides `qtconsole `_-based widget to +run :ref:`sardana-spock`. + +.. image:: /_static/qtspock.png + :align: center + +It provides most of the Spock features and can be launched either +as a standalone application + +.. code-block:: console + + python3 -m sardana.taurua.qt.qtgui.extran_sardana.qtspock + +or embedded in the :ref:`taurusgui_ui`: (when :ref:`panelcreation` use: +`sardana.taurus.qt.qtgui.extra_sardana.qtspock` module and +`~sardana.taurus.qt.qtgui.extra_sardana.qtspock.QtSpockWidget` +class). + +QtSpock requires a spock profile *spockdoor* to be created and upgraded +beforehand (use ``spock`` command to create/upgrade profile). + +Below you can find a list of features still not supported by QtSpock: + +* block update macros e.g. `~sardana.macroserver.macros.standard.umv` are not + updated in block but their output is simply appended +* `~sardana.spock.magic.edmac` magic command diff --git a/doc/source/users/taurus/showscan.rst b/doc/source/users/taurus/showscan.rst index 47a0cc3889..df040ed9e4 100644 --- a/doc/source/users/taurus/showscan.rst +++ b/doc/source/users/taurus/showscan.rst @@ -17,6 +17,15 @@ Showscan online is a simplified Taurus application which provides live scan plots. The number of plots and their configuration depends on the :ref:`measurement group plotting configuration `. +It can be launched from Spock using the :class:`~sardana.spock.magic.showscan` +command: + +.. sourcecode:: spock + + LAB-01-D01 [1]: showscan online + +or with the console command: ``showscan``. + When started it gets populated with empty plots of the :ref:`active measurement group `. As soon as you start scanning the curves and the legend will start to appear. Even if you change @@ -26,10 +35,18 @@ behavior is to keep the plotted data available for inspection after the scan execution. .. figure:: /_static/showscan-online.png - :height: 600 - Showscan online plotting two physical counters against the point - number and a pseudo counter against the moveable. + Showscan online plotting one physical counter against the motor's + position. + +Another interesting feature of this widget is its multi plot organization when +you ask for plotting multiple curves. You can choose between having a separate +plot per curve or group curves by the selected x axis + +.. figure:: /_static/showscan-online-multi.png + + Showscan online plotting three physical counters against the motor's + position on separate plots. ---------------- Showscan offline diff --git a/scripts/upgrade/upgrade_env.py b/scripts/upgrade/upgrade_env.py new file mode 100644 index 0000000000..2035a8c7f8 --- /dev/null +++ b/scripts/upgrade/upgrade_env.py @@ -0,0 +1,96 @@ +""" This serves to upgrade MacroServer environment from Python 2 to Python 3: + +IMPORTANT: +1. IT HAS TO BE USED WITH PYTHON 2.7!!! +2. CONDA python 2 package is missing the standard dbm package so this + script will fail inside a conda python 2 environment +3. a new env file with an additional .db extension will be created. You + should **NOT** change the macroserver EnvironmentDb property. The dbm + will figure out automatically the file extension +4. a backup of the original environment will be available with the + extension .py2 +5. run the script with the same OS user as you run the Sardana/MacroServer + server or after running the script give write permission to the newly + created environment file to the OS user that you run the server + +Usage: python upgrade_env.py +""" +import sys +import os +import shelve +import contextlib + +import PyTango + +assert sys.version_info[:2] in ((2, 6), (2, 7)),\ + "Must run with python 2.6 or 2.7" + +DefaultEnvBaseDir = "/tmp/tango" +DefaultEnvRelDir = "%(ds_exec_name)s/%(ds_inst_name)s/macroserver.properties" + + +def get_ds_full_name(dev_name): + db = PyTango.Database() + db_dev = PyTango.DeviceProxy(db.dev_name()) + return db_dev.DbGetDeviceInfo(dev_name)[1][3] + + +def get_ms_properties(ms_name, ds_name): + db = PyTango.Database() + prop = db.get_device_property(ms_name, "EnvironmentDb") + ms_properties = prop["EnvironmentDb"] + if not ms_properties: + dft_ms_properties = os.path.join( + DefaultEnvBaseDir, + DefaultEnvRelDir) + ds_exec_name, ds_inst_name = ds_name.split("/") + ms_properties = dft_ms_properties % { + "ds_exec_name": ds_exec_name, + "ds_inst_name": ds_inst_name} + else: + ms_properties = ms_properties[0] + ms_properties = os.path.normpath(ms_properties) + return ms_properties + + +def dbm_shelve(filename, backend, flag="c"): + # NOTE: dbm appends '.db' to the end of the filename + if backend == "gnu": + import gdbm + backend_dict = gdbm.open(filename, flag) + elif backend == "dumb": + import dumbdbm + backend_dict = dumbdbm.open(filename, flag) + return shelve.Shelf(backend_dict) + + +def migrate_file(filename, backend): + assert not filename.endswith('.db'), \ + "Cannot migrate '.db' (It would be overwritten)" + os.rename(filename, filename + '.py2') + with contextlib.closing(shelve.open(filename + '.py2')) as src_db: + data = dict(src_db) + with contextlib.closing(dbm_shelve(filename, backend)) as dst_db: + dst_db.update(data) + + +def upgrade_env(ms_name, backend): + db = PyTango.Database() + try: + ms_info = db.get_device_info(ms_name) + ds_name = ms_info.ds_full_name + except AttributeError: + ds_name = get_ds_full_name(ms_name) + env_filename = get_ms_properties(ms_name, ds_name) + migrate_file(env_filename, backend) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("python upgrade_env.py ") # noqa + sys.exit(1) + ms_name = sys.argv[1] + backend = sys.argv[2] + if backend not in ("gnu", "dumb"): + raise ValueError("{0} backend is not supported") + upgrade_env(ms_name, backend) diff --git a/setup.py b/setup.py index 43d54bc37c..faa673efb5 100644 --- a/setup.py +++ b/setup.py @@ -51,27 +51,18 @@ def get_release_info(): ] requires = [ - 'PyTango (>=7.2.3)', - # when using PyTango < 9 the dependency is >= 0.0.1 and < 0.1.0 - # when using PyTango >= 9 the dependency is >= 0.1.6 - 'itango (>=0.0.1)', - # for Taurus3 requires >= 3.12 (special version from - # taurus-org/taurus@3.x-sdn2.5.1 branch) - # for Taurus4 requires >= 4.5.0 - 'taurus (>= 3.12)', - 'lxml (>=2.1)', - # ordereddict is necessary for Python < 2.6 - 'ordereddict' + 'PyTango (>=9.2.5)', + 'itango (>=0.1.6)', + 'taurus (>= 4.7.0)', + 'lxml (>=2.3)', ] install_requires = [ - 'PyTango>=7.2.3', - 'itango>=0.0.1', - # for Taurus3 requires >= 3.10 (special version from - # taurus-org/taurus@3.x-sdn2.5.1 branch) - # for Taurus4 requires >= 4.5.0 - 'taurus>=3.12,!=4.0,!=4.1,!=4.3,!=4.4', - 'lxml>=2.1' + 'PyTango>=9.2.5', + 'itango>=0.1.6', + 'taurus>=4.7.0', + 'lxml>=2.3', + 'click', ] @@ -88,12 +79,19 @@ def get_release_info(): "hklscan = sardana.taurus.qt.qtgui.extra_hkl.hklscan:main", "macroexecutor = sardana.taurus.qt.qtgui.extra_macroexecutor.macroexecutor:main", "sequencer = sardana.taurus.qt.qtgui.extra_macroexecutor.sequenceeditor:main", - "ubmatrix = sardana.taurus.qt.qtgui.extra_hkl.ubmatrix:main" + "ubmatrix = sardana.taurus.qt.qtgui.extra_hkl.ubmatrix:main", + "showscan = sardana.taurus.qt.qtgui.extra_sardana.showscanonline:main" ] -entry_points = {'console_scripts': console_scripts, - 'gui_scripts': gui_scripts, - } +form_factories = [ + "sardana.pool = sardana.taurus.qt.qtgui.extra_pool.formitemfactory:pool_item_factory" # noqa +] + +entry_points = { + 'console_scripts': console_scripts, + 'gui_scripts': gui_scripts, + 'taurus.form.item_factories': form_factories, +} classifiers = [ 'Development Status :: 4 - Beta', @@ -108,7 +106,7 @@ def get_release_info(): 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', 'Operating System :: OS Independent', - 'Programming Language :: Python', + 'Programming Language :: Python :: 3.5', 'Topic :: Scientific/Engineering', 'Topic :: Software Development :: Libraries', ] @@ -132,6 +130,5 @@ def get_release_info(): entry_points=entry_points, provides=provides, requires=requires, - install_requires=install_requires, - test_suite='sardana.test.testsuite.get_sardana_unitsuite', + install_requires=install_requires ) diff --git a/src/sardana/__init__.py b/src/sardana/__init__.py index 7c72456212..3e01575808 100644 --- a/src/sardana/__init__.py +++ b/src/sardana/__init__.py @@ -26,16 +26,15 @@ """This package provides the sardana library""" -import release as __release -import requirements as __requirements - -__requirements.check_requirements() +from . import release as __release class Release: pass -Release.__dict__.update(__release.__dict__) + +for attr, value in __release.__dict__.items(): + setattr(Release, attr, value) Release.__doc__ = __release.__doc__ from .sardanadefs import * diff --git a/src/sardana/macroserver/basetypes.py b/src/sardana/macroserver/basetypes.py index 73e2a262a9..b19e583f23 100644 --- a/src/sardana/macroserver/basetypes.py +++ b/src/sardana/macroserver/basetypes.py @@ -141,7 +141,7 @@ def getNonAttrItemList(self): def __build_base_types(): - for sardana_type, info in INTERFACES.items(): + for sardana_type, info in list(INTERFACES.items()): _, doc = info class _I(ElementParamInterface): diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py index 5f7b8b225c..728ef8a85a 100644 --- a/src/sardana/macroserver/macro.py +++ b/src/sardana/macroserver/macro.py @@ -26,12 +26,13 @@ """This module contains the class definition for the MacroServer generic scan""" -from __future__ import with_statement -from __future__ import print_function + +import collections +import numbers __all__ = ["OverloadPrint", "PauseEvent", "Hookable", "ExecMacroHook", "MacroFinder", "Macro", "macro", "iMacro", "imacro", - "MacroFunc", "Type", "ParamRepeat", "Table", "List", "ViewOption", + "MacroFunc", "Type", "Table", "List", "ViewOption", "LibraryError", "Optional"] __docformat__ = 'restructuredtext' @@ -43,7 +44,7 @@ import ctypes import weakref import operator -import StringIO +import io import threading import traceback @@ -54,20 +55,16 @@ from sardana.sardanadefs import State from sardana.util.wrap import wraps +from sardana.util.thread import _asyncexc -from sardana.macroserver.msparameter import Type, ParamType, ParamRepeat, \ - Optional +from sardana.macroserver.msparameter import Type, ParamType, Optional from sardana.macroserver.msexception import StopException, AbortException, \ - MacroWrongParameterType, UnknownEnv, UnknownMacro, LibraryError + ReleaseException, MacroWrongParameterType, UnknownEnv, UnknownMacro, \ + LibraryError from sardana.macroserver.msoptions import ViewOption from sardana.taurus.core.tango.sardana.pool import PoolElement -asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc -# first define the async exception function args. This is -# absolutely necessary for 64 bits machines. -asyncexc.argtypes = (ctypes.c_long, ctypes.py_object) - class OverloadPrint(object): @@ -185,7 +182,7 @@ def getAllowedHookHints(self): return self.__class__.hints.get('allowsHooks') def getHints(self): - return self._getHookHintsDict().keys() + return list(self._getHookHintsDict().keys()) def getHooks(self, hint=None): """This will return a list of hooks that have the given hint. Two reserved @@ -286,7 +283,7 @@ def hooks(self, hooks): if hasattr(self, '_hookHintsDict'): del self._hookHintsDict # create _hookHintsDict - self._getHookHintsDict()['_ALL_'] = zip(*self._hooks)[0] + self._getHookHintsDict()['_ALL_'] = list(zip(*self._hooks))[0] nohints = self._hookHintsDict['_NOHINTS_'] for hook, hints in self._hooks: if len(hints) == 0: @@ -402,8 +399,8 @@ def __call__(self, fn): fn.macro_data = {} fn.param_def = self.param_def fn.result_def = self.result_def - fn.hints = self.env - fn.env = self.hints + fn.hints = self.hints + fn.env = self.env fn.interactive = self.interactive return fn @@ -771,7 +768,12 @@ def sendRecordData(self, data, codec=None): """**Macro API**. Sends the given data to the RecordData attribute of the Door - :param data: (sequence) the data to be sent""" + :param data: data to be sent (must be compatible with the codec) + :type data: object + :param codec: codec to encode data (in Tango server None defaults + to "utf8_json") + :type codec: str or None + """ self._sendRecordData(data, codec) def _sendRecordData(self, data, codec=None): @@ -846,7 +848,7 @@ def print(self, *args, **kwargs): """ fd = kwargs.get('file', sys.stdout) if fd in (sys.stdout, sys.stderr): - out = StringIO.StringIO() + out = io.StringIO() kwargs['file'] = out end = kwargs.get('end', '\n') if end == '\n': @@ -1328,10 +1330,10 @@ def execMacro(self, *args, **kwargs): macro_name = None arg0 = args[0] if len(args) == 1: - if type(arg0) in types.StringTypes: + if isinstance(arg0, str): # dealing with sth like args = ('ascan th 0 100 10 1.0',) macro_name = arg0.split()[0] - elif operator.isSequenceType(arg0): + elif isinstance(arg0, collections.Sequence): # dealing with sth like args = (['ascan', 'th', '0', '100', # '10', '1.0'],) macro_name = arg0[0] @@ -1388,9 +1390,9 @@ def outputBlock(self, line): """**Macro API**. Sends an line tagged as a block to the output :param :obj:`str` line: line to be sent""" - if isinstance(line, (str, unicode)): + if isinstance(line, str): o = line - elif operator.isSequenceType(line): + elif isinstance(line, collections.Sequence): o = "\n".join(line) else: o = str(line) @@ -1470,7 +1472,7 @@ def getObj(self, name, type_class=All, subtype=All, pool=All, reserve=True): automatically reserve the object for this macro [default: True] :return: the object or None if no compatible object is found""" - if not isinstance(name, (str, unicode)): + if not isinstance(name, str): raise self._buildWrongParamExp("getObj", "name", "string", str(type(name))) @@ -1973,7 +1975,7 @@ def _getViewOption(self, name): the environment, sets it to a default value and returns it. ''' view_options = self._getViewOptions() - if not view_options.has_key(name): + if name not in view_options: ViewOption.reset_option(view_options, name) self.setEnv('_ViewOptions', view_options) return view_options[name] @@ -2054,9 +2056,9 @@ def _outputBlock(self, line): Sends a line tagged as a block to the output :param :obj:`str` line: line to be sent""" - if isinstance(line, (str, unicode)): + if isinstance(line, str): o = line - elif operator.isSequenceType(line): + elif isinstance(line, collections.Sequence): o = "\n".join(line) else: o = str(line) @@ -2234,15 +2236,14 @@ def isPaused(self): """**Unofficial Macro API**.""" return self._pause_event.isPaused() - @classmethod - def hasResult(cls): + def hasResult(self): """**Unofficial Macro API**. Returns True if the macro should return a result or False otherwise :return: True if the macro should return a result or False otherwise :rtype: bool """ - return len(cls.result_def) > 0 + return len(self.result_def) > 0 def getResult(self): """**Unofficial Macro API**. Returns the macro result object (if any) @@ -2305,7 +2306,7 @@ def _reserveObjs(self, args): macro""" for obj in args: # isiterable - if not type(obj) in map(type, ([], ())): + if not type(obj) in list(map(type, ([], ()))): # if not operator.isSequenceType(obj) or type(obj) in # types.StringTypes: obj = (obj,) @@ -2332,14 +2333,14 @@ def exec_(self): if isinstance(res, types.GeneratorType): it = iter(res) for i in it: - if operator.isMappingType(i): + if isinstance(i, collections.Mapping): new_range = i.get('range') if new_range is not None: macro_status['range'] = new_range new_step = i.get('step') if new_step is not None: macro_status['step'] = new_step - elif operator.isNumberType(i): + elif isinstance(i, numbers.Number): macro_status['step'] = i macro_status['state'] = 'step' yield macro_status @@ -2361,8 +2362,8 @@ def __prepareResult(self, out): """ if out is None: out = () - if operator.isSequenceType(out) and not type(out) in types.StringTypes: - out = map(str, out) + if isinstance(out, collections.Sequence) and not type(out) in str: + out = list(map(str, out)) else: out = (str(out),) return out @@ -2382,6 +2383,8 @@ def _abortOnError(self): protecting it against exceptions""" try: self.on_abort() + except ReleaseException: + pass except Exception: Logger.error(self, "Error in on_abort(): %s", traceback.format_exc()) @@ -2409,7 +2412,7 @@ def abort(self): th = self._macro_thread th_id = ctypes.c_long(th.ident) Logger.debug(self, "Sending AbortException to %s", th.name) - ret = asyncexc(th_id, ctypes.py_object(AbortException)) + ret = _asyncexc(th_id, ctypes.py_object(AbortException)) i += 1 if ret == 0: # try again @@ -2483,7 +2486,7 @@ class MacroFunc(Macro): def __init__(self, *args, **kwargs): function = kwargs['function'] self._function = function - kwargs['as'] = self._function.func_name + kwargs['as'] = self._function.__name__ if function.param_def is not None: self.param_def = function.param_def if function.result_def is not None: diff --git a/src/sardana/macroserver/macros/demo.py b/src/sardana/macroserver/macros/demo.py index 9d4ae1289c..afc591891f 100644 --- a/src/sardana/macroserver/macros/demo.py +++ b/src/sardana/macroserver/macros/demo.py @@ -23,7 +23,7 @@ """This is the demo macro module""" -from __future__ import print_function + __all__ = ["sar_demo", "clear_sar_demo", "sar_demo_hkl", "clear_sar_demo_hkl"] @@ -85,6 +85,11 @@ def clear_sar_demo(self): for ctrl in SAR_DEMO.get("controllers", ()): self.udefctrl(ctrl) + self.print("Removing instruments...") + pool = self.getPools()[0] + for instrument in SAR_DEMO.get("instruments", ()): + pool.DeleteElement(instrument) + self.unsetEnv(_ENV) self.print("DONE!") @@ -192,14 +197,29 @@ def sar_demo(self): self.print("Setting %s as ActiveMntGrp" % mg_name) self.setEnv("ActiveMntGrp", mg_name) + self.print("Creating instruments: /slit, /mirror and /monitor ...") + pool.createInstrument('/slit', 'NXcollimator') + pool.createInstrument('/mirror', 'NXmirror') + pool.createInstrument('/monitor', 'NXmonitor') + + self.print("Assigning elements to instruments...") + self.getMotor(motor_names[0]).setInstrumentName('/slit') + self.getMotor(motor_names[1]).setInstrumentName('/slit') + self.getPseudoMotor(gap).setInstrumentName('/slit') + self.getPseudoMotor(offset).setInstrumentName('/slit') + self.getMotor(motor_names[2]).setInstrumentName('/mirror') + self.getMotor(motor_names[3]).setInstrumentName('/mirror') + self.getCounterTimer(ct_names[0]).setInstrumentName('/monitor') + controllers = pm_ctrl_name, mot_ctrl_name, ct_ctrl_name, \ zerod_ctrl_name, oned_ctrl_name, twod_ctrl_name, \ tg_ctrl_name, ior_ctrl_name elements = [gap, offset] + motor_names + ct_names + \ zerod_names + oned_names + twod_names + tg_names + \ ior_names + instruments = ["/slit", "/mirror", "/monitor"] d = dict(controllers=controllers, elements=elements, - measurement_groups=[mg_name]) + measurement_groups=[mg_name], instruments=instruments) self.setEnv(_ENV, d) @@ -209,7 +229,7 @@ def sar_demo(self): @macro([["motor", Type.Moveable, None, '']]) def mym2(self, pm): self.output(pm.getMotorNames()) - elements = map(self.getMoveable, pm.elements) + elements = list(map(self.getMoveable, pm.elements)) self.output(elements) self.output(type(pm)) self.output(type(elements[0])) diff --git a/src/sardana/macroserver/macros/discrete.py b/src/sardana/macroserver/macros/discrete.py index f95467990d..76ea8bf74c 100644 --- a/src/sardana/macroserver/macros/discrete.py +++ b/src/sardana/macroserver/macros/discrete.py @@ -51,7 +51,7 @@ def get_configuration(self): return data def has_calibration(self): - return all(['set' in self[x].keys() for x in self.keys()]) + return all(['set' in list(self[x].keys()) for x in list(self.keys())]) def add_point(self, label, pos, setpos, dmin, dmax): point = dict() @@ -65,7 +65,9 @@ def add_point(self, label, pos, setpos, dmin, dmax): else: point['set'] = float(setpos) # If point exists, we use current min, max values - if label in self.keys() and math.isinf(dmin) and math.isinf(dmax): + if (label in list(self.keys()) + and math.isinf(dmin) + and math.isinf(dmax)): p = self[label] min_pos = point['set'] + p['set'] - p['min'] max_pos = point['set'] + p['set'] - p['max'] @@ -185,7 +187,7 @@ def run(self, pseudo): row_head_str = [] value_list = [] - for k, v in conf.items(): + for k, v in list(conf.items()): row_head_str.append(k) _row_values = [k] for i in col_head_str: @@ -196,7 +198,7 @@ def run(self, pseudo): # Sort by position column value_list = sorted(value_list, key=lambda x: x[1]) # Transpose matrix - value_list = map(list, zip(*value_list)) + value_list = list(map(list, list(zip(*value_list)))) # Extract sorted row headers row_head_str = value_list[0] # Extract sorted values diff --git a/src/sardana/macroserver/macros/env.py b/src/sardana/macroserver/macros/env.py index ad7e5a7dfe..e267ae432a 100644 --- a/src/sardana/macroserver/macros/env.py +++ b/src/sardana/macroserver/macros/env.py @@ -23,14 +23,15 @@ """Environment related macros""" -__all__ = ["dumpenv", "load_env", "lsenv", "senv", "usenv", +__all__ = ["dumpenv", "load_env", "lsenv", "senv", "usenv", "genv", "lsvo", "setvo", "usetvo", "lsgh", "defgh", "udefgh"] __docformat__ = 'restructuredtext' +from taurus.core.tango.tangovalidator import TangoDeviceNameValidator from taurus.console.list import List -from sardana.macroserver.macro import Macro, Type, ParamRepeat +from sardana.macroserver.macro import Macro, Type from sardana.macroserver.msexception import UnknownEnv ########################################################################## @@ -56,7 +57,7 @@ class dumpenv(Macro): def run(self): env = self.getGlobalEnv() out = List(['Name', 'Value', 'Type']) - for k, v in env.iteritems(): + for k, v in env.items(): str_v = reprValue(v) type_v = type(v).__name__ out.appendRow([str(k), str_v, type_v]) @@ -71,7 +72,7 @@ class lsvo(Macro): def run(self): vo = self.getViewOptions() out = List(['View option', 'Value']) - for key, value in vo.items(): + for key, value in list(vo.items()): out.appendRow([key, str(value)]) for line in out.genOutput(): @@ -128,8 +129,8 @@ class lsenv(Macro): """Lists the environment in alphabetical order""" param_def = [ - ['macro_list', - ParamRepeat(['macro', Type.MacroClass, None, 'macro name'], min=0), + ['macro_list', [['macro', Type.MacroClass, None, 'macro name'], + {'min': 0}], None, 'List of macros to show environment'], ] @@ -174,17 +175,19 @@ def reprValue(self, v, max=54): class senv(Macro): """Sets the given environment variable to the given value""" - param_def = [['name', Type.Env, None, - 'Environment variable name. Can be one of the following:\n' - ' - - global variable\n' - ' - . - variable value for a specific door\n' - ' - . - variable value for a specific macro\n' - ' - .. - variable value for a specific macro running on a specific door'], - ['value_list', - ParamRepeat(['value', Type.String, None, - 'environment value item'], min=1), - None, 'value(s). one item will eval to a single element. More than one item will eval to a tuple of elements'], - ] + param_def = [ + ['name', Type.Env, None, + 'Environment variable name. Can be one of the following:\n' + ' - - global variable\n' + ' - . - variable value for a specific door\n' + ' - . - variable value for a specific macro\n' + ' - .. - variable value for a ' + 'specific macro running on a specific door'], + ['value_list', [['value', Type.String, None, + 'environment value item'], {'min': 1}], + None, 'value(s). one item will eval to a single element. More than ' + 'one item will eval to a tuple of elements'], + ] def run(self, env, value): if len(value) == 1: @@ -196,12 +199,49 @@ def run(self, env, value): self.output(line) +class genv(Macro): + """Gets the given environment variable""" + + param_def = [ + ["name", Type.Env, None, + "Environment variable name. Can be one of the following:\n" + " - - global variable\n" + " - . - variable value for a specific " + "door\n" + " - . - variable value for a specific" + " macro\n" + " - .. - variable value" + " for a specific macro running on a specific door"], + ] + + def run(self, var): + pars = var.split(".") + door_name = None + macro_name = None + if len(pars) == 1: + key = pars[0] + elif len(pars) > 1: + _, door_name, _ = TangoDeviceNameValidator().getNames(pars[0]) + if door_name is None: # first string is a Macro name + macro_name = pars[0] + if len(pars) == 3: + macro_name = pars[1] + key = pars[2] + else: + key = pars[1] + + env = self.getEnv(key=key, + macro_name=macro_name, + door_name=door_name) + + self.output("{:s} = {:s}".format(str(key), str(env))) + + class usenv(Macro): """Unsets the given environment variable""" param_def = [ - ['environment_list', - ParamRepeat( - ['env', Type.Env, None, 'Environment variable name'], min=1), + ['environment_list', [['env', Type.Env, None, + 'Environment variable name'], {'min': 1}], None, 'List of environment items to be removed'], ] @@ -344,10 +384,10 @@ def run(self): name = hook[0] places = hook[1] for place in places: - if place not in default_dict.keys(): + if place not in list(default_dict.keys()): default_dict[place] = [] default_dict[place].append(name) - for pos in default_dict.keys(): + for pos in list(default_dict.keys()): pos_set = 0 for hook in default_dict[pos]: if pos_set: @@ -379,8 +419,8 @@ class defgh(Macro): param_def = [ ['macro_name', Type.String, None, ('Macro name with parameters. ' 'Ex.: "mv exp_dmy01 10"')], - ['hookpos_list', - ParamRepeat(['position', Type.String, None, 'macro name'], min=1), + ['hookpos_list', [['position', Type.String, None, 'macro name'], + {'min': 1}], None, 'List of positions where the hook has to be executed'], ] diff --git a/src/sardana/macroserver/macros/examples/funcs.py b/src/sardana/macroserver/macros/examples/funcs.py index 2d3d69fecc..375920097c 100644 --- a/src/sardana/macroserver/macros/examples/funcs.py +++ b/src/sardana/macroserver/macros/examples/funcs.py @@ -23,7 +23,7 @@ """Examples of macro functions""" -from __future__ import print_function + __all__ = ["mfunc1", "mfunc2", "mfunc3", "mfunc4", "mfunc5"] diff --git a/src/sardana/macroserver/macros/examples/hooks.py b/src/sardana/macroserver/macros/examples/hooks.py index dcfc176144..83dca9fd5f 100644 --- a/src/sardana/macroserver/macros/examples/hooks.py +++ b/src/sardana/macroserver/macros/examples/hooks.py @@ -44,7 +44,7 @@ class loop(Macro, Hookable): def run(self, start, stop, step): self.info("Starting loop") - for i in xrange(start, stop, step): + for i in range(start, stop, step): self.output("At step %d" % i) self.flushOutput() for hook, hints in self.hooks: diff --git a/src/sardana/macroserver/macros/examples/parameters.py b/src/sardana/macroserver/macros/examples/parameters.py index 681aed83b8..53caffb11c 100644 --- a/src/sardana/macroserver/macros/examples/parameters.py +++ b/src/sardana/macroserver/macros/examples/parameters.py @@ -23,7 +23,7 @@ """This module contains macros that demonstrate the usage of macro parameters""" -from sardana.macroserver.macro import Macro, Type, ParamRepeat +from sardana.macroserver.macro import Macro, Type __all__ = ["pt0", "pt1", "pt2", "pt3", "pt3d", "pt4", "pt5", "pt6", "pt7", "pt7d1", "pt7d2", "pt8", "pt9", "pt10", "pt11", "pt12", "pt13", @@ -193,7 +193,8 @@ def run(self, *args, **kwargs): class pt7d1(Macro): - """Macro with a list of pair Motor,Float. Default value for last ParamRepeat element. + """Macro with a list of pair Motor,Float. Default value for last + repeat parameter element. Usages from Spock, ex.: pt7d1 [[mot1 1] [mot2 3]] pt7d1 mot1 1 mot2 3 @@ -214,7 +215,8 @@ def run(self, *args, **kwargs): class pt7d2(Macro): - """Macro with a list of pair Motor,Float. Default value for both ParamRepeat elements. + """Macro with a list of pair Motor,Float. Default value for both + repeat parameters elements. Usages from Spock, ex.: pt7d2 [[mot1 1] [mot2 3]] pt7d2 mot1 1 mot2 3 @@ -252,18 +254,17 @@ def run(self, *args, **kwargs): class pt9(Macro): - """Same as macro pt7 but with old style ParamRepeat. If you are writing - a macro with variable number of parameters for the first time don't even - bother to look at this example since it is DEPRECATED. + """Same as macro pt7 but with min and max number of repetitions of the + repeat parameter. Usages from Spock, ex.: pt9 [[mot1 1][mot2 3]] pt9 mot1 1 mot2 3 """ param_def = [ - ['m_p_pair', - ParamRepeat(['motor', Type.Motor, None, 'Motor to move'], - ['pos', Type.Float, None, 'Position to move to'], min=1, max=2), + ['m_p_pair', [['motor', Type.Motor, None, 'Motor to move'], + ['pos', Type.Float, None, 'Position to move to'], + {'min': 1, 'max': 2}], None, 'List of motor/position pairs'], ] diff --git a/src/sardana/macroserver/macros/examples/plotting.py b/src/sardana/macroserver/macros/examples/plotting.py index 750c2c56cc..de593146dc 100644 --- a/src/sardana/macroserver/macros/examples/plotting.py +++ b/src/sardana/macroserver/macros/examples/plotting.py @@ -10,7 +10,7 @@ def j0i(x): """Integral form of J_0(x)""" def integrand(phi): return math.cos(x * math.sin(phi)) - return (1.0 / math.pi) * quad(integrand, 0, math.pi)[0] + return (1 / math.pi) * quad(integrand, 0, math.pi)[0] @macro() @@ -19,13 +19,14 @@ def J0_plot(self): x = linspace(0, 20, 200) y = j0(x) x1 = x[::10] - y1 = map(j0i, x1) + y1 = list(map(j0i, x1)) self.pyplot.plot(x, y, label=r'$J_0(x)$') self.pyplot.plot(x1, y1, 'ro', label=r'$J_0^{integ}(x)$') self.pyplot.title( r'Verify $J_0(x)=\frac{1}{\pi}\int_0^{\pi}\cos(x \sin\phi)\,d\phi$') self.pyplot.xlabel('$x$') self.pyplot.legend() + self.pyplot.draw() from numpy import random @@ -36,6 +37,7 @@ def random_image(self): """Shows a random image 32x32""" img = random.random((32, 32)) self.pyplot.matshow(img) + self.pyplot.draw() import numpy @@ -55,10 +57,10 @@ def mandelbrot(self, interactions, density): fractal = numpy.zeros(z.shape, dtype=numpy.uint8) + 255 - finteractions = float(interactions) for n in range(interactions): z *= z z += c mask = (fractal == 255) & (abs(z) > 10) - fractal[mask] = 254 * n / finteractions + fractal[mask] = 254 * n / interactions self.pyplot.imshow(fractal) + self.pyplot.draw() diff --git a/src/sardana/macroserver/macros/examples/scans.py b/src/sardana/macroserver/macros/examples/scans.py index 26823cc2e2..72f120d431 100644 --- a/src/sardana/macroserver/macros/examples/scans.py +++ b/src/sardana/macroserver/macros/examples/scans.py @@ -38,7 +38,7 @@ import numpy -from sardana.macroserver.macro import Macro, Hookable, Type, ParamRepeat +from sardana.macroserver.macro import Macro, Hookable, Type from sardana.macroserver.scan import * @@ -99,7 +99,7 @@ def data(self): return self._gScan.data # the GScan provides scan data def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -171,7 +171,7 @@ def _generator(self): for point_no in range(self.nb_points): step["positions"] = self.starts + point_no * self.interv_sizes step["point_id"] = point_no - for i in xrange(self.repeat): + for i in range(self.repeat): extrainfo["repetition"] = i # !!! yield step @@ -184,7 +184,7 @@ def data(self): return self._gScan.data def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -261,18 +261,18 @@ def _generator(self): step["check_func"] = [] extrainfo = {"cycle": None, "interval": None, "sample": None, } step['extrainfo'] = extrainfo - halfcycle1 = range(self.nr_interv + 1) + halfcycle1 = list(range(self.nr_interv + 1)) halfcycle2 = halfcycle1[1:-1] halfcycle2.reverse() intervallist = halfcycle1 + halfcycle2 point_no = 0 - for cycle in xrange(self.nr_cycles): + for cycle in range(self.nr_cycles): extrainfo["cycle"] = cycle for interval in intervallist: extrainfo["interval"] = interval step["positions"] = numpy.array( [self.start_pos + (interval) * self.interv_size], dtype='d') - for sample in xrange(self.nr_samples): + for sample in range(self.nr_samples): extrainfo["sample"] = sample step["point_id"] = point_no yield step @@ -281,7 +281,7 @@ def _generator(self): # last step for closing the loop extrainfo["interval"] = 0 step["positions"] = numpy.array([self.start_pos], dtype='d') - for sample in xrange(self.nr_samples): + for sample in range(self.nr_samples): extrainfo["sample"] = sample step["point_id"] = point_no yield step @@ -296,7 +296,7 @@ def data(self): return self._gScan.data def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -318,8 +318,9 @@ class regscan(Macro): ['integ_time', Type.Float, None, 'Integration time'], ['start_pos', Type.Float, None, 'Start position'], ['step_region', - ParamRepeat(['next_pos', Type.Float, None, 'next position'], - ['region_nr_intervals', Type.Float, None, 'Region number of intervals']), + [['next_pos', Type.Float, None, 'next position'], + ['region_nr_intervals', Type.Float, None, + 'Region number of intervals']], None, 'List of tuples: (next_pos, region_nr_intervals'] ] @@ -328,7 +329,7 @@ def prepare(self, motor, integ_time, start_pos, regions, **opts): self.integ_time = integ_time self.start_pos = start_pos self.regions = regions - self.regions_count = len(self.regions) / 2 + self.regions_count = len(self.regions) // 2 generator = self._generator moveables = [motor] @@ -347,7 +348,7 @@ def _generator(self): r][0], self.regions[r][1] positions = numpy.linspace( region_start, region_stop, region_nr_intervals + 1) - if region_start != self.start_pos: + if point_id != 0: # positions must be calculated from the start to the end of the region # but after the first region, the 'start' point must not be # repeated @@ -379,8 +380,9 @@ class reg2scan(Macro): ['integ_time', Type.Float, None, 'Integration time'], ['start_pos', Type.Float, None, 'Start position'], ['step_region', - ParamRepeat(['next_pos', Type.Float, None, 'next position'], - ['region_nr_intervals', Type.Float, None, 'Region number of intervals']), + [['next_pos', Type.Float, None, 'next position'], + ['region_nr_intervals', Type.Float, None, + 'Region number of intervals']], None, 'List of tuples: (next_pos, region_nr_intervals'] ] @@ -389,7 +391,7 @@ def prepare(self, motor1, motor2, integ_time, start_pos, regions, **opts): self.integ_time = integ_time self.start_pos = start_pos self.regions = regions - self.regions_count = len(self.regions) / 2 + self.regions_count = len(self.regions) // 2 generator = self._generator moveables = [motor1, motor2] @@ -408,7 +410,7 @@ def _generator(self): r][0], self.regions[r][1] positions = numpy.linspace( region_start, region_stop, region_nr_intervals + 1) - if region_start != self.start_pos: + if point_id != 0: # positions must be calculated from the start to the end of the region # but after the first region, the 'start' point must not be # repeated @@ -427,11 +429,13 @@ def run(self, *args): class reg3scan(Macro): """reg3scan. - Do an absolute scan of the specified motors with different number of intervals for each region. - It uses the gscan framework. All the motors will be drived to the same position in each step + Do an absolute scan of the specified motors with different number of + intervals for each region. It uses the gscan framework. + All the motors will be drived to the same position in each step - NOTE: Due to a ParamRepeat limitation, integration time has to be - specified before the regions. + .. note:: + integration time is specified before the regions to facilitate + input of parameters in Spock. """ hints = {'scan': 'reg3scan'} @@ -444,8 +448,9 @@ class reg3scan(Macro): ['integ_time', Type.Float, None, 'Integration time'], ['start_pos', Type.Float, None, 'Start position'], ['step_region', - ParamRepeat(['next_pos', Type.Float, None, 'next position'], - ['region_nr_intervals', Type.Float, None, 'Region number of intervals']), + [['next_pos', Type.Float, None, 'next position'], + ['region_nr_intervals', Type.Float, None, + 'Region number of intervals']], None, 'List of tuples: (next_pos, region_nr_intervals'] ] @@ -454,7 +459,7 @@ def prepare(self, motor1, motor2, motor3, integ_time, start_pos, regions, **opts self.integ_time = integ_time self.start_pos = start_pos self.regions = regions - self.regions_count = len(self.regions) / 2 + self.regions_count = len(self.regions) // 2 generator = self._generator moveables = [motor1, motor2, motor3] @@ -473,7 +478,7 @@ def _generator(self): r][0], self.regions[r][1] positions = numpy.linspace( region_start, region_stop, region_nr_intervals + 1) - if region_start != self.start_pos: + if point_id != 0: # positions must be calculated from the start to the end of the region # but after the first region, the 'start' point must not be # repeated @@ -541,11 +546,11 @@ def _generator(self): positions_m2 = numpy.linspace(start2, end2, interv2 + 1) if interv1 > interv2: - positions_m2 = start2 + (float(end2 - start2) / interv2) * ( - numpy.arange(interv1 + 1) // (float(interv1) / float(interv2))) + positions_m2 = start2 + ((end2 - start2) / interv2) * ( + numpy.arange(interv1 + 1) // (interv1 / interv2)) elif interv2 > interv1: - positions_m1 = start1 + (float(end1 - start1) / interv1) * ( - numpy.arange(interv2 + 1) // (float(interv2) / float(interv1))) + positions_m1 = start1 + ((end1 - start1) / interv1) * ( + numpy.arange(interv2 + 1) // (interv2 / interv1)) point_id = 0 for pos1, pos2 in zip(positions_m1, positions_m2): @@ -640,7 +645,7 @@ def run(self, motor, start_pos, final_pos, nr_interv, integ_time, **opts): # "//custom_data") if none given dh.addCustomData('Hello world1', 'dummyChar1') # you can pass arrays (but not all recorders will handle them) - dh.addCustomData(range(10), 'dummyArray1') + dh.addCustomData(list(range(10)), 'dummyArray1') # you can pass a custom nxpath *relative* to the current entry dh.addCustomData('Hello world2', 'dummyChar2', nxpath='sample:NXsample') @@ -656,6 +661,7 @@ def run(self, motor, start_pos, final_pos, nr_interv, integ_time, **opts): # as a bonus, plot the fit self.pyplot.plot(x, y, 'ro') self.pyplot.plot(x, fitted_y, 'b-') + self.pyplot.draw() class ascanct_midtrigger(Macro): @@ -687,7 +693,7 @@ def run(self, *args, **kwargs): first_position = positions[0] second_position = positions[1] positive_direction = second_position > first_position - shift = abs(second_position - first_position) / 2. + shift = abs(second_position - first_position) / 2 if positive_direction: mid_positions = positions + shift else: diff --git a/src/sardana/macroserver/macros/examples/specific_experiments.py b/src/sardana/macroserver/macros/examples/specific_experiments.py index 5a197d9a6c..65be312089 100644 --- a/src/sardana/macroserver/macros/examples/specific_experiments.py +++ b/src/sardana/macroserver/macros/examples/specific_experiments.py @@ -41,7 +41,7 @@ class xas_acq(Macro, Hookable): Perform an X-ray absorption scan experiment. Data is stored in a NXxas-compliant file. """ - hints = {'FileRecorder': 'NXxas_FileRecorder', 'scan': 'xas_acq', 'allowsHooks': ( + hints = {'scan': 'xas_acq', 'allowsHooks': ( 'pre-move', 'post-move', 'pre-acq', 'post-acq', 'post-step')} # env = ('MonochromatorEnergy', )#'AbsorbedBeam', 'IncomingBeam', # 'Monitor') #this hints that the macro requires the ActiveMntGrp @@ -69,10 +69,11 @@ def prepare(self, start, final, nr_interv, integ_time, **opts): # print "!!!!!", type(self.getInstrument('/instrument/monochromator')), self.getEnv('MonochromatorEnergy', macro_name=self.name) # ElementWithInterface('Instrument','monochromator') - for n, e in self.getElementsWithInterface('Instrument').iteritems(): + for n, e in self.getElementsWithInterface('Instrument').items(): inst = e.getObj() # ,inst.getElements() - print n, e.name, inst.getFullName(), type(e), type(inst), type(inst.getPoolObj()) + print(n, e.name, inst.getFullName(), type(e), type(inst), + type(inst.getPoolObj())) # maybe I should use the instrument interface to obtain the right # counters @@ -122,7 +123,7 @@ def data(self): return self._gScan.data # the GScan provides scan data def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points diff --git a/src/sardana/macroserver/macros/examples/submacros.py b/src/sardana/macroserver/macros/examples/submacros.py index d447c7070c..eb29921740 100644 --- a/src/sardana/macroserver/macros/examples/submacros.py +++ b/src/sardana/macroserver/macros/examples/submacros.py @@ -29,7 +29,7 @@ __docformat__ = 'restructuredtext' -from sardana.macroserver.macro import Macro, Type, ParamRepeat +from sardana.macroserver.macro import Macro, Type #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # First example: @@ -47,8 +47,7 @@ def run(self): class call_wm(Macro): param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Motor, None, 'Motor to move']), + ['motor_list', [['motor', Type.Motor, None, 'Motor to move']], None, 'List of motor to show'], ] @@ -184,7 +183,7 @@ def run(self, mot): data = dscan.data len_data = len(data) - for point_nb in xrange(len_data): + for point_nb in range(len_data): position = data[point_nb].data[mot.getName()] positions.append(position) diff --git a/src/sardana/macroserver/macros/expconf.py b/src/sardana/macroserver/macros/expconf.py new file mode 100644 index 0000000000..b561aee69d --- /dev/null +++ b/src/sardana/macroserver/macros/expconf.py @@ -0,0 +1,367 @@ +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +"""Experiment configuration related macros""" + +__all__ = ["get_meas", "get_meas_conf", "set_meas", "set_meas_conf", + "lssnap", "defsnap", "udefsnap"] + +__docformat__ = 'restructuredtext' + +from collections import OrderedDict + +import taurus +from taurus.console import Alignment +from taurus.console.list import List + +from sardana.pool import AcqSynchType +from sardana.macroserver.msexception import UnknownEnv +from sardana.taurus.core.tango.sardana import PlotType +from sardana.macroserver.macro import macro, Macro, Type, Optional + + +def sanitizer(values): + return ["n/a" if v is None else v for v in values] + + +def plot_type_sanitizer(values): + return [PlotType.whatis(v) for v in values] + + +def plot_axes_sanitizer(values): + return ["n/a" if len(v) == 0 else v[0] for v in values] + + +def synchrtonization_sanitizer(values): + return [AcqSynchType.whatis(v) for v in values] + + +def plot_axes_validator(value): + value = value.lower() + if value in ("idx", ""): + value = [""] + elif value in ("mov", ""): + value = [""] + return value + + +def bool_validator(value): + in_value = value + value = value.lower() + if value in ['true', '1']: + value = True + elif value in ['false', '0']: + value = False + else: + raise ValueError('{0} is not a boolean'.format(in_value)) + return value + + +def synchronization_validator(value): + in_value = value + value = value.lower() + try: + try: + value = int(value) + except ValueError: + value = AcqSynchType.get(value.capitalize()) + else: + value = AcqSynchType.get(value) + except KeyError: + raise ValueError("{0} is not a synchronization type".format(in_value)) + return value + +# if sanitizers and validators evolve to sth too complicated refactor this +# to use classes +parameter_map = OrderedDict([ + ("enabled", ("Enabled", None, bool_validator)), + ("output", ("Output", None, bool_validator)), + ("plottype", ("PlotType", plot_type_sanitizer, None)), + ("plotaxes", ("PlotAxes", plot_axes_sanitizer, plot_axes_validator)), + ("timer", ("Timer", sanitizer, None)), + ("monitor", ("Monitor", sanitizer, None)), + ("synchronizer", ("Synchronizer", sanitizer, None)), + ("synchronization", + ("Synchronization", synchrtonization_sanitizer, + synchronization_validator)), + ("valuerefenabled", ("ValueRefEnabled", sanitizer, bool_validator)), + ("valuerefpattern", ("ValueRefPattern", sanitizer, None)) +]) + + +simple_parameters = ("enabled", "plottype", "plotaxes") + + +@macro([ + ["detail", Type.String, Optional, + "Detail level of parameters. If omitted or \"simple\" then " + "get simple parameters. If \"all\" then get all parameters."], + ["meas_grp", Type.MeasurementGroup, Optional, "Measurement group"] +]) +def get_meas_conf(self, detail, meas_grp): + """Print measurement group configuration in form of a table. + + Examples of usage: + + >>> get_meas_conf # get simple configuration + >>> get_meas_conf all # get complete configuration + >>> get_meas_conf simple mntgrp01 # get mntgrp01 simple configuration + >>> get_meas_conf all mntgrp01 # get mntgrp01 complete configuration + """ + if detail is None or detail == "simple": + parameters = simple_parameters + elif detail == "all": + parameters = parameter_map.keys() + else: + raise ValueError("wrong detail level: {}".format(detail)) + if meas_grp is None: + meas_grp = self.getEnv("ActiveMntGrp") + self.print("ActiveMntGrp = {}".format(meas_grp)) + meas_grp = self.getMeasurementGroup(meas_grp) + col_headers = ["Channel"] + width = [-1] + align = [Alignment.Right] + cols = [] + for parameter in parameters: + parameter, sanitizer, _ = parameter_map[parameter] + getter = getattr(meas_grp, "get" + parameter) + ret = getter() + if len(cols) == 0: + # add channel names as first column + cols.append(ret.keys()) + values = ret.values() + if sanitizer is not None: + values = sanitizer(values) + cols.append(values) + col_headers.append(parameter) + width.append(-1) + align.append(Alignment.Right) + out = List(col_headers, text_alignment=align, max_col_width=width) + for row in zip(*cols): + out.appendRow(row) + for line in out.genOutput(): + self.output(line) + + +@macro([ + ["parameter", Type.String, None, "Parameter (case insensitive) to set."], + ["value", Type.String, None, "Parameter value to set"], + ["items", [ + ["item", Type.String, None, "Experimental channel/controller"], + {"min": 0}], None, + "Experimental channels (also external e.g. Tango attribute: " + "sys/tg_test/1/ampli) or their controllers" + ], + ["meas_grp", Type.MeasurementGroup, Optional, "Measurement group"], +]) +def set_meas_conf(self, parameter, value, items, meas_grp): + """Set measurement group configuration parameter. + + Available configuration parameters and values: + + - **Enabled**: True/1 or False/0 + - **Output**: True/1 or False/0 + - **PlotType**: No, Spectrum or Image + - **PlotAxes**: idx, mov or a e.g. ct01 + (for image use "|" as separator of axes e.g. idx|ct01) + - **Timer**: e.g. ct01 + - **Monitor**: e.g. ct01 + - **Synchronizer**: software or e.g. tg01 + - **Synchronization**: 0/Trigger, 1/Gate or 2/Start + - **ValueRefEnabled** - True/1 or False/0 + - **ValueRefPattern** - URI e.g. file:///tmp/img_{index}.tiff + + Examples of usage: + + >>> # enable all channels in + >>> set_meas_conf enabled True + >>> # enable spectrum plotting for ct01 on + >>> set_meas_conf plottype spectrum ct01 + >>> # set plot x-axis to for all channels of + >>> set_meas_conf plotaxes mov + >>> # enable spectrum plotting for all mntgrp01 + >>> set_meas_conf plottype spectrum [] mntgrp01 + """ + try: + parameter, _, validator = parameter_map[parameter.lower()] + except KeyError: + raise ValueError("wrong parameter: {}".format(parameter)) + if meas_grp is None: + meas_grp = self.getEnv("ActiveMntGrp") + self.print("ActiveMntGrp = {}".format(meas_grp)) + meas_grp = self.getMeasurementGroup(meas_grp) + setter = getattr(meas_grp, "set" + parameter) + if validator is not None: + value = validator(value) + setter(value, *items) + + +@macro([ + ["meas_grp", Type.MeasurementGroup, None, + "Measurement group to activate"], + ["macro", Type.Macro, Optional, + "Activate measurement group on this macro." + "If omitted activate on all."], +]) +def set_meas(self, meas_grp, macro): + """Activate measurement group. + + It sets the ActiveMntGrp environment variable. + """ + var = "ActiveMntGrp" + if macro is not None: + var = "{}.{}".format(macro.name, var) + self.setEnv(var, meas_grp.getName()) + + +@macro([ + ["macro", Type.Macro, Optional, + "Get active measurement group set for this macro." + "If omitted get the one set for all."], +]) +def get_meas(self, macro): + """Activate measurement group. + + It gets the ActiveMntGrp environment variable. + """ + if macro is not None: + macro_name = macro.name + else: + macro_name = None + var = "ActiveMntGrp" + try: + value = self.getEnv(var, macro_name=macro_name) + except UnknownEnv: + self.warning("Active measurement group is not set. " + "Hint: use `set_meas` macro to set it.") + else: + self.print("{} = {}".format(var, value)) + + +class lssnap(Macro): + """List pre-scan snapshot group. + """ + + def run(self): + try: + snapshot_items = self.getEnv("PreScanSnapshot") + except UnknownEnv: + self.output("No pre-scan snapshot") + return + out = List(['Snap item', 'Snap item full name']) + for full_name, label in snapshot_items: + out.appendRow([label, full_name]) + for line in out.genOutput(): + self.output(line) + + +class defsnap(Macro): + """Define snapshot group item(s). + + Accepts: + + - Pool moveables: motor, pseudo motor + - Pool experimental channels: counter/timer, 0D, 1D, 2D, pseudo counter + - Taurus attributes + """ + + param_def = [ + ["snap_names", [[ + "name", Type.String, None, "Name of an item to be added to the " + "pre-scan snapshot group"]], + None, + "Items to be added to the pre-scan snapshot group"], + ] + + def run(self, snap_names): + + def get_item_info(item): + if isinstance(item, taurus.core.TaurusAttribute): + return item.fullname, item.label + else: + return item.full_name, item.name + try: + snap_items = self.getEnv("PreScanSnapshot") + except UnknownEnv: + snap_items = [] + snap_full_names = [item[0] for item in snap_items] + new_snap_items = [] + for name in snap_names: + obj = self.getObj(name) + if obj is None: + try: + obj = taurus.Attribute(name) + except taurus.TaurusException: + raise ValueError("item is neither Pool element not " + "Taurus attribute") + elif obj.type == "MotorGroup": + raise ValueError("MotorGroup item type is not accepted") + new_full_name, new_label = get_item_info(obj) + if new_full_name in snap_full_names: + msg = "{} already in pre-scan snapshot".format(name) + raise ValueError(msg) + new_snap_items.append((new_full_name, new_label)) + self.setEnv("PreScanSnapshot", snap_items + new_snap_items) + + +class udefsnap(Macro): + """Undefine snapshot group item(s). Without arguments undefine all. + """ + + param_def = [ + ["snap_names", [[ + "name", Type.String, None, "Name of an item to be removed " + "from the pre-scan snapshot group", + ], {"min": 0}], + None, + "Items to be remove from the pre-scan snapshot group"], + ] + + def run(self, snap_names): + if len(snap_names) == 0: + self.unsetEnv("PreScanSnapshot") + return + try: + snap_items = self.getEnv("PreScanSnapshot") + except UnknownEnv: + raise RuntimeError("no pre-scan snapshot defined") + snap_full_names = {} + for i, item in enumerate(snap_items): + snap_full_names[item[0]] = i + for name in snap_names: + obj = self.getObj(name) + if obj is None: + try: + obj = taurus.Attribute(name) + except taurus.TaurusException: + raise ValueError("item is neither Pool element not " + "Taurus attribute") + elif obj.type == "MotorGroup": + raise ValueError("MotorGroup item type is not accepted") + rm_full_name = obj.fullname + if rm_full_name not in snap_full_names.keys(): + msg = "{} not in pre-scan snapshot".format(name) + raise ValueError(msg) + i = snap_full_names[rm_full_name] + snap_items.pop(i) + self.setEnv("PreScanSnapshot", snap_items) diff --git a/src/sardana/macroserver/macros/expert.py b/src/sardana/macroserver/macros/expert.py index 102be48ba7..abc4003a17 100644 --- a/src/sardana/macroserver/macros/expert.py +++ b/src/sardana/macroserver/macros/expert.py @@ -23,7 +23,7 @@ """Expert macros""" -from __future__ import print_function + __all__ = ["addctrllib", "addmaclib", "commit_ctrllib", "defctrl", "defelem", "defm", "defmeas", "edctrlcls", "edctrllib", "prdef", @@ -38,7 +38,7 @@ from sardana.macroserver.msexception import UnknownMacroLibrary from sardana.macroserver.msparameter import WrongParam -from sardana.macroserver.macro import Macro, Type, ParamRepeat, Table, LibraryError +from sardana.macroserver.macro import Macro, Type, Table, LibraryError ########################################################################## # @@ -70,9 +70,8 @@ class defmeas(Macro): param_def = [ ['name', Type.String, None, 'Measurement group name'], - ['channel_list', - ParamRepeat(['channel', Type.String, None, - 'Measurement Channel'],), + ['channel_list', [['channel', Type.String, None, + 'Measurement Channel']], None, 'List of measurement channels'], ] @@ -95,10 +94,9 @@ class udefmeas(Macro): """Deletes existing measurement groups""" param_def = [ - ['mntgrps', - ParamRepeat(['mntgrp', Type.MeasurementGroup, None, + ['mntgrps', [['mntgrp', Type.MeasurementGroup, None, 'Measurement group name'], - min=1), + {'min': 1}], None, 'List of measurement group names'], ] def run(self, mntgrps): @@ -143,8 +141,8 @@ class udefelem(Macro): """Deletes existing elements""" param_def = [ - ['elements', - ParamRepeat(['element', Type.Element, None, 'element name'], min=1), + ['elements', [['element', Type.Element, None, 'element name'], + {'min': 1}], None, 'List of element(s) name'], ] @@ -174,9 +172,8 @@ class defctrl(Macro): param_def = [['class', Type.ControllerClass, None, 'controller class'], ['name', Type.String, None, 'new controller name'], - ['roles_props', - ParamRepeat(['role_prop', Type.String, None, - 'a role or property item'], min=0), + ['roles_props', [['role_prop', Type.String, None, + 'a role or property item'], {'min': 0}], None, 'roles and/or properties']] def run(self, ctrl_class, name, props): @@ -189,9 +186,8 @@ class udefctrl(Macro): """Deletes existing controllers""" param_def = [ - ['controllers', - ParamRepeat(['controller', Type.Controller, None, 'controller name'], - min=1), + ['controllers', [['controller', Type.Controller, None, + 'controller name'], {'min': 1}], None, 'List of controller(s) name(s)'], ] def run(self, controllers): @@ -218,9 +214,8 @@ class send2ctrl(Macro): """Sends the given data directly to the controller""" param_def = [['controller', Type.Controller, None, 'Controller name'], - ['data', - ParamRepeat(['string item', Type.String, - None, 'a string item'],), + ['data', [['string item', Type.String, None, + 'a string item']], None, 'data to be sent']] def run(self, controller, data): @@ -517,7 +512,7 @@ def run(self, obj): def dump_properties(self, obj): data = obj.serialize() - table = Table([data.values()], row_head_str=data.keys(), + table = Table([list(data.values())], row_head_str=list(data.keys()), row_head_fmt='%*s', col_sep=' = ') self.output("Properties:") self.output("-----------") diff --git a/src/sardana/macroserver/macros/hkl.py b/src/sardana/macroserver/macros/hkl.py index 78eb913f80..1f08299ade 100644 --- a/src/sardana/macroserver/macros/hkl.py +++ b/src/sardana/macroserver/macros/hkl.py @@ -47,6 +47,7 @@ import re import numpy as np +from sardana.sardanautils import py2_round from sardana.macroserver.macro import Macro, iMacro, Type from sardana.macroserver.macros.scan import aNscan from sardana.macroserver.msexception import UnknownEnv @@ -136,7 +137,7 @@ def on_stop(self): def check_collinearity(self, h0, k0, l0, h1, k1, l1): - print h0 + print(h0) cpx = k0 * l1 - l0 * k1 cpy = l0 * h1 - h0 * l1 cpz = h0 * k1 - k0 * h1 @@ -1551,7 +1552,7 @@ def run(self): self.output("New directory %s not found" % newdir) return - res = filter(lambda x: x.endswith('.txt'), files) + res = [x for x in files if x.endswith('.txt')] if len(res) == 0: self.output("No crystals available in set directory. Nothing done") return @@ -1605,7 +1606,7 @@ def run(self, parameter): self.output("Old lattice parameter %s = %s" % (parameter, a0)) h0 = self.h_device.position - h1 = round(h0) + h1 = py2_round(h0) # TODO: check if round would be fine? a1 = h1 / h0 * a0 self.output("New lattice parameter %s = %s" % (parameter, a1)) @@ -1615,7 +1616,7 @@ def run(self, parameter): self.output("Old lattice parameter %s = %s" % (parameter, a0)) h0 = self.k_device.position - h1 = round(h0) + h1 = py2_round(h0) # TODO: check if round would be fine? a1 = h1 / h0 * a0 self.output("New lattice parameter %s = %s" % (parameter, a1)) @@ -1625,7 +1626,7 @@ def run(self, parameter): self.output("Old lattice parameter %s = %s" % (parameter, a0)) h0 = self.l_device.position - h1 = round(h0) + h1 = py2_round(h0) # TODO: check if round would be fine? a1 = h1 / h0 * a0 self.output("New lattice parameter %s = %s" % (parameter, a1)) @@ -1661,11 +1662,7 @@ def run(self, flagprint): while(moving): moving = 0 for angle in self.angle_names: - # TODO: For Taurus 4 / Taurus 3 compatibility - if hasattr(mot_dev, "stateObj"): - angle_state = tmp_dev[angle].stateObj.read().rvalue - else: - angle_state = tmp_dev[angle].state() + angle_state = tmp_dev[angle].stateObj.read().rvalue if angle_state == 6: moving = 1 if flagprint == 1: diff --git a/src/sardana/macroserver/macros/lists.py b/src/sardana/macroserver/macros/lists.py index a89741ee75..e137453bff 100644 --- a/src/sardana/macroserver/macros/lists.py +++ b/src/sardana/macroserver/macros/lists.py @@ -34,7 +34,7 @@ from taurus.console import Alignment from taurus.console.list import List -from sardana.macroserver.macro import Macro, Type, ParamRepeat, ViewOption +from sardana.macroserver.macro import Macro, Type, ViewOption Left, Right, HCenter = Alignment.Left, Alignment.Right, Alignment.HCenter @@ -47,9 +47,8 @@ class _ls(Macro): # TODO: duplication of the default value definition is a workaround # for #427. See commit message cc3331a for more details. param_def = [ - ['filter', - ParamRepeat(['filter', Type.String, ".*", - 'a regular expression filter'], min=1), + ['filter', [['filter', Type.String, ".*", + 'a regular expression filter'], {'min': 1}], [".*"], 'a regular expression filter'], ] @@ -146,7 +145,7 @@ def run(self, filter): nb = len(objs) if nb is 0: if self.subtype is Macro.All: - if isinstance(self.type, (str, unicode)): + if isinstance(self.type, str): t = self.type.lower() else: t = ", ".join(self.type).lower() diff --git a/src/sardana/macroserver/macros/scan.py b/src/sardana/macroserver/macros/scan.py index d6fd1f34f1..57e9aa22fa 100644 --- a/src/sardana/macroserver/macros/scan.py +++ b/src/sardana/macroserver/macros/scan.py @@ -33,7 +33,8 @@ "d2scanc", "d3scanc", "d4scanc", "dscanc", "meshc", "a2scanct", "a3scanct", "a4scanct", "ascanct", "meshct", - "scanhist", "getCallable", "UNCONSTRAINED"] + "scanhist", "getCallable", "UNCONSTRAINED", + "scanstats"] __docformat__ = 'restructuredtext' @@ -46,8 +47,7 @@ from taurus.core.util import SafeEvaluator from sardana.macroserver.msexception import UnknownEnv -from sardana.macroserver.macro import Hookable, Macro, Type, ParamRepeat, \ - Table, List +from sardana.macroserver.macro import Hookable, Macro, Type, Table, List from sardana.macroserver.scan.gscan import SScan, CTScan, HScan, \ MoveableDesc, CSScan, TScan from sardana.util.motion import MotionPath @@ -224,7 +224,7 @@ def _stepGenerator(self): step["post-step-hooks"] = self.getHooks('post-step') step["check_func"] = [] - for point_no in xrange(self.nb_points): + for point_no in range(self.nb_points): step["positions"] = self.starts + point_no * self.interv_sizes step["point_id"] = point_no yield step @@ -235,7 +235,7 @@ def _waypoint_generator(self): step["post-move-hooks"] = self.getHooks('post-move') step["check_func"] = [] step["slow_down"] = self.slow_down - for point_no in xrange(self.nr_waypoints): + for point_no in range(self.nr_waypoints): step["positions"] = self.starts + point_no * self.way_lengths step["waypoint_id"] = point_no yield step @@ -302,7 +302,7 @@ def getTimeEstimation(self): # calculate motion time max_step0_time, max_step_time = 0.0, 0.0 # first motion takes longer, all others should be "equal" - step0 = it.next() + step0 = next(it) for v_motor, start, stop, length in zip(v_motors, curr_pos, step0['positions'], self.interv_sizes): @@ -336,7 +336,7 @@ def _fill_missing_records(self): scan.data.initRecords(missing_records) def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -498,9 +498,9 @@ class amultiscan(aNscan, Macro): param_def = [ ['motor_start_end_list', - ParamRepeat(['motor', Type.Moveable, None, 'Moveable to move'], - ['start', Type.Float, None, 'Starting position'], - ['end', Type.Float, None, 'Final position']), + [['motor', Type.Moveable, None, 'Moveable to move'], + ['start', Type.Float, None, 'Starting position'], + ['end', Type.Float, None, 'Final position']], None, 'List of motor, start and end positions'], ['nr_interv', Type.Integer, None, 'Number of scan intervals'], ['integ_time', Type.Float, None, 'Integration time'] @@ -531,9 +531,9 @@ class dmultiscan(dNscan, Macro): param_def = [ ['motor_start_end_list', - ParamRepeat(['motor', Type.Moveable, None, 'Moveable to move'], - ['start', Type.Float, None, 'Starting position'], - ['end', Type.Float, None, 'Final position']), + [['motor', Type.Moveable, None, 'Moveable to move'], + ['start', Type.Float, None, 'Starting position'], + ['end', Type.Float, None, 'Final position']], None, 'List of motor, start and end positions'], ['nr_interv', Type.Integer, None, 'Number of scan intervals'], ['integ_time', Type.Float, None, 'Integration time'] @@ -868,8 +868,8 @@ class fscan(Macro, Hookable): ['indepvars', Type.String, None, 'Independent Variables'], ['integ_time', Type.String, None, 'Integration time'], ['motor_funcs', - ParamRepeat(['motor', Type.Moveable, None, 'motor'], - ['func', Type.String, None, 'curve defining path']), + [['motor', Type.Moveable, None, 'motor'], + ['func', Type.String, None, 'curve defining path']], None, 'List of motor and path curves'] ] @@ -883,8 +883,8 @@ def prepare(self, *args, **opts): self.motors = [item[0] for item in args[2]] self.funcstrings = [item[1] for item in args[2]] - globals_lst = [dict(zip(indepvars, values)) - for values in zip(*indepvars.values())] + globals_lst = [dict(list(zip(indepvars, values))) + for values in zip(*list(indepvars.values()))] self.paths = [[SafeEvaluator(globals).eval( func) for globals in globals_lst] for func in self.funcstrings] @@ -964,7 +964,7 @@ def run(self, *args): yield step def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -1009,7 +1009,7 @@ def run(self, scan_number): try: hist = self.getEnv("ScanHistory") except UnknownEnv: - print "No scan recorded in history" + print("No scan recorded in history") return if scan_number < 0: self.show_all(hist) @@ -1826,7 +1826,7 @@ def _fill_missing_records(self): scan.data.initRecords(missing_records) def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points @@ -1875,9 +1875,234 @@ def getIntervalEstimation(self): return self.nr_interv def _get_nr_points(self): - msg = ("nr_points is deprecated since version Jan20. " + msg = ("nr_points is deprecated since version 3.0.3. " "Use nb_points instead.") self.warning(msg) return self.nb_points nr_points = property(_get_nr_points) + + +class scanstats(Macro): + """Calculate basic statistics of the enabled and plotted channels in + the active measurement group for the last scan. If no channel is selected + for plotting it fallbacks to the first enabled channel. Print stats and + publish them in the env. + The macro must be hooked in the post-scan hook place. + """ + + env = ("ActiveMntGrp", ) + + param_def = [ + ["channel", + [["channel", Type.ExpChannel, None, ""], {"min": 0}], + None, + "List of channels for statistics calculations" + ] + ] + + def run(self, channel): + parent = self.getParentMacro() + if not parent: + self.warning("for now the scanstats macro can only be executed as" + " a post-scan hook") + return + if not hasattr(parent, "motors"): + self.warning("scan must involve at least one moveable " + "to calculate statistics") + return + + active_meas_grp = self.getEnv("ActiveMntGrp") + meas_grp = self.getMeasurementGroup(active_meas_grp) + calc_channels = [] + enabled_channels = meas_grp.getEnabled() + if channel: + stat_channels = [chan.name for chan in channel] + else: + stat_channels = [key for key in enabled_channels.keys()] + + for chan in stat_channels: + enabled = enabled_channels.get(chan) + if enabled is None: + self.warning("{} not in {}".format(chan, meas_grp.name)) + else: + if not enabled and channel: + self.warning("{} not enabled".format(chan)) + elif enabled and channel: + # channel was given as parameters + calc_channels.append(chan) + elif enabled and meas_grp.getPlotType(chan)[chan] == 1: + calc_channels.append(chan) + + if len(calc_channels) == 0: + # fallback is first enabled channel in meas_grp + calc_channels.append(next(iter(enabled_channels))) + + scalar_channels = [] + for _, chan in self.getExpChannels().items(): + if chan.type in ("OneDExpChannel", "TwoDExpChannel"): + continue + scalar_channels.append(chan.name) + calc_channels = [ch for ch in calc_channels if ch in scalar_channels] + + if len(calc_channels) == 0: + self.warning("measurement group must contain at least one " + "enabled scalar channel to calculate statistics") + return + + selected_motor = str(parent.motors[0]) + stats = {} + col_header = [] + cols = [] + + motor_data = [] + channels_data = {} + for channel_name in calc_channels: + channels_data[channel_name] = [] + + for idx, rc in parent.data.items(): + motor_data.append(rc[selected_motor]) + for channel_name in calc_channels: + channels_data[channel_name].append(rc[channel_name]) + + motor_data = numpy.array(motor_data) + for channel_name, data in channels_data.items(): + channel_data = numpy.array(data) + + (_min, _max, min_at, max_at, half_max, com, mean, _int, + fwhm, cen) = self._calcStats(motor_data, channel_data) + stats[channel_name] = { + "min": _min, + "max": _max, + "minpos": min_at, + "maxpos": max_at, + "mean": mean, + "int": _int, + "com": com, + "fwhm": fwhm, + "cen": cen} + + col_header.append([channel_name]) + cols.append([ + stats[channel_name]["min"], + stats[channel_name]["max"], + stats[channel_name]["minpos"], + stats[channel_name]["maxpos"], + stats[channel_name]["mean"], + stats[channel_name]["int"], + stats[channel_name]["com"], + stats[channel_name]["fwhm"], + stats[channel_name]["cen"], + ]) + self.info("Statistics for movable: {:s}".format(selected_motor)) + + table = Table(elem_list=cols, elem_fmt=["%*g"], + row_head_str=["MIN", "MAX", "MIN@", "MAX@", + "MEAN", "INT", "COM", "FWHM", "CEN"], + col_head_str=col_header, col_head_sep="-") + out = table.genOutput() + + for line in out: + self.info(line) + self.setEnv("{:s}.ScanStats".format(self.getDoorName()), + {"Stats": stats, + "Motor": selected_motor, + "ScanID": self.getEnv("ScanID")}) + + @staticmethod + def _calcStats(x, y): + # max and min + _min = numpy.min(y) + _max = numpy.max(y) + + min_idx = numpy.argmin(y) + min_at = x[min_idx] + max_idx = numpy.argmax(y) + max_at = x[max_idx] + + # center of mass (com) + try: + com = numpy.sum(y*x)/numpy.sum(y) + except ZeroDivisionError: + com = 0 + + mean = numpy.mean(y) + _int = numpy.sum(y) + + # determine if it is a peak- or erf-like function + half_max = (_max-_min)/2+_min + + lower_left = False + lower_right = False + + if numpy.any(y[0:max_idx] < half_max): + lower_left = True + if numpy.any(y[max_idx:] < half_max): + lower_right = True + + if lower_left and lower_right: + # it is a peak-like function + y_data = y + elif lower_left: + # it is an erf-like function + # use the gradient for further calculation + y_data = numpy.gradient(y) + # use also the half maximum of the gradient + half_max = (numpy.max(y_data)-numpy.min(y_data)) \ + / 2+numpy.min(y_data) + else: + # it is an erf-like function + # use the gradient for further calculation + y_data = -1*numpy.gradient(y) + # use also the half maximum of the gradient + half_max = (numpy.max(y_data)-numpy.min(y_data)) \ + / 2+numpy.min(y_data) + + # cen and fwhm + # this part is adapted from: + # + # The PyMca X-Ray Fluorescence Toolkit + # + # Copyright (c) 2004-2014 European Synchrotron Radiation Facility + # + # This file is part of the PyMca X-ray Fluorescence Toolkit developed + # at the ESRF by the Software group. + + max_idx_data = numpy.argmax(y_data) + idx = max_idx_data + try: + while y_data[idx] >= half_max: + idx = idx-1 + + x0 = x[idx] + x1 = x[idx+1] + y0 = y_data[idx] + y1 = y_data[idx+1] + + lhmx = (half_max*(x1-x0) - (y0*x1)+(y1*x0)) / (y1-y0) + except ZeroDivisionError: + lhmx = 0 + except IndexError: + lhmx = x[0] + + idx = max_idx_data + try: + while y_data[idx] >= half_max: + idx = idx+1 + + x0 = x[idx-1] + x1 = x[idx] + y0 = y_data[idx-1] + y1 = y_data[idx] + + uhmx = (half_max*(x1-x0) - (y0*x1)+(y1*x0)) / (y1-y0) + except ZeroDivisionError: + uhmx = 0 + except IndexError: + uhmx = x[-1] + + fwhm = uhmx - lhmx + cen = (uhmx + lhmx)/2 + + return (_min, _max, min_at, max_at, half_max, com, mean, _int, + fwhm, cen) diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py old mode 100644 new mode 100755 index 98c1bdb4f0..7be0178ebd --- a/src/sardana/macroserver/macros/standard.py +++ b/src/sardana/macroserver/macros/standard.py @@ -25,7 +25,9 @@ __all__ = ["ct", "mstate", "mv", "mvr", "pwa", "pwm", "repeat", "set_lim", "set_lm", "set_pos", "settimer", "uct", "umv", "umvr", "wa", "wm", - "tw", "logmacro", "newfile"] + "tw", "logmacro", "newfile", "plotselect", "pic", "cen", + "where"] + __docformat__ = 'restructuredtext' @@ -38,8 +40,8 @@ import PyTango from PyTango import DevState -from sardana.macroserver.macro import Macro, macro, Type, ParamRepeat, \ - ViewOption, iMacro, Hookable +from sardana.macroserver.macro import Macro, macro, Type, ViewOption, \ + iMacro, Hookable from sardana.macroserver.msexception import StopException, UnknownEnv from sardana.macroserver.scan.scandata import Record from sardana.macroserver.macro import Optional @@ -55,8 +57,7 @@ class _wm(Macro): """Show motor positions""" param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Moveable, None, 'Motor to move']), + ['motor_list', [['motor', Type.Moveable, None, 'Motor to move']], None, 'List of motor to show'], ] @@ -82,7 +83,7 @@ def run(self, motor_list): data[name] = [] # get additional motor information (ctrl name & axis) if show_ctrlaxis: - for name, motor in motors.iteritems(): + for name, motor in motors.items(): ctrl_name = self.getController(motor.controller).name axis_nb = str(getattr(motor, "axis")) data[name].extend((ctrl_name, axis_nb)) @@ -90,7 +91,7 @@ def run(self, motor_list): # collect asynchronous replies while len(requests) > 0: req2delete = [] - for name, _id in requests.iteritems(): + for name, _id in requests.items(): motor = motors[name] try: attrs = motor.read_attributes_reply(_id) @@ -98,6 +99,10 @@ def run(self, motor_list): value = attr.value if value is None: value = float('NaN') + if attr.name == 'dialposition': + value = motor.getDialPosition() + if value is None: + value = float('NaN') data[name].append(value) req2delete.append(name) except PyTango.AsynReplyNotArrived: @@ -146,8 +151,7 @@ class _wum(Macro): """Show user motor positions""" param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Moveable, None, 'Motor to move']), + ['motor_list', [['motor', Type.Moveable, None, 'Motor to move']], None, 'List of motor to show'], ] @@ -206,9 +210,8 @@ class wa(Macro): # TODO: duplication of the default value definition is a workaround # for #427. See commit message cc3331a for more details. param_def = [ - ['filter', - ParamRepeat(['filter', Type.String, '.*', - 'a regular expression filter'], min=1), + ['filter', [['filter', Type.String, '.*', + 'a regular expression filter'], {'min': 1}], ['.*'], 'a regular expression filter'], ] @@ -239,9 +242,8 @@ class pwa(Macro): # TODO: duplication of the default value definition is a workaround # for #427. See commit message cc3331a for more details. param_def = [ - ['filter', - ParamRepeat(['filter', Type.String, '.*', - 'a regular expression filter'], min=1), + ['filter', [['filter', Type.String, '.*', + 'a regular expression filter'], {'min': 1}], ['.*'], 'a regular expression filter'], ] @@ -309,7 +311,7 @@ def run(self, motor, pos): name = motor.getName() old_pos = motor.getPosition(force=True) offset_attr = motor.getAttribute('Offset') - old_offset = offset_attr.read().value + old_offset = offset_attr.read().rvalue.magnitude new_offset = pos - (old_pos - old_offset) offset_attr.write(new_offset) msg = "%s reset from %.4f (offset %.4f) to %.4f (offset %.4f)" % ( @@ -321,9 +323,8 @@ class wm(Macro): """Show the position of the specified motors.""" param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Moveable, None, - 'Motor to see where it is']), + ['motor_list', [['motor', Type.Moveable, None, + 'Motor to see where it is']], None, 'List of motor to show'], ] @@ -368,28 +369,29 @@ def run(self, motor_list): except: val1 = str_fmt % motor.getPosition(force=True) - val2 = str_fmt % posObj.getMaxValue() - val3 = str_fmt % posObj.getMinValue() + val2 = str_fmt % posObj.getMaxRange().magnitude + val3 = str_fmt % posObj.getMinRange().magnitude if show_ctrlaxis: valctrl = str_fmt % (ctrl_name) valaxis = str_fmt % str(axis_nb) - upos = map(str, [valctrl, valaxis, ' ', val2, val1, val3]) + upos = list(map(str, [valctrl, valaxis, ' ', val2, val1, + val3])) else: - upos = map(str, ['', val2, val1, val3]) + upos = list(map(str, ['', val2, val1, val3])) pos_data = upos if show_dial: try: val1 = fmt % motor.getDialPosition(force=True) val1 = str_fmt % val1 - except: + except Exception: val1 = str_fmt % motor.getDialPosition(force=True) dPosObj = motor.getDialPositionObj() - val2 = str_fmt % dPosObj.getMaxValue() - val3 = str_fmt % dPosObj.getMinValue() + val2 = str_fmt % dPosObj.getMaxRange().magnitude + val3 = str_fmt % dPosObj.getMinRange().magnitude - dpos = map(str, [val2, val1, val3]) + dpos = list(map(str, [val2, val1, val3])) pos_data += [''] + dpos motor_pos.append(pos_data) @@ -412,9 +414,8 @@ class wum(Macro): """Show the user position of the specified motors.""" param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Moveable, None, - 'Motor to see where it is']), + ['motor_list', [['motor', Type.Moveable, None, + 'Motor to see where it is']], None, 'List of motor to show'], ] @@ -430,8 +431,9 @@ def run(self, motor_list): name = motor.getName() motor_names.append([name]) posObj = motor.getPositionObj() - upos = map(str, [posObj.getMaxValue(), motor.getPosition( - force=True), posObj.getMinValue()]) + upos = list(map(str, [posObj.getMaxRange().magnitude, + motor.getPosition(force=True), + posObj.getMinRange().magnitude])) pos_data = [''] + upos motor_pos.append(pos_data) @@ -449,8 +451,7 @@ class pwm(Macro): """Show the position of the specified motors in a pretty table""" param_def = [ - ['motor_list', - ParamRepeat(['motor', Type.Moveable, None, 'Motor to move']), + ['motor_list', [['motor', Type.Moveable, None, 'Motor to move']], None, 'List of motor to show'], ] @@ -463,8 +464,8 @@ class mv(Macro): param_def = [ ['motor_pos_list', - ParamRepeat(['motor', Type.Moveable, None, 'Motor to move'], - ['pos', Type.Float, None, 'Position to move to']), + [['motor', Type.Moveable, None, 'Motor to move'], + ['pos', Type.Float, None, 'Position to move to']], None, 'List of motor/position pairs'], ] @@ -490,7 +491,7 @@ class mstate(Macro): param_def = [['motor', Type.Moveable, None, 'Motor to check state']] def run(self, motor): - self.info("Motor %s" % str(motor.getState())) + self.info("Motor %s" % str(motor.stateObj.read().rvalue)) class umv(Macro): @@ -524,8 +525,8 @@ def _clean(self): posObj = motor.getPositionObj() try: posObj.unsubscribeEvent(self.positionChanged, motor) - except Exception, e: - print str(e) + except Exception as e: + print(str(e)) raise e def positionChanged(self, motor, position): @@ -547,8 +548,8 @@ class mvr(Macro): param_def = [ ['motor_disp_list', - ParamRepeat(['motor', Type.Moveable, None, 'Motor to move'], - ['disp', Type.Float, None, 'Relative displacement']), + [['motor', Type.Moveable, None, 'Motor to move'], + ['disp', Type.Float, None, 'Relative displacement']], None, 'List of motor/displacement pairs'], ] @@ -651,13 +652,22 @@ def run(self, motor, delta): def _value_to_repr(data): if data is None: return "" - elif np.rank(data) > 0: + elif np.ndim(data) > 0: return list(np.shape(data)) else: return data -class ct(Macro, Hookable): +class _ct: + + def dump_information(self, elements): + msg = ["Elements ended acquisition with:"] + for element in elements: + msg.append(element.information()) + self.info("\n".join(msg)) + + +class ct(Macro, Hookable, _ct): """Count for the specified time on the measurement group or experimental channel given as second argument (if not given the active measurement group is used)""" @@ -699,7 +709,21 @@ def run(self, integ_time, countable_elem): for preAcqHook in self.getHooks('pre-acq'): preAcqHook() - state, data = self.countable_elem.count(integ_time) + try: + state, data = self.countable_elem.count(integ_time) + except Exception: + if self.countable_elem.type == Type.MeasurementGroup: + names = self.countable_elem.ElementList + elements = [self.getObj(name) for name in names] + self.dump_information(elements) + raise + if state != DevState.ON: + if self.countable_elem.type == Type.MeasurementGroup: + names = self.countable_elem.ElementList + elements = [self.getObj(name) for name in names] + self.dump_information(elements) + raise ValueError("Acquisition ended with {}".format( + state.name.capitalize())) for postAcqHook in self.getHooks('post-acq'): postAcqHook() @@ -724,7 +748,7 @@ def run(self, integ_time, countable_elem): self.output(line) -class uct(Macro): +class uct(Macro, _ct): """Count on the active measurement group and update""" param_def = [ @@ -783,14 +807,27 @@ def run(self, integ_time, countable_elem): self.print_value = True try: - _, data = self.countable_elem.count(integ_time) - self.setData(Record(data)) + state, data = self.countable_elem.count(integ_time) + except Exception: + if self.countable_elem.type == Type.MeasurementGroup: + names = self.countable_elem.ElementList + elements = [self.getObj(name) for name in names] + self.dump_information(elements) + raise finally: self.finish() + if state != DevState.ON: + if self.countable_elem.type == Type.MeasurementGroup: + names = self.countable_elem.ElementList + elements = [self.getObj(name) for name in names] + self.dump_information(elements) + raise ValueError("Acquisition ended with {}".format( + state.name.capitalize())) + self.setData(Record(data)) + self.printAllValues() def finish(self): self._clean() - self.printAllValues() def _clean(self): for channel in self.channels: @@ -831,16 +868,16 @@ def run(self, timer): return try: - mnt_grp.setTimer(timer.getName()) - except Exception, e: + mnt_grp.getConfiguration().setTimer(timer.getName()) + except Exception as e: self.output(str(e)) self.output( "%s is not a valid channel in the active measurement group" % timer) -@macro([['message', ParamRepeat(['message_item', Type.String, None, - 'message item to be reported']), None, +@macro([['message', [['message_item', Type.String, None, + 'message item to be reported']], None, 'message to be reported']]) def report(self, message): """Logs a new record into the message report system (if active)""" @@ -919,7 +956,7 @@ def run(self, nr, macro_name_params): else: for i in range(nr): self.__loop() - progress = ((i + 1) / float(nr)) * 100 + progress = ((i + 1) / nr) * 100 yield progress @@ -957,7 +994,7 @@ def run(self, ScanFilePath_list, ScanID): ScanDir = self.getEnv('ScanDir') except UnknownEnv: ScanDir = '' - if not (isinstance(ScanDir, basestring) and len(ScanDir) > 0): + if not (isinstance(ScanDir, str) and len(ScanDir) > 0): msg = ('Data is not stored until ScanDir is correctly ' 'set! Provide ScanDir with newfile macro: ' '`newfile [/] ` ' @@ -1018,3 +1055,124 @@ def run(self, ScanFilePath_list, ScanID): for postNewfileHook in self.getHooks('post-newfile'): postNewfileHook() + + +class plotselect(Macro): + """select channels for plotting in the active measurement group""" + + env = ("ActiveMntGrp", ) + param_def = [ + ['channel', + [['channel', Type.ExpChannel, 'None', ""], {'min': 0}], + None, + "List of channels to plot"], + ] + + def run(self, channel): + active_meas_grp = self.getEnv('ActiveMntGrp') + meas_grp = self.getMeasurementGroup(active_meas_grp) + self.output("Active measurement group: {}".format(meas_grp.name)) + + plot_channels_ok = [] + enabled_channels = meas_grp.getEnabled() + # check channels first + for chan in channel: + enabled = enabled_channels.get(chan.name) + if enabled is None: + self.warning("{} not in {}".format(chan.name, meas_grp.name)) + else: + plot_channels_ok.append(chan.name) + if not enabled: + self.warning("{} is disabled".format(chan.name)) + else: + self.output("{} selected for plotting".format(chan.name)) + # set the plot type and plot axis in the meas_group + meas_grp.setPlotType("No", apply=False) + meas_grp.setPlotType("Spectrum", *plot_channels_ok, apply=False) + meas_grp.setPlotAxes([""], *plot_channels_ok) + + +class _movetostatspos(Macro): + """This macro does the logic for pic and cen""" + + env = ("ScanStats", ) + + param_def = [ + ['channel', Type.ExpChannel, Optional, 'name of channel'], + ['caller', Type.String, None, 'caller (pic or cen)'] + ] + + def run(self, channel, caller): + stats = self.getEnv('ScanStats', door_name=self.getDoorName()) + + if channel is None: + # use first channel in stats + channel = next(iter(stats['Stats'])) + else: + if channel.name in stats['Stats']: + channel = channel.name + else: + raise Exception("channel {} not present in ScanStats".format( + channel.name)) + + if caller == 'pic': + stats_value = 'maxpos' + stats_str = 'PIC' + elif caller == 'cen': + stats_value = 'cen' + stats_str = 'CEN' + else: + raise Exception("caller {} is unknown".format(caller)) + + motor_name = stats['Motor'] + motor = self.getMotion([motor_name]) + current_pos = motor.readPosition()[0] + pos = stats['Stats'][channel][stats_value] + + self.info("move motor {:s} from current position\nat {:.4f}\n" + "to {:s} of counter {:s}\nat {:.4f}".format(motor_name, + current_pos, + stats_str, + channel, + pos)) + motor.move(pos) + + +class pic(Macro): + """This macro moves the motor of the last scan to the PEAK position for a + given channel. If no channel is given, it selects the first channel from + the ScanStats env variable. + """ + + param_def = [ + ['channel', Type.ExpChannel, Optional, 'name of channel'] + ] + + def run(self, channel): + self.execMacro('_movetostatspos', channel, 'pic') + + +class cen(Macro): + """This macro moves the motor of the last scan to the CEN position for a + given channel. If no channel is given, it selects the first channel from + the ScanStats env variable. + """ + + param_def = [ + ['channel', Type.ExpChannel, Optional, 'name of channel'] + ] + + def run(self, channel): + self.execMacro('_movetostatspos', channel, 'cen') + + +class where(Macro): + """This macro shows the current position of the last scanned motor.""" + + env = ("ScanStats", ) + + def run(self): + motor_name = self.getEnv('ScanStats')['Motor'] + motor = self.getMoveable(motor_name) + self.info("motor {:s} is\nat {:.4f}".format(motor_name, + motor.getPosition())) diff --git a/src/sardana/macroserver/macros/test/__init__.py b/src/sardana/macroserver/macros/test/__init__.py index eab3a577a0..0ecb57b628 100644 --- a/src/sardana/macroserver/macros/test/__init__.py +++ b/src/sardana/macroserver/macros/test/__init__.py @@ -23,7 +23,7 @@ ## ############################################################################## -from macroexecutor import BaseMacroExecutor, MacroExecutorFactory -from base import (macroTest, BaseMacroTestCase, RunMacroTestCase, - RunStopMacroTestCase, testRun, testFail, testStop) -from sardemoenv import * +from .macroexecutor import BaseMacroExecutor, MacroExecutorFactory # noqa +from .base import (macroTest, BaseMacroTestCase, RunMacroTestCase, # noqa + RunStopMacroTestCase, testRun, testFail, testStop) # noqa +from .sardemoenv import * # noqa diff --git a/src/sardana/macroserver/macros/test/base.py b/src/sardana/macroserver/macros/test/base.py index f583327cf5..1135e432b7 100644 --- a/src/sardana/macroserver/macros/test/base.py +++ b/src/sardana/macroserver/macros/test/base.py @@ -44,7 +44,7 @@ class __NotPassedType(int): def macroTest(klass=None, helper_name=None, test_method_name=None, test_method_doc=None, **helper_kwargs): - """This decorator is an specialization of :function::`taurus.test.insertTest` + """This decorator is an specialization of `taurus.test.insertTest` for macro testing. It inserts test methods from a helper method that may accept arguments. @@ -110,7 +110,7 @@ class FooTest(RunMacroTestCase, unittest.TestCase): class FooTest(RunMacroTestCase, unittest.TestCase): macro_name = 'twice' - .. seealso:: :function::`taurus.test.insertTest` + .. seealso:: `taurus.test.insertTest` """ # recipe to allow decorating with and without arguments @@ -178,7 +178,7 @@ class RunMacroTestCase(BaseMacroTestCase): def assertFinished(self, msg): """Asserts that macro has finished. """ - finishStates = [u'finish'] + finishStates = ['finish'] state = self.macro_executor.getState() msg = msg + ';\nState: %s' % state exception_str = self.macro_executor.getExceptionStr() @@ -195,7 +195,7 @@ def setUp(self): self.macro_executor.registerAll() def macro_runs(self, macro_name=None, macro_params=None, - wait_timeout=float("inf"), data=_NOT_PASSED): + wait_timeout=None, data=_NOT_PASSED): """A helper method to create tests that check if the macro can be successfully executed for the given input parameters. It may also optionally perform checks on the outputs from the execution. @@ -206,7 +206,8 @@ class member) If passed, they must be given as a sequence of their string representations. :param wait_timeout: (float) maximum allowed time (in s) for the macro - to finish. By default infinite timeout is used. + to finish. By default infinite timeout is + used (None). :param data: (obj) Optional. If passed, the macro data after the execution is tested to be equal to this. """ @@ -227,14 +228,14 @@ class member) # in a similar way to what is done for macro data def macro_fails(self, macro_name=None, macro_params=None, - wait_timeout=float("inf"), exception=None): + wait_timeout=None, exception=None): """Check that the macro fails to run for the given input parameters :param macro_name: (str) macro name (takes precedence over macro_name class member) :param macro_params: (seq) input parameters for the macro :param wait_timeout: maximum allowed time for the macro to fail. By - default infinite timeout is used. + default infinite timeout (None) is used. :param exception: (str or Exception) if given, an additional check of the type of the exception is done. (IMPORTANT: this is just a comparison of str @@ -266,7 +267,7 @@ class RunStopMacroTestCase(RunMacroTestCase): def assertStopped(self, msg): """Asserts that macro was stopped """ - stoppedStates = [u'stop'] + stoppedStates = ['stop'] state = self.macro_executor.getState() # TODO buffer is just for debugging, attach only the last state state_buffer = self.macro_executor.getStateBuffer() @@ -274,7 +275,7 @@ def assertStopped(self, msg): self.assertIn(state, stoppedStates, msg) def macro_stops(self, macro_name=None, macro_params=None, stop_delay=0.1, - wait_timeout=float("inf")): + wait_timeout=None): """A helper method to create tests that check if the macro can be successfully stoped (a.k.a. aborted) after it has been launched. @@ -286,7 +287,8 @@ class member) :param stop_delay: (float) Time (in s) to wait between launching the macro and sending the stop command. default=0.1 :param wait_timeout: (float) maximum allowed time (in s) for the macro - to finish. By default infinite timeout is used. + to finish. By default infinite timeout (None) is + used. """ self.macro_executor.run(macro_name=macro_name or self.macro_name, macro_params=macro_params, @@ -300,7 +302,7 @@ class member) if __name__ == '__main__': - from taurus.external import unittest + import unittest from sardana.macroserver.macros.test import SarDemoEnv _m1 = SarDemoEnv().getMotors()[0] diff --git a/src/sardana/macroserver/macros/test/macroexecutor.py b/src/sardana/macroserver/macros/test/macroexecutor.py index 71854b0e72..5658a9a60b 100644 --- a/src/sardana/macroserver/macros/test/macroexecutor.py +++ b/src/sardana/macroserver/macros/test/macroexecutor.py @@ -65,7 +65,7 @@ def _clean(self): self._common.__init__() def run(self, macro_name, macro_params=None, sync=True, - timeout=float("inf")): + timeout=None): """Execute macro. :param macro_name: (string) name of macro to be executed @@ -75,7 +75,8 @@ def run(self, macro_name, macro_params=None, sync=True, :param sync: (bool) whether synchronous or asynchronous call (default is sync=True) :param timeout: (float) timeout (in s) that will be passed to the wait - method, in case of synchronous execution + method, in case of synchronous execution; None means + wait infinitely In asyncrhonous execution method :meth:`~wait` has to be explicitly called. @@ -101,14 +102,15 @@ def _run(self, macro_name, macro_params): raise NotImplementedError('Method _run not implemented in class %s' % self.__class__.__name__) - def wait(self, timeout=float("inf")): + def wait(self, timeout=None): """ Wait until macro is done. Use it in asynchronous executions. - :param timeout: (float) waiting timeout (in s) + :param timeout: (float) waiting timeout (in s); None means wait + infinitely """ - if timeout <= 0: - timeout = float("inf") + if timeout is not None and timeout <= 0: + timeout = None self._wait(timeout) # TODO: workaround: this sleep is necessary to perform multiple tests. @@ -301,11 +303,7 @@ def getMacroExecutor(self, door_name=None): # For the moment I implement it by calling an internal member of # TaurusManager from taurus.core import TaurusManager - try: - scheme = TaurusManager()._get_scheme(door_name) - except AttributeError: - # TODO: For Taurus 4 compatibility - scheme = TaurusManager().getScheme(door_name) + scheme = TaurusManager().getScheme(door_name) #====================================================================== if scheme == 'tango': @@ -322,4 +320,4 @@ def _getTangoMacroExecutor(self, door_name): if __name__ == '__main__': from sardana import sardanacustomsettings door_name = getattr(sardanacustomsettings, 'UNITTEST_DOOR_NAME') - print MacroExecutorFactory().getMacroExecutor(door_name) + print(MacroExecutorFactory().getMacroExecutor(door_name)) diff --git a/src/sardana/macroserver/macros/test/sardemoenv.py b/src/sardana/macroserver/macros/test/sardemoenv.py index 77eb3ae4e7..2000486025 100644 --- a/src/sardana/macroserver/macros/test/sardemoenv.py +++ b/src/sardana/macroserver/macros/test/sardemoenv.py @@ -188,7 +188,7 @@ def getElements(elem_type="all", fallback_name="element_not_defined", taurus.warning("The door %s is not running. " % (door_name) + "Ignore this message if you are building the documentation.") elements = [fallback_name] * fallback_elements_len - except Exception, e: + except Exception as e: import taurus taurus.debug(e) taurus.warning("It was not possible to retrieve the element. " + @@ -235,14 +235,14 @@ def getIORs(): if __name__ == '__main__': s = SarDemoEnv() - print s.env - print s.getControllers() - print s.getCTs() - print s.getMotors() - print s.getPseudoMotors() - print s.getZerods() - print s.getOneds() - print s.getTwods() - print s.getElements('Moveable') - print s.getMoveables() - print s.getElements() + print(s.env) + print(s.getControllers()) + print(s.getCTs()) + print(s.getMotors()) + print(s.getPseudoMotors()) + print(s.getZerods()) + print(s.getOneds()) + print(s.getTwods()) + print(s.getElements('Moveable')) + print(s.getMoveables()) + print(s.getElements()) diff --git a/src/sardana/macroserver/macros/test/test_ct.py b/src/sardana/macroserver/macros/test/test_ct.py index de3f1bcfaa..94261967db 100644 --- a/src/sardana/macroserver/macros/test/test_ct.py +++ b/src/sardana/macroserver/macros/test/test_ct.py @@ -25,7 +25,7 @@ """Tests for ct macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunStopMacroTestCase from sardana.macroserver.macros.test import testRun from sardana.macroserver.macros.test import testStop diff --git a/src/sardana/macroserver/macros/test/test_env.py b/src/sardana/macroserver/macros/test/test_env.py index 499b9a03f2..db54b2210a 100644 --- a/src/sardana/macroserver/macros/test/test_env.py +++ b/src/sardana/macroserver/macros/test/test_env.py @@ -25,7 +25,7 @@ """Tests for environment macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun diff --git a/src/sardana/macroserver/macros/test/test_expert.py b/src/sardana/macroserver/macros/test/test_expert.py index c85c0c0c11..bc02615926 100644 --- a/src/sardana/macroserver/macros/test/test_expert.py +++ b/src/sardana/macroserver/macros/test/test_expert.py @@ -25,7 +25,7 @@ """Tests for expert macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun, getCTs,\ getControllers @@ -60,7 +60,7 @@ def test_expert(self): self.macro_runs(macro_name="udefctrl", macro_params=[CTRL_NAME], wait_timeout=1) - except Exception, e: + except Exception as e: import taurus taurus.warning("Your system may stay dirty due to an unexpected" " exception during the test.") @@ -78,7 +78,7 @@ def test_meas(self): macro_params=[MNTGRP_NAME, CT_NAME1, CT_NAME2]) self.macro_runs(macro_name="udefmeas", macro_params=[MNTGRP_NAME]) - except Exception, e: + except Exception as e: import taurus taurus.warning("Your system may stay dirty due to an unexpected" " exception during the test.") diff --git a/src/sardana/macroserver/macros/test/test_gh.py b/src/sardana/macroserver/macros/test/test_gh.py index 17ab4fb352..e849297522 100644 --- a/src/sardana/macroserver/macros/test/test_gh.py +++ b/src/sardana/macroserver/macros/test/test_gh.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun from sardana.tango.macroserver.test import BaseMacroServerTestCase diff --git a/src/sardana/macroserver/macros/test/test_ioregister.py b/src/sardana/macroserver/macros/test/test_ioregister.py index 1fbc7315f7..f4220a715f 100644 --- a/src/sardana/macroserver/macros/test/test_ioregister.py +++ b/src/sardana/macroserver/macros/test/test_ioregister.py @@ -25,7 +25,7 @@ """Tests for ioregister macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun, getIORs IOR_NAME = getIORs()[0] diff --git a/src/sardana/macroserver/macros/test/test_list.py b/src/sardana/macroserver/macros/test/test_list.py index ff24c16d12..99f5650474 100644 --- a/src/sardana/macroserver/macros/test/test_list.py +++ b/src/sardana/macroserver/macros/test/test_list.py @@ -26,7 +26,7 @@ """Tests for list macros""" import time -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import (RunMacroTestCase, testRun, SarDemoEnv) diff --git a/src/sardana/macroserver/macros/test/test_macro.py b/src/sardana/macroserver/macros/test/test_macro.py index cca43cd648..ad813b649d 100644 --- a/src/sardana/macroserver/macros/test/test_macro.py +++ b/src/sardana/macroserver/macros/test/test_macro.py @@ -25,7 +25,7 @@ import os -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun from sardana.tango.macroserver.test import BaseMacroServerTestCase @@ -46,6 +46,6 @@ def setUp(self): RunMacroTestCase.setUp(self) def tearDown(self): - BaseMacroServerTestCase.tearDown(self) RunMacroTestCase.tearDown(self) + BaseMacroServerTestCase.tearDown(self) unittest.TestCase.tearDown(self) diff --git a/src/sardana/macroserver/macros/test/test_scan.py b/src/sardana/macroserver/macros/test/test_scan.py index 45b16abbb2..fcbca22e45 100644 --- a/src/sardana/macroserver/macros/test/test_scan.py +++ b/src/sardana/macroserver/macros/test/test_scan.py @@ -25,7 +25,7 @@ """Tests for scan macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import (RunStopMacroTestCase, testRun, testStop, getMotors) diff --git a/src/sardana/macroserver/macros/test/test_scanct.py b/src/sardana/macroserver/macros/test/test_scanct.py index b58a8478e2..702013cadb 100644 --- a/src/sardana/macroserver/macros/test/test_scanct.py +++ b/src/sardana/macroserver/macros/test/test_scanct.py @@ -26,7 +26,7 @@ """Tests for continuous scans (ct-like)""" import time import PyTango -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import (RunStopMacroTestCase, testRun, testStop) from sardana.pool import AcqSynchType @@ -66,10 +66,10 @@ def parsingOutputPoints(self, log_output): def orderPointsData(self, data): """A helper method to know if points are ordered based on getData. """ - obtained_nb_points_data = len(data.keys()) + obtained_nb_points_data = len(list(data.keys())) ordered_points_data = 0 for i in range(obtained_nb_points_data - 1): - if int(data.keys()[i + 1]) >= int(data.keys()[i]): + if int(list(data.keys())[i + 1]) >= int(list(data.keys())[i]): ordered_points_data = 1 else: ordered_points_data = 0 @@ -133,11 +133,11 @@ def check_using_data(self, expected_nb_points): # Test data from macro (macro_executor.getData()) data = self.macro_executor.getData() order_points_data = self.utils.orderPointsData(data) - obtained_nb_points_data = len(data.keys()) + obtained_nb_points_data = len(list(data.keys())) msg = ("The ascanct execution did not return any scan point.\n" "Checked using macro data.") - self.assertTrue(len(data.keys()) > 0, msg) + self.assertTrue(len(list(data.keys())) > 0, msg) msg = ("The ascanct execution did not return the expected number of " "points.\nExpected " + str(expected_nb_points) + " points." @@ -160,9 +160,9 @@ def check_stopped(self): self.assertEqual(state, desired_state, msg) def tearDown(self): + RunStopMacroTestCase.tearDown(self) BaseMacroServerTestCase.tearDown(self) MeasSarTestTestCase.tearDown(self) - RunStopMacroTestCase.tearDown(self) mg_config1 = { @@ -249,7 +249,7 @@ def setUp(self): unittest.TestCase.setUp(self) ScanctTest.setUp(self) - def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): + def macro_runs(self, meas_config, macro_params, wait_timeout=None): motors = [macro_params[0]] ScanctTest.configure_motors(self, motors) ScanctTest.configure_mntgrp(self, meas_config) @@ -263,7 +263,7 @@ def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): ScanctTest.check_using_output(self, expected_nb_points) ScanctTest.check_using_data(self, expected_nb_points) - def macro_stops(self, meas_config, macro_params, wait_timeout=float("inf"), + def macro_stops(self, meas_config, macro_params, wait_timeout=None, stop_delay=0.1): motors = [macro_params[0]] ScanctTest.configure_motors(self, motors) @@ -303,7 +303,7 @@ def setUp(self): unittest.TestCase.setUp(self) ScanctTest.setUp(self) - def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): + def macro_runs(self, meas_config, macro_params, wait_timeout=None): motors = [macro_params[self.MOT1], macro_params[self.MOT2]] ScanctTest.configure_motors(self, motors) ScanctTest.configure_mntgrp(self, meas_config) @@ -317,7 +317,7 @@ def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): ScanctTest.check_using_output(self, expected_nb_points) ScanctTest.check_using_data(self, expected_nb_points) - def macro_stops(self, meas_config, macro_params, wait_timeout=float("inf"), + def macro_stops(self, meas_config, macro_params, wait_timeout=None, stop_delay=0.1): motors = [macro_params[self.MOT1], macro_params[self.MOT2]] ScanctTest.configure_motors(self, motors) @@ -359,7 +359,7 @@ def setUp(self): unittest.TestCase.setUp(self) ScanctTest.setUp(self) - def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): + def macro_runs(self, meas_config, macro_params, wait_timeout=None): motors = [macro_params[self.MOT1], macro_params[self.MOT2]] ScanctTest.configure_motors(self, motors) ScanctTest.configure_mntgrp(self, meas_config) @@ -373,7 +373,7 @@ def macro_runs(self, meas_config, macro_params, wait_timeout=float("inf")): ScanctTest.check_using_output(self, expected_nb_points) ScanctTest.check_using_data(self, expected_nb_points) - def macro_stops(self, meas_config, macro_params, wait_timeout=float("inf"), + def macro_stops(self, meas_config, macro_params, wait_timeout=None, stop_delay=0.1): motors = [macro_params[self.MOT1], macro_params[self.MOT2]] ScanctTest.configure_motors(self, motors) diff --git a/src/sardana/macroserver/macros/test/test_standard.py b/src/sardana/macroserver/macros/test/test_standard.py index b7a51006c7..01764f4fe9 100644 --- a/src/sardana/macroserver/macros/test/test_standard.py +++ b/src/sardana/macroserver/macros/test/test_standard.py @@ -25,7 +25,7 @@ """Tests for standard macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import RunMacroTestCase, testRun,\ getMotors diff --git a/src/sardana/macroserver/macros/test/test_wm.py b/src/sardana/macroserver/macros/test/test_wm.py index 2388fd36bf..00bb7ce36c 100644 --- a/src/sardana/macroserver/macros/test/test_wm.py +++ b/src/sardana/macroserver/macros/test/test_wm.py @@ -25,7 +25,7 @@ """Tests for wm macros""" -from taurus.external import unittest +import unittest from sardana.macroserver.macros.test import (RunMacroTestCase, testRun, getMotors) diff --git a/src/sardana/macroserver/macroserver.py b/src/sardana/macroserver/macroserver.py index ef90eb32f1..721e9a9c38 100644 --- a/src/sardana/macroserver/macroserver.py +++ b/src/sardana/macroserver/macroserver.py @@ -23,7 +23,7 @@ ## ############################################################################## -from __future__ import with_statement + import os import re @@ -79,7 +79,7 @@ def __init__(self, **kwargs): #: dictionary #: dict<:data:`~sardana.ElementType`, :class:`~sardana.macroserver.macroserver.TypeData`> TYPE_MAP_OBJ = {} -for t, d in TYPE_MAP.items(): +for t, d in list(TYPE_MAP.items()): o = TypeData(type=t, name=d[0], family=d[1], klass=d[2], auto_full_name=d[3]) TYPE_MAP_OBJ[t] = o @@ -293,7 +293,7 @@ def set_pool_names(self, pool_names): :param pool_names: sequence of pool names :type pool_names: seq""" - for pool in self._pools.values(): + for pool in list(self._pools.values()): elements_attr = pool.getAttribute("Elements") elements_attr.removeListener(self.on_pool_elements_changed) @@ -315,7 +315,7 @@ def get_pool_names(self): the list of names of the pools this macro server is connected to :rtype: seq""" - return self._pools.keys() + return list(self._pools.keys()) def get_pool(self, pool_name): """Returns the device pool object corresponding to the given device name @@ -331,7 +331,7 @@ def get_pools(self): :return: the list of pools this macro server is connected to :rtype: seq""" - return self._pools.values() + return list(self._pools.values()) def on_pool_elements_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: @@ -385,13 +385,13 @@ def get_remote_elements_info(self): def get_local_elements_info(self): # fill macro library info ret = [macrolib.serialize() - for macrolib in self.get_macro_libs().values()] + for macrolib in list(self.get_macro_libs().values())] # fill macro info ret += [macro.serialize() - for macro in self.get_macros().values()] + for macro in list(self.get_macros().values())] # fill parameter type info ret += [paramtype.serialize() - for paramtype in self.get_data_types().values()] + for paramtype in list(self.get_data_types().values())] return ret @@ -455,8 +455,8 @@ def reload_macro_lib(self, lib_name): new_elements.append(new_lib) else: changed_elements.append(new_lib) - new_names = set([macro.name for macro in new_lib.get_macros()]) - old_names = set([macro.name for macro in old_lib.get_macros()]) + new_names = {macro.name for macro in new_lib.get_macros()} + old_names = {macro.name for macro in old_lib.get_macros()} changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) @@ -526,7 +526,7 @@ def get_macro_functions(self): def get_macro_libs_summary_info(self): libs = self.get_macro_libs() ret = [] - for module_name, macro_lib_info in libs.items(): + for module_name, macro_lib_info in list(libs.items()): elem = "%s (%s)" % (macro_lib_info.name, macro_lib_info.file_path) ret.append(elem) return ret @@ -662,7 +662,7 @@ def set_env_obj(self, data): env_man = self.environment_manager new, change = {}, {} - for key, value in data.items(): + for key, value in list(data.items()): d = new if env_man.hasEnv(key): d = change @@ -681,7 +681,7 @@ def change_env(self, data): del_env = data.get('del', []) new, change = {}, {} - for key, value in new_change_env.items(): + for key, value in list(new_change_env.items()): d = new if env_man.hasEnv(key): d = change @@ -740,7 +740,7 @@ def find_objects(self, param, type_class=All, subtype=All, pool=All): type_name_list = type_class obj_set = set() param = ['^%s$' % x for x in param] - re_objs = map(re.compile, param, len(param) * (re.IGNORECASE,)) + re_objs = list(map(re.compile, param, len(param) * (re.IGNORECASE,))) re_subtype = re.compile(subtype, re.IGNORECASE) for type_name in type_name_list: type_class_name = type_name @@ -750,14 +750,14 @@ def find_objects(self, param, type_class=All, subtype=All, pool=All): if not type_inst.hasCapability(ParamType.ItemList): continue if self.is_macroserver_interface(type_class_name): - for name, obj in type_inst.getObjDict(pool=pool).items(): + for name, obj in list(type_inst.getObjDict(pool=pool).items()): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = ElementType[obj.get_type()] if subtype is MacroServer.All or re_subtype.match(obj_type): obj_set.add(obj) else: - for name, obj in type_inst.getObjDict(pool=pool).items(): + for name, obj in list(type_inst.getObjDict(pool=pool).items()): for re_obj in re_objs: if re_obj.match(name) is not None: obj_type = obj.getType() diff --git a/src/sardana/macroserver/msdoor.py b/src/sardana/macroserver/msdoor.py index 0e0529a57f..1eb06fd24d 100644 --- a/src/sardana/macroserver/msdoor.py +++ b/src/sardana/macroserver/msdoor.py @@ -82,7 +82,7 @@ def rebuild(self): self.clear() door = self.door macros = self.door.get_macros() - for macro_name, macro_meta in macros.items(): + for macro_name, macro_meta in list(macros.items()): self[macro_name] = MacroProxy(door, macro_meta) @@ -209,7 +209,7 @@ def input(self, msg, *args, **kwargs): input_data = dict(prompt=msg, type='input') input_data.update(kwargs) data_type = kwargs['data_type'] - is_seq = not isinstance(data_type, (str, unicode)) and \ + is_seq = not isinstance(data_type, str) and \ isinstance(data_type, collections.Sequence) if is_seq: handle = self._handle_seq_input @@ -355,7 +355,7 @@ def get_macro_proxies(self): return self._macro_proxy_cache def run_macro(self, par_str_list, asynch=False): - if isinstance(par_str_list, (str, unicode)): + if isinstance(par_str_list, str): par_str_list = par_str_list, if not hasattr(self, "Output"): diff --git a/src/sardana/macroserver/msenvmanager.py b/src/sardana/macroserver/msenvmanager.py index 7292b6a89d..ac43dd5c20 100644 --- a/src/sardana/macroserver/msenvmanager.py +++ b/src/sardana/macroserver/msenvmanager.py @@ -32,12 +32,39 @@ import os import shelve +from itertools import zip_longest import operator from taurus.core.util.containers import CaselessDict from sardana.macroserver.msmanager import MacroServerManager from sardana.macroserver.msexception import UnknownEnv +from sardana import sardanacustomsettings +import collections + + +def _dbm_gnu(filename): + import dbm.gnu + return dbm.gnu.open(filename, "c") + + +def _dbm_dumb(filename): + import dbm.dumb + return dbm.dumb.open(filename, "c") + + +def _dbm_shelve(filename, backend): + if backend is None: + try: + return _dbm_gnu(filename) + except ImportError: + return _dbm_dumb(filename) + elif backend == "gnu": + return _dbm_gnu(filename) + elif backend == "dumb": + return _dbm_dumb(filename) + else: + raise ValueError("'{}' is not a supported backend".format(backend)) class EnvironmentManager(MacroServerManager): @@ -112,17 +139,26 @@ def setEnvironmentDb(self, f_name): try: self.info("Creating environment directory: %s" % dir_name) os.makedirs(dir_name) - except OSError, ose: + except OSError as ose: self.error("Creating environment: %s" % ose.strerror) self.debug("Details:", exc_info=1) raise ose - try: - self._env = shelve.open(f_name, flag='c', protocol=0, - writeback=False) - except: - self.error("Failed to create/access environment in %s", f_name) - self.debug("Details:", exc_info=1) - raise + if os.path.exists(f_name) or os.path.exists(f_name + ".dat"): + try: + self._env = shelve.open(f_name, flag='w', writeback=False) + except Exception: + self.error("Failed to access environment in %s", f_name) + self.debug("Details:", exc_info=1) + raise + else: + backend = getattr(sardanacustomsettings, "MS_ENV_SHELVE_BACKEND", + None) + try: + self._env = shelve.Shelf(_dbm_shelve(f_name, backend)) + except Exception: + self.error("Failed to create environment in %s", f_name) + self.debug("Details:", exc_info=1) + raise self.info("Environment is being stored in %s", f_name) @@ -136,7 +172,7 @@ def setEnvironmentDb(self, f_name): def _fillEnvironmentCaches(self, env): # fill the three environment caches env_dict = self._global_env - for k, v in env.items(): + for k, v in list(env.items()): k_parts = k.split('.', 1) key = k_parts[0] @@ -191,7 +227,7 @@ def _getDoorMacroPropertyEnv(self, prop): def _hasDoorMacroPropertyEnv(self, prop): """Determines if the environment contains a property with the format ..""" - return not self._getDoorMacroPropertyEnv() is None + return not self._getDoorMacroPropertyEnv(prop) is None def _getMacroPropertyEnv(self, prop): """Returns the property value for a property which must have the @@ -208,7 +244,7 @@ def _getMacroPropertyEnv(self, prop): def _hasMacroPropertyEnv(self, prop): """Determines if the environment contains a property with the format .""" - return not self._getMacroPropertyEnv() is None + return not self._getMacroPropertyEnv(prop) is None def _getDoorPropertyEnv(self, prop): """Returns the property value for a property which must have the @@ -225,7 +261,7 @@ def _getDoorPropertyEnv(self, prop): def _hasDoorPropertyEnv(self, prop): """Determines if the environment contains a property with the format .""" - return not self._getDoorPropertyEnv() is None + return not self._getDoorPropertyEnv(prop) is None def _getEnv(self, prop): """Returns the property value for a property which must have the @@ -315,7 +351,7 @@ def getAllDoorMacroEnv(self, door_name, macro_name): m_env = self._macro_env.get(macro_name, {}) # put the doors global environment - for k, v in d_env.iteritems(): + for k, v in d_env.items(): if k.count('.') == 0: ret[k] = v @@ -323,7 +359,7 @@ def getAllDoorMacroEnv(self, door_name, macro_name): ret.update(m_env) # put the door and macro specific environment - for k, v in d_env.iteritems(): + for k, v in d_env.items(): if k.count('.') > 0: m_name, key = k.split('.', 1) if m_name is macro_name: @@ -346,7 +382,7 @@ def getDoorMacroEnv(self, door_name, macro_name, keys=None): if keys is None: return self.getAllDoorMacroEnv(door_name, macro_name) - if isinstance(keys, (str, unicode)): + if isinstance(keys, str): keys = (keys,) door_name = door_name.lower() @@ -372,18 +408,18 @@ def getDoorMacroEnv(self, door_name, macro_name, keys=None): return ret - def _pairwise(self, iterable): - itnext = iter(iterable).next - while True: - yield itnext(), itnext() + def _grouper(self, iterable): + # https://docs.python.org/3/library/itertools.html#itertools-recipes + args = [iter(iterable)] * 2 + return zip_longest(*args) def _dictFromSequence(self, seq): - return dict(self._pairwise(seq)) + return dict(self._grouper(seq)) def _encode(self, d): ret = {} - for k, v in d.iteritems(): - if isinstance(v, (str, unicode)): + for k, v in d.items(): + if isinstance(v, str): try: v = eval(v) except: @@ -453,14 +489,14 @@ def setEnvObj(self, obj): @return a dict representing the added environment""" - if operator.isSequenceType(obj) and \ - not isinstance(obj, (str, unicode)): + if isinstance(obj, collections.Sequence) and \ + not isinstance(obj, str): obj = self._dictFromSequence(obj) - elif not operator.isMappingType(obj): + elif not isinstance(obj, collections.Mapping): raise TypeError("obj parameter must be a sequence or a map") obj = self._encode(obj) - for k, v in obj.iteritems(): + for k, v in obj.items(): self._setOneEnv(k, v) return obj @@ -480,7 +516,7 @@ def unsetEnv(self, key): :param key: the key for the environment to be unset :return: the sequence of keys which have been removed""" - if isinstance(key, (str, unicode)): + if isinstance(key, str): key = (key,) self._unsetEnv(key) return key diff --git a/src/sardana/macroserver/msexception.py b/src/sardana/macroserver/msexception.py index ca2ac2e22b..260f4960b2 100644 --- a/src/sardana/macroserver/msexception.py +++ b/src/sardana/macroserver/msexception.py @@ -30,12 +30,12 @@ "UnknownEnv", "UnknownMacro", "UnknownMacroLibrary", "UnknownRecorder", "MacroWrongParameterType", "LibraryError", "InterruptException", "StopException", "AbortException", - "InputCancelled"] + "ReleaseException", "InputCancelled"] __docformat__ = 'restructuredtext' from sardana.taurus.core.tango.sardana.pool import InterruptException, \ - StopException, AbortException + StopException, AbortException, ReleaseException from sardana.sardanaexception import SardanaException, SardanaExceptionList, \ UnknownCode, UnknownLibrary, LibraryError diff --git a/src/sardana/macroserver/msmacromanager.py b/src/sardana/macroserver/msmacromanager.py index 52b4b3ce89..7b95b8fd7c 100644 --- a/src/sardana/macroserver/msmacromanager.py +++ b/src/sardana/macroserver/msmacromanager.py @@ -46,11 +46,7 @@ from PyTango import DevFailed -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from taurus.core.util.log import Logger from taurus.core.util.codecs import CodecFactory @@ -58,7 +54,7 @@ from sardana.sardanadefs import ElementType from sardana.sardanamodulemanager import ModuleManager from sardana.sardanaexception import format_exception_only_str -from sardana.sardanautils import is_pure_str, is_non_str_seq +from sardana.sardanautils import is_pure_str, is_non_str_seq, recur_map from sardana.macroserver.msmanager import MacroServerManager from sardana.macroserver.msmetamacro import MACRO_TEMPLATE, MacroLibrary, \ @@ -69,8 +65,9 @@ Hookable from sardana.macroserver.msexception import UnknownMacroLibrary, \ LibraryError, UnknownMacro, MissingEnv, AbortException, StopException, \ - MacroServerException, UnknownEnv + ReleaseException, MacroServerException, UnknownEnv from sardana.util.parser import ParamParser +from sardana.util.thread import raise_in_thread # These classes are imported from the "client" part of sardana, if finally # both the client and the server side needs them, place them in some @@ -87,7 +84,7 @@ def islambda(f): f.__name__ == (lambda: True).__name__ -def is_macro(macro, abs_file=None, logger=None): +def _is_macro(macro, abs_file=None, logger=None): """Helper function to determine if a certain python object is a valid macro""" if inspect.isclass(macro): @@ -95,66 +92,54 @@ def is_macro(macro, abs_file=None, logger=None): return False # if it is a class defined in some other module forget it to # avoid replicating the same macro in different macro files - try: - # use normcase to treat case insensitivity of paths on - # certain platforms e.g. Windows - if os.path.normcase(inspect.getabsfile(macro)) !=\ - os.path.normcase(abs_file): - return False - except TypeError: + + # use normcase to treat case insensitivity of paths on + # certain platforms e.g. Windows + if os.path.normcase(inspect.getabsfile(macro)) !=\ + os.path.normcase(abs_file): return False + elif callable(macro) and not islambda(macro): # if it is a function defined in some other module forget it to # avoid replicating the same macro in different macro files - try: - # use normcase to treat case insensitivity of paths on - # certain platforms e.g. Windows - if os.path.normcase(inspect.getabsfile(macro)) !=\ - os.path.normcase(abs_file): - return False - except TypeError: + + # use normcase to treat case insensitivity of paths on + # certain platforms e.g. Windows + if os.path.normcase(inspect.getabsfile(macro)) !=\ + os.path.normcase(abs_file): return False if not hasattr(macro, 'macro_data'): return False - args, varargs, keywords, _ = inspect.getargspec(macro) + args, varargs, keywords, *_ = inspect.getfullargspec(macro) if len(args) == 0: if logger: logger.debug("Could not add macro %s: Needs at least one " "parameter (usually called 'self')", - macro.func_name) + macro.__name__) return False if keywords is not None: if logger: logger.debug("Could not add macro %s: Unsupported keyword " - "parameters '%s'", macro.func_name, keywords) + "parameters '%s'", macro.__name__, keywords) return False if varargs and len(args) > 1: if logger: logger.debug("Could not add macro %s: Unsupported giving " "named parameters '%s' and varargs '%s'", - macro.func_name, args, varargs) + macro.__name__, args, varargs) return False else: return False return True -def recur_map(fun, data, keep_none=False): - """Recursive map. Similar to map, but maintains the list objects structure - - :param fun: the same purpose as in map function - :param data: the same purpose as in map function - :param keep_none: keep None elements without applying fun - """ - if hasattr(data, "__iter__"): - return [recur_map(fun, elem, keep_none) for elem in data] - else: - if keep_none is True and data is None: - return data - else: - return fun(data) +def is_macro(macro, abs_file=None, logger=None): + try: + return _is_macro(macro, abs_file=abs_file, logger=logger) + except Exception: + return False def is_flat_list(obj): @@ -241,7 +226,7 @@ class A != class A).""" self._macro_path = p macro_file_names = self._findMacroLibNames() - for mod_name, file_name in macro_file_names.iteritems(): + for mod_name, file_name in macro_file_names.items(): dir_name = os.path.dirname(file_name) path = [dir_name] try: @@ -326,7 +311,7 @@ def getOrCreateMacroLib(self, lib_name, macro_name=None): f_name, code = self.createMacroLib(lib_name), '' else: f_name = macro_lib.file_path - f = file(f_name) + f = open(f_name) code = f.read() f.close() else: @@ -341,7 +326,7 @@ def getOrCreateMacroLib(self, lib_name, macro_name=None): else: _, line_nb = macro.code f_name = macro.file_path - f = file(f_name) + f = open(f_name) code = f.read() f.close() @@ -539,7 +524,7 @@ def reloadMacroLib(self, module_name, path=None): macro_name = macro.__name__ if macro_name in self._overwritten_macros: isoverwritten = True - elif (macro_name in self._macro_dict.keys() + elif (macro_name in list(self._macro_dict.keys()) and self._macro_dict[macro_name].lib != macro_lib): isoverwritten = True msg = ('Macro "{0}" defined in "{1}" macro library' @@ -564,7 +549,7 @@ def reloadMacroLib(self, module_name, path=None): finally: if macro_errors: msg = "" - for key, value in macro_errors.iteritems(): + for key, value in macro_errors.items(): msg_part = ("\n" + "Error adding macro(s): " + key + "\n" + "It presents an error: \n" + str(value)) msg += str(msg_part) + "\n" @@ -600,7 +585,7 @@ def addMacroClass(self, macro_lib, klass, isoverwritten=False): self._macro_dict[macro_name] = macro_class def addMacroFunction(self, macro_lib, func, isoverwritten=False): - macro_name = func.func_name + macro_name = func.__name__ action = (macro_lib.has_macro(macro_name) and "Updating") or "Adding" self.debug("%s macro function %s" % (action, macro_name)) @@ -618,7 +603,7 @@ def getMacroLibs(self, filter=None): return self._modules expr = re.compile(filter, re.IGNORECASE) ret = {} - for name, macro_lib in self._modules.iteritems(): + for name, macro_lib in self._modules.items(): if expr.match(name) is None: continue ret[name] = macro_lib @@ -639,7 +624,7 @@ def getMacros(self, filter=None): expr = re.compile(filter, re.IGNORECASE) ret = {} - for name, macro in self._macro_dict.iteritems(): + for name, macro in self._macro_dict.items(): if expr.match(name) is None: continue ret[name] = macro @@ -657,7 +642,7 @@ def getMacroClasses(self, filter=None): :obj:`dict`\<:obj:`str`\, :class:`~sardana.macroserver.msmetamacro.MacroClass`\>""" macros = self.getMacros(filter=filter) macro_classes = {} - for name, macro in macros.items(): + for name, macro in list(macros.items()): if macro.get_type() == ElementType.MacroClass: macro_classes[name] = macro return macro_classes @@ -674,7 +659,7 @@ def getMacroFunctions(self, filter=None): :obj:`dict`\<:obj:`str`\, :class:`~sardana.macroserver.msmetamacro.MacroFunction`\>""" macros = self.getMacros(filter=filter) macro_classes = {} - for name, macro in macros.items(): + for name, macro in list(macros.items()): if macro.get_type() == ElementType.MacroFunction: macro_classes[name] = macro return macro_classes @@ -700,12 +685,12 @@ def removeMacro(self, macro_name): def getMacroLib(self, name): if os.path.isabs(name): abs_file_name = name - for lib in self._modules.values(): + for lib in list(self._modules.values()): if lib.file_path == abs_file_name: return lib elif name.count(os.path.extsep): file_name = name - for lib in self._modules.values(): + for lib in list(self._modules.values()): if lib.file_name == file_name: return lib module_name = name @@ -724,7 +709,7 @@ def getMacroFunctionCode(self, macro_name): return self.getMacroFunction(macro_name).function def getMacroInfo(self, macro_names, format='json'): - if isinstance(macro_names, (str, unicode)): + if isinstance(macro_names, str): macro_names = [macro_names] ret = [] json_codec = CodecFactory().getCodec(format) @@ -736,7 +721,6 @@ def getMacroInfo(self, macro_names, format='json'): def _createMacroNode(self, macro_name, macro_params_raw): macro = self.getMacro(macro_name) params_def = macro.get_parameter() - # merge params to a single, space separated, string (spock like) macro_params_str = " ".join(macro_params_raw) param_parser = ParamParser(params_def) # parse string with macro params to the correct list representation @@ -763,7 +747,7 @@ def decodeMacroParameters(self, door, raw_params): type_manager = door.type_manager try: out_par_list = ParamDecoder(type_manager, params_def, raw_params) - except WrongParam, out_e: + except WrongParam as out_e: # only if raw params are passed as a list e.g. using macro API # execMacro("mv", mot01, 0.0) and parameters definition allows to # decode it from a flat list we give it a try @@ -773,10 +757,10 @@ def decodeMacroParameters(self, door, raw_params): try: out_par_list = FlatParamDecoder(type_manager, params_def, raw_params) - except WrongParam, in_e: + except WrongParam as in_e: msg = ("Either of: %s or %s made it impossible to decode" - " parameters" % (out_e.message, in_e.message)) - raise WrongParam, msg + " parameters" % (out_e, in_e)) + raise WrongParam(msg) else: raise out_e return macro_meta, raw_params, out_par_list @@ -813,10 +797,14 @@ def createMacroObj(self, macro_class, par_list, init_opts={}): macro_name = macro_class.__name__ environment = init_opts.get('environment') + executor = init_opts.get('executor') + door_name = executor.door.name r = [] for env in macro_env: - if not environment.has_env(env): + if not environment.has_env(env, + macro_name=macro_name, + door_name=door_name): r.append(env) if r: raise MissingEnv("The macro %s requires the following missing " @@ -841,13 +829,16 @@ def createMacroObjFromMeta(self, meta, par_list, init_opts={}): macro_env = code.env or () environment = init_opts.get('environment') - + executor = init_opts.get('executor') + door_name = executor.door.name + macro_name = meta.name r = [] for env in macro_env: - if not environment.has_env(env): + if not environment.has_env(env, + macro_name=macro_name, + door_name=door_name): r.append(env) if r: - macro_name = meta.name raise MissingEnv("The macro %s requires the following missing " "environment to be defined: %s" % (macro_name, str(r))) @@ -878,7 +869,7 @@ def __init__(self, param=None): def filter(self, record): allow = True if record.levelname == "DEBUG": - if type(record.msg) != str: + if not isinstance(record.msg, str): allow = False return allow if record.msg.find("[START]") != -1: @@ -953,7 +944,7 @@ def getFilterClass(self): except AttributeError: pass else: - if isinstance(log_macro_filter, basestring): + if isinstance(log_macro_filter, str): try: module_name, filter_name = log_macro_filter.rsplit('.', 1) __import__(module_name) @@ -1086,6 +1077,11 @@ def __init__(self, door): # key Macro - macro object # value - sequence of reserverd objects by the macro self._reserved_macro_objs = {} + # dict> + # key Macro - macro object + # value - sequence of reserverd objects by the macro + # which were already successfully stopped + self._stopped_macro_objs = {} # reset the stacks # self._macro_stack = None @@ -1093,9 +1089,12 @@ def __init__(self, door): self._macro_stack = [] self._xml_stack = [] self._macro_pointer = None + self._abort_thread = None self._aborted = False + self._stop_thread = None self._stopped = False self._paused = False + self._released = False self._last_macro_status = None # threading events for synchronization of stopping/abortting of # reserved objects @@ -1153,7 +1152,26 @@ def _preprocessParameters(self, par_str_list): xml_root = xml_seq = etree.Element('sequence') macro_name = par_str_list[0] macro_params = par_str_list[1:] - macro_node = self._createMacroNode(macro_name, macro_params) + + def quote_string(string): + # if string contains double quotes, use single quotes, + # otherwise use double quotes + if re.search('"', string): + return "'{}'".format(string) + else: + return '"{}"'.format(string) + + # param parser relies on whitespace separation of parameter values + # quote string parameter values containing whitespaces + macro_params_quoted = [] + for param in macro_params: + if (not re.match(r".*\s+.*", param) # no white spaces + or re.match(r"^'.*\s+.*'$", param) # already quoted + or re.match(r'^".*\s+.*"$', param)): # already quoted + macro_params_quoted.append(param) + else: + macro_params_quoted.append(quote_string(param)) + macro_node = self._createMacroNode(macro_name, macro_params_quoted) xml_macro = macro_node.toXml() xml_seq.append(xml_macro) else: @@ -1192,7 +1210,7 @@ def __preprocessResult(self, result): if result is None: return () if is_non_str_seq(result): - result = map(str, result) + result = list(map(str, result)) else: result = (str(result),) return result @@ -1204,7 +1222,7 @@ def _composeMacroLine(self, macro_name, macro_params, macro_id): # recursive map to maintain the list objects structure params_str_list = recur_map(str, macro_params) # plain map to be able to perform join (only strings may be joined) - params_str_list = map(str, params_str_list) + params_str_list = list(map(str, params_str_list)) params_str = ', '.join(params_str_list) macro_id = macro_id # create macro_line - string representation of macro, its parameters @@ -1261,7 +1279,7 @@ def _prepareXMLMacro(self, xml_macro, parent_macro=None): def _createMacroObj(self, macro_name_or_meta, pars, init_opts={}): macro_meta = macro_name_or_meta - if isinstance(macro_meta, (str, unicode)): + if isinstance(macro_meta, str): macro_meta = self.macro_manager.getMacro(macro_meta) macro_opts = { @@ -1269,7 +1287,7 @@ def _createMacroObj(self, macro_name_or_meta, pars, init_opts={}): 'environment': self.macro_server } macro_opts.update(init_opts) - if not macro_opts.has_key('id'): + if 'id' not in macro_opts: macro_opts['id'] = str(self.getNewMacroID()) macroObj = self.macro_manager.createMacroObjFromMeta(macro_meta, pars, @@ -1390,8 +1408,15 @@ def clearRunningMacro(self): def __stopObjects(self): """Stops all the reserved objects in the executor""" - for _, objs in self._reserved_macro_objs.items(): + for macro, objs in list(self._reserved_macro_objs.items()): + if self._aborted: + break # someone aborted, no sense to stop anymore + self._stopped_macro_objs[macro] = stopped_macro_objs = [] for obj in objs: + if self._aborted: + break # someone aborted, no sense to stop anymore + self.output( + "Stopping {} reserved by {}".format(obj, macro._name)) try: obj.stop() except AttributeError: @@ -1399,11 +1424,19 @@ def __stopObjects(self): except: self.warning("Unable to stop %s" % obj) self.debug("Details:", exc_info=1) + else: + self.output("{} stopped".format(obj)) + stopped_macro_objs.append(obj) def __abortObjects(self): """Aborts all the reserved objects in the executor""" - for _, objs in self._reserved_macro_objs.items(): + for macro, objs in list(self._reserved_macro_objs.items()): + stopped_macro_objs = self._stopped_macro_objs[macro] for obj in objs: + if obj in stopped_macro_objs: + continue + self.output( + "Aborting {} reserved by {}".format(obj, macro._name)) try: obj.abort() except AttributeError: @@ -1411,6 +1444,8 @@ def __abortObjects(self): except: self.warning("Unable to abort %s" % obj) self.debug("Details:", exc_info=1) + else: + self.output("{} aborted".format(obj)) def _setStopDone(self, _): self._stop_done.set() @@ -1418,29 +1453,66 @@ def _setStopDone(self, _): def _waitStopDone(self, timeout=None): self._stop_done.wait(timeout) + def _isStopDone(self): + return self._stop_done.is_set() + def _setAbortDone(self, _): self._abort_done.set() def _waitAbortDone(self, timeout=None): self._abort_done.wait(timeout) + def _isAbortDone(self): + return self._abort_done.is_set() + def abort(self): + """**Internal method**. Aborts the macro abruptly.""" + # carefull: Inside this method never call a method that has the + # mAPI decorator + self._aborted = True + if not self._isStopDone(): + Logger.debug(self, "Break stopping...") + raise_in_thread(ReleaseException, self._stop_thread) self.macro_server.add_job(self._abort, self._setAbortDone) + def release(self): + """**Internal method**. Release the macro from hang situations + + Hanged situations: + * hanged process of aborting reserved objects + * hanged macro on_abort method. + """ + # carefull: Inside this method never call a method that has the + # mAPI decorator + self._released = True + if self._isAbortDone(): + m = self.getRunningMacro() + Logger.debug(self, "Break {}.on_abort...".format(m._name)) + raise_in_thread(ReleaseException, m._macro_thread) + else: + Logger.debug(self, "Break aborting...") + raise_in_thread(ReleaseException, self._abort_thread) + + def stop(self): + self._stopped = True self.macro_server.add_job(self._stop, self._setStopDone) def _abort(self): + self._abort_thread = threading.current_thread() + if self._stopped: + # stopping did not finish on its own - we are aborting it + # but need to wait anyway so its thread finishes + self._waitStopDone() m = self.getRunningMacro() if m is not None: - self._aborted = True m.abort() self.__abortObjects() def _stop(self): + self._stop_thread = threading.current_thread() m = self.getRunningMacro() if m is not None: - self._stopped = True m.stop() if m.isPaused(): m.resume(cb=self._macroResumed) @@ -1598,31 +1670,33 @@ def runMacro(self, macro_obj): mse.traceback = traceback.format_exc() except DevFailed as df: exc_info = sys.exc_info() - exp_pars = {'type': df[0].reason, - 'msg': df[0].desc, + exp_pars = {'type': df.args[0].reason, + 'msg': df.args[0].desc, 'args': df.args, 'traceback': traceback.format_exc()} macro_exp = MacroServerException(exp_pars) - except Exception, err: + except Exception as err: exc_info = sys.exc_info() exp_pars = {'type': err.__class__.__name__, 'msg': str(err), 'args': err.args, 'traceback': traceback.format_exc()} macro_exp = MacroServerException(exp_pars) - finally: - self.returnObjs(self._macro_pointer) # make sure the macro's on_abort is called and that a proper macro # status is sent - if self._stopped: - self._waitStopDone() - macro_obj._stopOnError() - self.sendMacroStatusStop() - elif self._aborted: + if self._aborted: self._waitAbortDone() + self.output("Executing {}.on_abort method...".format(name)) macro_obj._abortOnError() self.sendMacroStatusAbort() + elif self._stopped: + self._waitStopDone() + self.output("Executing {}.on_stop method...".format(name)) + macro_obj._stopOnError() + self.sendMacroStatusStop() + + self.returnObjs(self._macro_pointer) # From this point on don't call any method of macro_obj which is part # of the mAPI (methods decorated with @mAPI) to avoid throwing an @@ -1635,8 +1709,11 @@ def runMacro(self, macro_obj): if isinstance(macro_exp, MacroServerException): if macro_obj.parent_macro is None: door.debug(macro_exp.traceback) - door.error("An error occurred while running %s:\n%s" % - (macro_obj.description, macro_exp.msg)) + msg = ("An error occurred while running {}:\n" + "{!r}").format(macro_obj.getName(), macro_exp) + door.error(msg) + msg = "Hint: in Spock execute `www`to get more details" + door.info(msg) self._popMacro() raise macro_exp self.debug("[ END ] runMacro %s" % desc) @@ -1757,6 +1834,8 @@ def returnObjs(self, macro_obj): """Free the macro reserved objects""" if macro_obj is None: return + # remove eventually stopped objects to not keep reference to them + self._stopped_macro_objs.pop(macro_obj, None) objs = self._reserved_macro_objs.get(macro_obj) if objs is None: return diff --git a/src/sardana/macroserver/msmetamacro.py b/src/sardana/macroserver/msmetamacro.py index 6986f1ce0c..5aca2fcf28 100644 --- a/src/sardana/macroserver/msmetamacro.py +++ b/src/sardana/macroserver/msmetamacro.py @@ -35,7 +35,8 @@ from sardana import InvalidId, ElementType from sardana.sardanameta import SardanaLibrary, SardanaClass, SardanaFunction -from sardana.macroserver.msparameter import Type, ParamRepeat +from sardana.macroserver.msparameter import Type +import collections MACRO_TEMPLATE = """class @macro_name@(Macro): \"\"\"@macro_name@ description.\"\"\" @@ -149,20 +150,17 @@ def _build_parameter(self, param_def): '''Builds a list of parameters, each of them represented by a dictionary containing information: name, type, default_value, description, min and max values. In case of simple parameters, type is the parameter type. - In case of ParamRepeat, type is a list containing definition of the - param repeat. + In case of repeat parameter, type is a list containing its definition. ''' ret = [] param_def = param_def or () for p in param_def: t = p[1] - ret_p = {'min': 1, 'max': None} - # take care of old ParamRepeat - if isinstance(t, ParamRepeat): - t = t.obj() + ret_p = {'min': None, 'max': None} - if operator.isSequenceType(t) and not isinstance(t, (str, unicode)): - if operator.isMappingType(t[-1]): + if isinstance(t, collections.Sequence) and not isinstance(t, str): + ret_p = {'min': 1, 'max': None} + if isinstance(t[-1], collections.Mapping): ret_p.update(t[-1]) t = self._build_parameter(t[:-1]) else: @@ -180,7 +178,7 @@ def build_parameter_info(self, param_def=None): info = [str(len(param_def))] for name, type_class, def_val, desc in param_def: - repeat = isinstance(type_class, ParamRepeat) + repeat = isinstance(type_class, list) info.append(name) type_name = (repeat and 'ParamRepeat') or type_class info.append(type_name) @@ -188,7 +186,7 @@ def build_parameter_info(self, param_def=None): if repeat: rep = type_class opts = sep = '' - for opt, val in rep.items(): + for opt, val in list(rep.items()): opts += '%s%s=%s' % (sep, opt, val) sep = ', ' info.append(opts) @@ -202,7 +200,7 @@ def build_result_info(self, result_def=None): info = [str(len(result_def))] for name, type_class, def_val, desc in result_def: - repeat = isinstance(type_class, ParamRepeat) + repeat = isinstance(type_class, list) info.append(name) type_name = (repeat and 'ParamRepeat') or type_class info.append(type_name) @@ -210,7 +208,7 @@ def build_result_info(self, result_def=None): if repeat: rep = type_class opts = sep = '' - for opt, val in rep.items(): + for opt, val in list(rep.items()): opts += '%s%s=%s' % (sep, opt, val) sep = ', ' info.append(opts) @@ -243,6 +241,9 @@ def __init__(self, **kwargs): SardanaClass.__init__(self, **kwargs) Parameterizable.__init__(self) + def __lt__(self, o): + return self.name < o.name + def serialize(self, *args, **kwargs): kwargs = SardanaClass.serialize(self, *args, **kwargs) kwargs = Parameterizable.serialize(self, *args, **kwargs) @@ -270,6 +271,9 @@ def __init__(self, **kwargs): SardanaFunction.__init__(self, **kwargs) Parameterizable.__init__(self) + def __lt__(self, o): + return self.name < o.name + def serialize(self, *args, **kwargs): kwargs = SardanaFunction.serialize(self, *args, **kwargs) kwargs = Parameterizable.serialize(self, *args, **kwargs) diff --git a/src/sardana/macroserver/msoptions.py b/src/sardana/macroserver/msoptions.py index d6c3dadeb0..b73b242bf4 100644 --- a/src/sardana/macroserver/msoptions.py +++ b/src/sardana/macroserver/msoptions.py @@ -25,8 +25,8 @@ """This module contains a definition for ViewOptions""" -from __future__ import with_statement -from __future__ import print_function + + __all__ = ['ViewOption'] @@ -37,9 +37,7 @@ def ViewOptionMeta(name, bases, attrs): return type(name, bases, attrs) -class ViewOption(object): - __metaclass__ = ViewOptionMeta - +class ViewOption(object, metaclass=ViewOptionMeta): _DEFAULT_VIEW_OPTIONS = { 'ShowDial': True, 'ShowCtrlAxis': False, diff --git a/src/sardana/macroserver/msparameter.py b/src/sardana/macroserver/msparameter.py index f7bd8ade2c..763545a0a0 100644 --- a/src/sardana/macroserver/msparameter.py +++ b/src/sardana/macroserver/msparameter.py @@ -29,7 +29,7 @@ __all__ = ["WrongParam", "MissingParam", "SupernumeraryParam", "UnknownParamObj", "WrongParamType", "MissingRepeat", "SupernumeraryRepeat", "TypeNames", "Type", "ParamType", - "ParamRepeat", "ElementParamType", "ElementParamInterface", + "ElementParamType", "ElementParamInterface", "AttrParamType", "AbstractParamTypes", "ParamDecoder"] __docformat__ = 'restructuredtext' @@ -51,9 +51,9 @@ def __init__(self, obj): attributes = dir(self) for attr in attributes: - # iteritems is necessary fo python 2.6 implementation of json - if attr in ['__setattr__', '__repr__', 'raise_error', '__class__', - '__dict__', '__weakref__', 'iteritems']: + # items is necessary fo python 3.5 implementation of json + if attr in ['__setattr__', 'raise_error', '__class__', + '__dict__', '__weakref__', 'items']: continue self.__setattr__(attr, self.raise_error) self.__setattr__ = self.raise_error @@ -140,7 +140,7 @@ def removeType(self, name): pass def __str__(self): - return str(self._type_names.keys()) + return str(list(self._type_names.keys())) # def __getattr__(self, name): # if name not in self._pending_type_names: @@ -186,26 +186,6 @@ def serialize(self, *args, **kwargs): return kwargs -class ParamRepeat(object): - # opts: min, max - - def __init__(self, *param_def, **opts): - self.param_def = param_def - self.opts = {'min': 1, 'max': None} - self.opts.update(opts) - self._obj = list(param_def) - self._obj.append(self.opts) - - def items(self): - return self.opts.items() - - def __getattr__(self, name): - return self.opts[name] - - def obj(self): - return self._obj - - class ElementParamType(ParamType): capabilities = ParamType.ItemList, ParamType.ItemListEvents @@ -252,10 +232,11 @@ def getObjDict(self, pool=ParamType.All, cache=False): for elem_info in pool.getElements(): if self.accepts(elem_info): objs[elem_info.name] = elem_info - for macro_lib_name, macro_lib in macro_server.get_macros().items(): + macros = macro_server.get_macros() + for macro_lib_name, macro_lib in list(macros.items()): if self.accepts(macro_lib): objs[macro_lib_name] = macro_lib - for macro_name, macro in macro_server.get_macros().items(): + for macro_name, macro in list(macro_server.get_macros().items()): if self.accepts(macro): objs[macro_name] = macro @@ -263,11 +244,11 @@ def getObjDict(self, pool=ParamType.All, cache=False): def getObjListStr(self, pool=ParamType.All, cache=False): obj_dict = self.getObjDict(pool=pool, cache=cache) - return obj_dict.keys() + return list(obj_dict.keys()) def getObjList(self, pool=ParamType.All, cache=False): obj_dict = self.getObjDict(pool=pool, cache=cache) - return obj_dict.values() + return list(obj_dict.values()) def serialize(self, *args, **kwargs): kwargs = ParamType.serialize(self, *args, **kwargs) @@ -325,18 +306,19 @@ def getObjDict(self, pool=ParamType.All, cache=False): else: pools = macro_server.get_pool(pool), for pool in pools: - for elem_info in pool.getElementsWithInterface(self._name).values(): + elements = pool.getElementsWithInterface(self._name) + for elem_info in list(elements.values()): if self.accepts(elem_info): objs[elem_info.name] = elem_info return objs def getObjListStr(self, pool=ParamType.All, cache=False): obj_dict = self.getObjDict(pool=pool, cache=cache) - return obj_dict.keys() + return list(obj_dict.keys()) def getObjList(self, pool=ParamType.All, cache=False): obj_dict = self.getObjDict(pool=pool, cache=cache) - return obj_dict.values() + return list(obj_dict.values()) class AttrParamType(ParamType): @@ -385,7 +367,7 @@ def decode(self): if len(raw_params) > len_params_def: msg = ("%r are supernumerary with respect to definition" % raw_params[len_params_def:]) - raise SupernumeraryParam, msg + raise SupernumeraryParam(msg) # iterate over definition since missing values may just mean using # the default values for i, param_def in enumerate(params_def): @@ -434,9 +416,9 @@ def decodeNormal(self, raw_param, param_def): param = param_type.getObj(str(value)) except ValueError as e: - raise WrongParamType(e.message) + raise WrongParamType(str(e)) from e except UnknownParamObj as e: - raise WrongParam(e.message) + raise WrongParam(str(e)) from e if param is None and not optional_param: msg = 'Could not create %s parameter "%s" for "%s"' % \ (param_type.getName(), name, raw_param) @@ -466,7 +448,7 @@ def decodeRepeat(self, raw_param_repeat, param_repeat_def): if min_rep and len_rep < min_rep: msg = 'Found %d repetitions of param %s, min is %d' % \ (len_rep, name, min_rep) - raise MissingRepeat, msg + raise MissingRepeat(msg) if max_rep and len_rep > max_rep: msg = 'Found %d repetitions of param %s, max is %d' % \ (len_rep, name, max_rep) @@ -498,7 +480,7 @@ def decodeRepeat(self, raw_param_repeat, param_repeat_def): # to indicate default value elif isinstance(raw_repeat, list) and len(raw_repeat) > 0: msg = 'Repetitions of just one member must not be lists' - raise WrongParam, msg + raise WrongParam(msg) repeat = self.decodeNormal(raw_repeat, param_type[0]) param_repeat.append(repeat) return param_repeat @@ -506,6 +488,9 @@ def decodeRepeat(self, raw_param_repeat, param_repeat_def): def getParamList(self): return self.params + def __iter__(self): + return iter(self.params) + def __getattr__(self, name): return getattr(self.params, name) @@ -524,7 +509,7 @@ def __init__(self, type_manager, params_def, raw_params): if not self.isPossible(params_def): msg = ("%s parameter definition is not compatible with" " FlatParamDecoder" % params_def) - raise AttributeError, msg + raise AttributeError(msg) self.decode() @staticmethod @@ -561,13 +546,13 @@ def decodeNormal(self, raw_params, params_def): if str_idx == str_len: if def_val is None: if not isinstance(type_class, list): - raise MissingParam, "'%s' not specified" % name + raise MissingParam("'%s' not specified" % name) elif isinstance(type_class, list): min_rep = par_def['min'] if min_rep > 0: msg = "'%s' demands at least %d values" %\ (name, min_rep) - raise WrongParam, msg + raise WrongParam(msg) if not def_val is None: new_obj = def_val else: @@ -582,14 +567,14 @@ def decodeNormal(self, raw_params, params_def): par_str = raw_params[str_idx] try: val = par_type.getObj(par_str) - except ValueError, e: - raise WrongParamType, e.message - except UnknownParamObj, e: - raise WrongParam, e.message + except ValueError as e: + raise WrongParamType(str(e)) from e + except UnknownParamObj as e: + raise WrongParam(str(e)) from e if val is None: msg = 'Could not create %s parameter "%s" for "%s"' % \ (par_type.getName(), name, par_str) - raise WrongParam, msg + raise WrongParam(msg) dec_token = 1 new_obj = val str_idx += dec_token @@ -618,11 +603,14 @@ def decodeRepeat(self, raw_params, par_def): if rep_nr < min_rep: msg = 'Found %d repetitions of param %s, min is %d' % \ (rep_nr, name, min_rep) - raise MissingRepeat, msg + raise MissingRepeat(msg) return dec_token, obj_list def getParamList(self): return self.params + def __iter__(self): + return iter(self.params) + def __getattr__(self, name): return getattr(self.params, name) diff --git a/src/sardana/macroserver/msrecordermanager.py b/src/sardana/macroserver/msrecordermanager.py index f4fc1b7b42..39b6179e07 100644 --- a/src/sardana/macroserver/msrecordermanager.py +++ b/src/sardana/macroserver/msrecordermanager.py @@ -35,11 +35,7 @@ import copy import inspect -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from sardana import sardanacustomsettings from sardana.sardanaexception import format_exception_only_str @@ -120,7 +116,7 @@ def setRecorderPath(self, recorder_path): recorder_file_names = self._findRecorderLibNames( _recorder_path) - for mod_name, file_name in recorder_file_names.iteritems(): + for mod_name, file_name in recorder_file_names.items(): dir_name = os.path.dirname(file_name) path = [dir_name] try: @@ -164,7 +160,7 @@ def getRecorderMetaClasses(self, filter=None, extension=None): if filter is None: filter = DataRecorder ret = {} - for name, klass in self._recorder_dict.items(): + for name, klass in list(self._recorder_dict.items()): if not issubclass(klass.recorder_class, filter): continue if extension is not None: @@ -176,7 +172,7 @@ def getRecorderMetaClasses(self, filter=None, extension=None): # second look into the standard map else: _map = self._scan_recorder_map - if extension not in _map.keys(): + if extension not in list(_map.keys()): continue elif klass not in _map[extension]: continue @@ -198,7 +194,7 @@ def getRecorderClasses(self, filter=None, extension=None): meta_klasses = self.getRecorderMetaClasses(filter=filter, extension=extension) return dict((key, value.klass) - for (key, value) in meta_klasses.items()) + for (key, value) in list(meta_klasses.items())) def getRecorderClass(self, klass_name): """ Return the Recorder class for the given class name. @@ -262,7 +258,7 @@ def reloadRecorderLib(self, module_name, path=None): for recorder in old_recorder_lib.get_recorders(): self._recorder_dict.pop(recorder.name) # remove recorders from the map - for _, recorders in self._scan_recorder_map.iteritems(): + for _, recorders in self._scan_recorder_map.items(): try: recorders.remove(recorder) except: @@ -271,8 +267,7 @@ def reloadRecorderLib(self, module_name, path=None): mod_manager = ModuleManager() m, exc_info = None, None try: - m = mod_manager.reloadModule( - module_name, path, reload=reload) + m = mod_manager.reloadModule(module_name, path) except: exc_info = sys.exc_info() @@ -342,7 +337,7 @@ def addRecorder(self, recorder_lib, klass): def _addRecorderToMap(self, recorder_class): klass = recorder_class.klass - for ext in klass.formats.values(): + for ext in list(klass.formats.values()): recorders = self._scan_recorder_map.get(ext, []) if len(recorders) == 0: recorders.append(recorder_class) diff --git a/src/sardana/macroserver/mstypemanager.py b/src/sardana/macroserver/mstypemanager.py index 7aee3c1a11..b0de143ba2 100644 --- a/src/sardana/macroserver/mstypemanager.py +++ b/src/sardana/macroserver/mstypemanager.py @@ -72,7 +72,7 @@ def cleanUp(self): return if self._modules: - for _, types_dict in self._modules.items(): + for _, types_dict in list(self._modules.items()): for type_name in types_dict: Type.removeType(type_name) @@ -138,8 +138,8 @@ def addType(self, type_obj): def getTypeListStr(self): type_list_basic, type_list_obj = [], [] - for _, type_class_dict in self._modules.items(): - for tname, tklass in type_class_dict.items(): + for _, type_class_dict in list(self._modules.items()): + for tname, tklass in list(type_class_dict.items()): if tklass.hasCapability(ParamType.ItemList): type_list_obj.append("%s*" % tname) else: @@ -149,7 +149,7 @@ def getTypeListStr(self): return type_list def getTypeClass(self, type_name): - for _, type_class_dict in self._modules.items(): + for _, type_class_dict in list(self._modules.items()): tklass = type_class_dict.get(type_name) if tklass is None: continue @@ -163,4 +163,4 @@ def getTypes(self): return self._inst_dict def getTypeNames(self): - return self._inst_dict.keys() + return list(self._inst_dict.keys()) diff --git a/src/sardana/macroserver/recorders/examples/dummy.py b/src/sardana/macroserver/recorders/examples/dummy.py index 847a29785a..420d8b1d68 100644 --- a/src/sardana/macroserver/recorders/examples/dummy.py +++ b/src/sardana/macroserver/recorders/examples/dummy.py @@ -47,7 +47,7 @@ def _startRecordList(self, recordlist): self.fd.write("Starting new recording\n") self.fd.write("# Title : %s\n" % recordlist.getEnvironValue('title')) env = recordlist.getEnviron() - for envky in env.keys(): + for envky in list(env.keys()): if envky != 'title' and envky != 'labels': self.fd.write("# %8s : %s \n" % (envky, str(env[envky]))) self.fd.write("# Started: %s\n" % env['starttime']) diff --git a/src/sardana/macroserver/recorders/examples/xas.py b/src/sardana/macroserver/recorders/examples/xas.py index f95d9a5ae1..c49a0a8b4c 100644 --- a/src/sardana/macroserver/recorders/examples/xas.py +++ b/src/sardana/macroserver/recorders/examples/xas.py @@ -190,7 +190,7 @@ def _writeRecord(self, record): rec_data, rec_nb = record.data, record.recordno for dd in self.datadesc: - if record.data.has_key(dd.name): + if dd.name in record.data: data = rec_data[dd.name] field = self.ddfieldsDict[dd.label] diff --git a/src/sardana/macroserver/recorders/h5storage.py b/src/sardana/macroserver/recorders/h5storage.py index 92f9825dd2..2b34d5abe7 100644 --- a/src/sardana/macroserver/recorders/h5storage.py +++ b/src/sardana/macroserver/recorders/h5storage.py @@ -66,8 +66,8 @@ class NXscanH5_FileRecorder(BaseFileRecorder): """ formats = {'h5': '.h5'} # from http://docs.h5py.org/en/latest/strings.html - str_dt = h5py.special_dtype(vlen=unicode) # Variable-length UTF-8 (PY2) - byte_dt = h5py.special_dtype(vlen=bytes) # Variable-length UTF-8 (PY2) + str_dt = h5py.special_dtype(vlen=str) # Variable-length UTF-8 + byte_dt = h5py.special_dtype(vlen=bytes) # Variable-length UTF-8 supported_dtypes = ('float32', 'float64', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', @@ -155,7 +155,7 @@ def _startRecordList(self, recordlist): nxentry = self.fd.create_group(self.entryname) except ValueError: # Warn and abort - if self.entryname in self.fd.keys(): + if self.entryname in list(self.fd.keys()): msg = ('{ename:s} already exists in {fname:s}. ' 'Aborting macro to prevent data corruption.\n' 'This is likely caused by a wrong ScanID\n' @@ -182,6 +182,8 @@ def _startRecordList(self, recordlist): if dd.dtype == 'bool': dd.dtype = 'int8' self.debug('%r will be stored with type=%r', dd.name, dd.dtype) + elif dd.dtype == 'str': + dd.dtype = NXscanH5_FileRecorder.str_dt if dd.value_ref_enabled: # substitute original data (image or spectrum) type and shape # since we will receive references instead @@ -282,7 +284,7 @@ def _createPreScanSnapshot(self, env): _snap = _meas.create_group('pre_scan_snapshot') _snap.attrs['NX_class'] = 'NXcollection' - meas_keys = _meas.keys() + meas_keys = list(_meas.keys()) for dd in self.preScanSnapShot: label = self.sanitizeName(dd.label) @@ -293,6 +295,9 @@ def _createPreScanSnapshot(self, env): pre_scan_value = numpy.int8(dd.pre_scan_value) self.debug('Pre-scan snapshot of %s will be stored as type %s', dd.name, dtype) + elif dd.dtype == 'str': + dd.dtype = NXscanH5_FileRecorder.str_dt + if dtype in self.supported_dtypes: _ds = _snap.create_dataset( label, @@ -498,7 +503,7 @@ def _createNXData(self): _meas = nxentry['measurement'] # write the 1D NXdata group - for axes, v in plots1d.items(): + for axes, v in list(plots1d.items()): _nxdata = nxentry.create_group(plots1d_names[axes]) _nxdata.attrs['NX_class'] = 'NXdata' diff --git a/src/sardana/macroserver/recorders/output.py b/src/sardana/macroserver/recorders/output.py index b41bba4b57..79983b640e 100644 --- a/src/sardana/macroserver/recorders/output.py +++ b/src/sardana/macroserver/recorders/output.py @@ -32,14 +32,14 @@ import numpy import datetime import operator -import string import weakref -from taurus.core.util.codecs import CodecFactory from taurus.core.util.containers import CaselessList from sardana.macroserver.scan.recorder.datarecorder import DataRecorder from sardana.macroserver.scan.recorder.storage import BaseFileRecorder +import collections +import numbers class JsonRecorder(DataRecorder): @@ -47,7 +47,6 @@ class JsonRecorder(DataRecorder): def __init__(self, stream, cols=None, **pars): DataRecorder.__init__(self, **pars) self._stream = weakref.ref(stream) - self._codec = CodecFactory().getCodec('json') def _startRecordList(self, recordlist): macro_id = recordlist.getEnvironValue('macro_id') @@ -102,9 +101,7 @@ def _writeRecord(self, record): def _sendPacket(self, **kwargs): '''creates a JSON packet using the keyword arguments passed and then sends it''' - #data = self._codec.encode(('', kwargs)) - # self._stream().sendRecordData(*data) - self._stream()._sendRecordData(kwargs, codec='json') + self._stream()._sendRecordData(kwargs, codec='utf8_json') def _addCustomData(self, value, name, **kwargs): ''' @@ -135,10 +132,10 @@ def __init__(self, stream, cols=None, number_fmt='%8.4f', col_width=8, self._number_fmt = number_fmt self._col_sep = col_sep self._col_width = col_width - if operator.isSequenceType(cols) and \ - not isinstance(cols, (str, unicode)): + if isinstance(cols, collections.Sequence) and \ + not isinstance(cols, str): cols = CaselessList(cols) - elif operator.isNumberType(cols): + elif isinstance(cols, numbers.Number): cols = cols else: cols = None @@ -158,7 +155,7 @@ def _startRecordList(self, recordlist): for fr in [r for r in dh.recorders if isinstance(r, BaseFileRecorder)]: if not hasattr(self._stream, "getAllEnv") or \ - "ScanRecorder" in self._stream.getAllEnv().keys(): + "ScanRecorder" in list(self._stream.getAllEnv().keys()): message = "%s from %s" % ( fr.getFormat(), fr.__class__.__name__) else: @@ -178,9 +175,9 @@ def _startRecordList(self, recordlist): if not getattr(column, 'output', True): continue name = column.name - if operator.isSequenceType(cols) and name not in cols: + if isinstance(cols, collections.Sequence) and name not in cols: continue - if operator.isNumberType(cols) and col >= cols: + if isinstance(cols, numbers.Number) and col >= cols: break col_names.append(name) label = column.label.strip() @@ -190,7 +187,7 @@ def _startRecordList(self, recordlist): label = [label] header_rows = max(header_rows, len(label)) labels.append(label) - col_size = max(col_width, max(map(len, label))) + col_size = max(col_width, max(list(map(len, label)))) header_len += col_size col_sizes.append(col_size) @@ -206,7 +203,7 @@ def _startRecordList(self, recordlist): for row in range(empty_row_nb): header[row].append(col_size * " ") for i, l in enumerate(label): - header[i + empty_row_nb].append(string.center(l, col_size)) + header[i + empty_row_nb].append(l.center(col_size)) head = [] for header_row in header: head.append(col_sep.join(header_row)) @@ -241,8 +238,8 @@ def _endRecordList(self, recordlist): endts = recordlist.getEnvironValue('endts') startts = recordlist.getEnvironValue('startts') totaltimets = endts - startts - deadtime_perc = deadtime * 100.0 / totaltimets - motiontime_perc = motiontime * 100.0 / totaltimets + deadtime_perc = deadtime * 100 / totaltimets + motiontime_perc = motiontime * 100 / totaltimets info_string = 'Scan #%s ended at %s, taking %s. ' + \ 'Dead time %.1f%% (motion dead time %.1f%%)' self._stream().info(info_string % (serialno, endtime, totaltime, @@ -256,7 +253,7 @@ def _writeRecord(self, record): cell = str(cell_data.shape) elif cell_data is None: cell = "" - elif isinstance(cell_data, (str, unicode)): + elif isinstance(cell_data, str): # TODO: for SEP2 needs strings are enabled for visualizing # value refs, previously "" was printed. This may # have side effects e.g. alignment of columns etc.. Check @@ -264,7 +261,8 @@ def _writeRecord(self, record): cell = cell_data else: cell %= record.data - cell = string.center(cell.strip(), self._col_sizes[i]) + cell = cell.strip() + cell = cell.center(self._col_sizes[i]) cells.append(cell) scan_line = self._col_sep.join(cells) @@ -280,7 +278,7 @@ def _addCustomData(self, value, name, **kwargs): The custom data will be added as an info line in the form: Custom data: name : value ''' - if numpy.rank(value) > 0: + if numpy.ndim(value) > 0: v = 'Array(%s)' % str(numpy.shape(value)) else: v = str(value) diff --git a/src/sardana/macroserver/recorders/sharedmemory.py b/src/sardana/macroserver/recorders/sharedmemory.py index 8ba8ed7868..e2622ea96a 100644 --- a/src/sardana/macroserver/recorders/sharedmemory.py +++ b/src/sardana/macroserver/recorders/sharedmemory.py @@ -35,6 +35,7 @@ from sardana.macroserver.scan.recorder import BaseSharedMemoryRecorder from sardana.macroserver.scan.recorder.datarecorder import DataRecorder +import numbers class SPSRecorder(BaseSharedMemoryRecorder): @@ -91,7 +92,7 @@ def putAllEnv(self, d): if not self.isInitialized(): return p, a = self.program, self.array_ENV - for k, v in d.iteritems(): + for k, v in d.items(): self.sps.putenv(p, a, k, str(v)) def _startRecordList(self, recordlist): @@ -143,9 +144,11 @@ def _writeRecord(self, record): for colname in self.labels: val = record.data.get(colname) - if (not val is None) and (operator.isNumberType(val) and (type(val) in [int, float, long])): + if ((val is not None) + and (isinstance(val, numbers.Number) + and (type(val) in [int, float]))): vals.append(val) - elif (not val is None) and (operator.isNumberType(val)): + elif (val is not None) and (isinstance(val, numbers.Number)): valsmca = [] for i in range(0, len(val)): valsmca.append(val[i]) @@ -234,11 +237,11 @@ def _startRecordList(self, recordlist): self.sps.create(self.progname, self.shm_id_env, self.maxenv, self.envlen, self.sps.STRING) - print "Starting new SHM recording" + print("Starting new SHM recording") self.putenv('title', recordlist.getEnvironValue('title')) - for env, val in recordlist.getEnviron().items(): + for env, val in list(recordlist.getEnviron().items()): if env != 'title' and env != 'labels': self.putenv(env, val) @@ -271,7 +274,7 @@ def _writeRecord(self, record): for colname in self.labels: dim_list.append(0) val = record.data.get(colname) - if (not val is None) and (type(val) in [int, float, long]): + if (val is not None) and (type(val) in [int, float]): vals.append(val) myj = 0 @@ -286,7 +289,7 @@ def _writeRecord(self, record): myj = 0 - for val2 in record.data.values(): + for val2 in list(record.data.values()): valsmca = [] if type(val2) in [list]: if dim_list[myj] == 1: diff --git a/src/sardana/macroserver/recorders/storage.py b/src/sardana/macroserver/recorders/storage.py index 772fede0e6..729a1b7d6c 100644 --- a/src/sardana/macroserver/recorders/storage.py +++ b/src/sardana/macroserver/recorders/storage.py @@ -89,7 +89,7 @@ def setFileName(self, filename): self.filename = "%s_%s.%s" % (tpl[0], "[ScanId]", tpl[2]) def getFormat(self): - return self.formats.keys()[0] + return list(self.formats.keys())[0] def _startRecordList(self, recordlist): @@ -137,7 +137,8 @@ def _startRecordList(self, recordlist): self.fd.write("!\n! Parameter\n!\n%p\n") self.fd.flush() env = self.macro().getAllEnv() - if env.has_key('FlagFioWriteMotorPositions') and env['FlagFioWriteMotorPositions']: + if ('FlagFioWriteMotorPositions' in env + and env['FlagFioWriteMotorPositions']): all_motors = sorted( self.macro().findObjs('.*', type_class=Type.Motor)) for mot in all_motors: @@ -304,7 +305,7 @@ def setFileName(self, filename): self.currentlist = None def getFormat(self): - return self.formats.keys()[0] + return list(self.formats.keys())[0] def _startRecordList(self, recordlist): '''Prepares and writes the scan header.''' @@ -391,7 +392,7 @@ def _startRecordList(self, recordlist): header += '#L %(labels)s\n' self.fd = io.open(self.filename, 'a', newline='\n') - self.fd.write(unicode(header % data)) + self.fd.write(str(header % data)) self.fd.flush() os.fsync(self.fd.fileno()) @@ -470,7 +471,7 @@ def _writeRecord(self, record): str_data += '%s' % data outstr = '@A %s' % str_data outstr += '\n' - fd.write(unicode(outstr)) + fd.write(str(outstr)) for c in names: data = record.data.get(c) @@ -480,7 +481,7 @@ def _writeRecord(self, record): outstr = ' '.join(d) outstr += '\n' - fd.write(unicode(outstr)) + fd.write(str(outstr)) fd.flush() os.fsync(self.fd.fileno()) @@ -491,7 +492,7 @@ def _endRecordList(self, recordlist): env = recordlist.getEnviron() end_time = env['endtime'].ctime() - self.fd.write(unicode("#C Acquisition ended at %s\n" % end_time)) + self.fd.write(str("#C Acquisition ended at %s\n" % end_time)) self.fd.flush() self.fd.close() @@ -507,7 +508,7 @@ def _addCustomData(self, value, name, **kwargs): self.info( 'Custom data "%s" will not be stored in SPEC file. Reason: uninitialized file', name) return - if numpy.rank(value) > 0: # ignore non-scalars + if numpy.ndim(value) > 0: # ignore non-scalars self.info( 'Custom data "%s" will not be stored in SPEC file. Reason: value is non-scalar', name) return @@ -525,7 +526,7 @@ def _addCustomData(self, value, name, **kwargs): self.info( 'Custom data "%s" will not be stored in SPEC file. Reason: cannot open file', name) return - self.fd.write(unicode('#C %s : %s\n' % (name, v))) + self.fd.write(str('#C %s : %s\n' % (name, v))) self.fd.flush() if fileWasClosed: self.fd.close() # leave the file descriptor as found @@ -560,7 +561,7 @@ def _startRecordList(self, recordlist): try: self.fd.makegroup(self.entryname, "NXentry") except self.nxs.NeXusError: - entrynames = self.fd.getentries().keys() + entrynames = list(self.fd.getentries().keys()) #================================================================== ##Warn and abort @@ -687,7 +688,7 @@ def _createPreScanSnapshot(self, env): self.fd.closegroup() # we are back at the measurement group measurement_entries = self.fd.getentries() - for label, nid in links.items(): + for label, nid in list(links.items()): if label not in measurement_entries: self.fd.makelink(nid) @@ -700,7 +701,7 @@ def _writeRecord(self, record): rec_data, rec_nb = record.data, record.recordno for dd in self.datadesc: - if record.data.has_key(dd.name): + if dd.name in record.data: data = rec_data[dd.name] fd.opendata(dd.label) @@ -768,7 +769,7 @@ def writeRecordList(self, recordlist): # if not all the records contain this field, we cannot write it # as a block.. so do it record by record (but only this field!) for record in recordlist.records: - if record.data.has_key(dd.label): + if dd.label in record.data: self.fd.putslab(record.data[dd.label], [ record.recordno] + [0] * len(dd.shape), [1] + list(dd.shape)) else: @@ -788,7 +789,7 @@ def _populateInstrumentInfo(self): nid = self.fd.getdataID() self._createBranch(dd.instrument) self.fd.makelink(nid) - except Exception, e: + except Exception as e: self.warning( "Could not create link to '%s' in '%s'. Reason: %s", datapath, dd.instrument, repr(e)) @@ -802,7 +803,7 @@ def _populateInstrumentInfo(self): nid = self.fd.getdataID() self._createBranch(dd.instrument) self.fd.makelink(nid) - except Exception, e: + except Exception as e: self.warning( "Could not create link to '%s' in '%s'. Reason: %s", datapath, dd.instrument, repr(e)) @@ -832,7 +833,7 @@ def _createNXData(self): continue # @todo: implement support for images and other # write the 1D NXdata group - for axes, v in plots1d.items(): + for axes, v in list(plots1d.items()): self.fd.openpath("/%s:NXentry" % (self.entryname)) groupname = plots1d_names[axes] self.fd.makegroup(groupname, 'NXdata') @@ -898,7 +899,7 @@ def _addCustomData(self, value, name, nxpath=None, dtype=None, **kwargs): self._createBranch(nxpath) try: self._writeData(name, value, dtype) - except ValueError, e: + except ValueError as e: msg = "Error writing %s. Reason: %s" % (name, str(e)) self.warning(msg) self.macro.warning(msg) diff --git a/src/sardana/macroserver/recorders/test/test_h5storage.py b/src/sardana/macroserver/recorders/test/test_h5storage.py index d47449eb94..413f4e4797 100644 --- a/src/sardana/macroserver/recorders/test/test_h5storage.py +++ b/src/sardana/macroserver/recorders/test/test_h5storage.py @@ -31,7 +31,7 @@ import h5py import numpy -from taurus.external.unittest import TestCase +from unittest import TestCase from sardana.macroserver.scan import ColumnDesc from sardana.macroserver.recorders.h5storage import NXscanH5_FileRecorder diff --git a/src/sardana/macroserver/scan/__init__.py b/src/sardana/macroserver/scan/__init__.py index e8ff34706c..8d00d3298f 100644 --- a/src/sardana/macroserver/scan/__init__.py +++ b/src/sardana/macroserver/scan/__init__.py @@ -27,5 +27,5 @@ __docformat__ = 'restructuredtext' -from scandata import * -from gscan import * +from .scandata import * # noqa +from .gscan import * # noqa diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index a4c356cf7d..eacd779395 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -41,12 +41,9 @@ import PyTango import taurus +import collections -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from taurus.core.util.log import Logger from taurus.core.util.user import USER_NAME @@ -54,7 +51,9 @@ from taurus.core.util.enumeration import Enumeration from taurus.core.util.threadpool import ThreadPool from taurus.core.util.event import CallableRef +from taurus.core.tango.tangovalidator import TangoDeviceNameValidator +from sardana.sardanathreadpool import OmniWorker from sardana.util.tree import BranchNode, LeafNode, Tree from sardana.util.motion import Motor as VMotor from sardana.util.motion import MotionPath @@ -187,7 +186,6 @@ class GScan(Logger): strict order after finishing acquisition but before recording the step. - 'post-step-hooks' : (optional) a sequence of callables to be called in strict order after finishing recording the step. - - 'hooks' : (deprecated, use post-acq-hooks instead) - 'point_id' : a hashable identifing the scan point. - 'check_func' : (optional) a list of callable objects. callable(moveables, counters) @@ -214,7 +212,7 @@ class GScan(Logger): - DataHandler with the following recorders: - OutputRecorder (depends on ``OutputCols`` environment variable) - SharedMemoryRecorder (depends on ``SharedMemory`` environment variable) - - FileRecorder (depends on ``ScanDir`` and ``ScanData`` + - FileRecorder (depends on ``ScanDir``, ``ScanData`` and ``ScanRecorder`` environment variables) - ScanDataEnvironment with the following contents: @@ -301,7 +299,7 @@ def __init__(self, macro, generator=None, moveables=[], env={}, macro.info("ActiveMntGrp not defined. Using %s", mnt_grp) macro.setEnv('ActiveMntGrp', mnt_grp.getName()) else: - if not isinstance(mnt_grp_name, (str, unicode)): + if not isinstance(mnt_grp_name, str): t = type(mnt_grp_name).__name__ raise TypeError("ActiveMntGrp MUST be string. It is '%s'" % t) @@ -312,7 +310,7 @@ def __init__(self, macro, generator=None, moveables=[], env={}, raise ScanSetupError("ActiveMntGrp has invalid value: '%s'" % mnt_grp_name) - self._master = mnt_grp.getTimer() + self._master = mnt_grp.getConfiguration().getTimer() if self._master is None: raise ScanSetupError('%s has no timer defined' % mnt_grp.getName()) @@ -435,7 +433,7 @@ def _getExtraColumns(self): ret.append(TangoExtraData(**kw)) except InterruptException: raise - except Exception, colexcept: + except Exception as colexcept: colname = kw.get('label', str(i)) self.macro.warning("Extra column %s is invalid: %s", colname, str(colexcept)) @@ -483,26 +481,34 @@ def _getFileRecorders(self): macro = self.macro try: scan_dir = macro.getEnv('ScanDir') + if scan_dir == '' or None: + macro.warning('ScanDir value is empty') + raise Exception('ScanDir value is empty') except InterruptException: raise except Exception: macro.warning('ScanDir is not defined. This operation will not be ' - 'stored persistently. Use Use "expconf" (or "senv ' - 'ScanDir ") to enable it') + 'stored persistently. Use "expconf" or "newfile" ' + 'to configure data storage (or eventually "senv ' + 'ScanDir ")') return () - if not isinstance(scan_dir, (str, unicode)): + if not isinstance(scan_dir, str): scan_dir_t = type(scan_dir).__name__ raise TypeError("ScanDir MUST be string. It is '%s'" % scan_dir_t) try: file_names = macro.getEnv('ScanFile') + if file_names == [''] or None: + macro.warning('ScanFile value is empty') + raise Exception('ScanFile value is empty') except InterruptException: raise except Exception: macro.warning('ScanFile is not defined. This operation will not ' - 'be stored persistently. Use "expconf" (or "senv ' - 'ScanFile ") to enable it') + 'be stored persistently. Use "expconf" or "newfile" ' + 'to configure data storage (or eventually "senv ' + 'ScanFile ")') return () scan_recorders = [] @@ -513,16 +519,16 @@ def _getFileRecorders(self): except UnknownEnv: pass - if isinstance(file_names, (str, unicode)): + if isinstance(file_names, str): file_names = (file_names,) - elif not operator.isSequenceType(file_names): + elif not isinstance(file_names, collections.Sequence): scan_file_t = type(file_names).__name__ raise TypeError("ScanFile MUST be string or sequence of strings." " It is '%s'" % scan_file_t) - if isinstance(scan_recorders, (str, unicode)): + if isinstance(scan_recorders, str): scan_recorders = (scan_recorders,) - elif not operator.isSequenceType(scan_recorders): + elif not isinstance(scan_recorders, collections.Sequence): scan_recorders_t = type(scan_recorders).__name__ raise TypeError("ScanRecorder MUST be string or sequence of " "strings. It is '%s'" % scan_recorders_t) @@ -540,7 +546,7 @@ def _getFileRecorders(self): file_recorders.append(file_recorder) except InterruptException: raise - except AmbiguousRecorderError, e: + except AmbiguousRecorderError as e: macro.error('Select recorder that you would like to use ' '(i.e. set ScanRecorder environment variable).') raise e @@ -665,7 +671,10 @@ def _setupEnvironment(self, additional_env): for ci in channels_info: full_name = ci.full_name try: - channel = taurus.Device(full_name) + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + # channel = taurus.Device(full_name) + channel = PyTango.DeviceProxy(full_name) instrument = channel.instrument except Exception: # full_name of external channels is the name of the attribute @@ -861,7 +870,7 @@ def _estimate(self, max_iter=None): v_motors = self.get_virtual_motors() motion_time, acq_time = 0.0, 0.0 while point_nb < max_iter: - step = iterator.next() + step = next(iterator) end_pos = step['positions'] max_path_duration = 0.0 for v_motor, start, stop in zip(v_motors, @@ -882,7 +891,7 @@ def _estimate(self, max_iter=None): else: try: while point_nb < max_iter: - step = iterator.next() + step = next(iterator) point_nb += 1 finally: total_time = self.macro.getTimeEstimation() @@ -970,7 +979,7 @@ def end(self): scan_history = [] scan_file = env['ScanFile'] - if isinstance(scan_file, (str, unicode)): + if isinstance(scan_file, str): scan_file = scan_file, names = [col.name for col in env['datadesc']] @@ -1003,10 +1012,13 @@ def step_scan(self): endstatus = ScanEndStatus.Normal except StopException: endstatus = ScanEndStatus.Stop + raise except AbortException: endstatus = ScanEndStatus.Abort + raise except Exception: endstatus = ScanEndStatus.Exception + raise finally: self._env["endstatus"] = endstatus self.end() @@ -1015,8 +1027,6 @@ def step_scan(self): if hasattr(macro, 'getHooks'): for hook in macro.getHooks('post-scan'): hook() - else: - raise def scan_loop(self): raise NotImplementedError('Scan method cannot be called by ' @@ -1075,7 +1085,7 @@ def scan_loop(self): self.stepUp(i, step, lstep) lstep = step if scream: - yield ((i + 1) / nb_points) * 100.0 + yield ((i + 1) / nb_points) * 100 if not scream: yield 100.0 @@ -1107,7 +1117,7 @@ def stepUp(self, n, step, lstep): except InterruptException: raise except Exception: - self.dump_information(n, step) + self.dump_information(n, step, self.motion.moveable_list) raise self.debug("[ END ] motion") @@ -1128,7 +1138,7 @@ def stepUp(self, n, step, lstep): self.macro.checkPoint() if state != Ready: - self.dump_information(n, step) + self.dump_information(n, step, self.motion.moveable_list) m = "Scan aborted after problematic motion: " \ "Motion ended with %s\n" % str(state) raise ScanException({'msg': m}) @@ -1146,10 +1156,18 @@ def stepUp(self, n, step, lstep): integ_time = step['integ_time'] # Acquire data self.debug("[START] acquisition") - if self._deterministic_scan: - state, data_line = mg.count_raw() - else: - state, data_line = mg.count(integ_time) + try: + if self._deterministic_scan: + state, data_line = mg.count_raw() + else: + state, data_line = mg.count(integ_time) + except InterruptException: + raise + except Exception: + names = mg.ElementList + elements = [self.macro.getObj(name) for name in names] + self.dump_information(n, step, elements) + raise for ec in self._extra_columns: data_line[ec.getName()] = ec.read() self.debug("[ END ] acquisition") @@ -1166,20 +1184,6 @@ def stepUp(self, n, step, lstep): except Exception: pass - # hooks for backwards compatibility: - if 'hooks' in step: - self.macro.info('Deprecation warning: you should use ' - '"post-acq-hooks" instead of "hooks" in the step ' - 'generator') - for hook in step.get('hooks', ()): - hook() - try: - step['extrainfo'].update(hook.getStepExtraInfo()) - except InterruptException: - raise - except Exception: - pass - # Add final moveable positions data_line['point_nb'] = n data_line['timestamp'] = dt @@ -1202,11 +1206,10 @@ def stepUp(self, n, step, lstep): except Exception: pass - def dump_information(self, n, step): - moveables = self.motion.moveable_list + def dump_information(self, n, step, elements): msg = ["Report: Stopped at step #" + str(n) + " with:"] - for moveable in moveables: - msg.append(moveable.information()) + for element in elements: + msg.append(element.information()) self.macro.info("\n".join(msg)) @@ -1443,7 +1446,7 @@ def _restore_motors(self): deceleration=motor_backup["deceleration"]) try: self.configure_motor(motor, attributes) - except ScanException, e: + except ScanException as e: msg = "Error when restoring motor's backup (%s)" % e raise ScanException(msg) @@ -1466,7 +1469,7 @@ def _setFastMotions(self, motors=None): deceleration=self.get_min_dec_time(motor)) try: self.configure_motor(motor, attributes) - except ScanException, e: + except ScanException as e: msg = "Error when setting fast motion (%s)" % e raise ScanException(msg) @@ -1580,7 +1583,7 @@ def configure_motor(self, motor, attributes): :param attributes: (OrderedDict) dictionary with attribute names (keys) and attribute values (values) """ - for param, value in attributes.items(): + for param, value in list(attributes.items()): try: motor._getAttrEG(param).write(value) except Exception: @@ -1860,7 +1863,7 @@ def scan_loop(self): macro.checkPoint() try: - point_nb, step = period_steps.next() + point_nb, step = next(period_steps) except StopIteration: self._all_waypoints_finished = True break @@ -1932,7 +1935,7 @@ def scan_loop(self): self.data.addRecord(data_line) if scream: - yield ((point_nb + 1) / nb_points) * 100.0 + yield ((point_nb + 1) / nb_points) * 100 else: break old_curr_time = curr_time @@ -1952,8 +1955,21 @@ def scan_loop(self): class CAcquisition(object): def __init__(self): - self._thread_pool = ThreadPool(name="ValueBufferTH", Psize=1, - Qsize=100000) + # protect older versions of Taurus (without the worker_cls argument) + # remove it whenever we bump Taurus dependency + try: + self._thread_pool = ThreadPool(name="ValueBufferTH", + Psize=1, + Qsize=100000, + worker_cls=OmniWorker) + except TypeError: + import taurus + taurus.warning("Your Sardana system is affected by bug" + "tango-controls/pytango#307. Please use " + "Taurus with taurus-org/taurus#1081.") + self._thread_pool = ThreadPool(name="ValueBufferTH", + Psize=1, + Qsize=100000) self._countdown_latch = CountLatch() self._index_offset = 0 @@ -2030,39 +2046,37 @@ def is_measurement_group_compatible(measurement_group): .. todo:: add validation for psuedo counters """ non_compatible_channels = [] + validator = TangoDeviceNameValidator() for channel_info in measurement_group.getChannels(): full_name = channel_info["full_name"] name = channel_info["name"] - try: - taurus.Device(full_name) - except Exception: - # external channels are attributes so Device constructor fails + if not validator.isValid(full_name): non_compatible_channels.append(name) - continue is_compatible = len(non_compatible_channels) == 0 return is_compatible, non_compatible_channels -def generate_timestamps(synchronization, initial_timestamp=0): +def generate_timestamps(synch_description, initial_timestamp=0): """Generate theoretical timestamps at which the acquisition should take place according to the synchronization description - :param synchronization: synchronization description data structure - :type synchronization: list + :param synch_description: synchronization description data + structure + :type synch_description: list :param initial_timestamp: initial timestamp to start from :type initial_timestamp: float """ ret = dict() timestamp = initial_timestamp index = 0 - for group in synchronization: + for group in synch_description: delay = group[SynchParam.Delay][SynchDomain.Time] total = group[SynchParam.Total][SynchDomain.Time] repeats = group[SynchParam.Repeats] timestamp += delay ret[index] = dict(timestamp=timestamp) index += 1 - for _ in xrange(1, repeats): + for _ in range(1, repeats): timestamp += total ret[index] = dict(timestamp=timestamp) index += 1 @@ -2082,9 +2096,9 @@ def generate_positions(motors, starts, finals, nb_points): label = motor.getName() dtype_spec.append((label, 'float64')) # convert to numpy array for easier handling - table = np.array(zip(*moveable_positions), dtype=dtype_spec) + table = np.array(list(zip(*moveable_positions)), dtype=dtype_spec) n_rows = table.shape[0] - for i in xrange(n_rows): + for i in range(n_rows): row = dict() for label in table.dtype.names: row[label] = table[label][i] @@ -2121,7 +2135,6 @@ class CTScan(CScan, CAcquisition): in strict order before starting to move - 'post-move-hooks': (optional) a sequence of callables to be called in strict order after finishing the move - - 'hooks' : (deprecated, use post-acq-hooks instead) - 'waypoint_id' : a hashable identifing the waypoint - 'check_func' : (optional) a list of callable objects. callable(moveables, counters) @@ -2361,8 +2374,8 @@ def _go_through_waypoints(self): SynchParam.Total: {SynchDomain.Position: total_position, SynchDomain.Time: total_time}, SynchParam.Repeats: repeats}] - self.debug('Synchronization: %s' % synch) - measurement_group.setSynchronization(synch) + self.debug('SynchDescription: %s' % synch) + measurement_group.setSynchDescription(synch) self.macro.checkPoint() # extra post configuration @@ -2404,7 +2417,7 @@ def _go_through_waypoints(self): continue try: self.configure_motor(motor, attributes) - except ScanException, e: + except ScanException as e: msg = "Error when configuring scan motion (%s)" % e raise ScanException(msg) @@ -2428,7 +2441,7 @@ def _go_through_waypoints(self): theoretical_positions = generate_positions(motors, starts, finals, nb_points) theoretical_timestamps = generate_timestamps(synch, dt_timestamp) - for index, data in theoretical_positions.items(): + for index, data in list(theoretical_positions.items()): data.update(theoretical_timestamps[index]) initial_data[index + self._index_offset] = data # TODO: this changes the initial data on-the-fly - seems like not @@ -2436,18 +2449,14 @@ def _go_through_waypoints(self): self.data.initial_data = initial_data if hasattr(macro, 'getHooks'): - pre_acq_hooks = macro.getHooks('pre-start') - if len(pre_acq_hooks) > 0: - self.macro.warning("pre-start hook place is deprecated," - "use pre-acq instead") - pre_acq_hooks += waypoint.get('pre-acq-hooks', []) - + pre_acq_hooks = waypoint.get('pre-acq-hooks', []) for hook in pre_acq_hooks: hook() self.macro.checkPoint() self.macro.debug("Starting measurement group") - + self.measurement_group.setNbStarts(1) + self.measurement_group.prepare() mg_id = self.measurement_group.start() if i == 0: first_timestamp = time.time() @@ -2474,11 +2483,7 @@ def _go_through_waypoints(self): timeout = 15 measurement_group.waitFinish(timeout=timeout, id=mg_id) finally: - # TODO: For Taurus 4 / Taurus 3 compatibility - if hasattr(measurement_group, "stateObj"): - state = measurement_group.stateObj.read().rvalue - else: - state = measurement_group.state() + state = measurement_group.stateObj.read().rvalue if state == PyTango.DevState.MOVING: measurement_group.Stop() if end_move: @@ -2628,7 +2633,7 @@ def stepUp(self, n, step, lstep): except InterruptException: raise except Exception: - self.dump_information(n, step) + self.dump_information(n, step, motion.moveable_list) raise try: @@ -2637,7 +2642,7 @@ def stepUp(self, n, step, lstep): except InterruptException: raise except Exception: - self.dump_information(n, step) + self.dump_information(n, step, motion.moveable_list) raise self._sum_acq_time += integ_time @@ -2647,7 +2652,7 @@ def stepUp(self, n, step, lstep): m_state, m_positions = motion.readState(), motion.readPosition() if m_state != Ready: - self.dump_information(n, step) + self.dump_information(n, step, motion.moveable_list) m = "Scan aborted after problematic motion: " \ "Motion ended with %s\n" % str(m_state) raise ScanException({'msg': m}) @@ -2676,11 +2681,10 @@ def stepUp(self, n, step, lstep): except Exception: pass - def dump_information(self, n, step): - moveables = self.motion.moveable_list + def dump_information(self, n, step, elements): msg = ["Report: Stopped at step #" + str(n) + " with:"] - for moveable in moveables: - msg.append(moveable.information()) + for element in elements: + msg.append(element.information()) self.macro.info("\n".join(msg)) @@ -2688,9 +2692,9 @@ class TScan(GScan, CAcquisition): """Time scan. Macro that employs the time scan must define the synchronization - information in either of the following two ways: - - synchronization attribute that follows the synchronization format of - the measurement group + description in either of the following two ways: + - synch_description attribute that follows the synchronization + description format of the measurement group - integ_time, nb_points and latency_time (optional) attributes """ def __init__(self, macro, generator=None, @@ -2699,9 +2703,9 @@ def __init__(self, macro, generator=None, moveables=moveables, env=env, constraints=constraints, extrainfodesc=extrainfodesc) CAcquisition.__init__(self) - self._synchronization = None + self._synch_description = None - def _create_synchronization(self, active_time, repeats, latency_time=0): + def _create_synch_description(self, active_time, repeats, latency_time=0): delay_time = 0 mg_latency_time = self.measurement_group.getLatencyTime() if mg_latency_time > latency_time: @@ -2709,38 +2713,39 @@ def _create_synchronization(self, active_time, repeats, latency_time=0): mg_latency_time) latency_time = mg_latency_time total_time = active_time + latency_time - synchronization = [ + synch_description = [ {SynchParam.Delay: {SynchDomain.Time: delay_time}, SynchParam.Active: {SynchDomain.Time: active_time}, SynchParam.Total: {SynchDomain.Time: total_time}, SynchParam.Repeats: repeats}] - return synchronization + return synch_description - def get_synchronization(self): - if self._synchronization is not None: - return self._synchronization - if hasattr(self.macro, "synchronization"): - synchronization = self.macro.synchronization + def get_synch_description(self): + if self._synch_description is not None: + return self._synch_description + if hasattr(self.macro, "synch_description"): + synch_description = self.macro.synch_description else: try: active_time = getattr(self.macro, "integ_time") repeats = getattr(self.macro, "nb_points") except AttributeError: - msg = "Macro object is missing synchronization attributes" + msg = "Macro object is missing synchronization description " \ + "attributes" raise ScanSetupError(msg) latency_time = getattr(self.macro, "latency_time", 0) - synchronization = self._create_synchronization(active_time, - repeats, - latency_time) - self._synchronization = synchronization - return synchronization + synch_description = \ + self._create_synch_description(active_time, repeats, + latency_time) + self._synch_description = synch_description + return synch_description - synchronization = property(get_synchronization) + synch_description = property(get_synch_description) def scan_loop(self): macro = self.macro measurement_group = self.measurement_group - synchronization = self.synchronization + synch_description = self.synch_description compatible, channels = \ self.is_measurement_group_compatible(measurement_group) @@ -2751,7 +2756,8 @@ def scan_loop(self): (measurement_group.getName(), macro.getName()) raise ScanException(msg) - theoretical_timestamps = generate_timestamps(synchronization) + theoretical_timestamps = \ + generate_timestamps(synch_description) self.data.initial_data = theoretical_timestamps msg = "Relative timestamp (dt) column contains theoretical values" self.macro.warning(msg) @@ -2761,8 +2767,10 @@ def scan_loop(self): hook() yield 0 - measurement_group.count_continuous(synchronization, - self.value_buffer_changed) + measurement_group.setNbStarts(1) + measurement_group.count_continuous(synch_description, + self.value_buffer_changed, + self.value_ref_buffer_changed) self.debug("Waiting for value buffer events to be processed") self.wait_value_buffer() self.join_thread_pool() @@ -2789,12 +2797,13 @@ def _estimate(self): i = self.macro.getIntervalEstimation() return t, i - if not hasattr(self.macro, "synchronization"): - raise AttributeError("synchronization is mandatory to estimate") - synchronization = self.macro.synchronization + if not hasattr(self.macro, "synch_description"): + raise AttributeError("synch_description is mandatory " + "to estimate") + synch_description = self.macro.synch_description time = 0 intervals = 0 - for group in synchronization: + for group in synch_description: delay = group[SynchParam.Delay][SynchDomain.Time] time += delay total = group[SynchParam.Total][SynchDomain.Time] diff --git a/src/sardana/macroserver/scan/recorder/datarecorder.py b/src/sardana/macroserver/scan/recorder/datarecorder.py index 2cf9230fcc..802903a991 100644 --- a/src/sardana/macroserver/scan/recorder/datarecorder.py +++ b/src/sardana/macroserver/scan/recorder/datarecorder.py @@ -150,7 +150,7 @@ def setSaveMode(self, mode): def addCustomData(self, value, name, **kwargs): try: self._addCustomData(value, name, **kwargs) - except Exception, e: + except Exception as e: raise RuntimeError('%s can not process custom data: %s' % (self.__class__.__name__, e)) diff --git a/src/sardana/macroserver/scan/recorder/storage.py b/src/sardana/macroserver/scan/recorder/storage.py index 526a2da681..c50634fdc8 100644 --- a/src/sardana/macroserver/scan/recorder/storage.py +++ b/src/sardana/macroserver/scan/recorder/storage.py @@ -104,8 +104,8 @@ def setFileName(self, filename): # obtain preferred nexus file mode for writing from the filename # extension (defaults to hdf5) extension = os.path.splitext(filename)[1] - inv_formats = dict(itertools.izip( - self.formats.itervalues(), self.formats.iterkeys())) + inv_formats = dict(list(zip( + iter(self.formats.values()), iter(self.formats.keys())))) self.nxfilemode = inv_formats.get(extension.lower(), 'w5') self.currentlist = None @@ -223,7 +223,7 @@ def _writeData(self, name, data, dtype, shape=None, chunks=None, attrs=None): self.fd.opendata(name) self.fd.putdata(data) if attrs is not None: - for k, v in attrs.items(): + for k, v in list(attrs.items()): self.fd.putattr(k, v) nid = self.fd.getdataID() self.fd.closedata() @@ -304,21 +304,14 @@ def _createBranch(self, path): def FileRecorder(filename, macro, **pars): ext = os.path.splitext(filename)[1].lower() or '.spec' rec_manager = macro.getMacroServer().recorder_manager - - hinted_recorder = getattr(macro, 'hints', {}).get('FileRecorder', None) - if hinted_recorder is not None: - macro.deprecated("FileRecorder macro hints are deprecated. " - "Use ScanRecorder variable instead.") - klass = rec_manager.getRecorderClass(hinted_recorder) + klasses = rec_manager.getRecorderClasses( + filter=BaseFileRecorder, extension=ext) + len_klasses = len(klasses) + if len_klasses == 0: + klass = rec_manager.getRecorderClass('SPEC_FileRecorder') + elif len_klasses == 1: + klass = list(klasses.values())[0] else: - klasses = rec_manager.getRecorderClasses( - filter=BaseFileRecorder, extension=ext) - len_klasses = len(klasses) - if len_klasses == 0: - klass = rec_manager.getRecorderClass('SPEC_FileRecorder') - elif len_klasses == 1: - klass = klasses.values()[0] - else: - raise AmbiguousRecorderError('Choice of recorder for %s ' - 'extension is ambiguous' % ext) + raise AmbiguousRecorderError('Choice of recorder for %s ' + 'extension is ambiguous' % ext) return klass(filename=filename, macro=macro, **pars) diff --git a/src/sardana/macroserver/scan/scandata.py b/src/sardana/macroserver/scan/scandata.py index 14897fd6e1..c4057f7352 100644 --- a/src/sardana/macroserver/scan/scandata.py +++ b/src/sardana/macroserver/scan/scandata.py @@ -200,14 +200,14 @@ def __get_t3_name(self, item): raise KeyError(item) v = proxy.getNameValidator() - params = v.getParams(proxy.getFullName()) + params = v.getUriGroups(proxy.getFullName()) name = '{0}:{1}/{2}'.format(params['host'].split('.')[0], params['port'], - params['devicename']) + params['devname']) - attr_name = params.get('attributename', None) + attr_name = params.get('_shortattrname', None) if attr_name is not None: - name = '{0}/{1}'.format(name, params['attributename']) + name = '{0}/{1}'.format(name, attr_name) return name @@ -269,7 +269,7 @@ def isValid(self): return 1 for ky in self.needed + self.__needed: - if ky not in self.keys(): + if ky not in list(self.keys()): return 0 else: return 1 @@ -399,7 +399,7 @@ def applyZeroOrderInterpolation(self, record): if self.currentIndex > 0: data = record.data prev_data = self.records[self.currentIndex - 1].data - for k, v in data.items(): + for k, v in list(data.items()): if v is None: continue # numpy arrays (1D or 2D) are valid values and does not require @@ -414,7 +414,7 @@ def applyZeroOrderInterpolation(self, record): def applyExtrapolation(self, record): """Apply extrapolation to the given record""" data = record.data - for k, v in data.items(): + for k, v in list(data.items()): if v is None: continue # numpy arrays (1D or 2D) are valid values and does not require @@ -491,7 +491,7 @@ def isRecordCompleted(self, recordno): return True def addRecords(self, records): - map(self.addRecord, records) + list(map(self.addRecord, records)) def end(self): start = self.currentIndex diff --git a/src/sardana/macroserver/scan/test/helper.py b/src/sardana/macroserver/scan/test/helper.py index e62ed2b6cc..d3bdefb8f7 100644 --- a/src/sardana/macroserver/scan/test/helper.py +++ b/src/sardana/macroserver/scan/test/helper.py @@ -36,7 +36,7 @@ def run(self): i = 0 for v, t in zip(self.values, self.intervals): try: - idx = range(i, i + len(v)) + idx = list(range(i, i + len(v))) i += len(v) skip = float('NaN') in v except TypeError: # if v is not a list @@ -119,7 +119,7 @@ def main(): f = nxs.load(file_name) m = f['entry1']['measurement'] ch1 = m['ch1'] - print ch1.nxdata + print(ch1.nxdata) if __name__ == "__main__": main() diff --git a/src/sardana/macroserver/scan/test/test_gscan.py b/src/sardana/macroserver/scan/test/test_gscan.py index 621e80da62..8b5e8de2cb 100644 --- a/src/sardana/macroserver/scan/test/test_gscan.py +++ b/src/sardana/macroserver/scan/test/test_gscan.py @@ -25,7 +25,7 @@ import sys -from taurus.external import unittest +import unittest from taurus.test import insertTest diff --git a/src/sardana/macroserver/scan/test/test_recorddata.py b/src/sardana/macroserver/scan/test/test_recorddata.py index 714c498508..5b0683a87b 100644 --- a/src/sardana/macroserver/scan/test/test_recorddata.py +++ b/src/sardana/macroserver/scan/test/test_recorddata.py @@ -25,7 +25,7 @@ import math import os -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.macroserver.scan.scandata import ScanData from sardana.macroserver.scan.recorder import DataHandler @@ -85,14 +85,14 @@ def setUp(self): def prepareScandData(self, data, apply_interpolation=False): scan_dir, scan_file = os.path.split(self.file_name) - env = createScanDataEnvironment(data.keys(), scan_dir, scan_file) + env = createScanDataEnvironment(list(data.keys()), scan_dir, scan_file) self.scan_data = ScanData(environment=env, data_handler=self.data_handler, apply_interpolation=apply_interpolation) self.srcs = [] self.inputs = {} max_len = -1 - for name, dat in data.items(): + for name, dat in list(data.items()): des = DummyEventSource(name, self.scan_data, dat, [0] * len(dat)) self.srcs.append(des) input_list = [] @@ -106,7 +106,7 @@ def prepareScandData(self, data, apply_interpolation=False): if max_len < len_il: max_len = len_il # Pading the list to fill it with float('Nan') - for name, dat in self.inputs.items(): + for name, dat in list(self.inputs.items()): diff = max_len - len(dat) self.inputs[name] = dat + [float('Nan')] * diff @@ -125,7 +125,7 @@ def recorddata(self, data, apply_interpolation): # Test the generated nxs file f = self.nxs.load(self.file_name) m = f['entry1']['measurement'] - for chn in data.keys(): + for chn in list(data.keys()): chn_data = m[chn].nxdata # check the data element by element for i in range(len(chn_data)): @@ -152,7 +152,7 @@ def zeroOrderInterpolation(self, data, apply_interpolation): # Test the generated nxs file f = self.nxs.load(self.file_name) m = f['entry1']['measurement'] - for chn in data.keys(): + for chn in list(data.keys()): chn_data = m[chn].nxdata # check the interpolations for i in range(len(chn_data)): diff --git a/src/sardana/macroserver/test/test_msparameter.py b/src/sardana/macroserver/test/test_msparameter.py index 1e357d10e5..3151081d39 100644 --- a/src/sardana/macroserver/test/test_msparameter.py +++ b/src/sardana/macroserver/test/test_msparameter.py @@ -1,4 +1,4 @@ -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.macroserver.macro import Type @@ -106,7 +106,7 @@ def decode(self, params_def, params_raw, expected_params=None, try: param_decoder = ParamDecoder(self.type_manager, params_def, params_raw) - except Exception, e: + except Exception as e: exception = e if expected_params: exception_message = getattr(exception, "message", None) diff --git a/src/sardana/macroserver/test/test_msrecordermanager.py b/src/sardana/macroserver/test/test_msrecordermanager.py index 4d2152098c..f9c91e328f 100644 --- a/src/sardana/macroserver/test/test_msrecordermanager.py +++ b/src/sardana/macroserver/test/test_msrecordermanager.py @@ -25,7 +25,7 @@ import os -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.macroserver.macroserver import MacroServer @@ -149,7 +149,7 @@ def test_SameFormats(self): self._updateRecorderManager(recorder_path) klasses = self.manager.getRecorderMetaClasses(filter=BaseFileRecorder, extension='.spec') - klass = klasses.values()[0] + klass = list(klasses.values())[0] # retrieve path to the recorder library path = os.sep.join(klass.lib.full_name.split(os.sep)[:-1]) msg = 'Ordered path precedence is not maintained by RecorderManager' diff --git a/src/sardana/pool/controller.py b/src/sardana/pool/controller.py index d90e9fe881..fc24d05030 100644 --- a/src/sardana/pool/controller.py +++ b/src/sardana/pool/controller.py @@ -109,17 +109,9 @@ class Controller(object): :keyword kwargs: """ - #: .. deprecated:: 1.0 - #: use :attr:`~Controller.ctrl_properties` instead - class_prop = {} - #: A sequence of :obj:`str` representing the controller features ctrl_features = [] - #: .. deprecated:: 1.0 - #: use :attr:`~Controller.axis_attributes` instead - ctrl_extra_attributes = {} - #: A :class:`dict` containing controller properties where: #: #: - key : (:obj:`str`) controller property name @@ -308,7 +300,7 @@ def __init__(self, inst, props, *args, **kwargs): self._args = args self._kwargs = kwargs self._api_version = self._findAPIVersion() - for prop_name, prop_value in props.items(): + for prop_name, prop_value in list(props.items()): setattr(self, prop_name, prop_value) def _findAPIVersion(self): @@ -339,14 +331,6 @@ def DeleteDevice(self, axis): :param int axis: axis number""" pass - @property - def inst_name(self): - """**Controller API**. The controller instance name. - - .. deprecated:: 1.0 - use :meth:`~Controller.GetName` instead""" - return self._inst_name - def GetName(self): """**Controller API**. The controller instance name. @@ -413,73 +397,36 @@ def GetCtrlPar(self, parameter): def SetAxisPar(self, axis, parameter, value): """**Controller API**. Override is MANDATORY. Called to set a parameter with a value on the given axis. Default - implementation calls deprecated :meth:`~Controller.SetPar` which, by - default, raises :exc:`NotImplementedError`. + implementation raises :exc:`NotImplementedError`. .. versionadded:: 1.0""" - return self.SetPar(axis, parameter, value) + raise NotImplementedError("SetAxisPar must be defined in the " + "controller") def GetAxisPar(self, axis, parameter): """**Controller API**. Override is MANDATORY. Called to get a parameter value on the given axis. Default - implementation calls deprecated :meth:`~Controller.GetPar` which, by - default, raises :exc:`NotImplementedError`. + implementation raises :exc:`NotImplementedError`. .. versionadded:: 1.0""" - return self.GetPar(axis, parameter) + raise NotImplementedError("GetAxisPar must be defined in the " + "controller") def SetAxisExtraPar(self, axis, parameter, value): """**Controller API**. Override if necessary. Called to set a parameter with a value on the given axis. Default - implementation calls deprecated :meth:`~Controller.SetExtraAttributePar` - which, by default, raises :exc:`NotImplementedError`. + implementation raises :exc:`NotImplementedError`. .. versionadded:: 1.0""" - return self.SetExtraAttributePar(axis, parameter, value) + raise NotImplementedError("SetAxisExtraPar must be defined in the " + "controller") def GetAxisExtraPar(self, axis, parameter): """**Controller API**. Override if necessary. Called to get a parameter value on the given axis. Default - implementation calls deprecated :meth:`~Controller.GetExtraAttributePar` - which, by default, raises :exc:`NotImplementedError`. + implementation raises :exc:`NotImplementedError`. .. versionadded:: 1.0""" - return self.GetExtraAttributePar(axis, parameter) - - def SetPar(self, axis, parameter, value): - """**Controller API**. Called to set a parameter with a value on - the given axis. Default implementation raises - :exc:`NotImplementedError`. - - .. deprecated:: 1.0 - use :meth:`~Controller.SetAxisPar` instead""" - raise NotImplementedError("SetAxisPar must be defined in the " - "controller") - - def GetPar(self, axis, parameter): - """**Controller API**. Called to get a parameter value on the given - axis. Default implementation raises :exc:`NotImplementedError`. - - .. deprecated:: 1.0 - use :meth:`~Controller.GetAxisPar` instead""" - raise NotImplementedError("GetAxisPar must be defined in the " - "controller") - - def SetExtraAttributePar(self, axis, parameter, value): - """**Controller API**. Called to set a parameter with a value on the - given axis. Default implementation raises :exc:`NotImplementedError`. - - .. deprecated:: 1.0 - use :meth:`~Controller.SetAxisExtraPar` instead""" - raise NotImplementedError("SetAxisExtraPar must be defined in the " - "controller") - - def GetExtraAttributePar(self, axis, parameter): - """**Controller API**. Called to get a parameter value on the given - axis. Default implementation raises :exc:`NotImplementedError`. - - .. deprecated:: 1.0 - use :meth:`~Controller.GetAxisExtraPar` instead""" raise NotImplementedError("GetAxisExtraPar must be defined in the " "controller") @@ -496,9 +443,7 @@ def GetAxisAttributes(self, axis): .. versionadded:: 1.0""" ret = copy.deepcopy(self.standard_axis_attributes) axis_attrs = copy.deepcopy(self.axis_attributes) - old_axis_attrs = copy.deepcopy(self.ctrl_extra_attributes) ret.update(axis_attrs) - ret.update(old_axis_attrs) return ret def SendToCtrl(self, stream): @@ -796,13 +741,7 @@ class MotorController(Controller, Startable, Stopable, Readable): - step_per_unit These parameters are configured through the - :meth:`~Controller.GetAxisPar`/:meth:`~Controller.SetAxisPar` - API (in version <1.0 the methods were called - :meth:`~Controller.GetPar`/:meth:`~Controller.SetPar`. Default - :meth:`~Controller.GetAxisPar` and - :meth:`~Controller.SetAxisPar` still call - :meth:`~Controller.GetPar` and :meth:`~Controller.SetPar` - respectively in order to maintain backward compatibility). + :meth:`~Controller.GetAxisPar`/:meth:`~Controller.SetAxisPar` API. """ #: A constant representing no active switch. @@ -924,7 +863,7 @@ class CounterTimerController(Controller, Readable, Startable, Stopable, - timer - monitor - - trigger_type""" + """ #: A :class:`dict` containing the standard attributes present on each axis #: device @@ -953,98 +892,13 @@ def __init__(self, inst, props, *args, **kwargs): self._latency_time = 0 self._synchronization = AcqSynch.SoftwareTrigger - def get_trigger_type(self): - msg = "trigger_type is deprecated since SEP6. " +\ - "Use synchronization instead" - self._log.warning(msg) - return self._synchronization - - _trigger_type = property(get_trigger_type) - - def PreStartAllCT(self): - """**Counter/Timer Controller API**. Override if necessary. - Called to prepare an acquisition of all selected axis. - Default implementation does nothing. - - .. deprecated:: 1.0 - use :meth:`~CounterTimerController.PreStartAll` instead""" - pass - - def PreStartOneCT(self, axis): - """**Counter/Timer Controller API**. Override if necessary. - Called to prepare an acquisition a single axis. - Default implementation returns True. - - :param int axis: axis number - :return: True means a successfull PreStartOneCT or False for a failure - :rtype: bool - - .. deprecated:: 1.0 - use :meth:`~CounterTimerController.PreStartOne` instead""" - return True - - def StartOneCT(self, axis): - """**Counter/Timer Controller API**. Override if necessary. - Called to start an acquisition of a selected axis. - Default implementation does nothing. - - :param int axis: axis number - - .. deprecated:: 1.0 - use :meth:`~CounterTimerController.StartOne` instead""" - pass - - def StartAllCT(self): - """**Counter/Timer Controller API**. - Called to start an acquisition of a group of channels. - Default implementation does nothing. - - .. deprecated:: 1.0 - use :meth:`~CounterTimerController.StartAll` instead""" - pass - - def PreStartAll(self): - """**Controller API**. Override if necessary. - Called to prepare a write of the position of all axis. Default - implementation calls deprecated - :meth:`~CounterTimerController.PreStartAllCT` which, by default, does - nothing. - - .. versionadded:: 1.0""" - return self.PreStartAllCT() - - def PreStartOne(self, axis, value=None): - """**Controller API**. Override if necessary. - Called to prepare a write of the position of a single axis. - Default implementation calls deprecated - :meth:`~CounterTimerController.PreStartOneCT` which, by default, - returns True. - - :param int axis: axis number - :param float value: the value - :return: True means a successfull pre-start or False for a failure - :rtype: bool - - .. versionadded:: 1.0""" - return self.PreStartOneCT(axis) - - def StartOne(self, axis, value=None): + def StartOne(self, axis, value): """**Controller API**. Override if necessary. - Called to write the position of a selected axis. Default - implementation calls deprecated - :meth:`~CounterTimerController.StartOneCT` which, by default, does - nothing. + Called to do a start of the given axis (whatever start means). :param int axis: axis number - :param float value: the value""" - return self.StartOneCT(axis) - - def StartAll(self): - """**Controller API**. - Default implementation calls deprecated - :meth:`~CounterTimerController.StartAllCT` which, by default, does - nothing.""" - return self.StartAllCT() + :param float value: new value""" + pass class TriggerGateController(Controller, Synchronizer, Stopable, Startable): @@ -1301,7 +1155,8 @@ def CalcPseudo(self, axis, physical_pos, curr_pseudo_pos): :rtype: float .. versionadded:: 1.0""" - return self.calc_pseudo(axis, physical_pos) + raise NotImplementedError("CalcPseudo must be defined in the " + "controller") def CalcPhysical(self, axis, pseudo_pos, curr_physical_pos): """**Pseudo Motor Controller API**. Override is **MANDATORY**. @@ -1318,77 +1173,6 @@ def CalcPhysical(self, axis, pseudo_pos, curr_physical_pos): :rtype: float .. versionadded:: 1.0""" - return self.calc_physical(axis, pseudo_pos) - - def calc_all_pseudo(self, physical_pos): - """**Pseudo Motor Controller API**. Override if necessary. - Calculates the positions of all pseudo motors that belong to the - pseudo motor system from the positions of the physical motors. - Default implementation does a loop calling - :meth:`PseudoMotorController.calc_pseudo` for each pseudo motor role. - - :param sequence physical_pos: a sequence of physical motor - positions - :return: a sequece of pseudo motor positions (one for each pseudo - motor role) - :rtype: sequence - - .. deprecated:: 1.0 - implement :meth:`~PseudoMotorController.CalcAllPseudo` instead""" - ret = [] - for i in range(len(self.pseudo_motor_roles)): - ret.append(self.calc_pseudo(i + 1, physical_pos)) - return ret - - def calc_all_physical(self, pseudo_pos): - """**Pseudo Motor Controller API**. Override if necessary. - Calculates the positions of all motors that belong to the pseudo - motor system from the positions of the pseudo motors. - Default implementation does a loop calling - :meth:`PseudoMotorController.calc_physical` for each motor role. - - :param pseudo_pos: a sequence of pseudo motor positions - :type pseudo_pos: sequence - :return: a sequece of motor positions (one for each motor role) - :rtype: sequence - - .. deprecated:: 1.0 - implement :meth:`~PseudoMotorController.CalcAllPhysical` - instead""" - ret = [] - for i in range(len(self.motor_roles)): - pos = self.calc_physical(i + 1, pseudo_pos) - ret.append(pos) - return ret - - def calc_pseudo(self, axis, physical_pos): - """**Pseudo Motor Controller API**. Override is **MANDATORY**. - Calculate pseudo motor position given the physical motor positions - - :param int axis: the pseudo motor role axis - :param sequence physical_pos: a sequence of motor positions - :return: a pseudo motor position corresponding to the given axis - pseudo motor role - :rtype: float - - .. deprecated:: 1.0 - implement :meth:`~PseudoMotorController.CalcPseudo` instead""" - raise NotImplementedError( - "CalcPseudo must be defined in the controller") - - def calc_physical(self, axis, pseudo_pos): - """**Pseudo Motor Controller API**. Override is **MANDATORY**. - Calculate physical motor position given the pseudo motor positions. - - :param axis: the motor role axis - :type axis: int - :param pseudo_pos: a sequence of pseudo motor positions - :type pseudo_pos: sequence - :return: a motor position corresponding to the given axis motor role - :rtype: float - - .. deprecated:: 1.0 - implement :meth:`~PseudoMotorController.CalcPhysical` instead""" raise NotImplementedError("CalcPhysical must be defined in the " "controller") @@ -1427,9 +1211,9 @@ def GetPseudoMotor(self, index_or_role): dict_ids = self._getPoolController().get_element_ids() dict_axis = self._getPoolController().get_element_axis() pseudo_motor_ids = [] - for akey, aname in dict_axis.items(): + for akey, aname in list(dict_axis.items()): pseudo_motor_ids.append( - dict_ids.keys()[dict_ids.values().index(aname)]) + list(dict_ids.keys())[list(dict_ids.values()).index(aname)]) return self._getElem(index_or_role, self.pseudo_motor_roles, self.__pseudo_motor_role_elements, pseudo_motor_ids) @@ -1484,21 +1268,6 @@ def Calc(self, axis, values): :rtype: float .. versionadded:: 1.0""" - return self.calc(axis, values) - - def calc(self, axis, values): - """**Pseudo Counter Controller API**. Override is **MANDATORY**. - Calculate pseudo counter value given the counter values. - - :param int axis: the pseudo counter role axis - :param sequence values: a sequence containing current values - of underlying elements - :return: a pseudo counter value corresponding to the given axis - pseudo counter role - :rtype: float - - .. deprecated:: 1.0 - implement :meth:`~PseudoCounterController.Calc` instead""" raise NotImplementedError("Calc must be defined in the controller") def CalcAll(self, values): @@ -1523,10 +1292,6 @@ class IORegisterController(Controller, Readable): implement your own IORegister controller for the device pool. """ - #: .. deprecated:: 1.0 - #: use :attr:`~Controller.axis_attributes` instead - predefined_values = () - #: A :class:`dict` containing the standard attributes present on each axis #: device standard_axis_attributes = { diff --git a/src/sardana/pool/pool.py b/src/sardana/pool/pool.py index 026504f2ff..832d81504e 100644 --- a/src/sardana/pool/pool.py +++ b/src/sardana/pool/pool.py @@ -25,8 +25,8 @@ """This module contains the main pool class""" -from __future__ import print_function -from __future__ import with_statement + + __all__ = ["Pool"] @@ -35,12 +35,7 @@ import os.path import logging.handlers -try: - from taurus.core.taurusvalidator import AttributeNameValidator as\ - TangoAttributeNameValidator -except ImportError: - # TODO: For Taurus 4 compatibility - from taurus.core.tango.tangovalidator import TangoAttributeNameValidator +from taurus.core.tango.tangovalidator import TangoAttributeNameValidator from taurus.core.util.containers import CaselessDict from sardana import InvalidId, ElementType, TYPE_ACQUIRABLE_ELEMENTS, \ @@ -54,6 +49,7 @@ from sardana.pool.poolmonitor import PoolMonitor from sardana.pool.poolmetacontroller import TYPE_MAP_OBJ from sardana.pool.poolcontrollermanager import ControllerManager +from sardana.pool.poolmeasurementgroup import PoolMeasurementGroup class Graph(dict): @@ -154,7 +150,7 @@ def init_local_logging(self): log_file_name = os.path.join(path, 'controller.log.txt') try: if not os.path.exists(path): - os.makedirs(path, 0777) + os.makedirs(path, 0o777) f_h = logging.handlers.RotatingFileHandler(log_file_name, maxBytes=1E7, backupCount=5) @@ -326,7 +322,7 @@ def get_controller_classes_summary_info(self): def get_elements_str_info(self, obj_type=None): if obj_type is None: - objs = self.get_element_id_map().values() + objs = list(self.get_element_id_map().values()) objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) elif obj_type == ElementType.ControllerClass: @@ -340,7 +336,7 @@ def get_elements_str_info(self, obj_type=None): def get_elements_info(self, obj_type=None): if obj_type is None: - objs = self.get_element_id_map().values() + objs = list(self.get_element_id_map().values()) objs.extend(self.get_controller_classes()) objs.extend(self.get_controller_libs()) objs.append(self) @@ -355,18 +351,18 @@ def get_elements_info(self, obj_type=None): def get_acquisition_elements_info(self): ret = [] - for _, element in self.get_element_name_map().items(): + for _, element in list(self.get_element_name_map().items()): if element.get_type() not in TYPE_ACQUIRABLE_ELEMENTS: continue acq_channel = element.get_default_acquisition_channel() full_name = "{0}/{1}".format(element.full_name, acq_channel) info = dict(name=element.name, full_name=full_name, origin='local') ret.append(info) - ret.extend(self._extra_acquisition_element_names.values()) + ret.extend(list(self._extra_acquisition_element_names.values())) return ret def get_acquisition_elements_str_info(self): - return map(self.str_object, self.get_acquisition_elements_info()) + return list(map(self.str_object, self.get_acquisition_elements_info())) def create_controller(self, **kwargs): ctrl_type = kwargs['type'] @@ -410,7 +406,7 @@ def create_controller(self, **kwargs): ctrl_prop_info = {} else: ctrl_prop_info = ctrl_class_info.ctrl_properties - for k, v in kwargs['properties'].items(): + for k, v in list(kwargs['properties'].items()): info = ctrl_prop_info.get(k) if info is None: props[k] = v @@ -525,7 +521,7 @@ def create_measurement_group(self, **kwargs): self.pool.get_element(id=elem_id) else: tg_attr_validator = TangoAttributeNameValidator() - params = tg_attr_validator.getParams(elem_id) + params = tg_attr_validator.getUriGroups(elem_id) if params is None: raise Exception("Invalid channel name %s" % elem_id) @@ -543,7 +539,10 @@ def create_measurement_group(self, **kwargs): def rename_element(self, old_name, new_name): elem = self.get_element_by_name(old_name) - elem.controller.rename_element(old_name, new_name) + if type(elem) == PoolMeasurementGroup: + elem.rename_element(old_name, new_name) + else: + elem.controller.rename_element(old_name, new_name) PoolContainer.rename_element(self, old_name, new_name) elem = self.get_element_by_name(new_name) self.fire_event(EventType("ElementChanged"), elem) @@ -581,6 +580,8 @@ def delete_element(self, name): self.remove_element(elem) self.fire_event(EventType("ElementDeleted"), elem) + if hasattr(elem, "get_controller"): + elem.set_deleted(True) def create_instrument(self, full_name, klass_name, id=None): is_root = full_name.count('/') == 1 @@ -688,8 +689,8 @@ def reload_controller_lib(self, lib_name): new_elements.extend(new_lib.get_controllers()) new_elements.append(new_lib) else: - new_names = set([ctrl.name for ctrl in new_lib.get_controllers()]) - old_names = set([ctrl.name for ctrl in old_lib.get_controllers()]) + new_names = {ctrl.name for ctrl in new_lib.get_controllers()} + old_names = {ctrl.name for ctrl in old_lib.get_controllers()} changed_names = set.intersection(new_names, old_names) deleted_names = old_names.difference(new_names) new_names = new_names.difference(old_names) @@ -755,6 +756,6 @@ def get_moveable_graph(self): for elem_type in TYPE_MOVEABLE_ELEMENTS: moveable_elems_map.update(elem_type_map[elem_type]) graph = Graph() - for moveable in moveable_elems_map.values(): + for moveable in list(moveable_elems_map.values()): self._build_element_dependencies(moveable, graph) return graph diff --git a/src/sardana/pool/poolacquisition.py b/src/sardana/pool/poolacquisition.py index a5ef121af6..224fb6ce1f 100644 --- a/src/sardana/pool/poolacquisition.py +++ b/src/sardana/pool/poolacquisition.py @@ -37,14 +37,19 @@ import weakref import datetime import traceback +import functools +import threading from taurus.core.util.log import DebugIt from taurus.core.util.enumeration import Enumeration -from sardana import SardanaValue, State, ElementType, TYPE_TIMERABLE_ELEMENTS +from sardana import AttrQuality, SardanaValue, State, ElementType, \ + TYPE_TIMERABLE_ELEMENTS + from sardana.sardanathreadpool import get_thread_pool from sardana.pool import AcqSynch, AcqMode -from sardana.pool.poolaction import ActionContext, PoolAction +from sardana.pool.poolaction import ActionContext, PoolAction, \ + OperationContext from sardana.pool.poolsynchronization import PoolSynchronization #: enumeration representing possible motion states @@ -265,6 +270,14 @@ def get_channels(self, enabled=None): return list(self._channels_disabled) +class AcquisitionBaseContext(OperationContext): + + def exit(self): + pool_action = self._pool_action + pool_action._reset_ctrl_dicts() + return OperationContext.exit(self) + + class PoolAcquisition(PoolAction): """Acquisition action which is internally composed for sub-actions. @@ -295,6 +308,7 @@ def __init__(self, main_element, name="Acquisition"): self._0d_acq = Pool0DAcquisition(main_element, name=zerodname) self._hw_acq = PoolAcquisitionHardware(main_element, name=hwname) self._synch = PoolSynchronization(main_element, name=synchname) + self._handled_first_active = False def event_received(self, *args, **kwargs): """Callback executed on event of software synchronizer. @@ -312,50 +326,62 @@ def event_received(self, *args, **kwargs): self.debug(msg) if name == "start": if self._sw_start_acq_args is not None: + self._sw_start_acq._wait() + self._sw_start_acq._set_busy() self.debug('Executing software start acquisition.') - get_thread_pool().add(self._sw_start_acq.run, None, + self._sw_start_acq._started = True + get_thread_pool().add(self._sw_start_acq.run, + self._sw_start_acq._set_ready, *self._sw_start_acq_args.args, **self._sw_start_acq_args.kwargs) elif name == "active": # this code is not thread safe, but for the moment we assume that # only one EventGenerator will work at the same time + if self._handled_first_active: + timeout = 0 + else: + timeout = None + self._handled_first_active = True if self._sw_acq_args is not None: - if self._sw_acq._is_started() or self._sw_acq.is_running(): + if not self._sw_acq._wait(timeout): msg = ('Skipping trigger: software acquisition is still' ' in progress.') self.debug(msg) return else: + self._sw_acq._set_busy() self.debug('Executing software acquisition.') self._sw_acq_args.kwargs.update({'index': index}) self._sw_acq._started = True - get_thread_pool().add(self._sw_acq.run, None, + get_thread_pool().add(self._sw_acq.run, + self._sw_acq._set_ready, *self._sw_acq_args.args, **self._sw_acq_args.kwargs) if self._0d_acq_args is not None: - if self._0d_acq._is_started() or self._0d_acq.is_running(): + if not self._0d_acq._wait(timeout): msg = ('Skipping trigger: ZeroD acquisition is still in' ' progress.') self.debug(msg) return else: + self._0d_acq._set_busy() self.debug('Executing ZeroD acquisition.') self._0d_acq_args.kwargs.update({'index': index}) self._0d_acq._started = True self._0d_acq._stopped = False self._0d_acq._aborted = False - get_thread_pool().add(self._0d_acq.run, None, + get_thread_pool().add(self._0d_acq.run, + self._0d_acq._set_ready, *self._0d_acq_args.args, **self._0d_acq_args.kwargs) elif name == "passive": # TODO: _0d_acq_args comparison may not be necessary if (self._0d_acq_args is not None - and (self._0d_acq._is_started() - or self._0d_acq.is_running())): + and not self._0d_acq._is_ready()): self.debug('Stopping ZeroD acquisition.') self._0d_acq.stop_action() - def prepare(self, config, acq_mode, value, synchronization=None, + def prepare(self, config, acq_mode, value, synch_description=None, moveable=None, sw_synch_initial_domain=None, nb_starts=1, **kwargs): """Prepare measurement process. @@ -368,12 +394,13 @@ def prepare(self, config, acq_mode, value, synchronization=None, self._0d_acq_args = None self._hw_acq_args = None self._synch_args = None + self._handled_first_active = False ctrls_hw = [] ctrls_sw = [] ctrls_sw_start = [] - repetitions = synchronization.repetitions - latency = synchronization.passive_time + repetitions = synch_description.repetitions + latency = synch_description.passive_time # Prepare controllers synchronized by hardware acq_sync_hw = [AcqSynch.HardwareTrigger, AcqSynch.HardwareStart, AcqSynch.HardwareGate] @@ -432,7 +459,7 @@ def prepare(self, config, acq_mode, value, synchronization=None, # Prepare synchronizer controllers ctrls = config.get_synch_ctrls(enabled=True) ctrls_synch = get_acq_ctrls(ctrls) - synch_args = (ctrls_synch, synchronization) + synch_args = (ctrls_synch, synch_description) synch_kwargs = {'moveable': moveable, 'sw_synch_initial_domain': sw_synch_initial_domain} synch_kwargs.update(kwargs) @@ -524,8 +551,11 @@ def run(self, *args, **kwargs): pseudo_elem.clear_value_buffer() if self._hw_acq_args is not None: + self._hw_acq._wait() + self._hw_acq._set_busy() self._hw_acq.run(*self._hw_acq_args.args, - **self._hw_acq_args.kwargs) + **self._hw_acq_args.kwargs, + cb=self._hw_acq._set_ready) if self._sw_acq_args is not None\ or self._sw_start_acq_args is not None\ @@ -533,8 +563,11 @@ def run(self, *args, **kwargs): self._synch.add_listener(self) if self._synch_args is not None: + self._synch._wait() + self._synch._set_busy() self._synch.run(*self._synch_args.args, - **self._synch_args.kwargs) + **self._synch_args.kwargs, + cb=self._synch._set_ready) def _get_action_for_element(self, element): elem_type = element.get_type() @@ -640,19 +673,57 @@ def read_value(self, ret=None, serial=False): class PoolAcquisitionBase(PoolAction): - """Base class for acquisitions with a generic start_action method. + """Base class for sub-acquisition. .. note:: The PoolAcquisitionBase class has been included in Sardana on a provisional basis. Backwards incompatible changes (up to and including removal of the module) may occur if deemed necessary by the core developers. + + .. todo: Think of moving the ready/busy mechanism to PoolAction """ def __init__(self, main_element, name): PoolAction.__init__(self, main_element, name) self._channels = [] self._index = None + self._ready = threading.Event() + self._ready.set() + + def _is_ready(self): + return self._ready.is_set() + + def _wait(self, timeout=None): + return self._ready.wait(timeout) + + def _set_ready(self, _=None): + self._ready.set() + + def _is_busy(self): + return not self._ready.is_set() + + def _set_busy(self): + self._ready.clear() + + +class PoolAcquisitionTimerable(PoolAcquisitionBase): + """Base class for acquisitions of timerable channels. + + Implements a generic start_action method. action_loop method must be + implemented by the sub-class. + + .. note:: + The PoolAcquisitionTimerable class has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including removal of the module) may occur if + deemed necessary by the core developers. + """ + + OperationContextClass = AcquisitionBaseContext + + def __init__(self, main_element, name): + PoolAcquisitionBase.__init__(self, main_element, name) self._nb_states_per_value = None self._acq_sleep_time = None self._pool_ctrl_dict_loop = None @@ -807,8 +878,6 @@ def start_action(self, ctrls, value, master, repetitions, latency, self._set_pool_ctrl_dict_loop(ctrls) # split controllers to read value and value reference self._split_ctrl(ctrls) - self.add_finish_hook(self._reset_ctrl_dicts, False) - # channels that are acquired (only enabled) self._channels = [] @@ -817,41 +886,12 @@ def load(channel, value, repetitions, latency=0): pool_ctrl = channel.controller ctrl = pool_ctrl.ctrl ctrl.PreLoadAll() - try: - res = ctrl.PreLoadOne(axis, value, repetitions, - latency) - except TypeError: - try: - res = ctrl.PreLoadOne(axis, value, repetitions) - msg = ("PreLoadOne(axis, value, repetitions) is " - "deprecated since version 2.7.0. Use PreLoadOne(" - "axis, value, repetitions, latency_time) instead.") - self.warning(msg) - except TypeError: - res = ctrl.PreLoadOne(axis, value) - msg = ("PreLoadOne(axis, value) is deprecated since " - "version 2.3.0. Use PreLoadOne(axis, value, " - "repetitions, latency_time) instead.") - self.warning(msg) + res = ctrl.PreLoadOne(axis, value, repetitions, latency) if not res: msg = ("%s.PreLoadOne(%d) returned False" % (pool_ctrl.name, axis)) raise Exception(msg) - try: - ctrl.LoadOne(axis, value, repetitions, latency) - except TypeError: - try: - ctrl.LoadOne(axis, value, repetitions) - msg = ("LoadOne(axis, value, repetitions) is deprecated " - "since version Jan18. Use LoadOne(axis, value, " - "repetitions, latency_time) instead.") - self.warning(msg) - except TypeError: - ctrl.LoadOne(axis, value) - msg = ("LoadOne(axis, value) is deprecated since " - "version 2.3.0. Use LoadOne(axis, value, " - "repetitions) instead.") - self.warning(msg) + ctrl.LoadOne(axis, value, repetitions, latency) ctrl.LoadAll() with ActionContext(self): @@ -875,8 +915,8 @@ def load(channel, value, repetitions, latency=0): for ctrl in ctrls: channels = ctrl.get_channels(enabled=True) - # make sure that the master timer/monitor is started as the - # last one + # make sure that the master timer/monitor is started as + # the last one channels.remove(ctrl.master) channels.append(ctrl.master) for channel in channels: @@ -968,7 +1008,7 @@ def clear_value_buffers(self): channel.clear_value_buffer() -class PoolAcquisitionHardware(PoolAcquisitionBase): +class PoolAcquisitionHardware(PoolAcquisitionTimerable): """Acquisition action for controllers synchronized by hardware .. note:: @@ -982,12 +1022,12 @@ class PoolAcquisitionHardware(PoolAcquisitionBase): """ def __init__(self, main_element, name="AcquisitionHardware"): - PoolAcquisitionBase.__init__(self, main_element, name) + PoolAcquisitionTimerable.__init__(self, main_element, name) def start_action(self, ctrls, value, repetitions, latency, acq_sleep_time=None, nb_states_per_value=None, **kwargs): - PoolAcquisitionBase.start_action(self, ctrls, value, None, + PoolAcquisitionTimerable.start_action(self, ctrls, value, None, repetitions, latency, None, acq_sleep_time, nb_states_per_value, **kwargs) @@ -1015,7 +1055,7 @@ def action_loop(self): # read value every n times if not i % nb_states_per_value: self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): if is_value_error(value): self.error("Loop read value error for %s" % acquirable.name) @@ -1030,14 +1070,10 @@ def action_loop(self): i += 1 with ActionContext(self): - self.raw_read_state_info(ret=states) self.raw_read_value(ret=values) self.raw_read_value_ref(ret=value_refs) - for acquirable, state_info in states.items(): - # first update the element state so that value calculation - # that is done after takes the updated state into account - acquirable.set_state_info(state_info, propagate=0) + for acquirable, state_info in list(states.items()): if acquirable in values: value = values[acquirable] if is_value_error(value): @@ -1058,13 +1094,15 @@ def action_loop(self): traceback.format_exception(*value_ref.exc_info)) self.debug(msg) acquirable.extend_value_ref_buffer(value_ref, propagate=2) - with acquirable: - acquirable.clear_operation() - state_info = acquirable._from_ctrl_state_info(state_info) - acquirable.set_state_info(state_info, propagate=2) + state_info = acquirable._from_ctrl_state_info(state_info) + set_state_info = functools.partial(acquirable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) -class PoolAcquisitionSoftware(PoolAcquisitionBase): +class PoolAcquisitionSoftware(PoolAcquisitionTimerable): """Acquisition action for controllers synchronized by software .. note:: @@ -1075,7 +1113,7 @@ class PoolAcquisitionSoftware(PoolAcquisitionBase): """ def __init__(self, main_element, name="AcquisitionSoftware", slaves=None): - PoolAcquisitionBase.__init__(self, main_element, name) + PoolAcquisitionTimerable.__init__(self, main_element, name) if slaves is None: slaves = () @@ -1102,7 +1140,7 @@ def get_read_value_loop_ctrls(self): def start_action(self, ctrls, value, master, index, acq_sleep_time=None, nb_states_per_value=None, **kwargs): - PoolAcquisitionBase.start_action(self, ctrls, value, master, 1, 0, + PoolAcquisitionTimerable.start_action(self, ctrls, value, master, 1, 0, index, acq_sleep_time, nb_states_per_value, **kwargs) @@ -1125,8 +1163,8 @@ def action_loop(self): # read value every n times if not i % nb_states_per_value: self.read_value_loop(ret=values) - for acquirable, value in values.items(): - acquirable.put_value(value) + for acquirable, value in list(values.items()): + acquirable.put_value(value, quality=AttrQuality.Changing) time.sleep(nap) i += 1 @@ -1140,14 +1178,10 @@ def action_loop(self): self.debug("Details", exc_info=1) with ActionContext(self): - self.raw_read_state_info(ret=states) self.raw_read_value(ret=values) self.raw_read_value_ref(ret=value_refs) - for acquirable, state_info in states.items(): - # first update the element state so that value calculation - # that is done after takes the updated state into account - acquirable.set_state_info(state_info, propagate=0) + for acquirable, state_info in list(states.items()): if acquirable in values: value = values[acquirable] if is_value_error(value): @@ -1156,7 +1190,10 @@ def action_loop(self): msg = "Details: " + "".join( traceback.format_exception(*value.exc_info)) self.debug(msg) - acquirable.append_value_buffer(value, self._index) + acquirable.get_value_attribute().set_quality( + AttrQuality.Valid) + acquirable.append_value_buffer(value, self._index, + propagate=2) if acquirable in value_refs: value_ref = value_refs[acquirable] if is_value_error(value_ref): @@ -1166,13 +1203,15 @@ def action_loop(self): traceback.format_exception(*value_ref.exc_info)) self.debug(msg) acquirable.append_value_ref_buffer(value_ref, self._index) - with acquirable: - acquirable.clear_operation() - state_info = acquirable._from_ctrl_state_info(state_info) - acquirable.set_state_info(state_info, propagate=2) + state_info = acquirable._from_ctrl_state_info(state_info) + set_state_info = functools.partial(acquirable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) -class PoolAcquisitionSoftwareStart(PoolAcquisitionBase): +class PoolAcquisitionSoftwareStart(PoolAcquisitionTimerable): """Acquisition action for controllers synchronized by software start .. note:: @@ -1186,7 +1225,7 @@ class PoolAcquisitionSoftwareStart(PoolAcquisitionBase): """ def __init__(self, main_element, name="AcquisitionSoftwareStart"): - PoolAcquisitionBase.__init__(self, main_element, name) + PoolAcquisitionTimerable.__init__(self, main_element, name) def get_read_value_ctrls(self): # technical debt in order to work both in case of meas group and @@ -1196,7 +1235,7 @@ def get_read_value_ctrls(self): def start_action(self, ctrls, value, master, repetitions, latency, acq_sleep_time=None, nb_states_per_value=None, **kwargs): - PoolAcquisitionBase.start_action(self, ctrls, value, master, + PoolAcquisitionTimerable.start_action(self, ctrls, value, master, repetitions, latency, None, acq_sleep_time, nb_states_per_value, **kwargs) @@ -1221,7 +1260,7 @@ def action_loop(self): # read value every n times if not i % nb_states_per_value: self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): if is_value_error(value): self.error("Loop read value error for %s" % acquirable.name) @@ -1232,7 +1271,7 @@ def action_loop(self): else: acquirable.extend_value_buffer(value) self.read_value_ref(ret=value_refs) - for acquirable, value_ref in value_refs.items(): + for acquirable, value_ref in list(value_refs.items()): if is_value_error(value_ref): self.error("Loop read value ref error for %s" % acquirable.name) @@ -1246,14 +1285,10 @@ def action_loop(self): i += 1 with ActionContext(self): - self.raw_read_state_info(ret=states) self.raw_read_value(ret=values) self.raw_read_value_ref(ret=value_refs) - for acquirable, state_info in states.items(): - # first update the element state so that value calculation - # that is done after takes the updated state into account - acquirable.set_state_info(state_info, propagate=0) + for acquirable, state_info in list(states.items()): if acquirable in values: value = values[acquirable] if is_value_error(value): @@ -1276,13 +1311,15 @@ def action_loop(self): acquirable.put_value_ref(value_ref) else: acquirable.extend_value_ref_buffer(value_ref, propagate=2) - with acquirable: - acquirable.clear_operation() - state_info = acquirable._from_ctrl_state_info(state_info) - acquirable.set_state_info(state_info, propagate=2) + state_info = acquirable._from_ctrl_state_info(state_info) + set_state_info = functools.partial(acquirable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) -class PoolCTAcquisition(PoolAcquisitionBase): +class PoolCTAcquisition(PoolAcquisitionTimerable): """..todo:: remove it, still used by pseudo counter""" def __init__(self, main_element, name="CTAcquisition", slaves=None): @@ -1292,7 +1329,7 @@ def __init__(self, main_element, name="CTAcquisition", slaves=None): slaves = () self._slaves = slaves - PoolAcquisitionBase.__init__(self, main_element, name) + PoolAcquisitionTimerable.__init__(self, main_element, name) def get_read_value_loop_ctrls(self): return self._pool_ctrl_dict_loop @@ -1327,7 +1364,7 @@ def action_loop(self): # read values to send a first event when starting to acquire with ActionContext(self): self.raw_read_value_loop(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_value(value, propagate=2) while True: @@ -1338,7 +1375,7 @@ def action_loop(self): # read value every n times if not i % nb_states_per_value: self.read_value_loop(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_value(value) time.sleep(nap) @@ -1356,25 +1393,25 @@ def action_loop(self): self.raw_read_state_info(ret=states) self.raw_read_value_loop(ret=values) - for acquirable, state_info in states.items(): + for acquirable, state_info in list(states.items()): # first update the element state so that value calculation # that is done after takes the updated state into account acquirable.set_state_info(state_info, propagate=0) if acquirable in values: value = values[acquirable] acquirable.put_value(value, propagate=2) - with acquirable: - acquirable.clear_operation() - state_info = acquirable._from_ctrl_state_info(state_info) - acquirable.set_state_info(state_info, propagate=2) + state_info = acquirable._from_ctrl_state_info(state_info) + set_state_info = functools.partial(acquirable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) -class Pool0DAcquisition(PoolAction): +class Pool0DAcquisition(PoolAcquisitionBase): def __init__(self, main_element, name="0DAcquisition"): - self._channels = None - self._index = None - PoolAction.__init__(self, main_element, name) + PoolAcquisitionBase.__init__(self, main_element, name) def start_action(self, conf_ctrls, index, acq_sleep_time=None, nb_states_per_value=None, **kwargs): @@ -1435,7 +1472,7 @@ def action_loop(self): nap = self._acq_sleep_time while True: self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_current_value(value, propagate=0) if self._stopped or self._aborted: break @@ -1448,14 +1485,13 @@ def action_loop(self): with ActionContext(self): self.raw_read_state_info(ret=states) - for acquirable, state_info in states.items(): - # first update the element state so that value calculation - # that is done after takes the updated state into account + for acquirable, state_info in list(states.items()): state_info = acquirable._from_ctrl_state_info(state_info) - acquirable.set_state_info(state_info, propagate=0) - with acquirable: - acquirable.clear_operation() - acquirable.set_state_info(state_info, propagate=2) + set_state_info = functools.partial(acquirable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) def stop_action(self, *args, **kwargs): """Stop procedure for this action.""" @@ -1490,7 +1526,7 @@ def action_loop(self): # read values to send a first event when starting to acquire self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_value(value, propagate=2) while True: @@ -1502,7 +1538,7 @@ def action_loop(self): # read value every n times if not i % 5: self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_value(value) i += 1 @@ -1512,7 +1548,7 @@ def action_loop(self): # first update the element state so that value calculation # that is done after takes the updated state into account - for acquirable, state_info in states.items(): + for acquirable, state_info in list(states.items()): acquirable.set_state_info(state_info, propagate=0) # Do NOT send events before we exit the OperationContext, otherwise @@ -1523,11 +1559,11 @@ def action_loop(self): def finish_hook(*args, **kwargs): # read values and propagate the change to all listeners self.read_value(ret=values) - for acquirable, value in values.items(): + for acquirable, value in list(values.items()): acquirable.put_value(value, propagate=2) # finally set the state and propagate to all listeners - for acquirable, state_info in states.items(): + for acquirable, state_info in list(states.items()): acquirable.set_state_info(state_info, propagate=2) self.set_finish_hook(finish_hook) diff --git a/src/sardana/pool/poolaction.py b/src/sardana/pool/poolaction.py index 2a20c406e3..1db4e968a9 100644 --- a/src/sardana/pool/poolaction.py +++ b/src/sardana/pool/poolaction.py @@ -36,11 +36,7 @@ import traceback import threading -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from taurus.core.util.log import Logger @@ -199,6 +195,8 @@ class PoolAction(Logger): """A generic class to handle any type of operation (like motion or acquisition)""" + OperationContextClass = OperationContext + def __init__(self, main_element, name="GlobalAction"): Logger.__init__(self, name) self._action_run_lock = threading.Lock() @@ -340,7 +338,7 @@ def run(self, *args, **kwargs): if synch: try: - with OperationContext(self) as context: + with self.OperationContextClass(self) as context: self.start_action(*args, **kwargs) self._started = False self.action_loop() @@ -348,7 +346,7 @@ def run(self, *args, **kwargs): self._started = False self._running = False else: - context = OperationContext(self) + context = self.OperationContextClass(self) context.enter() try: self.start_action(*args, **kwargs) @@ -358,7 +356,8 @@ def run(self, *args, **kwargs): raise finally: self._started = False - get_thread_pool().add(self._asynch_action_loop, None, context) + cb = kwargs.pop("cb", None) + get_thread_pool().add(self._asynch_action_loop, cb, context) def start_action(self, *args, **kwargs): """Start procedure for this action. Default implementation raises @@ -397,7 +396,7 @@ def finish_action(self): """Finishes the action execution. If a finish hook is defined it safely executes it. Otherwise nothing happens""" hooks = self._finish_hooks - for hook, permanent in hooks.items(): + for hook, permanent in list(hooks.items()): try: hook() except: @@ -410,19 +409,19 @@ def finish_action(self): def stop_action(self, *args, **kwargs): """Stop procedure for this action.""" self._stopped = True - for pool_ctrl, elements in self._pool_ctrl_dict.items(): + for pool_ctrl, elements in list(self._pool_ctrl_dict.items()): pool_ctrl.stop_elements(elements) def abort_action(self, *args, **kwargs): """Aborts procedure for this action""" self._aborted = True - for pool_ctrl, elements in self._pool_ctrl_dict.items(): + for pool_ctrl, elements in list(self._pool_ctrl_dict.items()): pool_ctrl.abort_elements(elements) def emergency_break(self): """Tries to execute a stop. If it fails try an abort""" self._stopped = True - for pool_ctrl, elements in self._pool_ctrl_dict.items(): + for pool_ctrl, elements in list(self._pool_ctrl_dict.items()): pool_ctrl.emergency_break(elements) def was_stopped(self): @@ -540,7 +539,7 @@ def _raw_read_ctrl_state_info(self, ret, pool_ctrl): state_infos, error = pool_ctrl.raw_read_axis_states(axes) if error: pool_ctrl.warning("Read state error") - for elem, (state_info, exc_info) in state_infos.items(): + for elem, (state_info, exc_info) in list(state_infos.items()): if exc_info is not None: pool_ctrl.debug("Axis %s error details:", elem.axis, exc_info=exc_info) diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 6a4d9960e7..29f739bad9 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -30,6 +30,7 @@ __docformat__ = 'restructuredtext' +from sardana.sardanadefs import AttrQuality from sardana.sardanaattribute import SardanaAttribute from sardana.sardanabuffer import SardanaBuffer from sardana.pool.poolelement import PoolElement @@ -241,19 +242,24 @@ def read_value(self): :class:`~sardana.sardanavalue.SardanaValue`""" return self.acquisition.read_value()[self] - def put_value(self, value, propagate=1): + def put_value(self, value, quality=AttrQuality.Valid, propagate=1): """Sets a value. :param value: the new value :type value: :class:`~sardana.sardanavalue.SardanaValue` + :param quality: + the new quality + :type quality: + :class:`~sardana.AttrQuality` :param propagate: 0 for not propagating, 1 to propagate, 2 propagate with priority :type propagate: int """ val_attr = self._value + val_attr.set_quality(quality) val_attr.set_value(value, propagate=propagate) return val_attr diff --git a/src/sardana/pool/poolbaseelement.py b/src/sardana/pool/poolbaseelement.py index 794615a1ea..68d3e88813 100644 --- a/src/sardana/pool/poolbaseelement.py +++ b/src/sardana/pool/poolbaseelement.py @@ -239,7 +239,7 @@ def put_status(self, status): # state information # -------------------------------------------------------------------------- - _STD_STATUS = "{name} is {state}\n{ctrl_status}" + _STD_STATUS = "{name} is {state}" def calculate_state_info(self, status_info=None): """Transforms the given state information. This specific base @@ -250,7 +250,8 @@ def calculate_state_info(self, status_info=None): status information. :param status_info: - given status information [default: None, meaning use current state status. + given status information [default: None, meaning use current state + status. :type status_info: tuple :return: a transformed state information :rtype: tuple""" @@ -258,12 +259,17 @@ def calculate_state_info(self, status_info=None): status_info = self._state, self._status state, status = status_info state_str = State[state] - new_status = self._STD_STATUS.format(name=self.name, state=state_str, - ctrl_status=status) + new_status = self._STD_STATUS.format(name=self.name, state=state_str) + if status is not None and len(status) > 0: + new_status += "\n{}".format(status) # append ctrl status return status_info[0], new_status - def set_state_info(self, state_info, propagate=1): - self._set_state_info(state_info, propagate=propagate) + def set_state_info(self, state_info, propagate=1, safe=False): + if safe: + with self: + self._set_state_info(state_info, propagate=propagate) + else: + self._set_state_info(state_info, propagate=propagate) def _set_state_info(self, state_info, propagate=1): state_info = self.calculate_state_info(state_info) @@ -280,15 +286,9 @@ def put_state_info(self, state_info): self.set_state_info(state_info, propagate=0) def _from_ctrl_state_info(self, state_info): - try: - state_str = State.whatis(state_info) - return int(state_info), "{0} is in {1}".format(self.name, state_str) - except KeyError: - pass - state_info, _ = state_info - state, status = state_info[:2] - state = int(state) - return state, status + state_info, _ = state_info # ignoring exc_info + state, status = state_info + return int(state), status # -------------------------------------------------------------------------- # default attribute diff --git a/src/sardana/pool/poolbasegroup.py b/src/sardana/pool/poolbasegroup.py index 07586d81c5..c7f456bc14 100644 --- a/src/sardana/pool/poolbasegroup.py +++ b/src/sardana/pool/poolbasegroup.py @@ -30,12 +30,7 @@ __docformat__ = 'restructuredtext' -try: - from taurus.core.taurusvalidator import AttributeNameValidator as\ - TangoAttributeNameValidator -except ImportError: - # TODO: For Taurus 4 compatibility - from taurus.core.tango.tangovalidator import TangoAttributeNameValidator +from taurus.core.tango.tangovalidator import TangoAttributeNameValidator from sardana import State, ElementType, TYPE_PHYSICAL_ELEMENTS from sardana.pool.poolexternal import PoolExternalObject @@ -75,7 +70,7 @@ def _get_action_cache(self): def _set_action_cache(self, action_cache): physical_elements = self.get_physical_elements() if self._action_cache is not None: - for ctrl_physical_elements in physical_elements.values(): + for ctrl_physical_elements in list(physical_elements.values()): for physical_element in ctrl_physical_elements: action_cache.remove_element(physical_element) @@ -86,7 +81,7 @@ def _fill_action_cache(self, action_cache=None, physical_elements=None): action_cache = self._create_action_cache() if physical_elements is None: physical_elements = self.get_physical_elements() - for _, ctrl_physical_elements in physical_elements.items(): + for _, ctrl_physical_elements in list(physical_elements.items()): for physical_element in ctrl_physical_elements: action_cache.add_element(physical_element) return action_cache @@ -94,7 +89,8 @@ def _fill_action_cache(self, action_cache=None, physical_elements=None): def _calculate_element_state(self, elem, elem_state_info): u_state, u_status = elem_state_info if u_status is None: - u_status = '%s is None' % elem.name + state_str = "None" if u_state is None else State.whatis(u_state) + u_status = '{} is {}'.format(elem.name, state_str) else: u_status = u_status.split("\n", 1)[0] return u_state, u_status @@ -113,7 +109,7 @@ def _calculate_states(self, state_info=None): # lock! si = elem.inspect_state(), elem.inspect_status() state_info[elem] = si - for elem, elem_state_info in state_info.items(): + for elem, elem_state_info in list(state_info.items()): elem_type = elem.get_type() if elem_type == ElementType.External: continue @@ -176,7 +172,7 @@ def _build_elements(self): # in measurement group) if not internal: validator = TangoAttributeNameValidator() - params = validator.getParams(user_element_id) + params = validator.getUriGroups(user_element_id) params['pool'] = self._get_pool() user_element = PoolExternalObject(**params) self.add_user_element(user_element) @@ -257,7 +253,7 @@ def get_physical_elements_iterator(self): :return: an iterator over the physical elements. :rtype: iter<:class:`~sardana.pool.poolelement.PoolElement` >""" - for _, elements in self.get_physical_elements().items(): + for _, elements in list(self.get_physical_elements().items()): for element in elements: yield element @@ -317,7 +313,8 @@ def _find_physical_elements(self, element, physical_elements=None, own_elements.add(element) physical_elements_set.add(element) else: - for ctrl, elements in element.get_physical_elements().items(): + elem_physical_elements = element.get_physical_elements() + for ctrl, elements in list(elem_physical_elements.items()): own_elements = physical_elements.get(ctrl) if own_elements is None: physical_elements[ctrl] = own_elements = set() @@ -357,7 +354,7 @@ def clear_user_elements(self): def stop(self): msg = "" - for ctrl, elements in self.get_physical_elements().items(): + for ctrl, elements in list(self.get_physical_elements().items()): self.debug("Stopping %s %s", ctrl.name, [e.name for e in elements]) try: @@ -383,7 +380,7 @@ def stop(self): def abort(self): msg = "" - for ctrl, elements in self.get_physical_elements().items(): + for ctrl, elements in list(self.get_physical_elements().items()): self.debug("Aborting %s %s", ctrl.name, [e.name for e in elements]) try: @@ -410,7 +407,7 @@ def abort(self): # -------------------------------------------------------------------------- def get_operation(self): - for _, elements in self.get_physical_elements().items(): + for _, elements in list(self.get_physical_elements().items()): for element in elements: op = element.get_operation() if op is not None: diff --git a/src/sardana/pool/poolcontroller.py b/src/sardana/pool/poolcontroller.py index 1371713833..5ac8ab3213 100644 --- a/src/sardana/pool/poolcontroller.py +++ b/src/sardana/pool/poolcontroller.py @@ -33,7 +33,7 @@ import sys import weakref -import StringIO +import io import traceback import functools @@ -71,7 +71,7 @@ def get_ctrl_types(self): raise NotImplementedError def get_ctrl_type_names(self): - return map(ElementType.whatis, self.get_ctrl_types()) + return list(map(ElementType.whatis, self.get_ctrl_types())) def is_online(self): return True @@ -84,7 +84,7 @@ def get_ctrl_error_str(self): err = self._ctrl_error if err is None: return "" - sio = StringIO.StringIO() + sio = io.StringIO() traceback.print_exception(err[0], err[1], err[2], None, sio) s = sio.getvalue() sio.close() @@ -118,9 +118,9 @@ def add_element(self, elem, propagate=1): def remove_element(self, elem, propagate=1): name, axis, eid = elem.get_name(), elem.get_axis(), elem.get_id() - f = self._element_ids.has_key(eid) + f = eid in self._element_ids if not f: - f = self._pending_element_ids.has_key(eid) + f = eid in self._pending_element_ids if not f: raise Exception("element '%s' is not in controller") del self._pending_element_ids[eid] @@ -162,9 +162,9 @@ def rename_element(self, old_name, new_name, propagate=1): elements) def remove_axis(self, axis, propagate=1): - f = self._element_axis.has_key(axis) + f = axis in self._element_axis if not f: - f = self._pending_element_axis.has_key(axis) + f = axis in self._pending_element_axis if not f: raise Exception("element '%s' is not in controller") elem = self._pending_element_axis[axis] @@ -380,7 +380,7 @@ def re_init(self): self._ctrl_info = self._lib_info.get_controller(class_name) self._init() - for elem in elem_axis.values(): + for elem in list(elem_axis.values()): self.add_element(elem, propagate=0) state, status = State.Fault, "" @@ -567,7 +567,7 @@ def raw_read_axis_states(self, axes=None, ctrl_states=None): information for each axis and a boolean telling if an error occured :rtype: dict, bool""" if axes is None: - axes = self._element_axis.keys() + axes = list(self._element_axis.keys()) if ctrl_states is None: ctrl_states = {} @@ -595,6 +595,8 @@ def raw_read_axis_states(self, axes=None, ctrl_states=None): if state_info is None: raise Exception("%s.StateOne(%s(%d)) returns 'None'" % (self.name, element.name, axis)) + if state_info in State: + state_info = (state_info, None) state_info = state_info, None except: exc_info = sys.exc_info() @@ -660,7 +662,7 @@ def raw_read_axis_values(self, axes=None, ctrl_values=None): :return: a map containing the controller value information for each axis :rtype: dict""" if axes is None: - axes = self._element_axis.keys() + axes = list(self._element_axis.keys()) if ctrl_values is None: ctrl_values = {} @@ -744,7 +746,7 @@ def raw_read_axis_value_refs(self, axes=None, ctrl_values=None): :rtype: dict """ if axes is None: - axes = self._element_axis.keys() + axes = list(self._element_axis.keys()) if ctrl_values is None: ctrl_values = {} @@ -830,7 +832,7 @@ def stop_elements(self, elements=None): """ if elements is None: - axes = self.get_element_axis().keys() + axes = list(self.get_element_axis().keys()) else: axes = [e.axis for e in elements] error_axes = self.stop_axes(axes) @@ -916,7 +918,7 @@ def abort_elements(self, elements=None): """ if elements is None: - axes = self.get_element_axis().keys() + axes = list(self.get_element_axis().keys()) else: axes = [e.axis for e in elements] error_axes = self.abort_axes(axes) @@ -972,13 +974,13 @@ def send_to_controller(self, stream): def raw_move(self, axis_pos): ctrl = self.ctrl ctrl.PreStartAll() - for axis, dial_position in axis_pos.items(): + for axis, dial_position in list(axis_pos.items()): ret = ctrl.PreStartOne(axis, dial_position) if not ret: raise Exception("%s.PreStartOne(%d, %f) returns False" % (self.name, axis, dial_position)) - for axis, dial_position in axis_pos.items(): + for axis, dial_position in list(axis_pos.items()): ctrl.StartOne(axis, dial_position) ctrl.StartAll() diff --git a/src/sardana/pool/poolcontrollermanager.py b/src/sardana/pool/poolcontrollermanager.py index aeb7d17ad9..89283b7484 100644 --- a/src/sardana/pool/poolcontrollermanager.py +++ b/src/sardana/pool/poolcontrollermanager.py @@ -37,11 +37,7 @@ import types import inspect -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from taurus.core import ManagerState from taurus.core.util.log import Logger @@ -164,7 +160,7 @@ def setControllerPath(self, controller_path, reload=True): controller_file_names = self._findControllerLibNames() - for mod_name, file_name in controller_file_names.iteritems(): + for mod_name, file_name in controller_file_names.items(): dir_name = os.path.dirname(file_name) path = [dir_name] try: @@ -238,7 +234,7 @@ def getOrCreateControllerLib(self, lib_name, controller_name=None): f_name, code = self.createControllerLib(lib_name), '' else: f_name = controller_lib.get_file_name() - f = file(f_name) + f = open(f_name) code = f.read() f.close() else: @@ -254,7 +250,7 @@ def getOrCreateControllerLib(self, lib_name, controller_name=None): else: _, line_nb = controller.getCode() f_name = controller.getFileName() - f = file(f_name) + f = open(f_name) code = f.read() f.close() @@ -413,7 +409,7 @@ def reloadControllerLib(self, module_name, path=None, reload=True): path.reverse() # if there was previous Controller Lib info remove it - if self._modules.has_key(module_name): + if module_name in self._modules: self._modules.pop(module_name) m, exc_info = None, None @@ -488,7 +484,7 @@ def getControllerLibs(self, filter=None): ret, expr = [], None if filter is not None: expr = re.compile(filter, re.IGNORECASE) - for name, lib in self._modules.iteritems(): + for name, lib in self._modules.items(): if lib.has_errors() or (expr is not None and expr.match(name) is None): continue ret.append(lib) @@ -500,7 +496,7 @@ def getControllers(self, filter=None): return sorted(self._controller_dict.values()) expr = re.compile(filter, re.IGNORECASE) - ret = sorted([kls for n, kls in self._controller_dict.iteritems() + ret = sorted([kls for n, kls in self._controller_dict.items() if not expr.match(n) is None]) return ret @@ -519,12 +515,12 @@ def getControllerMetaClasses(self, controller_names): def getControllerLib(self, name): if os.path.isabs(name): abs_file_name = name - for lib in self._modules.values(): + for lib in list(self._modules.values()): if lib.file_path == abs_file_name: return lib elif name.count(os.path.extsep): file_name = name - for lib in self._modules.values(): + for lib in list(self._modules.values()): if lib.file_name == file_name: return lib module_name = name @@ -546,7 +542,7 @@ def decodeControllerParameters(self, in_par_list): raise RuntimeError('Controller name not specified') controller_name_or_klass = in_par_list[0] controller_class = controller_name_or_klass - if type(controller_class) in types.StringTypes: + if isinstance(controller_class, str): controller_class = self.getControllerClass(controller_class) if controller_class is None: raise UnknownController("Unknown controller %s" % diff --git a/src/sardana/pool/poolcontrollers/DiscretePseudoMotorController.py b/src/sardana/pool/poolcontrollers/DiscretePseudoMotorController.py index 6e3613b96c..5dc2f7cd64 100644 --- a/src/sardana/pool/poolcontrollers/DiscretePseudoMotorController.py +++ b/src/sardana/pool/poolcontrollers/DiscretePseudoMotorController.py @@ -36,11 +36,6 @@ from sardana.pool.controller import PseudoMotorController from sardana.pool.controller import Type, Access, Description -CALIBRATION = 'Calibration' -LABELS = 'Labels' -MSG_API = 'Configuration attribute is in use. Labels and Calibration ' \ - 'attributes are deprecated since version 2.5.0' - class DiscretePseudoMotorController(PseudoMotorController): """ @@ -55,23 +50,7 @@ class DiscretePseudoMotorController(PseudoMotorController): pseudo_motor_roles = ("DiscreteMoveable",) motor_roles = ("ContinuousMoveable",) - axis_attributes = {CALIBRATION: # type hackish until arrays supported - {Type: str, - Description: 'Flatten list of a list of triples and ' - '[min,cal,max]. Deprecated since ' - 'version 2.5.0.', - Access: DataAccess.ReadWrite, - 'fget': 'get%s' % CALIBRATION, - 'fset': 'set%s' % CALIBRATION}, - LABELS: # type hackish until arrays supported - {Type: str, - Description: 'String list with the meaning of each ' - 'discrete position. Deprecated since ' - 'version 2.5.0.', - Access: DataAccess.ReadWrite, - 'fget': 'get%s' % LABELS, - 'fset': 'set%s' % LABELS}, - 'Configuration': + axis_attributes = {'Configuration': # type hackish until encoded attributes supported {Type: str, Description: 'String dictionary mapping the labels' @@ -96,15 +75,9 @@ def GetAxisAttributes(self, axis): return axis_attrs def CalcPseudo(self, axis, physical_pos, curr_pseudo_pos): - if self._configuration is not None: - positions = self._positions_cfg - calibration = self._calibration_cfg - labels = self._labels_cfg - else: - # TODO: Remove when we drop support to Labels and Calibration - positions = self._positions - calibration = self._calibration - labels = self._labels + positions = self._positions_cfg + calibration = self._calibration_cfg + labels = self._labels_cfg llabels = len(labels) lcalibration = len(calibration) @@ -135,16 +108,9 @@ def CalcPseudo(self, axis, physical_pos, curr_pseudo_pos): raise Exception("Bad configuration on axis attributes.") def CalcPhysical(self, axis, pseudo_pos, curr_physical_pos): - - if self._configuration is not None: - positions = self._positions_cfg - calibration = self._calibration_cfg - labels = self._labels_cfg - else: - # TODO: Remove when we drop support to Labels and Calibration - positions = self._positions - calibration = self._calibration - labels = self._labels + positions = self._positions_cfg + calibration = self._calibration_cfg + labels = self._labels_cfg # If Labels is well defined, the write value must be one this struct llabels = len(labels) @@ -176,93 +142,8 @@ def CalcPhysical(self, axis, pseudo_pos, curr_physical_pos): self._log.debug("calibrated_position = %s", calibrated_position) return calibrated_position - # TODO: Remove when we drop support to Labels and Calibration - def getLabels(self, axis): - if self._configuration is not None: - raise ValueError(MSG_API) - - self._log.warning("Labels attribute is deprecated since version " - "2.5.0. Use Configuration attribute instead.") - - # hackish until we support DevVarDoubleArray in extra attrs - labels = self._labels - positions = self._positions - labels_str = "" - for i in range(len(labels)): - labels_str += "%s:%d " % (labels[i], positions[i]) - return labels_str[:-1] # remove the final space - - # TODO: Remove when we drop support to Labels and Calibration - def setLabels(self, axis, value): - if self._configuration is not None: - raise ValueError(MSG_API) - - self._log.warning("Labels attribute is deprecated since version " - "2.5.0. Use Configuration attribute instead.") - - # hackish until we support DevVarStringArray in extra attrs - labels = [] - positions = [] - for pair in value.split(): - l, p = pair.split(':') - labels.append(l) - positions.append(int(p)) - if len(labels) == len(positions): - self._labels = labels - self._positions = positions - else: - raise Exception("Rejecting labels: invalid structure") - - # TODO: Remove when we drop support to Labels and Calibration - def getCalibration(self, axis): - if self._configuration is not None: - raise ValueError(MSG_API) - self._log.warning("Calibration attribute is deprecated since version " - "2.5.0. Use Configuration attribute instead.") - - return json.dumps(self._calibration) - - # TODO: Remove when we drop support to Labels and Calibration - def setCalibration(self, axis, value): - if self._configuration is not None: - raise ValueError(MSG_API) - self._log.warning("Calibration attribute is deprecated since version " - "2.5.0. Use Configuration attribute instead.") - - try: - self._calibration = json.loads(value) - except Exception: - raise Exception("Rejecting calibration: invalid structure") - def getConfiguration(self, axis): - if self._configuration is None: - # TODO: Remove when we drop support to Labels and Calibration - return self._getConfiguration() - else: - return json.dumps(self._configuration) - - # TODO: Remove when we drop support to Labels and Calibration - def _getConfiguration(self): - mapping = dict() - llab = len(self._labels) - lcal = len(self._calibration) - - if llab == 0: - return json.dumps(mapping) - elif lcal > 0 and lcal != llab: - msg = 'Calibration and Labels have different length' - raise RuntimeError(msg) - - for idx, label in enumerate(self._labels): - pos = self._positions[idx] - mapping[label] = {'pos': int(pos)} - if lcal > 0: - minimum, set, maximum = self._calibration[idx] - mapping[label]['set'] = set - mapping[label]['min'] = minimum - mapping[label]['max'] = maximum - - return json.dumps(mapping) + return json.dumps(self._configuration) def setConfiguration(self, axis, value): try: @@ -270,14 +151,14 @@ def setConfiguration(self, axis, value): labels = [] positions = [] calibration = [] - for k, v in mapping.items(): + for k, v in list(mapping.items()): labels.append(k) pos = int(v['pos']) if pos in positions: msg = 'position {0} is already used'.format(pos) raise ValueError(msg) positions.append(pos) - if all([x in v.keys() for x in ['min', 'set', 'max']]): + if all([x in list(v.keys()) for x in ['min', 'set', 'max']]): calibration.append([v['min'], v['set'], v['max']]) self._labels_cfg = labels self._positions_cfg = positions diff --git a/src/sardana/pool/poolcontrollers/DummyCounterTimerController.py b/src/sardana/pool/poolcontrollers/DummyCounterTimerController.py index e49043cbbd..eab5cf1416 100644 --- a/src/sardana/pool/poolcontrollers/DummyCounterTimerController.py +++ b/src/sardana/pool/poolcontrollers/DummyCounterTimerController.py @@ -139,7 +139,7 @@ def StateOne(self, axis): if self._armed: sta = State.Moving status = "Armed" - elif axis in self.counting_channels: + elif axis in self.counting_channels and self.start_time is not None: channel = self.channels[idx] now = time.time() elapsed_time = now - self.start_time @@ -182,10 +182,10 @@ def ReadAll(self): if self._armed: return # still armed - no trigger/gate arrived yet # if in acquisition then calculate the values to return - if self.counting_channels: + if self.counting_channels and self.start_time is not None: now = time.time() elapsed_time = now - self.start_time - for axis, channel in self.read_channels.items(): + for axis, channel in list(self.read_channels.items()): self._updateChannelState(axis, elapsed_time) if channel.is_counting: self._updateChannelValue(axis, elapsed_time) @@ -250,7 +250,7 @@ def _updateChannelValue(self, axis, elapsed_time): def _finish(self, elapsed_time, axis=None): if axis is None: - for axis, channel in self.counting_channels.items(): + for axis, channel in list(self.counting_channels.items()): channel.is_counting = False self._updateChannelValue(axis, elapsed_time) elif axis in self.counting_channels: @@ -263,12 +263,16 @@ def _finish(self, elapsed_time, axis=None): AcqSynch.HardwareGate): self._disconnect_hardware_synchronization() self._armed = False + self.start_time = None def AbortOne(self, axis): if axis not in self.counting_channels: return now = time.time() - elapsed_time = now - self.start_time + if self.start_time is not None: + elapsed_time = now - self.start_time + else: + elapsed_time = 0 self._finish(elapsed_time, axis) def GetCtrlPar(self, par): @@ -339,8 +343,8 @@ def event_received(self, src, type_, value): e.g. start, active passive """ # for the moment only react on first trigger - if type_.name.lower() == "active" and value == 0: + if type_.name.lower() == "start": self._armed = False - for axis, channel in self.counting_channels.iteritems(): + for axis, channel in self.counting_channels.items(): channel.is_counting = True self.start_time = time.time() diff --git a/src/sardana/pool/poolcontrollers/DummyIORController.py b/src/sardana/pool/poolcontrollers/DummyIORController.py index e649bb3406..181d1905ce 100644 --- a/src/sardana/pool/poolcontrollers/DummyIORController.py +++ b/src/sardana/pool/poolcontrollers/DummyIORController.py @@ -35,8 +35,6 @@ class DummyIORController(IORegisterController): MaxDevice = 1024 - predefined_values = "0", "Online", "1", "Offline", "2", "Standby" - def __init__(self, inst, props, *args, **kwargs): IORegisterController.__init__(self, inst, props, *args, **kwargs) self.myvalue = 1 diff --git a/src/sardana/pool/poolcontrollers/DummyMotorController.py b/src/sardana/pool/poolcontrollers/DummyMotorController.py index 2910c00752..ecc71a23bf 100644 --- a/src/sardana/pool/poolcontrollers/DummyMotorController.py +++ b/src/sardana/pool/poolcontrollers/DummyMotorController.py @@ -112,7 +112,7 @@ def setMinVelocity(self, vi): """ Sets the minimum velocity in ms^-1. A.k.a. base rate""" vi = float(vi) if vi < 0: - raise "Minimum velocity must be >= 0" + raise ValueError("Minimum velocity must be >= 0") self.min_vel = vi @@ -132,7 +132,7 @@ def setMaxVelocity(self, vf): """ Sets the maximum velocity in ms^-1.""" vf = float(vf) if vf <= 0: - raise "Maximum velocity must be > 0" + raise ValueError("Maximum velocity must be > 0") self.max_vel = vf @@ -148,11 +148,17 @@ def setMaxVelocity(self, vf): def getMaxVelocity(self): return self.max_vel + def setMaxUserVelocity(self, vel): + self.setMaxVelocity(vel * self.step_per_unit) + + def getMaxUserVelocity(self): + return self.getMaxVelocity() / self.step_per_unit + def setAccelerationTime(self, at): """Sets the time to go from minimum velocity to maximum velocity in seconds""" at = float(at) if at <= 0: - raise "Acceleration time must be > 0" + raise ValueError("Acceleration time must be > 0") self.accel_time = at self.accel = (self.max_vel - self.min_vel) / at @@ -166,7 +172,7 @@ def setDecelerationTime(self, dt): """Sets the time to go from maximum velocity to minimum velocity in seconds""" dt = float(dt) if dt <= 0: - raise "Deceleration time must be > 0" + raise ValueError("Deceleration time must be > 0") self.decel_time = dt self.decel = (self.min_vel - self.max_vel) / dt @@ -180,7 +186,7 @@ def setAcceleration(self, a): """Sets the acceleration in ms^-2""" a = float(a) if a < 0: - raise "Acceleration must be >= 0" + raise ValueError("Acceleration must be >= 0") self.accel = float(a) @@ -195,7 +201,7 @@ def setDeceleration(self, d): """Sets the deceleration in ms^-2""" d = float(d) if d > 0: - raise "Deceleration must be <= 0" + raise ValueError("Deceleration must be <= 0") self.decel = d @@ -472,23 +478,32 @@ def setPower(self, power): self.power = power def info(self): - print "Small movement =", self.small_motion - print "length =", self.dsplmnt - print "position where maximum velocity will be reached =", self.curr_max_vel_pos - print "necessary displacement to reach maximum velocity =", self.curr_dsplmnt_reach_max_vel - print "necessary displacement to stop from maximum velocity =", self.curr_dsplmnt_reach_min_vel - print "maximum velocity possible =", self.curr_max_vel - print "time at top velocity =", self.curr_at_max_vel_time - print "displacement at top velocity =", self.curr_at_max_vel_dsplmnt - print "time to reach maximum velocity =", self.curr_max_vel_time - print "time to reach minimum velocity =", self.curr_min_vel_time - print "time the motion will take =", self.duration - print "instant when maximum velocity should be reached =", self.curr_max_vel_instant - print "instant when should start decelerating =", self.curr_min_vel_instant - print "instant the motion will end", self.final_instant - print "" - print "For long movements (where top vel is possible), necessary displacement to reach maximum velocity =", self.dsplmnt_reach_max_vel - print "For long movements (where top vel is possible), necessary displacement to stop from maximum velocity =", self.dsplmnt_reach_min_vel + print("Small movement =", self.small_motion) + print("length =", self.dsplmnt) + print("position where maximum velocity will be reached =", + self.curr_max_vel_pos) + print("necessary displacement to reach maximum velocity =", + self.curr_dsplmnt_reach_max_vel) + print("necessary displacement to stop from maximum velocity =", + self.curr_dsplmnt_reach_min_vel) + print("maximum velocity possible =", self.curr_max_vel) + print("time at top velocity =", self.curr_at_max_vel_time) + print("displacement at top velocity =", self.curr_at_max_vel_dsplmnt) + print("time to reach maximum velocity =", self.curr_max_vel_time) + print("time to reach minimum velocity =", self.curr_min_vel_time) + print("time the motion will take =", self.duration) + print("instant when maximum velocity should be reached =", + self.curr_max_vel_instant) + print("instant when should start decelerating =", + self.curr_min_vel_instant) + print("instant the motion will end", self.final_instant) + print("") + print("For long movements (where top vel is possible), " + "necessary displacement to reach maximum velocity =", + self.dsplmnt_reach_max_vel) + print("For long movements (where top vel is possible), " + "necessary displacement to stop from maximum velocity =", + self.dsplmnt_reach_min_vel) class BasicDummyMotorController(MotorController): @@ -519,7 +534,7 @@ def AddDevice(self, axis): if self.m[idx] is None: m = Motion() m.setMinVelocity(0) - m.setMaxVelocity(100) + m.setMaxUserVelocity(100) m.setAccelerationTime(2) m.setDecelerationTime(2) m.setCurrentPosition(0) @@ -585,7 +600,7 @@ def StartOne(self, axis, pos): def StartAll(self): #raise Exception("Cannot move on StartAll") t = time.time() - for motion, pos in self.motions.items(): + for motion, pos in list(self.motions.items()): motion.startMotion(motion.getCurrentUserPosition(t), pos, t) def AbortOne(self, axis): @@ -657,7 +672,7 @@ def StartOne(self, axis, pos): self.motions[self.m[idx]] = pos def StartAll(self): - for motion, pos in self.motions.items(): + for motion, pos in list(self.motions.items()): motion.curr_pos = pos def AbortOne(self, axis): @@ -681,7 +696,7 @@ def AddDevice(self, axis): idx = axis - 1 m = self.m[idx] m.setMinVelocity(0) - m.setMaxVelocity(1) + m.setMaxUserVelocity(1) m.setAccelerationTime(0.01) m.setDecelerationTime(0.01) m.setCurrentPosition(0) @@ -755,7 +770,7 @@ def SetAxisPar(self, axis, name, value): elif name == "base_rate": m.setMinVelocity(value) elif name == "velocity": - m.setMaxVelocity(value) + m.setMaxUserVelocity(value) elif name == "step_per_unit": m.setStepPerUnit(value) @@ -770,7 +785,7 @@ def GetAxisPar(self, axis, name): elif name == "base_rate": v = m.getMinVelocity() elif name == "velocity": - v = m.getMaxVelocity() + v = m.getMaxUserVelocity() elif name == "step_per_unit": v = m.getStepPerUnit() return v diff --git a/src/sardana/pool/poolcontrollers/DummyOneDController.py b/src/sardana/pool/poolcontrollers/DummyOneDController.py index 35ad53d039..88f4e38ac5 100644 --- a/src/sardana/pool/poolcontrollers/DummyOneDController.py +++ b/src/sardana/pool/poolcontrollers/DummyOneDController.py @@ -174,7 +174,7 @@ def _updateChannelValue(self, axis, elapsed_time): elif self._synchronization in (AcqSynch.HardwareTrigger, AcqSynch.HardwareGate): if self.integ_time is not None: - n = int(t / self.integ_time) + n = int(t // self.integ_time) cp = 0 if n > self._repetitions: cp = n - self._repetitions @@ -187,7 +187,7 @@ def _updateChannelValue(self, axis, elapsed_time): def _finish(self, elapsed_time, axis=None): if axis is None: - for axis, channel in self.counting_channels.items(): + for axis, channel in list(self.counting_channels.items()): channel.is_counting = False self._updateChannelValue(axis, elapsed_time) else: @@ -212,7 +212,7 @@ def ReadAll(self): if self.counting_channels: now = time.time() elapsed_time = now - self.start_time - for axis, channel in self.read_channels.items(): + for axis, channel in list(self.read_channels.items()): self._updateChannelState(axis, elapsed_time) if channel.is_counting: self._updateChannelValue(axis, elapsed_time) @@ -248,7 +248,7 @@ def StartOne(self, axis, value): def StartAll(self): self.start_time = time.time() - def LoadOne(self, axis, value, repetitions): + def LoadOne(self, axis, value, repetitions, latency_time): idx = axis - 1 if value > 0: self.integ_time = value diff --git a/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py b/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py index caf7841eca..f2795057b6 100644 --- a/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py +++ b/src/sardana/pool/poolcontrollers/DummyTriggerGateController.py @@ -68,8 +68,8 @@ def StateOne(self, axis): status = "Moving" self._log.debug('StateOne(%d): returning (%s, %s)' % (axis, sta, status)) - except Exception, e: - print e + except Exception as e: + print(e) return sta, status def PreStartAll(self): diff --git a/src/sardana/pool/poolcontrollers/DummyTwoDController.py b/src/sardana/pool/poolcontrollers/DummyTwoDController.py index 73b6403d27..fdd543b933 100644 --- a/src/sardana/pool/poolcontrollers/DummyTwoDController.py +++ b/src/sardana/pool/poolcontrollers/DummyTwoDController.py @@ -264,7 +264,7 @@ def StateOne(self, axis): if self._armed: sta = State.Moving status = "Armed" - elif axis in self.counting_channels: + elif axis in self.counting_channels and self.start_time is not None: channel = self.channels[idx] now = time.time() elapsed_time = now - self.start_time @@ -329,7 +329,7 @@ def ReadOne(self, axis): def _finish(self, elapsed_time, axis=None): if axis is None: - for axis, channel in self.counting_channels.items(): + for axis, channel in list(self.counting_channels.items()): channel.is_counting = False self._updateChannelValue(axis, elapsed_time) elif axis in self.counting_channels: @@ -342,12 +342,16 @@ def _finish(self, elapsed_time, axis=None): AcqSynch.HardwareGate): self._disconnect_hardware_synchronization() self._armed = False + self.start_time = None def AbortOne(self, axis): if axis not in self.counting_channels: return now = time.time() - elapsed_time = now - self.start_time + if self.start_time is not None: + elapsed_time = now - self.start_time + else: + elapsed_time = 0 self._finish(elapsed_time, axis) def getAmplitude(self, axis): @@ -434,7 +438,7 @@ def event_received(self, src, type_, value): # for the moment only react on first trigger if type_.name.lower() == "active" and value == 0: self._armed = False - for axis, channel in self.counting_channels.iteritems(): + for axis, channel in self.counting_channels.items(): channel.is_counting = True self.start_time = time.time() @@ -522,7 +526,7 @@ def _updateChannelValue(self, axis, elapsed_time): channel.buffer_values.extend([img] * nb_new_acq) if channel.value_ref_enabled: start = self.start_idx * self.repetitions + channel.acq_idx - for img_idx in xrange(start, start + nb_new_acq): + for img_idx in range(start, start + nb_new_acq): value_ref_pattern = channel.value_ref_pattern scheme, path, dataset_name, msg = generate_ref( value_ref_pattern, img_idx) diff --git a/src/sardana/pool/poolcontrollers/DummyZeroDController.py b/src/sardana/pool/poolcontrollers/DummyZeroDController.py index b65092c33d..86107e115b 100644 --- a/src/sardana/pool/poolcontrollers/DummyZeroDController.py +++ b/src/sardana/pool/poolcontrollers/DummyZeroDController.py @@ -47,7 +47,7 @@ class DummyZeroDController(ZeroDController): def __init__(self, inst, props, *args, **kwargs): ZeroDController.__init__(self, inst, props, *args, **kwargs) - self.channels = [Channel(i + 1) for i in xrange(self.MaxDevice)] + self.channels = [Channel(i + 1) for i in range(self.MaxDevice)] self.read_channels = {} def AddDevice(self, ind): @@ -70,7 +70,7 @@ def PreReadOne(self, ind): self.read_channels[ind] = channel def ReadAll(self): - for channel in self.read_channels.values(): + for channel in list(self.read_channels.values()): self._setChannelValue(channel) def ReadOne(self, ind): diff --git a/src/sardana/pool/poolcontrollers/HklPseudoMotorController.py b/src/sardana/pool/poolcontrollers/HklPseudoMotorController.py index 9de372e631..5fcd6666d5 100644 --- a/src/sardana/pool/poolcontrollers/HklPseudoMotorController.py +++ b/src/sardana/pool/poolcontrollers/HklPseudoMotorController.py @@ -117,8 +117,10 @@ class DiffracBasis(PseudoMotorController): """ The PseudoMotor controller for the diffractometer""" - class_prop = {'DiffractometerType': {Type: str, - Description: 'Type of the diffractometer, e.g. E6C'}, # noqa + ctrl_properties = { + 'DiffractometerType': { + Type: str, + Description: 'Type of the diffractometer, e.g. E6C'}, } ctrl_attributes = {'Crystal': {Type: str, @@ -352,7 +354,7 @@ def __init__(self, inst, props, *args, **kwargs): self.detector = Hkl.Detector.factory_new(Hkl.DetectorType(0)) - for key, factory in Hkl.factories().iteritems(): + for key, factory in Hkl.factories().items(): if key == self.DiffractometerType: self.geometry = factory.create_new_geometry() self.engines = factory.create_new_engine_list() @@ -491,8 +493,8 @@ def CalcAllPhysical(self, pseudo_pos, curr_physical_pos): self.getWavelength() solutions = self._solutions(values, curr_physical_pos) - if self.selected_trajectory > len(solutions.items()): - self.selected_trajectory = len(solutions.items()) - 1 + if self.selected_trajectory > len(list(solutions.items())): + self.selected_trajectory = len(list(solutions.items())) - 1 for i, item in enumerate(solutions.items()): if i == self.selected_trajectory: angles = item.geometry_get().axis_values_get(USER) @@ -586,7 +588,7 @@ def getHKLModeList(self): # something special with the hkl one ???  neverthless I would # be possible to create a self.engines instead of recomputing # it all the time. - for key, factory in Hkl.factories().iteritems(): + for key, factory in Hkl.factories().items(): if key == self.DiffractometerType: new_engines = factory.create_new_engine_list() new_engines.init(self.geometry, self.detector, self.sample) @@ -788,7 +790,7 @@ def setComputeTrajectoriesSim(self, values): curr_physical_pos = self.geometry.axis_values_get(USER) solutions = self._solutions(values, curr_physical_pos) self.trajectorylist = [item.geometry_get().axis_values_get(USER) - for item in solutions.items()] + for item in list(solutions.items())] self.lastpseudos = tuple(values) def getTrajectoryList(self): diff --git a/src/sardana/pool/poolcontrollers/IoverI0.py b/src/sardana/pool/poolcontrollers/IoverI0.py index ee262017f6..9103e6eb9e 100644 --- a/src/sardana/pool/poolcontrollers/IoverI0.py +++ b/src/sardana/pool/poolcontrollers/IoverI0.py @@ -44,7 +44,7 @@ class IoverI0(PseudoCounterController): def Calc(self, axis, counter_values): i, i0 = counter_values try: - i = float(i / i0) + i = i / i0 except ZeroDivisionError: pass return i diff --git a/src/sardana/pool/poolcontrollers/Slit.py b/src/sardana/pool/poolcontrollers/Slit.py index 04e0671935..1593c0afc4 100644 --- a/src/sardana/pool/poolcontrollers/Slit.py +++ b/src/sardana/pool/poolcontrollers/Slit.py @@ -59,7 +59,7 @@ def __init__(self, inst, props, *args, **kwargs): self._example = {} def CalcPhysical(self, index, pseudo_pos, curr_physical_pos): - half_gap = pseudo_pos[0] / 2.0 + half_gap = pseudo_pos[0] / 2 if index == 1: ret = self.sign * (pseudo_pos[1] + half_gap) else: @@ -73,7 +73,7 @@ def CalcPseudo(self, index, physical_pos, curr_pseudo_pos): if index == 1: ret = self.sign * gap else: - ret = self.sign * (physical_pos[0] - gap / 2.0) + ret = self.sign * (physical_pos[0] - gap / 2) return ret def CalcAllPseudo(self, physical_pos, curr_pseudo_pos): @@ -81,7 +81,7 @@ def CalcAllPseudo(self, physical_pos, curr_pseudo_pos): pseudo motor system from the positions of the physical motors.""" gap = physical_pos[1] + physical_pos[0] return (self.sign * gap, - self.sign * (physical_pos[0] - gap / 2.0)) + self.sign * (physical_pos[0] - gap / 2)) # def CalcAllPhysical(self, pseudo_pos, curr_physical_pos): # """Calculates the positions of all motors that belong to the pseudo diff --git a/src/sardana/pool/poolcontrollers/TangoController.py b/src/sardana/pool/poolcontrollers/TangoController.py deleted file mode 100644 index f1dee4ac5a..0000000000 --- a/src/sardana/pool/poolcontrollers/TangoController.py +++ /dev/null @@ -1,209 +0,0 @@ -############################################################################## -## -# This file is part of Sardana -## -# http://www.sardana-controls.org/ -## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain -## -# Sardana is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -## -# Sardana is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -## -# You should have received a copy of the GNU Lesser General Public License -# along with Sardana. If not, see . -## -############################################################################## - -import math - -import PyTango - -from taurus.core.util.containers import CaselessDict - -from sardana import State, DataAccess -from sardana.pool.controller import MotorController, CounterTimerController, \ - ZeroDController, \ - Type, Access, Description, DefaultValue - -TangoAttribute = "TangoAttribute" -Formula = "Formula" - - -class ReadTangoAttributes(object): - """ Generic class that has as many devices as the user wants. - Each device has a tango attribute and a formula and the 'hardware' tango calls - are optimized in the sense that only one call per tango device is issued. - """ - axis_attributes = { - TangoAttribute: {Type: str, Access: DataAccess.ReadWrite, - Description: 'Attribute to read (e.g. a/b/c/attr)'}, - Formula: {Type: str, Access: DataAccess.ReadWrite, - DefaultValue: "VALUE", - Description: 'The Formula to get the desired value.\n' - 'e.g. "math.sqrt(VALUE)"'}, - } - - def __init__(self): - #: dict - self._pending = {} - - #: dict>> - self._devices = CaselessDict() - - #: dict, str, DeviceProxy>> - self._axis_tango_attributes = {} - - #: dict> - self._axis_formulas = {} - - def add_device(self, axis): - self._pending[ - axis] = "No tango attribute associated to this device yet" - self._axis_formulas[axis] = self.axis_attribute[Formula][DefaultValue] - - def delete_device(self, axis): - if axis in self._pending: - del self._pending[axis] - else: - del self._axis_tango_attributes[axis] - del self._axis_formulas[axis] - - def state_one(self, axis): - pending_info = self._pending.get(axis) - if pending_info is not None: - return State.Fault, pending_info - return State.On, 'Always ON, just reading tango attribute' - - def pre_read_all(self): - self._devices_read = {} - - def pre_read_one(self, axis): - attr_name, dev = self._axis_tango_attributes[axis][1:] - dev_attrs = self._devices_read.get(dev) - if dev_attrs is None: - self._ - self._devices_read[dev] = dev_attrs = [] - dev_attrs.append(attr_name) - - def read_all(self): - pass - - def read_one(self, axis): - pass - - def get_extra_attribute_par(self, axis, name): - if name == TangoAttribute: - return self._axis_tango_attributes[axis][0] - elif name == Formula: - return self._axis_formulas[axis] - - def set_extra_attribute_par(self, axis, name, value): - if name == TangoAttribute: - value = value.lower() - self._axis_tango_attributes[axis] = data = value, None, None - try: - dev_name, attr_name = value.rsplit("/", 1) - data[1] = attr_name - except: - self._pending[axis] = "invalid device name " + value - raise Exception(self._pending[axis]) - dev_info = self._devices.get(dev_name) - if dev_info is None: - try: - proxy = PyTango.DeviceProxy(dev_name) - except PyTango.DevFailed, df: - if len(df): - self._pending[axis] = df[0].reason + ": " + df[0].desc - else: - self._pending[ - axis] = "Unknwon PyTango Error: " + str(df) - raise - self._devices[dev_name] = dev_info = proxy, [] - data[2] = dev_info[0] - dev_info[1].append(attr_name) - - elif name == Formula: - self._axis_formulas[axis] = value - - -class TangoCounterTimerController(ReadTangoAttributes, CounterTimerController): - """This controller offers as many channels as the user wants. - Each channel has two _MUST_HAVE_ extra attributes: - +) TangoAttribute - Tango attribute to retrieve the value of the counter - +) Formula - Formula to evaluate using 'VALUE' as the tango attribute value - As examples you could have: - ch1.TangoExtraAttribute = 'my/tango/device/attribute1' - ch1.Formula = '-1 * VALUE' - ch2.TangoExtraAttribute = 'my/tango/device/attribute2' - ch2.Formula = 'math.sqrt(VALUE)' - ch3.TangoExtraAttribute = 'my_other/tango/device/attribute1' - ch3.Formula = 'math.cos(VALUE)' - - ..warning:: As pointed in sardana-org/sardana#722 this controller is - buggy. A working version of this and other Tango controllers can be found - in the third-party repository: http://sf.net/p/sardana/controllers.git. - As part of the sardana-org/sardana#181 this controller will be fixed or - removed from Sardana. - """ - - gender = "" - model = "" - organization = "Sardana team" - - MaxDevice = 1024 - - def __init__(self, inst, props, *args, **kwargs): - ReadTangoAttributes.__init__(self) - CounterTimerController.__init__(self, inst, props, *args, **kwargs) - - def AddDevice(self, axis): - self.add_device(axis) - - def DeleteDevice(self, axis): - self.delete_device(axis) - - def StateOne(self, axis): - return self.state_one(axis) - - def PreReadAll(self): - self.pre_read_all() - - def PreReadOne(self, axis): - self.pre_read_one(axis) - - def ReadAll(self): - self.read_all() - - def ReadOne(self, axis): - return self.read_one(axis) - - def GetExtraAttributePar(self, axis, name): - return self.get_extra_attribute_par(axis, name) - - def SetExtraAttributePar(self, axis, name, value): - self.set_extra_attribute_par(axis, name, value) - - def SendToCtrl(self, in_data): - return "" - - def AbortOne(self, axis): - pass - - def PreStartAllCT(self): - pass - - def StartOneCT(self, axis): - pass - - def StartAllCT(self): - pass - - def LoadOne(self, axis, value): - pass diff --git a/src/sardana/pool/poolcontrollers/TaurusController.py b/src/sardana/pool/poolcontrollers/TaurusController.py index 6186e058b5..15f11c1431 100644 --- a/src/sardana/pool/poolcontrollers/TaurusController.py +++ b/src/sardana/pool/poolcontrollers/TaurusController.py @@ -48,7 +48,7 @@ def AddDevice(self, axis): def DeleteDevice(self, axis): self._axes_taurus_attr.pop(axis) - def LoadOne(self, axis, value): + def LoadOne(self, axis, value, repetitions, latency): pass def StateOne(self, axis): diff --git a/src/sardana/pool/poolcontrollers/test/__init__.py b/src/sardana/pool/poolcontrollers/test/__init__.py index f7a77b2d69..4b40b38c84 100644 --- a/src/sardana/pool/poolcontrollers/test/__init__.py +++ b/src/sardana/pool/poolcontrollers/test/__init__.py @@ -1 +1 @@ -from base import * +from .base import * # noqa diff --git a/src/sardana/pool/poolcontrollers/test/base.py b/src/sardana/pool/poolcontrollers/test/base.py index 6fe43e1422..03b0d21ba7 100644 --- a/src/sardana/pool/poolcontrollers/test/base.py +++ b/src/sardana/pool/poolcontrollers/test/base.py @@ -22,14 +22,12 @@ # along with Sardana. If not, see . ## ############################################################################## -__all__ = ['BaseControllerTestCase', 'TriggerGateControllerTestCase', - 'PositionGenerator', 'TriggerGateReceiver'] import time import threading import numpy -from taurus.external import unittest +import unittest from sardana import State from sardana.pool.poolcontrollers.DummyMotorController import Motion @@ -37,6 +35,8 @@ from sardana.sardanaattribute import SardanaAttribute from taurus.core.util.log import Logger +__all__ = ['BaseControllerTestCase', 'TriggerGateControllerTestCase', + 'PositionGenerator', 'TriggerGateReceiver'] class BaseControllerTestCase(object): """ Base test case for unit testing arbitrary controllers. @@ -90,7 +90,7 @@ def stateOne(self, expected_state=State.On): def start_action(self, configuration): """ This method set the axis parameters and pre start the axis. """ - for key, value in configuration.items(): + for key, value in list(configuration.items()): self.axisPar(key, value) self.ctrl.SynchOne(configuration) @@ -240,7 +240,7 @@ def __init__(self): self.passive_events = {} def getCount(self): - count = len(self.passive_events.keys()) + count = len(list(self.passive_events.keys())) return count count = property(getCount) @@ -298,7 +298,7 @@ def calc_cycletocycle(self): i += 1 if len(periods) > 0: periods_array = numpy.array(periods) - print periods_array + print(periods_array) c2c = numpy.diff(periods_array) mean_c2c = c2c.mean() std_c2c = c2c.std() diff --git a/src/sardana/pool/poolcontrollers/test/test_DummyTriggerGateController.py b/src/sardana/pool/poolcontrollers/test/test_DummyTriggerGateController.py index bb54e75288..3b7c0f908e 100644 --- a/src/sardana/pool/poolcontrollers/test/test_DummyTriggerGateController.py +++ b/src/sardana/pool/poolcontrollers/test/test_DummyTriggerGateController.py @@ -1,5 +1,5 @@ from taurus.test.base import insertTest -from taurus.external import unittest +import unittest from sardana.pool.poolsynchronization import PoolSynchronization from sardana.pool.pooldefs import SynchDomain, SynchParam @@ -8,19 +8,19 @@ createPoolTriggerGate, dummyPoolTGCtrlConf01, dummyTriggerGateConf01, \ createControllerConfiguration -synchronization1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, - SynchParam.Active: {SynchDomain.Time: .03}, - SynchParam.Total: {SynchDomain.Time: .1}, - SynchParam.Repeats: 0}] +synch_description1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: .03}, + SynchParam.Total: {SynchDomain.Time: .1}, + SynchParam.Repeats: 0}] -synchronization2 = [{SynchParam.Delay: {SynchDomain.Time: 0}, - SynchParam.Active: {SynchDomain.Time: .01}, - SynchParam.Total: {SynchDomain.Time: .02}, - SynchParam.Repeats: 10}] +synch_description2 = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: .01}, + SynchParam.Total: {SynchDomain.Time: .02}, + SynchParam.Repeats: 10}] -@insertTest(helper_name='generation', synchronization=synchronization1) -@insertTest(helper_name='generation', synchronization=synchronization2) +@insertTest(helper_name='generation', synch_description=synch_description1) +@insertTest(helper_name='generation', synch_description=synch_description2) class PoolDummyTriggerGateTestCase(unittest.TestCase): """Parameterizable integration test of the PoolSynchronization action and the DummTriggerGateController. @@ -49,10 +49,10 @@ def setUp(self): self.tg_action = PoolSynchronization(self.dummy_tg) self.tg_action.add_element(self.dummy_tg) - def generation(self, synchronization): + def generation(self, synch_description): """Verify that the created PoolTGAction start_action starts correctly the involved controller.""" - args = ([self.ctrl_conf], synchronization) + args = ([self.ctrl_conf], synch_description) self.tg_action.start_action(*args) self.tg_action.action_loop() # TODO: add asserts applicable to a dummy controller e.g. listen to diff --git a/src/sardana/pool/pooldefs.py b/src/sardana/pool/pooldefs.py index d4f5efe995..e454d41063 100644 --- a/src/sardana/pool/pooldefs.py +++ b/src/sardana/pool/pooldefs.py @@ -31,7 +31,7 @@ __docformat__ = 'restructuredtext' from operator import __getitem__ -from taurus.external.enum import IntEnum +from enum import IntEnum from taurus.core.util.enumeration import Enumeration from sardana.taurus.core.tango.sardana import AcqTriggerType, AcqMode @@ -89,7 +89,7 @@ class SynchDomain(SynchEnum): class SynchParam(SynchEnum): - """Enumeration of synchronization's group parameters. + """Enumeration of synchronization description group parameters. - Delay - initial delay (relative to the synchronization start) - Total - total interval diff --git a/src/sardana/pool/poolelement.py b/src/sardana/pool/poolelement.py index ebbd8b0de8..2574c9fe4a 100644 --- a/src/sardana/pool/poolelement.py +++ b/src/sardana/pool/poolelement.py @@ -46,6 +46,7 @@ def __init__(self, **kwargs): self._ctrl = weakref.ref(ctrl) self._axis = kwargs.pop('axis') self._ctrl_id = ctrl.get_id() + self._deleted = False try: instrument = kwargs.pop('instrument') self.set_instrument(instrument) @@ -74,6 +75,12 @@ def get_controller_id(self): def get_axis(self): return self._axis + def is_deleted(self): + return self._deleted + + def set_deleted(self, deleted): + self._deleted = deleted + def set_action_cache(self, action_cache): self._action_cache = action_cache action_cache.add_element(self) @@ -143,3 +150,5 @@ def set_extra_par(self, name, value): controller_id = property(get_controller_id, doc="element controller id") instrument = property(get_instrument, set_instrument, doc="element instrument") + deleted = property(is_deleted, set_deleted, + doc="element is deleted from pool (experimental API)") diff --git a/src/sardana/pool/poolextension.py b/src/sardana/pool/poolextension.py index a9e78ed9eb..739962c8b2 100644 --- a/src/sardana/pool/poolextension.py +++ b/src/sardana/pool/poolextension.py @@ -80,7 +80,7 @@ def translate_ctrl_value(value): if isinstance(value, SardanaValue): return value - for _, translator in __CTRL_VALUE_TRANSLATORS.items(): + for _, translator in list(__CTRL_VALUE_TRANSLATORS.items()): try: return translator(value) except CannotTranslateException: diff --git a/src/sardana/pool/poolexternal.py b/src/sardana/pool/poolexternal.py index 95d01ff4b5..38113f0003 100644 --- a/src/sardana/pool/poolexternal.py +++ b/src/sardana/pool/poolexternal.py @@ -55,10 +55,10 @@ class PoolTangoObject(PoolBaseExternalObject): def __init__(self, **kwargs): scheme = kwargs.pop('scheme', 'tango') - attribute_name = kwargs.pop('attributename') + attribute_name = kwargs.pop('_shortattrname') host, port = kwargs.pop('host', None), kwargs.pop('port', None) - devalias = kwargs.pop('devalias', None) - device_name = kwargs.pop('devicename', None) + devalias = kwargs.pop('_devalias', None) + device_name = kwargs.pop('devname', None) if host is None: db = PyTango.Database() host, port = db.get_db_host(), db.get_db_port() @@ -80,7 +80,8 @@ def __init__(self, **kwargs): self._attribute_name = attribute_name self._config = None self._device = None - kwargs['name'] = attribute_name + # TODO evaluate to use alias instead of device_name + kwargs['name'] = '{0}/{1}'.format(device_name, attribute_name) kwargs['full_name'] = full_name PoolBaseExternalObject.__init__(self, **kwargs) diff --git a/src/sardana/pool/poolgroupelement.py b/src/sardana/pool/poolgroupelement.py index cb76b84533..173e389822 100644 --- a/src/sardana/pool/poolgroupelement.py +++ b/src/sardana/pool/poolgroupelement.py @@ -46,7 +46,7 @@ def serialize(self, *args, **kwargs): kwargs = PoolBaseElement.serialize(self, *args, **kwargs) elements = [elem.name for elem in self.get_user_elements()] physical_elements = [] - for elem_list in self.get_physical_elements().values(): + for elem_list in list(self.get_physical_elements().values()): for elem in elem_list: physical_elements.append(elem.name) kwargs['elements'] = elements @@ -66,7 +66,7 @@ def set_action_cache(self, action_cache): def read_state_info(self): state_info = {} ctrl_state_info = self.get_action_cache().read_state_info(serial=True) - for elem, ctrl_elem_state_info in ctrl_state_info.items(): + for elem, ctrl_elem_state_info in list(ctrl_state_info.items()): elem_state_info = elem._from_ctrl_state_info(ctrl_elem_state_info) elem.put_state_info(elem_state_info) state = elem.get_state(cache=True, propagate=0) diff --git a/src/sardana/pool/poolinstrument.py b/src/sardana/pool/poolinstrument.py index 6741e59fe6..56a0d1944b 100644 --- a/src/sardana/pool/poolinstrument.py +++ b/src/sardana/pool/poolinstrument.py @@ -71,7 +71,7 @@ def remove_instrument(self, instrument): del self._child_instruments[instrument.id] def get_instruments(self): - return self._child_instruments.values() + return list(self._child_instruments.values()) def set_parent_instrument(self, instrument): if instrument: @@ -93,7 +93,7 @@ def remove_element(self, element): del self._elements[element.id] def get_elements(self): - return [e() for e in self._elements.values()] + return [e() for e in list(self._elements.values())] def has_instruments(self): return len(self._child_instruments) > 0 diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 15cf983a27..6776a6f045 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -35,12 +35,8 @@ import threading import weakref -try: - from taurus.core.taurusvalidator import AttributeNameValidator as\ - TangoAttributeNameValidator -except ImportError: - # TODO: For Taurus 4 compatibility - from taurus.core.tango.tangovalidator import TangoAttributeNameValidator + +from taurus.core.tango.tangovalidator import TangoAttributeNameValidator from sardana import State, ElementType, TYPE_EXP_CHANNEL_ELEMENTS from sardana.sardanaevent import EventType @@ -49,7 +45,7 @@ from sardana.pool.poolgroupelement import PoolGroupElement from sardana.pool.poolacquisition import PoolAcquisition -from sardana.pool.poolsynchronization import SynchronizationDescription +from sardana.pool.poolsynchronization import SynchDescription from sardana.pool.poolexternal import PoolExternalObject from sardana.taurus.core.tango.sardana import PlotType, Normalization @@ -439,10 +435,7 @@ def __init__(self, parent=None): self._parent = None if parent is not None: self._parent = weakref.proxy(parent) - self._config = None - self._use_fqdn = True - # Structure to store the controllers and their channels self._timerable_ctrls = {} self._zerod_ctrls = [] @@ -458,6 +451,9 @@ def __init__(self, parent=None): self._channel_acq_synch = {} self._ctrl_acq_synch = {} self.changed = False + # provide back. compatibility for value_ref_{enabled,pattern} + # config parameters created with Sardana < 3. + self._value_ref_compat = False def get_acq_synch_by_channel(self, channel): """Return acquisition synchronization configured for this element. @@ -518,7 +514,7 @@ def get_timerable_ctrls(self, acq_synch=None, enabled=None): """ timerable_ctrls = [] if acq_synch is None: - for ctrls in self._timerable_ctrls.values(): + for ctrls in list(self._timerable_ctrls.values()): timerable_ctrls += ctrls elif isinstance(acq_synch, list): acq_synch_list = acq_synch @@ -601,8 +597,21 @@ def get_configuration_for_user(self): """Return measurement configuration serializable data structure.""" return self._user_config - def set_configuration_from_user(self, cfg, to_fqdn=True): - """Load measurement configuration from serializable data structure.""" + def set_configuration_from_user(self, cfg): + """Set measurement configuration from serializable data structure. + + Setting of the configuration includes the validation process. Setting + of invalid configuration raises an exception hence it is not necessary + that the client application does the validation. + + The configuration parameters for given channels/controllers may differ + depending on their types e.g. 0D channel does not support timer + parameter while C/T does. + + .. todo:: + Raise exceptions when setting _Synchronization_ parameter for + external channels, 0D and PSeudoCounters. + """ pool = self._parent.pool @@ -635,7 +644,7 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): user_config['label'] = label user_config['description'] = description - for ctrl_name, ctrl_data in cfg['controllers'].items(): + for ctrl_name, ctrl_data in list(cfg['controllers'].items()): # backwards compatibility for measurement groups created before # implementing feature-372: # https://sourceforge.net/p/sardana/tickets/372/ @@ -649,68 +658,67 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): if ch_count == 0: continue - external = ctrl_name.startswith('__') + external = ctrl_name in ['__tango__'] + if external: ctrl = ctrl_name else: - if to_fqdn: - ctrl_name = _to_fqdn(ctrl_name, logger=self._parent) ctrl = pool.get_element_by_full_name(ctrl_name) assert ctrl.get_type() == ElementType.Controller user_config['controllers'][ctrl_name] = user_config_ctrl = {} ctrl_conf = {} - - synchronizer = ctrl_data.get('synchronizer', 'software') conf_synch = None - if synchronizer is None or synchronizer == 'software': - ctrl_conf['synchronizer'] = 'software' - user_config_ctrl['synchronizer'] = 'software' + + # The external controllers should not have synchronizer + + if external: + if 'synchronizer' in ctrl_data: + raise ValueError('External controller does not allow ' + 'to have synchronizer') + if 'monitor' in ctrl_data: + raise ValueError('External controller does not allow ' + 'to have monitor') + if 'timer' in ctrl_data: + raise ValueError('External controller does not allow ' + 'to have timer') else: - if to_fqdn: - synchronizer = _to_fqdn(synchronizer, - logger=self._parent) - - user_config_ctrl['synchronizer'] = synchronizer - pool_synch = pool.get_element_by_full_name(synchronizer) - pool_synch_ctrl = pool_synch.controller - conf_synch_ctrl = None - conf_synch = None - for conf_ctrl_created in synch_ctrls: - if pool_synch_ctrl == conf_ctrl_created.element: - conf_synch_ctrl = conf_ctrl_created - for conf_synch_created in \ - conf_ctrl_created.get_channels(): - if pool_synch == conf_synch_created.element: - conf_synch = conf_synch_created - break - break - - if conf_synch_ctrl is None: - conf_synch_ctrl = ControllerConfiguration(pool_synch_ctrl) - synch_ctrls.append(conf_synch_ctrl) - - if conf_synch is None: - conf_synch = SynchronizerConfiguration(pool_synch) - conf_synch_ctrl.add_channel(conf_synch) - - ctrl_conf['synchronizer'] = conf_synch - - try: - synchronization = ctrl_data['synchronization'] - except KeyError: - # backwards compatibility for configurations before SEP6 + synchronizer = ctrl_data.get('synchronizer', 'software') + if synchronizer is None or synchronizer == 'software': + ctrl_conf['synchronizer'] = 'software' + user_config_ctrl['synchronizer'] = 'software' + else: + user_config_ctrl['synchronizer'] = synchronizer + pool_synch = pool.get_element_by_full_name(synchronizer) + pool_synch_ctrl = pool_synch.controller + conf_synch_ctrl = None + conf_synch = None + for conf_ctrl_created in synch_ctrls: + if pool_synch_ctrl == conf_ctrl_created.element: + conf_synch_ctrl = conf_ctrl_created + for conf_synch_created in \ + conf_ctrl_created.get_channels(): + if pool_synch == conf_synch_created.element: + conf_synch = conf_synch_created + break + break + + if conf_synch_ctrl is None: + conf_synch_ctrl = \ + ControllerConfiguration(pool_synch_ctrl) + synch_ctrls.append(conf_synch_ctrl) + + if conf_synch is None: + conf_synch = SynchronizerConfiguration(pool_synch) + conf_synch_ctrl.add_channel(conf_synch) + + ctrl_conf['synchronizer'] = conf_synch try: - synchronization = ctrl_data['trigger_type'] - msg = ("trigger_type configuration parameter is deprecated" - " in favor of synchronization. Re-apply " - "configuration in order to upgrade.") - self._parent.warning(msg) + synchronization = ctrl_data['synchronization'] except KeyError: synchronization = AcqSynchType.Trigger - - ctrl_conf['synchronization'] = synchronization - user_config_ctrl['synchronization'] = synchronization + ctrl_conf['synchronization'] = synchronization + user_config_ctrl['synchronization'] = synchronization acq_synch = None if external: @@ -721,12 +729,8 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): synchronization) ctrl_acq_synch[ctrl] = acq_synch timer = ctrl_data.get("timer") - if timer is not None and to_fqdn: - timer = _to_fqdn(timer, self._parent) ctrl_conf["timer"] = timer monitor = ctrl_data.get("monitor") - if monitor is not None and to_fqdn: - monitor = _to_fqdn(monitor, self._parent) ctrl_conf["monitor"] = monitor ctrl_item = TimerableControllerConfiguration(ctrl, ctrl_conf) else: @@ -735,17 +739,14 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): ctrl_enabled = False if 'channels' in ctrl_data: user_config_ctrl['channels'] = user_config_channel = {} - for ch_name, ch_data in ctrl_data['channels'].items(): + for ch_name, ch_data in list(ctrl_data['channels'].items()): if external: validator = TangoAttributeNameValidator() full_name = ch_data.get('full_name', ch_name) - params = validator.getParams(full_name) + params = validator.getUriGroups(full_name) params['pool'] = pool channel = PoolExternalObject(**params) else: - if to_fqdn: - ch_name = _to_fqdn(ch_name, logger=self._parent) - ch_data['full_name'] = ch_name channel = pool.get_element_by_full_name(ch_name) ch_data = self._fill_channel_data(channel, ch_data) user_config_channel[ch_name] = ch_data @@ -772,17 +773,11 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): msg_error = '' if ctrl_item.timer is None: timer_name = ctrl_data['timer'] - if to_fqdn: - timer_name = _to_fqdn(timer_name, - logger=self._parent) ch_timer = pool.get_element_by_full_name(timer_name) msg_error += 'Channel {0} is not present but used as ' \ 'timer. '.format(ch_timer.name) if ctrl_item.monitor is None: monitor_name = ctrl_data['monitor'] - if to_fqdn: - monitor_name = _to_fqdn(monitor_name, - logger=self._parent) ch_monitor = pool.get_element_by_full_name(monitor_name) msg_error += 'Channel {0} is not present but used as ' \ 'monitor.'.format(ch_monitor.name) @@ -823,7 +818,7 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): # Skip controllers disabled pass elif acq_synch in (AcqSynch.SoftwareTrigger, - AcqSynch.SoftwareGate): + AcqSynch.SoftwareGate): if ctrl_item.timer.index < master_timer_idx_sw: master_timer_sw = ctrl_item.timer master_timer_idx_sw = ctrl_item.timer.index @@ -854,8 +849,6 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): else: # Measurement Group with all channel synchronized by hardware mnt_grp_timer = cfg.get('timer') if mnt_grp_timer: - if to_fqdn: - mnt_grp_timer = _to_fqdn(mnt_grp_timer, self._parent) user_config['timer'] = mnt_grp_timer else: # for backwards compatibility use a random monitor @@ -868,8 +861,6 @@ def set_configuration_from_user(self, cfg, to_fqdn=True): else: # Measurement Group with all channel synchronized by hardware mnt_grp_monitor = cfg.get('monitor') if mnt_grp_monitor: - if to_fqdn: - mnt_grp_monitor = _to_fqdn(mnt_grp_monitor, self._parent) user_config['monitor'] = mnt_grp_monitor else: # for backwards compatibility use a random monitor @@ -927,6 +918,22 @@ def _fill_channel_data(self, channel, channel_data): if ctype != ElementType.External and channel.is_referable(): value_ref_enabled = channel_data.get('value_ref_enabled', False) channel_data['value_ref_enabled'] = value_ref_enabled + value_ref_pattern = channel_data.get('value_ref_pattern', '') + channel_data['value_ref_pattern'] = value_ref_pattern + elif 'value_ref_enabled' in channel_data or 'value_ref_pattern' in \ + channel_data: + if self._value_ref_compat: + msg = 'value_ref_pattern/value_ref_enabled is deprecated ' \ + 'for non-referable channels since 3.0.3. Re-apply ' \ + 'configuration in order to upgrade.' + self._parent.warning(msg) + channel_data.pop('value_ref_enabled') + channel_data.pop('value_ref_pattern') + else: + msg = ('The channel {} is not referable. You can not set ' + 'the enabled and/or the pattern parameters.').format( + name) + raise ValueError(msg) # Definitively should be initialized by measurement group # index MUST be here already (asserting this in the following line) channel_data['index'] = channel_data['index'] @@ -970,7 +977,7 @@ def __init__(self, **kwargs): # by default software synchronizer initial domain is set to Position self._sw_synch_initial_domain = SynchDomain.Position - self._synchronization = SynchronizationDescription() + self._synch_description = SynchDescription() kwargs['elem_type'] = ElementType.MeasurementGroup PoolGroupElement.__init__(self, **kwargs) @@ -989,13 +996,15 @@ def _calculate_states(self, state_info=None): # check if software synchronizer is occupied synch_soft = self.acquisition._synch._synch_soft acq_sw = self.acquisition._sw_acq + acq_sw_start = self.acquisition._sw_start_acq acq_0d = self.acquisition._0d_acq - if state in (State.On, State.Unknown) \ - and (synch_soft.is_started() or - acq_sw._is_started() or - acq_0d._is_started()): + if (state in (State.On, State.Unknown) + and (synch_soft.is_started() + or acq_sw._is_started() + or acq_sw_start._is_started() + or acq_0d._is_started())): state = State.Moving - status += "/nSoftware synchronization is in progress" + status += "\nSoftware synchronization is in progress" return state, status def on_element_changed(self, evt_src, evt_type, evt_value): @@ -1024,6 +1033,22 @@ def add_user_element(self, element, index=None): if element.get_type() is ElementType.TriggerGate: return return PoolGroupElement.add_user_element(self, element, index) + + def rename_element(self, old_name, new_name, propagate=1): + """Rename element in the controller. + + :param old_name: old name of the element + :type old_name: :obj:`str` + :param new_name: new name of the element + :type new_name: :obj:`str` + :param propagate: 0 for not propagating, 1 to propagate, + 2 propagate with priority + :type propagate: :obj:`int` + """ + self._config['label'] = new_name + self.fire_event(EventType("configuration", priority=propagate), + self._config) + # ------------------------------------------------------------------------- # configuration # ------------------------------------------------------------------------- @@ -1038,16 +1063,16 @@ def configuration(self): return self._config # TODO: Check if it needed - def set_configuration(self, config=None, propagate=1, to_fqdn=True): - self._config._use_fqdn = to_fqdn + def set_configuration(self, config=None, propagate=1): self._config.configuration = config self._config_dirty = True if not propagate: return - self.fire_event(EventType("configuration", priority=propagate), config) + self.fire_event(EventType("configuration", priority=propagate), + config) - def set_configuration_from_user(self, cfg, propagate=1, to_fqdn=True): - self._config.set_configuration_from_user(cfg, to_fqdn) + def set_configuration_from_user(self, cfg, propagate=1): + self._config.set_configuration_from_user(cfg) self._config_dirty = True if not propagate: return @@ -1068,14 +1093,15 @@ def get_timer(self): # ------------------------------------------------------------------------- def get_integration_time(self): - integration_time = self._synchronization.active_time - if type(integration_time) == float: + integration_time = self._synch_description.active_time + if isinstance(integration_time, float): return integration_time elif len(integration_time) == 0: - raise Exception("The synchronization group has not been" - " initialized") + raise Exception("The synchronization description group has not" + " been initialized") elif len(integration_time) > 1: - raise Exception("There are more than one synchronization groups") + raise Exception("There are more than one synchronization" + "description groups") def set_integration_time(self, integration_time, propagate=1): total_time = integration_time + self.latency_time @@ -1083,7 +1109,7 @@ def set_integration_time(self, integration_time, propagate=1): SynchParam.Active: {SynchDomain.Time: integration_time}, SynchParam.Total: {SynchDomain.Time: total_time}, SynchParam.Repeats: 1}] - self.set_synchronization(synch) + self.set_synch_description(synch) if not propagate: return self.fire_event(EventType("integration_time", priority=propagate), @@ -1128,22 +1154,24 @@ def set_acquisition_mode(self, acquisition_mode, propagate=1): doc="the current acquisition mode") # ------------------------------------------------------------------------- - # synchronization + # synch_description # ------------------------------------------------------------------------- - def get_synchronization(self): - return self._synchronization + def get_synch_description(self): + return self._synch_description - def set_synchronization(self, synchronization, propagate=1): - self._synchronization = SynchronizationDescription(synchronization) + def set_synch_description(self, description, propagate=1): + self._synch_description = \ + SynchDescription(description) self._config_dirty = True # acquisition mode goes to configuration if not propagate: return - self.fire_event(EventType("synchronization", priority=propagate), - synchronization) + self.fire_event(EventType("synch_description", + priority=propagate), + description) - synchronization = property(get_synchronization, set_synchronization, - doc="the current acquisition mode") + synch_description = property(get_synch_description, set_synch_description, + doc="the current acquisition mode") # ------------------------------------------------------------------------- # moveable @@ -1162,7 +1190,8 @@ def set_moveable(self, moveable, propagate=1, to_fqdn=True): moveable) moveable = property(get_moveable, set_moveable, - doc="moveable source used in synchronization") + doc="moveable source used in synchronization " + "description") # ------------------------------------------------------------------------- # latency time @@ -1221,12 +1250,10 @@ def set_nb_starts(self, nb_starts, propagate=1): # acquisition # ------------------------------------------------------------------------- - def prepare(self, multiple=1): + def prepare(self): """Prepare for measurement. Delegate measurement preparation to the acquisition action. - - ..todo:: remove multiple argument """ if len(self.get_user_elements()) == 0: # All channels were disabled @@ -1255,37 +1282,26 @@ def prepare(self, multiple=1): value = self._get_value() self._pending_starts = self.nb_starts - kwargs = {'head': self, - 'multiple': multiple} + kwargs = {'head': self} self.acquisition.prepare(self.configuration, self.acquisition_mode, value, - self._synchronization, + self._synch_description, self._moveable_obj, self.sw_synch_initial_domain, self.nb_starts, **kwargs) - def start_acquisition(self, value=None, multiple=1): + def start_acquisition(self, value=None): """Start measurement. Delegate start measurement to the acquisition action. Provide backwards compatibility for starts without previous prepare. - - ..todo:: remove value and multiple arguments. """ if self._pending_starts == 0: - msg = "starting acquisition without prior preparing is " \ - "deprecated since version Jan18." - self.warning(msg) - self.debug("Preparing with number_of_starts equal to 1") - nb_starts = self.nb_starts - self.set_nb_starts(1, propagate=0) - try: - self.prepare(multiple) - finally: - self.set_nb_starts(nb_starts, propagate=0) + msg = "prepare is mandatory before starting acquisition" + raise RuntimeError(msg) self._aborted = False self._pending_starts -= 1 if not self._simulation_mode: diff --git a/src/sardana/pool/poolmetacontroller.py b/src/sardana/pool/poolmetacontroller.py index 242c737306..7aa985922f 100644 --- a/src/sardana/pool/poolmetacontroller.py +++ b/src/sardana/pool/poolmetacontroller.py @@ -119,7 +119,7 @@ def __init__(self, **kwargs): #: dictionary #: dict<:data:`~sardana.ElementType`, :class:`~sardana.pool.poolmetacontroller.TypeData`> TYPE_MAP_OBJ = {} -for t, d in TYPE_MAP.items(): +for t, d in list(TYPE_MAP.items()): o = TypeData(type=t, name=d[0], family=d[1], klass=d[2], auto_full_name=d[3], ctrl_klass=d[4]) TYPE_MAP_OBJ[t] = o @@ -206,7 +206,7 @@ def toDataInfo(klass, name, info): fget = info.get(FGet) fset = info.get(FSet) if default_value is not None and dtype != DataType.String: - if type(default_value) in types.StringTypes: + if isinstance(default_value, str): default_value = eval(default_value) return DataInfo(name, dtype, dformat=dformat, access=daccess, description=description, default_value=default_value, @@ -269,40 +269,24 @@ def __init__(self, **kwargs): self.ctrl_properties = props = CaselessDict() self.ctrl_properties_descriptions = [] - dep_msg = ("Defining the controller property description using a " - + "string is deprecated, use " - + "sardana.pool.controller.Description constant instead.") - for k, v in klass.class_prop.items(): # old member + for k, v in list(klass.ctrl_properties.items()): props[k] = DataInfo.toDataInfo(k, v) if Description in v: self.ctrl_properties_descriptions.append(v[Description]) - elif 'Description' in v: - self.warning(dep_msg) - self.ctrl_properties_descriptions.append(v['Description']) - - for k, v in klass.ctrl_properties.items(): - props[k] = DataInfo.toDataInfo(k, v) - if Description in v: - self.ctrl_properties_descriptions.append(v[Description]) - elif 'Description' in v: - self.warning(dep_msg) - self.ctrl_properties_descriptions.append(v['Description']) self.dict_extra['properties'] = tuple(klass.ctrl_properties) self.dict_extra['properties_desc'] = self.ctrl_properties_descriptions self.ctrl_attributes = ctrl_attrs = CaselessDict() - for k, v in klass.ctrl_attributes.items(): + for k, v in list(klass.ctrl_attributes.items()): ctrl_attrs[k] = DataInfo.toDataInfo(k, v) self.axis_attributes = axis_attrs = CaselessDict() - for k, v in klass.ctrl_extra_attributes.items(): # old member - axis_attrs[k] = DataInfo.toDataInfo(k, v) - for k, v in klass.axis_attributes.items(): + for k, v in list(klass.axis_attributes.items()): axis_attrs[k] = DataInfo.toDataInfo(k, v) self.types = types = self.__build_types() - self.type_names = map(ElementType.whatis, types) + self.type_names = list(map(ElementType.whatis, types)) if ElementType.PseudoMotor in types: self.motor_roles = tuple(klass.motor_roles) @@ -320,17 +304,23 @@ def __init__(self, **kwargs): self.dict_extra['counter_roles'] = self.counter_roles self.dict_extra['pseudo_counter_roles'] = self.pseudo_counter_roles - if ElementType.IORegister in types: - self.dict_extra['predefined_values'] = klass.predefined_values - init_args = inspect.getargspec(klass.__init__) if init_args.varargs is None or init_args.keywords is None: self.api_version = 0 + def __lt__(self, o): + main_type = self.types[0] + o_main_type = o.types[0] + if main_type != o_main_type: + return main_type < o_main_type + if self.gender != o.gender: + return self.gender < o.gender + return self.name < o.name + def __build_types(self): types = [] klass = self.klass - for _type, type_data in TYPE_MAP_OBJ.items(): + for _type, type_data in list(TYPE_MAP_OBJ.items()): if _type not in TYPE_ELEMENTS: continue if issubclass(klass, type_data.ctrl_klass): diff --git a/src/sardana/pool/poolmonitor.py b/src/sardana/pool/poolmonitor.py index 6549fa03ea..ecc8d0e31c 100644 --- a/src/sardana/pool/poolmonitor.py +++ b/src/sardana/pool/poolmonitor.py @@ -76,7 +76,7 @@ def on_pool_changed(self, evt_src, evt_type, evt_value): types = set(pool_ctrl.get_ctrl_types()) if types.isdisjoint(TYPE_PSEUDO_ELEMENTS): ctrl_ids.append(pool_ctrl.id) - elem_ids.extend(pool_ctrl.get_element_ids().keys()) + elem_ids.extend(list(pool_ctrl.get_element_ids().keys())) elem_ids.sort() self._elem_ids = elem_ids self._ctrl_ids = ctrl_ids @@ -106,7 +106,7 @@ def update_state_info(self): else: blocked_ctrls.add(ctrl) - for ctrl, ctrl_elems in ctrl_items.items(): + for ctrl, ctrl_elems in list(ctrl_items.items()): ret = ctrl.lock(blocking=False) if ret: ctrls.append(ctrl) @@ -123,7 +123,7 @@ def update_state_info(self): elem.unlock() def _update_state_info_serial(self, pool_ctrls): - for pool_ctrl, elems in pool_ctrls.items(): + for pool_ctrl, elems in list(pool_ctrls.items()): self._update_ctrl_state_info(pool_ctrl, elems) def _update_ctrl_state_info(self, pool_ctrl, elems): @@ -131,7 +131,7 @@ def _update_ctrl_state_info(self, pool_ctrl, elems): state_infos, exc_info = pool_ctrl.raw_read_axis_states(axes) if len(exc_info): self.info("STATE ERROR %s", exc_info) - for elem, state_info in state_infos.items(): + for elem, state_info in list(state_infos.items()): state_info = elem._from_ctrl_state_info(state_info) elem.set_state_info(state_info) diff --git a/src/sardana/pool/poolmotion.py b/src/sardana/pool/poolmotion.py index 02adc3c5fc..831b6eb95d 100644 --- a/src/sardana/pool/poolmotion.py +++ b/src/sardana/pool/poolmotion.py @@ -31,6 +31,7 @@ __docformat__ = 'restructuredtext' import time +import functools from taurus.core.util.log import DebugIt from taurus.core.util.enumeration import Enumeration @@ -184,7 +185,7 @@ def _recover_start_error(self, ctrl, meth_name, read_state=False): if read_state: states = {} self.read_state_info(ret=states) - for moveable, state_info in states.items(): + for moveable, state_info in list(states.items()): state_info = moveable._from_ctrl_state_info(state_info) moveable._set_state_info(state_info) @@ -262,7 +263,7 @@ def start_action(self, *args, **kwargs): pool.motion_loop_states_per_position) self._motion_info = motion_info = {} - for moveable, motion_data in items.items(): + for moveable, motion_data in list(items.items()): it = moveable.instability_time motion_info[moveable] = PoolMotionItem(moveable, *motion_data, instability_time=it) @@ -336,7 +337,7 @@ def action_loop(self): state_error_occured = self._state_error_occured(states) timestamp = time.time() in_motion = False - for moveable, state_info in states.items(): + for moveable, state_info in list(states.items()): motion_item = motion_info[moveable] state_info = moveable._from_ctrl_state_info(state_info) @@ -390,9 +391,11 @@ def action_loop(self): # ... but before protect the motor so that the monitor # doesn't come in between the two instructions below and # send a state event on it's own - with moveable: - moveable.clear_operation() - moveable.set_state_info(real_state_info, propagate=2) + set_state_info = functools.partial(moveable.set_state_info, + state_info, + propagate=2, + safe=True) + self.add_finish_hook(set_state_info, False) # Then update the state if not stopped_now: @@ -420,7 +423,7 @@ def action_loop(self): self.error("Loop final read position error 2. " "Cannot send final position event!!!") - for moveable, position_info in positions.items(): + for moveable, position_info in list(positions.items()): moveable.put_dial_position(position_info, propagate=2) # send state @@ -438,7 +441,7 @@ def action_loop(self): if not i % nb_states_per_pos: self.read_dial_position(ret=positions) # send position - for moveable, position_value in positions.items(): + for moveable, position_value in list(positions.items()): if position_value.error: self.error("Loop read position error for %s" % moveable.name) @@ -447,14 +450,14 @@ def action_loop(self): time.sleep(nap) def _state_error_occured(self, d): - for _, (state_info, exc_info) in d.items(): + for _, (state_info, exc_info) in list(d.items()): state = state_info[0] if exc_info is not None or state not in _NON_ERROR_STATES: return True return False def _position_error_occured(self, positions): - for _, value in positions.items(): + for _, value in list(positions.items()): if value.error: return True @@ -487,7 +490,7 @@ def _recover_state_moving_error(self, location, emergency_stop, states): # send positions positions = {} self.read_dial_position(ret=positions) - for moveable, position_info in positions.items(): + for moveable, position_info in list(positions.items()): moveable.put_dial_position(position_info, propagate=2) motion_info = self._motion_info diff --git a/src/sardana/pool/poolmotor.py b/src/sardana/pool/poolmotor.py index 64dcec8885..da281c21d3 100644 --- a/src/sardana/pool/poolmotor.py +++ b/src/sardana/pool/poolmotor.py @@ -37,7 +37,7 @@ from sardana.sardanaattribute import SardanaAttribute, ScalarNumberAttribute, \ SardanaSoftwareAttribute from sardana.sardanaevent import EventType -from sardana.sardanautils import assert_type, is_number +from sardana.sardanautils import assert_type, is_number, py2_round from sardana.pool.poolelement import PoolElement from sardana.pool.poolmotion import PoolMotion, MotionState @@ -167,7 +167,8 @@ def calc_motion(self, new_position): # compute a rounding value if necessary if ctrl.wants_rounding(): - nb_step = round(new_dial * step_per_unit) + # TODO: check if round would be fine + nb_step = py2_round(new_dial * step_per_unit) new_dial = nb_step / step_per_unit backlash_position = new_dial @@ -244,19 +245,12 @@ def on_change(self, evt_src, evt_type, evt_value): def _from_ctrl_state_info(self, state_info): state_info, _ = state_info - - try: - state_str = State.whatis(state_info) - return int(state_info), "{0} is in {1}".format(self.name, state_str), 0 - except KeyError: - pass - if len(state_info) > 2: state, status, ls = state_info[:3] else: state, other = state_info[:2] if is_number(other): - ls, status = other, '' + ls, status = other, None else: ls, status = 0, other state, ls = int(state), tuple(map(bool, (ls & 1, ls & 2, ls & 4))) @@ -273,7 +267,7 @@ def _set_state_info(self, state_info, propagate=1): # state information # ------------------------------------------------------------------------- - _STD_STATUS = "{name} is {state}{limit_switches}{ctrl_status}" + _STD_STATUS = "{name} is {state}{limit_switches}" def calculate_state_info(self, state_info=None): if state_info is None: @@ -306,11 +300,10 @@ def calculate_state_info(self, state_info=None): if ls[2]: limit_switches += ". Hit lower switch" - if len(status) > 0: - status = "\n" + status new_status = self._STD_STATUS.format(name=self.name, state=state_str, - limit_switches=limit_switches, - ctrl_status=status) + limit_switches=limit_switches) + if status is not None and len(status) > 0: + new_status += "\n{}".format(status) # append ctrl status return state, new_status, ls # ------------------------------------------------------------------------- @@ -751,7 +744,8 @@ def calculate_motion(self, new_position, items=None, calculated=None): # compute a rounding value if necessary if ctrl.wants_rounding(): - nb_step = round(new_dial * step_per_unit) + # TODO: check if round would be fine + nb_step = py2_round(new_dial * step_per_unit) new_dial = nb_step / step_per_unit backlash_position = new_dial diff --git a/src/sardana/pool/poolmotorgroup.py b/src/sardana/pool/poolmotorgroup.py index af082aff7b..c18fa4ca65 100644 --- a/src/sardana/pool/poolmotorgroup.py +++ b/src/sardana/pool/poolmotorgroup.py @@ -112,7 +112,8 @@ def update(self, cache=True, propagate=1): if not cache: dial_position_values = self.obj.motion.read_dial_position( serial=True) - for motion_obj, position_value in dial_position_values.items(): + for motion_obj, position_value in \ + list(dial_position_values.items()): motion_obj.put_dial_position( position_value, propagate=propagate) @@ -254,28 +255,21 @@ def calculate_motion(self, new_positions, items=None): for new_position, element in zip(new_positions, user_elements): calculated[element] = new_position - # TODO: get the configuration for an specific sardana class and - # get rid of AttributeProxy. - config = AttributeProxy(element.name + '/position').get_config() + # TODO: use Sardana attribute configuration and + # get rid of accessing tango - see sardana-org/sardana#663 + from sardana.tango.core.util import _check_attr_range try: - high = float(config.max_value) - except ValueError: - high = None - try: - low = float(config.min_value) - except ValueError: - low = None - if high is not None: - if float(new_position) > high: - msg = "requested movement of %s is above its upper limit"\ - % element.name - raise RuntimeError(msg) - if low is not None: - if float(new_position) < low: - msg = "requested movement of %s is below its lower limit"\ - % element.name - raise RuntimeError(msg) + _check_attr_range(element.name, "position", new_position) + except ValueError as e: + # TODO: don't concatenate exception message whenever + # tango-controls/pytango#340 is fixed + msg = "requested move of {} is outside of limits ({})".format( + element.name, str(e) + ) + raise ValueError(msg) from e + element.calculate_motion(new_position, items=items, + calculated=calculated) for new_position, element in zip(new_positions, user_elements): element.calculate_motion(new_position, items=items, calculated=calculated) @@ -292,7 +286,7 @@ def _start_move(self, new_positions): self._aborted = False items = self.calculate_motion(new_positions) timestamp = time.time() - for item, position_info in items.items(): + for item, position_info in list(items.items()): item.set_write_position(position_info[0], timestamp=timestamp, propagate=0) if not self._simulation_mode: diff --git a/src/sardana/pool/poolpseudocounter.py b/src/sardana/pool/poolpseudocounter.py index efe7c45318..e0eb460273 100644 --- a/src/sardana/pool/poolpseudocounter.py +++ b/src/sardana/pool/poolpseudocounter.py @@ -53,7 +53,7 @@ def __init__(self, *args, **kwargs): value_buf.add_listener(self.on_change) def on_change(self, evt_src, evt_type, evt_value): - for idx in evt_value.iterkeys(): + for idx in evt_value.keys(): physical_values = [] for value_buf in self.obj.get_physical_value_buffer_iterator(): try: @@ -166,6 +166,8 @@ def calc(self, physical_values=None): "values (you gave %d)" % (obj.name, l_u, l_v)) ctrl, axis = obj.controller, obj.axis result = ctrl.calc(axis, physical_values) + if result.error: + self._exc_info = result.exc_info except SardanaException as se: self._exc_info = se.exc_info result = SardanaValue(exc_info=se.exc_info) @@ -209,7 +211,7 @@ def update(self, cache=True, propagate=1): values = self.obj.acquisition.read_value(serial=True) if not len(values): self._local_timestamp = time.time() - for acq_obj, value in values.items(): + for acq_obj, value in list(values.items()): acq_obj.put_value(value, propagate=propagate) @@ -233,7 +235,7 @@ def serialize(self, *args, **kwargs): kwargs = PoolBaseChannel.serialize(self, *args, **kwargs) elements = [elem.name for elem in self.get_user_elements()] physical_elements = [] - for elem_list in self.get_physical_elements().values(): + for elem_list in list(self.get_physical_elements().values()): for elem in elem_list: physical_elements.append(elem.name) cl_name = self.__class__.__name__ @@ -274,7 +276,8 @@ def set_action_cache(self, action_cache): def get_siblings(self): if self._siblings is None: self._siblings = siblings = set() - for axis, sibling in self.controller.get_element_axis().items(): + for axis, sibling in \ + list(self.controller.get_element_axis().items()): if axis == self.axis: continue siblings.add(sibling) @@ -399,7 +402,7 @@ def read_state_info(self, state_info=None): state_info = {} action_cache = self.get_action_cache() ctrl_state_infos = action_cache.read_state_info(serial=True) - for obj, ctrl_state_info in ctrl_state_infos.items(): + for obj, ctrl_state_info in list(ctrl_state_infos.items()): state_info = obj._from_ctrl_state_info(ctrl_state_info) obj.put_state_info(state_info) for user_element in self.get_user_elements(): diff --git a/src/sardana/pool/poolpseudomotor.py b/src/sardana/pool/poolpseudomotor.py index 33344ca223..7819b4cdea 100644 --- a/src/sardana/pool/poolpseudomotor.py +++ b/src/sardana/pool/poolpseudomotor.py @@ -45,7 +45,6 @@ from sardana.pool.poolmotion import PoolMotion from sardana.pool.poolexception import PoolException -from PyTango import AttributeProxy class Position(SardanaAttribute): @@ -202,7 +201,7 @@ def calc_physical(self, new_position): positions = obj.get_siblings_positions() positions[obj] = new_position new_positions = len(positions) * [None] - for pseudo, position in positions.items(): + for pseudo, position in list(positions.items()): new_positions[pseudo.axis - 1] = position result = obj.controller.calc_all_physical(new_positions, @@ -227,7 +226,8 @@ def update(self, cache=True, propagate=1): serial=True) if not len(dial_position_values): self._local_timestamp = time.time() - for motion_obj, position_value in dial_position_values.items(): + for motion_obj, position_value in \ + list(dial_position_values.items()): motion_obj.put_dial_position( position_value, propagate=propagate) @@ -258,7 +258,7 @@ def serialize(self, *args, **kwargs): kwargs = PoolElement.serialize(self, *args, **kwargs) elements = [elem.name for elem in self.get_user_elements()] physical_elements = [] - for elem_list in self.get_physical_elements().values(): + for elem_list in list(self.get_physical_elements().values()): for elem in elem_list: physical_elements.append(elem.name) cl_name = self.__class__.__name__ @@ -293,7 +293,8 @@ def set_action_cache(self, action_cache): def get_siblings(self): if self._siblings is None: self._siblings = siblings = set() - for axis, sibling in self.controller.get_element_axis().items(): + for axis, sibling in \ + list(self.controller.get_element_axis().items()): if axis == self.axis: continue siblings.add(sibling) @@ -486,7 +487,7 @@ def read_state_info(self, state_info=None): state_info = {} action_cache = self.get_action_cache() ctrl_state_infos = action_cache.read_state_info(serial=True) - for motion_obj, ctrl_state_info in ctrl_state_infos.items(): + for motion_obj, ctrl_state_info in list(ctrl_state_infos.items()): state_info[motion_obj] = motion_state_info = \ motion_obj._from_ctrl_state_info(ctrl_state_info) motion_obj.put_state_info(motion_state_info) @@ -534,7 +535,7 @@ def calculate_motion(self, new_position, items=None, calculated=None): write_pos=self.drift_correction) positions[self] = new_position pseudo_positions = len(positions) * [None] - for pseudo, position in positions.items(): + for pseudo, position in list(positions.items()): pseudo_positions[pseudo.axis - 1] = position curr_physical_positions = self._position.get_physical_positions() physical_positions = self.controller.calc_all_physical(pseudo_positions, @@ -550,31 +551,23 @@ def calculate_motion(self, new_position, items=None, calculated=None): if items is None: items = {} - for new_position, element in zip(physical_positions.value, user_elements): + for new_position, element in zip(physical_positions.value, + user_elements): if new_position is None: raise PoolException("Cannot calculate motion: %s reports " "position to be None" % element.name) - # TODO: get the configuration for an specific sardana class and - # get rid of AttributeProxy - see sardana-org/sardana#663 - config = AttributeProxy(element.name + '/position').get_config() + # TODO: use Sardana attribute configuration and + # get rid of accessing tango - see sardana-org/sardana#663 + from sardana.tango.core.util import _check_attr_range try: - high = float(config.max_value) - except ValueError: - high = None - try: - low = float(config.min_value) - except ValueError: - low = None - if high is not None: - if float(new_position) > high: - msg = "requested movement of %s is above its upper limit"\ - % element.name - raise RuntimeError(msg) - if low is not None: - if float(new_position) < low: - msg = "requested movement of %s is below its lower limit"\ - % element.name - raise RuntimeError(msg) + _check_attr_range(element.name, "position", new_position) + except ValueError as e: + # TODO: don't concatenate exception message whenever + # tango-controls/pytango#340 is fixed + msg = "requested move of {} is outside of limits ({})".format( + element.name, str(e) + ) + raise ValueError(msg) from e element.calculate_motion(new_position, items=items, calculated=calculated) @@ -592,7 +585,7 @@ def _start_move(self, new_position): self._stopped = False items = self.calculate_motion(new_position) timestamp = time.time() - for item, position_info in items.items(): + for item, position_info in list(items.items()): item.set_write_position(position_info[0], timestamp=timestamp, propagate=1) if not self._simulation_mode: diff --git a/src/sardana/pool/poolsynchronization.py b/src/sardana/pool/poolsynchronization.py index aaf81832bc..96d68f44b0 100644 --- a/src/sardana/pool/poolsynchronization.py +++ b/src/sardana/pool/poolsynchronization.py @@ -27,9 +27,10 @@ """This module is part of the Python Pool library. It defines the classes for the synchronization""" -__all__ = ["PoolSynchronization", "SynchronizationDescription", "TGChannel"] +__all__ = ["PoolSynchronization", "SynchDescription", "TGChannel"] import time +import threading from functools import partial from taurus.core.util.log import DebugIt from sardana import State @@ -61,7 +62,7 @@ def __getattr__(self, name): return getattr(self.element, name) -class SynchronizationDescription(list): +class SynchDescription(list): """Synchronization description. It is composed from groups - repetitions of equidistant synchronization events. Each group is described by :class:`~sardana.pool.pooldefs.SynchParam` parameters which may have @@ -90,8 +91,9 @@ def passive_time(self): def _get_param(self, param, domain=SynchDomain.Time): """ Extract parameter from synchronization description and its groups. If - there is only one group in the synchronization then returns float - with the value. Otherwise a list of floats with different values. + there is only one group in the synchronization description then + returns float with the value. Otherwise a list of floats with + different values. :param param: parameter type :type param: :class:`~sardana.pool.pooldefs.SynchParam` @@ -116,6 +118,8 @@ class PoolSynchronization(PoolAction): """Synchronization action. It coordinates trigger/gate elements and software synchronizer. + + .. todo: Think of moving the ready/busy mechanism to PoolAction """ def __init__(self, main_element, name="Synchronization"): @@ -129,19 +133,36 @@ def __init__(self, main_element, name="Synchronization"): soft_synch_name = main_element.name + "-SoftSynch" self._synch_soft = FunctionGenerator(name=soft_synch_name) self._listener = None + self._ready = threading.Event() + self._ready.set() + + def _is_ready(self): + return self._ready.is_set() + + def _wait(self, timeout=None): + return self._ready.wait(timeout) + + def _set_ready(self, _=None): + self._ready.set() + + def _is_busy(self): + return not self._ready.is_set() + + def _set_busy(self): + self._ready.clear() def add_listener(self, listener): self._listener = listener - def start_action(self, ctrls, synchronization, moveable=None, + def start_action(self, ctrls, synch_description, moveable=None, sw_synch_initial_domain=None, *args, **kwargs): """Start synchronization action. :param ctrls: list of enabled trigger/gate controllers :type ctrls: list - :param synchronization: synchronization description - :type synchronization: - :class:`~sardana.pool.poolsynchronization.SynchronizationDescription` + :param synch_description: synchronization description + :type synch_description: + :class:`~sardana.pool.poolsynchronization.SynchDescription` :param moveable: (optional) moveable object used as the synchronization source in the Position domain :type moveable: :class:`~sardna.pool.poolmotor.PoolMotor` or @@ -159,12 +180,12 @@ def start_action(self, ctrls, synchronization, moveable=None, pool_ctrl.ctrl.PreSynchAll() for channel in ctrl.get_channels(enabled=True): axis = channel.axis - ret = pool_ctrl.ctrl.PreSynchOne(axis, synchronization) + ret = pool_ctrl.ctrl.PreSynchOne(axis, synch_description) if not ret: msg = ("%s.PreSynchOne(%d) returns False" % (ctrl.name, axis)) raise Exception(msg) - pool_ctrl.ctrl.SynchOne(axis, synchronization) + pool_ctrl.ctrl.SynchOne(axis, synch_description) pool_ctrl.ctrl.SynchAll() # attaching listener (usually acquisition action) @@ -172,7 +193,7 @@ def start_action(self, ctrls, synchronization, moveable=None, if self._listener is not None: if sw_synch_initial_domain is not None: self._synch_soft.initial_domain = sw_synch_initial_domain - self._synch_soft.set_configuration(synchronization) + self._synch_soft.set_configuration(synch_description) self._synch_soft.add_listener(self._listener) remove_acq_listener = partial(self._synch_soft.remove_listener, self._listener) @@ -253,7 +274,7 @@ def action_loop(self): # Triggering loop # TODO: make nap configurable (see motion or acquisition loops) - nap = 0.2 + nap = 0.01 while True: self.read_state_info(ret=states) if not self.is_triggering(states): @@ -261,7 +282,7 @@ def action_loop(self): time.sleep(nap) # Set element states after ending the triggering - for element, state_info in states.items(): + for element, state_info in list(states.items()): with element: element.clear_operation() state_info = element._from_ctrl_state_info(state_info) diff --git a/src/sardana/pool/poolutil.py b/src/sardana/pool/poolutil.py index 93c5c9bf62..d29b94af91 100644 --- a/src/sardana/pool/poolutil.py +++ b/src/sardana/pool/poolutil.py @@ -43,6 +43,16 @@ def __call__(self, *args, **kwargs): return self def get_device(self, *args, **kwargs): + """Factory method to create a single `tango.DeviceProxy` instance + per controller instance. + + :param ctrl_name: Controller name to which assign the proxy object + :type ctrl_name: `str` + :param device_name: Tango device name + :type device_name: `str` + :return: single device proxy object + :rtype: `tango.DeviceProxy` + """ ctrl_name = args[0] device_name = args[1] with self._lock: @@ -56,8 +66,15 @@ def get_device(self, *args, **kwargs): return dev get_motor = get_phy_motor = get_pseudo_motor = get_motor_group = \ - get_exp_channel = get_ct_channel = get_zerod_channel = get_oned_channel = \ - get_twod_channel = get_pseudo_counter_channel = get_measurement_group = \ - get_com_channel = get_ioregister = get_device + get_exp_channel = get_ct_channel = get_zerod_channel = \ + get_oned_channel = get_twod_channel = get_pseudo_counter_channel = \ + get_measurement_group = get_com_channel = get_ioregister = get_device + +#: Singleton instance of the `~sardana.pool.poolutil._PoolUtil` class. +#: +#: It is a factory of `tango.DeviceProxy` objects and ensures only one +#: instance of such objects is created for the whole process. +#: Please refer to the `~sardana.pool.poolutil._PoolUtil` API on the available +#: methods. PoolUtil = _PoolUtil() diff --git a/src/sardana/pool/poolzerodexpchannel.py b/src/sardana/pool/poolzerodexpchannel.py index cd2fe1a7e9..9f97d362dc 100644 --- a/src/sardana/pool/poolzerodexpchannel.py +++ b/src/sardana/pool/poolzerodexpchannel.py @@ -118,7 +118,7 @@ def update_value(self, value, timestamp): else: last_value, last_timestamp = self.last_value dt = timestamp - last_timestamp - self.sum += dt * (last_value + value) / 2.0 + self.sum += dt * (last_value + value) / 2 total_dt = timestamp - self.start_time self.value = self.sum / total_dt self.last_value = value, timestamp diff --git a/src/sardana/pool/test/base.py b/src/sardana/pool/test/base.py index b65e3147dd..fe8b3f3ef5 100644 --- a/src/sardana/pool/test/base.py +++ b/src/sardana/pool/test/base.py @@ -193,26 +193,26 @@ def setUp(self): self.createMotorElement(ctrl_obj, name, axis) # Check the elements creation - cts = len(self.cts.keys()) - tgs = len(self.tgs.keys()) - mots = len(self.mots.keys()) + cts = len(list(self.cts.keys())) + tgs = len(list(self.tgs.keys())) + mots = len(list(self.mots.keys())) expected_cts = self.nctelems * self.nctctrls msg = 'Something happened during the creation of CT elements.\n' + \ 'Expected %s and there are %s, %s' % \ - (expected_cts, cts, self.cts.keys()) + (expected_cts, cts, list(self.cts.keys())) if cts != expected_cts: raise Exception(msg) expected_tgs = self.ntgelems * self.ntgctrls msg = 'Something happened during the creation of TG elements.\n' + \ 'Expected %s and there are %s, %s' % \ - (expected_tgs, tgs, self.tgs.keys()) + (expected_tgs, tgs, list(self.tgs.keys())) if tgs != expected_tgs: raise Exception(msg) expected_mots = self.nmotelems * self.nmotctrls msg = 'Something happened during the creation of MOT elements.\n' + \ 'Expected %s and there are %s, %s' % \ - (self.nmotelems, mots, self.mots.keys()) + (self.nmotelems, mots, list(self.mots.keys())) if mots != expected_mots: raise Exception(msg) diff --git a/src/sardana/pool/test/helper.py b/src/sardana/pool/test/helper.py index ce4649f0f3..93505598f1 100644 --- a/src/sardana/pool/test/helper.py +++ b/src/sardana/pool/test/helper.py @@ -67,7 +67,7 @@ def createPoolController(pool, conf): ctrl_properties = ctrl_class_info.ctrl_properties else: ctrl_properties = {} - for prop_info in ctrl_properties.values(): + for prop_info in list(ctrl_properties.values()): prop_name = prop_info.name prop_value = properties.get(prop_name) if prop_value is None: diff --git a/src/sardana/pool/test/test_acquisition.py b/src/sardana/pool/test/test_acquisition.py index 2a6ed1ca29..cf0c1139e8 100644 --- a/src/sardana/pool/test/test_acquisition.py +++ b/src/sardana/pool/test/test_acquisition.py @@ -27,7 +27,7 @@ import numpy -from taurus.external.unittest import TestCase +from unittest import TestCase from taurus.test import insertTest from sardana.sardanautils import is_number, is_pure_str @@ -84,7 +84,7 @@ def prepare(self, integ_time, repetitions, latency_time, nb_starts): def wait_finish(self): # waiting for acquisition and synchronization to finish - while (self.acquisition.is_running() + while (self.acquisition._is_busy() or self.synchronization.is_running()): time.sleep(.1) @@ -160,16 +160,16 @@ def event_received(self, *args, **kwargs): _, type_, index = args name = type_.name if name == "active": - if self.sw_acq_busy.is_set(): + if self.sw_acq._is_busy(): # skipping acquisition cause the previous on is ongoing return else: - self.sw_acq_busy.set() + self.sw_acq._set_busy() args = self.sw_acq_args kwargs = self.sw_acq_kwargs kwargs['index'] = index get_thread_pool().add(self.sw_acq.run, - None, + self.sw_acq._set_ready, *args, **kwargs) @@ -209,18 +209,8 @@ def continuous_acquisition(self, offset, active_interval, passive_interval, # creating acquisition actions self.hw_acq = self.create_action(PoolAcquisitionHardware, [ct_1_1]) self.sw_acq = self.create_action(PoolAcquisitionSoftware, [ct_2_1]) - # Since we deposit the software acquisition action on the PoolThread's - # queue we can not rely on the action's state - one may still wait - # in the queue (its state has not changed to running yet) and we would - # be depositing another one. This way we may be starting multiple - # times the same action (with the same elements involved), what results - # in "already involved in operation" errors. - # Use an external Event flag to mark if we have any software - # acquisition action pending. - self.sw_acq_busy = threading.Event() - self.sw_acq.add_finish_hook(self.sw_acq_busy.clear) self.sw_acq_args = (sw_ctrls, integ_time, sw_master) - self.sw_acq_kwargs = {} + self.sw_acq_kwargs = dict(synch=True) total_interval = active_interval + passive_interval group = { @@ -229,14 +219,14 @@ def continuous_acquisition(self, offset, active_interval, passive_interval, SynchParam.Total: {SynchDomain.Time: total_interval}, SynchParam.Repeats: repetitions } - synchronization = [group] + synch_description = [group] # get the current number of jobs jobs_before = get_thread_pool().qsize self.hw_acq.run(hw_ctrls, integ_time, repetitions, 0) - self.synchronization.run(synch_ctrls, synchronization) + self.synchronization.run(synch_ctrls, synch_description) # waiting for acquisition and synchronization to finish while (self.hw_acq.is_running() - or self.sw_acq.is_running() + or self.sw_acq._is_busy() or self.synchronization.is_running()): time.sleep(.1) self.do_asserts(repetitions, jobs_before) @@ -260,17 +250,17 @@ def event_received(self, *args, **kwargs): _, type_, value = args name = type_.name if name == "active": - if self.acq_busy.is_set(): + if self.acquisition._is_busy(): # skipping acquisition cause the previous on is ongoing return else: - self.acq_busy.set() + self.acquisition._set_busy() acq_args = list(self.acq_args) acq_kwargs = self.acq_kwargs index = value acq_args[3] = index get_thread_pool().add(self.acquisition.run, - None, + self.acquisition._set_ready, *acq_args, **acq_kwargs) @@ -291,18 +281,8 @@ def acquire(self, integ_time, repetitions, latency_time): # creating acquisition actions self.acquisition = self.create_action(PoolAcquisitionSoftware, [self.channel]) - # Since we deposit the software acquisition action on the PoolThread's - # queue we can not rely on the action's state - one may still wait - # in the queue (its state has not changed to running yet) and we would - # be depositing another one. This way we may be starting multiple - # times the same action (with the same elements involved), what results - # in "already involved in operation" errors. - # Use an external Event flag to mark if we have any software - # acquisition action pending. - self.acq_busy = threading.Event() - self.acquisition.add_finish_hook(self.acq_busy.clear) self.acq_args = (ctrls, integ_time, master, None) - self.acq_kwargs = {} + self.acq_kwargs = dict(synch=True) total_interval = integ_time + latency_time group = { @@ -311,10 +291,10 @@ def acquire(self, integ_time, repetitions, latency_time): SynchParam.Total: {SynchDomain.Time: total_interval}, SynchParam.Repeats: repetitions } - synchronization = [group] + synch_description = [group] # get the current number of jobs jobs_before = get_thread_pool().qsize - self.synchronization.run([], synchronization) + self.synchronization.run([], synch_description) self.wait_finish() self.do_asserts(repetitions, jobs_before, strict=False) @@ -331,7 +311,6 @@ class BaseAcquisitionSoftwareStartTestCase(AcquisitionTestCase): def setUp(self): """Create test actors (controllers and elements)""" - TestCase.setUp(self) AcquisitionTestCase.setUp(self) def event_received(self, *args, **kwargs): @@ -339,7 +318,9 @@ def event_received(self, *args, **kwargs): _, type_, value = args name = type_.name if name == "start": - get_thread_pool().add(self.acquisition.run, None, + self.acquisition._set_busy() + get_thread_pool().add(self.acquisition.run, + self.acquisition._set_ready, *self.acq_args, **self.acq_kwargs) @@ -361,7 +342,7 @@ def acquire(self, integ_time, repetitions, latency_time): self.acquisition = self.create_action(PoolAcquisitionSoftwareStart, [self.channel]) self.acq_args = (ctrls, integ_time, master, repetitions, latency_time) - self.acq_kwargs = {} + self.acq_kwargs = dict(synch=True) total_interval = integ_time + latency_time group = { @@ -370,10 +351,10 @@ def acquire(self, integ_time, repetitions, latency_time): SynchParam.Total: {SynchDomain.Time: total_interval}, SynchParam.Repeats: repetitions } - synchronization = [group] + synch_description = [group] # get the current number of jobs jobs_before = get_thread_pool().qsize - self.synchronization.run([], synchronization) + self.synchronization.run([], synch_description) self.wait_finish() self.do_asserts(repetitions, jobs_before, strict=False) @@ -415,14 +396,20 @@ def acquire(self, integ_time, repetitions, latency_time): SynchParam.Total: {SynchDomain.Time: total_interval}, SynchParam.Repeats: repetitions } - synchronization = [group] + synch_description = [group] # get the current number of jobs jobs_before = get_thread_pool().qsize self.acquisition.run(ctrls, integ_time, repetitions, 0) - self.synchronization.run(synch_ctrls, synchronization) + self.synchronization.run(synch_ctrls, synch_description) self.wait_finish() self.do_asserts(repetitions, jobs_before) + def wait_finish(self): + # waiting for acquisition and synchronization to finish + while (self.acquisition.is_running() + or self.synchronization.is_running()): + time.sleep(.1) + def tearDown(self): AcquisitionTestCase.tearDown(self) TestCase.tearDown(self) @@ -452,7 +439,7 @@ class Acquisition2DSoftwareTriggerTestCase(BaseAcquisitionSoftwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionSoftwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -471,7 +458,7 @@ class Acquisition2DSoftwareTriggerRefTestCase(BaseAcquisitionSoftwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionSoftwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuerefbuffer") @@ -494,7 +481,7 @@ class AcquisitionCTSoftwareStartTestCase( def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionSoftwareStartTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -512,7 +499,7 @@ class Acquisition2DSoftwareStartTestCase( def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionSoftwareStartTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -535,7 +522,7 @@ class Acquisition2DSoftwareStartRefTestCase( def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionSoftwareStartTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuerefbuffer") @@ -576,7 +563,7 @@ class Acquisition2DHardwareStartTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -595,7 +582,7 @@ class Acquisition2DHardwareStartRefTestCase( def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.channel_ctrl.set_log_level(10) self.data_listener = AttributeListener(dtype=object, attr_name="valuerefbuffer") @@ -619,7 +606,7 @@ class AcquisitionCTHardwareTriggerTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -637,7 +624,7 @@ class Acquisition2DHardwareTriggerTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -656,7 +643,7 @@ class Acquisition2DHardwareTriggerRefTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuerefbuffer") @@ -679,7 +666,7 @@ class AcquisitionCTHardwareGateTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -697,7 +684,7 @@ class Acquisition2DHardwareGateTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuebuffer") @@ -716,7 +703,7 @@ class Acquisition2DHardwareGateRefTestCase(BaseAcquisitionHardwareTestCase, def setUp(self): """Create test actors (controllers and elements)""" TestCase.setUp(self) - AcquisitionTestCase.setUp(self) + BaseAcquisitionHardwareTestCase.setUp(self) self.data_listener = AttributeListener(dtype=object, attr_name="valuerefbuffer") diff --git a/src/sardana/pool/test/test_ctacquisition.py b/src/sardana/pool/test/test_ctacquisition.py index 37e6344150..df454887ff 100644 --- a/src/sardana/pool/test/test_ctacquisition.py +++ b/src/sardana/pool/test/test_ctacquisition.py @@ -24,7 +24,7 @@ ############################################################################## import time -from taurus.external import unittest +import unittest from sardana.pool.poolmeasurementgroup import PoolMeasurementGroup from sardana.pool.test import (FakePool, createPoolController, createPoolMeasurementGroup, @@ -64,22 +64,20 @@ def test_init(self): 'PoolMeasurementGroup instance' self.assertIsInstance(self.pmg, PoolMeasurementGroup, msg) - # TODO: until the measurement group does not have a default software - # synchronizer mark this test as expected failure. - @unittest.expectedFailure def test_acquisition(self): """Test acquisition using the created measurement group without using a Sardana pool.""" msg = 'Pool Measurement Group does not acquire' integ_time = .1 self.pmg.integration_time = integ_time + self.pmg.prepare() self.pmg.start_acquisition() acq = self.pmg.acquisition # 'acquiring..' while acq.is_running(): time.sleep(0.05) - self.assertEqual(self._pct.value, integ_time, msg) + self.assertEqual(self._pct.value.value, integ_time, msg) def tearDown(self): unittest.TestCase.tearDown(self) diff --git a/src/sardana/pool/test/test_measurementgroup.py b/src/sardana/pool/test/test_measurementgroup.py index a2ea0944fa..49a147bfca 100644 --- a/src/sardana/pool/test/test_measurementgroup.py +++ b/src/sardana/pool/test/test_measurementgroup.py @@ -26,7 +26,7 @@ import threading import copy -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.sardanathreadpool import get_thread_pool @@ -55,7 +55,7 @@ def prepare_meas(self, config): conf["user_elements"] = channel_ids self.pmg = createPoolMeasurementGroup(pool, conf) pool.add_element(self.pmg) - self.pmg.set_configuration_from_user(mg_conf, to_fqdn=False) + self.pmg.set_configuration_from_user(mg_conf) return channel_names def prepare_attribute_listener(self): @@ -74,6 +74,7 @@ def remove_attribute_listener(self): def acquire(self): """ Run acquisition """ + self.pmg.prepare() self.pmg.start_acquisition() acq = self.pmg.acquisition while acq.is_running(): @@ -83,10 +84,10 @@ def acq_asserts(self, channel_names, repetitions): # printing acquisition records table = self.attr_listener.get_table() header = table.dtype.names - print header + print(header) n_rows = table.shape[0] - for row in xrange(n_rows): - print row, table[row] + for row in range(n_rows): + print(row, table[row]) # checking if any of data was acquired self.assertTrue(self.attr_listener.data, 'no data were acquired') # checking if all channels produced data @@ -101,38 +102,39 @@ def acq_asserts(self, channel_names, repetitions): (ch_name, ch_data_len, repetitions) self.assertEqual(ch_data_len, repetitions, msg) - def meas_double_acquisition(self, config, synchronization, moveable=None): + def meas_double_acquisition(self, config, synch_description, + moveable=None): """ Run two acquisition with the same measurement group, first with multiple repetitions and then one repetition. """ channel_names = self.prepare_meas(config) # setting measurement parameters - self.pmg.set_synchronization(synchronization) + self.pmg.set_synch_description(synch_description) self.pmg.set_moveable(moveable, to_fqdn=False) repetitions = 0 - for group in synchronization: + for group in synch_description: repetitions += group[SynchParam.Repeats] self.prepare_attribute_listener() self.acquire() self.acq_asserts(channel_names, repetitions) self.remove_attribute_listener() - synchronization = [{SynchParam.Delay: {SynchDomain.Time: 0}, - SynchParam.Active: {SynchDomain.Time: 0.1}, - SynchParam.Total: {SynchDomain.Time: 0}, - SynchParam.Repeats: 1}] - self.pmg.synchronization = synchronization + synch_description = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: 0.1}, + SynchParam.Total: {SynchDomain.Time: 0.2}, + SynchParam.Repeats: 1}] + self.pmg.synch_description = synch_description self.acquire() # TODO: implement asserts of Timer acquisition - def meas_double_acquisition_samemode(self, config, synchronization, + def meas_double_acquisition_samemode(self, config, synch_description, moveable=None): """ Run two acquisition with the same measurement group. """ channel_names = self.prepare_meas(config) - self.pmg.set_synchronization(synchronization) + self.pmg.set_synch_description(synch_description) self.pmg.set_moveable(moveable, to_fqdn=False) repetitions = 0 - for group in synchronization: + for group in synch_description: repetitions += group[SynchParam.Repeats] self.prepare_attribute_listener() self.acquire() @@ -144,31 +146,31 @@ def meas_double_acquisition_samemode(self, config, synchronization, self.remove_attribute_listener() # TODO: implement asserts of Timer acquisition - def consecutive_acquisitions(self, pool, config, synchronization): + def consecutive_acquisitions(self, pool, config, synch_description): # creating mg user configuration and obtaining channel ids mg_conf, channel_ids, channel_names = createMGUserConfiguration( pool, config) # setting mg configuration - this cleans the action cache! - self.pmg.set_configuration_from_user(mg_conf, to_fqdn=False) + self.pmg.set_configuration_from_user(mg_conf) repetitions = 0 - for group in synchronization: + for group in synch_description: repetitions += group[SynchParam.Repeats] self.prepare_attribute_listener() self.acquire() self.acq_asserts(channel_names, repetitions) - def meas_cont_acquisition(self, config, synchronization, moveable=None, + def meas_cont_acquisition(self, config, synch_description, moveable=None, second_config=None): """Executes measurement using the measurement group. Checks the lengths of the acquired data. """ jobs_before = get_thread_pool().qsize channel_names = self.prepare_meas(config) - self.pmg.set_synchronization(synchronization) + self.pmg.set_synch_description(synch_description) self.pmg.set_moveable(moveable, to_fqdn=False) repetitions = 0 - for group in synchronization: + for group in synch_description: repetitions += group[SynchParam.Repeats] self.prepare_attribute_listener() self.acquire() @@ -176,7 +178,7 @@ def meas_cont_acquisition(self, config, synchronization, moveable=None, if second_config is not None: self.consecutive_acquisitions( - self.pool, second_config, synchronization) + self.pool, second_config, synch_description) # checking if there are no pending jobs jobs_after = get_thread_pool().qsize @@ -188,16 +190,17 @@ def stop_acquisition(self): """Method used to abort a running acquisition""" self.pmg.stop() - def meas_cont_stop_acquisition(self, config, synchronization, + def meas_cont_stop_acquisition(self, config, synch_description, moveable=None): """Executes measurement using the measurement group and tests that the acquisition can be stopped. """ self.prepare_meas(config) - self.pmg.synchronization = synchronization + self.pmg.synch_description = synch_description self.pmg.set_moveable(moveable, to_fqdn=False) self.prepare_attribute_listener() + self.pmg.prepare() self.pmg.start_acquisition() # retrieving the acquisition since it was cleaned when applying mg conf acq = self.pmg.acquisition @@ -215,27 +218,30 @@ def meas_cont_stop_acquisition(self, config, synchronization, msg = "The number of busy workers is not zero; numBW = %s" % (numBW) self.assertEqual(numBW, 0, msg) # print the acquisition records - for i, record in enumerate(zip(*self.attr_listener.data.values())): - print i, record + for i, record in \ + enumerate(zip(*list(self.attr_listener.data.values()))): + print(i, record) - def meas_contpos_acquisition(self, config, synchronization, moveable, + def meas_contpos_acquisition(self, config, synch_description, moveable, second_config=None): # TODO: this code is ready only for one group configuration - initial = synchronization[0][SynchParam.Initial][SynchDomain.Position] - total = synchronization[0][SynchParam.Total][SynchDomain.Position] - repeats = synchronization[0][SynchParam.Repeats] + initial = \ + synch_description[0][SynchParam.Initial][SynchDomain.Position] + total = synch_description[0][SynchParam.Total][SynchDomain.Position] + repeats = synch_description[0][SynchParam.Repeats] position = initial + total * repeats mot = self.mots[moveable] mot.set_base_rate(0) mot.set_acceleration(0.1) mot.set_velocity(0.5) channel_names = self.prepare_meas(config) - self.pmg.synchronization = synchronization + self.pmg.synch_description = synch_description self.pmg.set_moveable(moveable, to_fqdn=False) repetitions = 0 - for group in synchronization: + for group in synch_description: repetitions += group[SynchParam.Repeats] self.prepare_attribute_listener() + self.pmg.prepare() self.pmg.start_acquisition() mot.set_position(position) acq = self.pmg.acquisition @@ -250,31 +256,31 @@ def tearDown(self): self.pmg = None -synchronization1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, - SynchParam.Active: {SynchDomain.Time: .01}, - SynchParam.Total: {SynchDomain.Time: .02}, - SynchParam.Repeats: 10}] +synch_description1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: .01}, + SynchParam.Total: {SynchDomain.Time: .02}, + SynchParam.Repeats: 10}] -synchronization2 = [{SynchParam.Delay: {SynchDomain.Time: 0}, - SynchParam.Active: {SynchDomain.Time: .01}, - SynchParam.Total: {SynchDomain.Time: .02}, - SynchParam.Repeats: 100}] +synch_description2 = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: .01}, + SynchParam.Total: {SynchDomain.Time: .02}, + SynchParam.Repeats: 100}] -synchronization3 = [{SynchParam.Delay: {SynchDomain.Time: .1}, - SynchParam.Initial: {SynchDomain.Position: 0}, - SynchParam.Active: {SynchDomain.Position: .1, +synch_description3 = [{SynchParam.Delay: {SynchDomain.Time: .1}, + SynchParam.Initial: {SynchDomain.Position: 0}, + SynchParam.Active: {SynchDomain.Position: .1, SynchDomain.Time: .01, }, - SynchParam.Total: {SynchDomain.Position: .2, + SynchParam.Total: {SynchDomain.Position: .2, SynchDomain.Time: .1}, - SynchParam.Repeats: 10}] + SynchParam.Repeats: 10}] -synchronization4 = [{SynchParam.Delay: {SynchDomain.Time: 0.1}, - SynchParam.Initial: {SynchDomain.Position: 0}, - SynchParam.Active: {SynchDomain.Position: -.1, - SynchDomain.Time: .01, }, - SynchParam.Total: {SynchDomain.Position: -.2, - SynchDomain.Time: .1}, - SynchParam.Repeats: 10}] +synch_description4 = [{SynchParam.Delay: {SynchDomain.Time: 0.1}, + SynchParam.Initial: {SynchDomain.Position: 0}, + SynchParam.Active: {SynchDomain.Position: -.1, + SynchDomain.Time: .01, }, + SynchParam.Total: {SynchDomain.Position: -.2, + SynchDomain.Time: .1}, + SynchParam.Repeats: 10}] doc_1 = 'Synchronized acquisition with two channels from the same controller'\ ' using the same trigger' @@ -351,25 +357,25 @@ def tearDown(self): # TODO: listener is not ready to handle 2D # @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_15, -# config=config_15, synchronization=synchronization1) +# config=config_15, synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_14, - config=config_14, synchronization=synchronization1) + config=config_14, synch_description=synch_description1) @insertTest(helper_name='meas_contpos_acquisition', test_method_doc=doc_12, - config=config_12, synchronization=synchronization4, + config=config_12, synch_description=synch_description4, moveable="_test_mot_1_1") @insertTest(helper_name='meas_contpos_acquisition', test_method_doc=doc_12, - config=config_12, synchronization=synchronization3, + config=config_12, synch_description=synch_description3, moveable="_test_mot_1_1") @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_1, - config=config_1, synchronization=synchronization1) + config=config_1, synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_2, - config=config_2, synchronization=synchronization1) + config=config_2, synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_3, - config=config_3, synchronization=synchronization1) + config=config_3, synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_4, - config=config_4, synchronization=synchronization1) + config=config_4, synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_5, - config=config_5, synchronization=synchronization1) + config=config_5, synch_description=synch_description1) # TODO: implement dedicated asserts/test for only software synchronized # acquisition. # Until this TODO gets implemented we comment the test since it may @@ -379,24 +385,24 @@ def tearDown(self): # @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_6, # params=params_1, config=config_6) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_7, - config=config_7, synchronization=synchronization1) + config=config_7, synch_description=synch_description1) @insertTest(helper_name='meas_cont_stop_acquisition', test_method_doc=doc_8, - config=config_7, synchronization=synchronization2) + config=config_7, synch_description=synch_description2) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_9, config=config_1, second_config=config_7, - synchronization=synchronization1) + synch_description=synch_description1) @insertTest(helper_name='meas_double_acquisition', test_method_doc=doc_10, - config=config_7, synchronization=synchronization2) + config=config_7, synch_description=synch_description2) @insertTest(helper_name='meas_double_acquisition', test_method_doc=doc_10, - config=config_4, synchronization=synchronization1) + config=config_4, synch_description=synch_description1) @insertTest(helper_name='meas_double_acquisition_samemode', test_method_doc=doc_11, - config=config_11, synchronization=synchronization2) + config=config_11, synch_description=synch_description2) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_9, config=config_1, second_config=config_7, - synchronization=synchronization1) + synch_description=synch_description1) @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_13, config=config_13, - synchronization=synchronization1) + synch_description=synch_description1) class AcquisitionTestCase(BasePoolTestCase, BaseAcquisition, unittest.TestCase): """Integration test of TGGeneration and Acquisition actions.""" diff --git a/src/sardana/pool/test/test_poolcontroller.py b/src/sardana/pool/test/test_poolcontroller.py index fea20f15fb..01c83de2b4 100644 --- a/src/sardana/pool/test/test_poolcontroller.py +++ b/src/sardana/pool/test/test_poolcontroller.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external import unittest +import unittest from sardana.pool.test import (FakePool, createPoolController, dummyPoolCTCtrlConf01) from sardana.pool.poolcontroller import PoolController diff --git a/src/sardana/pool/test/test_poolcontrollermanager.py b/src/sardana/pool/test/test_poolcontrollermanager.py index 2fee8cf8ec..228833a02a 100644 --- a/src/sardana/pool/test/test_poolcontrollermanager.py +++ b/src/sardana/pool/test/test_poolcontrollermanager.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external import unittest +import unittest from sardana.pool.poolcontrollermanager import ControllerManager diff --git a/src/sardana/pool/test/test_poolcountertimer.py b/src/sardana/pool/test/test_poolcountertimer.py index dec8347bc3..d4214e764d 100644 --- a/src/sardana/pool/test/test_poolcountertimer.py +++ b/src/sardana/pool/test/test_poolcountertimer.py @@ -25,8 +25,10 @@ import time -from taurus.external import unittest +import pytest +import unittest +from sardana import State from sardana.pool.poolcountertimer import PoolCounterTimer from sardana.pool.test import (FakePool, createPoolController, createPoolCounterTimer, dummyCounterTimerConf01, @@ -61,3 +63,34 @@ def test_acquisition(self): def tearDown(self): unittest.TestCase.tearDown(self) self.pct = None + + +def StateOne_state(self, axis): + return State.On + + +def StateOne_state_status(self, axis): + return State.On, "Status" + + +@pytest.mark.parametrize("mock_StateOne", [StateOne_state, + StateOne_state_status]) +def test_state(monkeypatch, mock_StateOne): + """Test variants of StateOne return value: + - state + - state, status + """ + pool = FakePool() + # when SEP19 gets implemented it should be possible to mock directly + # the imported class + DummyCounterTimerController = pool.ctrl_manager.getControllerClass( + "DummyCounterTimerController") + monkeypatch.setattr(DummyCounterTimerController, "StateOne", + mock_StateOne) + ct_ctrl = createPoolController(pool, dummyPoolCTCtrlConf01) + ct = createPoolCounterTimer(pool, ct_ctrl, dummyCounterTimerConf01) + ct_ctrl.add_element(ct) + pool.add_element(ct_ctrl) + pool.add_element(ct) + assert ct.state == State.On + assert type(ct.status) == str diff --git a/src/sardana/pool/test/test_poolmotion.py b/src/sardana/pool/test/test_poolmotion.py index 46dc95cf3e..b97c8f7a62 100644 --- a/src/sardana/pool/test/test_poolmotion.py +++ b/src/sardana/pool/test/test_poolmotion.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external import unittest +import unittest from sardana.pool.poolmotion import PoolMotion from sardana.sardanadefs import State diff --git a/src/sardana/pool/test/test_poolmotor.py b/src/sardana/pool/test/test_poolmotor.py new file mode 100644 index 0000000000..99ade78c6c --- /dev/null +++ b/src/sardana/pool/test/test_poolmotor.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +import pytest + +from sardana import State +from sardana.pool.test import (FakePool, createPoolController, + createPoolMotor, dummyPoolMotorCtrlConf01, + dummyMotorConf01) + + +def StateOne_state(self, axis): + return State.On + + +def StateOne_state_status(self, axis): + return State.On, "Status" + + +def StateOne_state_status_limits(self, axis): + return State.On, "Status", 0 + + +@pytest.mark.parametrize("mock_StateOne", [StateOne_state, + StateOne_state_status, + StateOne_state_status_limits]) +def test_state(monkeypatch, mock_StateOne): + """Test variants of StateOne return value: + - state + - state, status + - state, status, limit_switches + """ + pool = FakePool() + # when SEP19 gets implemented it should be possible to mock directly + # the imported class + DummyMotorController = pool.ctrl_manager.getControllerClass( + "DummyMotorController") + monkeypatch.setattr(DummyMotorController, "StateOne", mock_StateOne) + mot_ctrl = createPoolController(pool, dummyPoolMotorCtrlConf01) + mot = createPoolMotor(pool, mot_ctrl, dummyMotorConf01) + mot_ctrl.add_element(mot) + pool.add_element(mot_ctrl) + pool.add_element(mot) + assert mot.state == State.On + assert type(mot.status) == str + assert mot.limit_switches.value == (False, ) * 3 diff --git a/src/sardana/pool/test/test_poolpseudocounter.py b/src/sardana/pool/test/test_poolpseudocounter.py index bb16cf9264..d78e666668 100644 --- a/src/sardana/pool/test/test_poolpseudocounter.py +++ b/src/sardana/pool/test/test_poolpseudocounter.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external.unittest import TestCase +from unittest import TestCase from sardana.pool.test.base import BasePoolTestCase diff --git a/src/sardana/pool/test/test_poolsynchronization.py b/src/sardana/pool/test/test_poolsynchronization.py index 4401796561..dd23ff9af5 100644 --- a/src/sardana/pool/test/test_poolsynchronization.py +++ b/src/sardana/pool/test/test_poolsynchronization.py @@ -24,7 +24,7 @@ ############################################################################## import threading -from taurus.external import unittest +import unittest from sardana.pool.poolsynchronization import PoolSynchronization from sardana.pool.poolacquisition import get_acq_ctrls diff --git a/src/sardana/pool/test/test_synchronization.py b/src/sardana/pool/test/test_synchronization.py index 061d19820e..32295f0413 100644 --- a/src/sardana/pool/test/test_synchronization.py +++ b/src/sardana/pool/test/test_synchronization.py @@ -26,12 +26,10 @@ """This module contains tests for trigger gate generation using a given controller""" -__docformat__ = "restructuredtext" - import threading from taurus.test import insertTest -from taurus.external import unittest +import unittest from sardana.pool.poolsynchronization import PoolSynchronization from sardana.pool.poolacquisition import get_acq_ctrls @@ -41,6 +39,8 @@ createPoolController, createPoolTriggerGate, \ createControllerConfiguration +__docformat__ = "restructuredtext" + class SynchronizationTestCase(object): """Base class for integration tests of PoolSynchronization class and any diff --git a/src/sardana/pool/test/util.py b/src/sardana/pool/test/util.py index 0a5ab0a3c2..301cf28d55 100644 --- a/src/sardana/pool/test/util.py +++ b/src/sardana/pool/test/util.py @@ -51,8 +51,8 @@ def event_received(self, *args, **kwargs): # of buffered attributes) or from the value in case of normal # attributes chunk = v - idx = chunk.keys() - value = [sardana_value.value for sardana_value in chunk.values()] + idx = list(chunk.keys()) + value = [sardana_value.value for sardana_value in list(chunk.values())] # filling the measurement records with self.data_lock: channel_data = self.data.get(obj_name, []) @@ -65,7 +65,7 @@ def get_table(self): '''Construct a table-like array with padded channel data as columns. Return the ''' with self.data_lock: - max_len = max([len(d) for d in self.data.values()]) + max_len = max([len(d) for d in list(self.data.values())]) dtype_spec = [] table = [] for k in sorted(self.data.keys()): @@ -73,5 +73,5 @@ def get_table(self): v.extend([None] * (max_len - len(v))) table.append(v) dtype_spec.append((k, self.dtype)) - a = numpy.array(zip(*table), dtype=dtype_spec) + a = numpy.array(list(zip(*table)), dtype=dtype_spec) return a diff --git a/src/sardana/release.py b/src/sardana/release.py index 1524868171..c4d742b604 100644 --- a/src/sardana/release.py +++ b/src/sardana/release.py @@ -28,7 +28,7 @@ """ -Release data for the taurus project. It contains the following members: +Release data for the sardana project. It contains the following members: - version : (str) version string - description : (str) brief description @@ -47,17 +47,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '2.8.6' - -# generate version_info and revision (**deprecated** since v 2.1.2--alpha). -if '-' in version: - _v, _rel = version.split('-') -else: - _v, _rel = version, '' - -_v = tuple([int(n) for n in _v.split('.')]) -version_info = _v + (_rel, 0) # deprecated, do not use -revision = str(version_info[4]) # deprecated, do not use +version = '3.0.3' description = "instrument control and data acquisition system" diff --git a/src/sardana/requirements.py b/src/sardana/requirements.py deleted file mode 100644 index 8ed81ce2c6..0000000000 --- a/src/sardana/requirements.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python - -############################################################################## -## -# This file is part of Sardana -## -# http://www.sardana-controls.org/ -## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain -## -# Sardana is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -## -# Sardana is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -## -# You should have received a copy of the GNU Lesser General Public License -# along with Sardana. If not, see . -## -############################################################################## - -""" """ - -from __future__ import absolute_import - -__docformat__ = 'restructuredtext' - -__all__ = ["check_requirements"] - -import sys - -__requires__ = { - # module minimum - "Python": (2, 6, 0), - "PyTango": (7, 2, 3), - "taurus.core": (3, 10), -} - - -def check_requirements(exec_name=None): - - if exec_name is None: - exec_name = sys.argv[0] - - pyver_ = __requires__['Python'] - pytangover_ = __requires__['PyTango'] - taurusver_ = __requires__['taurus.core'] - - pyver_str_ = ".".join(map(str, pyver_)) - pytangover_str_ = ".".join(map(str, pytangover_)) - taurusver_str_ = ".".join(map(str, taurusver_)) - - pyver = sys.version_info[:3] - pyver_str = ".".join(map(str, pyver)) - - if pyver < pyver_: - print "Sardana requires python %s. Installed version is %s" % (pyver_str_, pyver_str) - sys.exit(-1) - - pytangover = None - try: - import PyTango - pytangover = PyTango.Release.version_info[:3] - except ImportError: - pass - except: - pytangover = tuple(map(int, PyTango.__version__.split('.', 3))) - - if pytangover is None: - print "%s requires PyTango %s. No version installed" % (exec_name, pytangover_str_,) - sys.exit(-1) - if pytangover < pytangover_: - pytangover_str = ".".join(map(str, pytangover)) - print "%s requires PyTango %s. Installed version is %s" % (exec_name, pytangover_str_, pytangover_str) - sys.exit(-1) - - # TODO: add itango as runtime dependency of spock - # now it is not possible because itango does not provide info about its - # version - - taurusver = None - try: - import taurus - taurusver = taurus.Release.version_info[:3] - except ImportError: - pass - except: - taurusver = tuple(map(int, taurus.Release.version.split('.', 3))) - - if taurusver is None: - print "%s requires taurus %s. No version installed" % (exec_name, taurusver_str_,) - sys.exit(-1) - if taurusver < taurusver_: - taurusver_str = ".".join(map(str, taurusver)) - print "%s requires taurus %s. Installed version is %s" % (exec_name, taurusver_str_, taurusver_str) - sys.exit(-1) - - try: - from lxml import etree - except: - print "Could not find any suitable XML library" - sys.exit(-1) diff --git a/src/sardana/sardanaattribute.py b/src/sardana/sardanaattribute.py index c1cca95693..4999f12969 100644 --- a/src/sardana/sardanaattribute.py +++ b/src/sardana/sardanaattribute.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines the base classes for Sardana attributes""" -from __future__ import absolute_import + __all__ = ["SardanaAttribute", "SardanaSoftwareAttribute", "ScalarNumberAttribute", "SardanaAttributeConfiguration"] @@ -37,7 +37,7 @@ import datetime from .sardanaevent import EventGenerator, EventType -from .sardanadefs import ScalarNumberFilter +from .sardanadefs import ScalarNumberFilter, AttrQuality from .sardanavalue import SardanaValue @@ -54,6 +54,7 @@ def __init__(self, obj, name=None, initial_value=None, **kwargs): self._r_value = None self._last_event_value = None self._w_value = None + self._quality = AttrQuality.Valid self.filter = lambda a, b: True self.config = SardanaAttributeConfiguration() if initial_value is not None: @@ -212,6 +213,12 @@ def _get_write_value_obj(self): if self.has_write_value(): return self._w_value + def get_quality(self): + return self._quality + + def set_quality(self, quality): + self._quality = quality + def get_exc_info(self): """Returns the exception information (like :func:`sys.exc_info`) about last attribute readout or None if last read did not generate an @@ -291,6 +298,8 @@ def fire_read_event(self, propagate=1): "current read value for this attribute") w_value = property(get_write_value, set_write_value, "current write value for this attribute") + quality = property(get_quality, set_quality, + "current quality for this attribute") timestamp = property(get_timestamp, doc="the read timestamp") w_timestamp = property(get_write_timestamp, doc="the write timestamp") error = property(in_error) diff --git a/src/sardana/sardanabase.py b/src/sardana/sardanabase.py index 5a462df28a..1d5fc7c223 100644 --- a/src/sardana/sardanabase.py +++ b/src/sardana/sardanabase.py @@ -26,7 +26,8 @@ """This module is part of the Python Sardana library. It defines the base classes for Sardana object""" -from __future__ import absolute_import + +import sys __all__ = ["SardanaBaseObject", "SardanaObjectID"] @@ -53,8 +54,8 @@ def __init__(self, **kwargs): EventGenerator.__init__(self) EventReceiver.__init__(self) self._type = kwargs.pop('elem_type') - self._name = intern(kwargs.pop('name')) - self._full_name = intern(kwargs.pop('full_name')) + self._name = sys.intern(kwargs.pop('name')) + self._full_name = sys.intern(kwargs.pop('full_name')) self._frontend = None Logger.__init__(self, self._name) self._manager = weakref.ref(kwargs.pop('manager')) @@ -162,7 +163,7 @@ def get_interface_names(self): The sequence of interfaces this object implements. :rtype: sequence<:obj:`str`>""" - return map(Interface.get, self.get_interfaces()) + return list(map(Interface.get, self.get_interfaces())) def serialize(self, *args, **kwargs): kwargs['name'] = self.name diff --git a/src/sardana/sardanabuffer.py b/src/sardana/sardanabuffer.py index 31e7585566..16c51e447a 100644 --- a/src/sardana/sardanabuffer.py +++ b/src/sardana/sardanabuffer.py @@ -26,17 +26,13 @@ """This module is part Sardana Python library. It defines the base clases for Sardana buffers""" -from __future__ import absolute_import + __all__ = ["SardanaBuffer", "LateValueException", "EarlyValueException"] import weakref -try: - from collections import OrderedDict -except ImportError: - # For Python < 2.7 - from ordereddict import OrderedDict +from collections import OrderedDict from .sardanavalue import SardanaValue from .sardanaevent import EventGenerator, EventType diff --git a/src/sardana/sardanacontainer.py b/src/sardana/sardanacontainer.py index 7e64de7cfb..5248ca0f7d 100644 --- a/src/sardana/sardanacontainer.py +++ b/src/sardana/sardanacontainer.py @@ -26,7 +26,7 @@ """This module is part of the Python Pool libray. It defines the base classes for a pool container element""" -from __future__ import absolute_import + __all__ = ["SardanaContainer"] @@ -132,11 +132,11 @@ def get_element(self, **kwargs): :throw: KeyError """ - if kwargs.has_key("id"): + if "id" in kwargs: id = kwargs.pop("id") return self.get_element_by_id(id, **kwargs) - if kwargs.has_key("full_name"): + if "full_name" in kwargs: full_name = kwargs.pop("full_name") return self.get_element_by_full_name(full_name, **kwargs) @@ -204,7 +204,7 @@ def get_elements_by_type(self, t): elem_types_dict = self._element_types.get(t) if elem_types_dict is None: return [] - return elem_types_dict.values() + return list(elem_types_dict.values()) def get_element_names_by_type(self, t): """Returns a list of all pool object names of the given type diff --git a/src/sardana/sardanacustomsettings.py b/src/sardana/sardanacustomsettings.py index ce3bc15a08..97c4859628 100644 --- a/src/sardana/sardanacustomsettings.py +++ b/src/sardana/sardanacustomsettings.py @@ -30,7 +30,7 @@ """ #: UnitTest door name: the door to be used by unit tests. -#: UNITTEST_DOOR_NAME Must be defined for running sardana unittests. +#: UNITTEST_DOOR_NAME must be defined for running sardana unittests. UNITTEST_DOOR_NAME = "door/demo1/1" #: UnitTests Pool DS name: Pool DS to use in unit tests. UNITTEST_POOL_DS_NAME = "unittest1" @@ -51,9 +51,14 @@ #: Use this map in order to avoid ambiguity with scan recorders (file) if #: extension is intended to be the recorder selector. -#: dict -#: key - scan file extension e.g. ".h5" -#: value - recorder name +#: Set it to a dict where: +#: +#: - key - scan file extension e.g. ".h5" +#: - value - recorder name +#: +#: The SCAN_RECORDER_MAP will make an union with the dynamically (created map +#: at the MacroServer startup) taking precedence in case the extensions repeats +#: in both of them. SCAN_RECORDER_MAP = None #: Filter for macro logging: name of the class to be used as filter @@ -76,3 +81,22 @@ #: Type of encoding for ValueRefBuffer Tango attribute of experimental #: channels VALUE_REF_BUFFER_CODEC = "pickle" + +#: Database backend for MacroServer environment implemented using shelve. +#: Available options: +#: +#: - None (default) - first try "gnu" and if not available fallback to "dumb" +#: - "gnu" - better performance than dumb, but requires installation of +#: additional package e.g. python3-gdbm on Debian. At the time of writing of +#: this documentation it is not available for conda. +#: - "dumb" - worst performance but directly available with Python 3. +MS_ENV_SHELVE_BACKEND = None + +#: macroexecutor maximum number of macros stored in the history. +#: Available options: +#: +#: - None (or no setting) - unlimited history (may slow down the GUI operation +#: if grows too big) +#: - 0 - history will not be filled +#: - - max number of macros stored in the history +MACROEXECUTOR_MAX_HISTORY = 100 diff --git a/src/sardana/sardanadefs.py b/src/sardana/sardanadefs.py index 4b0cad17f2..621a117db9 100644 --- a/src/sardana/sardanadefs.py +++ b/src/sardana/sardanadefs.py @@ -25,11 +25,12 @@ """This module contains the most generic sardana constants and enumerations""" -from __future__ import absolute_import + +import collections __all__ = ["EpsilonError", "SardanaServer", "ServerRunMode", "State", - "DataType", "DataFormat", "DataAccess", "DTYPE_MAP", "R_DTYPE_MAP", - "DACCESS_MAP", + "DataType", "DataFormat", "DataAccess", "AttrQuality", + "DTYPE_MAP", "R_DTYPE_MAP", "DACCESS_MAP", "from_dtype_str", "from_access_str", "to_dtype_dformat", "to_daccess", "InvalidId", "InvalidAxis", "ElementType", "Interface", "Interfaces", "InterfacesExpanded", @@ -42,6 +43,7 @@ __docformat__ = 'restructuredtext' import math +from enum import IntEnum from taurus.core.util.enumeration import Enumeration @@ -119,12 +121,26 @@ def __repr__(self): "ReadWrite", "Invalid")) + +class AttrQuality(IntEnum): + """Attribute quality factor""" + + #: Attribute is valid + Valid = 0 + #: Attribute is invalid + Invalid = 1 + #: Attribute is in alarm + Alarm = 2 + #: Attribute is changing e.g. element is in operation + Changing = 3 + #: Attribute is in warning + Warning = 4 + #: dictionary dict DTYPE_MAP = { 'int': DataType.Integer, 'integer': DataType.Integer, int: DataType.Integer, - long: DataType.Integer, 'long': DataType.Integer, DataType.Integer: DataType.Integer, 'float': DataType.Double, @@ -146,7 +162,6 @@ def __repr__(self): 'int': int, 'integer': int, int: int, - long: int, 'long': int, DataType.Integer: int, 'float': float, @@ -193,7 +208,7 @@ def from_dtype_str(dtype): dformat = DataFormat.Scalar if dtype is None: dtype = 'float' - elif isinstance(dtype, (str, unicode)): + elif isinstance(dtype, str): dtype = dtype.lower() if dtype.startswith("pytango."): dtype = dtype[len("pytango."):] @@ -215,7 +230,7 @@ def from_access_str(access): :type dtype: :obj:`str` :return: a simple string for the given access :rtype: :obj:`str`""" - if isinstance(access, (str, unicode)): + if isinstance(access, str): access = access.lower() if access.startswith("pytango."): access = access[len("pytango."):] @@ -234,16 +249,16 @@ def to_dtype_dformat(data): """ import operator dtype, dformat = data, DataFormat.Scalar - if isinstance(data, (str, unicode)): + if isinstance(data, str): dtype, dformat = from_dtype_str(data) - elif operator.isSequenceType(data): + elif isinstance(data, collections.Sequence): dformat = DataFormat.OneD dtype = data[0] if isinstance(dtype, str): dtype, dformat2 = from_dtype_str(dtype) if dformat2 == DataFormat.OneD: dformat = DataFormat.TwoD - elif operator.isSequenceType(dtype): + elif isinstance(dtype, collections.Sequence): dformat = DataFormat.TwoD dtype = dtype[0] if isinstance(dtype, str): @@ -262,7 +277,7 @@ def to_daccess(daccess): :rtype: :obj:`DataAccess`""" if daccess is None: daccess = DataAccess.ReadWrite - elif isinstance(daccess, (str, unicode)): + elif isinstance(daccess, str): daccess = DACCESS_MAP.get( from_access_str(daccess), DataAccess.ReadWrite) return daccess @@ -309,50 +324,51 @@ def to_daccess(daccess): #: a set containning all "controllable" element types. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_ELEMENTS = set((ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, - ET.OneDExpChannel, ET.TwoDExpChannel, ET.TriggerGate, - ET.ComChannel, ET.IORegister, ET.PseudoMotor, - ET.PseudoCounter, ET.Constraint)) +TYPE_ELEMENTS = {ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, + ET.OneDExpChannel, ET.TwoDExpChannel, ET.TriggerGate, + ET.ComChannel, ET.IORegister, ET.PseudoMotor, + ET.PseudoCounter, ET.Constraint} #: a set containing all group element types. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_GROUP_ELEMENTS = set((ET.MotorGroup, ET.MeasurementGroup)) +TYPE_GROUP_ELEMENTS = {ET.MotorGroup, ET.MeasurementGroup} #: a set containing the type of elements which are moveable. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_MOVEABLE_ELEMENTS = set((ET.Motor, ET.PseudoMotor, ET.MotorGroup)) +TYPE_MOVEABLE_ELEMENTS = {ET.Motor, ET.PseudoMotor, ET.MotorGroup} #: a set containing the possible types of physical elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_PHYSICAL_ELEMENTS = set((ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, - ET.OneDExpChannel, ET.TwoDExpChannel, ET.TriggerGate, - ET.ComChannel, ET.IORegister)) +TYPE_PHYSICAL_ELEMENTS = {ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, + ET.OneDExpChannel, ET.TwoDExpChannel, ET.TriggerGate, + ET.ComChannel, ET.IORegister} #: a set containing the possible types of acquirable elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_ACQUIRABLE_ELEMENTS = set((ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, - ET.OneDExpChannel, ET.TwoDExpChannel, - ET.ComChannel, ET.IORegister, ET.PseudoMotor, - ET.PseudoCounter)) +TYPE_ACQUIRABLE_ELEMENTS = {ET.Motor, ET.CTExpChannel, ET.ZeroDExpChannel, + ET.OneDExpChannel, ET.TwoDExpChannel, + ET.ComChannel, ET.IORegister, ET.PseudoMotor, + ET.PseudoCounter} #: a set containing the possible measure-able elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_COUNTABLE_ELEMENTS = set((ET.CTExpChannel, ET.OneDExpChannel, - ET.TwoDExpChannel, ET.MeasurementGroup)) +TYPE_COUNTABLE_ELEMENTS = {ET.CTExpChannel, ET.OneDExpChannel, + ET.TwoDExpChannel, ET.MeasurementGroup} #: a set containing the possible types of experimental channel elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_EXP_CHANNEL_ELEMENTS = set((ET.CTExpChannel, ET.ZeroDExpChannel, - ET.OneDExpChannel, ET.TwoDExpChannel, ET.PseudoCounter)) +TYPE_EXP_CHANNEL_ELEMENTS = {ET.CTExpChannel, ET.ZeroDExpChannel, + ET.OneDExpChannel, ET.TwoDExpChannel, + ET.PseudoCounter} #: a set containing the possible timer-able elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_TIMERABLE_ELEMENTS = set((ET.CTExpChannel, ET.OneDExpChannel, - ET.TwoDExpChannel)) +TYPE_TIMERABLE_ELEMENTS = {ET.CTExpChannel, ET.OneDExpChannel, + ET.TwoDExpChannel} #: a set containing the possible types of pseudo elements. #: Constant values belong to :class:`~sardana.sardanadefs.ElementType` -TYPE_PSEUDO_ELEMENTS = set((ET.PseudoMotor, ET.PseudoCounter)) +TYPE_PSEUDO_ELEMENTS = {ET.PseudoMotor, ET.PseudoCounter} # : An enumeration describing the all possible sardana interfaces # SardanaInterface = Enumeration("SardanaInterface", ( \ @@ -396,51 +412,54 @@ def to_daccess(daccess): INTERFACES = { "Meta": (set(), "A generic sardana meta object"), "Object": (set(), "A generic sardana object"), - "Element": (set(("Object",)), "A generic sardana element"), - "Class": (set(("Object",)), "A generic sardana class"), - "Function": (set(("Object",)), "A generic sardana function"), - "Library": (set(("Object",)), "A generic sardana library"), - "PoolObject": (set(("Object",)), "A Pool object"), - "PoolElement": (set(("Element", "PoolObject")), "A Pool element"), - "Pool": (set(("PoolElement",)), "A Pool"), - "Controller": (set(("PoolElement",)), "A controller"), - "Moveable": (set(("PoolElement",)), "A moveable element"), - "Acquirable": (set(("PoolElement",)), "An acquirable element"), - "Countable": (set(("PoolElement",)), "A countable element"), - "Instrument": (set(("PoolElement",)), "An instrument"), - "Motor": (set(("Moveable", "Acquirable")), "a motor"), - "PseudoMotor": (set(("Moveable", "Acquirable")), "A pseudo motor"), - "IORegister": (set(("Acquirable",)), "An IO register"), - "ExpChannel": (set(("Acquirable",)), "A generic experimental channel"), - "CTExpChannel": (set(("ExpChannel", "Countable")), + "Element": ({"Object"}, "A generic sardana element"), + "Class": ({"Object"}, "A generic sardana class"), + "Function": ({"Object"}, "A generic sardana function"), + "Library": ({"Object"}, "A generic sardana library"), + "PoolObject": ({"Object"}, "A Pool object"), + "PoolElement": ({"Element", "PoolObject"}, "A Pool element"), + "Pool": ({"PoolElement"}, "A Pool"), + "Controller": ({"PoolElement"}, "A controller"), + "Moveable": ({"PoolElement"}, "A moveable element"), + "Acquirable": ({"PoolElement"}, "An acquirable element"), + "Countable": ({"PoolElement"}, "A countable element"), + "Instrument": ({"PoolElement"}, "An instrument"), + "Motor": ({"Moveable", "Acquirable"}, "a motor"), + "PseudoMotor": ({"Moveable", "Acquirable"}, "A pseudo motor"), + "IORegister": ({"Acquirable"}, "An IO register"), + "ExpChannel": ({"Acquirable"}, "A generic experimental channel"), + "CTExpChannel": ({"ExpChannel", "Countable"}, "A counter/timer experimental channel"), - "ZeroDExpChannel": (set(("ExpChannel",)), "A 0D experimental channel"), - "OneDExpChannel": (set(("ExpChannel", "Countable")), + "ZeroDExpChannel": ({"ExpChannel"}, "A 0D experimental channel"), + "OneDExpChannel": ({"ExpChannel", "Countable"}, "A 1D experimental channel"), - "TwoDExpChannel": (set(("ExpChannel", "Countable")), + "TwoDExpChannel": ({"ExpChannel", "Countable"}, "A 2D experimental channel"), - "TriggerGate": (set(("PoolElement",)), "A trigger/gate"), - "PseudoCounter": (set(("ExpChannel",)), "A pseudo counter"), - "ComChannel": (set(("PoolElement",)), "A communication channel"), + "TriggerGate": ({"PoolElement"}, "A trigger/gate"), + "PseudoCounter": ({"ExpChannel"}, "A pseudo counter"), + "ComChannel": ({"PoolElement"}, "A communication channel"), "MotorGroup": (set(("PoolElement",),), "A motor group"), - "MeasurementGroup": (set(("PoolElement", "Countable")), + "MeasurementGroup": ({"PoolElement", "Countable"}, "A measurement group"), - "ControllerLibrary": (set(("Library", "PoolObject")), "A controller library"), - "ControllerClass": (set(("Class", "PoolObject")), "A controller class"), - "Constraint": (set(("PoolObject",)), "A constraint"), - "External": (set(("Object",)), "An external object"), - - "MacroServerObject": (set(("Object",)), "A generic macro server object"), - "MacroServerElement": (set(("Element", "MacroServerObject")), "A generic macro server element"), - "MacroServer": (set(("MacroServerElement",)), "A MacroServer"), - "Door": (set(("MacroServerElement",)), "A macro server door"), - "MacroLibrary": (set(("Library", "MacroServerObject")), "A macro server library"), - "MacroCode": (set(("MacroServerObject",)), "A macro server macro code"), - "MacroClass": (set(("Class", "MacroCode")), "A macro server macro class"), - "MacroFunction": (set(("Function", "MacroCode")), "A macro server macro function"), - "Macro": (set(("MacroClass", "MacroFunction")), "A macro server macro"), - - "ParameterType": (set(("Meta",)), "A generic macro server parameter type"), + "ControllerLibrary": ({"Library", "PoolObject"}, "A controller library"), + "ControllerClass": ({"Class", "PoolObject"}, "A controller class"), + "Constraint": ({"PoolObject"}, "A constraint"), + "External": ({"Object"}, "An external object"), + + "MacroServerObject": ({"Object"}, "A generic macro server object"), + "MacroServerElement": ({"Element", "MacroServerObject"}, + "A generic macro server element"), + "MacroServer": ({"MacroServerElement"}, "A MacroServer"), + "Door": ({"MacroServerElement"}, "A macro server door"), + "MacroLibrary": ({"Library", "MacroServerObject"}, + "A macro server library"), + "MacroCode": ({"MacroServerObject"}, "A macro server macro code"), + "MacroClass": ({"Class", "MacroCode"}, "A macro server macro class"), + "MacroFunction": ({"Function", "MacroCode"}, + "A macro server macro function"), + "Macro": ({"MacroClass", "MacroFunction"}, "A macro server macro"), + + "ParameterType": ({"Meta"}, "A generic macro server parameter type"), } #: a dictionary containing the *all* interfaces supported by each type @@ -450,7 +469,7 @@ def to_daccess(daccess): def __expand(name): direct_expansion, _ = INTERFACES[name] - if isinstance(direct_expansion, (str, unicode)): + if isinstance(direct_expansion, str): direct_expansion = direct_expansion, exp = set(direct_expansion) for e in direct_expansion: @@ -482,7 +501,7 @@ def __expand_sardana_interface_data(si_map, name, curr_id): curr_id = __expand_sardana_interface_data( si_map, interface, curr_id) d |= si_map[interface] - si_map[name] = long(d | curr_id) + si_map[name] = int(d | curr_id) return 2 * curr_id @@ -495,7 +514,7 @@ def __root_expand_sardana_interface_data(): #: An enumeration describing the all possible sardana interfaces Interface = Enumeration("Interface", - __root_expand_sardana_interface_data().items()) + list(__root_expand_sardana_interface_data().items())) def __create_sardana_interfaces(): diff --git a/src/sardana/sardanaevent.py b/src/sardana/sardanaevent.py index b9e6f7991b..1348affdb8 100644 --- a/src/sardana/sardanaevent.py +++ b/src/sardana/sardanaevent.py @@ -26,7 +26,7 @@ """This module is part of the Python Pool libray. It defines the base classes for pool event mechanism""" -from __future__ import absolute_import + __all__ = ["EventGenerator", "EventReceiver", "EventType"] diff --git a/src/sardana/sardanaexception.py b/src/sardana/sardanaexception.py index 9bef60311c..cbcf6fa0e2 100644 --- a/src/sardana/sardanaexception.py +++ b/src/sardana/sardanaexception.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines the base classes for sardana exceptions""" -from __future__ import absolute_import + __all__ = ["AbortException", "SardanaException", "SardanaExceptionList", "UnknownCode", "UnknownLibrary", "LibraryError", diff --git a/src/sardana/sardanalock.py b/src/sardana/sardanalock.py index 02690129f9..94dd3d940a 100644 --- a/src/sardana/sardanalock.py +++ b/src/sardana/sardanalock.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines a *slow* lock class that provides additional debugging information""" -from __future__ import absolute_import + __all__ = ["SardanaLock"] diff --git a/src/sardana/sardanamanager.py b/src/sardana/sardanamanager.py index 96e749b3be..15932ef4b9 100644 --- a/src/sardana/sardanamanager.py +++ b/src/sardana/sardanamanager.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines the base class for Sardana manager""" -from __future__ import absolute_import + __all__ = ["SardanaElementManager", "SardanaIDManager"] diff --git a/src/sardana/sardanameta.py b/src/sardana/sardanameta.py index 1ccda440ed..fe9d951942 100644 --- a/src/sardana/sardanameta.py +++ b/src/sardana/sardanameta.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines the base classes for MetaLibrary and MetaClass""" -from __future__ import absolute_import + __all__ = ["SardanaLibrary", "SardanaClass", "SardanaFunction"] @@ -34,7 +34,6 @@ import os import inspect -import string import weakref import linecache import traceback @@ -81,7 +80,7 @@ def getsource(object): or code object. The source code is returned as a single string. An IOError is raised if the source code cannot be retrieved.""" lines, lnum = getsourcelines(object) - return string.join(lines, '') + return str.join('', lines) # End patch around inspect issue http://bugs.python.org/issue993580 # ---------------------------------------------------------------------------- @@ -131,8 +130,8 @@ def __init__(self, **kwargs): kwargs['full_name'] = file_path or name SardanaBaseObject.__init__(self, **kwargs) - def __cmp__(self, o): - return cmp(self.full_name, o.full_name) + def __lt__(self, o): + return self.full_name < o.full_name def __str__(self): return self.name @@ -180,7 +179,7 @@ def get_meta_classes(self): :return: a sequence of meta classes that belong to this library :rtype: seq<:class:~`sardana.sardanameta.SardanaClass`>""" - return self.meta_classes.values() + return list(self.meta_classes.values()) def has_meta_class(self, meta_class_name): """Returns True if the given meta class name belongs to this library @@ -218,7 +217,7 @@ def get_meta_functions(self): :return: a sequence of meta functions that belong to this library :rtype: seq<:class:~`sardana.sardanameta.SardanaFunction`>""" - return self.meta_functions.values() + return list(self.meta_functions.values()) def has_meta_function(self, meta_function_name): """Returns True if the given meta function name belongs to this library @@ -380,8 +379,8 @@ def serialize(self, *args, **kwargs): kwargs['file_name'] = self.file_name kwargs['path'] = self.path kwargs['description'] = self.description - kwargs['elements'] = self.meta_classes.keys() + \ - self.meta_functions.keys() + kwargs['elements'] = list(self.meta_classes.keys()) + \ + list(self.meta_functions.keys()) if self.exc_info is None: kwargs['exc_summary'] = None kwargs['exc_info'] = None @@ -524,7 +523,7 @@ class SardanaFunction(SardanaCode): def __init__(self, **kwargs): function = kwargs.pop('function') kwargs['code'] = function - kwargs['name'] = kwargs.pop('name', function.func_name) + kwargs['name'] = kwargs.pop('name', function.__name__) SardanaCode.__init__(self, **kwargs) @property diff --git a/src/sardana/sardanamodulemanager.py b/src/sardana/sardanamodulemanager.py index d45623fe9a..680cd7e494 100644 --- a/src/sardana/sardanamodulemanager.py +++ b/src/sardana/sardanamodulemanager.py @@ -26,8 +26,8 @@ """This module is part of the Python Sardana library. It defines the base classes for module manager""" -from __future__ import with_statement -from __future__ import absolute_import + + __all__ = ["ModuleManager"] @@ -102,7 +102,7 @@ def add_python_path(self, path): with self._path_lock: path_id = self.get_new_id() - for _, p_info in pif.items(): + for _, p_info in list(pif.items()): p_info[0] += path_len pif[path_id] = [0, path] @@ -285,15 +285,15 @@ def loadModule(self, module_name, path=None): def unloadModule(self, module_name): """Unloads the given module name""" - if self._modules.has_key(module_name): + if module_name in self._modules: self.debug("unloading module %s" % module_name) - assert(sys.modules.has_key(module_name)) + assert(module_name in sys.modules) self._modules.pop(module_name) del sys.modules[module_name] def unloadModules(self, module_list=None): """Unloads the given module name""" - modules = module_list or self._modules.keys() + modules = module_list or list(self._modules.keys()) for module in modules: self.unloadModule(module) diff --git a/src/sardana/sardanathreadpool.py b/src/sardana/sardanathreadpool.py index 66faf4e29e..40d29390c4 100644 --- a/src/sardana/sardanathreadpool.py +++ b/src/sardana/sardanathreadpool.py @@ -25,8 +25,8 @@ """This module contains the function to access sardana thread pool""" -from __future__ import with_statement -from __future__ import absolute_import + + __all__ = ["get_thread_pool"] @@ -34,12 +34,36 @@ import threading -from taurus.core.util.threadpool import ThreadPool +from taurus.core.util.threadpool import ThreadPool, Worker + __thread_pool_lock = threading.Lock() __thread_pool = None +class OmniWorker(Worker): + + def run(self): + try: + import tango + except ImportError: + Worker.run(self) + # Tango is not thread safe when using threading.Thread. One must + # use omni threads instead. This was confirmed for parallel + # event subscriptions in PyTango#307. Use EnsureOmniThread introduced + # in PyTango#327 whenever available. + else: + if hasattr(tango, "EnsureOmniThread"): + with tango.EnsureOmniThread(): + Worker.run(self) + else: + import taurus + taurus.warning("Your Sardana system is affected by bug " + "tango-controls/pytango#307. Please use " + "PyTango with tango-controls/pytango#327.") + Worker.run(self) + + def get_thread_pool(): """Returns the global pool of threads for Sardana @@ -50,5 +74,16 @@ def get_thread_pool(): global __thread_pool_lock with __thread_pool_lock: if __thread_pool is None: - __thread_pool = ThreadPool(name="SardanaTP", Psize=10) + # protect older versions of Taurus (without the worker_cls + # argument) remove it whenever we bump Taurus dependency + try: + __thread_pool = ThreadPool(name="SardanaTP", Psize=10, + worker_cls=OmniWorker) + except TypeError: + import taurus + taurus.warning("Your Sardana system is affected by bug " + "tango-controls/pytango#307. Please use " + "Taurus with taurus-org/taurus#1081.") + __thread_pool = ThreadPool(name="SardanaTP", Psize=10) + return __thread_pool diff --git a/src/sardana/sardanautils.py b/src/sardana/sardanautils.py index b3108f25e7..2be31f671e 100644 --- a/src/sardana/sardanautils.py +++ b/src/sardana/sardanautils.py @@ -26,15 +26,16 @@ """This module is part of the Python Sardana library. It defines some utility methods""" -from __future__ import absolute_import + __all__ = ["is_pure_str", "is_non_str_seq", "is_integer", "is_number", "is_bool", "check_type", "assert_type", "str_to_value", "is_callable", "translate_version_str2int", - "translate_version_str2list"] + "translate_version_str2list", "py2_round", "recur_map"] __docformat__ = 'restructuredtext' +import math import numpy import numbers import collections @@ -47,24 +48,6 @@ __DTYPE_MAP = dict(DTYPE_MAP) -__use_unicode = False -try: - unicode - __use_unicode = True - __str_klasses.append(unicode) - __DTYPE_MAP[unicode] = DataType.String -except: - pass - -__use_long = False -try: - long - __use_long = True - __int_klasses.append(long) - __DTYPE_MAP[long] = DataType.Integer -except: - pass - __bool_klasses = [bool] + __int_klasses __str_klasses = tuple(__str_klasses) @@ -205,3 +188,27 @@ def translate_version_str2list(version_str, depth=2): i = 0 ver.append(i) return ver + + +def py2_round(x, d=0): + p = 10 ** d + if x > 0: + return float(math.floor((x * p) + 0.5)) / p + else: + return float(math.ceil((x * p) - 0.5)) / p + + +def recur_map(fun, data, keep_none=False): + """Recursive map. Similar to map, but maintains the list objects structure + + :param fun: the same purpose as in map function + :param data: the same purpose as in map function + :param keep_none: keep None elements without applying fun + """ + if hasattr(data, "__iter__") and not isinstance(data, str): + return [recur_map(fun, elem, keep_none) for elem in data] + else: + if keep_none is True and data is None: + return data + else: + return fun(data) diff --git a/src/sardana/sardanavalue.py b/src/sardana/sardanavalue.py index f002c88ce3..449706fe7a 100644 --- a/src/sardana/sardanavalue.py +++ b/src/sardana/sardanavalue.py @@ -26,7 +26,7 @@ """This module is part of the Python Sardana libray. It defines the base classes for Sardana values""" -from __future__ import absolute_import + __all__ = ["SardanaValue"] diff --git a/src/sardana/spock/__init__.py b/src/sardana/spock/__init__.py index 496c5b59f5..12d6d6f5f6 100644 --- a/src/sardana/spock/__init__.py +++ b/src/sardana/spock/__init__.py @@ -26,8 +26,8 @@ """This package provides spock""" -from genutils import load_ipython_extension, unload_ipython_extension, \ - load_config, run +from .genutils import (load_ipython_extension, unload_ipython_extension, # noqa + load_config, run) # noqa def main(): diff --git a/src/sardana/spock/genutils.py b/src/sardana/spock/genutils.py index 9d21a3b2c3..788581bc01 100644 --- a/src/sardana/spock/genutils.py +++ b/src/sardana/spock/genutils.py @@ -48,13 +48,7 @@ def get_ipython_version(): import IPython v = None try: - try: - v = IPython.Release.version - except: - try: - v = IPython.release.version - except: - pass + v = IPython.release.version except: pass return v @@ -64,10 +58,8 @@ def get_ipython_version_list(): ipv_str = get_ipython_version() return translate_version_str2list(ipv_str) + ipv = get_ipython_version_list() -if ipv >= [0, 10] and ipv < [0, 11]: - from ipython_00_10.genutils import * -elif ipv >= [0, 11] and ipv < [1, 0]: - from ipython_00_11.genutils import * -else: - from ipython_01_00.genutils import * +if ipv < [1, 0]: + raise Exception("IPython > 1 is required") +from .ipython_01_00.genutils import * # noqa diff --git a/src/sardana/spock/inputhandler.py b/src/sardana/spock/inputhandler.py index 1a5f528bbf..7b9b6f93c3 100644 --- a/src/sardana/spock/inputhandler.py +++ b/src/sardana/spock/inputhandler.py @@ -34,7 +34,7 @@ from taurus.core import TaurusManager from taurus.core.util.singleton import Singleton -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus.qt.qtgui.dialog import TaurusMessageBox, TaurusInputDialog from sardana.taurus.core.tango.sardana.macroserver import BaseInputHandler @@ -54,7 +54,7 @@ def input(self, input_data=None): prompt = input_data.get('prompt') if 'data_type' in input_data: if input_data['data_type'] != 'String': - print("Accepted input: %s" % input_data['data_type']) + print(("Accepted input: %s" % input_data['data_type'])) ret = dict(input=None, cancel=False) try: if prompt is None: @@ -66,12 +66,12 @@ def input(self, input_data=None): return ret def input_timeout(self, input_data): - print "SpockInputHandler input timeout" + print("SpockInputHandler input timeout") class MessageHandler(Qt.QObject): - messageArrived = Qt.pyqtSignal(object) + messageArrived = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, conn, parent=None): Qt.QObject.__init__(self, parent) @@ -120,12 +120,12 @@ def init(self, *args, **kwargs): def input(self, input_data=None): # parent process data_type = input_data.get('data_type', 'String') - if isinstance(data_type, (str, unicode)): + if isinstance(data_type, str): ms = genutils.get_macro_server() interfaces = ms.getInterfaces() if data_type in interfaces: input_data['data_type'] = [ - elem.name for elem in interfaces[data_type].values()] + elem.name for elem in list(interfaces[data_type].values())] self._conn.send(input_data) ret = self._conn.recv() return ret @@ -138,7 +138,7 @@ def safe_run(self, conn): # child process try: return self.run(conn) - except Exception, e: + except Exception as e: msgbox = TaurusMessageBox(*sys.exc_info()) conn.send((e, False)) msgbox.exec_() @@ -148,13 +148,13 @@ def run(self, conn): self._conn = conn app = Qt.QApplication.instance() if app is None: - app = Qt.QApplication([]) + app = Qt.QApplication(['spock']) app.setQuitOnLastWindowClosed(False) self._msg_handler = MessageHandler(conn) TaurusManager().addJob(self.run_forever, None) app.exec_() conn.close() - print "Quit input handler" + print("Quit input handler") def run_forever(self): # child process diff --git a/src/sardana/spock/ipython_00_10/genutils.py b/src/sardana/spock/ipython_00_10/genutils.py deleted file mode 100644 index f5e075984e..0000000000 --- a/src/sardana/spock/ipython_00_10/genutils.py +++ /dev/null @@ -1,1168 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -############################################################################## -## -# This file is part of Sardana -## -# http://www.sardana-controls.org/ -## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain -## -# Sardana is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -## -# Sardana is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -## -# You should have received a copy of the GNU Lesser General Public License -# along with Sardana. If not, see . -## -############################################################################## - -"""This package provides the spock generic utilities""" - -__all__ = ['page', 'arg_split', 'get_gui_mode', 'get_pylab_mode', - 'get_color_mode', 'get_ipapi', - 'get_editor', 'ask_yes_no', 'spock_input', - 'translate_version_str2int', 'get_ipython_version', - 'get_ipython_version_number', 'get_python_version', - 'get_python_version_number', 'get_ipython_dir', - 'get_ipython_profiles', 'get_spock_profiles', - 'get_non_spock_profiles', 'get_spock_user_profile_module', - 'get_pytango_version', 'get_pytango_version_number', - 'get_server_for_device', 'get_macroserver_for_door', - 'get_device_from_user', 'get_tango_db', 'get_tango_host_from_user', - 'print_dev_from_class', 'from_name_to_tango', 'clean_up', - 'get_taurus_core_version', 'get_taurus_core_version_number', - 'check_requirements', 'get_door', 'get_macro_server', 'expose_magic', - 'unexpose_magic', 'expose_variable', 'expose_variables', - 'unexpose_variable', - 'create_spock_profile', 'check_for_upgrade', 'get_args', - 'init_console', 'init_magic', 'init_pre_spock', 'init_post_spock', - 'init_spock', 'start', 'mainloop', 'run', - 'load_ipython_extension', 'unload_ipython_extension', 'load_config', - 'MSG_FAILED', 'MSG_FAILED_WR', 'MSG_R', 'MSG_ERROR', - 'MSG_DONE', 'MSG_OK'] - -__docformat__ = 'restructuredtext' - -import sys -import os -import socket -import imp - -import IPython -import IPython.genutils - -try: - import PyTango - import itango -except ImportError: - # postpone error to check_requirements - pass - -from taurus.core.taurushelper import Factory -from taurus.core.util.codecs import CodecFactory - -from sardana.spock import exception -from sardana.spock import colors -from sardana import release - -arg_split = IPython.iplib.arg_split -page = IPython.genutils.page -TermColors = colors.TermColors - - -requirements = { - # module minimum recommended - "IPython": ("0.10.0", "0.10.0"), - "Python": ("2.6.0", "2.6.0"), - "PyTango": ("7.1.2", "7.2.0"), - # for the moment just for reference since itango does not provide version - # when using PyTango < 9 the dependency is >= 0.0.1 and < 0.1.0 - # when using PyTango >= 9 the dependency is >= 0.1.6 - "itango": ("0.0.1", "0.0.1"), - "taurus.core": ("2.0.0", "2.1.0") -} - -ENV_NAME = "_E" - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# IPython utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def get_gui_mode(): - if '-q4thread' in sys.argv: - return 'qt' - elif '-gthread' in sys.argv: - return 'gtk' - elif '-wthread' in sys.argv: - return 'wx' - return '' - - -def get_pylab_mode(): - return get_gui_mode() - - -def get_color_mode(): - return get_ipapi().options.colors - - -def get_ipapi(): - return IPython.ipapi.get() - - -def get_editor(): - return get_ipapi().options.editor - - -def ask_yes_no(prompt, default=None): - """Asks a question and returns a boolean (y/n) answer. - - If default is given (one of 'y','n'), it is used if the user input is - empty. Otherwise the question is repeated until an answer is given. - - An EOF is treated as the default answer. If there is no default, an - exception is raised to prevent infinite loops. - - Valid answers are: y/yes/n/no (match is not case sensitive).""" - - if default: - prompt = '%s [%s]' % (prompt, default) - return IPython.genutils.ask_yes_no(prompt, default) - - -def spock_input(prompt='', ps2='... '): - return IPython.genutils.raw_input_ext(prompt=prompt, ps2=ps2) - - -def translate_version_str2int(version_str): - """Translates a version string in format x[.y[.z[...]]] into a 000000 number""" - import math - # Get the current version number ignoring the release part ("-alpha") - num_version_str = version_str.split('-')[0] - parts = num_version_str.split('.') - i, v, l = 0, 0, len(parts) - if not l: - return v - while i < 3: - try: - v += int(parts[i]) * int(math.pow(10, (2 - i) * 2)) - l -= 1 - i += 1 - except ValueError: - return v - if not l: - return v - return v - - try: - v += 10000 * int(parts[0]) - l -= 1 - except ValueError: - return v - if not l: - return v - - try: - v += 100 * int(parts[1]) - l -= 1 - except ValueError: - return v - if not l: - return v - - try: - v += int(parts[0]) - l -= 1 - except ValueError: - return v - if not l: - return v - - -def get_ipython_version(): - """Returns the current IPython version""" - v = None - try: - try: - v = IPython.Release.version - except Exception: - try: - v = IPython.release.version - except Exception: - pass - except Exception: - pass - return v - - -def get_ipython_version_number(): - """Returns the current IPython version number""" - ipyver_str = get_ipython_version() - if ipyver_str is None: - return None - return translate_version_str2int(ipyver_str) - - -def get_python_version(): - return '.'.join(map(str, sys.version_info[:3])) - - -def get_python_version_number(): - pyver_str = get_python_version() - return translate_version_str2int(pyver_str) - - -def get_ipython_dir(): - """Find the ipython local directory. Usually is /.ipython""" - if hasattr(itango, "get_ipython_dir"): - return itango.get_ipython_dir() - - if hasattr(IPython.iplib, 'get_ipython_dir'): - # Starting from ipython 0.9 they hadded this method - return IPython.iplib.get_ipython_dir() - - # Try to find the profile in the current directory and then in the - # default IPython dir - #userdir = os.path.realpath(os.path.curdir) - home_dir = IPython.genutils.get_home_dir() - - if os.name == 'posix': - ipdir = '.ipython' - else: - ipdir = '_ipython' - ipdir = os.path.join(home_dir, ipdir) - ipythondir = os.path.abspath(os.environ.get('IPYTHONDIR', ipdir)) - return ipythondir - - -def get_ipython_profiles(): - """Helper function to find all ipython profiles""" - if hasattr(itango, "get_ipython_profiles"): - return itango.get_ipython_profiles() - - ret = [] - ipydir = get_ipython_dir() - if os.path.isdir(ipydir): - for i in os.listdir(ipydir): - fullname = os.path.join(ipydir, i) - if i.startswith("ipy_profile_") and i.endswith(".py"): - if os.path.isfile(fullname): - ret.append(i[len("ipy_profile_"):i.rfind(".")]) - return ret - - -def get_spock_profiles(ipython_profiles=None): - """Helper function to find all spock ipython profiles""" - ret = [] - ipydir = get_ipython_dir() - if not os.path.isdir(ipydir): - return ret - if ipython_profiles is None: - ipython_profiles = get_ipython_profiles() - ret = [] - for profile in ipython_profiles: - profile_f = os.path.join(ipydir, "ipy_profile_%s.py" % profile) - if not os.path.isfile(profile_f): - continue - try: - for i, l in enumerate(file(profile_f)): - if i > 10: - break - if l.find("spock_creation_version") >= 0: - ret.append(profile) - break - except: - pass - return ret - - -def get_non_spock_profiles(ipython_profiles=None): - """Helper function to find all non spock ipython profiles""" - if ipython_profiles is None: - ipython_profiles = get_ipython_profiles() - ipython_profiles = set(ipython_profiles) - spock_profiles = set(get_spock_profiles(ipython_profiles=ipython_profiles)) - return ipython_profiles.difference(spock_profiles) - - -def get_spock_user_profile_module(profile_name): - return 'ipy_profile_%s' % profile_name - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# PyTango utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def get_pytango_version(): - try: - import PyTango - except: - return None - try: - return PyTango.Release.version - except: - return '0.0.0' - - -def get_pytango_version_number(): - tgver_str = get_pytango_version() - if tgver_str is None: - return None - return translate_version_str2int(tgver_str) - - -def get_server_for_device(device_name): - db = get_tango_db() - device_name = device_name.lower() - server_list = db.get_server_list() - for server in server_list: - for dev in db.get_device_class_list(server)[::2]: - if dev.lower() == device_name: - return server - return None - - -def get_macroserver_for_door(door_name): - """Returns the MacroServer device name in the same DeviceServer as the - given door device""" - full_door_name, door_name, door_alias = from_name_to_tango(door_name) - db = get_tango_db() - door_name = door_name.lower() - server_list = list(db.get_server_list('MacroServer/*')) - server_list += list(db.get_server_list('Sardana/*')) - server_devs = None - for server in server_list: - server_devs = db.get_device_class_list(server) - devs, klasses = server_devs[0::2], server_devs[1::2] - for dev in devs: - if dev.lower() == door_name: - for i, klass in enumerate(klasses): - if klass == 'MacroServer': - return "%s:%s/%s" % (db.get_db_host(), db.get_db_port(), devs[i]) - else: - return None - - -def get_device_from_user(expected_class, dft=None): - """Gets a device of the given device class from user input""" - dft = print_dev_from_class(expected_class, dft) - prompt = "%s name from the list" % expected_class - if not dft is None: - prompt += "[%s]" % dft - prompt += "? " - from_user = raw_input(prompt).strip() or dft - - name = '' - try: - full_name, name, alias = from_name_to_tango(from_user) - except: - print "Warning: the given %s does not exist" % expected_class - return name - - try: - db = get_tango_db() - cl_name = db.get_class_for_device(name) - class_correct = cl_name == expected_class - if not class_correct: - print "Warning: the given name is not a %s (it is a %s)" % (expected_class, cl_name) - except Exception as e: - print "Warning: unable to confirm if '%s' is valid" % name - print str(e) - return full_name - - -def get_tango_db(): - tg_host = PyTango.ApiUtil.get_env_var("TANGO_HOST") - - db = None - if tg_host is None: - host, port = get_tango_host_from_user() - tg_host = "%s:%d" % (host, port) - os.environ["TANGO_HOST"] = tg_host - db = PyTango.Database() - else: - try: - db = PyTango.Database() - except: - # tg host is not valid. Find a valid one - host, port = get_tango_host_from_user() - tg_host = "%s:%d" % (host, port) - os.environ["TANGO_HOST"] = tg_host - - db = PyTango.Database() - return db - - -def get_tango_host_from_user(): - - while True: - prompt = "Please enter a valid tango host (:): " - from_user = raw_input(prompt).strip() - - try: - host, port = from_user.split(':') - try: - port = int(port) - try: - socket.gethostbyname(host) - try: - db = PyTango.Database(host, port) - return db.get_db_host(), db.get_db_port() - except: - exp = "No tango database found at %s:%d" % (host, port) - except: - exp = "Invalid host name %s" % host - except: - exp = "Port must be a number > 0" - except: - exp = "Invalid tango host. Must be in format :" - exp = "Invalid tango host. %s " % exp - print exp - - -def print_dev_from_class(classname, dft=None): - - db = get_tango_db() - pytg_ver = get_pytango_version_number() - if pytg_ver >= 030004: - server_wildcard = '*' - try: - exp_dev_list = db.get_device_exported_for_class(classname) - except: - exp_dev_list = [] - else: - server_wildcard = '%' - exp_dev_list = [] - - res = None - dev_list = list(db.get_device_name(server_wildcard, classname)) - tg_host = "%s:%s" % (db.get_db_host(), db.get_db_port()) - print "Available", classname, "devices from", tg_host, ":" - - list_devices_with_alias = [] - list_devices_with_no_alias = [] - for dev in dev_list: - _, name, alias = from_name_to_tango(dev) - if alias: - dev_alias_name = (alias, name) - list_devices_with_alias.append(dev_alias_name) - else: - dev_alias_name = ("", name) - list_devices_with_no_alias.append(dev_alias_name) - - list_devices_with_alias = sorted(list_devices_with_alias, - key=lambda s: s[0].lower()) - list_devices_with_no_alias = sorted(list_devices_with_no_alias, - key=lambda s: s[0].lower()) - ordered_devices_list = list_devices_with_alias + list_devices_with_no_alias - - for dev in ordered_devices_list: - dev_alias = dev[0] - dev_name = dev[1] - if dev_alias == "": - out = dev_name - else: - out = "%s (a.k.a. %s)" % (dev_alias, dev_name) - out = "%-25s" % out - if dev_name in exp_dev_list: - out += " (running)" - print out - - if dft: - if dft.lower() == name.lower(): - res = name - elif alias is not None and dft.lower() == alias.lower(): - res = alias - return res - - -def from_name_to_tango(name): - - db = get_tango_db() - - alias = None - - c = name.count('/') - # if the db prefix is there, remove it first - if c == 3 or c == 1: - name = name[name.index("/") + 1:] - - elems = name.split('/') - l = len(elems) - - if l == 3: - try: - alias = db.get_alias(name) - if alias.lower() == 'nada': - alias = None - except: - alias = None - elif l == 1: - alias = name - name = db.get_device_alias(alias) - else: - raise Exception("Invalid device name '%s'" % name) - - full_name = "%s:%s/%s" % (db.get_db_host(), db.get_db_port(), name) - return full_name, name, alias - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# taurus utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def clean_up(): - taurus.Manager().cleanUp() - - -def get_taurus_core_version(): - try: - import taurus - return taurus.core.release.version - except: - return '0.0.0' - - -def get_taurus_core_version_number(): - tgver_str = get_taurus_core_version() - if tgver_str is None: - return None - return translate_version_str2int(tgver_str) - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# Requirements checking -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def check_requirements(): - r = requirements - minPyTango, recPyTango = map(translate_version_str2int, r["PyTango"]) - minIPython, recIPython = map(translate_version_str2int, r["IPython"]) - minPython, recPython = map(translate_version_str2int, r["Python"]) - minTaurusCore, recTaurusCore = map( - translate_version_str2int, r["taurus.core"]) - - currPython = get_python_version_number() - currIPython = get_ipython_version_number() - currPyTango = get_pytango_version_number() - currTaurusCore = get_taurus_core_version_number() - - errMsg = "" - warnMsg = "" - - errPython, errIPython, errPyTango, errTaurusCore = False, False, False, False - if currPython is None: - errMsg += "Spock needs Python version >= %s. No python installation found\n" % requirements[ - "Python"][0] - errPython = True - elif currPython < minPython: - errMsg += "Spock needs Python version >= %s. Current version is %s\n" % ( - requirements["Python"][0], get_python_version()) - errPython = True - - if currIPython is None: - errMsg += "Spock needs IPython version >= %s. No IPython installation found\n" % requirements[ - "IPython"][0] - errIPython = True - elif currIPython < minIPython: - errMsg += "Spock needs IPython version >= %s. Current version is %s\n" % ( - requirements["IPython"][0], get_ipython_version()) - errIPython = True - - if currPyTango is None: - errMsg += "Spock needs PyTango version >= %s. No PyTango installation found\n" % requirements[ - "IPython"][0] - errPyTango = True - elif currPyTango < minPyTango: - errMsg += "Spock needs PyTango version >= %s. " % requirements[ - "PyTango"][0] - if currPyTango > 0: - errMsg += "Current version is %s\n" % get_pytango_version() - else: - errMsg += "Current version is unknown (most surely too old)\n" - errPyTango = True - - # TODO: verify the version whenever itango starts to provide it - try: - import itango - except ImportError: - errMsg += "Spock needs itango version >= 0.0.1, < 0.1.0 (PyTango < 9) or version >= 0.1.6 (PyTanog >= 9). No itango installation found\n" - - if currTaurusCore is None: - errMsg += "Spock needs taurus.core version >= %s. No taurus.core installation found\n" % requirements[ - "taurus.core"][0] - errTaurusCore = True - elif currTaurusCore < minTaurusCore: - errMsg += "Spock needs taurus.core version >= %s. " % requirements[ - "taurus.core"][0] - if currTaurusCore > 0: - errMsg += "Current version is %s\n" % get_taurus_core_version() - else: - errMsg += "Current version is unknown (most surely too old)\n" - errTaurusCore = True - - # Warnings - if not errPython and currPython < recPython: - warnMsg += "Spock recommends Python version >= %s. Current version is %s\n" % ( - requirements["Python"][1], get_python_version()) - - if not errIPython and currIPython < recIPython: - warnMsg += "Spock recommends IPython version >= %s. Current version is %s\n" % ( - requirements["IPython"][1], get_ipython_version()) - - if not errPyTango and currPyTango < recPyTango: - warnMsg += "Spock recommends PyTango version >= %s. Current version is %s\n" % ( - requirements["PyTango"][1], get_pytango_version()) - - if not errTaurusCore and currTaurusCore < recTaurusCore: - warnMsg += "Spock recommends taurus.core version >= %s. Current version is %s\n" % ( - requirements["taurus.core"][1], get_taurus_core_version()) - - if errMsg: - errMsg += warnMsg - raise exception.SpockMissingRequirement, errMsg - - if warnMsg: - raise exception.SpockMissingRecommended, warnMsg - - return True - - -def _get_dev(dev_type): - ip = get_ipapi() - ret = ip.user_ns.get("_" + dev_type) - if ret is not None: - return ret - - dev_obj_name = '%s_NAME' % dev_type - # TODO: For Taurus 4 compatibility - dev_name = "tango://%s" % ip.user_ns[dev_obj_name] - factory = Factory() - dev_obj = factory.getDevice(dev_name) - ip.user_ns[dev_type] = PyTango.DeviceProxy(dev_name) - ip.user_ns["_" + dev_type] = dev_obj - setattr(ip, '_%s' % dev_type, dev_obj) - return dev_obj - - -def get_door(): - return _get_dev('DOOR') - - -def get_macro_server(): - return _get_dev('MACRO_SERVER') - - -def _macro_completer(self, event): - """Method called by the IPython autocompleter. It will determine possible - values for macro arguments. - """ - ms = get_macro_server() - - macro_name = event.command.lstrip('%') - - # calculate parameter index - param_idx = len(event.line.split()) - 1 - if not event.line.endswith(' '): - param_idx -= 1 - # get macro info - info = ms.getMacroInfoObj(macro_name) - # if macro doesn't have parameters return - if param_idx < 0 or not info.hasParams(): - return - # get the parameter info - possible_params = info.getPossibleParams(param_idx) - # return the existing elements for the given parameter type - if possible_params: - res = [] - for param in possible_params: - if param['type'].lower() == 'boolean': - res.extend(['True', 'False']) - else: - res.extend(ms.getElementNamesWithInterface(param['type'])) - return res - - -def expose_magic(name, fn, completer_func=_macro_completer): - ip = get_ipapi() - ip.expose_magic(name, fn) - - if completer_func is None: - return - - # enable macro param completion - ip.set_hook('complete_command', completer_func, str_key=name) - - # register also when the command as is typed with the magic prefix '%' - name = str('%') + name - ip.set_hook('complete_command', completer_func, str_key=name) - - -def unexpose_magic(name): - ip = get_ipapi() - mg = 'magic_%s' % name - delattr(ip.IP, mg) - - -def expose_variable(name, value): - get_ipapi().to_user_ns({name: value}) - - -def unexpose_variable(name): - user_ns = get_ipapi().user_ns - del user_ns[name] - - -def expose_variables(d): - get_ipapi().to_user_ns(d) - -# def _expose_device(name): -# ip.to_user_ns({ name : PyTango.DeviceProxy(name) }) - -# def expose_device(name): -# ip = get_ipapi() -# ip.magic("bg _expose_device(%s)" % name) - - -def create_spock_profile(userdir, dft_profile, profile, door_name=None): - """Create a profile file from a profile template file """ - - src_data = """\ -\"\"\"Settings for Spock session\"\"\" - -# -# Please do not delete the next lines has they are used to check the version -# number for possible upgrades -# spock_creation_version = {version} -# door_name = {door_name} -# - -import IPython -from sardana.spock.genutils import init_spock - -def main(): - ip = IPython.ipapi.get() - init_spock(ip, '{macroserver_name}', '{door_name}') - -main() -""" - - # - # Discover door name - # - if door_name is None: - door_name = get_device_from_user("Door", profile) - else: - full_door_name, door_name, door_alias = from_name_to_tango(door_name) - door_name = full_door_name - - # - # Discover macro server name - # - ms_name = get_macroserver_for_door(door_name) - - dest_data = src_data.format(version=release.version, - macroserver_name=ms_name, - door_name=door_name) - - f_name = '%s.py' % get_spock_user_profile_module(profile) - - dest_name = os.path.join(userdir, f_name) - - sys.stdout.write('Storing %s in %s... ' % (f_name, userdir)) - sys.stdout.flush() - res = MSG_FAILED - try: - dest = open(dest_name, "w") - dest.write(dest_data) - dest.flush() - dest.close() - res = MSG_DONE - finally: - sys.stdout.write(res + '\n') - sys.stdout.flush() - - -def check_for_upgrade(ipy_profile_file, ipythondir, session, profile): - # Check if the current profile is up to date with the spock version - spock_profile_ver_str = '0.0.0' - door_name = None - - # search for version and door inside the ipy_profile file - for i, line in enumerate(ipy_profile_file): - if i > 20: - break # give up after 20 lines - if line.startswith('# spock_creation_version = '): - spock_profile_ver_str = line[line.index('=') + 1:].strip() - if line.startswith('# door_name = '): - door_name = line[line.index('=') + 1:].strip() - - # convert version from string to numbers - spock_lib_ver_str = release.version - spocklib_ver = translate_version_str2int(spock_lib_ver_str) - spock_profile_ver = translate_version_str2int(spock_profile_ver_str) - - alpha_in_spock_profile = "-alpha" in spock_profile_ver_str - alpha_in_spock_lib = "-alpha" in spock_lib_ver_str - if spocklib_ver == spock_profile_ver and \ - alpha_in_spock_profile == alpha_in_spock_lib: - return - if spocklib_ver < spock_profile_ver: - print '%sYour spock profile (%s) is newer than your spock version ' \ - '(%s)!' % (TermColors.Brown, spock_profile_ver_str, spock_lib_ver_str) - print 'Please upgrade spock or delete the current profile %s' % TermColors.Normal - sys.exit(1) - - # there was no version track of spock profiles since spock 0.2.0 so change - # the message - if spock_profile_ver_str == '0.0.0': - spock_profile_ver_str = '<= 0.2.0' - msg = 'Your current spock door extension profile has been created with spock %s.\n' \ - 'Your current spock door extension version is %s, therefore a profile upgrade is needed.\n' \ - % (spock_profile_ver_str, spock_lib_ver_str) - print msg - prompt = 'Do you wish to upgrade now (warn: this will shutdown the current spock session) ([y]/n)? ' - r = raw_input(prompt) or 'y' - if r.lower() == 'y': - create_spock_profile(ipythondir, session, profile, door_name) - sys.exit(0) - - -def get_args(argv): - - script_name = argv[0] - script_dir, session = os.path.split(script_name) - script_name = os.path.realpath(script_name) - #script_dir = os.path.dirname(script_name) - - macro_server = None - door = None - - # Define the profile file - profile = "spockdoor" - try: - profile_idx = argv.index('-p') + 1 - if len(argv) > profile_idx: - profile = argv[profile_idx] - except: - pass - - profile_modulename = get_spock_user_profile_module(profile) - - # Try to find the profile in the current directory and then in the - # default IPython dir - #userdir = os.path.realpath(os.path.curdir) - ipythondir = get_ipython_dir() - - if not os.path.isdir(ipythondir): - # Platform-dependent suffix. - if os.name == 'posix': - rc_suffix = '' - else: - rc_suffix = '.ini' - IPython.iplib.user_setup(ipythondir, rc_suffix, mode='install', - interactive=False) - - try: - f, name, t = imp.find_module(profile_modulename, [ipythondir]) - check_for_upgrade(f, ipythondir, session, profile) - except ImportError: - # Create a new profile - r = '' - while not r in ['y', 'n']: - prompt = 'Profile \'%s\' does not exist. Do you want to create '\ - 'one now ([y]/n)? ' % profile - r = raw_input(prompt) or 'y' - if r.lower() == 'y': - create_spock_profile(ipythondir, session, profile) - else: - sys.stdout.write( - 'No spock door extension profile was created. Starting normal spock...\n') - sys.stdout.flush() - profile = '' - - # inform the shell of the profile it should use - if not '-p' in argv and profile: - argv.append('-p') - argv.append(profile) - - user_ns = {'MACRO_SERVER_NAME': macro_server, - 'DOOR_NAME': door, - 'PROFILE': profile} - - return user_ns - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# Useful constants -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - -MSG_G = '[%s%%s%s]' % (TermColors.Green, TermColors.Normal) -MSG_R = '[%s%%s%s]' % (TermColors.Red, TermColors.Normal) -MSG_FAILED = MSG_R % 'FAILED' -MSG_FAILED_WR = MSG_R % 'FAILED: %s' -MSG_ERROR = MSG_R % 'ERROR' -MSG_DONE = MSG_G % 'DONE' -MSG_OK = MSG_G % 'OK' - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# initialization methods -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def init_console(ip): - # Handy tab-completers for %cd, %run, import etc. - # Try commenting this out if you have completion problems/slowness - import ipy_stock_completers - - spockver = release.version - pyver = get_python_version() - ipyver = get_ipython_version() - pytangover = get_pytango_version() - tauruscorever = get_taurus_core_version() - - TermColors = IPython.ColorANSI.TermColors - - d = {"version": spockver, - "pyver": pyver, - "ipyver": ipyver, - "pytangover": pytangover, - "taurusver": tauruscorever, - "profile": ip.user_ns["PROFILE"], - "door": ip.user_ns["DOOR_ALIAS"]} - - d.update(TermColors.__dict__) - - # IPython options - o = ip.options - o.autocall = 1 - o.autoedit_syntax = 0 - o.autoindent = 1 - o.automagic = 1 - o.cache_size = 1000 - o.colors = 'Linux' - o.color_info = 1 - o.confirm_exit = 0 - o.deep_reload = 0 - #o.editor = 'gedit' - o.log = 0 - o.logfile = '' - o.messages = 1 - o.pdb = 0 - o.pprint = 1 - o.quick = 0 - o.readline = 1 - o.screen_length = 0 - o.separate_in = '\n' - o.separate_out = '\n' - o.separate_out2 = '' - o.nosep = 0 - o.wildcards_case_sensitive = 0 - o.object_info_string_level = 0 - o.xmode = 'Context' - o.multi_line_specials = 1 - o.system_header = 'IPython system call: ' - o.system_verbose = 1 - o.wxversion = '0' - o.colors = "GreenTango" - o.prompt_in1 = "$DOOR_ALIAS$DOOR_STATE [\\#]: " - o.prompt_out = "Result [\\#]: " - o.readline_parse_and_bind.append('tab: complete') - #o.readline_parse_and_bind.append('tab: menu-complete') - o.readline_parse_and_bind.append('"\C-l": possible-completions') - o.readline_parse_and_bind.append('set show-all-if-ambiguous on') - o.readline_parse_and_bind.append('"\C-o": tab-insert') - o.readline_parse_and_bind.append('"\M-i": " "') - o.readline_parse_and_bind.append('"\M-o": "\d\d\d\d"') - o.readline_parse_and_bind.append('"\M-I": "\d\d\d\d"') - o.readline_parse_and_bind.append('"\C-r": reverse-search-history') - o.readline_parse_and_bind.append('"\C-s": forward-search-history') - o.readline_parse_and_bind.append('"\C-p": history-search-backward') - o.readline_parse_and_bind.append('"\C-n": history-search-forward') - o.readline_parse_and_bind.append('"\e[A": history-search-backward') - o.readline_parse_and_bind.append('"\e[B": history-search-forward') - o.readline_parse_and_bind.append('"\C-k": kill-line') - o.readline_parse_and_bind.append('"\C-u": unix-line-discard') - o.readline_remove_delims = '-/~' - o.readline_merge_completions = 1 - o.readline_omit__names = 0 - - banner = """\ -%(Purple)sSpock %(version)s%(Normal)s -- An interactive laboratory application. - -help -> Spock's help system. -object? -> Details about 'object'. ?object also works, ?? prints more. -""" - banner = banner % d - banner = banner.format(**d) - - o.banner = banner - - -def init_magic(ip): - import sardana.spock.magic - magic = sardana.spock.magic - expose_magic('debug', magic.debug, magic.debug_completer) - expose_magic('www', magic.www, None) - expose_magic('post_mortem', magic.post_mortem, None) - expose_magic('spsplot', magic.spsplot, None) - expose_magic('macrodata', magic.macrodata, None) - expose_magic('edmac', magic.edmac, None) - expose_magic('showscan', magic.showscan, None) - expose_magic('expconf', magic.expconf, None) - ip.set_hook('late_startup_hook', magic.spock_late_startup_hook) - ip.set_hook('pre_prompt_hook', magic.spock_pre_prompt_hook) - - -def init_pre_spock(ip, macro_server, door): - so = IPython.ipstruct.Struct() - full_door_tg_name, door_tg_name, door_tg_alias = from_name_to_tango(door) - #macro_server = get_ms_for_door(door_tg_name) - full_ms_tg_name, ms_tg_name, ms_tg_alias = from_name_to_tango(macro_server) - ip.user_ns['MACRO_SERVER_NAME'] = full_ms_tg_name - ip.user_ns['MACRO_SERVER_ALIAS'] = ms_tg_alias or ms_tg_name - ip.user_ns['DOOR_NAME'] = full_door_tg_name - ip.user_ns['DOOR_ALIAS'] = door_tg_alias or door_tg_name - ip.user_ns['DOOR_STATE'] = "" - ip.user_ns['spock_options'] = so - - if ip.IP.alias_table.has_key('mv'): - del ip.IP.alias_table['mv'] - - v = release.version - alias = ip.user_ns['DOOR_ALIAS'] - profile = ip.user_ns['PROFILE'] - - so.spock_banner = """\ -{Blue}Spock's sardana extension %s loaded with profile: %s (linked to door '%s'){Normal} -""" % (v, profile, alias) - - # the CodecFactory is not thread safe. There are two attributes who will - # request for it in the first event at startup in different threads - # therefore this small hack: make sure CodecFactory is initialized. - CodecFactory() - - factory = Factory() - - import sardana.spock.spockms - macroserver = sardana.spock.spockms - - factory.registerDeviceClass('MacroServer', macroserver.SpockMacroServer) - - mode = get_gui_mode() - if mode == 'qt': - factory.registerDeviceClass('Door', macroserver.QSpockDoor) - else: - factory.registerDeviceClass('Door', macroserver.SpockDoor) - - door = get_door() - macro_server = get_macro_server() - - # Initialize the environment - expose_variable(ENV_NAME, macro_server.getEnvironment()) - - -def init_post_spock(ip): - init_console(ip) - init_magic(ip) - - -def init_spock(ip, macro_server, door): - init_pre_spock(ip, macro_server, door) - itango.init_ipython(ip) - init_post_spock(ip) - - -def start(user_ns=None): - if '-pylab' not in sys.argv: - sys.argv.insert(1, '-pylab') - if '-q4thread' not in sys.argv: - sys.argv.insert(1, '-q4thread') - - # Make sure the log level is changed to warning - from taurus.core.taurushelper import setLogLevel, Warning - CodecFactory() - setLogLevel(Warning) - - try: - check_requirements() - except exception.SpockMissingRequirement, requirement: - print str(requirement) - sys.exit(-1) - except exception.SpockMissingRecommended, recommended: - print str(recommended) - - user_ns = user_ns or {} - try: - user_ns.update(get_args(sys.argv)) - except exception.SpockException, e: - print e.message - print 'Starting normal IPython console' - except KeyboardInterrupt: - print "\nUser pressed Ctrl+C. Exiting..." - sys.exit() - except Exception, e: - print 'spock exited with an unmanaged exception: %s' % str(e) - sys.exit(-2) - - return IPython.Shell.start(user_ns=user_ns) - - -def mainloop(shell=None, user_ns=None): - if shell is None: - shell = start(user_ns) - shell.mainloop() - - -def run(user_ns=None): - - # TODO: Temporary solution, available while Taurus3 is being supported. - from taurus import tauruscustomsettings - from sardana import sardanacustomsettings - max_counts = getattr(sardanacustomsettings, - 'TAURUS_MAX_DEPRECATION_COUNTS', 0) - tauruscustomsettings._MAX_DEPRECATIONS_LOGGED = max_counts - # - - # initialize input handler as soon as possible - import sardana.spock.inputhandler - input_handler = sardana.spock.inputhandler.InputHandler() - - try: - mainloop(user_ns=user_ns) - finally: - try: - clean_up() - except Exception: - pass - - # TODO: Temporary solution, available while Taurus3 is being supported. - try: - from taurus.core.util.log import _DEPRECATION_COUNT - from taurus import info - info('\n*********************\n%s', _DEPRECATION_COUNT.pretty()) - except: - pass - -# for compatibility reasons with new IPython API (>=0.11) we add the following -# empty methods - - -def load_ipython_extension(ipython): - pass - - -def unload_ipython_extension(ipython): - pass - - -def load_config(config): - pass diff --git a/src/sardana/spock/ipython_00_11/genutils.py b/src/sardana/spock/ipython_00_11/genutils.py deleted file mode 100644 index 802d0c9774..0000000000 --- a/src/sardana/spock/ipython_00_11/genutils.py +++ /dev/null @@ -1,1309 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -############################################################################## -## -# This file is part of Sardana -## -# http://www.sardana-controls.org/ -## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain -## -# Sardana is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -## -# Sardana is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -## -# You should have received a copy of the GNU Lesser General Public License -# along with Sardana. If not, see . -## -############################################################################## - -"""This package provides the spock generic utilities""" - -__all__ = ['page', 'arg_split', 'get_gui_mode', 'get_pylab_mode', - 'get_color_mode', 'get_app', - 'get_shell', 'get_ipapi', 'get_config', 'get_editor', 'ask_yes_no', - 'spock_input', - 'translate_version_str2int', 'get_ipython_version', - 'get_ipython_version_number', 'get_python_version', - 'get_python_version_number', 'get_ipython_dir', - 'get_ipython_profiles', - 'get_pytango_version', 'get_pytango_version_number', - 'get_server_for_device', 'get_macroserver_for_door', - 'get_device_from_user', 'get_tango_db', 'get_tango_host_from_user', - 'print_dev_from_class', 'from_name_to_tango', 'clean_up', - 'get_taurus_core_version', 'get_taurus_core_version_number', - 'check_requirements', 'get_door', 'get_macro_server', - 'expose_magic', 'unexpose_magic', 'expose_variable', - 'expose_variables', 'unexpose_variable', - 'create_spock_profile', 'check_for_upgrade', 'get_args', - 'start', 'mainloop', 'run', - 'load_ipython_extension', 'unload_ipython_extension', 'load_config', - 'MSG_FAILED', 'MSG_FAILED_WR', 'MSG_R', 'MSG_ERROR', - 'MSG_DONE', 'MSG_OK'] - -__docformat__ = 'restructuredtext' - -import sys -import os -import socket - -import IPython -import IPython.core.magic -from IPython.core.page import page -from IPython.core.profiledir import ProfileDirError, ProfileDir -from IPython.core.application import BaseIPythonApplication -from IPython.core.interactiveshell import InteractiveShell -from IPython.utils.io import ask_yes_no as _ask_yes_no -from IPython.utils.io import raw_input_ext as _raw_input_ext -from IPython.utils.path import get_ipython_dir -from IPython.utils.process import arg_split -from IPython.utils.coloransi import TermColors -from IPython.config.application import Application -from IPython.frontend.terminal.ipapp import TerminalIPythonApp, \ - launch_new_instance - -from taurus.core.taurushelper import Factory, Manager, Warning -from taurus.core.util.codecs import CodecFactory -from taurus.core.taurushelper import setLogLevel - - -# make sure Qt is properly initialized -from taurus.external.qt import Qt - -from sardana.spock import exception -from sardana.spock import colors -from sardana import release - -SpockTermColors = colors.TermColors - -requirements = { - # module minimum recommended - "IPython": ("0.11.0", "0.12.0"), - "Python": ("2.6.0", "2.6.0"), - "PyTango": ("7.2.0", "7.2.3"), - # for the moment just for reference since itango does not provide version - # when using PyTango < 9 the dependency is >= 0.0.1 and < 0.1.0 - # when using PyTango >= 9 the dependency is >= 0.1.6 - "itango": ("0.0.1", "0.0.1"), - "taurus.core": ("3.0.0", "3.0.0") -} - -ENV_NAME = "_E" - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# IPython utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def get_gui_mode(): - return 'qt' - - -def get_pylab_mode(): - return get_app().pylab - - -def get_color_mode(): - return get_config().InteractiveShell.colors - - -def get_app(): - # return TerminalIPythonApp.instance() - return Application.instance() - - -def get_shell(): - """Get the global InteractiveShell instance.""" - return get_app().shell - - -def get_ipapi(): - """Get the global InteractiveShell instance.""" - return InteractiveShell.instance() - - -def get_config(): - return get_app().config - - -def get_editor(): - return get_ipapi().editor - - -def ask_yes_no(prompt, default=None): - """Asks a question and returns a boolean (y/n) answer. - - If default is given (one of 'y','n'), it is used if the user input is - empty. Otherwise the question is repeated until an answer is given. - - An EOF is treated as the default answer. If there is no default, an - exception is raised to prevent infinite loops. - - Valid answers are: y/yes/n/no (match is not case sensitive).""" - - if default: - prompt = '%s [%s]' % (prompt, default) - return _ask_yes_no(prompt, default) - - -def spock_input(prompt='', ps2='... '): - return _raw_input_ext(prompt=prompt, ps2=ps2) - - -def translate_version_str2int(version_str): - """Translates a version string in format x[.y[.z[...]]] into a 000000 number""" - import math - # Get the current version number ignoring the release part ("-alpha") - num_version_str = version_str.split('-')[0] - parts = num_version_str.split('.') - i, v, l = 0, 0, len(parts) - if not l: - return v - while i < 3: - try: - v += int(parts[i]) * int(math.pow(10, (2 - i) * 2)) - l -= 1 - i += 1 - except ValueError: - return v - if not l: - return v - return v - - try: - v += 10000 * int(parts[0]) - l -= 1 - except ValueError: - return v - if not l: - return v - - try: - v += 100 * int(parts[1]) - l -= 1 - except ValueError: - return v - if not l: - return v - - try: - v += int(parts[0]) - l -= 1 - except ValueError: - return v - if not l: - return v - - -def get_ipython_version(): - """Returns the current IPython version""" - v = None - try: - try: - v = IPython.Release.version - except Exception: - try: - v = IPython.release.version - except Exception, e2: - print e2 - except Exception, e3: - print e3 - return v - - -def get_ipython_version_number(): - """Returns the current IPython version number""" - ipyver_str = get_ipython_version() - if ipyver_str is None: - return None - return translate_version_str2int(ipyver_str) - - -def get_python_version(): - return '.'.join(map(str, sys.version_info[:3])) - - -def get_python_version_number(): - pyver_str = get_python_version() - return translate_version_str2int(pyver_str) - - -def get_ipython_profiles(path=None): - """list profiles in a given root directory""" - if path is None: - path = get_ipython_dir() - files = os.listdir(path) - profiles = [] - for f in files: - full_path = os.path.join(path, f) - if os.path.isdir(full_path) and f.startswith('profile_'): - profiles.append(f.split('_', 1)[-1]) - return profiles - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# PyTango utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def get_pytango_version(): - try: - import PyTango - try: - return PyTango.Release.version - except: - return '0.0.0' - except: - return None - - -def get_pytango_version_number(): - tgver_str = get_pytango_version() - if tgver_str is None: - return None - return translate_version_str2int(tgver_str) - - -def get_server_for_device(device_name): - db = get_tango_db() - device_name = device_name.lower() - server_list = db.get_server_list() - for server in server_list: - for dev in db.get_device_class_list(server)[::2]: - if dev.lower() == device_name: - return server - return None - - -def get_macroserver_for_door(door_name): - """Returns the MacroServer device name in the same DeviceServer as the - given door device""" - _, door_name, _ = from_name_to_tango(door_name) - db = get_tango_db() - door_name = door_name.lower() - server_list = list(db.get_server_list('MacroServer/*')) - server_list += list(db.get_server_list('Sardana/*')) - server_devs = None - for server in server_list: - server_devs = db.get_device_class_list(server) - devs, klasses = server_devs[0::2], server_devs[1::2] - for dev in devs: - if dev.lower() == door_name: - for i, klass in enumerate(klasses): - if klass == 'MacroServer': - return "%s:%s/%s" % (db.get_db_host(), db.get_db_port(), devs[i]) - else: - return None - - -def get_device_from_user(expected_class, dft=None): - """Gets a device of the given device class from user input""" - dft = print_dev_from_class(expected_class, dft) - prompt = "%s name from the list" % expected_class - if not dft is None: - prompt += "[%s]" % dft - prompt += "? " - from_user = raw_input(prompt).strip() or dft - - name = '' - try: - full_name, name, _ = from_name_to_tango(from_user) - except: - print "Warning: the given %s does not exist" % expected_class - return name - - try: - db = get_tango_db() - cl_name = db.get_class_for_device(name) - class_correct = cl_name == expected_class - if not class_correct: - print "Warning: the given name is not a %s (it is a %s)" % (expected_class, cl_name) - except Exception as e: - print "Warning: unable to confirm if '%s' is valid" % name - print str(e) - return full_name - - -def get_tango_db(): - import PyTango - tg_host = PyTango.ApiUtil.get_env_var("TANGO_HOST") - - db = None - if tg_host is None: - host, port = get_tango_host_from_user() - tg_host = "%s:%d" % (host, port) - os.environ["TANGO_HOST"] = tg_host - db = PyTango.Database() - else: - try: - db = PyTango.Database() - except: - # tg host is not valid. Find a valid one - host, port = get_tango_host_from_user() - tg_host = "%s:%d" % (host, port) - os.environ["TANGO_HOST"] = tg_host - - db = PyTango.Database() - return db - - -def get_tango_host_from_user(): - import PyTango - while True: - prompt = "Please enter a valid tango host (:): " - from_user = raw_input(prompt).strip() - - try: - host, port = from_user.split(':') - try: - port = int(port) - try: - socket.gethostbyname(host) - try: - PyTango.Database(host, port) - return (host, port) - except: - exp = "No tango database found at %s:%d" % (host, port) - except: - exp = "Invalid host name %s" % host - except: - exp = "Port must be a number > 0" - except: - exp = "Invalid tango host. Must be in format :" - exp = "Invalid tango host. %s " % exp - print exp - - -def print_dev_from_class(classname, dft=None): - - db = get_tango_db() - pytg_ver = get_pytango_version_number() - if pytg_ver >= 030004: - server_wildcard = '*' - try: - exp_dev_list = db.get_device_exported_for_class(classname) - except: - exp_dev_list = [] - else: - server_wildcard = '%' - exp_dev_list = [] - - res = None - dev_list = list(db.get_device_name(server_wildcard, classname)) - tg_host = "%s:%s" % (db.get_db_host(), db.get_db_port()) - print "Available", classname, "devices from", tg_host, ":" - - list_devices_with_alias = [] - list_devices_with_no_alias = [] - for dev in dev_list: - _, name, alias = from_name_to_tango(dev) - if alias: - dev_alias_name = (alias, name) - list_devices_with_alias.append(dev_alias_name) - else: - dev_alias_name = ("", name) - list_devices_with_no_alias.append(dev_alias_name) - - list_devices_with_alias = sorted(list_devices_with_alias, - key=lambda s: s[0].lower()) - list_devices_with_no_alias = sorted(list_devices_with_no_alias, - key=lambda s: s[0].lower()) - ordered_devices_list = list_devices_with_alias + list_devices_with_no_alias - - for dev in ordered_devices_list: - dev_alias = dev[0] - dev_name = dev[1] - if dev_alias == "": - out = dev_name - else: - out = "%s (a.k.a. %s)" % (dev_alias, dev_name) - out = "%-25s" % out - if dev_name in exp_dev_list: - out += " (running)" - print out - - if dft: - if dft.lower() == name.lower(): - res = name - elif alias is not None and dft.lower() == alias.lower(): - res = alias - return res - - -def from_name_to_tango(name): - - db = get_tango_db() - - alias = None - - c = name.count('/') - # if the db prefix is there, remove it first - if c == 3 or c == 1: - name = name[name.index("/") + 1:] - - elems = name.split('/') - l = len(elems) - - if l == 3: - try: - alias = db.get_alias(name) - if alias.lower() == 'nada': - alias = None - except: - alias = None - elif l == 1: - alias = name - name = db.get_device_alias(alias) - else: - raise Exception("Invalid device name '%s'" % name) - - full_name = "%s:%s/%s" % (db.get_db_host(), db.get_db_port(), name) - return full_name, name, alias - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# taurus utilities -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def clean_up(): - Manager().cleanUp() - - -def get_taurus_core_version(): - try: - import taurus - return taurus.core.release.version - except: - import traceback - traceback.print_exc() - return '0.0.0' - - -def get_taurus_core_version_number(): - tgver_str = get_taurus_core_version() - if tgver_str is None: - return None - return translate_version_str2int(tgver_str) - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# Requirements checking -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def check_requirements(): - r = requirements - minPyTango, recPyTango = map(translate_version_str2int, r["PyTango"]) - minIPython, recIPython = map(translate_version_str2int, r["IPython"]) - minPython, recPython = map(translate_version_str2int, r["Python"]) - minTaurusCore, recTaurusCore = map( - translate_version_str2int, r["taurus.core"]) - - currPython = get_python_version_number() - currIPython = get_ipython_version_number() - currPyTango = get_pytango_version_number() - currTaurusCore = get_taurus_core_version_number() - - errMsg = "" - warnMsg = "" - - errPython, errIPython, errPyTango, errTaurusCore = False, False, False, False - if currPython is None: - errMsg += "Spock needs Python version >= %s. No python installation found\n" % requirements[ - "Python"][0] - errPython = True - elif currPython < minPython: - errMsg += "Spock needs Python version >= %s. Current version is %s\n" % ( - requirements["Python"][0], get_python_version()) - errPython = True - - if currIPython is None: - errMsg += "Spock needs IPython version >= %s. No IPython installation found\n" % requirements[ - "IPython"][0] - errIPython = True - elif currIPython < minIPython: - errMsg += "Spock needs IPython version >= %s. Current version is %s\n" % ( - requirements["IPython"][0], get_ipython_version()) - errIPython = True - - if currPyTango is None: - errMsg += "Spock needs PyTango version >= %s. No PyTango installation found\n" % requirements[ - "IPython"][0] - errPyTango = True - elif currPyTango < minPyTango: - errMsg += "Spock needs PyTango version >= %s. " % requirements[ - "PyTango"][0] - if currPyTango > 0: - errMsg += "Current version is %s\n" % get_pytango_version() - else: - errMsg += "Current version is unknown (most surely too old)\n" - errPyTango = True - - # TODO: verify the version whenever itango starts to provide it - try: - import itango - except ImportError: - errMsg += "Spock needs itango version >= 0.0.1, < 0.1.0 (PyTango < 9) or version >= 0.1.6 (PyTanog >= 9). No itango installation found\n" - - if currTaurusCore is None: - errMsg += "Spock needs taurus.core version >= %s. No taurus.core installation found\n" % requirements[ - "taurus.core"][0] - errTaurusCore = True - elif currTaurusCore < minTaurusCore: - errMsg += "Spock needs taurus.core version >= %s. " % requirements[ - "taurus.core"][0] - if currTaurusCore > 0: - errMsg += "Current version is %s\n" % get_taurus_core_version() - else: - errMsg += "Current version is unknown (most surely too old)\n" - errTaurusCore = True - - # Warnings - if not errPython and currPython < recPython: - warnMsg += "Spock recommends Python version >= %s. Current version " \ - "is %s\n" % (requirements["Python"][1], - get_python_version()) - - if not errIPython and currIPython < recIPython: - warnMsg += "Spock recommends IPython version >= %s. Current version " \ - "is %s\n" % (requirements["IPython"][1], - get_ipython_version()) - - if not errPyTango and currPyTango < recPyTango: - warnMsg += "Spock recommends PyTango version >= %s. Current version " \ - "is %s\n" % (requirements["PyTango"][1], - get_pytango_version()) - - if not errTaurusCore and currTaurusCore < recTaurusCore: - warnMsg += "Spock recommends taurus.core version >= %s. Current " \ - "version is %s\n" % (requirements["taurus.core"][1], - get_taurus_core_version()) - - if errMsg: - errMsg += warnMsg - raise exception.SpockMissingRequirement, errMsg - - if warnMsg: - raise exception.SpockMissingRecommended, warnMsg - - return True - - -def _get_dev(dev_type): - spock_config = get_config().Spock - taurus_dev = None - taurus_dev_var = "_" + dev_type - if hasattr(spock_config, taurus_dev_var): - taurus_dev = getattr(spock_config, taurus_dev_var) - if taurus_dev is None: - # TODO: For Taurus 4 compatibility - dev_name = "tango://%s" % getattr(spock_config, dev_type + '_name') - factory = Factory() - taurus_dev = factory.getDevice(dev_name) - import PyTango - dev = PyTango.DeviceProxy(dev_name) - setattr(spock_config, dev_type, dev) - setattr(spock_config, taurus_dev_var, taurus_dev) - shell = get_shell() - dev_type_upper = dev_type.upper() - shell.user_ns[dev_type_upper] = dev - shell.user_ns["_" + dev_type_upper] = taurus_dev - return taurus_dev - - -def get_door(): - return _get_dev('door') - - -def get_macro_server(): - return _get_dev('macro_server') - - -def _macro_completer(self, event): - """Method called by the IPython autocompleter. It will determine possible - values for macro arguments. - """ - ms = get_macro_server() - - macro_name = event.command.lstrip('%') - # calculate parameter index - param_idx = len(event.line.split()) - 1 - if not event.line.endswith(' '): - param_idx -= 1 - # get macro info - info = ms.getMacroInfoObj(macro_name) - # if macro doesn't have parameters return - if param_idx < 0 or not info.hasParams(): - return - # get the parameter info - possible_params = info.getPossibleParams(param_idx) - # return the existing elements for the given parameter type - if possible_params: - res = [] - for param in possible_params: - if param['type'].lower() == 'boolean': - res.extend(['True', 'False']) - else: - res.extend(ms.getElementNamesWithInterface(param['type'])) - return res - - -def expose_magic(name, fn, completer_func=_macro_completer): - shell = get_shell() - fn.old_magic = shell.define_magic(name, fn) - fn.old_completer = completer_func - - if completer_func is None: - return - - # enable macro param completion - if completer_func is not None: - shell.set_hook('complete_command', completer_func, str_key=name) - shell.set_hook('complete_command', completer_func, str_key='%' + name) - - -def unexpose_magic(name): - shell = get_shell() - mg_name = 'magic_' + name - if hasattr(shell, mg_name): - magic_fn = getattr(shell, mg_name) - delattr(shell, mg_name) - if hasattr(magic_fn, 'old_magic') and magic_fn.old_magic is not None: - expose_magic(name, magic_fn.old_magic, magic_fn.old_completer) - - -def expose_variable(name, value): - get_shell().user_ns[name] = value - - -def expose_variables(d): - get_shell().user_ns.update(d) - - -def unexpose_variable(name): - user_ns = get_shell().user_ns - del user_ns[name] - - -def _create_config_file(location, door_name=None): - config_file_name = BaseIPythonApplication.config_file_name.default_value - abs_config_file_name = os.path.join(location, config_file_name) - - src_data = """\ -\"\"\"Settings for Spock session\"\"\" - -# -# Please do not delete the next lines has they are used to check the version -# number for possible upgrades -# spock_creation_version = {version} -# door_name = {door_name} -# - -import itango - -import sardana.spock.genutils -from sardana.spock.config import Spock - -config = get_config() -config.Spock.macro_server_name = '{macroserver_name}' -config.Spock.door_name = '{door_name}' - -load_subconfig('ipython_config.py', profile='default') -sardana.spock.load_config(config) - -# Put any additional environment here and/or overwrite default sardana config -config.IPKernelApp.pylab = 'inline' - -""" - # - # Discover door name - # - if door_name is None: - door_name = get_device_from_user("Door") - else: - full_door_name, door_name, _ = from_name_to_tango(door_name) - door_name = full_door_name - - # - # Discover macro server name - # - ms_name = get_macroserver_for_door(door_name) - - dest_data = src_data.format(version=release.version, - macroserver_name=ms_name, - door_name=door_name) - - sys.stdout.write('Storing %s in %s... ' % (config_file_name, location)) - sys.stdout.flush() - - with file(abs_config_file_name, "w") as f: - f.write(dest_data) - f.close() - sys.stdout.write(MSG_DONE + '\n') - - -def create_spock_profile(userdir, profile, door_name=None): - """Create spock profile directory and configuration file from a template - file - - :param userdir: directory where the spock profile will be created - :param profile: profile name - :param door_name: door name, if None, user will be asked for the door name - """ - - if not os.path.isdir(userdir): - ProfileDir.create_profile_dir(userdir) - p_dir = ProfileDir.create_profile_dir_by_name(userdir, profile) - ipy_profile_dir = p_dir.location - - _create_config_file(ipy_profile_dir) - - -def upgrade_spock_profile(ipy_profile_dir, door_name): - """Upgrade spock profile by recreating configuration file from scratch - - :param ipy_profile_dir: directory with the spock profile - :param door_name: door name - """ - _create_config_file(ipy_profile_dir, door_name) - - -def check_for_upgrade(ipy_profile_dir): - """Check if the current profile is up to date with the spock version - - :param ipy_profile_dir: directory with the spock profile - """ - spock_profile_ver_str = '0.0.0' - door_name = None - - config_file_name = BaseIPythonApplication.config_file_name.default_value - abs_config_file_name = os.path.join(ipy_profile_dir, config_file_name) - - # search for version and door inside the ipy_profile file - with file(abs_config_file_name, "r") as ipy_config_file: - for i, line in enumerate(ipy_config_file): - if i > 20: - break # give up after 20 lines - if line.startswith('# spock_creation_version = '): - spock_profile_ver_str = line[line.index('=') + 1:].strip() - if line.startswith('# door_name = '): - door_name = line[line.index('=') + 1:].strip() - - # convert version from string to numbers - spock_lib_ver_str = release.version - spocklib_ver = translate_version_str2int(spock_lib_ver_str) - spock_profile_ver = translate_version_str2int(spock_profile_ver_str) - - alpha_in_spock_profile = "-alpha" in spock_profile_ver_str - alpha_in_spock_lib = "-alpha" in spock_lib_ver_str - if spocklib_ver == spock_profile_ver and \ - alpha_in_spock_profile == alpha_in_spock_lib: - return - if spocklib_ver < spock_profile_ver: - print '%sYour spock profile (%s) is newer than your spock version ' \ - '(%s)!' % (SpockTermColors.Brown, - spock_profile_ver_str, spock_lib_ver_str) - print 'Please upgrade spock or delete the current profile %s' % SpockTermColors.Normal - sys.exit(1) - - # there was no version track of spock profiles since spock 0.2.0 so change - # the message - if spock_profile_ver_str == '0.0.0': - spock_profile_ver_str = '<= 0.2.0' - msg = 'Your current spock door extension profile has been created with spock %s.\n' \ - 'Your current spock door extension version is %s, therefore a profile upgrade is needed.\n' \ - % (spock_profile_ver_str, spock_lib_ver_str) - print msg - prompt = 'Do you wish to upgrade now (warn: this will shutdown the current spock session) ([y]/n)? ' - r = raw_input(prompt) or 'y' - if r.lower() == 'y': - upgrade_spock_profile(ipy_profile_dir, door_name) - sys.exit(0) - - -def get_args(argv): - - script_name = argv[0] - _, session = os.path.split(script_name) - script_name = os.path.realpath(script_name) - - macro_server = None - door = None - - # Define the profile file - profile = "spockdoor" - try: - for _, arg in enumerate(argv[1:]): - if arg.startswith('--profile='): - profile = arg[10:] - break - else: - argv.append("--profile=" + profile) - except: - pass - - ipython_dir = get_ipython_dir() - try: - ProfileDir.find_profile_dir_by_name(ipython_dir, profile) - except ProfileDirError: - r = '' - while not r in ('y', 'n'): - prompt = 'Profile \'%s\' does not exist. Do you want to create '\ - 'one now ([y]/n)? ' % profile - r = raw_input(prompt) or 'y' - if r.lower() == 'y': - create_spock_profile(ipython_dir, profile) - else: - sys.stdout.write( - 'No spock door extension profile was created. Starting normal spock...\n') - sys.stdout.flush() - profile = '' - - # inform the shell of the profile it should use - if not '--profile=' in argv and profile: - argv.append('--profile=' + profile) - - user_ns = {'MACRO_SERVER_NAME': macro_server, - 'DOOR_NAME': door, - 'PROFILE': profile} - - return user_ns - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# Useful constants -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - -MSG_G = '[%s%%s%s]' % (SpockTermColors.Green, SpockTermColors.Normal) -MSG_R = '[%s%%s%s]' % (SpockTermColors.Red, SpockTermColors.Normal) -MSG_FAILED = MSG_R % 'FAILED' -MSG_FAILED_WR = MSG_R % 'FAILED: %s' -MSG_ERROR = MSG_R % 'ERROR' -MSG_DONE = MSG_G % 'DONE' -MSG_OK = MSG_G % 'OK' - -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# initialization methods -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - -def init_taurus(): - # the CodecFactory is not thread safe. There are two attributes who will - # request for it in the first event at startup in different threads - # therefore this small hack: make sure CodecFactory is initialized. - CodecFactory() - - factory = Factory() - - import sardana.spock.spockms - macroserver = sardana.spock.spockms - - factory.registerDeviceClass('MacroServer', macroserver.SpockMacroServer) - - mode = get_gui_mode() - if mode == 'qt': - factory.registerDeviceClass('Door', macroserver.QSpockDoor) - else: - factory.registerDeviceClass('Door', macroserver.SpockDoor) - - -def load_ipython_extension(ipython): - import sardana.spock.magic - magic = sardana.spock.magic - - init_taurus() - - config = ipython.config - user_ns = ipython.user_ns - user_ns['MACRO_SERVER_NAME'] = config.Spock.macro_server_name - user_ns['MACRO_SERVER_ALIAS'] = config.Spock.macro_server_alias - user_ns['DOOR_NAME'] = config.Spock.door_name - user_ns['DOOR_ALIAS'] = config.Spock.door_alias - user_ns['DOOR_STATE'] = "" - - #shell.set_hook('late_startup_hook', magic.spock_late_startup_hook) - ipython.set_hook('pre_prompt_hook', magic.spock_pre_prompt_hook) - - # if ip.IP.alias_table.has_key('mv'): - # del ip.IP.alias_table['mv'] - - door = get_door() - macro_server = get_macro_server() - - # Initialize the environment - expose_variable(ENV_NAME, macro_server.getEnvironment()) - - new_style_magics = hasattr(IPython.core.magic, 'Magics') and hasattr( - IPython.core.magic, 'magics_class') - - if new_style_magics: - @IPython.core.magic.magics_class - class Sardana(IPython.core.magic.Magics): - debug = IPython.core.magic.line_magic(magic.debug) - www = IPython.core.magic.line_magic(magic.www) - post_mortem = IPython.core.magic.line_magic(magic.post_mortem) - spsplot = IPython.core.magic.line_magic(magic.spsplot) - macrodata = IPython.core.magic.line_magic(magic.macrodata) - edmac = IPython.core.magic.line_magic(magic.edmac) - showscan = IPython.core.magic.line_magic(magic.showscan) - expconf = IPython.core.magic.line_magic(magic.expconf) - - ipython.register_magics(Sardana) - else: - expose_magic('debug', magic.debug, magic.debug_completer) - expose_magic('www', magic.www, None) - expose_magic('post_mortem', magic.post_mortem, None) - expose_magic('spsplot', magic.spsplot, None) - expose_magic('macrodata', magic.macrodata, None) - expose_magic('edmac', magic.edmac, None) - expose_magic('showscan', magic.showscan, None) - expose_magic('expconf', magic.expconf, None) - - door.setConsoleReady(True) - - -def unload_ipython_extension(ipython): - pass - - -def load_config(config): - spockver = release.version - pyver = get_python_version() - ipyver = get_ipython_version() - pytangover = get_pytango_version() - tauruscorever = get_taurus_core_version() - - door = config.Spock.door_name - - if not hasattr(config.Spock, 'macro_server_name'): - macro_server = get_macroserver_for_door(door) - else: - macro_server = config.Spock.macro_server_name - - full_door_tg_name, door_tg_name, door_tg_alias = from_name_to_tango(door) - door_alias = door_tg_alias or door_tg_name - full_ms_tg_name, ms_tg_name, ms_tg_alias = from_name_to_tango(macro_server) - ms_alias = ms_tg_alias or ms_tg_name - - config.Spock.macro_server_name = full_ms_tg_name - config.Spock.door_name = full_door_tg_name - config.Spock.macro_server_alias = ms_alias - config.Spock.door_alias = door_alias - - d = {"version": spockver, - "pyver": pyver, - "ipyver": ipyver, - "pytangover": pytangover, - "taurusver": tauruscorever, - #"profile" : ip.user_ns["PROFILE"], - "door": door_alias} - - d.update(TermColors.__dict__) - - gui_mode = get_gui_mode() - - banner = """\ -%(Purple)sSpock %(version)s%(Normal)s -- An interactive laboratory application. - -help -> Spock's help system. -object? -> Details about 'object'. ?object also works, ?? prints more. -""" - banner = banner % d - banner = banner.format(**d) - - ipy_ver = get_ipython_version_number() - - # ------------------------------------ - # Application - # ------------------------------------ - app = config.Application - app.log_level = 30 - - # ------------------------------------ - # BaseIPythonApplication - # ------------------------------------ - i_app = config.BaseIPythonApplication - extensions = getattr(i_app, 'extensions', []) - extensions.extend(["itango", "sardana.spock"]) - i_app.extensions = extensions - - # ------------------------------------ - # InteractiveShell - # (IPython.core.interactiveshell) - # ------------------------------------ - i_shell = config.InteractiveShell - i_shell.autocall = 0 - i_shell.automagic = True - i_shell.color_info = True - i_shell.colors = 'Linux' - i_shell.deep_reload = True - i_shell.confirm_exit = False - - if ipy_ver >= 1200: - # ------------------------------------ - # PromptManager (ipython >= 0.12) - # ------------------------------------ - prompt = config.PromptManager - prompt.in_template = '{DOOR_ALIAS} [\\#]: ' - prompt.in2_template = ' .\\D.: ' - prompt.out_template = 'Result [\\#]: ' - prompt.color_scheme = 'Linux' - else: - # (Deprecated in ipython >= 0.12 use PromptManager.in_template) - i_shell.prompt_in1 = config.Spock.door_alias + ' [\\#]: ' - - # (Deprecated in ipython >= 0.12 use PromptManager.in2_template) - i_shell.prompt_in2 = ' .\\D.: ' - - # (Deprecated in ipython >= 0.12 use PromptManager.out_template) - i_shell.prompt_out = 'Result [\\#]: ' - - # (Deprecated in ipython >= 0.12 use PromptManager.justify) - i_shell.prompts_pad_left = True - - # ------------------------------------ - # IPCompleter - # ------------------------------------ - completer = config.IPCompleter - completer.omit__names = 2 - completer.greedy = False - - # ------------------------------------ - # InteractiveShellApp - # ------------------------------------ - i_shell_app = config.InteractiveShellApp - i_shell_app.ignore_old_config = True - - # ------------------------------------ - # TerminalIPythonApp: options for the IPython terminal (and not Qt Console) - # ------------------------------------ - term_app = config.TerminalIPythonApp - term_app.display_banner = True - term_app.gui = gui_mode - term_app.pylab = 'qt' - term_app.pylab_import_all = False - #term_app.nosep = False - #term_app.classic = True - - # ------------------------------------ - # IPKernelApp: options for the Qt Console - # ------------------------------------ - #kernel_app = config.IPKernelApp - ipython_widget = config.IPythonWidget - ipython_widget.in_prompt = ' Spock [%i]: ' - ipython_widget.out_prompt = 'Result [%i]: ' - ipython_widget.input_sep = '\n' - ipython_widget.output_sep = '' - ipython_widget.output_sep2 = '' - ipython_widget.enable_calltips = True - if ipy_ver >= 1300: - ipython_widget.gui_completion = 'droplist' - else: - ipython_widget.gui_completion = True - ipython_widget.ansi_codes = True - ipython_widget.paging = 'inside' - #ipython_widget.pylab = 'inline' - - # ------------------------------------ - # ConsoleWidget - # ------------------------------------ - # console_widget = config.ConsoleWidget - - # ------------------------------------ - # FrontendWidget - # ------------------------------------ - frontend_widget = config.FrontendWidget - frontend_widget.banner = banner - - # ------------------------------------ - # TerminalInteractiveShell - # ------------------------------------ - term_i_shell = config.TerminalInteractiveShell - term_i_shell.autocall = 2 - term_i_shell.automagic = True - #term_i_shell.editor = 'gedit' - #term_i_shell.editor = 'nano' - - term_i_shell.banner1 = banner - term_i_shell.banner2 = "Connected to " + door_alias + "\n" - #term_app.banner1 = banner - #term_app.banner2 = "Connected to " + door_alias + "\n" - - # ------------------------------------ - # InlineBackend - # ------------------------------------ - inline_backend = config.InlineBackend - inline_backend.figure_format = 'svg' - - # ------------------------------------ - # InteractiveShellEmbed - # ------------------------------------ - #i_shell_embed = config.InteractiveShellEmbed - - # ------------------------------------ - # NotebookApp - # ------------------------------------ - #notebook_app = config.NotebookApp - - # ------------------------------------ - # NotebookManager - # ------------------------------------ - #notebook_manager = config.NotebookManager - - # ------------------------------------ - # ZMQInteractiveShell - # ------------------------------------ - #zmq_i_shell = config.ZMQInteractiveShell - - # Tell console everything is ready. - config.Spock.ready = True - return config - - -def start(user_ns=None): - # Make sure the log level is changed to warning - CodecFactory() - setLogLevel(Warning) - - try: - check_requirements() - except exception.SpockMissingRequirement, requirement: - print str(requirement) - sys.exit(-1) - except exception.SpockMissingRecommended, recommended: - print str(recommended) - - user_ns = user_ns or {} - try: - user_ns.update(get_args(sys.argv)) - except exception.SpockException, e: - print e.message - print 'Starting normal IPython console' - except KeyboardInterrupt: - print "\nUser pressed Ctrl+C. Exiting..." - sys.exit() - except Exception, e: - print 'spock exited with an unmanaged exception: %s' % str(e) - sys.exit(-2) - - app = TerminalIPythonApp.instance() - app.initialize() - #config = get_config() - return app - - -def mainloop(app=None, user_ns=None): - if app is None: - app = start(user_ns) - app.start() - - -def prepare_input_handler(): - # initialize input handler as soon as possible - import sardana.spock.inputhandler - _ = sardana.spock.inputhandler.InputHandler() - - -def prepare_cmdline(argv=None): - if argv is None: - argv = sys.argv - - script_name = argv[0] - _, session = os.path.split(script_name) - script_name = os.path.realpath(script_name) - - # Define the profile file - profile, append_profile = "spockdoor", True - try: - # in ipython the last option in the list takes precedence - # so reversing order for searching of the profile - reversed_argv = reversed(argv[1:]) - for _, arg in enumerate(reversed_argv): - if arg.startswith('--profile='): - profile = arg[10:] - append_profile = False - break - except: - pass - - ipython_dir = get_ipython_dir() - try: - pd = ProfileDir.find_profile_dir_by_name(ipython_dir, profile) - except ProfileDirError: - r = '' - while not r in ('y', 'n'): - prompt = "Profile '%s' does not exist. Do you want to create "\ - "one now ([y]/n)? " % profile - r = raw_input(prompt) or 'y' - if r.lower() == 'y': - create_spock_profile(ipython_dir, profile) - else: - sys.stdout.write('No spock profile was created. ' - 'Starting ipython with default profile...\n') - sys.stdout.flush() - # removing all options refering to profile - for _, arg in enumerate(argv[1:]): - if arg.startswith('--profile='): - argv.remove(arg) - return - else: - ipy_profile_dir = pd.location # directory with the spock profile - check_for_upgrade(ipy_profile_dir) - - if append_profile: - argv.append("--profile=" + profile) - - -def run(): - - # TODO: Temporary solution, available while Taurus3 is being supported. - from taurus import tauruscustomsettings - from sardana import sardanacustomsettings - max_counts = getattr(sardanacustomsettings, - 'TAURUS_MAX_DEPRECATION_COUNTS', 0) - tauruscustomsettings._MAX_DEPRECATIONS_LOGGED = max_counts - # - - try: - from IPython.utils.traitlets import Unicode - from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget - - class SpockConsole(RichIPythonWidget): - - banner = Unicode(config=True) - - def _banner_default(self): - config = get_config() - return config.FrontendWidget.banner - - import IPython.frontend.qt.console.qtconsoleapp - IPythonQtConsoleApp = IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp - IPythonQtConsoleApp.widget_factory = SpockConsole - IPythonQtConsoleApp.version.default_value = release.version - except ImportError: - pass - - try: - check_requirements() - except exception.SpockMissingRequirement, requirement: - print str(requirement) - sys.exit(-1) - except exception.SpockMissingRecommended, recommended: - print str(recommended) - - prepare_input_handler() - prepare_cmdline() - - launch_new_instance() - - # TODO: Temporary solution, available while Taurus3 is being supported. - try: - from taurus.core.util.log import _DEPRECATION_COUNT - from taurus import info - info('\n*********************\n%s', _DEPRECATION_COUNT.pretty()) - except: - pass diff --git a/src/sardana/spock/ipython_01_00/genutils.py b/src/sardana/spock/ipython_01_00/genutils.py index 080425497d..1c26d28af8 100644 --- a/src/sardana/spock/ipython_01_00/genutils.py +++ b/src/sardana/spock/ipython_01_00/genutils.py @@ -161,7 +161,7 @@ def ask_yes_no(prompt, default=None): def spock_input(prompt='', ps2='... '): - return raw_input(prompt) + return input(prompt) def translate_version_str2int(version_str): @@ -218,10 +218,10 @@ def get_ipython_version(): except Exception: try: v = IPython.release.version - except Exception, e2: - print e2 - except Exception, e3: - print e3 + except Exception as e2: + print(e2) + except Exception as e3: + print(e3) return v @@ -317,13 +317,13 @@ def get_device_from_user(expected_class, dft=None): if not dft is None: prompt += "[%s]" % dft prompt += "? " - from_user = raw_input(prompt).strip() or dft + from_user = input(prompt).strip() or dft name = None try: full_name, name, _ = from_name_to_tango(from_user) except: - print "Warning: the given %s does not exist" % expected_class + print("Warning: the given %s does not exist" % expected_class) return name try: @@ -331,10 +331,11 @@ def get_device_from_user(expected_class, dft=None): cl_name = db.get_class_for_device(name) class_correct = cl_name == expected_class if not class_correct: - print "Warning: the given name is not a %s (it is a %s)" % (expected_class, cl_name) + print("Warning: the given name is not a %s (it is a %s)" % + (expected_class, cl_name)) except Exception as e: - print "Warning: unable to confirm if '%s' is valid" % name - print str(e) + print("Warning: unable to confirm if '%s' is valid" % name) + print(str(e)) return full_name @@ -365,7 +366,7 @@ def get_tango_host_from_user(): import PyTango while True: prompt = "Please enter a valid tango host (:): " - from_user = raw_input(prompt).strip() + from_user = input(prompt).strip() try: host, port = from_user.split(':') @@ -385,14 +386,14 @@ def get_tango_host_from_user(): except: exp = "Invalid tango host. Must be in format :" exp = "Invalid tango host. %s " % exp - print exp + print(exp) def print_dev_from_class(classname, dft=None): db = get_tango_db() pytg_ver = get_pytango_version_number() - if pytg_ver >= 030004: + if pytg_ver >= 0o30004: server_wildcard = '*' try: exp_dev_list = db.get_device_exported_for_class(classname) @@ -405,7 +406,7 @@ def print_dev_from_class(classname, dft=None): res = None dev_list = list(db.get_device_name(server_wildcard, classname)) tg_host = "%s:%s" % (db.get_db_host(), db.get_db_port()) - print "Available", classname, "devices from", tg_host, ":" + print("Available", classname, "devices from", tg_host, ":") list_devices_with_alias = [] list_devices_with_no_alias = [] @@ -434,7 +435,7 @@ def print_dev_from_class(classname, dft=None): out = "%-25s" % out if dev_name in exp_dev_list: out += " (running)" - print out + print(out) if dft: if dft.lower() == name.lower(): @@ -514,11 +515,11 @@ def get_taurus_core_version_number(): def check_requirements(): r = requirements - minPyTango, recPyTango = map(translate_version_str2int, r["PyTango"]) - minIPython, recIPython = map(translate_version_str2int, r["IPython"]) - minPython, recPython = map(translate_version_str2int, r["Python"]) - minTaurusCore, recTaurusCore = map( - translate_version_str2int, r["taurus.core"]) + minPyTango, recPyTango = list(map(translate_version_str2int, r["PyTango"])) + minIPython, recIPython = list(map(translate_version_str2int, r["IPython"])) + minPython, recPython = list(map(translate_version_str2int, r["Python"])) + minTaurusCore, recTaurusCore = list(map( + translate_version_str2int, r["taurus.core"])) currPython = get_python_version_number() currIPython = get_ipython_version_number() @@ -602,10 +603,10 @@ def check_requirements(): if errMsg: errMsg += warnMsg - raise exception.SpockMissingRequirement, errMsg + raise exception.SpockMissingRequirement(errMsg) if warnMsg: - raise exception.SpockMissingRecommended, warnMsg + raise exception.SpockMissingRecommended(warnMsg) return True @@ -768,7 +769,7 @@ def _create_config_file(location, door_name=None): sys.stdout.write('Storing %s in %s... ' % (config_file_name, location)) sys.stdout.flush() - with file(abs_config_file_name, "w") as f: + with open(abs_config_file_name, "w") as f: f.write(dest_data) f.close() sys.stdout.write(MSG_DONE + '\n') @@ -811,11 +812,8 @@ def upgrade_spock_profile(ipy_profile_dir, door_name): _create_config_file(ipy_profile_dir, door_name) -def check_for_upgrade(ipy_profile_dir): - """Check if the current profile is up to date with the spock version - - :param ipy_profile_dir: directory with the spock profile - """ +def get_profile_metadata(ipy_profile_dir): + """Read the profile version string and the door name from the profile""" spock_profile_ver_str = '0.0.0' door_name = None @@ -830,7 +828,7 @@ def check_for_upgrade(ipy_profile_dir): abs_config_file_name = os.path.join(ipy_profile_dir, config_file_name) # search for version and door inside the ipy_profile file - with file(abs_config_file_name, "r") as ipy_config_file: + with open(abs_config_file_name, "r") as ipy_config_file: for i, line in enumerate(ipy_config_file): if i > 20: break # give up after 20 lines @@ -839,6 +837,16 @@ def check_for_upgrade(ipy_profile_dir): if line.startswith('# door_name = '): door_name = line[line.index('=') + 1:].strip() + return spock_profile_ver_str, door_name + + +def check_for_upgrade(ipy_profile_dir): + """Check if the current profile is up to date with the spock version + + :param ipy_profile_dir: directory with the spock profile + """ + spock_profile_ver_str, door_name = get_profile_metadata(ipy_profile_dir) + # convert version from string to numbers spock_lib_ver_str = release.version spocklib_ver = translate_version_str2int(spock_lib_ver_str) @@ -850,22 +858,25 @@ def check_for_upgrade(ipy_profile_dir): alpha_in_spock_profile == alpha_in_spock_lib: return if spocklib_ver < spock_profile_ver: - print '%sYour spock profile (%s) is newer than your spock version ' \ + print('%sYour spock profile (%s) is newer than your spock version ' '(%s)!' % (SpockTermColors.Brown, - spock_profile_ver_str, spock_lib_ver_str) - print 'Please upgrade spock or delete the current profile %s' % SpockTermColors.Normal + spock_profile_ver_str, spock_lib_ver_str)) + print('Please upgrade spock or delete the current profile %s' % + SpockTermColors.Normal) sys.exit(1) # there was no version track of spock profiles since spock 0.2.0 so change # the message if spock_profile_ver_str == '0.0.0': spock_profile_ver_str = '<= 0.2.0' - msg = 'Your current spock door extension profile has been created with spock %s.\n' \ - 'Your current spock door extension version is %s, therefore a profile upgrade is needed.\n' \ - % (spock_profile_ver_str, spock_lib_ver_str) - print msg - prompt = 'Do you wish to upgrade now (warn: this will shutdown the current spock session) ([y]/n)? ' - r = raw_input(prompt) or 'y' + print('Your current spock door extension profile has been created with ' + 'spock %s.\n' + 'Your current spock door extension version is %s, therefore a ' + 'profile upgrade is needed.\n' + % (spock_profile_ver_str, spock_lib_ver_str)) + prompt = ('Do you wish to upgrade now (warn: this will shutdown the ' + 'current spock session) ([y]/n)? ') + r = input(prompt) or 'y' if r.lower() == 'y': upgrade_spock_profile(ipy_profile_dir, door_name) sys.exit(0) @@ -900,7 +911,7 @@ def get_args(argv): while not r in ('y', 'n'): prompt = 'Profile \'%s\' does not exist. Do you want to create '\ 'one now ([y]/n)? ' % profile - r = raw_input(prompt) or 'y' + r = input(prompt) or 'y' if r.lower() == 'y': create_spock_profile(ipython_dir, profile) else: @@ -1221,7 +1232,8 @@ def out_prompt_tokens(self): # ------------------------------------ # ZMQInteractiveShell # ------------------------------------ - #zmq_i_shell = config.ZMQInteractiveShell + zmq_i_shell = config.ZMQInteractiveShell + zmq_i_shell.banner1 = banner # Tell console everything is ready. config.Spock.ready = True @@ -1235,23 +1247,23 @@ def start(user_ns=None): try: check_requirements() - except exception.SpockMissingRequirement, requirement: - print str(requirement) + except exception.SpockMissingRequirement as requirement: + print(str(requirement)) sys.exit(-1) - except exception.SpockMissingRecommended, recommended: - print str(recommended) + except exception.SpockMissingRecommended as recommended: + print(str(recommended)) user_ns = user_ns or {} try: user_ns.update(get_args(sys.argv)) - except exception.SpockException, e: - print e.message - print 'Starting normal IPython console' + except exception.SpockException as e: + print(e) + print('Starting normal IPython console') except KeyboardInterrupt: - print "\nUser pressed Ctrl+C. Exiting..." + print("\nUser pressed Ctrl+C. Exiting...") sys.exit() - except Exception, e: - print 'spock exited with an unmanaged exception: %s' % str(e) + except Exception as e: + print('spock exited with an unmanaged exception: %s' % str(e)) sys.exit(-2) app = TerminalIPythonApp.instance() @@ -1302,7 +1314,7 @@ def prepare_cmdline(argv=None): while not r in ('y', 'n'): prompt = "Profile '%s' does not exist. Do you want to create "\ "one now ([y]/n)? " % profile - r = raw_input(prompt) or 'y' + r = input(prompt) or 'y' if r.lower() == 'y': create_spock_profile(ipython_dir, profile) else: @@ -1330,42 +1342,14 @@ def run(): max_counts = getattr(sardanacustomsettings, 'TAURUS_MAX_DEPRECATION_COUNTS', 0) tauruscustomsettings._MAX_DEPRECATIONS_LOGGED = max_counts - # - - try: - try: - # IPython 4.x - from traitlets import Unicode - from qtconsole.rich_jupyter_widget import RichIPythonWidget - from qtconsole.qtconsoleapp import IPythonQtConsoleApp - # TODO: check if we can/should set IPythonQtConsoleApp.version - except: - # IPython <4.x - from IPython.utils.traitlets import Unicode - from IPython.qt.console.rich_ipython_widget import RichIPythonWidget - from IPython.qt.console.qtconsoleapp import IPythonQtConsoleApp - IPythonQtConsoleApp.version.default_value = release.version - - class SpockConsole(RichIPythonWidget): - - banner = Unicode(config=True) - - def _banner_default(self): - config = get_config() - return config.FrontendWidget.banner - - IPythonQtConsoleApp.widget_factory = SpockConsole - - except ImportError: - pass try: check_requirements() - except exception.SpockMissingRequirement, requirement: - print str(requirement) + except exception.SpockMissingRequirement as requirement: + print(str(requirement)) sys.exit(-1) - except exception.SpockMissingRecommended, recommended: - print str(recommended) + except exception.SpockMissingRecommended as recommended: + print(str(recommended)) prepare_input_handler() prepare_cmdline() diff --git a/src/sardana/spock/magic.py b/src/sardana/spock/magic.py index b659a11b41..0f4b1c6126 100644 --- a/src/sardana/spock/magic.py +++ b/src/sardana/spock/magic.py @@ -31,6 +31,7 @@ 'post_mortem', 'macrodata', 'edmac', 'spock_late_startup_hook', 'spock_pre_prompt_hook'] +from sardana.util.whichpython import which_python_executable from .genutils import MSG_DONE, MSG_FAILED from .genutils import get_ipapi from .genutils import page, get_door, get_macro_server, ask_yes_no, arg_split @@ -42,14 +43,10 @@ def expconf(self, parameter_s=''): try: from sardana.taurus.qt.qtgui.extra_sardana import ExpDescriptionEditor except: - print "Error importing ExpDescriptionEditor " \ - "(hint: is taurus extra_sardana installed?)" + print("Error importing ExpDescriptionEditor " + "(hint: is taurus extra_sardana installed?)") return - try: - doorname = get_door().name() - except TypeError: - # TODO: For Taurus 4 adaptation - doorname = get_door().fullname + doorname = get_door().fullname # ======================================================================= # ugly hack to avoid ipython/qt thread problems #e.g. see # https://sourceforge.net/p/sardana/tickets/10/ @@ -64,7 +61,8 @@ def expconf(self, parameter_s=''): import subprocess import sys fname = sys.modules[ExpDescriptionEditor.__module__].__file__ - args = ['python', fname, doorname] + python_executable = which_python_executable() + args = [python_executable, fname, doorname] if parameter_s == '--auto-update': args.insert(2, parameter_s) subprocess.Popen(args) @@ -85,7 +83,7 @@ def showscan(self, parameter_s=''): """ params = parameter_s.split() door = get_door() - online, scan_nb = False, None + scan_nb = None if len(params) > 0: if params[0].lower() == 'online': try: @@ -93,14 +91,11 @@ def showscan(self, parameter_s=''): ShowScanOnline except Exception as e: - print "Error importing ShowScanOnline" - print e + print("Error importing ShowScanOnline") + print(e) return - try: - doorname = get_door().name() - except TypeError: - # TODO: For Taurus 4 adaptation - doorname = get_door().fullname + + doorname = get_door().fullname # =============================================================== # ugly hack to avoid ipython/qt thread problems #e.g. see # https://sourceforge.net/p/sardana/tickets/10/ @@ -115,15 +110,14 @@ def showscan(self, parameter_s=''): import subprocess import sys fname = sys.modules[ShowScanOnline.__module__].__file__ - args = ['python', fname, doorname] + python_executable = which_python_executable() + args = [python_executable, fname, doorname, + '--taurus-log-level=error'] subprocess.Popen(args) - - # show the scan plot, ignoring the plot configuration - elif params[0].lower() == 'online_raw': - online = True + return else: scan_nb = int(params[0]) - door.show_scan(scan_nb, online=online) + door.show_scan(scan_nb) def spsplot(self, parameter_s=''): @@ -145,17 +139,17 @@ def debug(self, parameter_s=''): door = get_door() if len(params) == 0: s = door.getDebugMode() and 'on' or 'off' - print "debug mode is %s" % s + print("debug mode is %s" % s) return elif len(params) == 1: s = params[0].lower() if s not in ('off', 'on'): - print "Usage: debug [on|off]" + print("Usage: debug [on|off]") return door.setDebugMode(s == 'on') - print "debug mode is now %s" % s + print("debug mode is now %s" % s) else: - print "Usage: debug [on|off]" + print("Usage: debug [on|off]") def www(self, parameter_s=''): @@ -177,7 +171,7 @@ def www(self, parameter_s=''): return exc = "".join(last_macro.exc_stack) door.write(exc) - except Exception, e: + except Exception as e: door.writeln("Unexpected exception occurred executing www:", stream=door.Error) door.writeln(str(e), stream=door.Error) @@ -249,7 +243,7 @@ def edmac(self, parameter_s=''): macro_info_obj = ms.getMacroInfoObj(macro_name) if not is_new_macro: if macro_info_obj is None: - print "Macro '%s' could not be found" % macro_name + print("Macro '%s' could not be found" % macro_name) return macro_lib = macro_info_obj.module @@ -259,21 +253,21 @@ def edmac(self, parameter_s=''): ' override the already existing macro in module "%s"' % (macro_name, macro_lib, macro_info_obj.module)) if not ask_yes_no(msg, 'y'): - print "Aborting edition..." + print("Aborting edition...") return macro_info = (macro_lib, macro_name) - print 'Opening %s.%s...' % macro_info + print('Opening %s.%s...' % macro_info) try: remote_fname, code, line_nb = ms.GetMacroCode(macro_info) - except PyTango.DevFailed, e: + except PyTango.DevFailed as e: PyTango.Except.print_exception(e) return fd, local_fname = tempfile.mkstemp(prefix='spock_%s_' % pars[0], suffix='.py', text=True) - os.write(fd, code) + os.write(fd, code.encode('utf8')) os.close(fd) cmd = 'edit -x -n %s %s' % (line_nb, local_fname) @@ -281,23 +275,23 @@ def edmac(self, parameter_s=''): ip.magic(cmd) if ask_yes_no('Do you want to apply the new code on the server?', 'y'): - print 'Storing...', + print('Storing...', end=' ') try: - f = file(local_fname) + f = open(local_fname) try: new_code = f.read() ms.SetMacroCode([remote_fname, new_code]) - print MSG_DONE - except Exception, e: - print MSG_FAILED - print 'Reason:', str(e) + print(MSG_DONE) + except Exception as e: + print(MSG_FAILED) + print('Reason:', str(e)) f.close() except: - print 'Could not open file \'%s\' for safe transfer to the ' \ - 'server' % local_fname - print 'Did you forget to save?' + print('Could not open file \'%s\' for safe transfer to the ' + 'server' % local_fname) + print('Did you forget to save?') else: - print "Discarding changes..." + print("Discarding changes...") # if os.path.exists(local_fname): # if ask_yes_no('Delete temporary file \'%s\'?' % local_fname, 'y'): @@ -317,7 +311,7 @@ def spock_late_startup_hook(self): except: import traceback - print "Exception in spock_late_startup_hook:" + print("Exception in spock_late_startup_hook:") traceback.print_exc() @@ -327,7 +321,7 @@ def spock_pre_prompt_hook(self): except: import traceback - print "Exception in spock_pre_prompt_hook:" + print("Exception in spock_pre_prompt_hook:") traceback.print_exc() # def spock_pre_runcode_hook(self): diff --git a/src/sardana/spock/parameter.py b/src/sardana/spock/parameter.py index 8352a665c0..78ecff0d68 100644 --- a/src/sardana/spock/parameter.py +++ b/src/sardana/spock/parameter.py @@ -93,7 +93,7 @@ def __init__(self, name=None, desc=None, opts=None, param_def=None, type_name = from_array.read() if type_name != 'ParamRepeat': msg = 'Expecting "ParamRepeat" type, got ' + type_name - raise ValueError, msg + raise ValueError(msg) self.desc = from_array.read() opt_str = from_array.read() opt_list = opt_str.split(', ') @@ -174,8 +174,8 @@ def getParamCount(self): nb = 0 for par in self.pars: local_nb = par.getParamCount() - if local_nb == sys.maxint: - return sys.maxint + if local_nb == sys.maxsize: + return sys.maxsize nb += local_nb return nb diff --git a/src/sardana/spock/release.py b/src/sardana/spock/release.py deleted file mode 100644 index ec1a9cc1ee..0000000000 --- a/src/sardana/spock/release.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -############################################################################## -## -# This file is part of Sardana -## -# http://www.sardana-controls.org/ -## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain -## -# Sardana is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -## -# Sardana is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -## -# You should have received a copy of the GNU Lesser General Public License -# along with Sardana. If not, see . -## -############################################################################## - -# DEPRECATED MODULE!!!! -# This module is deprecated avoid to use anything from it. -# Use sardana.release module instead -#### - -"""Release data for the Spock project. -""" - -# Name of the package for release purposes. This is the name which labels -# the tarballs and RPMs made by distutils, so it's best to lowercase it. -name = 'spock' - -# For versions with substrings (like 0.6.16.svn), use an extra . to separate -# the new substring. We have to avoid using either dashes or underscores, -# because bdist_rpm does not accept dashes (an RPM) convention, and -# bdist_deb does not accept underscores (a Debian convention). - -revision = '1' - -#version = '0.8.1.svn.r' + revision.rstrip('M') -version = '2.1.1' - -description = "An enhanced interactive Macro Server shell." - -long_description = \ - """ -Spock provides an interactive environment for interacting with the Tango -MacroServer Device. It is completely based on IPython which itself provides a -replacement for the interactive Python interpreter with extra functionality. - """ - -license = 'GNU' - -authors = {'Tiago': ('Tiago Coutinho', 'tiago.coutinho@esrf.fr'), - 'Reszela': ('Zbigniew Reszela', 'zreszela@cells.es'), - 'Pascual-Izarra': ('Carlos Pascual-Izarra', 'cpascual@cells.es')} - -url = '' - -download_url = '' - -platforms = ['Linux', 'Windows XP/2000/NT', 'Windows 95/98/ME'] - -keywords = ['Sardana', 'Interactive', 'MacroServer', 'Tango', 'Shell'] diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py index ed3a0b77c0..adbcf9853a 100644 --- a/src/sardana/spock/spockms.py +++ b/src/sardana/spock/spockms.py @@ -33,7 +33,7 @@ import ctypes import PyTango -from taurus.core import TaurusEventType, TaurusSWDevState +from taurus.core import TaurusEventType, TaurusSWDevState, TaurusDevState from sardana.sardanautils import is_pure_str, is_non_str_seq from sardana.spock import genutils @@ -53,12 +53,8 @@ from sardana.taurus.core.tango.sardana.macroserver import BaseDoor, BaseMacroServer BaseGUIViewer = object -try: - RUNNING_STATE = TaurusSWDevState.Running -except RuntimeError: - # TODO: For Taurus 4 compatibility - from taurus.core import TaurusDevState - RUNNING_STATE = TaurusDevState.Ready + +RUNNING_STATE = TaurusDevState.Ready class GUIViewer(BaseGUIViewer): @@ -71,19 +67,6 @@ def run(self): self.plot() def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): - if scan_nb is None and scan_history_info is None: - #================================================================== - # Hack to avoid ipython-qt issues. See similar workaround in expconf magic command - # @todo: do this in a better way - #import taurus.qt.qtgui.plot - #w = taurus.qt.qtgui.plot.TaurusTrend() - #w.model = "scan://" + self._door.getNormalName() - # w.show() - import subprocess - args = ['taurustrend', 'scan://%s' % self._door.getNormalName()] - subprocess.Popen(args) - #================================================================== - return scan_dir, scan_file = None, None if scan_nb is None: import h5py @@ -92,7 +75,7 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): scan_file = scan.get('ScanFile') if scan_dir is None or scan_file is None: continue - if not isinstance(scan_file, (str, unicode)): + if not isinstance(scan_file, str): scan_files = scan_file scan_file = None for fname in scan_files: @@ -116,8 +99,8 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): return break else: - print "Cannot plot scan:" - print "No scan in scan history was saved into a file" + print("Cannot plot scan:") + print("No scan in scan history was saved into a file") return else: for scan in reversed(scan_history_info): @@ -125,15 +108,15 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): scan_dir = scan.get('ScanDir') scan_file = scan.get('ScanFile') if scan_dir is None or scan_file is None: - print "Cannot plot scan:" - print "Scan %d was not saved into a file" % (scan_nb,) + print("Cannot plot scan:") + print("Scan %d was not saved into a file" % (scan_nb,)) return - if not isinstance(scan_file, (str, unicode)): + if not isinstance(scan_file, str): scan_file = scan_file[0] break else: - print "Cannot plot scan:" - print "Scan %d not found in scan history" % (scan_nb,) + print("Cannot plot scan:") + print("Scan %d not found in scan history" % (scan_nb,)) return remote_file = os.path.join(scan_dir, scan_file) @@ -148,7 +131,7 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): local_directories = directory_map[scan_dir] # local_directories may be either a string or a list of strings # the further logic is unified to work on a list, so convert it - if isinstance(local_directories, (str, unicode)): + if isinstance(local_directories, str): local_directories = [local_directories] locations = local_directories if scan_dir not in locations: @@ -159,16 +142,17 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): local_file = os.path.join(local_directory, scan_file) break if local_file is None: - print "Cannot plot scan:" - print "Could not find %s in any of the following locations:" % (scan_file,) - print "\n".join(locations) + print("Cannot plot scan:") + print("Could not find %s in any of the following locations:" % + (scan_file,)) + print("\n".join(locations)) return import taurus.qt.qtgui.extra_nexus taurus_nexus_widget = taurus.qt.qtgui.extra_nexus.TaurusNeXusBrowser() taurus_nexus_widget.setMinimumSize(800, 600) - print "Trying to open local scan file %s..." % (local_file,) + print("Trying to open local scan file %s..." % (local_file,)) taurus_nexus_widget.openFile(local_file) taurus_nexus_widget.show() nexus_widget = taurus_nexus_widget.neXusWidget() @@ -189,32 +173,33 @@ def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None): file_model = nexus_widget.model() title = file_model.getNodeFromIndex(title_index)[0] windowTitle += " - " + title - except Exception, e: - print "Cannot plot scan:" - print str(e) + except Exception as e: + print("Cannot plot scan:") + print(str(e)) taurus_nexus_widget.setWindowTitle(windowTitle) def plot(self): try: import sps - except: - print 'sps module not available. No plotting' + except Exception: + print('sps module not available. No plotting') return try: import pylab - except: - print "pylab not available (try running 'spock -pylab'). No plotting" + except Exception: + print("pylab not available (try running 'spock -pylab'). " + "No plotting") return door = genutils.get_door() try: env = dict(door.getEnvironmentObj().read().value) - except Exception, e: - print 'Unable to read environment. No plotting' - print str(e) + except Exception as e: + print('Unable to read environment. No plotting') + print(str(e)) return program = door.getNormalName().replace('/', '').replace('_', '') @@ -223,26 +208,26 @@ def plot(self): '/', '').replace('_', '').upper() + "0D" array_ENV = '%s_ENV' % array except: - print 'ActiveMntGrp not defined. No plotting' + print('ActiveMntGrp not defined. No plotting') return if not program in sps.getspeclist(): - print '%s not found. No plotting' % program + print('%s not found. No plotting' % program) return if not array in sps.getarraylist(program): - print '%s not found in %s. No plotting' % (array, program) + print('%s not found in %s. No plotting' % (array, program)) return if not array_ENV in sps.getarraylist(program): - print '%s not found in %s. No plotting' % (array_ENV, program) + print('%s not found in %s. No plotting' % (array_ENV, program)) return try: mem = sps.attach(program, array) mem_ENV = sps.attach(program, array_ENV) - except Exception, e: - print 'sps.attach error: %s. No plotting' % str(e) + except Exception as e: + print('sps.attach error: %s. No plotting' % str(e)) return # reconstruct the environment @@ -259,7 +244,7 @@ def plot(self): col_nb = len(labels) if col_nb < 4: - print 'No data columns available in sps' + print('No data columns available in sps') return rows = int(env['nopts']) @@ -270,7 +255,7 @@ def plot(self): colors = 'bgrcmyk' col_nb = min(col_nb, len(colors) + 3) # skip point_nb, motor and timer columns - for i in xrange(3, col_nb): + for i in range(3, col_nb): y = m[i][:rows] line, = pylab.plot(x, y, label=labels[i]) line.linestyle = '-' @@ -297,7 +282,7 @@ class SpockBaseDoor(BaseDoor): def __init__(self, name, **kw): self._consoleReady = kw.get("consoleReady", False) - if not kw.has_key('silent'): + if 'silent' not in kw: kw['silent'] = False self._lines = [] self._spock_state = None @@ -347,42 +332,84 @@ def preRunMacro(self, obj, parameters): def runMacro(self, obj, parameters=[], synch=False): return BaseDoor.runMacro(self, obj, parameters=parameters, synch=synch) + def _handle_second_release(self): + try: + self.write('4th Ctrl-C received: Releasing...\n') + self.release() + self.block_lines = 0 + self.release() + self.writeln("Releasing done!") + except KeyboardInterrupt: + self.write('5th Ctrl-C received: Giving up...\n') + self.write('(The macro is probably still executing the on_abort ' + 'method. Either wait or restart the server.)\n') + + def _handle_first_release(self): + try: + self.write('3rd Ctrl-C received: Releasing...\n') + self.block_lines = 0 + self.release() + self.writeln("Releasing done!") + except KeyboardInterrupt: + self._handle_second_release() + + def _handle_abort(self): + try: + self.write('2nd Ctrl-C received: Aborting...\n') + self.block_lines = 0 + self.abort() + self.writeln("Aborting done!") + except KeyboardInterrupt: + self._handle_first_release() + + def _handle_stop(self): + try: + self.write('\nCtrl-C received: Stopping...\n') + self.block_lines = 0 + self.stop() + self.writeln("Stopping done!") + except KeyboardInterrupt: + self._handle_abort() + def _runMacro(self, xml, **kwargs): # kwargs like 'synch' are ignored in this re-implementation if self._spock_state != RUNNING_STATE: - print "Unable to run macro: No connection to door '%s'" % self.getSimpleName() + print("Unable to run macro: No connection to door '%s'" % + self.getSimpleName()) raise Exception("Unable to run macro: No connection") + if self.stateObj.read().rvalue == PyTango.DevState.RUNNING: + print("Another macro is running. Wait until it finishes...") + raise Exception("Unable to run macro: door in RUNNING state") if xml is None: xml = self.getRunningXML() kwargs['synch'] = True try: return BaseDoor._runMacro(self, xml, **kwargs) except KeyboardInterrupt: - self.write('\nCtrl-C received: Stopping... ') - self.block_lines = 0 - self.stop() - self.writeln("Done!") - except PyTango.DevFailed, e: + self._handle_stop() + except PyTango.DevFailed as e: if is_non_str_seq(e.args) and \ - not isinstance(e.args, (str, unicode)): + not isinstance(e.args, str): reason, desc = e.args[0].reason, e.args[0].desc macro_obj = self.getRunningMacro() if reason == 'MissingParam': - print "Missing parameter:", desc - print macro_obj.getInfo().doc + print("Missing parameter:", desc) + print(macro_obj.getInfo().doc) elif reason == 'WrongParam': - print "Wrong parameter:", desc - print macro_obj.getInfo().doc + print("Wrong parameter:", desc) + print(macro_obj.getInfo().doc) elif reason == 'UnkownParamObj': - print "Unknown parameter:", desc + print("Unknown parameter:", desc) elif reason == 'MissingEnv': - print "Missing environment:", desc - elif reason in ('API_CantConnectToDevice', 'API_DeviceNotExported'): + print("Missing environment:", desc) + elif reason in ('API_CantConnectToDevice', + 'API_DeviceNotExported'): self._updateState(self._old_sw_door_state, TaurusSWDevState.Shutdown, silent=True) - print "Unable to run macro: No connection to door '%s'" % self.getSimpleName() + print("Unable to run macro: No connection to door '%s'" % + self.getSimpleName()) else: - print "Unable to run macro:", reason, desc + print("Unable to run macro:", reason, desc) def _getMacroResult(self, macro): ret = None @@ -420,10 +447,7 @@ def _getMacroResult(self, macro): def plot(self): self._plotter.run() - def show_scan(self, scan_nb=None, online=False): - if online: - self._plotter.show_scan() - return + def show_scan(self, scan_nb=None): env = self.getEnvironment() scan_history_info = env.get("ScanHistory") directory_map = env.get("DirectoryMap") @@ -484,7 +508,9 @@ def processRecordData(self, data): if data is None: return data = data[1] - if data['type'] == 'function': + if (isinstance(data, dict) + and 'type' in data + and data['type'] == 'function'): func_name = data['func_name'] if func_name.startswith("pyplot."): func_name = self.MathFrontend + "." + func_name @@ -522,7 +548,7 @@ def _processRecordData(self, data): value = data.value size = len(value[1]) if size > self._RECORD_DATA_THRESOLD: - sizekb = size / 1024 + sizekb = size // 1024 self.logReceived(self.Info, ['Received long data record (%d Kb)' % sizekb, 'It may take some time to process. Please wait...']) return BaseDoor._processRecordData(self, data) @@ -589,7 +615,7 @@ def _addElement(self, element_data): # TODO: when it becomes possible to do: # some taurus.Device. = # replace device_proxy with element - device_proxy = element.getObj().getHWObj() + device_proxy = element.getObj().getDeviceProxy() genutils.expose_variable(element.name, device_proxy) return element @@ -627,7 +653,7 @@ def macro_fn(parameter_s='', name=macro_name): if macro is not None: # maybe none if macro was aborted return macro.getResult() - macro_fn.func_name = macro_name + macro_fn.__name__ = macro_name macro_fn.__doc__ = macro_info.doc + "\nWARNING: do not rely on the" \ " file path below\n" diff --git a/src/sardana/spock/test/test_parameter.py b/src/sardana/spock/test/test_parameter.py index 89783d635a..bc5e42a99e 100644 --- a/src/sardana/spock/test/test_parameter.py +++ b/src/sardana/spock/test/test_parameter.py @@ -25,7 +25,7 @@ """test_parameter module documentation""" -from taurus.external import unittest +import unittest from sardana.spock import parameter diff --git a/src/sardana/tango/__init__.py b/src/sardana/tango/__init__.py index 5242b40b6e..4e609cc46c 100644 --- a/src/sardana/tango/__init__.py +++ b/src/sardana/tango/__init__.py @@ -31,17 +31,17 @@ def prepare_sardana(util): - import pool - import macroserver + from . import pool + from . import macroserver pool.prepare_pool(util) macroserver.prepare_macroserver(util) def main_sardana(args=None, start_time=None, mode=None): - import core.util + from .core import util # pass server name so the scripts generated with setuptools work on Windows - return core.util.run(prepare_sardana, args=args, start_time=start_time, - mode=mode, name=SERVER_NAME) + return util.run(prepare_sardana, args=args, start_time=start_time, + mode=mode, name=SERVER_NAME) run = main_sardana diff --git a/src/sardana/tango/core/SardanaDevice.py b/src/sardana/tango/core/SardanaDevice.py index bbfd962b08..3e496be7a2 100644 --- a/src/sardana/tango/core/SardanaDevice.py +++ b/src/sardana/tango/core/SardanaDevice.py @@ -25,7 +25,7 @@ """Generic Sardana Tango device module""" -from __future__ import with_statement + __all__ = ["SardanaDevice", "SardanaDeviceClass"] @@ -184,7 +184,7 @@ def init_device_nodb(self): """Internal method. Initialize the device when tango database is not being used (example: in demos)""" _, _, props = self._get_nodb_device_info() - for prop_name, prop_value in props.items(): + for prop_name, prop_value in list(props.items()): setattr(self, prop_name, prop_value) def delete_device(self): @@ -254,12 +254,12 @@ def set_write_attribute(self, attr, w_value): try: attr.set_write_value(w_value) except DevFailed as df: - df0 = df[0] + df0 = df.args[0] reason = df0.reason # if outside limit prefix the description with the device name if reason == PyTango.constants.API_WAttrOutsideLimit: desc = self.alias + ": " + df0.desc - _df = DevFailed(*df[1:]) + _df = DevFailed(*df.args[1:]) PyTango.Except.re_throw_exception( _df, df0.reason, desc, df0.origin) raise df @@ -405,7 +405,7 @@ def _set_attribute_push(self, attr, value=None, w_value=None, timestamp=None, try: attr.set_write_value(w_value) except DevFailed as df: - error = df[0] + error = df.args[0] reason = error.reason if reason == PyTango.constants.API_WAttrOutsideLimit and\ attr_name == 'position': diff --git a/src/sardana/tango/core/attributehandler.py b/src/sardana/tango/core/attributehandler.py index 9c130a3a30..3517c0769e 100644 --- a/src/sardana/tango/core/attributehandler.py +++ b/src/sardana/tango/core/attributehandler.py @@ -34,6 +34,7 @@ import operator from taurus.core.util.containers import LIFO +import collections class AttributeLogHandler(logging.Handler): @@ -70,8 +71,8 @@ def clearBuffer(self): self._buff.clear() def appendBuffer(self, d): - if operator.isSequenceType(d): - if isinstance(d, (str, unicode)): + if isinstance(d, collections.Sequence): + if isinstance(d, str): self._buff.append(d) else: self._buff.extend(d) diff --git a/src/sardana/tango/core/util.py b/src/sardana/tango/core/util.py index 04a2a654fd..7f84681d03 100644 --- a/src/sardana/tango/core/util.py +++ b/src/sardana/tango/core/util.py @@ -61,7 +61,8 @@ import sardana from sardana import State, SardanaServer, DataType, DataFormat, InvalidId, \ - DataAccess, to_dtype_dformat, to_daccess, Release, ServerRunMode + DataAccess, to_dtype_dformat, to_daccess, Release, ServerRunMode, \ + AttrQuality from sardana.sardanaexception import SardanaException, AbortException from sardana.sardanavalue import SardanaValue from sardana.util.wrap import wraps @@ -286,6 +287,33 @@ def __get_last_write_value(attribute): return lrv +def _check_attr_range(dev_name, attr_name, attr_value): + util = PyTango.Util.instance() + dev = util.get_device_by_name(dev_name) + multi_attr = dev.get_device_attr() + attr = multi_attr.get_w_attr_by_name(attr_name) + try: + min_value = attr.get_min_value() + # not specified min value raises DevFailed + except PyTango.DevFailed: + pass + else: + if attr_value < min_value: + msg = "w_value {} of {}/{} is lower than min_value {}".format( + attr_value, dev_name, attr_name, min_value) + raise ValueError(msg) + try: + max_value = attr.get_max_value() + # not specified max value raises DevFailed + except PyTango.DevFailed: + pass + else: + if attr_value > max_value: + msg = "w_value {} of {}/{} is greater than max_value {}".format( + attr_value, dev_name, attr_name, max_value) + raise ValueError(msg) + + def memorize_write_attribute(write_attr_func): """The main purpose is to use this as a decorator for write_ device methods. @@ -389,7 +417,7 @@ def from_tango_state_to_state(state): DataType.String: DevString, DataType.Boolean: DevBoolean, } -R_TTYPE_MAP = dict((v, k) for k, v in TTYPE_MAP.items()) +R_TTYPE_MAP = dict((v, k) for k, v in list(TTYPE_MAP.items())) #: dictionary dict<:class:`sardana.DataFormat`, :class:`PyTango.AttrFormat`> TFORMAT_MAP = { @@ -397,16 +425,29 @@ def from_tango_state_to_state(state): DataFormat.OneD: SPECTRUM, DataFormat.TwoD: IMAGE, } -R_TFORMAT_MAP = dict((v, k) for k, v in TFORMAT_MAP.items()) +R_TFORMAT_MAP = dict((v, k) for k, v in list(TFORMAT_MAP.items())) + -#: dictionary dict<:class:`sardana.DataAccess`, :class:`PyTango.AttrWriteType`> +#: dictionary dict<:class:`sardana.AttrQuality`, :class:`PyTango.AttrQuality`> +TQUALITY_MAP = { + AttrQuality.Valid: PyTango.AttrQuality.ATTR_VALID, + AttrQuality.Invalid: PyTango.AttrQuality.ATTR_INVALID, + AttrQuality.Alarm: PyTango.AttrQuality.ATTR_ALARM, + AttrQuality.Changing: PyTango.AttrQuality.ATTR_CHANGING, + AttrQuality.Warning: PyTango.AttrQuality.ATTR_WARNING +} + + +R_TQUALITY_MAP = dict((v, k) for k, v in list(TQUALITY_MAP.items())) + + +#: dictionary dict<:class:`sardana.AttrQuality`, :class:`PyTango.AttrQuality`> TACCESS_MAP = { DataAccess.ReadOnly: READ, DataAccess.ReadWrite: READ_WRITE, } -R_TACCESS_MAP = dict((v, k) for k, v in TACCESS_MAP.items()) - +R_TACCESS_MAP = dict((v, k) for k, v in list(TACCESS_MAP.items())) def exception_str(etype=None, value=None, sep='\n'): if etype is None: @@ -468,6 +509,16 @@ def from_tango_type_format(dtype, dformat=PyTango.SCALAR): return R_TTYPE_MAP[dtype], R_TFORMAT_MAP[dformat] +def to_tango_quality(quality): + """Transforms a :obj:`~sardana.AttrQuality` into a + :obj:`~PyTango.AttrQuality` + + :param access: the quality to be transformed + :type access: :obj:`~sardana.AttrQuality` + :return: the tango attribute quality + :rtype: :obj:`PyTango.AttrQuality`""" + return TQUALITY_MAP[quality] + def to_tango_attr_info(attr_name, attr_info): if isinstance(attr_info, DataInfo): data_type, data_format = attr_info.dtype, attr_info.dformat @@ -528,17 +579,17 @@ def ask_yes_no(prompt, default=None): elif d_l in ('n', 'no'): prompt += " (N/y) ?" - while ans not in answers.keys(): + while ans not in list(answers.keys()): try: - ans = raw_input(prompt + ' ').lower() + ans = input(prompt + ' ').lower() if not ans: # response was an empty string ans = default except KeyboardInterrupt: - print + print() except EOFError: - if default in answers.keys(): + if default in list(answers.keys()): ans = default - print + print() else: raise @@ -706,27 +757,27 @@ def prepare_server(args, tango_args): nodb = "-nodb" in tango_args if nodb and not hasattr(DeviceClass, "device_name_factory"): - print "In order to start %s with 'nodb' you need PyTango >= 7.2.3" %\ - server_name + print("In order to start %s with 'nodb' you need PyTango >= 7.2.3" % + server_name) sys.exit(1) if len(tango_args) < 2: valid = False while not valid: - inst_name = raw_input( + inst_name = input( "Please indicate %s instance name: " % server_name) # should be a instance name validator. - valid_set = string.letters + string.digits + '_' + '-' + valid_set = string.ascii_letters + string.digits + '_' + '-' out = ''.join([c for c in inst_name if c not in valid_set]) valid = len(inst_name) > 0 and len(out) == 0 if not valid: - print "We only accept alphanumeric combinations" + print("We only accept alphanumeric combinations") args.append(inst_name) tango_args.append(inst_name) else: inst_name = tango_args[1].lower() - if "-nodb" in tango_args: + if nodb: return log_messages db = Database() @@ -738,12 +789,12 @@ def prepare_server(args, tango_args): # to pool_names = [] pools = get_dev_from_class(db, "Pool") - all_pools = pools.keys() - for pool in pools.values(): + all_pools = list(pools.keys()) + for pool in list(pools.values()): pool_alias = pool[2] if pool_alias is not None: all_pools.append(pool_alias) - all_pools = map(str.lower, all_pools) + all_pools = list(map(str.lower, all_pools)) pools_for_choosing = [] for i in pools: pools_for_choosing.append(pools[i][3]) @@ -756,7 +807,7 @@ def prepare_server(args, tango_args): else: print("\nAvailable Pools:") for pool in pools_for_choosing: - print pool + print(pool) print("") while True: msg = "Please select the Pool to connect to " \ @@ -764,19 +815,19 @@ def prepare_server(args, tango_args): # user may abort it with Ctrl+C - this will not # register anything in the database and the # KeyboardInterrupt will be raised - elem = raw_input(msg).strip() + elem = input(msg).strip() # no pools selected and user ended loop if len(elem) == 0 and len(pool_names) == 0: print(no_pool_msg) break # user ended loop with some pools selected elif len(elem) == 0: - print("\nMacroServer %s has been connected to " - "Pool/s %s\n" % (inst_name, pool_names)) + print(("\nMacroServer %s has been connected to " + "Pool/s %s\n" % (inst_name, pool_names))) break # user entered unknown pool elif elem.lower() not in all_pools: - print "Unknown pool element" + print("Unknown pool element") else: pool_names.append(elem) log_messages += register_sardana(db, server_name, inst_name, @@ -789,7 +840,7 @@ def prepare_server(args, tango_args): def exists_server_instance(db, server_name, server_instance): - known_inst = map(str.lower, db.get_instance_name_list(server_name)) + known_inst = list(map(str.lower, db.get_instance_name_list(server_name))) return server_instance.lower() in known_inst @@ -915,7 +966,7 @@ def get_dev_from_class_server(db, classname, server): def pairwise(iterable): "s -> (s0, s1), (s2, s3), (s4, s5), ..." a = iter(iterable) - return itertools.izip(a, a) + return list(zip(a, a)) devices = [] device_class_list = db.get_device_class_list(server) @@ -1108,7 +1159,7 @@ def prepare_logging(options, args, tango_args, start_time=None, taurus._handlers = handlers = [] try: if not os.path.exists(path): - os.makedirs(path, 0777) + os.makedirs(path, 0o777) from sardana import sardanacustomsettings maxBytes = getattr(sardanacustomsettings, 'LOG_FILES_SIZE', 1E7) @@ -1247,8 +1298,8 @@ def terminate(self): try: log_messages.extend(prepare_server(args, tango_args)) - except AbortException, e: - print e.message + except AbortException as e: + print(e) return except KeyboardInterrupt: print("\nInterrupted by keyboard") diff --git a/src/sardana/tango/macroserver/Door.py b/src/sardana/tango/macroserver/Door.py index 8def9d4c7d..cfee3e75f3 100644 --- a/src/sardana/tango/macroserver/Door.py +++ b/src/sardana/tango/macroserver/Door.py @@ -175,7 +175,7 @@ def delete_device(self): self.macro_executor.abort() self.macro_executor.clearRunningMacro() - for handler, filter, format in self._handler_dict.values(): + for handler, filter, format in list(self._handler_dict.values()): handler.finish() door = self.door @@ -271,6 +271,8 @@ def on_door_changed(self, event_source, event_type, event_value): event_value = self.calculate_tango_status(event_value) elif name == "recorddata": format, value = event_value + if format is None: + format = "utf8_json" codec = CodecFactory().getCodec(format) event_value = codec.encode(('', value)) else: @@ -281,7 +283,7 @@ def on_door_changed(self, event_source, event_type, event_value): event_value = event_value.value if attr.get_data_type() == ArgType.DevEncoded: - codec = CodecFactory().getCodec('json') + codec = CodecFactory().getCodec('utf8_json') event_value = codec.encode(('', event_value)) self.set_attribute(attr, value=event_value, timestamp=timestamp) @@ -340,7 +342,7 @@ def read_RecordData(self, attr): macro_data = self.door.get_macro_data() codec = CodecFactory().getCodec('bz2_pickle') data = codec.encode(('', macro_data)) - except MacroServerException, mse: + except MacroServerException as mse: throw_sardana_exception(mse) attr.set_value(*data) @@ -352,10 +354,6 @@ def read_RecordData(self, attr): def read_MacroStatus(self, attr): attr.set_value('', '') - def Abort(self): - self.debug("Abort is deprecated. Use StopMacro instead") - return self.StopMacro() - def AbortMacro(self): macro = self.getRunningMacro() if macro is None: @@ -363,13 +361,23 @@ def AbortMacro(self): self.debug("aborting %s" % macro._getDescription()) self.macro_executor.abort() - def is_Abort_allowed(self): - return True + def ReleaseMacro(self): + macro = self.getRunningMacro() + if macro is None: + return + self.debug("releasing %s" % macro._getDescription()) + self.macro_executor.release() + + def is_ReleaseMacro_allowed(self): + is_release_allowed = (self.get_state() == Macro.Running + or self.get_state() == Macro.Pause) + return is_release_allowed + def PauseMacro(self): macro = self.getRunningMacro() if macro is None: - print "Unable to pause Null macro" + print("Unable to pause Null macro") return self.macro_executor.pause() @@ -400,14 +408,15 @@ def is_ResumeMacro_allowed(self): def RunMacro(self, par_str_list): # first empty all the buffers - for handler, filter, fmt in self._handler_dict.values(): + for handler, filter, fmt in list(self._handler_dict.values()): handler.clearBuffer() if len(par_str_list) == 0: return [] xml_seq = self.door.run_macro(par_str_list, asynch=True) - return [etree.tostring(xml_seq, pretty_print=False)] + return [etree.tostring(xml_seq, encoding='unicode', + pretty_print=False)] def is_RunMacro_allowed(self): return self.get_state() in [Macro.Finished, Macro.Abort, @@ -424,7 +433,7 @@ def GetMacroEnv(self, argin): macro_env = self.door.get_macro_class_info(macro_name).env env = self.door.get_env(macro_env, macro_name=macro_name) ret = [] - for k, v in env.iteritems(): + for k, v in env.items(): ret.extend((k, v)) return ret @@ -456,15 +465,15 @@ class DoorClass(SardanaDeviceClass): # Command definitions cmd_list = { - 'Abort': - [[DevVoid, ""], - [DevVoid, ""]], 'PauseMacro': [[DevVoid, ""], [DevVoid, ""]], 'AbortMacro': [[DevVoid, ""], [DevVoid, ""]], + 'ReleaseMacro': + [[DevVoid, ""], + [DevVoid, ""]], 'StopMacro': [[DevVoid, ""], [DevVoid, ""]], diff --git a/src/sardana/tango/macroserver/MacroServer.py b/src/sardana/tango/macroserver/MacroServer.py index dd80efb168..b21c0f14c6 100644 --- a/src/sardana/tango/macroserver/MacroServer.py +++ b/src/sardana/tango/macroserver/MacroServer.py @@ -68,7 +68,7 @@ def delete_device(self): self._macro_server.clear_log_report() # Workaround for bug #494. factory = taurus.Factory("tango") - for attr in factory.tango_attrs.values(): + for attr in list(factory.tango_attrs.values()): attr.cleanUp() def init_device(self): @@ -153,7 +153,7 @@ def on_macro_server_changed(self, evt_src, evt_type, evt_value): # force the element list cache to be rebuild next time someone reads # the element list self.ElementsCache = None - self.set_attribute(elems_attr, value=evt_value.value) + self.set_attribute(elems_attr, value=evt_value.rvalue) #self.push_change_event('Elements', *evt_value.value) elif evt_name in ("elementcreated", "elementdeleted"): # force the element list cache to be rebuild next time someone reads @@ -169,7 +169,7 @@ def on_macro_server_changed(self, evt_src, evt_type, evt_value): key = 'del' json_elem = elem.serialize(pool=self.pool.full_name) value[key] = json_elem, - value = CodecFactory().getCodec('json').encode(('', value)) + value = CodecFactory().getCodec('utf8_json').encode(('', value)) self.set_attribute(elems_attr, value=value) #self.push_change_event('Elements', *value) elif evt_name == "elementschanged": @@ -190,7 +190,7 @@ def on_macro_server_changed(self, evt_src, evt_type, evt_value): deleted_values.append(json_elem) value = {"new": new_values, "change": changed_values, "del": deleted_values} - value = CodecFactory().getCodec('json').encode(('', value)) + value = CodecFactory().getCodec('utf8_json').encode(('', value)) self.set_attribute(elems_attr, value=value) #self.push_change_event('Elements', *value) elif evt_name == "environmentchanged": @@ -229,7 +229,7 @@ def getElements(self, cache=True): return value elements = self.macro_server.get_elements_info() value = dict(new=elements) - value = CodecFactory().getCodec('json').encode(('', value)) + value = CodecFactory().getCodec('utf8_json').encode(('', value)) self.ElementsCache = value return value @@ -261,7 +261,7 @@ def GetMacroInfo(self, macro_names): codec = CodecFactory().getCodec('json') ret = [] - for _, macro in macro_server.get_macros().items(): + for _, macro in list(macro_server.get_macros().items()): if macro.name in macro_names: ret.append(codec.encode(('', macro.serialize()))[1]) @@ -272,7 +272,7 @@ def ReloadMacro(self, macro_names): try: for macro_name in macro_names: self.macro_server.reload_macro(macro_name) - except MacroServerException, mse: + except MacroServerException as mse: Except.throw_exception(mse.type, mse.msg, 'ReloadMacro') return ['OK'] @@ -282,7 +282,7 @@ def ReloadMacroLib(self, lib_names): try: for lib_name in lib_names: self.macro_server.reload_macro_lib(lib_name) - except MacroServerException, mse: + except MacroServerException as mse: Except.throw_exception(mse.type, mse.msg, 'ReloadMacroLib') return ['OK'] @@ -290,7 +290,7 @@ def GetMacroCode(self, argin): """GetMacroCode( [, ]) -> full filename, code, line_nb """ ret = self.macro_server.get_or_create_macro_lib(*argin) - return map(str, ret) + return list(map(str, ret)) def SetMacroCode(self, argin): lib_name, code = argin[:2] diff --git a/src/sardana/tango/macroserver/test/__init__.py b/src/sardana/tango/macroserver/test/__init__.py index 6d9650059e..23f927f75e 100644 --- a/src/sardana/tango/macroserver/test/__init__.py +++ b/src/sardana/tango/macroserver/test/__init__.py @@ -23,5 +23,5 @@ ## ############################################################################## -from macroexecutor import TangoMacroExecutor -from base import * +from .macroexecutor import TangoMacroExecutor # noqa +from .base import * # noqa diff --git a/src/sardana/tango/macroserver/test/base.py b/src/sardana/tango/macroserver/test/base.py index ee8b7cd40c..1ce7fce16c 100644 --- a/src/sardana/tango/macroserver/test/base.py +++ b/src/sardana/tango/macroserver/test/base.py @@ -78,15 +78,15 @@ def setUp(self, properties=None): self._msstarter.addNewDevice(self.door_name, klass='Door') # Add properties if properties: - for key, values in properties.items(): + for key, values in list(properties.items()): db.put_device_property(self.ms_name, {key: values}) # start MS server - self._msstarter.startDs() + self._msstarter.startDs(wait_seconds=20) self.door = PyTango.DeviceProxy(self.door_name) - except Exception, e: + except Exception as e: # force tearDown in order to eliminate the MacroServer - print e + print(e) self.tearDown() def tearDown(self): @@ -110,16 +110,21 @@ def tearDown(self): "ds_exec_name": "MacroServer", "ds_inst_name": ds_inst_name} ms_properties = os.path.normpath(ms_properties) - try: - os.remove(ms_properties) - except Exception, e: - msg = "Not possible to remove macroserver environment file" - print(msg) - print("Details: %s" % e) + extensions = [".bak", ".dat", ".dir"] + for ext in extensions: + name = ms_properties + ext + if not os.path.exists(name): + continue + try: + os.remove(name) + except Exception as e: + msg = "Not possible to remove macroserver environment file" + print(msg) + print(("Details: %s" % e)) if __name__ == '__main__': bms = BaseMacroServerTestCase() bms.setUp() - print bms.door, bms.macroserver + print(bms.door, bms.macroserver) bms.tearDown() diff --git a/src/sardana/tango/macroserver/test/macroexecutor.py b/src/sardana/tango/macroserver/test/macroexecutor.py index 7a1b25625e..9459301336 100644 --- a/src/sardana/tango/macroserver/test/macroexecutor.py +++ b/src/sardana/tango/macroserver/test/macroexecutor.py @@ -90,21 +90,16 @@ def push_event(self, *args, **kwargs): if event_data.err: self._state_buffer = event_data.errors self._tango_macro_executor._done_event.set() - # make sure we get it as string since PyTango 7.1.4 returns a buffer - # object and json.loads doesn't support buffer objects (only str) attr_value = getattr(event_data, 'attr_value') if attr_value is None: return - v = map(str, attr_value.value) + v = attr_value.value if not len(v[1]): return fmt = v[0] codec = CodecFactory().getCodec(fmt) - # make sure we get it as string since PyTango 7.1.4 returns a buffer - # object and json.loads doesn't support buffer objects (only str) - v[1] = str(v[1]) fmt, data = codec.decode(v) for macro_status in data: state = macro_status['state'] diff --git a/src/sardana/tango/pool/CTExpChannel.py b/src/sardana/tango/pool/CTExpChannel.py index 8c193abc58..c9db626ee0 100644 --- a/src/sardana/tango/pool/CTExpChannel.py +++ b/src/sardana/tango/pool/CTExpChannel.py @@ -39,7 +39,8 @@ from sardana import State, SardanaServer from sardana.sardanaattribute import SardanaAttribute -from sardana.tango.core.util import to_tango_type_format, exception_str +from sardana.tango.core.util import to_tango_type_format, to_tango_quality, \ + exception_str from sardana.tango.pool.PoolDevice import PoolTimerableDevice, \ PoolTimerableDeviceClass @@ -137,10 +138,9 @@ def _on_ct_changed(self, event_source, event_type, event_value): if name == "timer" and value is None: value = "None" elif name == "value": - w_value = event_source.get_value_attribute().w_value - state = self.ct.get_state() - if state == State.Moving: - quality = AttrQuality.ATTR_CHANGING + value_attr = event_source.get_value_attribute() + w_value = value_attr.w_value + quality = to_tango_quality(value_attr.quality) self.set_attribute(attr, value=value, w_value=w_value, timestamp=timestamp, quality=quality, diff --git a/src/sardana/tango/pool/Controller.py b/src/sardana/tango/pool/Controller.py index 9b590505c6..d721999234 100644 --- a/src/sardana/tango/pool/Controller.py +++ b/src/sardana/tango/pool/Controller.py @@ -46,7 +46,7 @@ from sardana.sardanaattribute import SardanaAttribute from sardana.tango.core.util import to_tango_attr_info -from PoolDevice import PoolDevice, PoolDeviceClass +from .PoolDevice import PoolDevice, PoolDeviceClass def to_bool(s): @@ -109,7 +109,7 @@ def get_role_ids(self): 'counter_role_ids'] if len(role_ids) == 0: role_ids = self.Role_ids - role_ids = map(int, role_ids) + role_ids = list(map(int, role_ids)) return role_ids @@ -127,14 +127,14 @@ def _get_ctrl_properties(self): props = {} if prop_infos: props.update(db.get_device_property( - self.get_name(), prop_infos.keys())) - for p in props.keys(): + self.get_name(), list(prop_infos.keys()))) + for p in list(props.keys()): if len(props[p]) == 0: props[p] = None ret = {} missing_props = [] - for prop_name, prop_value in props.items(): + for prop_name, prop_value in list(props.items()): if prop_value is None: dv = prop_infos[prop_name].default_value if dv is None: @@ -151,7 +151,7 @@ def _get_ctrl_properties(self): op = float elif dtype == DataType.Boolean: op = to_bool - prop_value = map(op, prop_value) + prop_value = list(map(op, prop_value)) if dformat == DataFormat.Scalar: prop_value = prop_value[0] ret[prop_name] = prop_value @@ -235,7 +235,7 @@ def get_dynamic_attributes(self): return PoolDevice.get_dynamic_attributes(self) self._dynamic_attributes_cache = dyn_attrs = CaselessDict() self._standard_attributes_cache = std_attrs = CaselessDict() - for attr_name, attr_data in info.ctrl_attributes.items(): + for attr_name, attr_data in list(info.ctrl_attributes.items()): name, tg_info = to_tango_attr_info(attr_name, attr_data) dyn_attrs[attr_name] = attr_name, tg_info, attr_data return std_attrs, dyn_attrs diff --git a/src/sardana/tango/pool/IORegister.py b/src/sardana/tango/pool/IORegister.py index 7e9c2d6939..b6f4bd2378 100644 --- a/src/sardana/tango/pool/IORegister.py +++ b/src/sardana/tango/pool/IORegister.py @@ -202,10 +202,10 @@ def initialize_dynamic_attributes(self): non_detect_evts = () for attr_name in detect_evts: - if attrs.has_key(attr_name): + if attr_name in attrs: self.set_change_event(attr_name, True, True) for attr_name in non_detect_evts: - if attrs.has_key(attr_name): + if attr_name in attrs: self.set_change_event(attr_name, True, False) return diff --git a/src/sardana/tango/pool/MeasurementGroup.py b/src/sardana/tango/pool/MeasurementGroup.py index 78018368d9..0225d826ef 100644 --- a/src/sardana/tango/pool/MeasurementGroup.py +++ b/src/sardana/tango/pool/MeasurementGroup.py @@ -33,7 +33,7 @@ import time from PyTango import Except, DevVoid, DevLong, DevDouble, DevString, \ - DispLevel, DevState, AttrQuality, READ, READ_WRITE, SCALAR + DispLevel, DevState, AttrQuality, READ, READ_WRITE, SCALAR, Util from taurus.core.util.codecs import CodecFactory from taurus.core.util.log import DebugIt @@ -73,7 +73,7 @@ def delete_device(self): def init_device(self): PoolGroupDevice.init_device(self) # state and status are already set by the super class - detect_evts = "moveable", "synchronization", \ + detect_evts = "moveable", "synchdescription", \ "softwaresynchronizerinitialdomain" # TODO: nbstarts could be moved to detect events with # abs_change criteria of 1, but be careful with @@ -142,7 +142,7 @@ def _on_measurement_group_changed(self, event_source, event_type, cfg = self.measurement_group.get_user_configuration() codec = CodecFactory().getCodec('json') _, event_value = codec.encode(('', cfg)) - elif name == "synchronization": + elif name == "synchdescription": codec = CodecFactory().getCodec('json') _, event_value = codec.encode(('', event_value)) elif name == "moveable" and event_value is None: @@ -158,23 +158,36 @@ def _on_measurement_group_changed(self, event_source, event_type, quality=quality, priority=priority, error=error, synch=False) - def _synchronization_str2enum(self, synchronization): - '''Translates synchronization data structure so it uses SynchDomain - enums as keys instead of strings. - ''' - for group in synchronization: - for param, conf in group.iteritems(): - group.pop(param) - param = SynchParam.fromStr(param) + def _synch_description_str2enum(self, _synch_description_str): + """Translates synchronization description data structure so it uses + SynchParam and SynchDomain enums as keys instead of strings. + + .. todo:: At some point remove the backwards compatibility + for memorized values created with Python 2. In Python 2 IntEnum was + serialized to "." e.g. "SynchDomain.Time" and we were + using a class method `fromStr` to interpret the enumeration objects. + """ + synch_description = [] + for group_str in _synch_description_str: + group = {} + for param_str, conf_str in group_str.items(): + try: + param = SynchParam(int(param_str)) + except ValueError: + param = SynchParam.fromStr(param_str) + if isinstance(conf_str, dict): + conf = {} + for domain_str, value in conf_str.items(): + try: + domain = SynchDomain(int(domain_str)) + except ValueError: + domain = SynchDomain.fromStr(domain_str) + conf[domain] = value + else: + conf = conf_str group[param] = conf - # skip repeats cause its value is just a long number - if param == SynchParam.Repeats: - continue - for domain, value in conf.iteritems(): - conf.pop(domain) - domain = SynchDomain.fromStr(domain) - conf[domain] = value - return synchronization + synch_description.append(group) + return synch_description def always_executed_hook(self): pass @@ -212,7 +225,7 @@ def write_AcquisitionMode(self, attr): acq_mode = AcqMode.lookup[acq_mode_str] except KeyError: raise Exception("Invalid acquisition mode. Must be one of " + - ", ".join(AcqMode.keys())) + ", ".join(list(AcqMode.keys()))) self.measurement_group.acquisition_mode = acq_mode def read_Configuration(self, attr): @@ -223,7 +236,12 @@ def read_Configuration(self, attr): def write_Configuration(self, attr): data = attr.get_write_value() - cfg = CodecFactory().decode(('json', data), ensure_ascii=True) + cfg = CodecFactory().decode(('json', data)) + util = Util.instance() + if util.is_svr_starting(): + self.measurement_group._config._value_ref_compat = True + else: + self.measurement_group._config._value_ref_compat = False self.measurement_group.set_configuration_from_user(cfg) def read_NbStarts(self, attr): @@ -247,19 +265,19 @@ def write_Moveable(self, attr): moveable = None self.measurement_group.moveable = moveable - def read_Synchronization(self, attr): - synchronization = self.measurement_group.synchronization + def read_SynchDescription(self, attr): + synch_description = self.measurement_group.synch_description codec = CodecFactory().getCodec('json') - data = codec.encode(('', synchronization)) + data = codec.encode(('', synch_description)) attr.set_value(data[1]) - def write_Synchronization(self, attr): + def write_SynchDescription(self, attr): data = attr.get_write_value() - synchronization = CodecFactory().decode(('json', data), - ensure_ascii=True) + synch_description = CodecFactory().decode(('json', data)) # translate dictionary keys - synchronization = self._synchronization_str2enum(synchronization) - self.measurement_group.synchronization = synchronization + synch_description = \ + self._synch_description_str2enum(synch_description) + self.measurement_group.synch_description = synch_description def read_LatencyTime(self, attr): latency_time = self.measurement_group.latency_time @@ -291,13 +309,6 @@ def Start(self): def Stop(self): self.measurement_group.stop() - def StartMultiple(self, n): - try: - self.wait_for_operation() - except: - raise Exception("Cannot acquire: already involved in an operation") - self.measurement_group.start_acquisition(multiple=n) - class MeasurementGroupClass(PoolGroupDeviceClass): @@ -313,8 +324,7 @@ class MeasurementGroupClass(PoolGroupDeviceClass): # Command definitions cmd_list = { 'Prepare': [[DevVoid, ""], [DevVoid, ""]], - 'Start': [[DevVoid, ""], [DevVoid, ""]], - 'StartMultiple': [[DevLong, ""], [DevVoid, ""]], + 'Start': [[DevVoid, ""], [DevVoid, ""]] } cmd_list.update(PoolGroupDeviceClass.cmd_list) @@ -338,9 +348,10 @@ class MeasurementGroupClass(PoolGroupDeviceClass): 'Moveable': [[DevString, SCALAR, READ_WRITE], {'Memorized': "true", 'Display level': DispLevel.EXPERT}], - 'Synchronization': [[DevString, SCALAR, READ_WRITE], - {'Memorized': "true", - 'Display level': DispLevel.EXPERT}], + # TODO: Does it have sense to memorize SynchDescription? + 'SynchDescription': [[DevString, SCALAR, READ_WRITE], + {'Memorized': "true", + 'Display level': DispLevel.EXPERT}], 'LatencyTime': [[DevDouble, SCALAR, READ], {'Display level': DispLevel.EXPERT}], 'SoftwareSynchronizerInitialDomain': [[DevString, SCALAR, READ_WRITE], diff --git a/src/sardana/tango/pool/Motor.py b/src/sardana/tango/pool/Motor.py index 13aa82ba8d..1bb33f93ba 100644 --- a/src/sardana/tango/pool/Motor.py +++ b/src/sardana/tango/pool/Motor.py @@ -349,7 +349,7 @@ def init_device(self): pass if self.Sleep_bef_last_read > 0: - motor.set_instability_time(self.Sleep_bef_last_read / 1000.0) + motor.set_instability_time(self.Sleep_bef_last_read / 1000) motor.add_listener(self.on_motor_changed) self.set_state(DevState.ON) @@ -475,7 +475,7 @@ def write_Position(self, attr): raise Exception("Cannot move: already in motion") try: self.motor.position = position - except PoolException, pe: + except PoolException as pe: throw_sardana_exception(pe) # manually store write dial position in the database diff --git a/src/sardana/tango/pool/MotorGroup.py b/src/sardana/tango/pool/MotorGroup.py index d5570f2782..fdf63a6c66 100644 --- a/src/sardana/tango/pool/MotorGroup.py +++ b/src/sardana/tango/pool/MotorGroup.py @@ -77,7 +77,7 @@ def init_device(self): detect_evts = "position", non_detect_evts = "elementlist", self.set_change_events(detect_evts, non_detect_evts) - self.Elements = map(int, self.Elements) + self.Elements = list(map(int, self.Elements)) motor_group = self.motor_group if motor_group is None: full_name = self.get_full_name() @@ -190,7 +190,7 @@ def write_Position(self, attr): raise Exception("Cannot move: already in motion") try: self.motor_group.position = position - except PoolException, pe: + except PoolException as pe: throw_sardana_exception(pe) finally: self.in_write_position = False diff --git a/src/sardana/tango/pool/Pool.py b/src/sardana/tango/pool/Pool.py index 3fae33c04f..e073abf4f4 100644 --- a/src/sardana/tango/pool/Pool.py +++ b/src/sardana/tango/pool/Pool.py @@ -45,6 +45,7 @@ from sardana.pool.pool import Pool as POOL from sardana.pool.poolmetacontroller import TYPE_MAP_OBJ from sardana.tango.core.util import get_tango_version_number +import collections class Pool(PyTango.Device_4Impl, Logger): @@ -116,10 +117,10 @@ def init_device(self): p = self.pool p.set_python_path(self.PythonPath) p.set_path(self.PoolPath) - p.set_motion_loop_sleep_time(self.MotionLoop_SleepTime / 1000.0) + p.set_motion_loop_sleep_time(self.MotionLoop_SleepTime / 1000) p.set_motion_loop_states_per_position( self.MotionLoop_StatesPerPosition) - p.set_acq_loop_sleep_time(self.AcqLoop_SleepTime / 1000.0) + p.set_acq_loop_sleep_time(self.AcqLoop_SleepTime / 1000) p.set_acq_loop_states_per_value(self.AcqLoop_StatesPerValue) p.set_drift_correction(self.DriftCorrection) if self.RemoteLog is None: @@ -249,7 +250,7 @@ def getElements(self, cache=True): if cache and value is not None: return value value = dict(new=self.pool.get_elements_info()) - value = CodecFactory().encode('json', ('', value)) + value = CodecFactory().encode('utf8_json', ('', value)) self.ElementsCache = value return value @@ -339,7 +340,7 @@ def CreateController(self, argin): "interface" % (class_name, type_str)) # check that necessary property values are set - for prop_name, prop_info in ctrl_class.ctrl_properties.items(): + for prop_name, prop_info in list(ctrl_class.ctrl_properties.items()): prop_value = properties.get(prop_name) if prop_value is None: if prop_info.default_value is None: @@ -383,7 +384,7 @@ def CreateController(self, argin): info = dict(id=pm_id, name=pm_name, ctrl_name=name, axis=i + 1, type='PseudoMotor', elements=motor_ids) if pm_name.count(',') > 0: - n, fn = map(str.strip, pm_name.split(',', 1)) + n, fn = list(map(str.strip, pm_name.split(',', 1))) info['name'], info['full_name'] = n, fn pseudo_motor_infos[klass_pseudo_role] = info pseudo_motor_ids.append(pm_id) @@ -423,7 +424,7 @@ def CreateController(self, argin): info = dict(id=pc_id, name=pc_name, ctrl_name=name, axis=i + 1, type='PseudoCounter', elements=counter_ids) if pc_name.count(',') > 0: - n, fn = map(str.strip, pc_name.split(',', 1)) + n, fn = list(map(str.strip, pc_name.split(',', 1))) info['name'], info['full_name'] = n, fn pseudo_counter_infos[klass_pseudo_role] = info pseudo_counter_ids.append(pc_id) @@ -450,7 +451,7 @@ def create_controller_cb(device_name): # Determine which controller writtable attributes have default value # and apply them to the newly created controller attrs = [] - for attr_name, attr_info in ctrl_class.ctrl_attributes.items(): + for attr_name, attr_info in list(ctrl_class.ctrl_attributes.items()): default_value = attr_info.default_value if default_value is None: continue @@ -466,10 +467,10 @@ def create_controller_cb(device_name): # for pseudo motor/counter controller also create pseudo # motor(s)/counter(s) automatically if elem_type == ElementType.PseudoMotor: - for pseudo_motor_info in pseudo_motor_infos.values(): + for pseudo_motor_info in list(pseudo_motor_infos.values()): self._create_single_element(pseudo_motor_info) elif elem_type == ElementType.PseudoCounter: - for pseudo_counter_info in pseudo_counter_infos.values(): + for pseudo_counter_info in list(pseudo_counter_infos.values()): self._create_single_element(pseudo_counter_info) #@DebugIt() @@ -621,7 +622,8 @@ def create_element_cb(device_name): # them to the newly created element ctrl_class_info = ctrl.get_ctrl_info() attrs = [] - for attr_name, attr_info in ctrl_class_info.getAxisAttributes().items(): + for attr_name, attr_info in \ + list(ctrl_class_info.getAxisAttributes().items()): default_value = attr_info.default_value if default_value is None: continue @@ -762,7 +764,7 @@ def on_pool_changed(self, evt_src, evt_type, evt_value): key = 'change' json_elem = elem.serialize(pool=self.pool.full_name) value[key] = json_elem, - value = CodecFactory().getCodec('json').encode(('', value)) + value = CodecFactory().getCodec('utf8_json').encode(('', value)) self.push_change_event('Elements', *value) elif evt_name == "elementschanged": # force the element list cache to be rebuild next time someone reads @@ -781,16 +783,16 @@ def on_pool_changed(self, evt_src, evt_type, evt_value): deleted_values.append(json_elem) value = {"new": new_values, "change": changed_values, "del": deleted_values} - value = CodecFactory().getCodec('json').encode(('', value)) + value = CodecFactory().getCodec('utf8_json').encode(('', value)) self.push_change_event('Elements', *value) def _format_create_json_arguments(self, argin): elems, ret = json.loads(argin[0]), [] - if operator.isMappingType(elems): + if isinstance(elems, collections.Mapping): elems = [elems] for elem in elems: d = {} - for k, v in elem.items(): + for k, v in list(elem.items()): d[str(k)] = str(v) ret.append(d) return ret @@ -814,15 +816,15 @@ def _format_CreateController_arguments(self, argin): raise Exception(msg) if len(argin) == 1: ret = self._format_create_json_arguments(argin) - if not ret.has_key('type'): + if 'type' not in ret: raise KeyError("Missing key 'type'") - if not ret.has_key('library'): + if 'library' not in ret: raise KeyError("Missing key 'library'") - if not ret.has_key('klass'): + if 'klass' not in ret: raise KeyError("Missing key 'klass'") - if not ret.has_key('name'): + if 'name' not in ret: raise KeyError("Missing key 'name'") - if not ret.has_key('properties'): + if 'properties' not in ret: ret['properties'] = CaselessDict() return ret @@ -861,11 +863,11 @@ def _format_CreateMotorGroup_arguments(self, argin): elems = json.loads(argin[0]) except: elems = argin - if operator.isMappingType(elems): + if isinstance(elems, collections.Mapping): elems = [elems] for elem in elems: d = {} - for k, v in elem.items(): + for k, v in list(elem.items()): d[str(k)] = str(v) ret.append(d) return ret @@ -886,11 +888,11 @@ def _format_CreateMeasurementGroup_arguments(self, argin): elems = json.loads(argin[0]) except: elems = argin - if operator.isMappingType(elems): + if isinstance(elems, collections.Mapping): elems = [elems] for elem in elems: d = {} - for k, v in elem.items(): + for k, v in list(elem.items()): d[str(k)] = str(v) ret.append(d) return ret @@ -1128,7 +1130,7 @@ def SetControllerCode(self, argin): {1} """.format(CREATE_MOTOR_GROUP_PAR_IN_DOC, CREATE_MOTOR_GROUP_PAR_OUT_DOC) -Pool.CreateMotorGroup.__func__.__doc__ = CREATE_MOTOR_GROUP_DOC +Pool.CreateMotorGroup.__doc__ = CREATE_MOTOR_GROUP_DOC CREATE_MEASUREMENT_GROUP_PAR_IN_DOC = """\ Must give either: @@ -1299,18 +1301,18 @@ def SetControllerCode(self, argin): {1} """.format(SEND_TO_CONTROLLER_PAR_IN_DOC, SEND_TO_CONTROLLER_PAR_OUT_DOC) -Pool.CreateController.__func__.__doc__ = CREATE_CONTROLLER_DOC -Pool.CreateElement.__func__.__doc__ = CREATE_ELEMENT_DOC -Pool.CreateInstrument.__func__.__doc__ = CREATE_INSTRUMENT_DOC -Pool.CreateMotorGroup.__func__.__doc__ = CREATE_MOTOR_GROUP_DOC -Pool.CreateMeasurementGroup.__func__.__doc__ = CREATE_MEASUREMENT_GROUP_DOC -Pool.DeleteElement.__func__.__doc__ = DELETE_ELEMENT_DOC -Pool.GetControllerClassInfo.__func__.__doc__ = GET_CONTROLLER_CLASS_INFO_DOC -Pool.ReloadControllerLib.__func__.__doc__ = RELOAD_CONTROLLER_LIB_INFO_DOC -Pool.ReloadControllerClass.__func__.__doc__ = RELOAD_CONTROLLER_CLASS_INFO_DOC -Pool.RenameElement.__func__.__doc__ = RENAME_ELEMENT_CLASS_INFO_DOC -Pool.Stop.__func__.__doc__ = STOP_DOC -Pool.Abort.__func__.__doc__ = ABORT_DOC +Pool.CreateController.__doc__ = CREATE_CONTROLLER_DOC +Pool.CreateElement.__doc__ = CREATE_ELEMENT_DOC +Pool.CreateInstrument.__doc__ = CREATE_INSTRUMENT_DOC +Pool.CreateMotorGroup.__doc__ = CREATE_MOTOR_GROUP_DOC +Pool.CreateMeasurementGroup.__doc__ = CREATE_MEASUREMENT_GROUP_DOC +Pool.DeleteElement.__doc__ = DELETE_ELEMENT_DOC +Pool.GetControllerClassInfo.__doc__ = GET_CONTROLLER_CLASS_INFO_DOC +Pool.ReloadControllerLib.__doc__ = RELOAD_CONTROLLER_LIB_INFO_DOC +Pool.ReloadControllerClass.__doc__ = RELOAD_CONTROLLER_CLASS_INFO_DOC +Pool.RenameElement.__doc__ = RENAME_ELEMENT_CLASS_INFO_DOC +Pool.Stop.__doc__ = STOP_DOC +Pool.Abort.__doc__ = ABORT_DOC class PoolClass(PyTango.DeviceClass): diff --git a/src/sardana/tango/pool/PoolDevice.py b/src/sardana/tango/pool/PoolDevice.py index 2946605ec2..ca4ce9b944 100644 --- a/src/sardana/tango/pool/PoolDevice.py +++ b/src/sardana/tango/pool/PoolDevice.py @@ -194,7 +194,7 @@ def initialize_dynamic_attributes(self): read = self.__class__._read_DynamicAttribute write = self.__class__._write_DynamicAttribute is_allowed = self.__class__._is_DynamicAttribute_allowed - for attr_name, data_info in std_attrs.items(): + for attr_name, data_info in list(std_attrs.items()): attr_name, data_info, attr_info = data_info attr = self.add_standard_attribute(attr_name, data_info, attr_info, read, @@ -205,7 +205,7 @@ def initialize_dynamic_attributes(self): read = self.__class__._read_DynamicAttribute write = self.__class__._write_DynamicAttribute is_allowed = self.__class__._is_DynamicAttribute_allowed - for attr_name, data_info in dyn_attrs.items(): + for attr_name, data_info in list(dyn_attrs.items()): attr_name, data_info, attr_info = data_info attr = self.add_dynamic_attribute(attr_name, data_info, attr_info, read, @@ -219,7 +219,8 @@ def remove_unwanted_dynamic_attributes(self, new_std_attrs, new_dyn_attrs): dev_class = self.get_device_class() multi_attr = self.get_device_attr() multi_class_attr = dev_class.get_class_attr() - static_attr_names = map(str.lower, dev_class.attr_list.keys()) + static_attr_names = \ + list(map(str.lower, list(dev_class.attr_list.keys()))) static_attr_names.extend(('state', 'status')) new_attrs = CaselessDict(new_std_attrs) @@ -430,7 +431,7 @@ def dev_status(self): ctrl_status = self.element.get_status(cache=use_cache, propagate=0) status = self.calculate_tango_status(ctrl_status) return status - except Exception, e: + except Exception as e: msg = "Exception trying to return status: %s" % str(e) self.error(msg) self.debug("Details:", exc_info=1) @@ -592,6 +593,21 @@ def init_device(self): except ValueError: pass + def delete_device(self): + element = self.element + if not element.deleted: + pool_ctrl = self.ctrl + ctrl = pool_ctrl.ctrl + if ctrl is not None: + axis = element.axis + try: + ctrl.DeleteDevice(axis) + except Exception: + name = element.name + self.error("Unable to delete %s(%s)", name, axis, + exc_info=1) + PoolDevice.delete_device(self) + def read_Instrument(self, attr): """Read the value of the ``Instrument`` tango attribute. Returns the instrument full name or empty string if this element doesn't @@ -670,7 +686,7 @@ def _get_dynamic_attributes(self): std_attrs_lower = [attr.lower() for attr in dev_class.standard_attr_list] - for attr_name, attr_info in axis_attrs.items(): + for attr_name, attr_info in list(axis_attrs.items()): attr_name_lower = attr_name.lower() if attr_name_lower in std_attrs_lower: data_info = DataInfo.toDataInfo(attr_name, attr_info) @@ -855,7 +871,7 @@ def _encode_value_chunk(self, value_chunk): :rtype: str""" index = [] value = [] - for idx, sdn_value in value_chunk.iteritems(): + for idx, sdn_value in value_chunk.items(): index.append(idx) value.append(sdn_value.value) data = dict(index=index, value=value) @@ -873,7 +889,7 @@ def _encode_value_ref_chunk(self, value_ref_chunk): """ index = [] value_ref = [] - for idx, sdn_value in value_ref_chunk.iteritems(): + for idx, sdn_value in value_ref_chunk.items(): index.append(idx) value_ref.append(sdn_value.value) data = dict(index=index, value_ref=value_ref) diff --git a/src/sardana/tango/pool/PseudoCounter.py b/src/sardana/tango/pool/PseudoCounter.py index 66720e3d58..f1c422295a 100644 --- a/src/sardana/tango/pool/PseudoCounter.py +++ b/src/sardana/tango/pool/PseudoCounter.py @@ -76,7 +76,7 @@ def delete_device(self): def init_device(self): PoolExpChannelDevice.init_device(self) - self.Elements = map(int, self.Elements) + self.Elements = list(map(int, self.Elements)) pseudo_counter = self.pseudo_counter if pseudo_counter is None: full_name = self.get_full_name() diff --git a/src/sardana/tango/pool/PseudoMotor.py b/src/sardana/tango/pool/PseudoMotor.py index e3363e0526..562b36b162 100644 --- a/src/sardana/tango/pool/PseudoMotor.py +++ b/src/sardana/tango/pool/PseudoMotor.py @@ -78,7 +78,7 @@ def delete_device(self): def init_device(self): PoolElementDevice.init_device(self) - self.Elements = map(int, self.Elements) + self.Elements = list(map(int, self.Elements)) pseudo_motor = self.pseudo_motor if self.pseudo_motor is None: full_name = self.get_full_name() diff --git a/src/sardana/tango/pool/test/__init__.py b/src/sardana/tango/pool/test/__init__.py index 25b3ca6527..a8b698a6c7 100644 --- a/src/sardana/tango/pool/test/__init__.py +++ b/src/sardana/tango/pool/test/__init__.py @@ -23,9 +23,9 @@ ## ############################################################################## -from base import (BasePoolTestCase, ControllerLoadsTestCase, - ControllerCreationTestCase, ElementCreationTestCase) -from base_sartest import SarTestTestCase -from .test_measurementgroup import * -from .test_Motor import * -from .test_persistence import * +from .base import (BasePoolTestCase, ControllerLoadsTestCase, # noqa + ControllerCreationTestCase, ElementCreationTestCase) # noqa +from .base_sartest import SarTestTestCase # noqa +from .test_measurementgroup import * # noqa +from .test_Motor import * # noqa +from .test_persistence import * # noqa diff --git a/src/sardana/tango/pool/test/base.py b/src/sardana/tango/pool/test/base.py index c043ac70ed..a6708761dc 100644 --- a/src/sardana/tango/pool/test/base.py +++ b/src/sardana/tango/pool/test/base.py @@ -25,17 +25,17 @@ """Base classes for the controller tests""" -__all__ = ['BasePoolTestCase', 'ControllerLoadsTestCase', - 'ControllerCreationTestCase', 'ElementCreationTestCase'] - import PyTango -from taurus.external import unittest +import unittest from taurus.core.tango.starter import ProcessStarter from sardana import sardanacustomsettings from sardana.tango.core.util import (get_free_server, get_free_device, get_free_alias) from taurus.core.util import whichexecutable +__all__ = ['BasePoolTestCase', 'ControllerLoadsTestCase', + 'ControllerCreationTestCase', 'ElementCreationTestCase'] + class BasePoolTestCase(object): """Abstract class for pool DS testing. @@ -62,11 +62,11 @@ def setUp(self, properties=None): self._starter.addNewDevice(self.pool_name, klass='Pool') # Add properties if properties is not None: - for key, values in properties.items(): + for key, values in list(properties.items()): db.put_device_property(self.pool_name, {key: values}) # start Pool server - self._starter.startDs() + self._starter.startDs(wait_seconds=20) # register extensions so the test methods can use them self.pool = PyTango.DeviceProxy(self.pool_name) @@ -89,12 +89,14 @@ class ControllerLoadsTestCase(BasePoolTestCase): def test_controller_loads(self): """Test that the controller library and class can be loaded. """ - libraries = self.pool.getElementsOfType('ControllerLibrary').values() + libraries = \ + list(self.pool.getElementsOfType('ControllerLibrary').values()) libraries_names = [lib.getName() for lib in libraries] - classes = self.pool.getElementsOfType('ControllerClass').values() + classes = \ + list(self.pool.getElementsOfType('ControllerClass').values()) classes_names = [cls.getName() for cls in classes] - for test_lib, test_classes in self.controller_classes.items(): + for test_lib, test_classes in list(self.controller_classes.items()): msg = 'ControllerLibrary %s was not correctly loaded.' % test_lib self.assertIn(test_lib, libraries_names, msg) msg = 'ControllerClass %s was not correctly loaded.' diff --git a/src/sardana/tango/pool/test/base_sartest.py b/src/sardana/tango/pool/test/base_sartest.py index d4ee463536..cbd7901501 100644 --- a/src/sardana/tango/pool/test/base_sartest.py +++ b/src/sardana/tango/pool/test/base_sartest.py @@ -37,7 +37,7 @@ def _cleanup_device(dev_name): device = taurus.Device(dev_name) # tango_alias_devs contains any names in which we have referred # to the device, could be alias, short name, etc. pop all of them - for k, v in factory.tango_alias_devs.items(): + for k, v in list(factory.tango_alias_devs.items()): if v is device: factory.tango_alias_devs.pop(k) full_name = device.getFullName() @@ -112,8 +112,8 @@ def setUp(self, pool_properties=None): ctrl = PyTango.DeviceProxy(ctrl_name) # use the first trigger/gate element by default ctrl.write_attribute("Synchronizer", "_test_tg_1_1") - except Exception, e: - print e + except Exception as e: + print(e) msg = 'Impossible to create ctrl: "%s"' % (ctrl_name) raise Exception('Aborting SartestTestCase: %s' % (msg)) self.ctrl_list.append(ctrl_name) @@ -123,8 +123,8 @@ def setUp(self, pool_properties=None): try: self.pool.createElement( [sar_type, ctrl_name, str(axis), elem_name]) - except Exception, e: - print e + except Exception as e: + print(e) msg = 'Impossible to create element: "%s"' % ( elem_name) raise Exception('Aborting SartestTestCase: %s' % (msg)) @@ -139,8 +139,8 @@ def setUp(self, pool_properties=None): argin.extend(roles) try: self.pool.CreateController(argin) - except Exception, e: - print e + except Exception as e: + print(e) msg = 'Impossible to create ctrl: "%s"' % (ctrl_name) raise Exception('Aborting SartestTestCase: %s' % (msg)) self.ctrl_list.append(ctrl_name) @@ -148,10 +148,10 @@ def setUp(self, pool_properties=None): elem = role.split("=")[1] if elem not in self.elem_list: self.elem_list.append(elem) - except Exception, e: + except Exception as e: # force tearDown in order to eliminate the Pool BasePoolTestCase.tearDown(self) - print e + print(e) def tearDown(self): """Remove the elements and the controllers @@ -169,7 +169,8 @@ def tearDown(self): _cleanup_device(elem_name) try: self.pool.DeleteElement(elem_name) - except: + except Exception as e: + print(e) dirty_elems.append(elem_name) for ctrl_name in self.ctrl_list: diff --git a/src/sardana/tango/pool/test/test_Motor.py b/src/sardana/tango/pool/test/test_Motor.py index f0578923a4..52f1d8f683 100644 --- a/src/sardana/tango/pool/test/test_Motor.py +++ b/src/sardana/tango/pool/test/test_Motor.py @@ -25,7 +25,7 @@ """Tests Read Position from Sardana using PyTango""" import PyTango -from taurus.external import unittest +import unittest from sardana.tango.pool.test import BasePoolTestCase from sardana.tango.core.util import get_free_alias import numbers diff --git a/src/sardana/tango/pool/test/test_measurementgroup.py b/src/sardana/tango/pool/test/test_measurementgroup.py index 7dca88966c..042db14541 100644 --- a/src/sardana/tango/pool/test/test_measurementgroup.py +++ b/src/sardana/tango/pool/test/test_measurementgroup.py @@ -32,7 +32,7 @@ # TODO: decide what to use: taurus or PyTango import PyTango from PyTango import DeviceProxy -from taurus.external import unittest +import unittest from taurus.test import insertTest from taurus.core.util import CodecFactory @@ -100,8 +100,8 @@ def event_received(self, *args, **kwargs): pad = [None] * (idx[0] - expected_idx) channel_data.extend(pad + value) self.data[obj_fullname] = channel_data - except Exception, e: - print e + except Exception as e: + print(e) raise Exception('"data" event callback failed') @@ -121,18 +121,18 @@ def create_meas(self, config): self.expchan_names = [] self.tg_names = [] ordered_chns = [None] * 10 - for ctrl_name, ctrl_config in config.items(): + for ctrl_name, ctrl_config in list(config.items()): channels = ctrl_config["channels"] - for chn, chn_config in channels.items(): + for chn, chn_config in list(channels.items()): index = chn_config["index"] ordered_chns[index] = chn self.expchan_names = [chn for chn in ordered_chns if chn is not None] self.pool.CreateMeasurementGroup([self.mg_name] + self.expchan_names) - for ctrl_name in config.keys(): + for ctrl_name in list(config.keys()): ctrl_config = config.pop(ctrl_name) channels = ctrl_config["channels"] - for chn_name in channels.keys(): + for chn_name in list(channels.keys()): chn_config = channels.pop(chn_name) chn_full_name = _get_full_name(DeviceProxy(chn_name)) channels[chn_full_name] = chn_config @@ -160,7 +160,7 @@ def create_meas(self, config): ctrl_test_config = config[ctrl] channels = ctrl_config['channels'] channels_test = ctrl_test_config.pop("channels") - for chn, chn_config in channels.items(): + for chn, chn_config in list(channels.items()): chn_test_config = channels_test[chn] chn_config.update(chn_test_config) ctrl_config.update(ctrl_test_config) @@ -171,16 +171,16 @@ def create_meas(self, config): def prepare_meas(self, params): """ Set the measurement group parameters """ - synchronization = params["synchronization"] + synch_description = params["synch_description"] codec = CodecFactory().getCodec('json') - data = codec.encode(('', synchronization)) - self.meas.write_attribute('synchronization', data[1]) + data = codec.encode(('', synch_description)) + self.meas.write_attribute('SynchDescription', data[1]) def _add_attribute_listener(self, config): self.attr_listener = TangoAttributeListener() chn_names = [] - for ctrl_config in config.values(): - for chn, chn_config in ctrl_config["channels"].items(): + for ctrl_config in list(config.values()): + for chn, chn_config in list(ctrl_config["channels"].items()): if chn_config.get("value_ref_enabled", False): buffer_attr = "ValueRefBuffer" else: @@ -220,31 +220,49 @@ def meas_cont_acquisition(self, params, config): self.prepare_meas(params) chn_names = self._add_attribute_listener(config) # Do acquisition + self.meas.write_attribute("NbStarts", 1) + self.meas.Prepare() self.meas.Start() while self.meas.State() == PyTango.DevState.MOVING: - print "Acquiring..." + print("Acquiring...") time.sleep(0.1) time.sleep(1) - repetitions = params['synchronization'][0][SynchParam.Repeats] + repetitions = params['synch_description'][0][SynchParam.Repeats] self._acq_asserts(chn_names, repetitions) + def push_event(self, event): + value = event.attr_value.value + self.meas_state = value + if value == PyTango.DevState.MOVING: + self.meas_started = True + elif self.meas_started and value == PyTango.DevState.ON: + self.meas_finished.set() + def stop_meas_cont_acquisition(self, params, config): '''Helper method to do measurement and stop it''' self.create_meas(config) self.prepare_meas(params) + self.meas_state = None + self.meas_started = False + self.meas_finished = threading.Event() chn_names = self._add_attribute_listener(config) # Do measurement - self.meas.Start() - # starting timer (0.2 s) which will stop the measurement group - threading.Timer(0.2, self.stopMeas).start() - while self.meas.State() == PyTango.DevState.MOVING: - print "Acquiring..." - time.sleep(0.1) - state = self.meas.State() + id_ = self.meas.subscribe_event("State", + PyTango.EventType.CHANGE_EVENT, + self.push_event) + try: + # starting timer (0.2 s) which will stop the measurement group + self.meas.write_attribute("NbStarts", 1) + self.meas.Prepare() + self.meas.Start() + threading.Timer(0.2, self.stopMeas).start() + self.assertTrue(self.meas_finished.wait(5), "mg has not stopped") + finally: + self.meas.unsubscribe_event(id_) desired_state = PyTango.DevState.ON msg = 'mg state after stop is %s (should be %s)' %\ - (state, desired_state) - self.assertEqual(state, desired_state, msg) + (self.meas_state, desired_state) + self.assertEqual(self.meas_state, desired_state, msg) for name in chn_names: channel = PyTango.DeviceProxy(name) state = channel.state() @@ -257,25 +275,37 @@ def stopMeas(self): self.meas.stop() def tearDown(self): - for channel, event_id in self.event_ids.items(): + for channel, event_id in list(self.event_ids.items()): channel.unsubscribe_event(event_id) try: # Delete the meas self.pool.DeleteElement(self.mg_name) - except Exception, e: - print('Impossible to delete MeasurementGroup: %s' % (self.mg_name)) - print e + except Exception as e: + print('Impossible to delete MeasurementGroup: %s' % + self.mg_name) + print(e) SarTestTestCase.tearDown(self) -synchronization1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, +synch_description1 = [{SynchParam.Delay: {SynchDomain.Time: 0}, SynchParam.Active: {SynchDomain.Time: .01}, SynchParam.Total: {SynchDomain.Time: .02}, SynchParam.Repeats: 10}] params_1 = { - "synchronization": synchronization1, - "integ_time": 0.01, + "synch_description": synch_description1, + "integ_time": 0.1, + "name": '_exp_01' +} + +synch_description2 = [{SynchParam.Delay: {SynchDomain.Time: 0}, + SynchParam.Active: {SynchDomain.Time: 0.1}, + SynchParam.Total: {SynchDomain.Time: 0.15}, + SynchParam.Repeats: 10}] + +params_2 = { + "synch_description": synch_description2, + "integ_time": 0.1, "name": '_exp_01' } doc_1 = 'Synchronized acquisition with two channels from the same controller'\ @@ -360,11 +390,11 @@ def tearDown(self): @insertTest(helper_name='meas_cont_acquisition', test_method_doc=doc_3, params=params_1, config=config_3) @insertTest(helper_name='stop_meas_cont_acquisition', test_method_doc=doc_4, - params=params_1, config=config_1) + params=params_2, config=config_1) @insertTest(helper_name='stop_meas_cont_acquisition', test_method_doc=doc_5, - params=params_1, config=config_2) + params=params_2, config=config_2) @insertTest(helper_name='stop_meas_cont_acquisition', test_method_doc=doc_6, - params=params_1, config=config_3) + params=params_2, config=config_3) class TangoAcquisitionTestCase(MeasSarTestTestCase, unittest.TestCase): """Integration test of TGGeneration and Acquisition actions.""" diff --git a/src/sardana/tango/pool/test/test_persistence.py b/src/sardana/tango/pool/test/test_persistence.py index 81bbbffadf..cac52dc997 100644 --- a/src/sardana/tango/pool/test/test_persistence.py +++ b/src/sardana/tango/pool/test/test_persistence.py @@ -24,7 +24,7 @@ ############################################################################## import PyTango -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.tango.pool.test import BasePoolTestCase from sardana.tango.core.util import get_free_alias @@ -68,7 +68,7 @@ def check_elems_presistence(self, info): self.elem_name]) # Restart Pool self._starter.stopDs(hard_kill=True) - self._starter.startDs() + self._starter.startDs(wait_seconds=20) # Check if the element exists try: obj = PyTango.DeviceProxy(self.elem_name) diff --git a/src/sardana/taurus/core/tango/sardana/__init__.py b/src/sardana/taurus/core/tango/sardana/__init__.py index b984503c11..94f0cb943b 100644 --- a/src/sardana/taurus/core/tango/sardana/__init__.py +++ b/src/sardana/taurus/core/tango/sardana/__init__.py @@ -23,7 +23,31 @@ ## ############################################################################## -"""The sardana package. It contains specific part of sardana""" +"""Taurus extensions for Sardana devices. + +Objects obtained with :func:`taurus.Device` expose standard interfaces +e.g., allow to interact with their attributes, check their state, etc. +This module defines classes for enriched interaction with Sardana devices +(also for other elements not exported as devices), e.g. synchronous move +of a sardana motor with +:meth:`~sardana.taurus.core.tango.sardana.pool.Motor.move` +method instead of writing motor's position attribute and then waiting for its +state change. + +To obtain these enriched objects with :func:`taurus.Device` you need to first +register the extension classes with +the :obj:`~sardana.taurus.core.tango.sardana.registerExtensions` function. + +The registration needs to be done before the first access to the given +:func:`taurus.Device`. + +When you would like to get back to the default :func:`taurus.Device` behavior +you need to unregister the extension classes with the +:obj:`~sardana.taurus.core.tango.sardana.unregisterExtensions` function. + +Note that the unregistration will not remove the already created devices from +the :func:`taurus.Factory` cache. +""" __docformat__ = 'restructuredtext' diff --git a/src/sardana/taurus/core/tango/sardana/macro.py b/src/sardana/taurus/core/tango/sardana/macro.py index ef89e3acde..d77feb4f91 100644 --- a/src/sardana/taurus/core/tango/sardana/macro.py +++ b/src/sardana/taurus/core/tango/sardana/macro.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from __future__ import absolute_import + ############################################################################## ## # This file is part of Sardana @@ -24,7 +24,7 @@ ############################################################################## """The macro submodule.""" -from __future__ import absolute_import + __all__ = ["MacroInfo", "Macro", "MacroNode", "ParamFactory", "MacroRunException"] @@ -103,13 +103,13 @@ def _isParamComplex(self, p): return not self._isParamAtomic(p) def _isParamAtomic(self, p): - return type(p['type']) in types.StringTypes + return isinstance(p['type'], str) def _buildParameterLine(self, parameters): l = [] for p in parameters: t = p['type'] - if type(t) in types.StringTypes: + if isinstance(t, str): # Simple parameter l.append('<%s>' % p['name']) else: @@ -122,7 +122,7 @@ def _buildParameterDescription(self, parameters): l = [] for p in parameters: t = p['type'] - if type(t) in types.StringTypes: + if isinstance(t, str): # Simple parameter l.append('{name} : ({type}) {description}'.format(**p)) else: @@ -270,13 +270,13 @@ def formatResult(self, result): raise Exception('Macro %s does not return any result' % self.name) result_info = self.getResult() rtype = result_info['type'] + # TODO: formalize and document way of passing files with macro result if rtype == 'File': fd, filename = tempfile.mkstemp(prefix='spock_', text=True) - os.write(fd, result[1]) - os.close(fd) - # put the local filename in the result - result.insert(0, filename) - return result + with os.fdopen(fd, 'w') as fp: + result = result[0] + fp.write(result) + return filename, result res = [] for idx, item in enumerate(result): result_info = self.getResult(idx) @@ -483,7 +483,12 @@ def setMax(self, max): class SingleParamNode(ParamNode): - """Single parameter class.""" + """Single parameter class. + + .. todo: All the usages of setValue are converting the + values to str before calling the setter. + This most probably should not be the case - to be fixed + """ def __init__(self, parent=None, param=None): ParamNode.__init__(self, parent, param) @@ -511,9 +516,7 @@ def setValue(self, value): self._value = value def defValue(self): - if self._defValue is None: - return None - return str(self._defValue) + return self._defValue def setDefValue(self, defValue): if defValue == "None": @@ -532,7 +535,7 @@ def toXml(self): # set value attribute only if it is different than the default value # the server will assign the default value anyway. if value is not None or str(value).lower() != 'none': - if value != self.defValue(): + if value != str(self.defValue()): paramElement.set("value", value) return paramElement @@ -572,7 +575,7 @@ def fromList(self, v): Empty list indicates default value.""" if isinstance(v, list): if len(v) == 0: - v = self.defValue() + v = str(self.defValue()) elif not isinstance(self.parent(), RepeatNode): msg = "Only members of repeat parameter allow list values" raise ValueError(msg) @@ -1021,7 +1024,7 @@ def toRun(self): def toSpockCommand(self): values, alerts = self.toRun() - values = map(str, values) + values = list(map(str, values)) return "%s %s" % (self.name(), str.join(' ', values)) def value(self): @@ -1284,8 +1287,8 @@ def fromPlainText(self, plainTextMacros, macroInfos): try: macroParams = ParamParser(paramsDef).parse(plainTextParams) except ParseError as e: - msg = "{0} can not be parsed ({1})".format(plainTextMacro, - e.message) + msg = "{0} can not be parsed ({1})".format(plainTextMacro, e) + # TODO: think of using `raise from` syntax raise ValueError(msg) macro.fromList(macroParams) @@ -1307,7 +1310,8 @@ def ParamFactory(paramInfo): """ if isinstance(paramInfo.get('type'), list): param = RepeatParamNode(param=paramInfo) - if param.min() > 0: + param_min = param.min() + if param_min is not None and param_min > 0: param.addRepeat() else: param = SingleParamNode(param=paramInfo) diff --git a/src/sardana/taurus/core/tango/sardana/macroserver.py b/src/sardana/taurus/core/tango/sardana/macroserver.py index 75e2248d2a..8849c9ef81 100644 --- a/src/sardana/taurus/core/tango/sardana/macroserver.py +++ b/src/sardana/taurus/core/tango/sardana/macroserver.py @@ -48,42 +48,33 @@ from taurus.core.taurusbasetypes import TaurusEventType, TaurusSWDevState, \ TaurusSerializationMode +from taurus.core import TaurusDevState from taurus.core.util.log import Logger from taurus.core.util.containers import CaselessDict from taurus.core.util.codecs import CodecFactory from taurus.core.util.event import EventGenerator, AttributeEventWait from taurus.core.tango import TangoDevice + + +from sardana.sardanautils import recur_map from .macro import MacroInfo, Macro, MacroNode, ParamFactory, \ SingleParamNode, ParamNode, createMacroNode from .sardana import BaseSardanaElementContainer, BaseSardanaElement from .pool import getChannelConfigs -from itertools import izip_longest +from itertools import zip_longest CHANGE_EVT_TYPES = TaurusEventType.Change, TaurusEventType.Periodic -def recur_map(fun, data, keep_none=False): - """Recursive map. Similar to map, but maintains the list objects structure - - :param fun: the same purpose as in map function - :param data: the same purpose as in map function - :param keep_none: keep None elements without applying fun - """ - if hasattr(data, "__iter__"): - return [recur_map(fun, elem, keep_none) for elem in data] - else: - if keep_none is True and data is None: - return data - else: - return fun(data) - - -def _get_console_width(): +def get_terminal_size(fileno=None): try: - width = int(os.popen('stty size', 'r').read().split()[1]) + if fileno is None: + fileno = sys.stdout.fileno() + if not os.isatty(fileno): + return None + return os.get_terminal_size(fileno) except Exception: - width = float('inf') - return width + return None def _get_nb_lines(nb_chrs, max_chrs): @@ -107,7 +98,7 @@ def eventReceived(self, src, type, evt_value): self.fireEvent(None) elif type != TaurusEventType.Config: if evt_value: - self.fireEvent(evt_value.value) + self.fireEvent(evt_value.rvalue) else: self.fireEvent(None) @@ -133,14 +124,14 @@ def clearLogBuffer(self): def eventReceived(self, src, type, evt_value): if type == TaurusEventType.Change: - if evt_value is None or evt_value.value is None: + if evt_value is None or evt_value.rvalue is None: self.fireEvent(None) else: - self._log_buffer.extend(evt_value.value) + self._log_buffer.extend(evt_value.rvalue) while len(self._log_buffer) > self._max_buff_size: self._log_buffer.pop(0) if evt_value: - self.fireEvent(evt_value.value) + self.fireEvent(evt_value.rvalue) class BaseInputHandler(object): @@ -166,7 +157,7 @@ def input(self, input_data=None): return ret def input_timeout(self, input_data): - print "input timeout" + print("input timeout") class MacroServerDevice(TangoDevice): @@ -198,12 +189,12 @@ def get(self, cache=False): scan_file = env.get('ScanFile') if scan_file is None: scan_file = [] - elif isinstance(scan_file, (str, unicode)): + elif isinstance(scan_file, str): scan_file = [scan_file] ret['ScanFile'] = scan_file mnt_grps = macro_server.getElementsOfType("MeasurementGroup") - mnt_grps_names = [mnt_grp.name for mnt_grp in mnt_grps.values()] - mnt_grps_full_names = mnt_grps.keys() + mnt_grps_names = [mnt_grp.name for mnt_grp in list(mnt_grps.values())] + mnt_grps_full_names = list(mnt_grps.keys()) active_mnt_grp = env.get('ActiveMntGrp') if active_mnt_grp is None and len(mnt_grps): @@ -225,9 +216,8 @@ def get(self, cache=False): for mnt_grp, reply in zip(mnt_grps_names, replies): try: mnt_grp_configs[mnt_grp] = \ - codec.decode(('json', reply.get_data().value), - ensure_ascii=True)[1] - except Exception, e: + codec.decode(('json', reply.get_data().value))[1] + except Exception as e: from taurus.core.util.log import warning warning('Cannot load Measurement group "%s": %s', repr(mnt_grp), repr(e)) @@ -236,7 +226,7 @@ def get(self, cache=False): def set(self, conf, mnt_grps=None): """Sets the ExperimentConfiguration dictionary.""" if mnt_grps is None: - mnt_grps = conf['MntGrpConfigs'].keys() + mnt_grps = list(conf['MntGrpConfigs'].keys()) codec = CodecFactory().getCodec('json') msg_error = '' @@ -256,7 +246,7 @@ def set(self, conf, mnt_grps=None): except Exception: # if the mnt_grp did not already exist, create it now chconfigs = getChannelConfigs(mnt_grp_cfg) - chnames, chinfos = zip(*chconfigs) # unzipping + chnames, chinfos = list(zip(*chconfigs)) # unzipping # We assume that all the channels belong to the same # pool! pool = self._getPoolOfElement(chnames[0]) @@ -352,14 +342,9 @@ def __init__(self, name, **kw): self.call__init__(MacroServerDevice, name, **kw) self._old_door_state = PyTango.DevState.UNKNOWN - try: - self._old_sw_door_state = TaurusSWDevState.Uninitialized - except RuntimeError: - # TODO: For Taurus 4 compatibility - from taurus.core import TaurusDevState - self._old_sw_door_state = TaurusDevState.Undefined + self._old_sw_door_state = TaurusDevState.Undefined - self.getStateObj().addListener(self.stateChanged) + self.stateObj.addListener(self.stateChanged) for log_name in self.log_streams: tg_attr = self.getAttribute(log_name) @@ -495,6 +480,33 @@ def abort(self, synch=True): evt_wait.unlock() evt_wait.disconnect() + def release(self, synch=True): + if not synch: + try: + self.command_inout("ReleaseMacro") + except PyTango.DevFailed as df: + # Macro already finished - no need to release + if df.args[0].reason == "API_CommandNotAllowed": + pass + return + + evt_wait = AttributeEventWait(self.getAttribute("state")) + evt_wait.lock() + try: + time_stamp = time.time() + try: + self.command_inout("ReleaseMacro") + except PyTango.DevFailed as df: + # Macro already finished - no need to release + if df.args[0].reason == "API_CommandNotAllowed": + return + evt_wait.waitEvent(self.Running, equal=False, + after=time_stamp, + timeout=self.InteractiveTimeout) + finally: + evt_wait.unlock() + evt_wait.disconnect() + def stop(self, synch=True): if not synch: self.command_inout("StopMacro") @@ -513,7 +525,7 @@ def stop(self, synch=True): def _clearRunMacro(self): # Clear the log buffer - map(LogAttr.clearLogBuffer, self._log_attr.values()) + list(map(LogAttr.clearLogBuffer, list(self._log_attr.values()))) self._running_macros = None self._running_macro = None self._user_xml = None @@ -537,7 +549,7 @@ def preRunMacro(self, obj, parameters): self._clearRunMacro() xml_root = None - if isinstance(obj, (str, unicode)): + if isinstance(obj, str): if obj.startswith('<') and not parameters: xml_root = etree.fromstring(obj) else: @@ -578,7 +590,9 @@ def runMacro(self, obj, parameters=[], synch=False): def _runMacro(self, xml, synch=False): if not synch: - return self.command_inout("RunMacro", [etree.tostring(xml)]) + return self.command_inout("RunMacro", + [etree.tostring(xml, + encoding='unicode')]) timeout = self.InteractiveTimeout evt_wait = self._getEventWait() evt_wait.connect(self.getAttribute("state")) @@ -591,7 +605,9 @@ def _runMacro(self, xml, synch=False): # the time stamp resolution is not better than 1 ms. evt_wait.clearEventSet() ts = time.time() - result = self.command_inout("RunMacro", [etree.tostring(xml)]) + result = self.command_inout("RunMacro", + [etree.tostring(xml, + encoding='unicode')]) evt_wait.waitEvent(self.Running, after=ts, timeout=timeout) if synch: evt_wait.waitEvent(self.Running, equal=False, after=ts, @@ -608,14 +624,11 @@ def stateChanged(self, s, t, v): # In this case provide the same behavior as Taurus3 - assign None to # the old state try: - self._old_door_state = self.getState() + self._old_door_state = self.stateObj.rvalue except PyTango.DevFailed: self._old_door_state = None - try: - self._old_sw_door_state = self.getSWState() - except: - # TODO: For Taurus 4 compatibility - self._old_sw_door_state = self.state + + self._old_sw_door_state = self.state def resultReceived(self, log_name, result): """Method invoked by the arrival of a change event on the Result @@ -665,11 +678,9 @@ def recordDataReceived(self, s, t, v): return self._processRecordData(v) def _processRecordData(self, data): - if data is None or data.value is None: + if data is None or data.rvalue is None: return - # make sure we get it as string since PyTango 7.1.4 returns a buffer - # object and json.loads doesn't support buffer objects (only str) - data = map(str, data.value) + data = data.rvalue size = len(data[1]) if size == 0: @@ -688,17 +699,12 @@ def macroStatusReceived(self, s, t, v): if t not in CHANGE_EVT_TYPES: return - # make sure we get it as string since PyTango 7.1.4 returns a buffer - # object and json.loads doesn't support buffer objects (only str) - v = map(str, v.value) + v = v.value if not len(v[1]): return format = v[0] codec = CodecFactory().getCodec(format) - # make sure we get it as string since PyTango 7.1.4 returns a buffer - # object and json.loads doesn't support buffer objects (only str) - v[1] = str(v[1]) fmt, data = codec.decode(v) for macro_status in data: id = macro_status.get('id') @@ -712,7 +718,8 @@ def macroStatusReceived(self, s, t, v): return data def logReceived(self, log_name, output): - max_chrs = _get_console_width() + term_size = get_terminal_size() + max_chrs = term_size.columns if term_size else None if not output or self._silent or self._ignore_logs: return @@ -724,8 +731,8 @@ def logReceived(self, log_name, output): if not self._debug: if line == self.BlockStart: self._in_block = True - for i in xrange(self._block_lines): - if max_chrs == float('inf'): + for i in range(self._block_lines): + if max_chrs is None: nb_lines = 1 else: nb_lines = _get_nb_lines( @@ -753,7 +760,6 @@ def logReceived(self, log_name, output): def write(self, msg, stream=None): if self.isSilent(): return - msg = msg.encode('utf-8') self._output_stream = sys.stdout out = self._output_stream if stream is not None: @@ -794,7 +800,7 @@ def refresh(self): self.macro_path = mp = self._ms().get_property("MacroPath")[ "MacroPath"] self.base_macro_path = osp.commonprefix(self.macro_path) - self.rel_macro_path = [osp.relpath for p in mp, self.base_macro_path] + self.rel_macro_path = [osp.relpath for p in (mp, self.base_macro_path)] class Environment(dict): @@ -816,7 +822,7 @@ def __delattr__(self, key): ms.removeEnvironment(key) def __dir__(self): - return [key for key in self.keys() if not key.startswith("_")] + return [key for key in list(self.keys()) if not key.startswith("_")] class BaseMacroServer(MacroServerDevice): @@ -863,15 +869,15 @@ def _on_environment_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: return ret - env = CodecFactory().decode(evt_value.value) + env = CodecFactory().decode(evt_value.rvalue) - for key, value in env.get('new', {}).items(): + for key, value in list(env.get('new', {}).items()): self._addEnvironment(key, value) added.add(key) for key in env.get('del', []): self._removeEnvironment(key) removed.add(key) - for key, value in env.get('change', {}).items(): + for key, value in list(env.get('change', {}).items()): self._removeEnvironment(key) self._addEnvironment(key, value) changed.add(key) @@ -941,10 +947,10 @@ def _on_elements_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: return ret try: - elems = CodecFactory().decode(evt_value.value, ensure_ascii=True) + elems = CodecFactory().decode(evt_value.rvalue) except: self.error("Could not decode element info format=%s len=%s", - evt_value.value[0], len(evt_value.value[1])) + evt_value.rvalue[0], len(evt_value.rvalue[1])) return ret for element_data in elems.get('new', ()): @@ -1125,7 +1131,7 @@ def validateSingleParam(self, singleParamNode): "%s parameter value: %s is above maximum allowed value." % (name, value)) else: - allowedInterfaces = self.getInterfaces().keys() + allowedInterfaces = list(self.getInterfaces().keys()) if type not in allowedInterfaces: raise Exception( "No element with %s interface exist in this sardana " @@ -1161,9 +1167,9 @@ def fillMacroNodeAdditionalInfos(self, macroNode): in XML file. :param macroNode: (MacroNode) macro node obj populated from XML - information + information - See Also: getMacroNodeObj + See also: getMacroNodeObj """ macroName = macroNode.name() macroInfoObj = self.getMacroInfoObj(macroName) @@ -1226,7 +1232,7 @@ def __fillParamNodesValues(self, paramInfo, paramNode): if isinstance(paramType, list): for repeatNode in paramNode.children(): children = repeatNode.children() - for child, paramT in izip_longest(children, paramType): + for child, paramT in zip_longest(children, paramType): if child is None: node = ParamFactory(paramT, repeatNode) repeatNode.insertChild(node) @@ -1240,7 +1246,7 @@ def __fillParamNodesValues(self, paramInfo, paramNode): def printTree(self, nodes, tabs=0): tabs = tabs + 1 for node in nodes: - print ('\t'*tabs) + str(type(node)) + str(node) + print(('\t'*tabs) + str(type(node)) + str(node)) if isinstance(node, SingleParamNode): pass else: diff --git a/src/sardana/taurus/core/tango/sardana/motion.py b/src/sardana/taurus/core/tango/sardana/motion.py index 099c83fcdb..ace214903e 100644 --- a/src/sardana/taurus/core/tango/sardana/motion.py +++ b/src/sardana/taurus/core/tango/sardana/motion.py @@ -136,7 +136,7 @@ def __init__(self, elements, moveable_srcs, allow_repeat=False, first_elem = elements[0] - if isinstance(first_elem, (str, unicode)): + if isinstance(first_elem, str): self.init_by_names(elements, moveable_srcs, allow_repeat, allow_unknown) else: @@ -271,12 +271,12 @@ def init_by_names(self, names, moveable_srcs, allow_repeat, allow_unknown): # map ms_moveables = {} - for moveable_source, ms_names in ms_elem_names.items(): + for moveable_source, ms_names in list(ms_elem_names.items()): moveable = moveable_source.getMoveable(ms_names) ms_moveables[moveable_source] = moveable # list - moveable_list = ms_moveables.values() + moveable_list = list(ms_moveables.values()) # list pos_to_moveable = len(names) * [None, ] @@ -338,7 +338,7 @@ def getElemNamesByMoveableSource(self, names, moveable_sources, for moveable_source in moveable_sources: moveable = moveable_source.getMoveable([name]) if not moveable is None: - if not ms_elems.has_key(moveable_source): + if moveable_source not in ms_elems: ms_elems[moveable_source] = [] moveable_source_moveables = ms_elems.get(moveable_source) present = name in moveable_source_moveables @@ -565,18 +565,18 @@ def test(): try: m = Motion(["m1", "m2"], [ms1, ms2, ms3], read_only=True) m.startMove([0.5, 20.4]) - except Exception, e: - assert(e.message == "Trying to move read only motion") + except Exception as e: + assert(str(e) == "Trying to move read only motion") try: m = Motion(["m1", "m1"], [ms1, ms2, ms3]) - except Exception, e: - assert(e.message == "Moveable item m1 appears more than once") + except Exception as e: + assert(str(e) == "Moveable item m1 appears more than once") try: m = Motion(["m1", "m999"], [ms1, ms2, ms3]) - except Exception, e: - assert(e.message == "Moveable item m999 not found") + except Exception as e: + assert(str(e) == "Moveable item m999 not found") if __name__ == "__main__": test() diff --git a/src/sardana/taurus/core/tango/sardana/pool.py b/src/sardana/taurus/core/tango/sardana/pool.py index af7421ddd8..91c2bfa124 100644 --- a/src/sardana/taurus/core/tango/sardana/pool.py +++ b/src/sardana/taurus/core/tango/sardana/pool.py @@ -26,9 +26,9 @@ """The device pool submodule. It contains specific part of sardana device pool""" -from __future__ import absolute_import __all__ = ["InterruptException", "StopException", "AbortException", + "ReleaseException", "BaseElement", "ControllerClass", "ControllerLibrary", "PoolElement", "Controller", "ComChannel", "ExpChannel", "CTExpChannel", "ZeroDExpChannel", "OneDExpChannel", @@ -46,21 +46,20 @@ import time import traceback import weakref +import json +from datetime import datetime import numpy - +import threading import PyTango +import collections from PyTango import DevState, AttrDataFormat, AttrQuality, DevFailed, \ - DeviceProxy -from taurus import Factory, Device, Attribute + DeviceProxy, AttributeProxy +from taurus import Factory, Device from taurus.core.taurusbasetypes import TaurusEventType -try: - from taurus.core.taurusvalidator import AttributeNameValidator as \ - TangoAttributeNameValidator -except ImportError: - # TODO: For Taurus 4 compatibility - from taurus.core.tango.tangovalidator import TangoAttributeNameValidator +from taurus.core.tango.tangovalidator import TangoAttributeNameValidator, \ + TangoDeviceNameValidator from taurus.core.util.log import Logger from taurus.core.util.codecs import CodecFactory from taurus.core.util.containers import CaselessDict @@ -72,6 +71,9 @@ from .sardana import BaseSardanaElementContainer, BaseSardanaElement from .motion import Moveable, MoveableSource +from sardana.pool import AcqSynchType +from sardana.taurus.core.tango.sardana import PlotType + Ready = Standby = DevState.ON Counting = Acquiring = Moving = DevState.MOVING Alarm = DevState.ALARM @@ -91,6 +93,15 @@ } +def _is_referable(channel): + # Equivalent to ExpChannel.isReferable. + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + if isinstance(channel, str): + channel = DeviceProxy(channel) + return "valueref" in list(map(str.lower, channel.get_attribute_list())) + + class InterruptException(Exception): pass @@ -103,6 +114,10 @@ class AbortException(InterruptException): pass +class ReleaseException(InterruptException): + pass + + class BaseElement(object): """ The base class for elements in the Pool (Pool itself, Motor, ControllerClass, ExpChannel all should inherit from this class directly or @@ -129,9 +144,8 @@ def str(self, n=0): return CodecFactory.encode(('json'), self.serialize()) return self._str_tuple[:n] - def __cmp__(self, o): - return cmp(self.getPoolData()['full_name'], - o.getPoolData()['full_name']) + def __lt__(self, o): + return self.getPoolData()['full_name'] < o.getPoolData()['full_name'] def getName(self): return self.getPoolData()['name'] @@ -182,14 +196,12 @@ def getModel(self): def getOrganization(self): return self.organization - def __cmp__(self, o): - t = cmp(self.getType(), o.getType()) - if t != 0: - return t - t = cmp(self.getGender(), o.getGender()) - if t != 0: - return t - return cmp(self.getClassName(), o.getClassName()) + def __lt__(self, o): + if self.getType() != o.getType(): + return self.getType() < o.getType() + if self.getGender() != o.getGender(): + return self.getGender() < o.getGender() + return self.getClassName() < o.getClassName() class ControllerLibrary(BaseElement): @@ -224,12 +236,17 @@ def eventReceived(self, evt_src, evt_type, evt_value): if evt_value is None: v = None else: - v = evt_value.value + v = evt_value.rvalue + if hasattr(v, "magnitude"): + v = v.magnitude EventGenerator.fireEvent(self, v) def read(self, force=False): try: - self.last_val = self._attr.read(cache=not force).value + last_val = self._attr.read(cache=not force).rvalue + if hasattr(last_val, "magnitude"): + last_val = last_val.magnitude + self.last_val = last_val except: self.error("Read error") self.debug("Details:", exc_info=1) @@ -299,7 +316,7 @@ def __init__(self, name, **kwargs): self.getStateEG() def _find_pool_obj(self): - pool = get_pool_for_device(self.getParentObj(), self.getHWObj()) + pool = get_pool_for_device(self.getParentObj(), self.getDeviceProxy()) return pool def _find_pool_data(self): @@ -336,7 +353,7 @@ def cleanUp(self): f = self.factory() attr_map = self._attrEG - for attr_name in attr_map.keys(): + for attr_name in list(attr_map.keys()): attrEG = attr_map.pop(attr_name) attr = attrEG.getAttribute() attrEG = None @@ -447,6 +464,9 @@ def getInstrumentName(self, force=False): # instr_name = instr_name[:instr_name.index('(')] return instr_name + def setInstrumentName(self, instr_name): + self.getInstrumentObj().write(instr_name) + def getInstrument(self): instr_name = self.getInstrumentName() if not instr_name: @@ -457,9 +477,13 @@ def getInstrument(self): def start(self, *args, **kwargs): evt_wait = self._getEventWait() evt_wait.connect(self.getAttribute("state")) - evt_wait.lock() try: evt_wait.waitEvent(DevState.MOVING, equal=False) + # Clear event set to not confuse the value coming from the + # connection with the event of of end of the operation + # in the next wait event. This was observed on Windows where + # the time stamp resolution is very poor. + evt_wait.clearEventSet() self.__go_time = 0 self.__go_start_time = ts1 = time.time() self._start(*args, **kwargs) @@ -468,8 +492,6 @@ def start(self, *args, **kwargs): except: evt_wait.disconnect() raise - finally: - evt_wait.unlock() ts2 = evt_wait.getRecordedEvents().get(DevState.MOVING, ts2) return (ts2,) @@ -481,21 +503,25 @@ def waitFinish(self, timeout=None, id=None): :param id: id of the opertation returned by start :type id: tuple(float) """ - # Due to taurus-org/taurus #573 we need to divide the timeout - # in two intervals - if timeout is not None: - timeout = timeout / 2. + if timeout is None: + # 0.1 s of timeout with infinite retries facilitates aborting + # by raising exceptions from a different threads + timeout = 0.1 + retries = -1 + else: + # Due to taurus-org/taurus #573 we need to divide the timeout + # in two intervals + timeout = timeout / 2 + retries = 1 if id is not None: id = id[0] evt_wait = self._getEventWait() - evt_wait.lock() try: evt_wait.waitEvent(DevState.MOVING, after=id, equal=False, - timeout=timeout, retries=1) + timeout=timeout, retries=retries) finally: self.__go_end_time = time.time() self.__go_time = self.__go_end_time - self.__go_start_time - evt_wait.unlock() evt_wait.disconnect() @reservedOperation @@ -544,41 +570,43 @@ def _information(self, tab=' '): indent = "\n" + tab + 10 * ' ' msg = [self.getName() + ":"] try: - # TODO: For Taurus 4 / Taurus 3 compatibility - if hasattr(self, "stateObj"): - state_value = self.stateObj.read().rvalue - # state_value is DevState enumeration (IntEnum) - state = state_value.name.capitalize() - else: - state = str(self.state()).capitalize() + t = time.time() + state_time = datetime.fromtimestamp(t).strftime("%H:%M:%S.%f") + # TODO: use expiration_period=float("inf") to always use event + # value (taurus-org/taurus#1105) + state = self.stateObj.read() + state_time = state.time.strftime("%H:%M:%S.%f") + # state_value is DevState enumeration (IntEnum) + state = state.rvalue.name.capitalize() except DevFailed as df: if len(df.args): state = df.args[0].desc else: e_info = sys.exc_info()[:2] - state = traceback.format_exception_only(*e_info) - except: + state = traceback.format_exception_only(*e_info)[0].rstrip() + except Exception: e_info = sys.exc_info()[:2] - state = traceback.format_exception_only(*e_info) - try: - msg.append(tab + " State: " + state) - except TypeError: - msg.append(tab + " State: " + state[0]) + state = traceback.format_exception_only(*e_info)[0].rstrip() + msg.append(tab + " State: " + state + " ({})".format(state_time)) try: - e_info = sys.exc_info()[:2] - status = self.status() - status = status.replace('\n', indent) + t = time.time() + status_time = datetime.fromtimestamp(t).strftime("%H:%M:%S.%f") + # TODO: ideally status should come from the event and no extra + # readout should be made + status = self.read_attribute("status") + status_time = status.time.strftime("%H:%M:%S.%f") + status = status.value.replace('\n', indent) except DevFailed as df: if len(df.args): status = df.args[0].desc else: e_info = sys.exc_info()[:2] - status = traceback.format_exception_only(*e_info) - except: + status = traceback.format_exception_only(*e_info)[0].rstrip() + except Exception: e_info = sys.exc_info()[:2] - status = traceback.format_exception_only(*e_info) - msg.append(tab + " Status: " + status) + status = traceback.format_exception_only(*e_info)[0].rstrip() + msg.append(tab + " Status: " + status + " ({})".format(status_time)) return msg @@ -615,7 +643,8 @@ def removeElement(self, elem): def getElementByAxis(self, axis): pool = self.getPoolObj() - for _, elem in pool.getElementsOfType(self.getMainType()).items(): + for _, elem in \ + list(pool.getElementsOfType(self.getMainType()).items()): if (elem.controller != self.getFullName() or elem.getAxis() != axis): continue @@ -623,7 +652,8 @@ def getElementByAxis(self, axis): def getElementByName(self, name): pool = self.getPoolObj() - for _, elem in pool.getElementsOfType(self.getMainType()).items(): + for _, elem in \ + list(pool.getElementsOfType(self.getMainType()).items()): if (elem.controller != self.getFullName() or elem.getName() != name): continue @@ -638,18 +668,13 @@ def getUsedAxes(self): pool = self.getPoolObj() axes = [] - for _, elem in pool.getElementsOfType(self.getMainType()).items(): + for _, elem in \ + list(pool.getElementsOfType(self.getMainType()).items()): if elem.controller != self.getFullName(): continue axes.append(elem.getAxis()) return sorted(axes) - def getUsedAxis(self): - msg = ("getUsedAxis is deprecated since version 2.5.0. ", - "Use getUsedAxes instead.") - self.warning(msg) - self.getUsedAxes() - def getLastUsedAxis(self): """Return the last used axis (the highest axis) in this controller @@ -661,8 +686,8 @@ def getLastUsedAxis(self): return None return max(used_axes) - def __cmp__(self, o): - return cmp(self.getName(), o.getName()) + def __lt__(self, o): + return self.getName() < o.getName() class ComChannel(PoolElement): @@ -691,7 +716,7 @@ def __init__(self, name, **kw): self._value_ref_buffer_codec = CodecFactory().getCodec(codec_name) def isReferable(self): - if "valueref" in map(str.lower, self.get_attribute_list()): + if "valueref" in list(map(str.lower, self.get_attribute_list())): return True return False @@ -982,12 +1007,12 @@ def setSign(self, value): def _start(self, *args, **kwargs): new_pos = args[0] - if operator.isSequenceType(new_pos): + if isinstance(new_pos, collections.Sequence): new_pos = new_pos[0] try: self.write_attribute('position', new_pos) except DevFailed as df: - for err in df: + for err in df.args: if err.reason == 'API_AttrNotAllowed': raise RuntimeError('%s is already moving' % self) else: @@ -1009,7 +1034,7 @@ def go(self, *args, **kwargs): @reservedOperation def iterMove(self, new_pos, timeout=None): - if operator.isSequenceType(new_pos): + if isinstance(new_pos, collections.Sequence): new_pos = new_pos[0] state, pos = self.getAttribute("state"), self.getAttribute("position") @@ -1022,7 +1047,7 @@ def iterMove(self, new_pos, timeout=None): try: self.getPositionObj().write(new_pos) except DevFailed as err_traceback: - for err in err_traceback: + for err in err_traceback.args: if err.reason == 'API_AttrNotAllowed': raise RuntimeError('%s is already moving' % self) else: @@ -1074,7 +1099,7 @@ def _information(self, tab=' '): pos = str(position.value) if position.quality != AttrQuality.ATTR_VALID: pos += " [" + QUALITY[position.quality] + "]" - except DevFailed, df: + except DevFailed as df: if len(df.args): pos = df.args[0].desc else: @@ -1114,12 +1139,12 @@ def getDialPositionObj(self): def _start(self, *args, **kwargs): new_pos = args[0] - if operator.isSequenceType(new_pos): + if isinstance(new_pos, collections.Sequence): new_pos = new_pos[0] try: self.write_attribute('position', new_pos) - except DevFailed, df: - for err in df: + except DevFailed as df: + for err in df.args: if err.reason == 'API_AttrNotAllowed': raise RuntimeError('%s is already moving' % self) else: @@ -1164,7 +1189,7 @@ def _information(self, tab=' '): pos = str(position.value) if position.quality != AttrQuality.ATTR_VALID: pos += " [" + QUALITY[position.quality] + "]" - except DevFailed, df: + except DevFailed as df: if len(df.args): pos = df.args[0].desc else: @@ -1193,7 +1218,7 @@ def getMotorNames(self): return self.getPoolData()['elements'] def hasMotor(self, name): - motor_names = map(str.lower, self.getMotorNames()) + motor_names = list(map(str.lower, self.getMotorNames())) return name.lower() in motor_names def getPosition(self, force=False): @@ -1210,8 +1235,8 @@ def _start(self, *args, **kwargs): new_pos = args[0] try: self.write_attribute('position', new_pos) - except DevFailed, df: - for err in df: + except DevFailed as df: + for err in df.args: if err.reason == 'API_AttrNotAllowed': raise RuntimeError('%s is already moving' % self) else: @@ -1242,7 +1267,7 @@ def getSize(self): def getIndex(self, name): try: - motor_names = map(str.lower, self.getMotorNames()) + motor_names = list(map(str.lower, self.getMotorNames())) return motor_names.index(name.lower()) except: return -1 @@ -1258,7 +1283,7 @@ def _information(self, tab=' '): pos = str(position.value) if position.quality != AttrQuality.ATTR_VALID: pos += " [" + QUALITY[position.quality] + "]" - except DevFailed, df: + except DevFailed as df: if len(df.args): pos = df.args[0].desc else: @@ -1301,7 +1326,7 @@ def set_info(self, info): data_type = info.data_type try: self.data_type = FROM_TANGO_TO_STR_TYPE[data_type] - except KeyError, e: + except KeyError as e: # For backwards compatibility: # starting from Taurus 4.3.0 DevVoid was added to the dict if data_type == PyTango.DevVoid: @@ -1344,9 +1369,9 @@ def getChannelConfigs(mgconfig, ctrls=None, sort=True): chconfigs = [] if not mgconfig: return [] - for ctrl_name, ctrl_data in mgconfig['controllers'].items(): + for ctrl_name, ctrl_data in list(mgconfig['controllers'].items()): if ctrls is None or ctrl_name in ctrls: - for ch_name, ch_data in ctrl_data['channels'].items(): + for ch_name, ch_data in list(ctrl_data['channels'].items()): ch_data.update({'_controller_name': ctrl_name}) chconfigs.append((ch_name, ch_data)) if sort: @@ -1362,34 +1387,12 @@ def getChannelConfigs(mgconfig, ctrls=None, sort=True): class MGConfiguration(object): def __init__(self, mg, data): self._mg = weakref.ref(mg) - if isinstance(data, (str, unicode)): - data = CodecFactory().decode(('json', data), ensure_ascii=True) - self.raw_data = data - self.__dict__.update(data) - - # dict - # where key is the channel name and value is the channel data in form - # of a dict as receveid by the MG configuration attribute - self.channels = channels = CaselessDict() - - for _, ctrl_data in self.controllers.items(): - for channel_name, channel_data in ctrl_data['channels'].items(): - channels[channel_name] = channel_data - - ##################### - # @todo: the for-loops above could be replaced by something like: - # self.channels = channels = CaselessDict(getChannelConfigs(data, - # sort=False)) - ##################### - - # seq each element is the channel data in form of a dict as - # receveid by the MG configuration attribute. This seq is just a cache - # ordered by channel index in the MG. - self.channel_list = len(channels) * [None] - - for channel in channels.values(): - self.channel_list[channel['index']] = channel + self._raw_data = None + self._pending_event_data = None + self._local_changes = False + self.set_data(data) + def set_data(self, data, force=False): # dict]> # where key is a device name and value is a list with two elements: # - A device proxy or None if there was an error building it @@ -1418,7 +1421,84 @@ def __init__(self, mg, data): # representing channel data as received in raw data self.non_tango_channels = None - self.initialized = False + # object each time + if isinstance(data, str): + data = CodecFactory().decode(('json', data)) + if not force: + if self._raw_data == data: + # The new data received on the on_change_event was generated by + # this object. + return + elif self._local_changes: + self._pending_event_data = data + return + self._pending_event_data = None + self._local_changes = False + + self._raw_data = data + self.__dict__.update(data) + + # dict + # where key is the channel name and value is the channel data in form + # of a dict as received by the MG configuration attribute + self.channels = channels = CaselessDict() + self.channels_names = channels_names = CaselessDict() + self.channels_labels = channels_labels = CaselessDict() + self.controllers_names = controllers_names = CaselessDict() + self.controllers_channels = controllers_channels = CaselessDict() + self.controllers_alias = CaselessDict() + + # TODO private controllers attr + for ctrl_name, ctrl_data in list(self.controllers.items()): + try: + if ctrl_name != '__tango__': + proxy = DeviceProxy(ctrl_name) + ctrl_full_name = ctrl_name + ctrl_name = proxy.alias() + self.controllers_alias[ctrl_full_name] = ctrl_name + + controllers_names[ctrl_name] = ctrl_data + controllers_channels[ctrl_name] = [] + except Exception: + pass + for channel_name, channel_data in \ + list(ctrl_data['channels'].items()): + channels[channel_name] = channel_data + name = channel_data['name'] + channels_names[name] = channel_data + label = channel_data['label'] + channels_labels[name] = channel_data + index = channel_data['index'] + ch_data = {'fullname': channel_name, + 'label': label, + 'name': name, + 'index': index} + controllers_channels[ctrl_name].append(ch_data) + + ##################### + # @todo: the for-loops above could be replaced by something like: + # self.channels = channels = \ + # CaselessDict(getChannelConfigs(data, sort=False)) + ##################### + + # Create ordered list by channel index in the MG as cache + # channel_list: seq each element is the channel data in form + # of a dict as received by the MG configuration attribute. + # channel_list_name: seq + # controller_list_names: seg + self.channel_list = len(channels) * [None] + self.channel_list_name = len(channels) * [None] + self.controller_list_name = [] + + for channel, channel_data in channels.items(): + idx = channel_data['index'] + self.channel_list[idx] = channel_data + self.channel_list_name[idx] = channel + + for channel_name in self.channel_list_name: + ctrl = self._get_ctrl_for_element(channel_name) + if ctrl not in self.controller_list_name: + self.controller_list_name.append(ctrl) def _build(self): # internal channel structure that groups channels by tango device so @@ -1431,17 +1511,17 @@ def _build(self): self.cache = cache = {} tg_attr_validator = TangoAttributeNameValidator() - for channel_name, channel_data in self.channels.items(): + for channel_name, channel_data in list(self.channels.items()): cache[channel_name] = None data_source = channel_data['source'] - params = tg_attr_validator.getParams(data_source) + params = tg_attr_validator.getUriGroups(data_source) if params is None: # Handle NON tango channel n_tg_chs[channel_name] = channel_data else: # Handle tango channel - dev_name = params['devicename'].lower() - attr_name = params['attributename'].lower() + dev_name = params['devname'].lower() + attr_name = params['_shortattrname'].lower() host, port = params.get('host'), params.get('port') if host is not None and port is not None: dev_name = "tango://{0}:{1}/{2}".format(host, port, @@ -1449,12 +1529,15 @@ def _build(self): dev_data = tg_dev_chs.get(dev_name) # technical debt: read Value or ValueRef attribute # ideally the source configuration should include this info - channel = Device(dev_name) - if (isinstance(channel, ExpChannel) - and channel.isReferable() + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + # channel = Device(dev_name) + # if (isinstance(channel, ExpChannel) + # and channel.isReferable() + # and channel_data.get("value_ref_enabled", False)): + if (_is_referable(dev_name) and channel_data.get("value_ref_enabled", False)): attr_name += "Ref" - if dev_data is None: # Build tango device dev = None @@ -1495,7 +1578,7 @@ def prepare(self): # prepare missing tango devices if self.tango_dev_channels_in_error > 0: - for dev_name, dev_data in self.tango_dev_channels.items(): + for dev_name, dev_data in list(self.tango_dev_channels.items()): if dev_data[0] is None: try: dev_data[0] = DeviceProxy(dev_name) @@ -1505,7 +1588,7 @@ def prepare(self): # prepare missing tango attribute configuration if self.tango_channels_info_in_error > 0: - for _, attr_data in self.tango_channels_info.items(): + for _, attr_data in list(self.tango_channels_info.items()): dev_name, attr_name, attr_info = attr_data if attr_info.has_info(): continue @@ -1525,9 +1608,10 @@ def getChannels(self): def getChannelInfo(self, channel_name): try: return self.tango_channels_info[channel_name] - except: + except Exception: channel_name = channel_name.lower() - for d_name, a_name, ch_info in self.tango_channels_info.values(): + for d_name, a_name, ch_info in \ + list(self.tango_channels_info.values()): if ch_info.name.lower() == channel_name: return d_name, a_name, ch_info @@ -1548,7 +1632,7 @@ def getChannelsInfo(self, only_enabled=False): self.prepare() ret = CaselessDict(self.tango_channels_info) ret.update(self.non_tango_channels) - for ch_name, (_, _, ch_info) in ret.items(): + for ch_name, (_, _, ch_info) in list(ret.items()): if only_enabled and not ch_info.enabled: ret.pop(ch_name) return ret @@ -1564,9 +1648,9 @@ def getChannelsInfoList(self, only_enabled=False): """ channels_info = self.getChannelsInfo(only_enabled=only_enabled) ret = [] - for _, (_, _, ch_info) in channels_info.items(): + for _, (_, _, ch_info) in list(channels_info.items()): ret.append(ch_info) - ret = sorted(ret, lambda x, y: cmp(x.index, y.index)) + ret = sorted(ret, key=lambda x: x.index) return ret def getCountersInfoList(self): @@ -1596,9 +1680,9 @@ def getTangoDevChannels(self, only_enabled=False): if not only_enabled: return self.tango_dev_channels tango_dev_channels = {} - for dev_name, dev_data in self.tango_dev_channels.items(): + for dev_name, dev_data in list(self.tango_dev_channels.items()): dev_proxy, attrs = dev_data[0], copy.deepcopy(dev_data[1]) - for attr_name, channel_data in attrs.items(): + for attr_name, channel_data in list(attrs.items()): if not channel_data["enabled"]: attrs.pop(attr_name) tango_dev_channels[dev_name] = [dev_proxy, attrs] @@ -1616,18 +1700,18 @@ def _read_parallel(self): # deposit read requests tango_dev_channels = self.getTangoDevChannels(only_enabled=True) - for _, dev_data in tango_dev_channels.items(): + for _, dev_data in list(tango_dev_channels.items()): dev, attrs = dev_data if dev is None: continue try: dev_replies[dev] = dev.read_attributes_asynch( - attrs.keys()), attrs - except: + list(attrs.keys())), attrs + except Exception: dev_replies[dev] = None, attrs # gather all replies - for dev, reply_data in dev_replies.items(): + for dev, reply_data in list(dev_replies.items()): reply, attrs = reply_data try: data = dev.read_attributes_reply(reply, 0) @@ -1638,8 +1722,8 @@ def _read_parallel(self): else: value = data_item.value ret[channel_data['full_name']] = value - except: - for _, channel_data in attrs.items(): + except Exception: + for _, channel_data in list(attrs.items()): ret[channel_data['full_name']] = None return ret @@ -1648,10 +1732,10 @@ def _read(self): self.prepare() ret = CaselessDict(self.cache) tango_dev_channels = self.getTangoDevChannels(only_enabled=True) - for _, dev_data in tango_dev_channels.items(): + for _, dev_data in list(tango_dev_channels.items()): dev, attrs = dev_data try: - data = dev.read_attributes(attrs.keys()) + data = dev.read_attributes(list(attrs.keys())) for data_item in data: channel_data = attrs[data_item.name] if data_item.has_failed: @@ -1659,14 +1743,645 @@ def _read(self): else: value = data_item.value ret[channel_data['full_name']] = value - except: - for _, channel_data in attrs.items(): + except Exception: + for _, channel_data in list(attrs.items()): ret[channel_data['full_name']] = None return ret + def _get_channel_data(self, channel_name): + if channel_name in self.channels_names: + return self.channels_names[channel_name] + elif channel_name in self.channels_labels: + return self.channels_labels[channel_name] + elif channel_name in self.channels: + return self.channels[channel_name] + v = TangoDeviceNameValidator() + names = v.getNames(channel_name) + msg = 'element "{}" is not in {}'.format(channel_name, self.label) + if names is None: + v = TangoAttributeNameValidator() + names = v.getNames(channel_name) + if names is None: + raise KeyError(msg) + full_name = names[0] + data = self.channels.get(full_name) + if data is None: + raise KeyError(msg) + return data + + def _get_ctrl_data(self, ctrl_name): + if ctrl_name in self.controllers_names: + return self.controllers_names[ctrl_name] + elif ctrl_name in self.controllers: + return self.controllers[ctrl_name] + v = TangoDeviceNameValidator() + names = v.getNames(ctrl_name) + msg = 'element "{}" is not in {}'.format(ctrl_name, self.label) + if names is None: + raise KeyError(msg) + full_name = names[0] + data = self.controllers.get(full_name) + if data is None: + raise KeyError(msg) + return data + + def _set_channels_key(self, key, value, channels_names=None, + apply_cfg=True): + + self._local_changes = True + if channels_names is None: + channels_names = self.channels.keys() + # Protections: + if key in ['enabled', 'output']: + if type(value) != bool: + raise ValueError('The value must be a boolean') + + for channel_name in channels_names: + channel = self._get_channel_data(channel_name) + channel[key] = value + if apply_cfg: + self.applyConfiguration() + + def _get_channels_key(self, key, channels_names=None, use_fullname=False): + """ + Helper method to return the value for one channel configuration key, + if the key does not exist the value will be None. + """ + result = collections.OrderedDict({}) + + if channels_names is None: + channels_names = self.channel_list_name + + for channel_name in channels_names: + channel = self._get_channel_data(channel_name) + if use_fullname: + label = channel['full_name'] + else: + label = channel['label'] + try: + value = channel[key] + except KeyError: + result[label] = None + continue + if key == 'plot_axes': + res = [] + for v in value: + if v not in ['', '']: + v = self.channels[v]['label'] + res.append(v) + value = res + result[label] = value + return result + + def _set_ctrls_key(self, key, value, ctrls_names=None, apply_cfg=True): + self._local_changes = True + if ctrls_names is None: + ctrls_names = self.controllers.keys() + + for ctrl_name in ctrls_names: + # if ctrl_name == '__tango__': + # continue + ctrl = self._get_ctrl_data(ctrl_name) + ctrl[key] = value + if apply_cfg: + self.applyConfiguration() + + def _get_ctrls_key(self, key, ctrls_names=None, use_fullname=False): + """ + Helper method to return the value for one controller configuration key, + if the key does not exist the value will be None. + """ + result = collections.OrderedDict({}) + if ctrls_names is None: + ctrls_names = self.controller_list_name + + for ctrl_name in ctrls_names: + if ctrl_name == '__tango__': + result[ctrl_name] = None + continue + ctrl = self._get_ctrl_data(ctrl_name) + + if use_fullname: + label = ctrl_name + else: + label = DeviceProxy(ctrl_name).alias() + + try: + value = ctrl[key] + except KeyError: + result[label] = None + continue + + if key in ['timer', 'monitor']: + value = self.channels[value]['label'] + elif key == 'synchronizer' and value != 'software': + value = DeviceProxy(value).alias() + + result[label] = value + return result + + def _get_ctrl_for_channel(self, channels_names, unique=False): + result = collections.OrderedDict({}) + + if channels_names is None: + channels_names = self.channel_list_name + + for channel_name in channels_names: + channel = self._get_channel_data(channel_name) + try: + ctrl = channel['_controller_name'] + except KeyError: + ctrl = '__tango__' + if unique and ctrl in result.values(): + raise KeyError('There are more than one channel of the same ' + 'controller') + result[channel['full_name']] = ctrl + + return result + + def _get_ctrl_channels(self, ctrl, use_fullname=False): + idx_channel = {} + if ctrl not in self.controllers_channels: + ctrl = self.controllers_alias[ctrl] + channels_datas = self.controllers_channels[ctrl] + for channel_data in channels_datas: + if use_fullname: + name = channel_data['fullname'] + else: + name = channel_data['label'] + idx = channel_data['index'] + idx_channel[idx] = name + channels = [] + for idx in sorted(idx_channel): + channels.append(idx_channel[idx]) + + return channels + + def _get_channels_for_element(self, element, use_fullname=False): + channels = [] + if element in self.controllers_channels: + channels += self._get_ctrl_channels(element, use_fullname) + else: + channels += [element] + return channels + + def _get_ctrl_for_element(self, element): + if element in self.controllers_channels: + ctrl = element + else: + # TODO: find more elegant way + channel_ctrl = self._get_ctrl_for_channel([element]) + ctrl = list(channel_ctrl.values())[0] + return ctrl + + def applyConfiguration(self, timeout=3): + if not self._local_changes: + return + if self._pending_event_data is not None: + self.set_data(self._pending_event_data, force=True) + raise RuntimeError('The configuration changed on the server ' + 'during your changes.') + mg = self._mg() + try: + mg.setConfiguration(self._raw_data) + except Exception as e: + self._local_changes = False + self._pending_event_data = None + data = mg.getConfigurationAttrEG().readValue(force=True) + self.set_data(data, force=True) + raise e + self._local_changes = False + self._pending_event_data = None + if not mg._flg_event.wait(timeout): + raise RuntimeError('timeout on applying configuration') + + def _getValueRefEnabledChannels(self, channels=None, use_fullname=False): + """get acquisition Enabled channels. + + :param channels: (seq) a list of channels names to get the + Enabled info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where the key are the channels and value the + Enabled state + """ + + return self._get_channels_key('value_ref_enabled', channels, + use_fullname) + + def _setValueRefEnabledChannels(self, state, channels=None, + apply_cfg=True): + """Enable acquisition of the indicated channels. + + :param state: The state of the channels to be set. + :param channels: (seq) a sequence of strings indicating + channel names + """ + self._set_channels_key('value_ref_enabled', state, channels, apply_cfg) + + def _getValueRefPatternChannels(self, channels=None, use_fullname=False): + """get acquisition Enabled channels. + + :param channels: (seq) a list of channels names to get the + Enabled info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where the key are the channels and value the + Enabled state + """ + + return self._get_channels_key('value_ref_pattern', channels, + use_fullname) + + def _setValueRefPatternChannels(self, pattern, channels=None, + apply_cfg=True): + """Enable acquisition of the indicated channels. + + :param pattern: The state of the channels to be set. + :param channels: (seq) a sequence of strings indicating + channel names + """ + self._set_channels_key('value_ref_pattern', pattern, channels, + apply_cfg) + + def _getEnabledChannels(self, channels=None, use_fullname=False): + """get acquisition Enabled channels. + + :param channels: (seq) a list of channels names to get the + Enabled info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where the key are the channels and value the + Enabled state + """ + + return self._get_channels_key('enabled', channels, use_fullname) + + def _setEnabledChannels(self, state, channels=None, apply_cfg=True): + """Enable acquisition of the indicated channels. + + :param state: The state of the channels to be set. + :param channels: (seq) a sequence of strings indicating + channel names + """ + self._set_channels_key('enabled', state, channels, apply_cfg) + + def _getOutputChannels(self, channels=None, use_fullname=False): + """get the output State of the channels. + + :param channels: (list) a string indicating the channel name, + in case of None, it will return all the Outputs Info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where keys are channel names and + value the Outputs configuration + """ + + return self._get_channels_key('output', channels, use_fullname) + + def _setOutputChannels(self, state, channels=None, apply_cfg=True): + """Set the Output state of the indicated channels. + + :param state: (bool) Indicate the state of the output. + :param channels: (seq) a sequence of strings indicating + channel names + """ + + self._set_channels_key('output', state, channels, apply_cfg) + + def _getPlotTypeChannels(self, channels=None, use_fullname=False): + """get the Plot Type for the channel indicated. In case of empty + channel value it will return all the Plot Type Info + + :param channels: (list) Indicate the channel to return the + Plot Type Info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where keys are channel names and + value the plot axes info + """ + # TODO: Change to return enum value SEP12 + return self._get_channels_key('plot_type', channels, use_fullname) + + def _setPlotTypeChannels(self, ptype, channels=None, apply_cfg=True): + """Set the Plot Type for the indicated channels. + + :param ptype: string indicating the type name + :param channels: (seq) a list of strings indicating the channels + to apply the PlotType + """ + + msg_error = 'Wrong value! PlotType allowed: ' \ + '{0}'.format(PlotType.keys()) + if type(ptype) == str: + if ptype.lower() not in map(str.lower, PlotType.keys()): + raise ValueError(msg_error) + for value in PlotType.keys(): + if value.lower() == ptype.lower(): + ptype = PlotType[value] + break + elif type(ptype) == int: + try: + PlotType[ptype] + except Exception: + raise ValueError(msg_error) + else: + raise ValueError() + self._set_channels_key('plot_type', ptype, channels, apply_cfg) + + def _getPlotAxesChannels(self, channels=None, use_fullname=False): + """get the PlotAxes for the channel indicated. In case of empty channel + value it will return all the PlotAxes Info + + :param channels: (list) Indicate the channel to return the + PlotAxes Info + :param use_fullname: (bool) returns a full name instead sardana + element name + + :return a OrderedDict where keys are channel names and + value the plot axes info + """ + + return self._get_channels_key('plot_axes', channels, use_fullname) + + def _setPlotAxesChannels(self, axes, channels_names=None, apply_cfg=True): + """Set the PlotAxes for the indicated channels. + + :param axes: string indicating the axis name + :param channels_names: (seq) a list of strings indicating the + channels to apply the PlotAxes + """ + # Validate axes values + for i, value in enumerate(axes): + if value in ['', '']: + continue + else: + axes[i] = self._get_channel_data(value)["full_name"] + + if channels_names is None: + channels_names = self.channels.keys() + + for channel_name in channels_names: + channel_data = self._get_channel_data(channel_name) + + # Check the current channel plot type + plot_type = PlotType[PlotType[channel_data['plot_type']]] + if plot_type == PlotType.No: + raise RuntimeError('You must set firs the PlotType') + elif plot_type == PlotType.Spectrum: + if len(axes) != 1: + raise ValueError('The Spectrum Type only allows one axis') + elif plot_type == PlotType.Image: + if len(axes) != 2: + raise ValueError('The Image Type only allows two axis') + + self._set_channels_key('plot_axes', axes, [channel_name], apply_cfg) + + def _getCtrlsTimer(self, ctrls=None, use_fullname=False): + """get the acquisition Timer. + + :param ctrls: list of Controllers names to get the timer + info + :param use_fullname: returns a full name instead sardana + element name + + :return a OrderedDict where keys are controller names and + value the Timer Info + """ + + return self._get_ctrls_key('timer', ctrls, use_fullname) + + def _setCtrlsTimer(self, timers, apply_cfg=True): + """Set the acquisition Timer to the controllers compatibles, + it finds the controller comptible with this timer and set it + . + :param timer_name: strings indicating the timer name + """ + result = self._get_ctrl_for_channel(timers, unique=True) + meas_ctrl = self.channels[self.timer]['_controller_name'] + + for timer, ctrl in result.items(): + if ctrl == meas_ctrl: + self._local_changes = True + self._raw_data['timer'] = timer + self._set_ctrls_key('timer', timer, [ctrl], apply_cfg) + + def _getCtrlsMonitor(self, ctrls=None, use_fullname=False): + """get the Monitor for the channel indicated. In case of empty channel + value it will return all the Monitor Info + + :param ctrls: Indicate the controllers to return the Monitor Info + :param use_fullname: returns a full name instead sardana + element name + + :return a OrderedDict where keys are channel names and + value the Monitor Info + """ + + return self._get_ctrls_key('monitor', ctrls, use_fullname) + + def _setCtrlsMonitor(self, monitors, apply_cfg=True): + """Set the Monitor for to the controllers compatibles, + it finds the controller comptible with this timer and set it + + :param monitors: (seq) a list of strings indicating the channels + to apply the monitor + :param monitor: string indicating the monitor name + """ + + result = self._get_ctrl_for_channel(monitors, unique=True) + meas_ctrl = self.channels[self.monitor]['_controller_name'] + + for monitor, ctrl in result.items(): + if ctrl == meas_ctrl: + self._local_changes = True + self._raw_data['monitor'] = monitor + self._set_ctrls_key('monitor', monitor, [ctrl], apply_cfg) + + def _getCtrlsSynchronization(self, ctrls=None, use_fullname=False): + """get the Synchronization for the channel indicated. In case of empty + ctrl value it will return all the Synchronization Info + + :param ctrl: Indicate the controllers to return the + Synchronization Info + :param use_fullname: returns a full name instead sardana + element name + + :return a OrderedDict where keys are controllers names and + value the Synchronization Info + """ + + return self._get_ctrls_key('synchronization', ctrls, use_fullname) + + def _setCtrlsSynchronization(self, synchronization, ctrls=None, + apply_cfg=True): + """Set the Synchronization to the indicated controllers. + + :param synchronization: string indicating the synchronization + :param ctrls: (seq) a list of strings indicating the channels + to apply the Synchronization + name + """ + msg_error = 'Wrong value! Synchronization allowed: ' \ + '{0}'.format(AcqSynchType.keys()) + if type(synchronization) == str: + if synchronization.lower() not in map(str.lower, + AcqSynchType.keys()): + raise ValueError(msg_error) + for value in AcqSynchType.keys(): + if value.lower() == synchronization.lower(): + synchronization = AcqSynchType[value] + break + elif type(synchronization) == int: + try: + AcqSynchType[synchronization] + except Exception: + raise ValueError(msg_error) + else: + raise ValueError() + self._set_ctrls_key('synchronization', synchronization, ctrls, + apply_cfg) + + def _getCtrlsSynchronizer(self, ctrls=None, use_fullname=False): + """get the synchronizer for the channel indicated. In case of empty + channel value it will return all the Synchronizers Info + + :param ctrls: Indicate the controllers to return the + Synchronizer Info + :param use_fullname: returns a full name instead sardana + element name + :return a OrderedDict where keys are controllers names and + value the synchronizer info + """ + + return self._get_ctrls_key('synchronizer', ctrls, use_fullname) + + def _setCtrlsSynchronizer(self, synchronizer, ctrls=None, apply_cfg=True): + """Set the synchronizer for the indicated controollers. In case of + empty ctrls value it will be applied to all the controllers + + :param syncronizer: string indicating the synchronizer name + :param ctrls: (seq) a list of strings indicating the + controllers to apply the synchronizer + """ + if synchronizer == 'software': + pass + else: + # TODO: Improve how to check if the element is a trigger_gate + sync = Device(synchronizer) + if 'triggergate' not in sync.fullname: + raise ValueError('The "{0}" is not a ' + 'triggergate'.format(synchronizer)) + synchronizer = sync.fullname + self._set_ctrls_key('synchronizer', synchronizer, ctrls, apply_cfg) + + def _getTimerName(self): + return self._getTimer()['name'] + + def _getTimer(self): + return self.channels[self.timer] + + def _getTimerValue(self): + return self._getTimerName() + + def _getMonitorName(self): + return self._getMonitor()['name'] + + def _getMonitor(self): + return self.channels[self.monitor] + + def getValues(self, parallel=True): + return self.read(parallel=parallel) + + def _getCounters(self): + return [c for c in self.getChannels() if c['full_name'] != self.timer] + + def _getChannelNames(self): + return [ch['name'] for ch in self.getChannels()] + + def _getCounterNames(self): + return [ch['name'] for ch in self.getCounters()] + + def _getChannelLabels(self): + return [ch['label'] for ch in self.getChannels()] + + def _getCounterLabels(self): + return [ch['label'] for ch in self.getCounters()] + + def _getChannel(self, name): + return self.channels[name] + + def getChannelsEnabledInfo(self): + """ + Returns information about **only enabled** channels present in the + measurement group in a form of ordered, based on the channel index, + list. + + :return: list with channels info + :rtype: list + """ + return self.getChannelsInfoList(only_enabled=True) + + def getCountersInfo(self): + return self.getCountersInfoList() + + def setTimer(self, timer, apply_cfg=True): + """DEPRECATED: Set the Global Timer to the measurement group. + + Also it changes the timer in the controllers with the previous timer. + + :param timer: timer name + """ + self._mg().warning("setTimer() is deprecated since 3.0.3. " + "Global measurement group timer does not exist") + result = self._get_ctrl_for_channel([timer], unique=True) + + for timer, ctrl in result.items(): + self._local_changes = True + self._raw_data['timer'] = timer + self._set_ctrls_key('timer', timer, [ctrl], apply_cfg) + + def getTimer(self): + """DEPRECATED""" + self._mg().warning("getTimer() is deprecated since 3.0.3. " + "Global measurement group timer does not exist") + return self._getTimer() + + def getMonitor(self): + """DEPRECATED""" + self._mg().warning("getMonitor() is deprecated since 3.0.3. " + "Global measurement group monitor does not exist") + return self._getMonitor() + + def __repr__(self): + return json.dumps(self._raw_data, indent=4, sort_keys=True) + class MeasurementGroup(PoolElement): - """ Class encapsulating MeasurementGroup functionality.""" + """MeasurementGroup Sardana-Taurus extension. + + Setting configuration parameters using e.g., + `~sardana.taurus.core.tango.sardana.pool.MeasurementGroup.setEnabled` or + `~sardana.taurus.core.tango.sardana.pool.MeasurementGroup.setTimer`, etc. + by default applies changes on the server. Since setting the configuration + means passing to the server all the configuration parameters of + the measurement group at once this behavior can be changed with the + ``apply=False``. Then the configuration changes are kept locally. + This is useful when changing more then one parameter. In this case only + setting of the last parameter should use ``apply=True`` or use + `~sardana.taurus.core.tango.sardana.pool.MeasurementGroup.applyConfiguration` + afterwards:: + + # or in a macro use: meas_grp = self.getMeasurementGroup("mntgrp01") + meas_grp = taurus.Device("mntgrp01") + meas_grp.setEnabled(False, apply=False) + meas_grp.setEnabled(True, "ct01", "ct02") + """ def __init__(self, name, **kw): """PoolElement initialization.""" @@ -1675,14 +2390,17 @@ def __init__(self, name, **kw): self._last_integ_time = None self.call__init__(PoolElement, name, **kw) + self._flg_event = threading.Event() self.__cfg_attr = self.getAttribute('configuration') self.__cfg_attr.addListener(self.on_configuration_changed) self._value_buffer_cb = None + self._value_buffer_channels = None codec_name = getattr(sardanacustomsettings, "VALUE_BUFFER_CODEC") self._value_buffer_codec = CodecFactory().getCodec(codec_name) self._value_ref_buffer_cb = None + self._value_ref_buffer_channels = None codec_name = getattr(sardanacustomsettings, "VALUE_REF_BUFFER_CODEC") self._value_ref_buffer_codec = CodecFactory().getCodec(codec_name) @@ -1699,12 +2417,16 @@ def getConfigurationAttrEG(self): return self._getAttrEG('Configuration') def setConfiguration(self, configuration): + self._flg_event.clear() codec = CodecFactory().getCodec('json') f, data = codec.encode(('', configuration)) self.write_attribute('configuration', data) def _setConfiguration(self, data): - self._configuration = MGConfiguration(self, data) + if self._configuration is None: + self._configuration = MGConfiguration(self, data) + else: + self._configuration.set_data(data) def getConfiguration(self, force=False): if force or self._configuration is None: @@ -1716,69 +2438,607 @@ def on_configuration_changed(self, evt_src, evt_type, evt_value): if evt_type not in CHANGE_EVT_TYPES: return self.info("Configuration changed") - self._setConfiguration(evt_value.value) + self._setConfiguration(evt_value.rvalue) + self._flg_event.set() - def getTimerName(self): - return self.getTimer()['name'] + def getValueBuffers(self): + value_buffers = [] + for channel_info in self.getChannels(): + channel = Device(channel_info["full_name"]) + value_buffers.append(channel.getValueBuffer()) + return value_buffers - def getTimer(self): - cfg = self.getConfiguration() - return cfg.channels[cfg.timer] + def getIntegrationTime(self): + return self._getAttrValue('IntegrationTime') - def getTimerValue(self): - return self.getTimerName() + def getIntegrationTimeObj(self): + return self._getAttrEG('IntegrationTime') - def getMonitorName(self): - return self.getMonitor()['name'] + def setIntegrationTime(self, ctime): + self.getIntegrationTimeObj().write(ctime) - def getMonitor(self): - cfg = self.getConfiguration() - return cfg.channels[cfg.monitor] + def putIntegrationTime(self, ctime): + if self._last_integ_time == ctime: + return + self._last_integ_time = ctime + self.getIntegrationTimeObj().write(ctime) - def setTimer(self, timer_name): - try: - self.getChannel(timer_name) - except KeyError: - raise Exception("%s does not contain a channel named '%s'" - % (str(self), timer_name)) - cfg = self.getConfiguration().raw_data - cfg['timer'] = timer_name - import json - self.write_attribute("configuration", json.dumps(cfg)) + def getAcquisitionModeObj(self): + return self._getAttrEG('AcquisitionMode') - def getChannels(self): - return self.getConfiguration().getChannels() + def getAcquisitionMode(self): + return self._getAttrValue('AcquisitionMode') - def getCounters(self): - cfg = self.getConfiguration() - return [c for c in self.getChannels() if c['full_name'] != cfg.timer] + def setAcquisitionMode(self, acqMode): + self.getAcquisitionModeObj().write(acqMode) - def getChannelNames(self): - return [ch['name'] for ch in self.getChannels()] + def getSynchDescriptionObj(self): + return self._getAttrEG('SynchDescription') - def getCounterNames(self): - return [ch['name'] for ch in self.getCounters()] + def getSynchDescription(self): + return self._getAttrValue('SynchDescription') - def getChannelLabels(self): - return [ch['label'] for ch in self.getChannels()] + def setSynchDescription(self, synch_description): + codec = CodecFactory().getCodec('json') + _, data = codec.encode(('', synch_description)) + self.getSynchDescriptionObj().write(data) + self._last_integ_time = None - def getCounterLabels(self): - return [ch['label'] for ch in self.getCounters()] + def _get_channels_for_elements(self, elements): + if not elements: + return None + config = self.getConfiguration() + channels = [] + for element in elements: + channels += config._get_channels_for_element(element) + return channels + + def _get_ctrl_for_elements(self, elements): + if not elements: + return None + ctrls = [] + config = self.getConfiguration() + for element in elements: + ctrl = config._get_ctrl_for_element(element) + if ctrl in ctrls: + continue + ctrls.append(ctrl) + return ctrls + + def setOutput(self, output, *elements, apply=True): + """Set the output configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the output + on the controller means setting it to all channels of this controller + present in this measurement group. + + :param output: `True` - output enabled, `False` - output disabled + :type output: bool + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ - def getChannel(self, name): - return self.getConfiguration().channels[name] + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setOutputChannels(output, channels, apply_cfg=apply) + + def getOutput(self, *elements, ret_full_name=False): + """Get the output configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the output + from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, bool) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + return config._getOutputChannels(channels, use_fullname=ret_full_name) + + def setEnabled(self, enabled, *elements, apply=True): + """Set the enabled configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the enabled + on the controller means setting it to all channels of this controller + present in this measurement group. + + :param enabled: `True` - element enabled, `False` - element disabled + :type enabled: bool + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ - def getChannelInfo(self, name): - return self.getConfiguration().getChannelInfo(name) + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setEnabledChannels(enabled, channels, apply_cfg=apply) + + def getEnabled(self, *elements, ret_full_name=False): + """Get the output configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the enabled + from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, bool) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + return config._getEnabledChannels(channels, use_fullname=ret_full_name) + + def setPlotType(self, plot_type, *elements, apply=True): + """Set the enabled configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the plot + type on the controller means setting it to all channels of this + controller present in this measurement group. + + :param plot_type: 'No'/0 , 'Spectrum'/1, 'Image'/2 + :type plot_type: str or int + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ - def getChannelsInfo(self): - return self.getConfiguration().getChannelsInfoList() + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setPlotTypeChannels(plot_type, channels, apply_cfg=apply) + + def getPlotType(self, *elements, ret_full_name=False): + """Get the output configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the plot + type from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, int) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + # TODO Change the documentation when _getPlotTypeChannels return enum + # value + return config._getPlotTypeChannels(channels, + use_fullname=ret_full_name) + + def setPlotAxes(self, plot_axes, *elements, apply=True): + """Set the enabled configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the plot + axes on the controller means setting it to all channels of this + controller present in this measurement group. + + :param plot_axes: [''] / ['', ''] + :type plot_axes: list(str) + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setPlotAxesChannels(plot_axes, channels, apply_cfg=apply) + + def getPlotAxes(self, *elements, ret_full_name=False): + """Get the output configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the plot + axes from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, str) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + return config._getPlotAxesChannels(channels, + use_fullname=ret_full_name) + + def setValueRefEnabled(self, value_ref_enabled, *elements, apply=True): + """Set the output configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the value + reference enabled on the controller means setting it to all channels + of this controller present in this measurement group. + + :param value_ref_enabled: `True` - enabled, `False` - disabled + :type value_ref_enabled: bool + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setValueRefEnabledChannels(value_ref_enabled, channels, + apply_cfg=apply) + + def getValueRefEnabled(self, *elements, ret_full_name=False): + """Get the value reference enabled configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the value + from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, bool) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + return config._getValueRefEnabledChannels(channels, + use_fullname=ret_full_name) + + def setValueRefPattern(self, value_ref_pattern, *elements, apply=True): + """Set the output configuration for the given elements. + + Channels and controllers are accepted as elements. Setting the value + reference pattern on the controller means setting it to all channels + of this controller present in this measurement group. + + :param value_ref_pattern: `/path/file{index:03d}.txt` + :type value_ref_pattern: :py:obj:`str` + :param elements: sequence of element names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + config._setValueRefPatternChannels(value_ref_pattern, channels, + apply_cfg=apply) + + def getValueRefPattern(self, *elements, ret_full_name=False): + """Get the value reference enabled configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the value + from the controller means getting it from all channels of this + controller present in this measurement group. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their output + configurations. Note that even if the *elements* contained + controllers, the returned configuration will always contain + only channels. + :rtype: dict(str, str) + """ + channels = self._get_channels_for_elements(elements) + config = self.getConfiguration() + return config._getValueRefPatternChannels(channels, + use_fullname=ret_full_name) + + def _get_value_per_channel(self, config, ctrls_values, use_fullname=False): + channels_values = collections.OrderedDict({}) + for ctrl, value in ctrls_values.items(): + for channel in config._get_ctrl_channels(ctrl, use_fullname): + channels_values[channel] = value + return channels_values + + def setTimer(self, timer, *elements, apply=True): + """Set the timer configuration for the given channels of the same + controller. + + .. note:: Currently the controller's timer must be unique. Hence this + method will set it for the whole controller regardless of the + ``elements`` argument. + + :param timer: channel use as timer + :type timer: :py:obj:`str` + :param elements: sequence of channels names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + config = self.getConfiguration() + # TODO: Implement solution to set the timer per channel when it is + # allowed. + config._setCtrlsTimer([timer], apply_cfg=apply) + + def getTimer(self, *elements, ret_full_name=False, ret_by_ctrl=False): + """Get the timer configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the output + from the controller means getting it from all channels of this + controller present in this measurement group, unless + `ret_by_ctrl=True`. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :param ret_by_ctrl: whether keys in the returned dictionary are + controllers or channels (default: `False` means return channels) + :type ret_by_ctrl: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their timer + configurations + :rtype: dict(str, str) + """ + # TODO: Implement solution to set the timer per channel when it is + # allowed. + ctrls = self._get_ctrl_for_elements(elements) + config = self.getConfiguration() + ctrls_timers = config._getCtrlsTimer(ctrls, use_fullname=ret_full_name) + if ret_by_ctrl: + return ctrls_timers + else: + return self._get_value_per_channel(config, ctrls_timers, + use_fullname=ret_full_name) + + def setMonitor(self, monitor, *elements, apply=True): + """Set the monitor configuration for the given channels of the same + controller. + + .. note:: Currently the controller's monitor must be unique. + Hence this method will set it for the whole controller regardless of + the ``elements`` argument. + + :param monitor: channel use as monitor + :type monitor: :py:obj:`str` + :param elements: sequence of channels names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + config = self.getConfiguration() + # TODO: Implement solution to set the moniotor per channel when it is + # allowed. + config._setCtrlsMonitor([monitor], apply_cfg=apply) + + def getMonitor(self, *elements, ret_full_name=False, ret_by_ctrl=False): + """Get the monitor configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the output + from the controller means getting it from all channels of this + controller present in this measurement group, unless + `ret_by_ctrl=True`. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :param ret_by_ctrl: whether keys in the returned dictionary are + controllers or channels (default: `False` means return channels) + :type ret_by_ctrl: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their monitor + configurations + :rtype: dict(str, str) + """ + # TODO: Implement solution to set the timer per channel when it is + # allowed. + ctrls = self._get_ctrl_for_elements(elements) + config = self.getConfiguration() + ctrls_monitor = config._getCtrlsMonitor(ctrls, + use_fullname=ret_full_name) + if ret_by_ctrl: + return ctrls_monitor + else: + return self._get_value_per_channel(config, ctrls_monitor, + use_fullname=ret_full_name) + + def setSynchronizer(self, synchronizer, *elements, apply=True): + """Set the synchronizer configuration for the given channels or + controller. + + .. note:: Currently the controller's synchronizer must be unique. + Hence this method will set it for the whole controller regardless of + the ``elements`` argument. + + :param synchronizer: triger/gate element name or software + :type synchronizer: :py:obj:`str` + :param elements: sequence of channels names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + config = self.getConfiguration() + # TODO: Implement solution to set the timer per channel when it is + # allowed. + ctrls = self._get_ctrl_for_elements(elements) + config._setCtrlsSynchronizer(synchronizer, ctrls, apply_cfg=apply) + + def getSynchronizer(self, *elements, ret_full_name=False, + ret_by_ctrl=False): + """Get the synchronizer configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the output + from the controller means getting it from all channels of this + controller present in this measurement group, unless + `ret_by_ctrl=True`. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :param ret_by_ctrl: whether keys in the returned dictionary are + controllers or channels (default: `False` means return channels) + :type ret_by_ctrl: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their synchronizer + configurations + :rtype: dict(str, str) + """ + # TODO: Implement solution to set the synchronizer per channel when it + # is allowed. + ctrls = self._get_ctrl_for_elements(elements) + config = self.getConfiguration() + ctrls_sync = config._getCtrlsSynchronizer(ctrls, + use_fullname=ret_full_name) + if ret_by_ctrl: + return ctrls_sync + else: + return self._get_value_per_channel(config, ctrls_sync, + use_fullname=ret_full_name) + + def setSynchronization(self, synchronization, *elements, apply=True): + """Set the synchronization configuration for the given channels or + controller. + + .. note:: Currently the controller's synchronization must be unique. + Hence this method will set it for the whole controller regardless of + the ``elements`` argument. + + :param synchronization: synchronization type e.g. Trigger, Gate or + Start + :type synchronization: `sardana.pool.AcqSynchType` + :param elements: sequence of channels names or full names, no elements + means set to all + :type elements: list(str) + :param apply: `True` - apply on the server, `False` - do not apply yet + on the server and keep locally (default: `True`) + :type apply: bool + """ + config = self.getConfiguration() + # TODO: Implement solution to set the synchronization per channel when + # it is allowed. + ctrls = self._get_ctrl_for_elements(elements) + config._setCtrlsSynchronization(synchronization, ctrls, + apply_cfg=apply) + + def getSynchronization(self, *elements, ret_full_name=False, + ret_by_ctrl=False): + """Get the synchronization configuration of the given elements. + + Channels and controllers are accepted as elements. Getting the output + from the controller means getting it from all channels of this + controller present in this measurement group, unless + `ret_by_ctrl=True`. + + :param elements: sequence of element names or full names, no elements + means get from all + :type elements: list(str) + :param ret_full_name: whether keys in the returned dictionary are + full names or names (default: `False` means return names) + :type ret_full_name: bool + :param ret_by_ctrl: whether keys in the returned dictionary are + controllers or channels (default: `False` means return channels) + :type ret_by_ctrl: bool + :return: ordered dictionary where keys are **channel** names (or full + names if `ret_full_name=True`) and values are their + synchronization configurations + :rtype: dict<`str`, `sardana.pool.AcqSynchType`> + """ + # TODO: Implement solution to set the synchronization per channel + # when it is allowed. + ctrls = self._get_ctrl_for_elements(elements) + config = self.getConfiguration() + ctrls_sync = \ + config._getCtrlsSynchronization(ctrls, use_fullname=ret_full_name) + if ret_by_ctrl: + return ctrls_sync + else: + return self._get_value_per_channel(config, ctrls_sync, + use_fullname=ret_full_name) + + def applyConfiguration(self): + """Apply configuration changes kept locally on the server. + + Setting configuration parameters using e.g., + `~sardana.taurus.core.tango.sardana.pool.MeasurementGroup.setEnabled` + or + `~sardana.taurus.core.tango.sardana.pool.MeasurementGroup.setTimer`, + etc. + with ``apply=False`` keeps the changes locally. Use this method to + apply them on the server afterwards. + """ + self.getConfiguration().applyConfiguration() + + ######################################################################### + # TODO: review the following API def getChannelsEnabledInfo(self): """Returns information about **only enabled** channels present in the measurement group in a form of ordered, based on the channel index, list. - :return: list with channels info :rtype: list """ @@ -1788,50 +3048,77 @@ def getCountersInfo(self): return self.getConfiguration().getCountersInfoList() def getValues(self, parallel=True): - return self.getConfiguration().read(parallel=parallel) + return self.getConfiguration().getValues(parallel) - def getValueBuffers(self): - value_buffers = [] - for channel_info in self.getChannels(): - channel = Device(channel_info["full_name"]) - value_buffers.append(channel.getValueBuffer()) - return value_buffers + def getChannels(self): + return self.getConfiguration().getChannels() - def getIntegrationTime(self): - return self._getAttrValue('IntegrationTime') + def getCounters(self): + return self.getConfiguration()._getCounters() - def getIntegrationTimeObj(self): - return self._getAttrEG('IntegrationTime') + def getChannelNames(self): + return self.getConfiguration()._getChannelNames() - def setIntegrationTime(self, ctime): - self.getIntegrationTimeObj().write(ctime) + def getCounterNames(self): + return self.getConfiguration()._getCounterNames() - def putIntegrationTime(self, ctime): - if self._last_integ_time == ctime: - return - self._last_integ_time = ctime - self.getIntegrationTimeObj().write(ctime) + def getChannelLabels(self): + return self.getConfiguration()._getChannelLabels() - def getAcquisitionModeObj(self): - return self._getAttrEG('AcquisitionMode') + def getCounterLabels(self): + return self.getConfiguration()._getCounterLabels() - def getAcquisitionMode(self): - return self._getAttrValue('AcquisitionMode') + def getChannel(self, name): + return self.getConfiguration()._getChannel(name) - def setAcquisitionMode(self, acqMode): - self.getAcquisitionModeObj().write(acqMode) + def getChannelInfo(self, name): + return self.getConfiguration().getChannelInfo(name) - def getSynchronizationObj(self): - return self._getAttrEG('Synchronization') + ######################################################################### - def getSynchronization(self): - return self._getAttrValue('Synchronization') + def getChannelsInfo(self): + """DEPRECATED""" + self.warning('Deprecation warning: you should use ' + '"getChannelsInfoList" instead of "getChannelsInfo"') + return self.getConfiguration().getChannelsInfoList() - def setSynchronization(self, synchronization): - codec = CodecFactory().getCodec('json') - _, data = codec.encode(('', synchronization)) - self.getSynchronizationObj().write(data) - self._last_integ_time = None + def getMonitorName(self): + """DEPRECATED""" + self.warning("getMonitorName() is deprecated since 3.0.3. " + "Global measurement group monitor does not exist.") + return self.getConfiguration()._getMonitorName() + + def getTimerName(self): + """DEPRECATED""" + self.warning("getTimerName() is deprecated since 3.0.3. " + "Global measurement group timer does not exist.") + return self.getConfiguration()._getTimerName() + + def getTimerValue(self): + """DEPRECATED""" + self.warning("getTimerValue() is deprecated since 3.0.3. " + "Global measurement group timer does not exist.") + return self.getConfiguration()._getTimerValue() + + def enableChannels(self, channels): + '''DEPRECATED: Enable acquisition of the indicated channels. + + :param channels: (seq) a sequence of strings indicating + channel names + ''' + self.warning("enableChannels() in deprecated since 3.0.3. " + "Use setEnabled() instead.") + self.setEnabled(True, *channels) + + def disableChannels(self, channels): + '''DEPRECATED: Disable acquisition of the indicated channels. + + :param channels: (seq) a sequence of strings indicating + channel names + ''' + self.warning("enableChannels() in deprecated since 3.0.3. " + "Use setEnabled() instead.") + self.setEnabled(False, *channels) # NbStarts Methods def getNbStartsObj(self): @@ -1875,7 +3162,7 @@ def valueBufferChanged(self, channel, value_buffer): _, value_buffer = self._value_buffer_codec.decode(value_buffer) values = value_buffer["value"] if isinstance(values[0], list): - np_values = map(numpy.array, values) + np_values = list(map(numpy.array, values)) value_buffer["value"] = np_values self._value_buffer_cb(channel, value_buffer) @@ -1888,12 +3175,15 @@ def subscribeValueBuffer(self, cb=None): channel's callback :type cb: callable """ + self._value_buffer_channels = [] for channel_info in self.getChannels(): full_name = channel_info["full_name"] value_ref_enabled = channel_info.get("value_ref_enabled", False) - channel = Device(full_name) - if channel.isReferable() and value_ref_enabled: + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + if _is_referable(full_name) and value_ref_enabled: continue + channel = Device(full_name) value_buffer_obj = channel.getValueBufferObj() if cb is not None: self._value_buffer_cb = cb @@ -1902,6 +3192,7 @@ def subscribeValueBuffer(self, cb=None): else: value_buffer_obj.subscribeEvent(channel.valueBufferChanged, with_first_event=False) + self._value_buffer_channels.append(channel) def unsubscribeValueBuffer(self, cb=None): """Unsubscribe from channels' value buffer events. If no callback is @@ -1914,9 +3205,11 @@ def unsubscribeValueBuffer(self, cb=None): for channel_info in self.getChannels(): full_name = channel_info["full_name"] value_ref_enabled = channel_info.get("value_ref_enabled", False) - channel = Device(full_name) - if channel.isReferable() and value_ref_enabled: + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + if _is_referable(full_name) and value_ref_enabled: continue + channel = Device(full_name) value_buffer_obj = channel.getValueBufferObj() if cb is not None: value_buffer_obj.unsubscribeEvent(self.valueBufferChanged, @@ -1924,6 +3217,7 @@ def unsubscribeValueBuffer(self, cb=None): self._value_buffer_cb = None else: value_buffer_obj.unsubscribeEvent(channel.valueBufferChanged) + self._value_buffer_channels = None def valueRefBufferChanged(self, channel, value_ref_buffer): """Receive value ref buffer updates, pre-process them, and call @@ -1950,14 +3244,17 @@ def subscribeValueRefBuffer(self, cb=None): channel's callback :type cb: callable """ + self._value_ref_buffer_channels = [] for channel_info in self.getChannels(): full_name = channel_info["full_name"] value_ref_enabled = channel_info.get("value_ref_enabled", False) - channel = Device(full_name) - if not channel.isReferable(): + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + if not _is_referable(full_name): continue if not value_ref_enabled: continue + channel = Device(full_name) value_ref_buffer_obj = channel.getValueRefBufferObj() if cb is not None: self._value_ref_buffer_cb = cb @@ -1966,6 +3263,7 @@ def subscribeValueRefBuffer(self, cb=None): else: value_ref_buffer_obj.subscribeEvent( channel.valueRefBufferChanged, with_first_event=False) + self._value_ref_buffer_channels.append(channel) def unsubscribeValueRefBuffer(self, cb=None): """Unsubscribe from channels' value ref buffer events. If no @@ -1978,11 +3276,13 @@ def unsubscribeValueRefBuffer(self, cb=None): for channel_info in self.getChannels(): full_name = channel_info["full_name"] value_ref_enabled = channel_info.get("value_ref_enabled", False) - channel = Device(full_name) - if not channel.isReferable(): + # Use DeviceProxy instead of taurus to avoid crashes in Py3 + # See: tango-controls/pytango#292 + if not _is_referable(full_name): continue if not value_ref_enabled: continue + channel = Device(full_name) value_ref_buffer_obj = channel.getValueRefBufferObj() if cb is not None: value_ref_buffer_obj.unsubscribeEvent( @@ -1991,42 +3291,7 @@ def unsubscribeValueRefBuffer(self, cb=None): else: value_ref_buffer_obj.unsubscribeEvent( channel.valueRefBufferChanged) - - def enableChannels(self, channels): - '''Enable acquisition of the indicated channels. - - :param channels: (seq) a sequence of strings indicating - channel names - ''' - self._enableChannels(channels, True) - - def disableChannels(self, channels): - '''Disable acquisition of the indicated channels. - - :param channels: (seq) a sequence of strings indicating - channel names - ''' - self._enableChannels(channels, False) - - def _enableChannels(self, channels, state): - found = {} - for channel in channels: - found[channel] = False - cfg = self.getConfiguration() - for channel in cfg.getChannels(): - name = channel['name'] - if name in channels: - channel['enabled'] = state - found[name] = True - wrong_channels = [] - for ch, f in found.items(): - if f is False: - wrong_channels.append(ch) - if len(wrong_channels) > 0: - msg = 'channels: %s are not present in measurement group' % \ - wrong_channels - raise Exception(msg) - self.setConfiguration(cfg.raw_data) + self._value_ref_buffer_channels = None def _start(self, *args, **kwargs): try: @@ -2034,7 +3299,7 @@ def _start(self, *args, **kwargs): except DevFailed as e: # TODO: Workaround for CORBA timeout on measurement group start # remove it whenever sardana-org/sardana#93 gets implemented - if e[-1].reason == "API_DeviceTimedOut": + if e.args[-1].reason == "API_DeviceTimedOut": self.error("start timed out, trying to stop") self.stop() self.debug("stopped") @@ -2101,14 +3366,19 @@ def go(self, *args, **kwargs): self.prepare() return self.count_raw(start_time) - def count_continuous(self, synchronization, value_buffer_cb=None): + def count_continuous(self, synch_description, value_buffer_cb=None, + value_ref_buffer_cb=None): """Execute measurement process according to the given synchronization description. - :param synchronization: synchronization description - :type synchronization: list of groups with equidistant synchronizations + :param synch_description: synchronization description + :type synch_description: list of groups with equidistant + synchronizations :param value_buffer_cb: callback on value buffer updates :type value_buffer_cb: callable + :param value_ref_buffer_cb: callback on value reference + buffer updates + :type value_ref_buffer_cb: callable :return: state and eventually value buffers if no callback was passed :rtype: tuple,> @@ -2122,10 +3392,15 @@ class on a provisional basis. Backwards incompatible changes start_time = time.time() cfg = self.getConfiguration() cfg.prepare() - self.setSynchronization(synchronization) + self.setSynchDescription(synch_description) + self.prepare() self.subscribeValueBuffer(value_buffer_cb) - self.count_raw(start_time) - self.unsubscribeValueBuffer(value_buffer_cb) + self.subscribeValueRefBuffer(value_ref_buffer_cb) + try: + self.count_raw(start_time) + finally: + self.unsubscribeValueBuffer(value_buffer_cb) + self.unsubscribeValueRefBuffer(value_ref_buffer_cb) state = self.getStateEG().readValue() if state == Fault: msg = "Measurement group ended acquisition with Fault state" @@ -2163,7 +3438,7 @@ def startWriteValue(self, new_value, timeout=None): self.getValueObj().write(new_value) self.final_val = new_value except DevFailed as err_traceback: - for err in err_traceback: + for err in err_traceback.args: if err.reason == 'API_AttrNotAllowed': raise RuntimeError('%s is already chaging' % self) else: @@ -2239,7 +3514,7 @@ def on_elements_changed(self, evt_src, evt_type, evt_value): if evt_type == TaurusEventType.Error: msg = evt_value if isinstance(msg, DevFailed): - d = msg[0] + d = msg.args[0] # skip configuration errors if d.reason == "API_BadConfigurationProperty": return @@ -2254,10 +3529,10 @@ def on_elements_changed(self, evt_src, evt_type, evt_value): elif evt_type not in CHANGE_EVT_TYPES: return try: - elems = CodecFactory().decode(evt_value.value, ensure_ascii=True) + elems = CodecFactory().decode(evt_value.rvalue) except: self.error("Could not decode element info") - self.info("value: '%s'", evt_value.value) + self.info("value: '%s'", evt_value.rvalue) self.debug("Details:", exc_info=1) return elements = self.getElementsInfo() @@ -2314,14 +3589,14 @@ def getElementWithInterface(self, elem_name, interface): def getObj(self, name, elem_type=None): if elem_type is None: return self.getElementInfo(name) - elif isinstance(elem_type, (str, unicode)): + elif isinstance(elem_type, str): elem_types = elem_type, else: elem_types = elem_type name = name.lower() for e_type in elem_types: elems = self.getElementsOfType(e_type) - for elem in elems.values(): + for elem in list(elems.values()): if elem.name.lower() == name: return elem elem = elems.get(name) @@ -2344,7 +3619,7 @@ def getMoveable(self, names): Returns a moveable object that handles all the moveable items given in names.""" # if simple motor just return it (if the pool has it) - if isinstance(names, (str, unicode)): + if isinstance(names, str): names = names, if len(names) == 1: @@ -2362,7 +3637,7 @@ def getMoveable(self, names): while True: name = "_mg_ms_{0}_{1}".format(pid, i) exists = False - for mg in mgs.values(): + for mg in list(mgs.values()): if mg.name == name: exists = True break @@ -2373,10 +3648,10 @@ def getMoveable(self, names): return moveable def __findMotorGroupWithElems(self, names): - names_lower = map(str.lower, names) + names_lower = list(map(str.lower, names)) len_names = len(names) mgs = self.getElementsOfType('MotorGroup') - for mg in mgs.values(): + for mg in list(mgs.values()): mg_elems = mg.elements if len(mg_elems) != len_names: continue @@ -2396,7 +3671,7 @@ def _wait_for_element_in_container(self, container, elem_name, timeout=0.5, cond = True nap = 0.01 if timeout: - nap = timeout / 10. + nap = timeout / 10 while cond: elem = container.getElement(elem_name) if contains: @@ -2414,14 +3689,14 @@ def _wait_for_element_in_container(self, container, elem_name, timeout=0.5, time.sleep(nap) def createMotorGroup(self, mg_name, elements): - params = [mg_name, ] + map(str, elements) + params = [mg_name, ] + list(map(str, elements)) self.debug('trying to create motor group for elements: %s', params) self.command_inout('CreateMotorGroup', params) elements_info = self.getElementsInfo() return self._wait_for_element_in_container(elements_info, mg_name) def createMeasurementGroup(self, mg_name, elements): - params = [mg_name, ] + map(str, elements) + params = [mg_name, ] + list(map(str, elements)) self.debug('trying to create measurement group: %s', params) self.command_inout('CreateMeasurementGroup', params) elements_info = self.getElementsInfo() @@ -2466,7 +3741,7 @@ def createController(self, class_name, name, *props): raise Exception("Controller class %s not found" % class_name) cmd = "CreateController" pars = [ctrl_class.types[0], ctrl_class.file_name, class_name, name] - pars.extend(map(str, props)) + pars.extend(list(map(str, props))) self.command_inout(cmd, pars) elements_info = self.getElementsInfo() return self._wait_for_element_in_container(elements_info, name) @@ -2474,6 +3749,11 @@ def createController(self, class_name, name, *props): def deleteController(self, name): return self.deleteElement(name) + def createInstrument(self, full_name, class_name): + self.command_inout("CreateInstrument", [full_name, class_name]) + elements_info = self.getElementsInfo() + return self._wait_for_element_in_container(elements_info, full_name) + def registerExtensions(): factory = Factory() diff --git a/src/sardana/taurus/core/tango/sardana/sardana.py b/src/sardana/taurus/core/tango/sardana/sardana.py index 4d96cd8640..1b0fc8c9b2 100644 --- a/src/sardana/taurus/core/tango/sardana/sardana.py +++ b/src/sardana/taurus/core/tango/sardana/sardana.py @@ -94,8 +94,8 @@ def __str__(self): def __getattr__(self, name): return getattr(self.getObj(), name) - def __cmp__(self, elem): - return cmp(self.name, elem.name) + def __lt__(self, elem): + return self.name < elem.name def getData(self): return self._data @@ -111,7 +111,7 @@ def getType(self): def getTypes(self): elem_types = self.type - if isinstance(elem_types, (str, unicode)): + if isinstance(elem_types, str): return [elem_types] return elem_types @@ -183,7 +183,7 @@ def getElementsOfType(self, t): return elems def getElementNamesOfType(self, t): - return [e.name for e in self.getElementsOfType(t).values()] + return [e.name for e in list(self.getElementsOfType(t).values())] def getElementsWithInterface(self, interface): elems = self._interfaces_dict.get(interface, {}) @@ -196,18 +196,19 @@ def getElementsWithInterfaces(self, interfaces): return ret def getElementNamesWithInterface(self, interface): - return [e.name for e in self.getElementsWithInterface(interface).values()] + return [e.name for e in + list(self.getElementsWithInterface(interface).values())] def hasElementName(self, elem_name): return self.getElement(elem_name) is not None def getElement(self, elem_name): elem_name = elem_name.lower() - for elems in self._type_elems_dict.values(): + for elems in list(self._type_elems_dict.values()): elem = elems.get(elem_name) # full_name? if elem is not None: return elem - for elem in elems.values(): + for elem in list(elems.values()): if elem.name.lower() == elem_name: return elem @@ -216,14 +217,14 @@ def getElementWithInterface(self, elem_name, interface): elems = self._interfaces_dict.get(interface, {}) if elem_name in elems: return elems[elem_name] - for elem in elems.values(): + for elem in list(elems.values()): if elem.name.lower() == elem_name: return elem def getElements(self): ret = set() - for elems in self._type_elems_dict.values(): - ret.update(elems.values()) + for elems in list(self._type_elems_dict.values()): + ret.update(list(elems.values())) return ret def getInterfaces(self): @@ -284,9 +285,8 @@ def get_model(self): def get_icon(self): # fake data ############### - import taurus.qt.qtgui.resource - - return taurus.qt.qtgui.resource.getIcon(":/designer/extra_motor.png") + from taurus.external.qt import Qt + return Qt.QIcon("designer:extra_motor.png") def get_organization(self): # fake data ############### @@ -532,24 +532,27 @@ def _init(self): pass elif dev_class_name == "MacroServer": ms_dev_name = dev_name - ms_prop_list = map( - str.lower, db.get_device_property_list(ms_dev_name, "*")) + ms_prop_list = list(map( + str.lower, db.get_device_property_list(ms_dev_name, "*"))) ms_props = db.get_device_property(ms_dev_name, ms_prop_list) ms_name = dev_info.server().serverInstance() ms_alias = dev_info.alias() - ms = MacroServer(self, ms_name, ms_props.get("macropath"), ms_props.get("poolnames"), + ms = MacroServer(self, ms_name, ms_props.get("macropath"), + ms_props.get("poolnames"), ms_props.get("version"), ms_alias, ms_dev_name) self._macroservers.append(ms) for pool_dev_name in ms_props.get("poolnames", ()): - pool_prop_list = map( - str.lower, db.get_device_property_list(pool_dev_name, "*")) + pool_prop_list = \ + list(map(str.lower, + db.get_device_property_list(pool_dev_name, "*"))) pool_props = db.get_device_property( pool_dev_name, pool_prop_list) pool_dev_info = cache.devices()[pool_dev_name] pool_name = pool_dev_info.server().serverInstance() pool_alias = pool_dev_info.alias() - pool = Pool(self, pool_name, pool_props.get( - "poolpath"), pool_props.get("version"), pool_alias, pool_dev_name) + pool = Pool(self, pool_name, pool_props.get("poolpath"), + pool_props.get("version"), pool_alias, + pool_dev_name) self._pools.append(pool) def get_name(self): @@ -647,16 +650,16 @@ def __init__(self, db): def refresh(self): self._sardanas = sardanas = {} services = self._db.get_service_list("Sardana/.*") - for service, dev in services.items(): + for service, dev in list(services.items()): service_type, service_instance = service.split("/", 1) try: sardanas[service_instance] = Sardana( self, service_instance, dev) - except: + except Exception: pass def create_sardana(self, name, device_name): - if self._sardanas.has_key(name): + if name in self._sardanas: raise Exception("Sardana '%s' already exists" % name) self._db.register_service("Sardana", name, device_name) sardana = Sardana(self, name) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_macro.py b/src/sardana/taurus/core/tango/sardana/test/test_macro.py index 0f6090537f..21362cdf74 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_macro.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_macro.py @@ -27,7 +27,7 @@ from lxml import etree -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.taurus.core.tango.sardana.macro import MacroNode from sardana.taurus.core.tango.sardana.macro import createMacroNode @@ -100,12 +100,13 @@ def _validateXML(self, macronode_xml, expected_xml): :param macronode_xml: macronode lxml.etree :param expected_xml: expected lxml.etree ''' - expected_str = etree.tostring(expected_xml) - macronode_str = etree.tostring(macronode_xml, pretty_print=True) + expected_str = etree.tostring(expected_xml, encoding='unicode') + macronode_str = etree.tostring(macronode_xml, encoding='unicode', + pretty_print=True) msg = "XML encodings are not equal" # TODO: check why macronode_str has an extra whitespace charactger # at the end. strips should not be necessary - self.assertEquals(expected_str.strip(), macronode_str.strip(), msg) + self.assertEqual(expected_str.strip(), macronode_str.strip(), msg) def verifyXML(self, macro_name, param_def, param_str, expected_xml_rep): """ diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py new file mode 100644 index 0000000000..d7addfc2de --- /dev/null +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpconf.py @@ -0,0 +1,568 @@ +import uuid +import unittest +from taurus import Device +from taurus.core.tango.tangovalidator import TangoDeviceNameValidator +from sardana.pool import AcqSynchType +from sardana.taurus.core.tango.sardana.pool import registerExtensions +from sardana.tango.pool.test.base_sartest import SarTestTestCase + + +class TestMeasurementGroupConfiguration(SarTestTestCase, unittest.TestCase): + + def setUp(self): + SarTestTestCase.setUp(self) + registerExtensions() + + def tearDown(self): + SarTestTestCase.tearDown(self) + + def _assertResult(self, result, channels, expected_value): + expected_channels = list(channels) + for channel, value in result.items(): + msg = "unexpected key: {}".format(channel) + self.assertIn(channel, expected_channels, msg) + expected_channels.remove(channel) + self.assertEqual(value, expected_value) + msg = "{} are missing".format(expected_channels) + self.assertEqual(len(expected_channels), 0, msg) + + def _assertMultipleResults(self, result, channels, expected_values): + expected_channels = list(channels) + for (channel, value), expected_value in zip(result.items(), + expected_values): + msg = "unexpected key: {}".format(channel) + self.assertIn(channel, expected_channels, msg) + expected_channels.remove(channel) + self.assertEqual(value, expected_value) + msg = "{} are missing".format(expected_channels) + self.assertEqual(len(expected_channels), 0, msg) + + def test_enabled(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_2d_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + + # Check initial state of all kind of channels, nonexistent + # channels for the feature return None as result. + result = mg.getEnabled(*elements) + expected = [True] * len(elements) + self._assertMultipleResults(result, elements, expected) + + # Test every possible combination of setting values + # Check that changing one channel doesn't affect the other + mg.setEnabled(False, *elements) + result = mg.getEnabled(*elements) + expected = [False] * len(elements) + self._assertMultipleResults(result, elements, expected) + mg.setEnabled(True, elements[0]) + result = mg.getEnabled(*elements) + expected = [False] * len(elements) + expected[0] = True + self._assertMultipleResults(result, elements, expected) + mg.setEnabled(False, *elements) + resutl = mg.getEnabled(*elements) + self._assertResult(resutl, elements, False) + + # Redefine elements to ony use existing values + elements = ["_test_ct_1_1", "_test_ct_1_2"] + + # Set values using the controller instead of channels + mg.setEnabled(True, "_test_ct_ctrl_1") + resutl = mg.getEnabled(*elements) + self._assertResult(resutl, elements, True) + + # Get values by controller + mg.setEnabled(False, *elements) + resutl = mg.getEnabled("_test_ct_ctrl_1") + self._assertResult(resutl, elements, False) + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + resutl = mg.getEnabled(*full_names) + self._assertResult(resutl, elements, False) + mg.setEnabled(True, *full_names) + resutl = mg.getEnabled(*elements, ret_full_name=True) + self._assertResult(resutl, full_names, True) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_output(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_2d_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + + try: + mg = Device(mg_name) + + # Check initial state of all kind of channels, nonexistent + # channels for the feature return None as result. + enabled = mg.getOutput(*elements) + expected = [True] * len(elements) + self._assertMultipleResults(enabled, elements, expected) + + # Test every possible combination of setting values + # Check that changing one channel doesn't affect the other + mg.setOutput(False, *elements) + is_output = mg.getOutput(*elements) + self._assertResult(is_output, elements, False) + mg.setOutput(True, elements[0]) + result = mg.getOutput(*elements) + expected = [False] * len(elements) + expected[0] = True + self._assertMultipleResults(result, elements, expected) + mg.setOutput(False, *elements) + is_output = mg.getOutput(*elements) + self._assertResult(is_output, elements, False) + + # Redefine elements to ony use existing values + elements = ["_test_ct_1_1", "_test_ct_1_2"] + + # Set values using the controller instead of channels + mg.setOutput(True, "_test_ct_ctrl_1") + is_output = mg.getOutput(*elements) + self._assertResult(is_output, elements, True) + + # Get values by controller + mg.setOutput(False, *elements) + is_output = mg.getOutput("_test_ct_ctrl_1") + self._assertResult(is_output, elements, False) + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + is_output = mg.getOutput(*full_names) + self._assertResult(is_output, elements, False) + mg.setOutput(True, *full_names) + is_output = mg.getOutput(*elements, ret_full_name=True) + self._assertResult(is_output, full_names, True) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_PlotType(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", "_test_2d_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + + try: + mg = Device(mg_name) + plottype = mg.getPlotType() + self._assertResult(plottype, elements, 0) + mg.setPlotType("Image", elements[0]) + mg.setPlotType("Spectrum", elements[1]) + mg.setPlotType("No", elements[2]) + mg.setPlotType("Image", elements[3]) + mg.setPlotType("Image", elements[4]) + plottype = mg.getPlotType() + expected_values = [2, 1, 0, 2, 2] + self._assertMultipleResults(plottype, elements, expected_values) + with self.assertRaises(ValueError): + mg.setPlotType("asdf", elements[2]) + + # Redefine elements + elements = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3"] + + # Set values using the controller instead of channels + mg.setPlotType("Image", "_test_ct_ctrl_1") + plottype = mg.getPlotType(*elements) + self._assertResult(plottype, elements, 2) + + # Get values by controller + mg.setPlotType("Spectrum", *elements) + plottype = mg.getPlotType("_test_ct_ctrl_1") + self._assertResult(plottype, elements, 1) + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + plottype = mg.getPlotType(*full_names) + self._assertResult(plottype, elements, 1) + mg.setPlotType("Image", *full_names) + plottype = mg.getPlotType(*elements, ret_full_name=True) + self._assertResult(plottype, full_names, 2) + + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_PlotAxes(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", "_test_2d_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + + try: + mg = Device(mg_name) + mg.setPlotType("Image", elements[0]) + mg.setPlotType("Spectrum", elements[1]) + mg.setPlotType("No", elements[2]) + mg.setPlotType("Image", elements[3]) + mg.setPlotType("Image", elements[4]) + result = mg.getPlotAxes() + self._assertResult(result, elements, []) + + mg.setPlotAxes(["", ""], elements[0]) + mg.setPlotAxes([""], elements[1]) + with self.assertRaises(Exception): + mg.setPlotAxes([''], elements[2]) + mg.setPlotAxes(["", ""], elements[3]) + mg.setPlotAxes(["", ""], elements[4]) + + result = mg.getPlotAxes() + expected_result = [['', ''], [''], [], + ['', ''], ['', '']] + self._assertMultipleResults(result, elements, expected_result) + + mg.setPlotAxes(["", ""], elements[0]) + mg.setPlotAxes([""], elements[1]) + result = mg.getPlotAxes() + expected_result = [['', ''], [''], [], + ['', ''], ['', '']] + self._assertMultipleResults(result, elements, expected_result) + + mg.setPlotAxes(["", ""], elements[0]) + result = mg.getPlotAxes() + expected_result = [['', ''], [''], [], + ['', ''], ['', '']] + self._assertMultipleResults(result, elements, expected_result) + + with self.assertRaises(RuntimeError): + mg.setPlotAxes([""], elements[2]) + with self.assertRaises(ValueError): + mg.setPlotAxes(["", ""], elements[1]) + with self.assertRaises(ValueError): + mg.setPlotAxes([""], elements[0]) + + elements = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3"] + # Set values using the controller instead of channels + with self.assertRaises(Exception): + mg.setPlotAxes([""], "_test_ct_ctrl_1") + # TODO get method by controller doesn't give the order + # Get values by controller + result = mg.getPlotAxes("_test_ct_ctrl_1") + expected_result = [['', ''], [''], []] + self._assertMultipleResults(result, elements, expected_result) + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + result = mg.getPlotAxes(*full_names) + expected_result = [['', ''], [''], []] + self._assertMultipleResults(result, elements, expected_result) + mg.setPlotAxes(["", ""], full_names[0]) + mg.setPlotAxes([""], full_names[1]) + result = mg.getPlotAxes(*elements, ret_full_name=True) + expected_result = [['', ''], [''], []] + self._assertMultipleResults(result, full_names, expected_result) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_Timer(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + + result = mg.getTimer("_test_mt_1_3/position") + with self.assertRaises(Exception): + mg.setTimer("_test_mt_1_3/position") + self._assertResult(result, ["_test_mt_1_3/position"], None) + mg.setTimer('_test_ct_1_3') + result = mg.getTimer(*elements) + expected = ['_test_ct_1_3', '_test_ct_1_3', '_test_ct_1_3', None] + self._assertMultipleResults(result, elements, expected) + + mg.setTimer('_test_ct_1_2') + result = mg.getTimer(*elements) + expected = ['_test_ct_1_2', '_test_ct_1_2', '_test_ct_1_2', None] + self._assertMultipleResults(result, elements, expected) + + result = mg.getTimer(*elements, ret_by_ctrl=True) + self._assertMultipleResults(result, + ['_test_ct_ctrl_1', '__tango__'], + ['_test_ct_1_2', None]) + + # Check ret_full_name + v = TangoDeviceNameValidator() + counters = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3"] + full_names = [v.getNames(counter)[0] for counter in counters] + mg.setTimer(v.getNames('_test_ct_1_1')[0]) + result = mg.getTimer() + expected = ['_test_ct_1_1', '_test_ct_1_1', '_test_ct_1_1', None] + self._assertMultipleResults(result, elements, expected) + # TODO ret_full_name gives controler name + mg.setTimer("_test_ct_1_2") + result = mg.getTimer(*counters, ret_full_name=True) + self._assertResult(result, full_names, "_test_ct_1_2") + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_Monitor(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", "_test_2d_1_1", + '_test_2d_1_2', + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + + with self.assertRaises(Exception): + mg.setMonitor("_test_mt_1_3/position") + + mg.setMonitor('_test_2d_1_2') + mg.setMonitor("_test_ct_1_3") + expected = ["_test_ct_1_3", "_test_ct_1_3", "_test_ct_1_3", + "_test_2d_1_2", '_test_2d_1_2', None] + result = mg.getMonitor() + self._assertMultipleResults(result, elements, expected) + + expected = ["_test_ct_1_3", '_test_2d_1_2', None] + result = mg.getMonitor(ret_by_ctrl=True) + ctrls = ['_test_ct_ctrl_1', '_test_2d_ctrl_1', '__tango__'] + self._assertMultipleResults(result, ctrls, expected) + + # Check ret_full_name + v = TangoDeviceNameValidator() + counters = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", + '_test_2d_1_1', '_test_2d_1_2'] + full_names = [v.getNames(counter)[0] for counter in counters] + mg.setMonitor(v.getNames('_test_ct_1_1')[0]) + mg.setMonitor(v.getNames('_test_2d_1_2')[0]) + + result = mg.getMonitor(*counters, ret_full_name=True) + expected = ["_test_ct_1_1", "_test_ct_1_1", "_test_ct_1_1", + "_test_2d_1_2", '_test_2d_1_2'] + self._assertMultipleResults(result, full_names, expected) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_Synchronizer(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", "_test_2d_1_1", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + result = mg.getSynchronizer() + expected = ['software', 'software', 'software', 'software', None] + self._assertMultipleResults(result, elements, expected) + with self.assertRaises(Exception): + mg.setSynchronizer('_test_tg_1_2', "_test_mt_1_3/position") + + mg.setSynchronizer('_test_tg_1_2', "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + expected = ['_test_tg_1_2', '_test_tg_1_2', '_test_tg_1_2', + '_test_tg_1_2', None] + result = mg.getSynchronizer() + self._assertMultipleResults(result, elements, expected) + + mg.setSynchronizer('software', "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + result = mg.getSynchronizer() + expected = ['software', 'software', 'software', 'software', None] + self._assertMultipleResults(result, elements, expected) + + with self.assertRaises(Exception): + mg.setSynchronizer('asdf', "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + # Check ret_full_name + v = TangoDeviceNameValidator() + counters = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", + '_test_2d_1_1'] + full_names = [v.getNames(counter)[0] for counter in counters] + mg.setSynchronizer('_test_tg_1_2', "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + result = mg.getSynchronizer(*counters, ret_full_name=True) + + self._assertResult(result, full_names, '_test_tg_1_2') + + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_Synchronization(self, elements=["_test_ct_1_1", "_test_ct_1_2", + "_test_ct_1_3", "_test_2d_1_1", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + result = mg.getSynchronization() + expected = [AcqSynchType.Trigger, AcqSynchType.Trigger, + AcqSynchType.Trigger, AcqSynchType.Trigger, None] + self._assertMultipleResults(result, elements, expected) + # TODO: maybe we should raise an exception here? + # with self.assertRaises(Exception): + # mg.setSynchronization(AcqSynchType.Trigger, + # "_test_mt_1_3/position") + + mg.setSynchronization(AcqSynchType.Gate, "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + expected = [AcqSynchType.Gate, AcqSynchType.Gate, + AcqSynchType.Gate, AcqSynchType.Gate, None] + result = mg.getSynchronization() + self._assertMultipleResults(result, elements, expected) + + mg.setSynchronization(AcqSynchType.Start, "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + result = mg.getSynchronization() + expected = [AcqSynchType.Start, AcqSynchType.Start, + AcqSynchType.Start, AcqSynchType.Start, None] + self._assertMultipleResults(result, elements, expected) + + with self.assertRaises(Exception): + mg.setSynchronization('asdf', "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + # Check ret_full_name + v = TangoDeviceNameValidator() + counters = ["_test_ct_1_1", "_test_ct_1_2", "_test_ct_1_3", + '_test_2d_1_1'] + full_names = [v.getNames(counter)[0] for counter in counters] + mg.setSynchronization(AcqSynchType.Trigger, "_test_ct_ctrl_1", + "_test_2d_ctrl_1") + + result = mg.getSynchronization(*counters, ret_full_name=True) + + self._assertResult(result, full_names, AcqSynchType.Trigger) + + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_ValueRefEnabled(self, elements=["_test_2d_1_1", "_test_2d_1_2", + "_test_ct_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + + # Check initial state of all kind of channels, nonexistent + # channels for the feature return None as result. + enabled = mg.getValueRefEnabled(*elements) + expected = [False, False, None, None] + self._assertMultipleResults(enabled, elements, expected) + + # Check if the nonexistent channels raise error if trying to set + with self.assertRaises(Exception): + mg.setValueRefEnabled(True, *elements[-2]) + with self.assertRaises(Exception): + mg.setValueRefEnabled(True, *elements[-1]) + + # Redefine elements to ony use existing values + elements = ["_test_2d_1_1", "_test_2d_1_2"] + + # Test every possible combination of setting values + # Check that changing one channel doesn't affect the other + mg.setValueRefEnabled(True, *elements) + enabled = mg.getValueRefEnabled(*elements) + self._assertResult(enabled, elements, True) + mg.setValueRefEnabled(False, elements[0]) + result = mg.getValueRefEnabled(*elements) + expected = [True] * len(elements) + expected[0] = False + self._assertMultipleResults(result, elements, expected) + mg.setValueRefEnabled(True, *elements) + enabled = mg.getValueRefEnabled(*elements) + self._assertResult(enabled, elements, True) + + # Set values using the controller instead of channels + mg.setValueRefEnabled(True, "_test_2d_ctrl_1") + enabled = mg.getValueRefEnabled(*elements) + self._assertResult(enabled, elements, True) + + # Get values by controller + mg.setValueRefEnabled(False, *elements) + enabled = mg.getValueRefEnabled("_test_2d_ctrl_1") + self._assertResult(enabled, elements, False) + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + enabled = mg.getValueRefEnabled(*full_names) + self._assertResult(enabled, elements, False) + mg.setValueRefEnabled(True, *full_names) + enabled = mg.getValueRefEnabled(*elements, ret_full_name=True) + self._assertResult(enabled, full_names, True) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def test_ValueRefPattern(self, elements=["_test_2d_1_1", "_test_2d_1_2", + "_test_ct_1_3", + "_test_mt_1_3/position"]): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + + # Check initial state of all kind of channels, nonexistent + # channels for the feature return None as result. + pattern = mg.getValueRefPattern(*elements) + expected = ['', '', None, None] + self._assertMultipleResults(pattern, elements, expected) + + # Check if the nonexistent channels raise error if trying to set + with self.assertRaises(Exception): + mg.setValueRefPattern('/tmp/test_foo.txt', *elements[-2]) + with self.assertRaises(Exception): + mg.setValueRefPattern('/tmp/test_foo.txt', *elements[-1]) + + # Redefine elements to ony use existing values + elements = ["_test_2d_1_1", "_test_2d_1_2"] + + # Test every possible combination of setting values + # Check that changing one channel doesn't affect the other + mg.setValueRefPattern('/tmp/test_foo.txt', *elements) + pattern = mg.getValueRefPattern(*elements) + self._assertResult(pattern, elements, '/tmp/test_foo.txt') + + # Set values using the controller instead of channels + mg.setValueRefPattern('/tmp/test_foo2.txt', "_test_2d_ctrl_1") + pattern = mg.getValueRefPattern(*elements) + self._assertResult(pattern, elements, '/tmp/test_foo2.txt') + + # Get values by controller + mg.setValueRefPattern('/tmp/test_foo.txt', *elements) + pattern = mg.getValueRefPattern("_test_2d_ctrl_1") + self._assertResult(pattern, elements, '/tmp/test_foo.txt') + + # Check ret_full_name + v = TangoDeviceNameValidator() + full_names = [v.getNames(element)[0] for element in elements] + pattern = mg.getValueRefPattern(*full_names) + self._assertResult(pattern, elements, '/tmp/test_foo.txt') + mg.setValueRefPattern('/tmp/test_foo2.txt', *full_names) + pattern = mg.getValueRefPattern(*elements, ret_full_name=True) + self._assertResult(pattern, full_names, '/tmp/test_foo2.txt') + + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py new file mode 100644 index 0000000000..5f751c7d4c --- /dev/null +++ b/src/sardana/taurus/core/tango/sardana/test/test_measgrpstress.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +import uuid +from unittest import TestCase + +from tango import DevState +from taurus import Device +from taurus.test.base import insertTest + +from .test_pool import is_numerical +from sardana.pool.pooldefs import AcqSynchType +from sardana.taurus.core.tango.sardana.pool import registerExtensions +from sardana.tango.pool.test.base_sartest import SarTestTestCase + + +@insertTest(helper_name="stress_count", + test_method_doc="stress count with CT (hardware trigger) and 0D", + elements=["_test_ct_1_1", "_test_0d_1_1"], repeats=100, + synchronizer="_test_tg_1_1", synchronization=AcqSynchType.Trigger) +@insertTest(helper_name="stress_count", + test_method_doc="stress count with CT (software trigger) and 0D", + elements=["_test_ct_1_1", "_test_0d_1_1"], repeats=100, + synchronizer="software", synchronization=AcqSynchType.Trigger) +@insertTest(helper_name="stress_count", + test_method_doc="stress count with CT (hardware start)", + elements=["_test_ct_1_1"], repeats=100, + synchronizer="_test_tg_1_1", synchronization=AcqSynchType.Start) +@insertTest(helper_name="stress_count", + test_method_doc="stress count with CT (software start)", + elements=["_test_ct_1_1"], repeats=100, + synchronizer="software", synchronization=AcqSynchType.Start) +@insertTest(helper_name="stress_count", + test_method_doc="stress count with CT (hardware trigger)", + elements=["_test_ct_1_1"], repeats=100, + synchronizer="_test_tg_1_1", synchronization=AcqSynchType.Trigger) +@insertTest(helper_name="stress_count", + test_method_doc="count with CT (software trigger)", + elements=["_test_ct_1_1"], repeats=100, + synchronizer="software", synchronization=AcqSynchType.Trigger) +class TestStressMeasurementGroup(SarTestTestCase, TestCase): + + def setUp(self): + SarTestTestCase.setUp(self) + registerExtensions() + + def stress_count(self, elements, repeats, synchronizer, synchronization): + mg_name = str(uuid.uuid1()) + argin = [mg_name] + elements + self.pool.CreateMeasurementGroup(argin) + try: + mg = Device(mg_name) + mg.setSynchronizer(synchronizer, elements[0], apply=False) + mg.setSynchronization(synchronization, elements[0]) + for i in range(repeats): + state, values = mg.count(.001) + self.assertEqual(state, DevState.ON, + "wrong state after measurement {}".format(i)) + for channel_name, value in values.items(): + msg = ("Value {} for {} is not numerical in " + "measurement {}").format(value, channel_name, i) + self.assertTrue(is_numerical(value), msg) + finally: + mg.cleanUp() + self.pool.DeleteElement(mg_name) + + def tearDown(self): + SarTestTestCase.tearDown(self) diff --git a/src/sardana/taurus/core/tango/sardana/test/test_pool.py b/src/sardana/taurus/core/tango/sardana/test/test_pool.py index 3713df2c0e..292b4ef2aa 100644 --- a/src/sardana/taurus/core/tango/sardana/test/test_pool.py +++ b/src/sardana/taurus/core/tango/sardana/test/test_pool.py @@ -28,7 +28,7 @@ import numpy from taurus import Device -from taurus.external.unittest import TestCase +from unittest import TestCase from taurus.test.base import insertTest from sardana.sardanautils import is_number, is_non_str_seq, is_pure_str from sardana.taurus.core.tango.sardana.pool import registerExtensions @@ -72,7 +72,7 @@ def count(self, elements): try: mg = Device(mg_name) _, values = mg.count(.1) - for channel_name, value in values.iteritems(): + for channel_name, value in values.items(): msg = "Value (%s) for %s is not numerical" % \ (value, channel_name) self.assertTrue(is_numerical(value), msg) @@ -84,15 +84,6 @@ def tearDown(self): SarTestTestCase.tearDown(self) -def _set_value_ref_enabled(conf, channel, value_ref_enabled): - ctrl = channel.getControllerObj() - ctrl_full_name = ctrl.getFullName() - channel_full_name = channel.getFullName() - ctrl_conf = conf["controllers"][ctrl_full_name] - channel_conf = ctrl_conf["channels"][channel_full_name] - channel_conf["value_ref_enabled"] = value_ref_enabled - - class TestMeasurementGroupValueRef(SarTestTestCase, TestCase): def setUp(self): @@ -107,11 +98,9 @@ def test_value_ref_enabled(self): try: mg = Device(mg_name) channel = Device(channel_name) - conf = mg.getConfiguration().raw_data - _set_value_ref_enabled(conf, channel, True) - mg.setConfiguration(conf) + mg.setValueRefEnabled(True, channel_name) _, values = mg.count(.1) - for channel_name, value in values.iteritems(): + for channel_name, value in values.items(): msg = "ValueRef (%s) for %s is not string" %\ (value, channel_name) self.assertTrue(is_pure_str(value), msg) @@ -128,11 +117,9 @@ def test_value_ref_disabled(self): try: mg = Device(mg_name) channel = Device(channel_name) - conf = mg.getConfiguration().raw_data - _set_value_ref_enabled(conf, channel, False) - mg.setConfiguration(conf) + mg.setValueRefEnabled(False, channel_name) _, values = mg.count(.1) - for channel_name, value in values.iteritems(): + for channel_name, value in values.items(): msg = "Value (%s) for %s is not numerical" %\ (value, channel_name) self.assertTrue(is_numerical(value), msg) diff --git a/src/sardana/taurus/qt/qtcore/tango/sardana/__init__.py b/src/sardana/taurus/qt/qtcore/tango/sardana/__init__.py index 5fe1959e5e..243769f157 100644 --- a/src/sardana/taurus/qt/qtcore/tango/sardana/__init__.py +++ b/src/sardana/taurus/qt/qtcore/tango/sardana/__init__.py @@ -23,8 +23,30 @@ ## ############################################################################## -""" -Sardana extension for taurus Qt +"""Taurus Qt extensions for Sardana devices. + +Objects obtained with :func:`taurus.Device` expose standard interfaces +e.g., allow to interact with their attributes, check their state, etc. +This module defines classes for enriched interaction with Sardana devices +(also for other elements not exported as devices), e.g. synchronous move +of a sardana motor with +:meth:`~sardana.taurus.core.tango.sardana.pool.Motor.move` +method instead of writing motor's position attribute and then waiting for its +state change. The difference between these classes with respect to the ones +from the :mod:`sardana.taurus.core.tango.sardana` module is the Qt friendly +interface e.g. the Sardana events are translated to Qt signals. + +To obtain these enriched objects with :func:`taurus.Device` you need to first +register the extension classes with +the :obj:`~sardana.taurus.qt.qtcore.tango.sardana.registerExtensions` function. + +The registration needs to be done before the first access to the given +:func:`taurus.Device`. + +.. note:: If you are using + :class:`~taurus.qt.qtgui.application.TaurusApplication` + then the registration is done behind the scene at the moment of + :class:`~taurus.qt.qtgui.application.TaurusApplication` construction. """ __docformat__ = 'restructuredtext' diff --git a/src/sardana/taurus/qt/qtcore/tango/sardana/macroserver.py b/src/sardana/taurus/qt/qtcore/tango/sardana/macroserver.py index 706d96594c..d32ecdcef8 100644 --- a/src/sardana/taurus/qt/qtcore/tango/sardana/macroserver.py +++ b/src/sardana/taurus/qt/qtcore/tango/sardana/macroserver.py @@ -30,7 +30,7 @@ import copy from taurus.core.taurusbasetypes import TaurusEventType -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from sardana.taurus.core.tango.sardana.macroserver import BaseMacroServer, \ BaseDoor @@ -49,15 +49,15 @@ class QDoor(BaseDoor, Qt.QObject): # sometimes we emit None hence the type is object # (but most of the data are passed with type list) - resultUpdated = Qt.pyqtSignal(object) - recordDataUpdated = Qt.pyqtSignal(object) - macroStatusUpdated = Qt.pyqtSignal(object) - errorUpdated = Qt.pyqtSignal(object) - warningUpdated = Qt.pyqtSignal(object) - infoUpdated = Qt.pyqtSignal(object) - outputUpdated = Qt.pyqtSignal(object) - debugUpdated = Qt.pyqtSignal(object) - experimentConfigurationChanged = Qt.pyqtSignal(object) + resultUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + recordDataUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + macroStatusUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + errorUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + warningUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + infoUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + outputUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + debugUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + experimentConfigurationChanged = Qt.pyqtSignal(compat.PY_OBJECT) elementsChanged = Qt.pyqtSignal() environmentChanged = Qt.pyqtSignal() @@ -95,8 +95,8 @@ def macroStatusReceived(self, s, t, v): def logReceived(self, log_name, output): res = BaseDoor.logReceived(self, log_name, output) log_name = log_name.lower() - recordDataUpdated = getattr(self, "%sUpdated" % log_name) - recordDataUpdated.emit(output) + logUpdated = getattr(self, "%sUpdated" % log_name) + logUpdated.emit(output) return res def _prepare_connections(self): @@ -113,7 +113,7 @@ def _elementsChanged(self): # one or more measurement group was deleted mntgrp_changed = len(self._mntgrps_connected) > len(mntgrps) new_mntgrps_connected = [] - for name, mg in mntgrps.items(): + for name, mg in list(mntgrps.items()): if name not in self._mntgrps_connected: mntgrp_changed = True # this measurement group is new obj = mg.getObj() @@ -172,7 +172,7 @@ class QMacroServer(BaseMacroServer, Qt.QObject): elementsUpdated = Qt.pyqtSignal() elementsChanged = Qt.pyqtSignal() macrosUpdated = Qt.pyqtSignal() - environmentChanged = Qt.pyqtSignal(object) + environmentChanged = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, name, qt_parent=None, **kw): self.call__init__wo_kw(Qt.QObject, qt_parent) diff --git a/src/sardana/taurus/qt/qtcore/tango/sardana/model.py b/src/sardana/taurus/qt/qtcore/tango/sardana/model.py index 0719bea8c1..9cbdc9eb4a 100644 --- a/src/sardana/taurus/qt/qtcore/tango/sardana/model.py +++ b/src/sardana/taurus/qt/qtcore/tango/sardana/model.py @@ -42,15 +42,16 @@ except: pygments = None -from taurus.core.taurusdevice import TaurusDevice from taurus.external.qt import Qt -from taurus.core.util.enumeration import Enumeration from taurus.qt.qtcore.model import TaurusBaseTreeItem, TaurusBaseModel, \ TaurusBaseProxyModel from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE, \ TAURUS_MODEL_MIME_TYPE -_MOD, _CLS, _FNC, _TNG = ":/python-module.png", ":/class.png", ":/function.png", ":/tango.png" +_MOD = ":python-module.png" +_CLS = ":class.png" +_FNC = ":function.png" +_TNG = ":tango.png" TYPE_MAP = { "ControllerLibrary": ("Controller libraries", _MOD, "Controller library",), @@ -78,9 +79,8 @@ def getElementTypeLabel(t): def getElementTypeIcon(t): - import taurus.qt.qtgui.resource try: - return taurus.qt.qtgui.resource.getIcon(TYPE_MAP.get(t, (None, _TNG))[1]) + return Qt.QIcon(TYPE_MAP.get(t, (None, _TNG))[1]) except: return None @@ -231,11 +231,11 @@ def mimeData(self, indexes): mime_data_item = tree_item.mimeData(index) if mime_data_item is None: continue - data.append(mime_data_item) - ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, "\r\n".join(data)) - ret.setText(", ".join(data)) + data.append(bytes(mime_data_item, encoding='utf8')) + ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, b"\r\n".join(data)) + ret.setText(", ".join(map(str, data))) if len(data) == 1: - ret.setData(TAURUS_MODEL_MIME_TYPE, str(data[0])) + ret.setData(TAURUS_MODEL_MIME_TYPE, data[0]) return ret def accept(self, element): @@ -442,11 +442,11 @@ def mimeData(self, indexes): mime_data_item = tree_item.mimeData(index) if mime_data_item is None: continue - data.append(mime_data_item) - ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, "\r\n".join(data)) - ret.setText(", ".join(data)) + data.append(bytes(mime_data_item, encoding='utf8')) + ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, b"\r\n".join(data)) + ret.setText(", ".join(map(str, data))) if len(data) == 1: - ret.setData(TAURUS_MODEL_MIME_TYPE, str(data[0])) + ret.setData(TAURUS_MODEL_MIME_TYPE, data[0]) return ret def accept(self, environment): @@ -460,7 +460,7 @@ def setupModelData(self, data): env = dev.getEnvironment() root = self._rootItem - for key, value in env.items(): + for key, value in list(env.items()): if not self.accept(key): continue env_item = EnvironmentTreeItem(self, (key, value), root) diff --git a/src/sardana/taurus/qt/qtgui/extra_hkl/__init__.py b/src/sardana/taurus/qt/qtgui/extra_hkl/__init__.py index ecf90ecd97..9521c3a919 100644 --- a/src/sardana/taurus/qt/qtgui/extra_hkl/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_hkl/__init__.py @@ -27,6 +27,6 @@ __init__.py: """ -from hklscan import HKLScan -from ubmatrix import UBMatrixBase -from diffractometeralignment import DiffractometerAlignment +from .hklscan import HKLScan # noqa +from .ubmatrix import UBMatrixBase # noqa +from .diffractometeralignment import DiffractometerAlignment # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_hkl/diffractometeralignment.py b/src/sardana/taurus/qt/qtgui/extra_hkl/diffractometeralignment.py index 560e4986ba..6b704a0bbe 100644 --- a/src/sardana/taurus/qt/qtgui/extra_hkl/diffractometeralignment.py +++ b/src/sardana/taurus/qt/qtgui/extra_hkl/diffractometeralignment.py @@ -23,7 +23,6 @@ ## ############################################################################## - __docformat__ = 'restructuredtext' import sys @@ -42,15 +41,13 @@ from taurus.qt.qtcore.communication import SharedDataManager from taurus.qt.qtgui.input import TaurusValueLineEdit - import taurus.core.util.argparse import taurus.qt.qtgui.application from taurus.qt.qtgui.util.ui import UILoadable from sardana.taurus.qt.qtgui.extra_macroexecutor import TaurusMacroConfigurationDialog - -from selectsignal import SelectSignal +from .selectsignal import SelectSignal class EngineModesComboBox(Qt.QComboBox, TaurusBaseWidget): @@ -146,7 +143,7 @@ def setModel(self, model): angles_taurus_label = [] angles_taurus_input = [] - gap_x = 650 / self.nb_motors + gap_x = 650 // self.nb_motors try: self.angles_names = self.device.motorroles @@ -216,7 +213,7 @@ def setModel(self, model): tomax_functions = [self.tomax_scan1, self.tomax_scan2, self.tomax_scan3, self.tomax_scan4, self.tomax_scan5, self.tomax_scan6] - gap_x = 650 / self.nb_motors + gap_x = 650 // self.nb_motors for i in range(0, self.nb_motors): scan_buttons.append(QtGui.QPushButton(self)) @@ -353,15 +350,14 @@ def open_selectsignal_panel(self): def main(): - parser = taurus.core.util.argparse.get_taurus_parser() parser.usage = "%prog [door_name]" desc = ("a taurus application for diffractometer alignment: h, k, l " + "movements and scans, go to maximum, ...") parser.set_description(desc) - app = taurus.qt.qtgui.application.TaurusApplication(cmd_line_parser=parser, - app_version=sardana.Release.version) + app = taurus.qt.qtgui.application.TaurusApplication( + cmd_line_parser=parser, app_version=sardana.Release.version) app.setApplicationName("diffractometeralignment") args = app.get_command_line_args() if len(args) < 1: diff --git a/src/sardana/taurus/qt/qtgui/extra_hkl/hklscan.py b/src/sardana/taurus/qt/qtgui/extra_hkl/hklscan.py index 3300f8f36e..b9d9230354 100644 --- a/src/sardana/taurus/qt/qtgui/extra_hkl/hklscan.py +++ b/src/sardana/taurus/qt/qtgui/extra_hkl/hklscan.py @@ -23,8 +23,6 @@ ## ############################################################################## -__docformat__ = 'restructuredtext' - import sys import sardana @@ -39,7 +37,7 @@ from taurus.qt.qtcore.communication import SharedDataManager from taurus.qt.qtgui.input import TaurusValueLineEdit -from displayscanangles import DisplayScanAngles +from .displayscanangles import DisplayScanAngles import taurus.core.util.argparse import taurus.qt.qtgui.application @@ -50,6 +48,8 @@ TaurusMacroConfigurationDialog, \ TaurusMacroDescriptionViewer, DoorOutput, DoorDebug, DoorResult +__docformat__ = 'restructuredtext' + class EngineModesComboBox(Qt.QComboBox, TaurusBaseWidget): """ComboBox representing list of engine modes""" @@ -121,7 +121,7 @@ def setModel(self, model): angles_names = [] angles_taurus_label = [] - gap_x = 800 / self.nb_motors + gap_x = 800 // self.nb_motors try: angles_names = self.device.motorroles @@ -361,8 +361,8 @@ def main(): parser.usage = "%prog [door_name]" parser.set_description("a taurus application for performing hkl scans") - app = taurus.qt.qtgui.application.TaurusApplication(cmd_line_parser=parser, - app_version=sardana.Release.version) + app = taurus.qt.qtgui.application.TaurusApplication( + cmd_line_parser=parser, app_version=sardana.Release.version) app.setApplicationName("hklscan") args = app.get_command_line_args() if len(args) < 1: @@ -378,7 +378,8 @@ def main(): if len(args) > 1: w.onDoorChanged(args[1]) else: - print "WARNING: Not door name supplied. Connection to MacroServer/Door not automatically done" + print("WARNING: Not door name supplied. Connection to " + "MacroServer/Door not automatically done") w.show() sys.exit(app.exec_()) diff --git a/src/sardana/taurus/qt/qtgui/extra_hkl/selectsignal.py b/src/sardana/taurus/qt/qtgui/extra_hkl/selectsignal.py index 4ac80a56b5..d351b17f25 100644 --- a/src/sardana/taurus/qt/qtgui/extra_hkl/selectsignal.py +++ b/src/sardana/taurus/qt/qtgui/extra_hkl/selectsignal.py @@ -90,7 +90,7 @@ def update_signals(self, doorname=''): if self.doorName != doorname: self.doorName = doorname self.door_device = taurus.Device(self.doorName) - print "Create door_device with name " + str(self.doorName) + print("Create door_device with name " + str(self.doorName)) if self.doorName is not None: signals = [] diff --git a/src/sardana/taurus/qt/qtgui/extra_hkl/ubmatrix.py b/src/sardana/taurus/qt/qtgui/extra_hkl/ubmatrix.py index bdb8a85d3e..be0d6a4fbe 100644 --- a/src/sardana/taurus/qt/qtgui/extra_hkl/ubmatrix.py +++ b/src/sardana/taurus/qt/qtgui/extra_hkl/ubmatrix.py @@ -23,15 +23,13 @@ ## ############################################################################## -__docformat__ = 'restructuredtext' - import sys import sardana from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusWidget -from reflectionslist import ReflectionsList -from reflectionseditor import ReflectionsEditor +from .reflectionslist import ReflectionsList +from .reflectionseditor import ReflectionsEditor from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.external.qt import QtCore, QtGui @@ -47,6 +45,8 @@ global flag_update flag_update = 0 +__docformat__ = 'restructuredtext' + class PrivateComboBox(Qt.QComboBox, TaurusBaseWidget): """ComboBox""" @@ -463,14 +463,14 @@ def affine(self): def main(): - parser = taurus.core.util.argparse.get_taurus_parser() parser.usage = "%prog " parser.set_description( - "a taurus application for setting diffractometer parameters: ubmatrix, lattice, reflections, ...") + "a taurus application for setting diffractometer parameters: " + "ubmatrix, lattice, reflections, ...") - app = taurus.qt.qtgui.application.TaurusApplication(cmd_line_parser=parser, - app_version=sardana.Release.version) + app = taurus.qt.qtgui.application.TaurusApplication( + cmd_line_parser=parser, app_version=sardana.Release.version) app.setApplicationName("ubmatrix") args = app.get_command_line_args() if len(args) < 1: diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/common.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/common.py index b00db939d3..a22149c7e9 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/common.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/common.py @@ -31,7 +31,6 @@ from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.input import TaurusAttrListComboBox from taurus.qt.qtgui.container import TaurusMainWindow -from taurus.qt.qtgui.resource import getThemeIcon, getIcon def str2bool(text): @@ -109,7 +108,7 @@ def __loadMacroNames(self): if ms is None: return macros = ms.getElementsWithInterface('MacroCode') - macroNames = sorted([macro.name for macro in macros.values()]) + macroNames = sorted([macro.name for macro in list(macros.values())]) macroNames.insert(0, '') # adding blank item self.addItems(macroNames) self.updateStyle() @@ -130,13 +129,13 @@ def __init__(self, parent=None, initMacroServer=None, initDoor=None): Qt.QDialog.__init__(self, parent) self.initMacroServer = initMacroServer self.initDoor = initDoor - configureAction = Qt.QAction(getThemeIcon( + configureAction = Qt.QAction(Qt.QIcon.fromTheme( "folder-open"), "Change custom macro editors paths", self) configureAction.triggered.connect(self.onReloadMacroServers) configureAction.setToolTip("Change custom macro editors paths") configureAction.setShortcut("F11") self.refreshMacroServersAction = Qt.QAction( - getThemeIcon("view-refresh"), "Reload macroservers", self) + Qt.QIcon.fromTheme("view-refresh"), "Reload macroservers", self) self.refreshMacroServersAction.triggered.connect( self.onReloadMacroServers) self.refreshMacroServersAction.setToolTip( @@ -188,8 +187,8 @@ def accept(self): def __retriveMacroServersFromDB(self): ms_stateIcons = [] - db = taurus.Database() - macroServerList = db.getValueObj().get_device_name('*', 'MacroServer') + db = taurus.Authority() + macroServerList = db.getTangoDB().get_device_name('*', 'MacroServer') for macroServer in macroServerList: #state = Device(macroServer).getState() state = None @@ -200,11 +199,11 @@ def __retriveMacroServersFromDB(self): pass icon = None if state == PyTango.DevState.ON: - icon = getIcon(":/leds/images24/ledgreen.png") + icon = Qt.QIcon("leds_images24:ledgreen.png") elif state == PyTango.DevState.FAULT: - icon = getIcon(":/leds/images24/ledred.png") + icon = Qt.QIcon("leds_images24:ledred.png") elif state is None: - icon = getIcon(":/leds/images24/ledredoff.png") + icon = Qt.QIcon("leds_images24:ledredoff.png") ms_stateIcons.append((macroServer, icon)) return ms_stateIcons @@ -263,7 +262,7 @@ def __init__(self, parent=None, designMode=False): self.registerConfigProperty( "customMacroEditorPaths", "setCustomMacroEditorPaths", "customMacroEditorPaths") self._qDoor = None - self.setWindowIcon(getIcon(":/apps/preferences-system-session.svg")) + self.setWindowIcon(Qt.QIcon("apps:preferences-system-session.svg")) toolBar = self.basicTaurusToolbar() toolBar.setIconSize(Qt.QSize(24, 24)) self.configureAction = self.createConfigureAction() @@ -315,7 +314,7 @@ def setModel(self, model): self.setWindowTitle(Qt.QApplication.applicationName() + ": " + model) def createConfigureAction(self): - configureAction = Qt.QAction(getThemeIcon( + configureAction = Qt.QAction(Qt.QIcon.fromTheme( "preferences-system-session"), "Change configuration", self) configureAction.triggered.connect(self.changeConfiguration) configureAction.setToolTip("Configuring MacroServer and Door") @@ -323,7 +322,7 @@ def createConfigureAction(self): return configureAction def createCustomMacroEditorPathsAction(self): - configureAction = Qt.QAction(getThemeIcon( + configureAction = Qt.QAction(Qt.QIcon.fromTheme( "folder-open"), "Change custom macro editors paths", self) configureAction.triggered.connect(self.onCustomMacroEditorPaths) configureAction.setToolTip("Change custom macro editors paths") @@ -358,8 +357,10 @@ def test_macrocombobox(ms_name): if __name__ == "__main__": import sys + from taurus.core.util.argparse import get_taurus_parser from taurus.qt.qtgui.application import TaurusApplication - app = TaurusApplication() + parser = get_taurus_parser() + app = TaurusApplication(cmd_line_parser=parser) args = app.get_command_line_args() ms_name = args[0] test_macrocombobox(ms_name) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/dooroutput.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/dooroutput.py index 1a9af58a31..9ad0104b54 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/dooroutput.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/dooroutput.py @@ -31,6 +31,7 @@ from taurus.external.qt import Qt + class DoorOutput(Qt.QPlainTextEdit): """Widget used for displaying changes of door's attributes: Output, Info, Warning and Error.""" @@ -42,6 +43,10 @@ def __init__(self, parent=None): self.stopAction.setCheckable(True) self.stopAction.setChecked(False) self._isStopped = False + self.showDebug = Qt.QAction("Show debug details", self) + self.showDebug.setCheckable(True) + self.showDebug.setChecked(False) + self._isDebugging = False def onDoorOutputChanged(self, output): """call on output attribute changed""" @@ -91,6 +96,21 @@ def onDoorErrorChanged(self, error): txt += "" self.appendHtmlText(txt) + def onDoorDebugChanged(self, debug): + """call on debug attribute changed""" + txt = "" + if self._isDebugging: + if debug is None: + return + for i, line in enumerate(debug): + if i > 0: + txt += "
" + txt += line.replace(' ', ' ') + txt += "
" + self.appendHtmlText(txt) + if not self._isStopped: + self.moveCursor(Qt.QTextCursor.End) + def appendHtmlText(self, text): self.appendHtml(text) if not self._isStopped: @@ -101,18 +121,25 @@ def contextMenuEvent(self, event): clearAction = Qt.QAction("Clear", menu) menu.addAction(clearAction) menu.addAction(self.stopAction) + menu.addAction(self.showDebug) if not len(self.toPlainText()): clearAction.setEnabled(False) clearAction.triggered.connect(self.clear) self.stopAction.toggled.connect(self.stopScrolling) + self.showDebug.toggled.connect(self.showDebugDetails) menu.exec_(event.globalPos()) def stopScrolling(self, stop): self._isStopped = stop + def showDebugDetails(self, debug): + self._isDebugging = debug + class DoorDebug(Qt.QPlainTextEdit): + """Deprecated. Do not use""" + """Widget used for displaying changes of door's Debug attribute.""" def __init__(self, parent=None): @@ -124,6 +151,12 @@ def __init__(self, parent=None): self.stopAction.setChecked(False) self._isStopped = False + from taurus.core.util.log import warning + + msg = ("DoorDebug is deprecated since version 3.0.3. " + "Use DoorOutput 'Show debug details' feature instead.") + warning(msg) + def onDoorDebugChanged(self, debug): """call on debug attribute changed""" if debug is None: @@ -177,45 +210,14 @@ def contextMenuEvent(self, event): menu.exec_(event.globalPos()) -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- -# Door attributes listeners -#-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - -class DoorAttrListener(Qt.QObject): - """Deprecated. Do not use""" - - def __init__(self, attrName): - Qt.QObject.__init__(self) - from taurus.core.util.log import deprecated - deprecated(dep="DoorAttrListener", rel="2.5.1") - self.attrName = attrName - self.attrObj = None - - def setDoorName(self, doorName): - if not self.attrObj is None: - self.attrObj.removeListener(self) - self.attrObj = taurus.Attribute(doorName, self.attrName) - self.attrObj.addListener(self) - - def eventReceived(self, src, type, value): - if (type == taurus.core.taurusbasetypes.TaurusEventType.Error or - type == taurus.core.taurusbasetypes.TaurusEventType.Config): - return - - # The old code (using old-style signals) emitted a signal called - # doorChanged . Emulating this with new-style signasl - # is problematic, and in this case it is not worth it since this class - # is unused, deprecated and will disappear soon - # self.emit(Qt.SIGNAL('door%sChanged' % self.attrName), value.value) - - - if __name__ == "__main__": import sys import taurus + from taurus.core.util.argparse import get_taurus_parser from taurus.qt.qtgui.application import TaurusApplication - app = TaurusApplication(sys.argv) + parser = get_taurus_parser() + app = TaurusApplication(sys.argv, cmd_line_parser=parser) args = app.get_command_line_args() doorOutput = DoorOutput() @@ -225,6 +227,7 @@ def eventReceived(self, src, type, value): door.infoUpdated.connect(doorOutput.onDoorInfoChanged) door.warningUpdated.connect(doorOutput.onDoorWarningChanged) door.errorUpdated.connect(doorOutput.onDoorErrorChanged) + door.debugUpdated.connect(doorOutput.onDoorDebugChanged) doorOutput.show() sys.exit(app.exec_()) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/__init__.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/__init__.py index 5d88d0fcce..98d39adcdb 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/__init__.py @@ -23,5 +23,5 @@ ## ############################################################################## -from favouriteseditor import FavouritesMacrosEditor -from historyviewer import HistoryMacrosViewer +from .favouriteseditor import FavouritesMacrosEditor # noqa +from .historyviewer import HistoryMacrosViewer # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/favouriteseditor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/favouriteseditor.py index d1d763811c..c63ebcfd0d 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/favouriteseditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/favouriteseditor.py @@ -28,11 +28,10 @@ """ import copy -from taurus.external.qt import Qt -from taurus.qt.qtgui.resource import getIcon +from taurus.external.qt import Qt, compat from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtcore.configuration import BaseConfigurableClass -from model import MacrosListModel +from .model import MacrosListModel class FavouritesMacrosEditor(TaurusWidget): @@ -103,34 +102,34 @@ def getQtDesignerPluginInfo(cls): class FavouritesMacrosList(Qt.QListView, BaseConfigurableClass): - favouriteSelected = Qt.pyqtSignal(object) + favouriteSelected = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, parent=None): Qt.QListView.__init__(self, parent) self.setSelectionMode(Qt.QListView.ExtendedSelection) - self.removeAction = Qt.QAction(getIcon(":/actions/list-remove.svg"), + self.removeAction = Qt.QAction(Qt.QIcon("actions:list-remove.svg"), "Remove from favourites", self) self.removeAction.triggered.connect(self.removeMacros) self.removeAction.setToolTip( "Clicking this button will remove selected macros " "from favourites.") - self.removeAllAction = Qt.QAction(getIcon(":/places/user-trash.svg"), + self.removeAllAction = Qt.QAction(Qt.QIcon("places:user-trash.svg"), "Remove all from favourites", self) self.removeAllAction.triggered.connect(self.removeAllMacros) self.removeAllAction.setToolTip( "Clicking this button will remove all macros from favourites.") - self.moveUpAction = Qt.QAction(getIcon(":/actions/go-up.svg"), + self.moveUpAction = Qt.QAction(Qt.QIcon("actions:go-up.svg"), "Move up", self) self.moveUpAction.triggered.connect(self.upMacro) self.moveUpAction.setToolTip( "Clicking this button will move the macro up " "in the favourites hierarchy.") - self.moveDownAction = Qt.QAction(getIcon(":/actions/go-down.svg"), + self.moveDownAction = Qt.QAction(Qt.QIcon("actions:go-down.svg"), "Move down", self) self.moveDownAction.triggered.connect(self.downMacro) self.moveDownAction.setToolTip( @@ -221,9 +220,11 @@ def test(): import sys import taurus import time + from taurus.core.util.argparse import get_taurus_parser from taurus.qt.qtgui.application import TaurusApplication - app = TaurusApplication(sys.argv) + parser = get_taurus_parser() + app = TaurusApplication(sys.argv, cmd_line_parser=parser) favouritesEditor = FavouritesMacrosEditor() diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/historyviewer.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/historyviewer.py index 4f2a3406ec..0ac3c9d730 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/historyviewer.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/historyviewer.py @@ -28,11 +28,11 @@ """ import copy -from taurus.external.qt import Qt -from taurus.qt.qtgui.resource import getIcon +from sardana import sardanacustomsettings +from taurus.external.qt import Qt, compat from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtcore.configuration import BaseConfigurableClass -from model import MacrosListModel +from .model import MacrosListModel class HistoryMacrosViewer(TaurusWidget): @@ -50,6 +50,10 @@ def initComponents(self): self.list = HistoryMacrosList(self) self._model = MacrosListModel() + max_history = getattr(sardanacustomsettings, + "MACROEXECUTOR_MAX_HISTORY", + None) + self._model.setMaxLen(max_history) self.list.setModel(self._model) # self.registerConfigDelegate(self.list) @@ -114,13 +118,13 @@ def getQtDesignerPluginInfo(cls): class HistoryMacrosList(Qt.QListView, BaseConfigurableClass): - historySelected = Qt.pyqtSignal(object) + historySelected = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, parent=None): Qt.QListView.__init__(self, parent) self.setSelectionMode(Qt.QListView.SingleSelection) - self.removeAllAction = Qt.QAction(getIcon(":/places/user-trash.svg"), + self.removeAllAction = Qt.QAction(Qt.QIcon("places:user-trash.svg"), "Remove all from history", self) self.removeAllAction.triggered.connect(self.removeAllMacros) self.removeAllAction.setToolTip( @@ -170,9 +174,11 @@ def test(): import sys import taurus import time + from taurus.core.util.argparse import get_taurus_parser from taurus.qt.qtgui.application import TaurusApplication - app = TaurusApplication(sys.argv) + parser = get_taurus_parser() + app = TaurusApplication(sys.argv, cmd_line_parser=parser) historyViewer = HistoryMacrosViewer() diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/model.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/model.py index 449cb30681..cdf69dd8ff 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/model.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/favouriteseditor/model.py @@ -38,6 +38,10 @@ class MacrosListModel(Qt.QAbstractListModel): def __init__(self, parent=None): Qt.QAbstractListModel.__init__(self, parent) self.list = [] + self._max_len = None + + def setMaxLen(self, max_len): + self._max_len = max_len def rowCount(self, parent=Qt.QModelIndex()): return len(self.list) @@ -57,6 +61,8 @@ def index(self, row, column=0, parent=Qt.QModelIndex()): def insertRow(self, macroNode, row=0): self.beginInsertRows(Qt.QModelIndex(), row, row) + if self._max_len is not None and len(self.list) == self._max_len: + self.list.pop() self.list.insert(row, macroNode) self.endInsertRows() return self.index(row) @@ -92,13 +98,16 @@ def toXmlString(self, pretty=False): for macroNode in self.list: listElement.append(macroNode.toXml(withId=False)) xmlTree = etree.ElementTree(listElement) - xmlString = etree.tostring(xmlTree, pretty_print=pretty) + xmlString = etree.tostring(xmlTree, encoding='unicode', + pretty_print=pretty) return xmlString def fromXmlString(self, xmlString): self.beginResetModel() listElement = etree.fromstring(xmlString) for childElement in listElement.iterchildren("macro"): + if self._max_len is not None and len(self.list) >= self._max_len: + break macroNode = macro.MacroNode() macroNode.fromXml(childElement) self.list.append(macroNode) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py index 3eb1bc7749..d4f73f08f3 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macrobutton.py @@ -37,7 +37,7 @@ import taurus from taurus.core import TaurusEventType, TaurusDevice -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.button import TaurusCommandButton @@ -55,7 +55,7 @@ class DoorStateListener(Qt.QObject): __pyqtSignals__ = ["doorStateChanged"] - doorStateChanged = Qt.pyqtSignal(object) + doorStateChanged = Qt.pyqtSignal(compat.PY_OBJECT) def eventReceived(self, evt_src, evt_type, evt_value): if evt_type not in (TaurusEventType.Change, TaurusEventType.Periodic): @@ -76,8 +76,8 @@ class MacroButton(TaurusWidget): __pyqtSignals__ = ['statusUpdated', 'resultUpdated'] - statusUpdated = Qt.pyqtSignal(object) - resultUpdated = Qt.pyqtSignal(object) + statusUpdated = Qt.pyqtSignal(compat.PY_OBJECT) + resultUpdated = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, parent=None, designMode=False): TaurusWidget.__init__(self, parent, designMode) @@ -100,11 +100,6 @@ def __init__(self, parent=None, designMode=False): def handleEvent(self, evt_src, evt_type, evt_value): pass - def toggleProgress(self, visible): - '''deprecated''' - self.warning('toggleProgress is deprecated. Use showProgress') - self.showProgress(visible) - def showProgress(self, visible): '''Set whether the progress bar is shown @@ -247,29 +242,6 @@ def updateMacroArgument(self, index, value): # update tooltip self.setToolTip(self.macro_name + ' ' + ' '.join(self.macro_args)) - def updateMacroArgumentFromSignal(self, index, obj, signal): - '''deprecated''' - msg = 'updateMacroArgumentFromSignal is deprecated. connectArgEditors' - self.warning(msg) - self.connect(obj, signal, - functools.partial(self.updateMacroArgument, index)) - - def connectArgEditors(self, signals): - """ - Associate signals to argument changes. - - :param signals: (seq) An ordered sequence of signals - """ - - for i, signal in enumerate(signals): - if not self.__isSignal(signal): - # bck-compat: (sender, sig) tuples used instead of pyqtsignals - sender, sig = signal - self.deprecated(dep='Passing (sender, signature) tuples', - alt='pyqtSignal objects', rel='2.5.1') - signal = getattr(sender, sig.split('(')[0]) - signal.connect(functools.partial(self.updateMacroArgument, i)) - @staticmethod def __isSignal(obj): if not hasattr(obj, 'emit'): @@ -300,7 +272,7 @@ def runMacro(self): sec_xml = self.door.getRunningXML() # get the id of the current running macro self.macro_id = sec_xml[0].get("id") - except Exception, e: + except Exception as e: self.ui.button.setChecked(False) raise e @@ -382,7 +354,8 @@ def abort(self): parser.set_description("Macro button for macro execution") app = TaurusApplication(app_name="macrobutton", - app_version=taurus.Release.version) + app_version=taurus.Release.version, + cmd_line_parser=parser) args = app.get_command_line_args() @@ -415,7 +388,7 @@ def update_result(self, result): def toggle_progress(self, toggle): visible = self.show_progress.isChecked() - self.mb.toggleProgress(visible or toggle) + self.mb.showProgress(visible or toggle) def getMacroInfo(self, macro_name): @@ -423,7 +396,7 @@ def getMacroInfo(self, macro_name): try: pars = door.macro_server.getMacroInfoObj(macro_name).parameters except AttributeError as e: - print "Macro %s does not exists!" % macro_name + print("Macro %s does not exists!" % macro_name) return None param_names = [] @@ -468,7 +441,9 @@ def create_layout(self, macro_name): _argEditors.append(self.argEdit) for e, v in zip(_argEditors, d_values): - e.setText(v) + if v is None: + continue + e.setText(str(v)) # Create bottom layout self.mb = MacroButton() @@ -493,9 +468,9 @@ def create_layout(self, macro_name): # Toggle progressbar self.show_progress.stateChanged.connect(self.toggle_progress) # connect the argument editors - # signals = [(e, 'textChanged(QString)') for e in _argEditors] - signals = [getattr(e, 'textChanged') for e in _argEditors] - self.mb.connectArgEditors(signals) + for i, editor in enumerate(_argEditors): + slot = functools.partial(self.mb.updateMacroArgument, i) + editor.textChanged.connect(slot) self.setLayout(Qt.QVBoxLayout()) self.layout().addWidget(self.w_arg) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroeditor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroeditor.py index 042eed69e3..74d88efd7c 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroeditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroeditor.py @@ -32,8 +32,6 @@ raise ImportError('MacroEditor requires Qsci (qscintilla): %r', e) -from taurus.qt.qtgui.resource import getThemeIcon - class MacroEditor(Qsci.QsciScintilla): __pyqtSignals__ = ("modelChanged(const QString &)",) @@ -54,36 +52,39 @@ def __init__(self, parent=None, designMode=False): self.pythonLexer.setAPIs(self.api) self.textEdit.setLexer(self.pythonLexer) - self.newAction = Qt.QAction(getThemeIcon("document-new"), "New", self) + self.newAction = Qt.QAction(Qt.QIcon.fromTheme("document-new"), "New", + self) self.newAction.triggered.connect(self.newFile) self.newAction.setToolTip("Create new file") self.newAction.setShortcut("Ctrl+N") self.openAction = Qt.QAction( - getThemeIcon("document-open"), "Open", self) + Qt.QIcon.fromTheme("document-open"), "Open", self) self.openAction.triggered.connect(self.openFile) self.openAction.setToolTip("Open existing file") self.openAction.setShortcut("Ctrl+O") self.saveAction = Qt.QAction( - getThemeIcon("document-save"), "Save", self) + Qt.QIcon.fromTheme("document-save"), "Save", self) self.saveAction.triggered.connect(self.saveFile) self.saveAction.setToolTip("Save document to disk") self.saveAction.setShortcut("Ctrl+S") - self.saveAsAction = Qt.QAction(getThemeIcon( + self.saveAsAction = Qt.QAction(Qt.QIcon.fromTheme( "document-save-as"), "Save as...", self) self.saveAction.triggered.connect(self.saveFile) self.saveAsAction.setToolTip("Save document under a new name") - self.cutAction = Qt.QAction(getThemeIcon("edit-cut"), "Cut", self) + self.cutAction = Qt.QAction(Qt.QIcon.fromTheme("edit-cut"), "Cut", + self) self.cutAction.triggered.connect(self.cut) self.cutAction.setToolTip( "Cut current selection's contents to the clipboard") self.cutAction.setShortcut("Ctrl+X") self.cutAction.setEnabled(False) - self.copyAction = Qt.QAction(getThemeIcon("edit-copy"), "Copy", self) + self.copyAction = Qt.QAction(Qt.QIcon.fromTheme("edit-copy"), "Copy", + self) self.copyAction.triggered.connect(self.copy) self.copyAction.setToolTip( "Copy current selection's contents to the clipboard") @@ -91,7 +92,7 @@ def __init__(self, parent=None, designMode=False): self.copyAction.setEnabled(False) self.pasteAction = Qt.QAction( - getThemeIcon("edit-paste"), "Paste", self) + Qt.QIcon.fromTheme("edit-paste"), "Paste", self) self.pasteAction.triggered.connect(self.paste) self.pasteAction.setToolTip( "Paste the clipboard's contents into the current selection") diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py index 7bfeee426d..e1a4193a57 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroexecutor.py @@ -28,16 +28,16 @@ """ import sys +import pickle from copy import deepcopy import PyTango -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus import Device from taurus.qt.qtgui.container import TaurusWidget, TaurusMainWindow, TaurusBaseContainer from taurus.qt.qtgui.display import TaurusLed from taurus.qt.qtgui.dialog import TaurusMessageBox -from taurus.qt.qtgui.resource import getIcon, getThemeIcon import sardana from sardana.taurus.core.tango.sardana import macro @@ -137,12 +137,16 @@ def onDataChanged(self, idx): self.setCommand() def setModel(self, model): - enable = bool(model) - self.disableEditMode = not enable - self.setEnabled(enable) - self._model = model - self._model.dataChanged.connect(self.onDataChanged) - self._model.modelReset.connect(self.setCommand) + if isinstance(model, Qt.QAbstractItemModel): + enable = bool(model) + self.disableEditMode = not enable + self.setEnabled(enable) + self._model = model + self._model.dataChanged.connect(self.onDataChanged) + self._model.modelReset.connect(self.setCommand) + else: + TaurusBaseContainer.setModel(self, model) + def model(self): return self._model @@ -251,7 +255,7 @@ def validateAllExpresion(self, secValidation=False): if self.disableEditMode: self.updateMacroEditor(mlist[0]) raise Exception(e) - message = e[0] + message = e.args[0] #raise Exception(e) problems.append(message) @@ -265,17 +269,7 @@ def validateAllExpresion(self, secValidation=False): self.currentIndex = ix counter = 1 - # Get the parameters information to check if there are optional - # paramters - ms_obj = self.getModelObj() - macro_obj = ms_obj.getElementInfo(mlist[0]) - macro_params_info = None - if macro_obj is not None: - macro_params_info = macro_obj.parameters - while not ix == Qt.QModelIndex(): - if macro_params_info is None: - break try: propValue = mlist[counter] try: @@ -284,20 +278,17 @@ def validateAllExpresion(self, secValidation=False): except Exception as e: self.model().setData(self.currentIndex, 'None') txt = str(ix.sibling(ix.row(), 0).data()) - message = "" + txt + " " + e[0] + message = "" + txt + " " + e.args[0] problems.append(message) except IndexError: - param_info = macro_params_info[counter-1] - # Skip validation in case of optional parameters - if param_info['default_value'] == Optional: - self.model().setData(self.currentIndex, None) - else: - txt = str(ix.sibling(ix.row(), 0).data()) - problems.append("" + txt + " is missing!") + txt = str(ix.sibling(ix.row(), 0).data()) + problems.append("" + txt + " is missing!") - data = str(ix.data()) - if data != 'None': - self.model().setData(self.currentIndex, 'None') + data = str(ix.data()) + if data != 'None': + self.model().setData(self.currentIndex, 'None') + else: + self.model().setData(self.currentIndex, None) counter += 1 ix = self.getIndex() self.currentIndex = ix @@ -534,7 +525,7 @@ def getParamItems(self, index): return None type = node.type() ms = self.getParentModelObj() - items = ms.getElementsWithInterface(type).keys() + items = list(ms.getElementsWithInterface(type).keys()) return items, type def nextValue(self, current): @@ -626,7 +617,7 @@ class TaurusMacroExecutorWidget(TaurusWidget): doorChanged = Qt.pyqtSignal('QString') macroNameChanged = Qt.pyqtSignal('QString') macroStarted = Qt.pyqtSignal('QString') - plotablesFilterChanged = Qt.pyqtSignal(object) + plotablesFilterChanged = Qt.pyqtSignal(compat.PY_OBJECT) shortMessageEmitted = Qt.pyqtSignal('QString') def __init__(self, parent=None, designMode=False): @@ -638,20 +629,21 @@ def __init__(self, parent=None, designMode=False): self.setLayout(Qt.QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) - self.addToFavouritesAction = Qt.QAction(getThemeIcon( - "software-update-available"), "Add to favourites", self) + self.addToFavouritesAction = Qt.QAction( + Qt.QIcon("status:software-update-available.svg"), + "Add to favourites", self) self.addToFavouritesAction.triggered.connect(self.onAddToFavourites) self.addToFavouritesAction.setToolTip("Add to favourites") self.stopMacroAction = Qt.QAction( - getIcon(":/actions/media_playback_stop.svg"), "Stop macro", self) + Qt.QIcon("actions:media_playback_stop.svg"), "Stop macro", self) self.stopMacroAction.triggered.connect(self.onStopMacro) self.stopMacroAction.setToolTip("Stop macro") self.pauseMacroAction = Qt.QAction( - getIcon(":/actions/media_playback_pause.svg"), "Pause macro", self) + Qt.QIcon("actions:media_playback_pause.svg"), "Pause macro", self) self.pauseMacroAction.triggered.connect(self.onPauseMacro) self.pauseMacroAction.setToolTip("Pause macro") self.playMacroAction = Qt.QAction( - getIcon(":/actions/media_playback_start.svg"), "Start macro", self) + Qt.QIcon("actions:media_playback_start.svg"), "Start macro", self) self.playMacroAction.triggered.connect(self.onPlayMacro) self.playMacroAction.setToolTip("Start macro") actionsLayout = Qt.QHBoxLayout() @@ -662,7 +654,6 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(addToFavouritsButton) self.macroComboBox = MacroComboBox(self) - self.macroComboBox.setUseParentModel(True) self.macroComboBox.setModelColumn(0) actionsLayout.addWidget(self.macroComboBox) stopMacroButton = Qt.QToolButton() @@ -694,13 +685,11 @@ def __init__(self, parent=None, designMode=False): self._favouritesBuffer = None self.favouritesMacrosEditor = FavouritesMacrosEditor(self) self.registerConfigDelegate(self.favouritesMacrosEditor) - self.favouritesMacrosEditor.setUseParentModel(True) self.favouritesMacrosEditor.setFocusPolicy(Qt.Qt.NoFocus) self._historyBuffer = None self.historyMacrosViewer = HistoryMacrosViewer(self) self.registerConfigDelegate(self.historyMacrosViewer) - self.historyMacrosViewer.setUseParentModel(True) self.historyMacrosViewer.setFocusPolicy(Qt.Qt.NoFocus) self.tabMacroListsWidget = Qt.QTabWidget(self) @@ -725,7 +714,6 @@ def __init__(self, parent=None, designMode=False): self.spockCommand = SpockCommandWidget("Spock", self) self.spockCommand.setSizePolicy( Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Minimum) - self.spockCommand.setUseParentModel(True) spockCommandLayout = Qt.QHBoxLayout() spockCommandLayout.setContentsMargins(0, 0, 0, 0) # spockCommandLayout.addWidget(spockCommandLabel) @@ -751,17 +739,13 @@ def macroId(self): def contextMenuEvent(self, event): menu = Qt.QMenu() - action = menu.addAction(getThemeIcon( - "view-refresh"), "Check door state", self.checkDoorState) + menu.addAction(Qt.QIcon.fromTheme("view-refresh"), "Check door state", + self.checkDoorState) menu.exec_(event.globalPos()) def checkDoorState(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState == PyTango.DevState.RUNNING: self.playMacroAction.setEnabled(False) self.pauseMacroAction.setEnabled(True) @@ -892,11 +876,7 @@ def onDoorChanged(self, doorName): return self.doorStateLed.setModel(self.doorName() + "/State") door = Device(doorName) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.stateObj.rvalue if doorState == PyTango.DevState.ON: self.playMacroAction.setText("Start macro") self.playMacroAction.setToolTip("Start macro") @@ -906,11 +886,7 @@ def onDoorChanged(self, doorName): def onPlayMacro(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState == PyTango.DevState.ON or doorState == PyTango.DevState.ALARM: self.setFocus() paramEditorModel = self.paramEditorModel() @@ -934,11 +910,7 @@ def onPlayMacro(self): def onStopMacro(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState in (PyTango.DevState.RUNNING, PyTango.DevState.STANDBY): door.command_inout("StopMacro") @@ -948,11 +920,7 @@ def onStopMacro(self): def onPauseMacro(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState == PyTango.DevState.RUNNING: door.command_inout("PauseMacro") @@ -1036,6 +1004,10 @@ def setModel(self, model): newModelObj = self.getModelObj() newModelObj.macrosUpdated.connect( self.macroComboBox.onMacrosUpdated) + self.macroComboBox.setModel(model) + self.favouritesMacrosEditor.setModel(model) + self.historyMacrosViewer.setModel(model) + self.spockCommand.setModel(model) @classmethod def getQtDesignerPluginInfo(cls): @@ -1053,7 +1025,9 @@ def __init__(self, parent=None, designMode=False): def initComponents(self): self.taurusMacroExecutorWidget = TaurusMacroExecutorWidget(self) self.registerConfigDelegate(self.taurusMacroExecutorWidget) - self.taurusMacroExecutorWidget.setUseParentModel(True) + self.taurusMacroExecutorWidget.setModelInConfig(True) + self.taurusMacroExecutorWidget.doorChanged.connect( + self.taurusMacroExecutorWidget.onDoorChanged) self.setCentralWidget(self.taurusMacroExecutorWidget) self.taurusMacroExecutorWidget.shortMessageEmitted.connect( self.onShortMessage) @@ -1081,6 +1055,10 @@ def onDoorChanged(self, doorName): self.taurusMacroExecutorWidget.onMacroStatusUpdated) self.taurusMacroExecutorWidget.onDoorChanged(doorName) + def setModel(self, model): + MacroExecutionWindow.setModel(self, model) + self.taurusMacroExecutorWidget.setModel(model) + @classmethod def getQtDesignerPluginInfo(cls): return None @@ -1088,7 +1066,6 @@ def getQtDesignerPluginInfo(cls): def createMacroExecutorWidget(args): macroExecutor = TaurusMacroExecutorWidget() - macroExecutor.setModelInConfig(True) macroExecutor.doorChanged.connect(macroExecutor.onDoorChanged) if len(args) == 2: macroExecutor.setModel(args[0]) @@ -1098,20 +1075,36 @@ def createMacroExecutorWidget(args): def createMacroExecutor(args): macroExecutor = TaurusMacroExecutor() - macroExecutor.setModelInConfig(True) macroExecutor.doorChanged.connect(macroExecutor.onDoorChanged) + load_settings = True if len(args) == 2: macroExecutor.setModel(args[0]) macroExecutor.doorChanged.emit(args[1]) - macroExecutor.loadSettings() + settings = macroExecutor.getQSettings() + taurus_config_raw = settings.value("TaurusConfig") + if taurus_config_raw is not None: + taurus_config = pickle.loads(taurus_config_raw.data()) + oldmodel = taurus_config['__itemConfigurations__']['model'] + if args[0] == oldmodel: + load_settings = False + if load_settings: + macroExecutor.loadSettings() return macroExecutor def main(): + from taurus.core.util import argparse from taurus.qt.qtgui.application import TaurusApplication - import taurus - app = TaurusApplication(sys.argv, app_version=sardana.Release.version) + parser = argparse.get_taurus_parser() + parser.set_usage("%prog [options]") + parser.set_description("Sardana macro executor.\n" + "It allows execution of macros, keeping history " + "of previous executions and favourites.") + app = TaurusApplication(sys.argv, + cmd_line_parser=parser, + app_name="macroexecutor", + app_version=sardana.Release.version) args = app.get_command_line_args() app.setOrganizationName("Taurus") diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/__init__.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/__init__.py index fe70da9c51..5aa02a0610 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/__init__.py @@ -26,5 +26,6 @@ """ __init__.py: """ -from macroparameterseditor import ParamEditorManager, StandardMacroParametersEditor -from model import ParamEditorModel +from .macroparameterseditor import (ParamEditorManager, # noqa + StandardMacroParametersEditor) # noqa +from .model import ParamEditorModel # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/__init__.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/__init__.py index c470b50746..b9b532224b 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/__init__.py @@ -23,4 +23,4 @@ ## ############################################################################## -from senv import SenvEditor +from .senv import SenvEditor # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/senv.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/senv.py index 0ad5dfa871..929cdd3b90 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/senv.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/customeditors/senv.py @@ -26,10 +26,8 @@ from taurus.external.qt import Qt from taurus import Database from taurus.core.taurusbasetypes import TaurusElementType -from taurus.core.taurusdatabase import TaurusAttrInfo -from taurus.qt.qtgui.input import TaurusAttrListComboBox +from taurus.core.tango.tangodatabase import TangoAttrInfo from taurus.qt.qtgui.tree import TaurusDbTreeWidget -from taurus.qt.qtgui.resource import getThemeIcon from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.macroparameterseditor import MacroParametersEditor from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.parameditors import LineEditParam, ParamBase, ComboBoxParam, CheckBoxParam, DirPathParam, MSAttrListComboBoxParam from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.model import ParamEditorModel @@ -90,6 +88,9 @@ def onNameComboBoxChanged(self, index): self.valueWidget = None self.valueWidget, label = getSenvValueEditor(text, self) + if text == "ActiveMntGrp": + self.valueWidget.setModel(self.model()) + self.valueWidget.setModel("/MeasurementGroupList") paramRepeatIndex = self.model().index(1, 0, self.rootIndex()) repeatIndex = paramRepeatIndex.child(0, 0) @@ -110,8 +111,6 @@ def getSenvValueEditor(envName, parent): label = "value:" if envName == "ActiveMntGrp": editor = MSAttrListComboBoxParam(parent) - editor.setUseParentModel(True) - editor.setModel("/MeasurementGroupList") elif envName == "ExtraColumns": editor = ExtraColumnsEditor(parent) label = None @@ -135,9 +134,9 @@ def __init__(self, parent=None, paramModel=None): self.layout().setContentsMargins(0, 0, 0, 0) addNewColumnButton = Qt.QPushButton( - getThemeIcon("list-add"), "Add new column...", self) + Qt.QIcon.fromTheme("list-add"), "Add new column...", self) removeSelectedColumnsButton = Qt.QPushButton( - getThemeIcon("list-remove"), "Remove selected...", self) + Qt.QIcon.fromTheme("list-remove"), "Remove selected...", self) buttonsLayout = Qt.QHBoxLayout() buttonsLayout.addWidget(addNewColumnButton) buttonsLayout.addWidget(removeSelectedColumnsButton) @@ -223,7 +222,7 @@ def createEditor(self, parent, option, index): editor.setView(treeView) elif index.column() == 2: editor = MSAttrListComboBox(parent) - editor.setUseParentModel(True) + editor.setModel(index.model()) editor.setModel("/InstrumentList") else: editor = Qt.QItemDelegate.createEditor(self, parent, option, index) @@ -244,7 +243,7 @@ def setModelData(self, editor, model, index): return taurusTreeAttributeItem = selectedItems[0] itemData = taurusTreeAttributeItem.itemData() - if isinstance(itemData, TaurusAttrInfo): + if isinstance(itemData, TangoAttrInfo): model.setData(index, itemData.fullName()) elif column == 2: model.setData(index, editor.currentText()) @@ -385,9 +384,12 @@ def removeRow(self, row, parentIndex=None): if __name__ == "__main__": import sys import taurus + from taurus.core.util.argparse import get_taurus_parser from taurus.qt.qtgui.application import TaurusApplication + from sardana.taurus.core.tango.sardana.macro import MacroNode - app = TaurusApplication(sys.argv) + parser = get_taurus_parser() + app = TaurusApplication(sys.argv, cmd_line_parser=parser) args = app.get_command_line_args() editor = SenvEditor() macroServer = taurus.Device(args[0]) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/macroparameterseditor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/macroparameterseditor.py index de58b3125c..1d62c27983 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/macroparameterseditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/macroparameterseditor.py @@ -27,12 +27,10 @@ macroparameterseditor.py: """ import sys -import inspect import glob from taurus.external.qt import Qt from taurus.core.util.singleton import Singleton -from taurus.qt.qtgui.resource import getThemeIcon from sardana.taurus.core.tango.sardana import macro from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.delegate import ParamEditorDelegate @@ -116,30 +114,31 @@ def __init__(self, parent=None, designMode=False): # self.setTabKeyNavigation(True) self.setEditTriggers(Qt.QAbstractItemView.AllEditTriggers) - self.addAction = Qt.QAction(getThemeIcon( + self.addAction = Qt.QAction(Qt.QIcon.fromTheme( "list-add"), "Add new repetition", self) self.addAction.triggered.connect(self.onAddRepeat) self.addAction.setToolTip( "Clicking this button will add new repetition to current parameter.") - self.deleteAction = Qt.QAction(getThemeIcon( + self.deleteAction = Qt.QAction(Qt.QIcon.fromTheme( "list-remove"), "Remove repetition", self) self.deleteAction.triggered.connect(self.onDelRepeat) self.deleteAction.setToolTip( "Clicking this button will remove current repetition.") - self.moveUpAction = Qt.QAction(getThemeIcon("go-up"), "Move up", self) + self.moveUpAction = Qt.QAction(Qt.QIcon.fromTheme("go-up"), "Move up", + self) self.moveUpAction.triggered.connect(self.onUpRepeat) self.moveUpAction.setToolTip( "Clicking this button will move current repetition up.") self.moveDownAction = Qt.QAction( - getThemeIcon("go-down"), "Move down", self) + Qt.QIcon.fromTheme("go-down"), "Move down", self) self.moveDownAction.triggered.connect(self.onDownRepeat) self.moveDownAction.setToolTip( "Clicking this button will move current repetition down.") - self.duplicateAction = Qt.QAction(getThemeIcon("edit-copy"), + self.duplicateAction = Qt.QAction(Qt.QIcon.fromTheme("edit-copy"), "Duplicate", self) self.duplicateAction.triggered.connect(self.onDuplicateRepeat) msg = "Clicking this button will duplicate the given node." diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/model.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/model.py index 6cf78837f8..a31b2a3abe 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/model.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/macroparameterseditor/model.py @@ -34,6 +34,7 @@ from sardana.taurus.core.tango.sardana import macro from sardana.taurus.qt.qtgui.extra_macroexecutor import globals +from sardana.macroserver.msparameter import Optional class ParamEditorModel(Qt.QAbstractItemModel): @@ -181,7 +182,13 @@ def data(self, index, role): if index.column() == 0: return node.name() elif index.column() == 1: - return str(node.value()) + value = node.value() + if value is None: + value = node.defValue() + if value == Optional: + # TODO: treat optional parameters + value = None + return str(value) return None def setData(self, index, value, role=Qt.Qt.EditRole): @@ -245,4 +252,4 @@ def toXmlString(self): """ xmlElement = self.root().toXml() - return etree.tostring(xmlElement) + return etree.tostring(xmlElement, encoding='unicode') diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/scanplotter.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/scanplotter.py index 778cfb5a5c..450150a73a 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/scanplotter.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/scanplotter.py @@ -45,7 +45,6 @@ class ScanPlotter(TaurusTrend): def __init__(self, parent=None, designMode=False): TaurusTrend.__init__(self, parent, designMode) - self.setUseParentModel(False) self._plotables = CaselessDict() self._movingMotors = [] self._macroNames = [] diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/__init__.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/__init__.py index ce4abc642c..20d6a8932a 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/__init__.py @@ -27,4 +27,4 @@ __init__.py: """ -from sequenceeditor import TaurusSequencer, TaurusSequencerWidget, main +from .sequenceeditor import TaurusSequencer, TaurusSequencerWidget, main # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/delegate.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/delegate.py index 6880180b01..3210756773 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/delegate.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/delegate.py @@ -90,7 +90,7 @@ def createEditor(self, parent, option, index): paramType = node.type() if paramType in globals.EDITOR_COMBOBOX_PARAMS: comboBox = AttrListComboBoxParam(parent, node) - comboBox.setUseParentModel(True) + comboBox.setModel(index.model()) return comboBox elif paramType in globals.EDITOR_SPINBOX_PARAMS: return SpinBoxParam(parent, node) diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/model.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/model.py index 61e858175d..7055fce8d8 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/model.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/model.py @@ -239,7 +239,8 @@ def nodeFromIndex(self, index): def toXmlString(self, pretty=False, withId=True): xmlSequence = self.root().toXml(withId=withId) xmlTree = etree.ElementTree(xmlSequence) - xmlString = etree.tostring(xmlTree, pretty_print=pretty) + xmlString = etree.tostring(xmlTree, encoding='unicode', + pretty_print=pretty) return xmlString def fromXmlString(self, xmlString): @@ -368,7 +369,7 @@ def nodeFromIndex(self, index): def createIdIndexDictionary(self): d = self.sourceModel().createIdIndexDictionary() - for id, sourceIndex in d.iteritems(): + for id, sourceIndex in d.items(): proxyIndex = self.mapFromSource(sourceIndex) d[id] = Qt.QPersistentModelIndex(proxyIndex) return d diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py index 5fcde36840..45c6feab7c 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/sequenceeditor/sequenceeditor.py @@ -28,7 +28,7 @@ """ import os import sys - +import pickle from lxml import etree import PyTango @@ -39,7 +39,6 @@ from taurus.qt.qtcore.configuration import BaseConfigurableClass from taurus.qt.qtgui.display import TaurusLed from taurus.qt.qtgui.dialog import TaurusMessageBox -from taurus.qt.qtgui.resource import getIcon, getThemeIcon import sardana from sardana.taurus.qt.qtgui.extra_macroexecutor.common import \ @@ -85,7 +84,7 @@ def onToggle(self, trueFalse): class MacroSequenceTree(Qt.QTreeView, BaseConfigurableClass): macroNameChanged = Qt.pyqtSignal('QString') - macroChanged = Qt.pyqtSignal(object) + macroChanged = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self, parent=None): Qt.QTreeView.__init__(self, parent) @@ -104,30 +103,31 @@ def __init__(self, parent=None): self.setDropIndicatorShown(True) self.deleteAction = Qt.QAction( - getThemeIcon("list-remove"), "Remove macro", self) + Qt.QIcon.fromTheme("list-remove"), "Remove macro", self) self.deleteAction.triggered.connect(self.deleteMacro) self.deleteAction.setToolTip( "Clicking this button will remove current macro.") - self.moveUpAction = Qt.QAction(getThemeIcon("go-up"), "Move up", self) + self.moveUpAction = Qt.QAction(Qt.QIcon.fromTheme("go-up"), "Move up", + self) self.moveUpAction.triggered.connect(self.upMacro) self.moveUpAction.setToolTip( "Clicking this button will move current macro up.") self.moveDownAction = Qt.QAction( - getThemeIcon("go-down"), "Move down", self) + Qt.QIcon.fromTheme("go-down"), "Move down", self) self.moveDownAction.triggered.connect(self.downMacro) self.moveDownAction.setToolTip( "Clicking this button will move current macro down.") self.moveLeftAction = Qt.QAction( - getThemeIcon("go-previous"), "Move left", self) + Qt.QIcon.fromTheme("go-previous"), "Move left", self) self.moveLeftAction.triggered.connect(self.leftMacro) self.moveLeftAction.setToolTip( "Clicking this button will move current macro to the left.") self.moveRightAction = Qt.QAction( - getThemeIcon("go-next"), "Move right", self) + Qt.QIcon.fromTheme("go-next"), "Move right", self) self.moveRightAction.triggered.connect(self.rightMacro) self.moveRightAction.setToolTip( "Clicking this button will move current macro to the right.") @@ -287,7 +287,7 @@ def prepareMacroIds(self): def prepareMacroProgresses(self): self._idIndexDict = self.model().createIdIndexDictionary() - for macroId in self._idIndexDict.iterkeys(): + for macroId in self._idIndexDict.keys(): self.setProgressForMacro(macroId, 0) def setProgressForMacro(self, macroId, progress): @@ -324,9 +324,10 @@ def dropEvent(self, event): class TaurusSequencerWidget(TaurusWidget): + doorChanged = Qt.pyqtSignal('QString') macroStarted = Qt.pyqtSignal('QString') - plotablesFilterChanged = Qt.pyqtSignal(object) - currentMacroChanged = Qt.pyqtSignal(object) + plotablesFilterChanged = Qt.pyqtSignal(compat.PY_OBJECT) + currentMacroChanged = Qt.pyqtSignal(compat.PY_OBJECT) macroNameChanged = Qt.pyqtSignal('QString') shortMessageEmitted = Qt.pyqtSignal('QString') sequenceEmpty = Qt.pyqtSignal() @@ -350,13 +351,12 @@ def __init__(self, parent=None, designMode=False): self.layout().addWidget(splitter) splitter.setOrientation(Qt.Qt.Vertical) - sequenceEditor = TaurusWidget() - splitter.addWidget(sequenceEditor) - sequenceEditor.setUseParentModel(True) - sequenceEditor.setLayout(Qt.QVBoxLayout()) - sequenceEditor.layout().setContentsMargins(0, 0, 0, 0) + self.sequenceEditor = TaurusWidget() + splitter.addWidget(self.sequenceEditor) + self.sequenceEditor.setLayout(Qt.QVBoxLayout()) + self.sequenceEditor.layout().setContentsMargins(0, 0, 0, 0) - self.tree = MacroSequenceTree(sequenceEditor) + self.tree = MacroSequenceTree(self.sequenceEditor) self.sequenceProxyModel = MacroSequenceProxyModel() self.sequenceProxyModel.setSourceModel(self._sequenceModel) self.tree.setModel(self.sequenceProxyModel) @@ -365,7 +365,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout = Qt.QHBoxLayout() actionsLayout.setContentsMargins(0, 0, 0, 0) self.newSequenceAction = Qt.QAction( - getThemeIcon("document-new"), "New", self) + Qt.QIcon.fromTheme("document-new"), "New", self) self.newSequenceAction.triggered.connect(self.onNewSequence) self.newSequenceAction.setToolTip("New sequence") self.newSequenceAction.setEnabled(False) @@ -374,7 +374,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(newSequenceButton) self.openSequenceAction = Qt.QAction( - getThemeIcon("document-open"), "Open...", self) + Qt.QIcon.fromTheme("document-open"), "Open...", self) self.openSequenceAction.triggered.connect(self.onOpenSequence) self.openSequenceAction.setToolTip("Open sequence...") openSequenceButton = Qt.QToolButton() @@ -382,7 +382,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(openSequenceButton) self.saveSequenceAction = Qt.QAction( - getThemeIcon("document-save"), "Save...", self) + Qt.QIcon.fromTheme("document-save"), "Save...", self) self.saveSequenceAction.triggered.connect(self.onSaveSequence) self.saveSequenceAction.setToolTip("Save sequence...") self.saveSequenceAction.setEnabled(False) @@ -391,7 +391,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(saveSequenceButton) self.stopSequenceAction = Qt.QAction( - getIcon(":/actions/media_playback_stop.svg"), "Stop", self) + Qt.QIcon("actions:media_playback_stop.svg"), "Stop", self) self.stopSequenceAction.triggered.connect(self.onStopSequence) self.stopSequenceAction.setToolTip("Stop sequence") stopSequenceButton = Qt.QToolButton() @@ -399,7 +399,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(stopSequenceButton) self.pauseSequenceAction = Qt.QAction( - getIcon(":/actions/media_playback_pause.svg"), "Pause", self) + Qt.QIcon("actions:media_playback_pause.svg"), "Pause", self) self.pauseSequenceAction.triggered.connect(self.onPauseSequence) self.pauseSequenceAction.setToolTip("Pause sequence") pauseSequenceButton = Qt.QToolButton() @@ -407,7 +407,7 @@ def __init__(self, parent=None, designMode=False): actionsLayout.addWidget(pauseSequenceButton) self.playSequenceAction = Qt.QAction( - getIcon(":/actions/media_playback_start.svg"), "Play", self) + Qt.QIcon("actions:media_playback_start.svg"), "Play", self) self.playSequenceAction.triggered.connect(self.onPlaySequence) self.playSequenceAction.setToolTip("Play sequence") playSequenceButton = Qt.QToolButton() @@ -429,21 +429,20 @@ def __init__(self, parent=None, designMode=False): 0, 0, Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed) actionsLayout.addItem(spacerItem) - sequenceEditor.layout().addLayout(actionsLayout) + self.sequenceEditor.layout().addLayout(actionsLayout) macroLayout = Qt.QHBoxLayout() macroLayout.setContentsMargins(0, 0, 0, 0) macroLabel = Qt.QLabel("Macro:") macroLayout.addWidget(macroLabel) self.macroComboBox = MacroComboBox(self) - self.macroComboBox.setUseParentModel(True) self.macroComboBox.setModelColumn(0) self.macroComboBox.setSizePolicy( Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Minimum) macroLayout.addWidget(self.macroComboBox) self.addMacroAction = Qt.QAction( - getThemeIcon("list-add"), "Add macro...", self) + Qt.QIcon.fromTheme("list-add"), "Add macro...", self) self.addMacroAction.triggered.connect(self.onAdd) self.addMacroAction.setToolTip( "Clicking this button will add selected macro") @@ -452,7 +451,7 @@ def __init__(self, parent=None, designMode=False): addButton.setDefaultAction(self.addMacroAction) macroLayout.addWidget(addButton) - sequenceEditor.layout().addLayout(macroLayout) + self.sequenceEditor.layout().addLayout(macroLayout) sequenceLayout = Qt.QHBoxLayout() sequenceLayout.addWidget(self.tree) @@ -482,7 +481,7 @@ def __init__(self, parent=None, designMode=False): 0, 40, Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Expanding) layout.addItem(spacerItem) sequenceLayout.addLayout(layout) - sequenceEditor.layout().addLayout(sequenceLayout) + self.sequenceEditor.layout().addLayout(sequenceLayout) self.parametersProxyModel = MacroParametersProxyModel() self.parametersProxyModel.setSourceModel(self._sequenceModel) @@ -503,8 +502,8 @@ def __init__(self, parent=None, designMode=False): def contextMenuEvent(self, event): menu = Qt.QMenu() - action = menu.addAction(getThemeIcon( - "view-refresh"), "Check door state", self.checkDoorState) + menu.addAction(Qt.QIcon.fromTheme("view-refresh"), "Check door state", + self.checkDoorState) menu.exec_(event.globalPos()) def checkDoorState(self): @@ -513,11 +512,7 @@ def checkDoorState(self): about the macro status does not reach the sequencer widget.""" door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState == PyTango.DevState.RUNNING: self.playSequenceAction.setEnabled(False) self.pauseSequenceAction.setEnabled(True) @@ -672,18 +667,14 @@ def onSaveSequence(self): "Error while saving macros sequence", "There was a problem while writing to the file: %s" % fileName) - print e + print(e) finally: if not file is None: file.close() def onPlaySequence(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if (doorState == PyTango.DevState.ON or doorState == PyTango.DevState.ALARM): first, last, ids = self.tree.prepareMacroIds() @@ -704,11 +695,7 @@ def onPlaySequence(self): def onStopSequence(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState in (PyTango.DevState.RUNNING, PyTango.DevState.STANDBY): door.command_inout("StopMacro") else: @@ -721,11 +708,7 @@ def onStopSequence(self): def onPauseSequence(self): door = Device(self.doorName()) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.getState() if doorState == PyTango.DevState.RUNNING: door.command_inout("PauseMacro") else: @@ -813,11 +796,7 @@ def onDoorChanged(self, doorName): return self.doorStateLed.setModel(self.doorName() + "/State") door = Device(doorName) - try: - doorState = door.state() - except TypeError: - # TODO: For Taurus 4 adaptation - doorState = door.getState() + doorState = door.stateObj.rvalue if doorState == PyTango.DevState.ON: self.playSequenceAction.setText("Start sequence") self.playSequenceAction.setToolTip("Start sequence") @@ -926,6 +905,8 @@ def setModel(self, model): TaurusWidget.setModel(self, model) newModelObj = self.getModelObj() newModelObj.macrosUpdated.connect(self.macroComboBox.onMacrosUpdated) + self.sequenceEditor.setModel(model) + self.macroComboBox.setModel(model) @classmethod def getQtDesignerPluginInfo(cls): @@ -942,10 +923,10 @@ def __init__(self, parent=None, designMode=False): MacroExecutionWindow.__init__(self) def initComponents(self): - #@todo: take care about storing model - self.setModelInConfig(True) self.taurusSequencerWidget = TaurusSequencerWidget(self) - self.taurusSequencerWidget.setUseParentModel(True) + self.taurusSequencerWidget.setModelInConfig(True) + self.taurusSequencerWidget.doorChanged.connect( + self.taurusSequencerWidget.onDoorChanged) self.registerConfigDelegate(self.taurusSequencerWidget) self.setCentralWidget(self.taurusSequencerWidget) self.taurusSequencerWidget.shortMessageEmitted.connect( @@ -976,6 +957,10 @@ def onDoorChanged(self, doorName): self.taurusSequencerWidget.onMacroStatusUpdated) self.taurusSequencerWidget.onDoorChanged(doorName) + def setModel(self, model): + MacroExecutionWindow.setModel(self, model) + self.taurusSequencerWidget.setModel(model) + @classmethod def getQtDesignerPluginInfo(cls): return None @@ -983,7 +968,6 @@ def getQtDesignerPluginInfo(cls): def createSequencerWidget(args): sequencer = TaurusSequencerWidget() - sequencer.setModelInConfig(True) sequencer.doorChanged.connect(sequencer.onDoorChanged) if len(args) == 2: @@ -994,12 +978,20 @@ def createSequencerWidget(args): def createSequencer(args, options): sequencer = TaurusSequencer() - sequencer.setModelInConfig(True) sequencer.doorChanged.connect(sequencer.onDoorChanged) + load_settings = True if len(args) == 2: sequencer.setModel(args[0]) sequencer.doorChanged.emit(args[1]) - sequencer.loadSettings() + settings = sequencer.getQSettings() + taurus_config_raw = settings.value("TaurusConfig") + if taurus_config_raw is not None: + taurus_config = pickle.loads(taurus_config_raw.data()) + oldmodel = taurus_config['__itemConfigurations__']['model'] + if args[0] == oldmodel: + load_settings = False + if load_settings: + sequencer.loadSettings() if options.file is not None: sequencer.taurusSequencerWidget.loadFile(options.file) return sequencer diff --git a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/ui/MacroButton.ui b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/ui/MacroButton.ui index 600d533ceb..8bab1901d9 100644 --- a/src/sardana/taurus/qt/qtgui/extra_macroexecutor/ui/MacroButton.ui +++ b/src/sardana/taurus/qt/qtgui/extra_macroexecutor/ui/MacroButton.ui @@ -1,7 +1,8 @@ - + + MacroButton - - + + 0 0 @@ -9,76 +10,88 @@ 79 - + Form - - + + 0 - + 0 - - - + + + QFrame::Box - + QFrame::Plain - + 3 - + 3 - - + + 0 - + 0 - - - - + + + + 0 0 - + + + 25 + 15 + + + PushButton - + true - + false - - - - + + + + 0 0 - + + + 1 + 0 + + + 16777215 10 - + 6 - + 24 @@ -93,9 +106,9 @@ TaurusWidget QWidget
taurus.qt.qtgui.container
+ 1
- diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/__init__.py b/src/sardana/taurus/qt/qtgui/extra_pool/__init__.py index f604a8979e..5fdd4123dc 100644 --- a/src/sardana/taurus/qt/qtgui/extra_pool/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_pool/__init__.py @@ -27,9 +27,11 @@ __init__.py: """ -from motor import TaurusMotorH, TaurusMotorH2, TaurusMotorV, TaurusMotorV2 -from poolmotor import PoolMotorSlim, LabelWidgetDragsDeviceAndAttribute -from poolmotor import PoolMotorTV, PoolMotorTVLabelWidget, PoolMotorTVReadWidget, PoolMotorTVWriteWidget, PoolMotorTVUnitsWidget -from poolmotor import PoolMotor -from poolchannel import PoolChannel, PoolChannelTV -from poolioregister import PoolIORegisterTV, PoolIORegisterReadWidget, PoolIORegisterWriteWidget, PoolIORegister, PoolIORegisterButtons +from .motor import TaurusMotorH, TaurusMotorH2, TaurusMotorV, TaurusMotorV2 # noqa +from .poolmotor import LabelWidgetDragsDeviceAndAttribute # noqa +from .poolmotor import (PoolMotorTV, PoolMotorTVLabelWidget, # noqa + PoolMotorTVReadWidget, PoolMotorTVWriteWidget, PoolMotorTVUnitsWidget, # noqa + PoolMotor) # noqa +from .poolchannel import PoolChannel, PoolChannelTV, _PoolChannelTV # noqa +from .poolioregister import (PoolIORegisterTV, PoolIORegisterReadWidget, # noqa + PoolIORegisterWriteWidget, PoolIORegister, PoolIORegisterButtons) # noqa diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/formitemfactory.py b/src/sardana/taurus/qt/qtgui/extra_pool/formitemfactory.py new file mode 100644 index 0000000000..ee9ee1646d --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_pool/formitemfactory.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +""" +This module provides TaurusValue item factories to be registered as providers +of custom TaurusForm item widgets. +""" + +from . import PoolMotorTV, PoolChannelTV, PoolIORegisterTV, _PoolChannelTV + +T_FORM_POOL_WIDGET_MAP = { + "SimuMotor": PoolMotorTV, + "Motor": PoolMotorTV, + "PseudoMotor": PoolMotorTV, + "PseudoCounter": _PoolChannelTV, + "CTExpChannel": PoolChannelTV, + "ZeroDExpChannel": _PoolChannelTV, + "OneDExpChannel": PoolChannelTV, + "TwoDExpChannel": PoolChannelTV, + "IORegister": PoolIORegisterTV, +} + + +def pool_item_factory(model): + """ + Taurus Value Factory to be registered as a TaurusForm item factory plugin + + :param model: taurus model object + + :return: custom TaurusValue class + """ + # TODO: use sardana element types instead of tango classes + try: + key = model.getDeviceProxy().info().dev_class + klass = T_FORM_POOL_WIDGET_MAP[key] + except Exception: + return None + return klass() diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/poolchannel.py b/src/sardana/taurus/qt/qtgui/extra_pool/poolchannel.py index 1fb5d01af4..f65974f331 100644 --- a/src/sardana/taurus/qt/qtgui/extra_pool/poolchannel.py +++ b/src/sardana/taurus/qt/qtgui/extra_pool/poolchannel.py @@ -27,13 +27,354 @@ channelWidgets.py: """ -__all__ = ["PoolChannel", "PoolChannelTV"] +__all__ = ["PoolChannel", "PoolChannelTV", "_PoolChannelTV"] +import weakref + +import tango import taurus +from taurus.core import DataType, DataFormat from taurus.external.qt import Qt -from taurus.qt.qtgui.panel import TaurusValue, TaurusDevButton +from taurus.qt.qtcore.mimetypes import (TAURUS_DEV_MIME_TYPE, + TAURUS_ATTR_MIME_TYPE) +from taurus.qt.qtgui.panel import (TaurusValue, TaurusDevButton, + DefaultLabelWidget, DefaultUnitsWidget, + TaurusAttrForm, TaurusForm, + DefaultReadWidgetLabel, TaurusPlotButton, + TaurusImageButton, TaurusValuesTableButton, + ) +from taurus.qt.qtgui.input import TaurusValueLineEdit +from taurus.qt.qtgui.dialog import ProtectTaurusMessageBox +from taurus.qt.qtgui.compact import TaurusReadWriteSwitcher from taurus.qt.qtgui.container import TaurusWidget -from poolmotor import LabelWidgetDragsDeviceAndAttribute +from taurus.qt.qtgui.resource import getIcon +from sardana.taurus.qt.qtgui.extra_pool.poolmotor import \ + LabelWidgetDragsDeviceAndAttribute, TaurusAttributeListener + + +class PoolChannelTVLabelWidget(TaurusWidget): + """ + @TODO tooltip should be extended with status info + @TODO context menu should be the lbl_alias extended + @TODO default tooltip extended with the complete (multiline) status + @TODO rightclick popup menu with actions: (1) switch user/expert view, + (2) Config -all attributes-, (3) change channel + For the (3), a drop event should accept if it is a device, + and add it to the "change-channel" list and select + """ + + def __init__(self, parent=None, designMode=False): + TaurusWidget.__init__(self, parent, designMode) + self.setLayout(Qt.QGridLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().setSpacing(0) + + self.lbl_alias = DefaultLabelWidget(parent, designMode) + self.lbl_alias.setBgRole("none") + self.layout().addWidget(self.lbl_alias) + + # I don't like this approach, there should be something like + # self.lbl_alias.addAction(...) + self.lbl_alias.contextMenuEvent = \ + lambda event: self.contextMenuEvent(event) + + # I' don't like this approach, there should be something like + # self.lbl_alias.addToolTipCallback(self.calculate_extra_tooltip) + self.lbl_alias.getFormatedToolTip = self.calculateExtendedTooltip + + # I' don't like this approach, there should be something like + # self.lbl_alias.disableDrag() or self.lbl_alias.setDragEnabled(False) + # or better, define if Attribute or Device or Both have to be included + # in the mimeData + self.lbl_alias.mouseMoveEvent = self.mouseMoveEvent + + def setModel(self, model): + if model in (None, ""): + self.lbl_alias.setModel(model) + TaurusWidget.setModel(self, model) + return + self.lbl_alias.taurusValueBuddy = self.taurusValueBuddy + self.lbl_alias.setModel(model) + TaurusWidget.setModel(self, model + "/Status") + + def calculateExtendedTooltip(self, cache=False): + default_label_widget_tooltip = DefaultLabelWidget.getFormatedToolTip( + self.lbl_alias, cache) + status_info = "" + channel_dev = self.taurusValueBuddy().channel_dev + if channel_dev is not None: + status = channel_dev.getAttribute("Status").read().value + # MAKE IT LOOK LIKE THE STANDARD TABLE FOR TAURUS TOOLTIPS + status_lines = status.split("\n") + status_info = ("") + for status_extra_line in status_lines[1:]: + status_info += ("") + status_info += "
Status:" + + status_lines[0] + + "
" + + status_extra_line + + "
" + return default_label_widget_tooltip + status_info + + def contextMenuEvent(self, event): + # Overwrite the default taurus label behaviour + menu = Qt.QMenu(self) + action_tango_attributes = Qt.QAction(self) + action_tango_attributes.setIcon( + getIcon(":/categories/preferences-system.svg")) + action_tango_attributes.setText("Tango Attributes") + menu.addAction(action_tango_attributes) + action_tango_attributes.triggered.connect( + self.taurusValueBuddy().showTangoAttributes) + + cm_action = menu.addAction("Compact") + cm_action.setCheckable(True) + cm_action.setChecked(self.taurusValueBuddy().isCompact()) + cm_action.toggled.connect(self.taurusValueBuddy().setCompact) + + menu.exec_(event.globalPos()) + event.accept() + + def mouseMoveEvent(self, event): + model = self.taurusValueBuddy().getModelObj() + mimeData = Qt.QMimeData() + mimeData.setText(self.lbl_alias.text()) + dev_name = model.getFullName().encode("utf-8") + attr_name = dev_name + b"/Value" + mimeData.setData(TAURUS_DEV_MIME_TYPE, dev_name) + mimeData.setData(TAURUS_ATTR_MIME_TYPE, attr_name) + + drag = Qt.QDrag(self) + drag.setMimeData(mimeData) + drag.setHotSpot(event.pos() - self.rect().topLeft()) + drag.exec_(Qt.Qt.CopyAction, Qt.Qt.CopyAction) + + +class _IntegTimeTaurusValueLineEdit(TaurusValueLineEdit): + + def sizeHint(self): + size = Qt.QSize() + size.setWidth(55) + return size + + +class PoolChannelTVExtraWidget(TaurusWidget): + """ + """ + + def __init__(self, parent=None, designMode=False): + TaurusWidget.__init__(self, parent, designMode) + self.setLayout(Qt.QHBoxLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().setSpacing(0) + + self.le_integ_time = _IntegTimeTaurusValueLineEdit() + le_policy = Qt.QSizePolicy(Qt.QSizePolicy.Preferred, + Qt.QSizePolicy.Preferred) + self.le_integ_time.setSizePolicy(le_policy) + self.layout().addWidget(self.le_integ_time) + + # TODO: state listener should be part of *ExpChannel device Taurus + # Qt extension, it is necessary to change button's action, + # icon, etc. + self.state_listener = TaurusAttributeListener() + self.state_listener.eventReceivedSignal.connect(self.updateState) + self.btn_start_stop_clicked_slot = None + self.btn_start_stop = Qt.QPushButton() + btn_policy = Qt.QSizePolicy(Qt.QSizePolicy.Fixed, + Qt.QSizePolicy.Preferred) + btn_policy.setHorizontalStretch(0) + self.btn_start_stop.setSizePolicy(btn_policy) + self.layout().addWidget(self.btn_start_stop) + + def updateState(self, state): + btn_start_stop = self.btn_start_stop + if self.btn_start_stop_clicked_slot is not None: + btn_start_stop.clicked.disconnect( + self.btn_start_stop_clicked_slot) + if state == tango.DevState.MOVING: + btn_start_stop.setToolTip("Stop the channel") + btn_start_stop.setIcon( + getIcon(":/actions/media_playback_stop.svg")) + self.btn_start_stop_clicked_slot = self.abort + else: + btn_start_stop.setToolTip("Start the channel") + btn_start_stop.setIcon( + getIcon(":/actions/media_playback_start.svg")) + self.btn_start_stop_clicked_slot = self.start + btn_start_stop.clicked.connect(self.btn_start_stop_clicked_slot) + + def setModel(self, model): + # first disconnect old model state listener + model_obj = self.getModelObj() + if model_obj is not None: + model_obj.getAttribute("State").removeListener( + self.state_listener) + TaurusWidget.setModel(self, model) + if model in (None, ""): + self.le_integ_time.setModel(model) + return + self.le_integ_time.setModel(model + "/IntegrationTime") + # connect new model state listener + self.getModelObj().getAttribute("State").addListener( + self.state_listener) + + @Qt.pyqtSlot() + @ProtectTaurusMessageBox( + msg="An error occurred trying to start the acquisition.") + def start(self): + channel_dev = self.taurusValueBuddy().channel_dev + if channel_dev is not None: + channel_dev.Start() + + @Qt.pyqtSlot() + @ProtectTaurusMessageBox( + msg="An error occurred trying to abort the acquisition.") + def abort(self): + channel_dev = self.taurusValueBuddy().channel_dev + if channel_dev is not None: + channel_dev.abort() + + +class PoolChannelTVUnitsWidget(DefaultUnitsWidget): + + def __init__(self, parent=None, designMode=False): + DefaultUnitsWidget.__init__(self, parent, designMode) + + def setModel(self, model): + if model in (None, ""): + DefaultUnitsWidget.setModel(self, model) + return + DefaultUnitsWidget.setModel(self, model + "/Value") + + +class PoolChannelTV(TaurusValue): + """A widget that displays and controls a pool channel device. + It differs from :class:`PoolChannel` in that it behaves as a TaurusValue + (i.e., it allows its subwidgets to be aligned in columns in a TaurusForm)` + + .. todo:: Ideally overriding of `getDefaultReadWidgetClass` and + `updateReadWidget` should not be necessary. Creation of a dedicated wiget + for displaying value should be delegated to a custom read widget. + .. todo:: draw state-based coloured frame around spectrum and image + buttons, when state is MOVING - blue, when ON - green, etc. + """ + + def __init__(self, parent=None, designMode=False): + TaurusValue.__init__(self, parent=parent, designMode=designMode) + self.setLabelWidgetClass(PoolChannelTVLabelWidget) + self.setWriteWidgetClass(None) + self.setUnitsWidgetClass(PoolChannelTVUnitsWidget) + self.setExtraWidgetClass(PoolChannelTVExtraWidget) + self.channel_dev = None + + def getDefaultReadWidgetClass(self, returnAll=False): + """ + Returns the default class (or classes) to use as read widget for the + current model. + + Override TaurusValue.getDefaultReadWidgetClass. Simply do the same + but based on the Value attribute while our model obj is a device. + + :param returnAll: (bool) if True, the return value is a list of valid + classes instead of just one class + + :return: (class or list) the default class to use for the read + widget (or, if returnAll==True, a list of classes that can + show the attribute ). If a list is returned, it will be + loosely ordered by preference, being the first element + always the default one. + """ + modelobj = self.getModelObj() + if modelobj is None: + if returnAll: + return [DefaultReadWidgetLabel] + else: + return DefaultReadWidgetLabel + + valueobj = modelobj.getAttribute("Value") + if valueobj.data_format == DataFormat._0D: + result = [DefaultReadWidgetLabel] + elif valueobj.data_format == DataFormat._1D: + if valueobj.type in (DataType.Float, DataType.Integer): + result = [TaurusPlotButton, + TaurusValuesTableButton, DefaultReadWidgetLabel] + else: + result = [TaurusValuesTableButton, DefaultReadWidgetLabel] + elif valueobj.data_format == DataFormat._2D: + if valueobj.type in (DataType.Float, DataType.Integer): + try: + # unused import but useful to determine if + # TaurusImageButton should be added + from taurus.qt.qtgui.extra_guiqwt import TaurusImageDialog # noqa + result = [TaurusImageButton, + TaurusValuesTableButton, DefaultReadWidgetLabel] + except ImportError: + result = [TaurusValuesTableButton, + DefaultReadWidgetLabel] + else: + result = [TaurusValuesTableButton, DefaultReadWidgetLabel] + else: + self.warning('Unsupported attribute type %s' % valueobj.type) + result = None + + if returnAll: + return result + else: + return result[0] + + def updateReadWidget(self): + """Update read widget by recreating it from scratch. + + Override TaurusValue.updateReadWidget. Simply do the same, just + don't call setModel on the read widget at the end. Model of the read + widget is set by our setModel when the read widget is already + recreated. + """ + # get the class for the widget and replace it if necessary + try: + klass = self.readWidgetClassFactory(self.readWidgetClassID) + self._readWidget = self._newSubwidget(self._readWidget, klass) + except Exception as e: + self._destroyWidget(self._readWidget) + self._readWidget = Qt.QLabel('[Error]') + msg = 'Error creating read widget:\n' + str(e) + self._readWidget.setToolTip(msg) + self.debug(msg) + + # take care of the layout + self.addReadWidgetToLayout() + + if self._readWidget is not None: + # give the new widget a reference to its buddy TaurusValue object + self._readWidget.taurusValueBuddy = weakref.ref(self) + if isinstance(self._readWidget, TaurusReadWriteSwitcher): + self._readWidget.readWidget.taurusValueBuddy = weakref.ref( + self) + self._readWidget.writeWidget.taurusValueBuddy = weakref.ref( + self) + + # tweak the new widget + if self.minimumHeight() is not None: + self._readWidget.setMinimumHeight(self.minimumHeight()) + + def setModel(self, model): + TaurusValue.setModel(self, model) + if model == "" or model is None: + return + self.readWidget().setModel(model + "/Value") + self.channel_dev = taurus.Device(model) + + def showTangoAttributes(self): + model = self.getModel() + taurus_attr_form = TaurusAttrForm() + taurus_attr_form.setMinimumSize(Qt.QSize(555, 800)) + taurus_attr_form.setModel(model) + taurus_attr_form.setWindowTitle( + '%s Tango Attributes' % self.getModelObj().getSimpleName()) + taurus_attr_form.show() class _ParentDevButton(TaurusDevButton): @@ -55,7 +396,7 @@ def setModel(self, model): TaurusDevButton.setModel(self, devname) -class PoolChannelTV(TaurusValue): +class _PoolChannelTV(TaurusValue): ''' A widget that displays and controls a pool channel device. It differs from :class:`PoolChannel` in that it behaves as a TaurusValue (i.e., it allows its subwidgets to be aligned in columns in a TaurusForm)` @@ -64,7 +405,7 @@ class PoolChannelTV(TaurusValue): def __init__(self, parent=None, designMode=False): TaurusValue.__init__(self, parent=parent, designMode=designMode) self.setLabelWidgetClass(LabelWidgetDragsDeviceAndAttribute) - self.setLabelConfig('') + self.setLabelConfig('{dev.name}') def getDefaultExtraWidgetClass(self): return _ParentDevButton @@ -90,6 +431,7 @@ def hideEvent(self, event): pass + class PoolChannel(TaurusWidget): ''' A widget that displays and controls a pool channel device @@ -107,7 +449,7 @@ def __init__(self, parent=None, designMode=False): self._TaurusValue = TaurusValue(parent=w, designMode=designMode) self._TaurusValue.setLabelWidgetClass( LabelWidgetDragsDeviceAndAttribute) - self._TaurusValue.setLabelConfig('') + self._TaurusValue.setLabelConfig('{dev.name}') self.layout().addWidget(w) #...and a dev button next to the widget @@ -123,18 +465,25 @@ def _updateTaurusValue(self): self._devButton.setModel(m) -# if __name__ == '__main__': -# import sys -# app = Qt.QApplication(sys.argv) -# -# form = PoolChannel() -# -# #model = 'tango://controls02:10000/expchan/bl97_simucotictrl_1/1' -# model = 'ct_cp1_1' -# if len(sys.argv)>1: -# model = sys.argv[1] -# form.setModel(model) -# -# -# form.show() -# sys.exit(app.exec_()) +if __name__ == "__main__": + import sys + argv = sys.argv + if len(argv) > 0: + models = argv[1:] + app = Qt.QApplication(sys.argv) + + form_tv = TaurusForm() + form_tv.setModifiableByUser(True) + tv_widget_class = "sardana.taurus.qt.qtgui.extra_pool.PoolChannelTV" + tv_class_map = {"CTExpChannel": (tv_widget_class, (), {}), + "OneDExpChannel": (tv_widget_class, (), {}), + "TwoDExpChannel": (tv_widget_class, (), {})} + form_tv.setCustomWidgetMap(tv_class_map) + form_tv.setModel(models) + + w = Qt.QWidget() + w.setLayout(Qt.QVBoxLayout()) + w.layout().addWidget(form_tv) + + w.show() + sys.exit(app.exec_()) diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/poolioregister.py b/src/sardana/taurus/qt/qtgui/extra_pool/poolioregister.py index 5a95b4d6d6..c4d82fad48 100644 --- a/src/sardana/taurus/qt/qtgui/extra_pool/poolioregister.py +++ b/src/sardana/taurus/qt/qtgui/extra_pool/poolioregister.py @@ -37,7 +37,7 @@ from taurus.qt.qtgui.panel import TaurusValue from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtgui.util.ui import UILoadable -from poolmotor import LabelWidgetDragsDeviceAndAttribute +from .poolmotor import LabelWidgetDragsDeviceAndAttribute import taurus @@ -247,7 +247,7 @@ def setModel(self, model): # Empty previous buttons # self.ui.lo_buttons_write. - for button in self.button_value_dict.keys(): + for button in list(self.button_value_dict.keys()): self.button.clicked.disconnect(self.writeValue) button.deleteLater() self.button_value_dict = {} diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py b/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py index df4d4d36c8..a31d5db83c 100644 --- a/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py +++ b/src/sardana/taurus/qt/qtgui/extra_pool/poolmotor.py @@ -23,37 +23,28 @@ ## ############################################################################## -import sys -import copy +import operator +from enum import IntEnum + import PyTango -import numpy -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat import taurus from taurus.core.util.colors import DEVICE_STATE_PALETTE from taurus.core.taurusbasetypes import TaurusEventType -try: - from taurus.core.taurusvalidator import DeviceNameValidator as \ - TangoDeviceNameValidator -except ImportError: - # TODO: For Taurus 4 adaptation - from taurus.core.tango.tangovalidator import TangoDeviceNameValidator +from taurus.core.tango.tangovalidator import TangoDeviceNameValidator import taurus.qt.qtcore.mimetypes -from taurus.qt.qtgui.base import TaurusBaseWritableWidget from taurus.qt.qtgui.compact import TaurusReadWriteSwitcher from taurus.qt.qtgui.dialog import ProtectTaurusMessageBox -from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtgui.container import TaurusFrame from taurus.qt.qtgui.display import TaurusLabel from taurus.qt.qtgui.input import TaurusValueLineEdit -from taurus.qt.qtgui.input import TaurusValueSpinBox from taurus.qt.qtgui.panel import DefaultLabelWidget from taurus.qt.qtgui.panel import DefaultUnitsWidget from taurus.qt.qtgui.panel import TaurusValue, TaurusAttrForm from taurus.qt.qtcore.mimetypes import TAURUS_DEV_MIME_TYPE, TAURUS_ATTR_MIME_TYPE -from taurus.qt.qtgui.resource import getIcon from taurus.qt.qtgui.util.ui import UILoadable @@ -64,7 +55,7 @@ class LimitsListener(Qt.QObject): can do whatever with it. """ - updateLimits = Qt.pyqtSignal(object) + updateLimits = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self): Qt.QObject.__init__(self) @@ -76,6 +67,91 @@ def eventReceived(self, evt_src, evt_type, evt_value): self.updateLimits.emit(limits.tolist()) +class Limit(IntEnum): + Home = 0 + Positive = 1 + Negative = 2 + + +class LimitInfo: + + def __init__(self, exist=False, active=False, status=""): + # for software limit True when defined + # for hardware limit True when physical motor, False when pseudo + self.exist = exist + self.active = active + self.status = status + + +def eval_hw_limit(limit, value): + """Evaluate hardware limit information + + :param limit: which limit (negative or positive) + :type limit: `Limit` + :param value: limit value, `None` means no hardware limit (pseudo motor) + :type value: `bool` or `None` + :return: limit information + :rtype: `LimitInfo` + """ + if limit is Limit.Positive: + status = "Positive " + elif limit is Limit.Negative: + status = "Negative " + else: + raise ValueError("{} wrong limit".format(limit)) + exist = True + active = False + status += "hardware limit " + if value is None: + exist = False + status += "does not exist." + elif value: + active = True + status += "is active." + else: + status += "is not active." + return LimitInfo(exist, active, status) + + +def eval_sw_limit(limit, position_attr): + """Evaluate software limit information + + :param limit: which limit (negative or positive) + :type limit: `Limit` + :param position_attr: position attribute + :type position_attr: `taurus.core.tango.TangoAttribute` + :return: limit information + :rtype: `LimitInfo` + """ + if limit == Limit.Positive: + getter_name = "getMaxRange" + comparator = operator.ge + not_defined = float("inf") + status = "Positive " + elif limit == Limit.Negative: + getter_name = "getMinRange" + comparator = operator.le + not_defined = float("-inf") + status = "Negative " + else: + raise ValueError("{} wrong limit".format(limit)) + active = False + exist = False + lim_value = getattr(position_attr, getter_name)().magnitude + status += "software limit is " + if lim_value == not_defined: + status += "not defined." + else: + exist = True + position = position_attr.read().rvalue.magnitude + if comparator(position, lim_value): + status += "active." + active = True + else: + status += "defined." + return LimitInfo(exist, active, status) + + class PoolMotorClient(): maxint_in_32_bits = 2147483647 @@ -94,7 +170,7 @@ def setMotor(self, pool_motor_dev_name): # PENDING. self.has_limits = hasattr(self.motor_dev, 'Limit_Switches') self.has_encoder = hasattr(self.motor_dev, 'Encoder') - except Exception, e: + except Exception as e: taurus.warning('Exception Creating Motor Device %s', str(e)) def moveMotor(self, pos): @@ -107,7 +183,7 @@ def moveInc(self, inc): @Qt.pyqtSlot() def jogNeg(self): - neg_limit = -((self.maxint_in_32_bits / 2) - 1) + neg_limit = -((self.maxint_in_32_bits // 2) - 1) # THERE IS A BUG IN THE ICEPAP THAT DOES NOT ALLOW MOVE ABSOLUTE FURTHER THAN 32 BIT # SO IF THERE ARE STEPS PER UNIT, max_int HAS TO BE REDUCED if hasattr(self.motor_dev, 'step_per_unit'): @@ -122,7 +198,7 @@ def jogNeg(self): @Qt.pyqtSlot() def jogPos(self): - pos_limit = (self.maxint_in_32_bits / 2) - 1 + pos_limit = (self.maxint_in_32_bits // 2) - 1 # THERE IS A BUG IN THE ICEPAP THAT DOES NOT ALLOW MOVE ABSOLUTE FURTHER THAN 32 BIT # SO IF THERE ARE STEPS PER UNIT, max_int HAS TO BE REDUCED if hasattr(self.motor_dev, 'step_per_unit'): @@ -171,7 +247,7 @@ def __init__(self, parent=None, designMode=False): def getMotorControllerType(self): modelObj = self.getModelObj() modelNormalName = modelObj.getNormalName() - poolDsId = modelObj.getHWObj().info().server_id + poolDsId = modelObj.getDeviceProxy().info().server_id db = taurus.Database() pool_devices = tuple(db.get_device_class_list(poolDsId).value_string) pool_dev_name = pool_devices[pool_devices.index('Pool') - 1] @@ -308,499 +384,6 @@ def setModel(self, modelName): att.name.lower()) if att.name.lower() in attributes else 1) -@UILoadable(with_ui='ui') -class PoolMotorSlim(TaurusWidget, PoolMotorClient): - - __pyqtSignals__ = ("modelChanged(const QString &)",) - - def __init__(self, parent=None, designMode=False): - TaurusWidget.__init__(self, parent) - msg = ("PoolMotorSlim is deprecated since 2.5.0. Use PoolMotorTV " - "instead.") - self.deprecated(msg) - - #self.call__init__wo_kw(Qt.QWidget, parent) - #self.call__init__(TaurusBaseWidget, str(self.objectName()), designMode=designMode) - PoolMotorClient.__init__(self) - self.loadUi() - self.recheckTaurusParent() - - self.show_context_menu = True - - self.setAcceptDrops(True) - - if designMode: - self.__setTaurusIcons() - return - - # CREATE THE TaurusValue that can not be configured in the Designer - self.taurus_value = TaurusValue(self.ui.taurusValueContainer) - - # Use a DragDevAndAttributeLabelWidget to provide a richer QMimeData - # content - self.taurus_value.setLabelWidgetClass( - LabelWidgetDragsDeviceAndAttribute) - - # Make the label to be the device alias - self.taurus_value.setLabelConfig('dev_alias') - - self.taurus_value_enc = TaurusValue(self.ui.taurusValueContainer) - - # THIS WILL BE DONE IN THE DESIGNER - # Config Button will launch a PoolMotorConfigurationForm -# 19.08.2011 after discussion between cpascual, gcui and zreszela, Configuration Panel was rolled back to -# standard TaurusAttrForm - list of all attributes alphabetically ordered -# taurus_attr_form = PoolMotorConfigurationForm() - taurus_attr_form = TaurusAttrForm() - - taurus_attr_form.setMinimumSize(Qt.QSize(470, 800)) - self.ui.btnCfg.setWidget(taurus_attr_form) - self.ui.btnCfg.setUseParentModel(True) - - # ADD AN EVENT FILTER FOR THE STATUS LABEL IN ORDER TO PROVIDE JUST THE - # STRING FROM THE CONTROLLER (LAST LINE) - def just_ctrl_status_line(evt_src, evt_type, evt_value): - if evt_type not in [TaurusEventType.Change, TaurusEventType.Periodic]: - return evt_src, evt_type, evt_value - try: - status = evt_value.value - last_line = status.split('\n')[-1] - new_evt_value = PyTango.DeviceAttribute(evt_value) - new_evt_value.value = last_line - return evt_src, evt_type, new_evt_value - except: - return evt_src, evt_type, evt_value - self.ui.lblStatus.insertEventFilter(just_ctrl_status_line) - - # These buttons are just for showing if the limit is active or not - self.ui.btnMin.setEnabled(False) - self.ui.btnMax.setEnabled(False) - - # HOMING NOT IMPLMENTED YET - self.ui.btnHome.setEnabled(False) - - # DEFAULT VISIBLE COMPONENTS - self.toggleHideAll() - self.toggleMoveAbsolute(True) - self.toggleStopMove(True) - - # SET TAURUS ICONS - self.__setTaurusIcons() - - self.ui.motorGroupBox.setContextMenuPolicy(Qt.Qt.CustomContextMenu) - - self.ui.motorGroupBox.customContextMenuRequested.connect( - self.buildContextMenu) - self.ui.btnGoToNeg.clicked.connect(self.jogNeg) - self.ui.btnGoToNegPress.pressed.connect(self.jogNeg) - self.ui.btnGoToNegPress.released.connect(self.abort) - self.ui.btnGoToNegInc.clicked.connect(self.goToNegInc) - self.ui.btnGoToPos.clicked.connect(self.jogPos) - self.ui.btnGoToPosPress.pressed.connect(self.jogPos) - self.ui.btnGoToPosPress.released.connect(self.abort) - self.ui.btnGoToPosInc.clicked.connect(self.goToPosInc) - - self.ui.btnHome.clicked.connect(self.goHome) - self.ui.btnStop.clicked.connect(self.abort) - - # ALSO UPDATE THE WIDGETS EVERYTIME THE FORM HAS TO BE SHOWN - self.ui.btnCfg.clicked.connect(taurus_attr_form._updateAttrWidgets) - self.ui.btnCfg.clicked.connect(self.buildBetterCfgDialogTitle) - - ####################################################################### - ######################################## - # LET TAURUS CONFIGURATION MECANISM SHINE! - ######################################## - self.registerConfigProperty( - self.ui.inc.isVisible, self.toggleMoveRelative, 'MoveRelative') - self.registerConfigProperty( - self.ui.btnGoToNegPress.isVisible, self.toggleMoveContinuous, 'MoveContinuous') - self.registerConfigProperty( - self.ui.btnGoToNeg.isVisible, self.toggleMoveToLimits, 'MoveToLimits') - self.registerConfigProperty( - self.ui.btnStop.isVisible, self.toggleStopMove, 'StopMove') - self.registerConfigProperty( - self.ui.btnHome.isVisible, self.toggleHoming, 'Homing') - self.registerConfigProperty( - self.ui.btnCfg.isVisible, self.toggleConfig, 'Config') - self.registerConfigProperty( - self.ui.lblStatus.isVisible, self.toggleStatus, 'Status') - ####################################################################### - - def __setTaurusIcons(self): - self.ui.btnMin.setText('') - self.ui.btnMin.setIcon(getIcon(':/actions/list-remove.svg')) - self.ui.btnMax.setText('') - self.ui.btnMax.setIcon(getIcon(':/actions/list-add.svg')) - - self.ui.btnGoToNeg.setText('') - self.ui.btnGoToNeg.setIcon( - getIcon(':/actions/media_skip_backward.svg')) - self.ui.btnGoToNegPress.setText('') - self.ui.btnGoToNegPress.setIcon( - getIcon(':/actions/media_seek_backward.svg')) - self.ui.btnGoToNegInc.setText('') - self.ui.btnGoToNegInc.setIcon( - getIcon(':/actions/media_playback_backward.svg')) - self.ui.btnGoToPos.setText('') - self.ui.btnGoToPos.setIcon(getIcon(':/actions/media_skip_forward.svg')) - self.ui.btnGoToPosPress.setText('') - self.ui.btnGoToPosPress.setIcon( - getIcon(':/actions/media_seek_forward.svg')) - self.ui.btnGoToPosInc.setText('') - self.ui.btnGoToPosInc.setIcon( - getIcon(':/actions/media_playback_start.svg')) - self.ui.btnStop.setText('') - self.ui.btnStop.setIcon(getIcon(':/actions/media_playback_stop.svg')) - self.ui.btnHome.setText('') - self.ui.btnHome.setIcon(getIcon(':/actions/go-home.svg')) - self.ui.btnCfg.setText('') - self.ui.btnCfg.setIcon(getIcon(':/categories/preferences-system.svg')) - ####################################################################### - - #@Qt.pyqtSlot(list) - def updateLimits(self, limits): - if isinstance(limits, dict): - limits = limits["limits"] - pos_lim = limits[1] - pos_btnstylesheet = '' - enabled = True - if pos_lim: - pos_btnstylesheet = 'QPushButton{%s}' % DEVICE_STATE_PALETTE.qtStyleSheet( - PyTango.DevState.ALARM) - enabled = False - self.ui.btnMax.setStyleSheet(pos_btnstylesheet) - self.ui.btnGoToPos.setEnabled(enabled) - self.ui.btnGoToPosPress.setEnabled(enabled) - self.ui.btnGoToPosInc.setEnabled(enabled) - - neg_lim = limits[2] - neg_btnstylesheet = '' - enabled = True - if neg_lim: - neg_btnstylesheet = 'QPushButton{%s}' % DEVICE_STATE_PALETTE.qtStyleSheet( - PyTango.DevState.ALARM) - enabled = False - self.ui.btnMin.setStyleSheet(neg_btnstylesheet) - self.ui.btnGoToNeg.setEnabled(enabled) - self.ui.btnGoToNegPress.setEnabled(enabled) - self.ui.btnGoToNegInc.setEnabled(enabled) - - # def sizeHint(self): - # return Qt.QSize(300,30) - - @Qt.pyqtSlot() - def goToNegInc(self): - self.moveInc(-1 * self.ui.inc.value()) - - @Qt.pyqtSlot() - def goToPosInc(self): - self.moveInc(self.ui.inc.value()) - - def buildContextMenu(self, point): - if not self.show_context_menu: - return - menu = Qt.QMenu(self) - - action_hide_all = Qt.QAction(self) - action_hide_all.setText('Hide All') - menu.addAction(action_hide_all) - - action_show_all = Qt.QAction(self) - action_show_all.setText('Show All') - menu.addAction(action_show_all) - - action_move_absolute = Qt.QAction(self) - action_move_absolute.setText('Move Absolute') - action_move_absolute.setCheckable(True) - action_move_absolute.setChecked( - self.taurus_value.writeWidget().isVisible()) - menu.addAction(action_move_absolute) - - action_move_relative = Qt.QAction(self) - action_move_relative.setText('Move Relative') - action_move_relative.setCheckable(True) - action_move_relative.setChecked(self.ui.inc.isVisible()) - menu.addAction(action_move_relative) - - action_move_continuous = Qt.QAction(self) - action_move_continuous.setText('Move Continuous') - action_move_continuous.setCheckable(True) - action_move_continuous.setChecked(self.ui.btnGoToNegPress.isVisible()) - menu.addAction(action_move_continuous) - - action_move_to_limits = Qt.QAction(self) - action_move_to_limits.setText('Move to Limits') - action_move_to_limits.setCheckable(True) - action_move_to_limits.setChecked(self.ui.btnGoToNeg.isVisible()) - menu.addAction(action_move_to_limits) - - action_encoder = Qt.QAction(self) - action_encoder.setText('Encoder Read') - action_encoder.setCheckable(True) - action_encoder.setChecked(self.taurus_value_enc.isVisible()) - if self.has_encoder: - menu.addAction(action_encoder) - - action_stop_move = Qt.QAction(self) - action_stop_move.setText('Stop Movement') - action_stop_move.setCheckable(True) - action_stop_move.setChecked(self.ui.btnStop.isVisible()) - menu.addAction(action_stop_move) - - action_homing = Qt.QAction(self) - action_homing.setText('Homing') - action_homing.setCheckable(True) - action_homing.setChecked(self.ui.btnHome.isVisible()) - menu.addAction(action_homing) - - action_config = Qt.QAction(self) - action_config.setText('Config') - action_config.setCheckable(True) - action_config.setChecked(self.ui.btnCfg.isVisible()) - menu.addAction(action_config) - - action_status = Qt.QAction(self) - action_status.setText('Status') - action_status.setCheckable(True) - action_status.setChecked(self.ui.lblStatus.isVisible()) - menu.addAction(action_status) - - action_hide_all.triggered.connect(self.toggleHideAll) - action_show_all.triggered.connect(self.toggleShowAll) - action_move_absolute.toggled.connect(self.toggleMoveAbsolute) - action_move_relative.toggled.connect(self.toggleMoveRelative) - action_move_continuous.toggled.connect(self.toggleMoveContinuous) - action_move_to_limits.toggled.connect(self.toggleMoveToLimits) - action_encoder.toggled.connect(self.toggleEncoder) - action_stop_move.toggled.connect(self.toggleStopMove) - action_homing.toggled.connect(self.toggleHoming) - action_config.toggled.connect(self.toggleConfig) - action_status.toggled.connect(self.toggleStatus) - - menu.popup(self.cursor().pos()) - - def toggleHideAll(self): - self.toggleAll(False) - - def toggleShowAll(self): - self.toggleAll(True) - - def toggleAll(self, visible): - self.toggleMoveAbsolute(visible) - self.toggleMoveRelative(visible) - self.toggleMoveContinuous(visible) - self.toggleMoveToLimits(visible) - self.toggleEncoder(visible) - self.toggleStopMove(visible) - self.toggleHoming(visible) - self.toggleConfig(visible) - self.toggleStatus(visible) - - def toggleMoveAbsolute(self, visible): - if self.taurus_value.writeWidget() is not None: - self.taurus_value.writeWidget().setVisible(visible) - - def toggleMoveRelative(self, visible): - self.ui.btnGoToNegInc.setVisible(visible) - self.ui.inc.setVisible(visible) - self.ui.btnGoToPosInc.setVisible(visible) - - def toggleMoveContinuous(self, visible): - self.ui.btnGoToNegPress.setVisible(visible) - self.ui.btnGoToPosPress.setVisible(visible) - - def toggleMoveToLimits(self, visible): - self.ui.btnGoToNeg.setVisible(visible) - self.ui.btnGoToPos.setVisible(visible) - - def toggleEncoder(self, visible): - self.taurus_value_enc.setVisible(visible) - - def toggleStopMove(self, visible): - self.ui.btnStop.setVisible(visible) - - def toggleHoming(self, visible): - self.ui.btnHome.setVisible(visible) - - def toggleConfig(self, visible): - self.ui.btnCfg.setVisible(visible) - - def toggleStatus(self, visible): - self.ui.lblStatus.setVisible(visible) - - def dragEnterEvent(self, event): - event.accept() - - def dropEvent(self, event): - mimeData = event.mimeData() - if mimeData.hasFormat(TAURUS_DEV_MIME_TYPE): - model = str(mimeData.data(TAURUS_DEV_MIME_TYPE)) - elif mimeData.hasFormat(TAURUS_ATTR_MIME_TYPE): - model = str(mimeData.data(TAURUS_ATTR_MIME_TYPE)) - else: - model = str(mimeData.text()) - self.setModel(model) - - def keyPressEvent(self, key_event): - if key_event.key() == Qt.Qt.Key_Escape: - self.abort() - key_event.accept() - TaurusWidget.keyPressEvent(self, key_event) - - def buildBetterCfgDialogTitle(self): - while self.ui.btnCfg._dialog is None: - pass - model = self.getModel() - self.ui.btnCfg._dialog.setWindowTitle( - '%s config' % taurus.Factory().getDevice(model).getSimpleName()) - - @classmethod - def getQtDesignerPluginInfo(cls): - ret = TaurusWidget.getQtDesignerPluginInfo() - ret['module'] = 'taurus.qt.qtgui.extra_pool' - ret['group'] = 'Taurus Sardana' - ret['icon'] = ':/designer/extra_motor.png' - ret['container'] = False - return ret - - def showEvent(self, event): - TaurusWidget.showEvent(self, event) - try: - self.motor_dev.getAttribute('Position').enablePolling(force=True) - except AttributeError, e: - self.debug('Error in showEvent: %s', repr(e)) - - def hideEvent(self, event): - TaurusWidget.hideEvent(self, event) - try: - self.motor_dev.getAttribute('Position').disablePolling() - except AttributeError, e: - self.debug('Error in hideEvent: %s', repr(e)) - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # QT properties - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - @Qt.pyqtSlot() - def getModel(self): - return self.ui.motorGroupBox.getModel() - - @Qt.pyqtSlot("QString") - def setModel(self, model): - # DUE TO A BUG IN TAUGROUPBOX, WE NEED THE FULL MODEL NAME - try: - # In case the model is an attribute of a motor, get the device name - # TODO: When sardana is moved to Taurus 4 replace this line by - # taurushelper.isValidName(model, [TaurusElementType.Device]) - if not TangoDeviceNameValidator().isValid(model): - model = model.rpartition('/')[0] - model = taurus.Factory().getDevice(model).getFullName() - self.setMotor(model) - self.ui.motorGroupBox.setModel(model) - self.ui.motorGroupBox.setEnabled(True) - - self.taurus_value.setModel(model + '/Position') - - # DUE TO A BUG IN TAURUSVALUE, THAT DO NOT USE PARENT MODEL WE NEED - # TO ALWAYS SET THE MODEL - self.taurus_value.setUseParentModel(False) - - # THE FORCED APPLY HAS TO BE DONE AFTER THE MODEL IS SET, SO THE - # WRITEWIDGET IS AVAILABLE - if self.taurus_value.writeWidget() is not None: - self.taurus_value.writeWidget().setForcedApply(True) - - show_enc = self.taurus_value_enc.isVisible() - if self.has_encoder: - self.taurus_value_enc.setModel(model + '/Encoder') - self.taurus_value_enc.setUseParentModel(False) - self.taurus_value_enc.readWidget().setBgRole('none') - else: - self.taurus_value_enc.setModel(None) - show_enc = False - if not show_enc: - self.toggleEncoder(False) - - try: - self.unregisterConfigurableItem('MoveAbsolute') - self.unregisterConfigurableItem('Encoder') - except: - pass - self.registerConfigProperty(self.taurus_value.writeWidget( - ).isVisible, self.toggleMoveAbsolute, 'MoveAbsolute') - self.registerConfigProperty( - self.taurus_value_enc.isVisible, self.toggleEncoder, 'Encoder') - - # SINCE TAURUSLAUNCHERBUTTON HAS NOT THIS PROPERTY IN THE - # DESIGNER, WE MUST SET IT HERE - self.ui.btnCfg.setUseParentModel(True) - - # CONFIGURE A LISTENER IN ORDER TO UPDATE LIMIT SWITCHES STATES - self.limits_listener = LimitsListener() - self.limits_listener.updateLimits.connect( - self.updateLimits) - limits_visible = False - if self.has_limits: - limits_attribute = self.motor_dev.getAttribute( - 'Limit_switches') - limits_attribute.addListener(self.limits_listener) - # self.updateLimits(limits_attribute.read().value) - limits_visible = True - self.ui.btnMin.setVisible(limits_visible) - self.ui.btnMax.setVisible(limits_visible) - except Exception, e: - self.ui.motorGroupBox.setEnabled(False) - self.info('Error setting model "%s". Reason: %s' % - (model, repr(e))) - self.traceback() - - @Qt.pyqtSlot() - def resetModel(self): - self.ui.motorGroupBox.resetModel() - - @Qt.pyqtSlot() - def getShowContextMenu(self): - return self.show_context_menu - - @Qt.pyqtSlot() - def setShowContextMenu(self, showContextMenu): - self.show_context_menu = showContextMenu - - @Qt.pyqtSlot() - def resetShowContextMenu(self): - self.show_context_menu = True - - @Qt.pyqtSlot() - def getStepSize(self): - return self.ui.inc.value() - - @Qt.pyqtSlot(float) - def setStepSize(self, stepSize): - self.ui.inc.setValue(stepSize) - - @Qt.pyqtSlot() - def resetStepSize(self): - self.setStepSize(1) - - @Qt.pyqtSlot() - def getStepSizeIncrement(self): - return self.ui.inc.singleStep() - - @Qt.pyqtSlot(float) - def setStepSizeIncrement(self, stepSizeIncrement): - self.ui.inc.setSingleStep(stepSizeIncrement) - - @Qt.pyqtSlot() - def resetStepSizeIncrement(self): - self.setStepSizeIncrement(1) - - model = Qt.pyqtProperty("QString", getModel, setModel, resetModel) - stepSize = Qt.pyqtProperty( - "double", getStepSize, setStepSize, resetStepSize) - stepSizeIncrement = Qt.pyqtProperty( - "double", getStepSizeIncrement, setStepSizeIncrement, resetStepSizeIncrement) - - ########################################################################## # NEW APPROACH TO OPERATE POOL MOTORS FROM A TAURUS FORM INHERITTING DIRECTLY FROM TaurusVALUE # # AND USING PARTICULAR CLASSES THAT KNOW THEY ARE PART OF A TAURUSVALUE AND CAN INTERACT # @@ -812,7 +395,7 @@ class TaurusAttributeListener(Qt.QObject): If that is the case it emits a signal with the event's value. """ - eventReceivedSignal = Qt.pyqtSignal(object) + eventReceivedSignal = Qt.pyqtSignal(compat.PY_OBJECT) def __init__(self): Qt.QObject.__init__(self) @@ -820,7 +403,11 @@ def __init__(self): def eventReceived(self, evt_src, evt_type, evt_value): if evt_type not in [TaurusEventType.Change, TaurusEventType.Periodic]: return - value = evt_value.value + try: + value = evt_value.rvalue.magnitude + except AttributeError: + # spectrum/images or str attribute values are not Quantities + value = evt_value.rvalue self.eventReceivedSignal.emit(value) @@ -859,8 +446,8 @@ def __init__(self, parent=None, designMode=False): # I don't like this approach, there should be something like # self.lbl_alias.addAction(...) - self.lbl_alias.contextMenuEvent = lambda( - event): self.contextMenuEvent(event) + self.lbl_alias.contextMenuEvent = \ + lambda event: self.contextMenuEvent(event) # I' don't like this approach, there should be something like # self.lbl_alias.addToolTipCallback(self.calculate_extra_tooltip) @@ -873,11 +460,13 @@ def __init__(self, parent=None, designMode=False): self.lbl_alias.mouseMoveEvent = self.mouseMoveEvent def setExpertView(self, expertView): - btn_poweron_visible = expertView and self.taurusValueBuddy().hasPowerOn() + btn_poweron_visible = expertView \ + and self.taurusValueBuddy().hasPowerOn() self.btn_poweron.setVisible(btn_poweron_visible) @Qt.pyqtSlot() - @ProtectTaurusMessageBox(msg='An error occurred trying to write PowerOn Attribute.') + @ProtectTaurusMessageBox( + msg='An error occurred trying to write PowerOn Attribute.') def setPowerOn(self): motor_dev = self.taurusValueBuddy().motor_dev if motor_dev is not None: @@ -914,7 +503,7 @@ def calculateExtendedTooltip(self, cache=False): status_info = '' motor_dev = self.taurusValueBuddy().motor_dev if motor_dev is not None: - status = motor_dev.getAttribute('Status').read().value + status = motor_dev.getAttribute('Status').read().rvalue # MAKE IT LOOK LIKE THE STANDARD TABLE FOR TAURUS TOOLTIPS status_lines = status.split('\n') status_info = '
Status:' + \ @@ -937,7 +526,7 @@ def contextMenuEvent(self, event): action_tango_attributes = Qt.QAction(self) action_tango_attributes.setIcon( - getIcon(':/categories/preferences-system.svg')) + Qt.QIcon("categories:preferences-system.svg")) action_tango_attributes.setText('Tango Attributes') menu.addAction(action_tango_attributes) action_tango_attributes.triggered.connect( @@ -989,19 +578,19 @@ def __init__(self, parent=None, designMode=False): limits_layout.setContentsMargins(0, 0, 0, 0) limits_layout.setSpacing(0) - self.btn_lim_neg = Qt.QPushButton() - self.btn_lim_neg.setToolTip('Negative Limit') - # self.btn_lim_neg.setEnabled(False) - self.prepare_button(self.btn_lim_neg) - self.btn_lim_neg.setIcon(getIcon(':/actions/list-remove.svg')) - limits_layout.addWidget(self.btn_lim_neg) + self.lim_neg = Qt.QLabel() + self.prepare_limit(self.lim_neg) + self._config_limit(Limit.Negative, + hw_lim=LimitInfo(), + sw_lim=LimitInfo()) + limits_layout.addWidget(self.lim_neg) - self.btn_lim_pos = Qt.QPushButton() - self.btn_lim_pos.setToolTip('Positive Limit') - # self.btn_lim_pos.setEnabled(False) - self.prepare_button(self.btn_lim_pos) - self.btn_lim_pos.setIcon(getIcon(':/actions/list-add.svg')) - limits_layout.addWidget(self.btn_lim_pos) + self.lim_pos = Qt.QLabel() + self.prepare_limit(self.lim_pos) + self._config_limit(Limit.Positive, + hw_lim=LimitInfo(), + sw_lim=LimitInfo()) + limits_layout.addWidget(self.lim_pos) self.layout().addLayout(limits_layout, 0, 0) @@ -1015,7 +604,7 @@ def __init__(self, parent=None, designMode=False): self.btn_stop = Qt.QPushButton() self.btn_stop.setToolTip('Stops the motor') self.prepare_button(self.btn_stop) - self.btn_stop.setIcon(getIcon(':/actions/media_playback_stop.svg')) + self.btn_stop.setIcon(Qt.QIcon("actions:media_playback_stop.svg")) self.layout().addWidget(self.btn_stop, 0, 2) self.btn_stop.clicked.connect(self.abort) @@ -1028,14 +617,8 @@ def __init__(self, parent=None, designMode=False): # self.cb_expertRead.addItems(['Enc']) #self.layout().addWidget(self.cb_expertRead, 1, 0) - self.lbl_enc = Qt.QLabel('Encoder') - self.layout().addWidget(self.lbl_enc, 1, 0) - - self.lbl_enc_read = TaurusLabel() - self.lbl_enc_read.setBgRole('none') - self.lbl_enc_read.setSizePolicy(Qt.QSizePolicy( - Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed)) - self.layout().addWidget(self.lbl_enc_read, 1, 1) + self.lbl_enc = None + self.lbl_enc_read = None # Align everything on top self.layout().addItem(Qt.QSpacerItem( @@ -1046,8 +629,6 @@ def __init__(self, parent=None, designMode=False): # SO WE ASSUME 'expertview is FALSE' AND WE HAVE TO AVOID self.setExpertView :-( # WOULD BE NICE THAT THE taurusValueBuddy COULD EMIT THE PROPER # SIGNAL... - self.lbl_enc.setVisible(False) - self.lbl_enc_read.setVisible(False) def eventFilter(self, obj, event): if event.type() == Qt.QEvent.MouseButtonDblClick: @@ -1070,13 +651,10 @@ def abort(self): motor_dev.abort() def setExpertView(self, expertView): + if self.lbl_enc_read is None: + return self.lbl_enc.setVisible(False) self.lbl_enc_read.setVisible(False) - if self.taurusValueBuddy().motor_dev is not None: - hw_limits = self.taurusValueBuddy().hasHwLimits() - self.btn_lim_neg.setEnabled(hw_limits) - self.btn_lim_pos.setEnabled(hw_limits) - if expertView and self.taurusValueBuddy().motor_dev is not None: encoder = self.taurusValueBuddy().hasEncoder() self.lbl_enc.setVisible(encoder) @@ -1091,6 +669,65 @@ def prepare_button(self, btn): btn.setMaximumSize(25, 25) btn.setText('') + def prepare_limit(self, lbl): + lbl_policy = Qt.QSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed) + lbl_policy.setHorizontalStretch(0) + lbl_policy.setVerticalStretch(0) + lbl.setSizePolicy(lbl_policy) + lbl.setText('') + + def _config_limit(self, limit, hw_lim, sw_lim): + """Configure sub-widgets according to limit information + + :param limit: which limit (negative or positive) + :type limit: `Limit` + :param hw_lim: hardware limit information + :type hw_lim: `LimitInfo` + :param hw_lim: software limit information + :type hw_lim: `LimitInfo` + """ + if limit == Limit.Negative: + widget = self.lim_neg + icon = Qt.QIcon("designer:minus.png") + elif limit == Limit.Positive: + widget = self.lim_pos + icon = Qt.QIcon("designer:plus.png") + else: + raise ValueError("{} wrong limit".format(limit)) + widget.setStyleSheet('') + if hw_lim.exist or sw_lim.exist: + pixmap = Qt.QPixmap(icon.pixmap(Qt.QSize(32, 32), + Qt.QIcon.Active)) + if hw_lim.active or sw_lim.active: + colour = DEVICE_STATE_PALETTE.qtStyleSheet( + PyTango.DevState.ALARM) + widget.setStyleSheet(colour) + else: + pixmap = Qt.QPixmap(icon.pixmap(Qt.QSize(32, 32), + Qt.QIcon.Disabled)) + tool_tip = hw_lim.status + " " + sw_lim.status + widget.setToolTip(tool_tip) + widget.setPixmap(pixmap) + + def _create_encoder(self): + if self.taurusValueBuddy().hasEncoder() and self.lbl_enc_read is None: + self.lbl_enc = Qt.QLabel('Encoder') + self.layout().addWidget(self.lbl_enc, 1, 0) + + self.lbl_enc_read = TaurusLabel() + self.lbl_enc_read.setBgRole('none') + self.lbl_enc_read.setSizePolicy(Qt.QSizePolicy( + Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Fixed)) + self.layout().addWidget(self.lbl_enc_read, 1, 1) + + # Align everything on top + self.layout().addItem(Qt.QSpacerItem( + 1, 1, Qt.QSizePolicy.Minimum, + Qt.QSizePolicy.Expanding), 2, 0, 1, 2) + + self.lbl_enc.setVisible(False) + self.lbl_enc_read.setVisible(False) + def setModel(self, model): if hasattr(self, 'taurusValueBuddy'): try: @@ -1101,11 +738,15 @@ def setModel(self, model): if model in (None, ''): TaurusWidget.setModel(self, model) self.lbl_read.setModel(model) - self.lbl_enc_read.setModel(model) + if self.lbl_enc_read is not None: + self.lbl_enc_read.setModel(model) return TaurusWidget.setModel(self, model + '/Position') self.lbl_read.setModel(model + '/Position') - self.lbl_enc_read.setModel(model + '/Encoder') + if (self.taurusValueBuddy().hasEncoder() + and self.lbl_enc_read is None): + self._create_encoder() + self.lbl_enc_read.setModel(model + '/Encoder') # Handle User/Expert view self.setExpertView(self.taurusValueBuddy()._expertView) self.taurusValueBuddy().expertViewChanged.connect( @@ -1156,13 +797,13 @@ def __init__(self, parent=None, designMode=False): self.btn_step_down.setToolTip('Decrements motor position') self.prepare_button(self.btn_step_down) self.btn_step_down.setIcon( - getIcon(':/actions/media_playback_backward.svg')) + Qt.QIcon("actions:media_playback_backward.svg")) self.qw_write_relative.layout().addWidget(self.btn_step_down) self.btn_step_up = Qt.QPushButton() self.btn_step_up.setToolTip('Increments motor position') self.prepare_button(self.btn_step_up) - self.btn_step_up.setIcon(getIcon(':/actions/media_playback_start.svg')) + self.btn_step_up.setIcon(Qt.QIcon("actions:media_playback_start.svg")) self.qw_write_relative.layout().addWidget(self.btn_step_up) self.layout().addWidget(self.qw_write_relative, 0, 0) @@ -1172,7 +813,9 @@ def __init__(self, parent=None, designMode=False): self.cbAbsoluteRelativeChanged) self.cbAbsoluteRelative.addItems(['Abs', 'Rel']) self.layout().addWidget(self.cbAbsoluteRelative, 0, 1) - + self.registerConfigProperty( + self.cbAbsoluteRelative.currentIndex, + self.cbAbsoluteRelative.setCurrentIndex, 'AbsRelIndex') # WITH THE COMPACCT VIEW FEATURE, BETTER TO HAVE IT IN THE READ WIDGET # WOULD BE BETTER AS AN 'EXTRA WIDGET' (SOME DAY...) #self.btn_stop = Qt.QPushButton() @@ -1192,7 +835,7 @@ def __init__(self, parent=None, designMode=False): self.btn_to_neg.setToolTip( 'Moves the motor towards the Negative Software Limit') self.prepare_button(self.btn_to_neg) - self.btn_to_neg.setIcon(getIcon(':/actions/media_skip_backward.svg')) + self.btn_to_neg.setIcon(Qt.QIcon("actions:media_skip_backward.svg")) btns_layout.addWidget(self.btn_to_neg) self.btn_to_neg_press = Qt.QPushButton() @@ -1200,7 +843,7 @@ def __init__(self, parent=None, designMode=False): 'Moves the motor (while pressed) towards the Negative Software Limit') self.prepare_button(self.btn_to_neg_press) self.btn_to_neg_press.setIcon( - getIcon(':/actions/media_seek_backward.svg')) + Qt.QIcon("actions:media_seek_backward.svg")) btns_layout.addWidget(self.btn_to_neg_press) self.btn_to_pos_press = Qt.QPushButton() @@ -1208,14 +851,14 @@ def __init__(self, parent=None, designMode=False): self.btn_to_pos_press.setToolTip( 'Moves the motor (while pressed) towards the Positive Software Limit') self.btn_to_pos_press.setIcon( - getIcon(':/actions/media_seek_forward.svg')) + Qt.QIcon("actions:media_seek_forward.svg")) btns_layout.addWidget(self.btn_to_pos_press) self.btn_to_pos = Qt.QPushButton() self.btn_to_pos.setToolTip( 'Moves the motor towards the Positive Software Limit') self.prepare_button(self.btn_to_pos) - self.btn_to_pos.setIcon(getIcon(':/actions/media_skip_forward.svg')) + self.btn_to_pos.setIcon(Qt.QIcon("actions:media_skip_forward.svg")) btns_layout.addWidget(self.btn_to_pos) btns_layout.addItem(Qt.QSpacerItem( @@ -1285,6 +928,8 @@ def eventFilter(self, obj, event): def cbAbsoluteRelativeChanged(self, abs_rel_option): abs_visible = abs_rel_option == 'Abs' rel_visible = abs_rel_option == 'Rel' + if rel_visible: + self.resetPendingOperations() self.le_write_absolute.setVisible(abs_visible) self.qw_write_relative.setVisible(rel_visible) @@ -1301,7 +946,8 @@ def goRelative(self, direction): motor_dev = self.taurusValueBuddy().motor_dev if motor_dev is not None: increment = direction * float(self.cb_step.currentText()) - position = float(motor_dev.getAttribute('Position').read().value) + position = float( + motor_dev.getAttribute('Position').read().rvalue.magnitude) target_position = position + increment motor_dev.getAttribute('Position').write(target_position) @@ -1367,7 +1013,7 @@ def setModel(self, model): self.le_write_absolute.setModel(model) return TaurusWidget.setModel(self, model + '/Position') - self.le_write_absolute.setModel(model + '/Position') + self.le_write_absolute.setModel(model + '/Position#wvalue.magnitude') # Handle User/Expert View self.setExpertView(self.taurusValueBuddy()._expertView) @@ -1384,6 +1030,37 @@ def keyPressEvent(self, key_event): def emitEditingFinished(self): self.applied.emit() + def _config_limit(self, limit, hw_lim, sw_lim): + """Configure sub-widgets according to limit information + + :param limit: which limit (negative or positive) + :type limit: `Limit` + :param hw_lim: hardware limit information + :type hw_lim: `LimitInfo` + :param hw_lim: software limit information + :type hw_lim: `LimitInfo` + """ + if limit == Limit.Negative: + step_widget = self.btn_step_down + to_widget = self.btn_to_neg + to_press_widget = self.btn_to_neg_press + elif limit == Limit.Positive: + step_widget = self.btn_step_up + to_widget = self.btn_to_pos + to_press_widget = self.btn_to_pos_press + else: + raise ValueError("{} wrong limit".format(limit)) + active = hw_lim.active or sw_lim.active + style_sheet = "" + if active: + style_sheet = 'QPushButton{{{}}}'.format( + DEVICE_STATE_PALETTE.qtStyleSheet(PyTango.DevState.ALARM)) + step_widget.setEnabled(not active) + allow_to = not active and sw_lim.exist + to_widget.setEnabled(allow_to) + to_press_widget.setEnabled(allow_to) + step_widget.setStyleSheet(style_sheet) + ################################################## # UNITS WIDGET # @@ -1426,6 +1103,8 @@ def __init__(self, parent=None, designMode=False): self.setUnitsWidgetClass(PoolMotorTVUnitsWidget) self.motor_dev = None self._expertView = False + self.registerConfigProperty( + self.getExpertView, self.setExpertView, 'ExpertView') self.limits_listener = None self.poweron_listener = None self.status_listener = None @@ -1436,6 +1115,9 @@ def setExpertView(self, expertView): self._expertView = expertView self.expertViewChanged.emit(expertView) + def getExpertView(self): + return self._expertView + def minimumHeight(self): return None # @todo: UGLY HACK to avoid subwidgets being forced to minimumheight=20 @@ -1494,8 +1176,9 @@ def setModel(self, model): if self.hasHwLimits(): self.limits_listener.eventReceivedSignal.connect( self.updateLimits) - self.motor_dev.getAttribute( - 'Limit_Switches').addListener(self.limits_listener) + self._limit_switches = self.motor_dev.getAttribute( + 'Limit_Switches') + self._limit_switches.addListener(self.limits_listener) # CONFIGURE AN EVENT RECEIVER IN ORDER TO PROVIDE POWERON <- # True/False EXPERT OPERATION @@ -1503,27 +1186,27 @@ def setModel(self, model): if self.hasPowerOn(): self.poweron_listener.eventReceivedSignal.connect( self.updatePowerOn) - self.motor_dev.getAttribute( - 'PowerOn').addListener(self.poweron_listener) + self._poweron = self.motor_dev.getAttribute('PowerOn') + self._poweron.addListener(self.poweron_listener) # CONFIGURE AN EVENT RECEIVER IN ORDER TO UPDATED STATUS TOOLTIP self.status_listener = TaurusAttributeListener() self.status_listener.eventReceivedSignal.connect( self.updateStatus) - self.motor_dev.getAttribute( - 'Status').addListener(self.status_listener) + self._status = self.motor_dev.getAttribute('Status') + self._status.addListener(self.status_listener) # CONFIGURE AN EVENT RECEIVER IN ORDER TO ACTIVATE LIMIT BUTTONS ON # SOFTWARE LIMITS self.position_listener = TaurusAttributeListener() self.position_listener.eventReceivedSignal.connect( self.updatePosition) - self.motor_dev.getAttribute( - 'Position').addListener(self.position_listener) + self._position = self.motor_dev.getAttribute('Position') + self._position.addListener(self.position_listener) self.motor_dev.getAttribute('Position').enablePolling(force=True) self.setExpertView(self._expertView) - except Exception, e: + except Exception as e: self.warning("Exception caught while setting model: %s", repr(e)) self.motor_dev = None return @@ -1543,67 +1226,20 @@ def hasHwLimits(self): def updateLimits(self, limits, position=None): if isinstance(limits, dict): limits = limits["limits"] - limits = list(limits) - HOME = 0 - POS = 1 - NEG = 2 - # Check also if the software limit is 'active' - if self.motor_dev is not None: - position_attribute = self.motor_dev.getAttribute('Position') - if position is None: - position = position_attribute.read().value - max_value_str = position_attribute.max_value - min_value_str = position_attribute.min_value - try: - max_value = float(max_value_str) - limits[POS] = limits[POS] or (position >= max_value) - except: - pass - try: - min_value = float(min_value_str) - limits[NEG] = limits[NEG] or (position <= min_value) - except: - pass + pos_hw_lim = eval_hw_limit(Limit.Positive, limits[Limit.Positive]) + neg_hw_lim = eval_hw_limit(Limit.Negative, limits[Limit.Negative]) + position_attr = self.motor_dev.getAttribute('Position') + pos_sw_lim = eval_sw_limit(Limit.Positive, position_attr) + neg_sw_lim = eval_sw_limit(Limit.Negative, position_attr) + + read_widget = self.readWidget(followCompact=True) + read_widget._config_limit(Limit.Positive, pos_hw_lim, pos_sw_lim) + read_widget._config_limit(Limit.Negative, neg_hw_lim, neg_sw_lim) - pos_lim = limits[POS] - - pos_btnstylesheet = '' - enabled = True - if pos_lim: - pos_btnstylesheet = 'QPushButton{%s}' % DEVICE_STATE_PALETTE.qtStyleSheet( - PyTango.DevState.ALARM) - enabled = False - self.readWidget(followCompact=True).btn_lim_pos.setStyleSheet( - pos_btnstylesheet) - - self.writeWidget(followCompact=True).btn_step_up.setEnabled(enabled) - self.writeWidget(followCompact=True).btn_step_up.setStyleSheet( - pos_btnstylesheet) - enabled = enabled and self.motor_dev.getAttribute( - 'Position').max_value.lower() != 'not specified' - self.writeWidget(followCompact=True).btn_to_pos.setEnabled(enabled) - self.writeWidget( - followCompact=True).btn_to_pos_press.setEnabled(enabled) - - neg_lim = limits[NEG] - neg_btnstylesheet = '' - enabled = True - if neg_lim: - neg_btnstylesheet = 'QPushButton{%s}' % DEVICE_STATE_PALETTE.qtStyleSheet( - PyTango.DevState.ALARM) - enabled = False - self.readWidget(followCompact=True).btn_lim_neg.setStyleSheet( - neg_btnstylesheet) - - self.writeWidget(followCompact=True).btn_step_down.setEnabled(enabled) - self.writeWidget(followCompact=True).btn_step_down.setStyleSheet( - neg_btnstylesheet) - enabled = enabled and self.motor_dev.getAttribute( - 'Position').min_value.lower() != 'not specified' - self.writeWidget(followCompact=True).btn_to_neg.setEnabled(enabled) - self.writeWidget( - followCompact=True).btn_to_neg_press.setEnabled(enabled) + write_widget = self.writeWidget(followCompact=True) + write_widget._config_limit(Limit.Positive, pos_hw_lim, pos_sw_lim) + write_widget._config_limit(Limit.Negative, neg_hw_lim, neg_sw_lim) def updatePowerOn(self, poweron='__no_argument__'): if poweron == '__no_argument__': @@ -1631,10 +1267,10 @@ def updatePosition(self, position='__no_argument__'): # we just want to check if any software limit is 'active' # and updateLimits takes care of it if self.motor_dev is not None: - limit_switches = [False, False, False] + limit_switches = [None, None, None] if self.hasHwLimits(): limit_switches = self.motor_dev.getAttribute( - 'Limit_switches').read().value + 'Limit_switches').read().rvalue # print "update limits", limit_switches self.updateLimits(limit_switches, position=position) @@ -1718,17 +1354,6 @@ def main(): # tests.append(3) # tests.append(4) - # 1) Test PoolMotorSlim motor widget - form_pms = TaurusForm() - pms_widget_class = 'sardana.taurus.qt.qtgui.extra_pool.PoolMotorSlim' - pms_tgclass_map = {'SimuMotor': (pms_widget_class, (), {}), - 'Motor': (pms_widget_class, (), {}), - 'PseudoMotor': (pms_widget_class, (), {})} - form_pms.setCustomWidgetMap(pms_tgclass_map) - if 1 in tests: - form_pms.setModel(models) - w.layout().addWidget(form_pms) - # 2) Test PoolMotorTV motor widget form_tv = TaurusForm() form_tv.setModifiableByUser(True) @@ -1752,13 +1377,6 @@ def main(): motor_widget.setModel(motor) w.layout().addWidget(motor_widget) - # 4) Test Stand-Alone PoolMotorSlim widget - if 4 in tests: - for motor in models: - motor_widget = PoolMotorSlim() - motor_widget.setModel(motor) - w.layout().addWidget(motor_widget) - w.show() sys.exit(app.exec_()) diff --git a/src/sardana/taurus/qt/qtgui/extra_pool/ui/PoolMotorSlim.ui b/src/sardana/taurus/qt/qtgui/extra_pool/ui/PoolMotorSlim.ui deleted file mode 100644 index 9233390342..0000000000 --- a/src/sardana/taurus/qt/qtgui/extra_pool/ui/PoolMotorSlim.ui +++ /dev/null @@ -1,354 +0,0 @@ - - - PoolMotorSlim - - - - 0 - 0 - 549 - 212 - - - - Dialog - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - false - - - - 0 - - - 0 - - - - - - 30 - 16777215 - - - - Stops the motor - - - S - - - - - - - 0 - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Negative limit - - - - - - - true - - - false - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Moves the motor towards negative limit - - - |< - - - false - - - false - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Moves the motor towards negative limit while pressed - - - « - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Decrements motor position <inc> units - - - < - - - - - - - - 80 - 16777215 - - - - 10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104.000000000000000 - - - 1.000000000000000 - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Increments motor position <inc> units - - - > - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Moves the motor towards positive limit while pressed - - - » - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Moves the motor towards positive limit - - - >| - - - - - - - - 0 - 0 - - - - - 20 - 16777215 - - - - Positive limit - - - + - - - false - - - - - - - - - - 0 - 0 - - - - - - - - - 30 - 16777215 - - - - Configures the motor - - - Cfg - - - - - - - /Status - - - true - - - - - - - - 30 - 16777215 - - - - Goes Home - - - H - - - - - - - - - - - TaurusLauncherButton - QPushButton -
taurus.qt.qtgui.button
-
- - TaurusWidget - QWidget -
taurus.qt.qtgui.container
- 1 -
- - TaurusLabel - QLabel -
taurus.qt.qtgui.display
-
- - TaurusGroupBox - QGroupBox -
taurus.qt.qtgui.container
- 1 -
-
- - -
diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/cmdline.py b/src/sardana/taurus/qt/qtgui/extra_sardana/cmdline.py index 8c85078b34..336b4235e1 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/cmdline.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/cmdline.py @@ -29,7 +29,6 @@ __docformat__ = 'restructuredtext' from taurus.external.qt import Qt -from taurus.qt.qtgui.resource import getIcon, getThemeIcon class CommandLineHistory(list): @@ -63,14 +62,13 @@ def __init__(self, qt_parent=None, designMode=False): self._cmdLine.setEditable(True) self._applyButton = Qt.QToolButton() - self._applyButton.setIcon( - getIcon(":/actions/media_playback_start.svg")) + self._applyButton.setIcon(Qt.QIcon("actions:media_playback_start.svg")) self._stopButton = Qt.QToolButton() - self._stopButton.setIcon(getIcon(":/actions/media_playback_stop.svg")) + self._stopButton.setIcon(Qt.QIcon("actions:media_playback_stop.svg")) self._clearButton = Qt.QToolButton() - self._clearButton.setIcon(getThemeIcon("edit-clear")) + self._clearButton.setIcon(Qt.QIcon.fromTheme("edit-clear")) l.addWidget(self._detailsButton, 0) l.addWidget(self._cmdLine, 1) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/controllertree.py b/src/sardana/taurus/qt/qtgui/extra_sardana/controllertree.py index d49fd19c48..be09324393 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/controllertree.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/controllertree.py @@ -29,16 +29,12 @@ __docformat__ = 'restructuredtext' -import sys -import os - import taurus.core from taurus.core.util.enumeration import Enumeration from taurus.external.qt import Qt from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_MIME_TYPE, TAURUS_MODEL_LIST_MIME_TYPE from taurus.qt.qtcore.model import TaurusBaseTreeItem, TaurusBaseModel, TaurusBaseProxyModel from taurus.qt.qtgui.tree import TaurusBaseTreeWidget -from taurus.qt.qtgui.resource import getThemeIcon, getIcon PoolControllerView = Enumeration( "PoolControllerView", ("ControllerModule", "ControllerClass", "Unknown")) @@ -46,10 +42,10 @@ def getElementTypeIcon(t): if t == PoolControllerView.ControllerModule: - return getIcon(":/python-file.png") + return Qt.QIcon(":python-file.png") elif t == PoolControllerView.ControllerClass: - return getIcon(":/python.png") - return getIcon(":/tango.png") + return Qt.QIcon(":python.png") + return Qt.QIcon(":tango.png") def getElementTypeSize(t): @@ -94,7 +90,7 @@ def toolTip(self): return "The controller module '%s'" % self.display() def icon(self): - return getIcon(":/python-file.png") + return Qt.QIcon(":python-file.png") class ControllerTreeItem(ControllerBaseTreeItem): @@ -113,7 +109,7 @@ def toolTip(self): return self._itemData.doc def icon(self): - return getIcon(":/python.png") + return Qt.QIcon(":python.png") class ControllerBaseModel(TaurusBaseModel): @@ -170,11 +166,11 @@ def mimeData(self, indexes): mime_data_item = tree_item.mimeData(index) if mime_data_item is None: continue - data.append(mime_data_item) - ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, "\r\n".join(data)) - ret.setText(", ".join(data)) + data.append(bytes(mime_data_item, encoding='utf8')) + ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, b"\r\n".join(data)) + ret.setText(", ".join(map(str, data))) if len(data) == 1: - ret.setData(TAURUS_MODEL_MIME_TYPE, str(data[0])) + ret.setData(TAURUS_MODEL_MIME_TYPE, data[0]) return ret def pyData(self, index, role): @@ -259,13 +255,13 @@ class ControllerClassTreeWidget(TaurusBaseTreeWidget): KnownPerspectives = {PoolControllerView.ControllerModule: { "label": "By module", - "icon": ":/python-file.png", + "icon": ":python-file.png", "tooltip": "View by controller module", "model": [ControllerModuleModelProxy, ControllerModuleModel], }, PoolControllerView.ControllerClass: { "label": "By controller", - "icon": ":/python.png", + "icon": ":python.png", "tooltip": "View by controller class", "model": [PlainControllerModelProxy, PlainControllerModel], } @@ -315,7 +311,7 @@ def main_ControllerClassSelecionDialog(pool, perspective=PoolControllerView.Cont model_name=pool, perspective=perspective) if w.result() == Qt.QDialog.Accepted: - print w.getSelectedMacros() + print(w.getSelectedMacros()) return w diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/environment.py b/src/sardana/taurus/qt/qtgui/extra_sardana/environment.py index 19b1509e82..5cc583f177 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/environment.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/environment.py @@ -40,7 +40,7 @@ class SardanaEnvironmentTreeWidget(TaurusBaseTreeWidget): KnownPerspectives = {"Type": { "label": "By key", - "icon": ":/python-file.png", + "icon": ":python-file.png", "tooltip": "View elements by key", "model": [SardanaEnvironmentModel], }, @@ -55,7 +55,7 @@ def getQtDesignerPluginInfo(cls): ret = TaurusBaseTreeWidget.getQtDesignerPluginInfo() ret['module'] = 'taurus.qt.qtgui.extra_sardana' ret['group'] = 'Taurus Sardana' - ret['icon'] = ":/designer/listview.png" + ret['icon'] = "designer:listview.png" return ret diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/expdescription.py b/src/sardana/taurus/qt/qtgui/extra_sardana/expdescription.py index deb8ba33a5..ed201145c8 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/expdescription.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/expdescription.py @@ -30,18 +30,21 @@ import json -from taurus.external.qt import Qt, QtCore, QtGui +from taurus.external.qt import Qt, QtCore, QtGui, compat import copy import taurus import taurus.core from taurus.qt.qtgui.base import TaurusBaseWidget -from taurus.qt.qtgui import resource from sardana.taurus.qt.qtcore.tango.sardana.model import SardanaBaseProxyModel, SardanaTypeTreeItem +from sardana.sardanadefs import ElementType, TYPE_ACQUIRABLE_ELEMENTS from taurus.qt.qtgui.util.ui import UILoadable -# Using a plain model and filtering and checking 'Acquirable' in item.itemData().interfaces is more elegant, but things don't get properly sorted... -#from taurus.qt.qtcore.tango.sardana.model import SardanaElementPlainModel +# Using a plain model and filtering and checking +# 'Acquirable' in item.itemData().interfaces is more elegant, +# but things don't get properly sorted... + +# from taurus.qt.qtcore.tango.sardana.model import SardanaElementPlainModel def _to_fqdn(name, logger=None): @@ -87,7 +90,6 @@ class SardanaAcquirableProxyModel(SardanaBaseProxyModel): # 'TwoDExpChannel', 'ComChannel', 'IORegister', 'PseudoMotor', # 'PseudoCounter'] - from sardana.sardanadefs import ElementType, TYPE_ACQUIRABLE_ELEMENTS ALLOWED_TYPES = [ElementType[t] for t in TYPE_ACQUIRABLE_ELEMENTS] def filterAcceptsRow(self, sourceRow, sourceParent): @@ -147,7 +149,7 @@ def find_diff(first, second): idiff = 'Error on processing' if len(idiff) > 0: diff[key] = idiff - elif type(value1) == list and key.lower() not in SKIPLIST: + elif isinstance(value1, list) and key.lower() not in SKIPLIST: ldiff = [] for v1, v2 in zip(value1, value2): try: @@ -174,10 +176,9 @@ class ExpDescriptionEditor(Qt.QWidget, TaurusBaseWidget): ''' createExpConfChangedDialog = Qt.pyqtSignal() - experimentConfigurationChanged = Qt.pyqtSignal(object) + experimentConfigurationChanged = Qt.pyqtSignal(compat.PY_OBJECT) - def __init__(self, parent=None, door=None, plotsButton=True, - autoUpdate=False): + def __init__(self, parent=None, door=None, autoUpdate=False): Qt.QWidget.__init__(self, parent) TaurusBaseWidget.__init__(self, 'ExpDescriptionEditor') self.loadUi() @@ -240,32 +241,6 @@ def __init__(self, parent=None, door=None, plotsButton=True, self.ui.choosePathBT.clicked.connect( self.onChooseScanDirButtonClicked) - self.__plotManager = None - tooltip = None - - # TODO: Disable show scan button since scan plot have to be - # adapted to support QT5 - # -------------------------------------------------------------------- - from taurus.external.qt import PYQT4, API - if not PYQT4: - self.debug('Show plots is only supported with PyQt4 for now') - plotsButton = False - tooltip = "Show/Hide plots is not ready for %s" % API - # -------------------------------------------------------------------- - - icon = resource.getIcon(":/actions/view.svg") - measGrpTab = self.ui.tabWidget.widget(0) - self.togglePlotsAction = Qt.QAction(icon, "Show/Hide plots", self) - if tooltip is not None: - self.togglePlotsAction.setToolTip(tooltip) - self.togglePlotsAction.setCheckable(True) - self.togglePlotsAction.setChecked(False) - self.togglePlotsAction.setEnabled(plotsButton) - measGrpTab.addAction(self.togglePlotsAction) - measGrpTab.setContextMenuPolicy(Qt.Qt.ActionsContextMenu) - self.togglePlotsAction.toggled.connect(self.onPlotsButtonToggled) - self.ui.plotsButton.setDefaultAction(self.togglePlotsAction) - if door is not None: self.setModel(door) @@ -432,7 +407,7 @@ def setModel(self, model): if door is None: return # @todo: get the tghost from the door model instead - tghost = taurus.Database().getNormalName() + tghost = taurus.Authority().getNormalName() msname = door.macro_server.getFullName() self.ui.taurusModelTree.setModel(tghost) self.ui.sardanaElementTree.setModel(msname) @@ -456,18 +431,18 @@ def _reloadConf(self, force=False): self._dirtyMntGrps = set() # set a list of available channels avail_channels = {} - for ch_info in door.macro_server.getExpChannelElements().values(): + for ch_info in \ + list(door.macro_server.getExpChannelElements().values()): avail_channels[ch_info.full_name] = ch_info.getData() self.ui.channelEditor.getQModel().setAvailableChannels(avail_channels) # set a list of available triggers avail_triggers = {'software': {"name": "software"}} tg_elements = door.macro_server.getElementsOfType('TriggerGate') - for tg_info in tg_elements.values(): + for tg_info in list(tg_elements.values()): avail_triggers[tg_info.full_name] = tg_info.getData() self.ui.channelEditor.getQModel().setAvailableTriggers(avail_triggers) self.experimentConfigurationChanged.emit(copy.deepcopy(conf)) - def _setDirty(self, dirty): self._dirty = dirty self._updateButtonBox() @@ -501,7 +476,7 @@ def setLocalConfig(self, conf): # set the measurement group ComboBox self.ui.activeMntGrpCB.clear() mntGrpLabels = [] - for _, mntGrpConf in self._localConfig['MntGrpConfigs'].items(): + for _, mntGrpConf in list(self._localConfig['MntGrpConfigs'].items()): # get labels to visualize names with lower and upper case mntGrpLabels.append(mntGrpConf['label']) self.ui.activeMntGrpCB.addItems(sorted(mntGrpLabels)) @@ -544,7 +519,7 @@ def writeExperimentConfiguration(self, ask=True): conf = self.getLocalConfig() # make sure that no empty measurement groups are written - for mgname, mgconfig in conf.get('MntGrpConfigs', {}).items(): + for mgname, mgconfig in list(conf.get('MntGrpConfigs', {}).items()): if mgconfig is not None and not mgconfig.get('controllers'): mglabel = mgconfig['label'] Qt.QMessageBox.information(self, "Empty Measurement group", @@ -610,13 +585,17 @@ def createMntGrp(self): # check that the given name is not an existing pool element ms = self.getModelObj().macro_server poolElementNames = [ - v.name for v in ms.getElementsWithInterface("PoolElement").values()] + v.name for v in + list(ms.getElementsWithInterface("PoolElement").values())] while mntGrpName in poolElementNames: + msg = ("The name '%s' already is used for another pool element. " + "Please Choose a different one." % mntGrpName) Qt.QMessageBox.warning(self, "Cannot create Measurement group", - "The name '%s' already is used for another pool element. Please Choose a different one." % mntGrpName, - Qt.QMessageBox.Ok) - mntGrpName, ok = Qt.QInputDialog.getText(self, "New Measurement Group", - "Enter a name for the new measurement Group", + msg, Qt.QMessageBox.Ok) + msg = "Enter a name for the new measurement Group" + mntGrpName, ok = Qt.QInputDialog.getText(self, + "New Measurement Group", + msg, Qt.QLineEdit.Normal, mntGrpName) if not ok: @@ -624,9 +603,11 @@ def createMntGrp(self): mntGrpName = str(mntGrpName) # check that the measurement group is not already in the localConfig + msg = ('A measurement group named "%s" already exists. A new one ' + 'will not be created' % mntGrpName) if mntGrpName in self._localConfig['MntGrpConfigs']: Qt.QMessageBox.warning(self, "%s already exists" % mntGrpName, - 'A measurement group named "%s" already exists. A new one will not be created' % mntGrpName) + msg) return # add an empty configuration dictionary to the local config @@ -694,17 +675,6 @@ def onPreScanSnapshotChanged(self, items): self._localConfig['PreScanSnapshot'] = preScanList self._setDirty(True) - def onPlotsButtonToggled(self, checked): - if checked: - from sardana.taurus.qt.qtgui.macrolistener import \ - DynamicPlotManager - self.__plotManager = DynamicPlotManager(self) - self.__plotManager.setModel(self.getModelName()) - else: - self.__plotManager.removePanels() - self.__plotManager.setModel(None) - self.__plotManager = None - def demo(model=None, autoUpdate=False): """Experiment configuration""" @@ -736,6 +706,7 @@ def main(): parser.add_option('--auto-update', dest='auto_update', action='store_true', help='Set auto update of experiment configuration') + app = Application(app_name="Exp. Description demo", app_version="1.0", org_domain="Sardana", org_name="Tango community", cmd_line_parser=parser) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/macrotree.py b/src/sardana/taurus/qt/qtgui/extra_sardana/macrotree.py index ab701163c3..2ff01d2938 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/macrotree.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/macrotree.py @@ -37,9 +37,6 @@ from taurus.qt.qtcore.model import TaurusBaseTreeItem, TaurusBaseModel, \ TaurusBaseProxyModel from taurus.qt.qtgui.tree import TaurusBaseTreeWidget -from taurus.qt.qtgui.resource import getIcon - -from sardana.taurus.core.tango.sardana.macro import MacroInfo MacroView = Enumeration("MacroView", ("MacroModule", "Macro", "Unknown")) @@ -47,10 +44,10 @@ def getElementTypeIcon(t): if t == MacroView.MacroModule: - return getIcon(":/python-file.png") + return Qt.QIcon(":python-file.png") elif t == MacroView.Macro: - return getIcon(":/python.png") - return getIcon(":/tango.png") + return Qt.QIcon(":python.png") + return Qt.QIcon(":tango.png") def getElementTypeSize(t): @@ -96,7 +93,7 @@ def toolTip(self, index): return "The macro module '%s'" % self.display() def icon(self, index): - return getIcon(":/python-file.png") + return Qt.QIcon(":python-file.png") class MacroTreeItem(MacroTreeBaseItem): @@ -115,7 +112,7 @@ def toolTip(self, index): return self._itemData.doc def icon(self, index): - return getIcon(":/python.png") + return Qt.QIcon(":python.png") class MacroBaseModel(TaurusBaseModel): @@ -172,11 +169,11 @@ def mimeData(self, indexes): mime_data_item = tree_item.mimeData(index) if mime_data_item is None: continue - data.append(mime_data_item) - ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, "\r\n".join(data)) - ret.setText(", ".join(data)) + data.append(bytes(mime_data_item, encoding='utf8')) + ret.setData(TAURUS_MODEL_LIST_MIME_TYPE, b"\r\n".join(data)) + ret.setText(", ".join(map(str, data))) if len(data) == 1: - ret.setData(TAURUS_MODEL_MIME_TYPE, str(data[0])) + ret.setData(TAURUS_MODEL_MIME_TYPE, data[0]) return ret def setupModelData(self, data): @@ -186,7 +183,7 @@ def setupModelData(self, data): root = self._rootItem macro_modules = {} macro_dict = ms.getMacros() - for macro_name, macro in macro_dict.items(): + for macro_name, macro in list(macro_dict.items()): module_name = macro.module moduleNode = macro_modules.get(module_name) if moduleNode is None: @@ -236,7 +233,7 @@ class MacroTreeWidget(TaurusBaseTreeWidget): KnownPerspectives = {MacroView.MacroModule: { "label": "By module", - "icon": ":/python-file.png", + "icon": ":python-file.png", "tooltip": "View by macro module", "model": [MacroModuleModelProxy, MacroModuleModel], }, @@ -287,7 +284,7 @@ def main_MacroSelecionDialog(ms, perspective=MacroView.MacroModule): w = MacroSelectionDialog(model_name=ms, perspective=perspective) if w.result() == Qt.QDialog.Accepted: - print w.getSelectedMacros() + print(w.getSelectedMacros()) return w diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py b/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py index debb29bb6a..e7c16f7e01 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py @@ -34,7 +34,6 @@ from taurus.external.qt import Qt from taurus.qt.qtcore.model import TaurusBaseTreeItem, TaurusBaseModel from taurus.qt.qtgui.model import EditorToolBar -from taurus.qt.qtgui.resource import getIcon, getThemeIcon from taurus.qt.qtgui.table import TaurusBaseTableWidget from taurus.qt.qtgui.panel import TaurusModelChooser from taurus.core.taurusbasetypes import TaurusElementType @@ -89,7 +88,7 @@ def createChannelDict(channel, index=None, **kwargs): import PyTango import numpy - if isinstance(channel, (str, unicode)): + if isinstance(channel, str): #@fixme: to make things uglier, I lazily assume Tango attribute namin dev_name, attr_name = channel.rsplit('/', 1) name = attr_name @@ -125,8 +124,8 @@ def createChannelDict(channel, index=None, **kwargs): # 'timer': '', #should contain a channel name # 'monitor': '', #should contain a channel name # 'trigger': '', #should contain a channel name - 'value_ref_enabled': False, # bool - 'value_ref_pattern': '', # str + # 'value_ref_enabled': False, # bool + # 'value_ref_pattern': '', # str 'conditioning': '', # this is a python expresion to be evaluated for conditioning the data. The data for this channel can be referred as 'x' and data from other channels can be referred by channel name 'normalization': Normalization.No, # one of the Normalization enumeration members # string indicating the location of the data of this channel within @@ -144,8 +143,8 @@ def createChannelDict(channel, index=None, **kwargs): # avoid trying to read for scalars. We know that their shape must be () if attrconf.data_format != PyTango.AttrDataFormat.SCALAR: value = attrproxy.read().value - except Exception, e: - print str(e) + except Exception as e: + print(str(e)) if value is not None: shape = list(numpy.shape(value)) @@ -202,27 +201,27 @@ def createChannelDict(channel, index=None, **kwargs): def getElementTypeIcon(t): if t == ChannelView.Channel: - return getIcon(":/actions/system-shutdown.svg") + return Qt.QIcon("actions:system-shutdown.svg") elif t == ChannelView.Enabled: - return getIcon(":/status/true.svg") + return Qt.QIcon("status:true.svg") elif t == ChannelView.Output: - return getThemeIcon("utilities-terminal") + return Qt.QIcon.fromTheme("utilities-terminal") elif t == ChannelView.PlotType: - return getIcon(":/apps/utilities-system-monitor.svg") + return Qt.QIcon("apps:utilities-system-monitor.svg") elif t == ChannelView.PlotAxes: - return getIcon(":/apps/utilities-system-monitor.svg") + return Qt.QIcon("apps:utilities-system-monitor.svg") elif t == ChannelView.Timer: - return getIcon(":/status/flag-green-clock.svg") + return Qt.QIcon("status:flag-green-clock.svg") elif t == ChannelView.Monitor: - return getIcon(":/status/flag-green.svg") + return Qt.QIcon("status:flag-green.svg") elif t == ChannelView.Synchronization: - return getIcon(":/actions/system-shutdown.svg") + return Qt.QIcon("actions:system-shutdown.svg") elif t == ChannelView.NXPath: - return getThemeIcon("document-save-as") + return Qt.QIcon.fromTheme("document-save-as") elif t == ChannelView.Synchronizer: - return getIcon(":/actions/system-shutdown.svg") + return Qt.QIcon("actions:system-shutdown.svg") - return getIcon(":/tango.png") + return Qt.QIcon(":tango.png") def getElementTypeSize(t): @@ -387,7 +386,7 @@ def toolTip(self, index): def icon(self, index): taurus_role = index.model().role(index.column()) if taurus_role == ChannelView.Channel: - return getIcon(":/actions/system-shutdown.svg") + return Qt.QIcon("actions:system-shutdown.svg") class MntGrpUnitItem(TaurusBaseTreeItem): @@ -495,20 +494,18 @@ def flags(self, index): taurus_role = self.role(index.column()) if taurus_role == ChannelView.Channel: # channel column is not editable return flags - elif taurus_role == ChannelView.Synchronization: + elif taurus_role in (ChannelView.Timer, + ChannelView.Monitor, + ChannelView.Synchronizer, + ChannelView.Synchronization): ch_name, ch_data = index.internalPointer().itemData() if not ch_data['_controller_name'].startswith("__"): ch_info = self.getAvailableChannels()[ch_name] - # only timer/monitor columns of counter timers are editable - if ch_info['type'] in ('CTExpChannel', 'OneDExpChannel', 'TwoDExpChannel'): + # only timerable channels accept these configurations + if ch_info['type'] in ('CTExpChannel', + 'OneDExpChannel', + 'TwoDExpChannel'): flags |= Qt.Qt.ItemIsEditable - elif taurus_role in (ChannelView.Timer, ChannelView.Monitor): - ch_name, ch_data = index.internalPointer().itemData() - if not ch_data['_controller_name'].startswith("__"): - #ch_info = self.getAvailableChannels()[ch_name] - # if 'CTExpChannel' == ch_info['type']: #only timer/monitor columns of counter timers are editable - # flags |= Qt.Qt.ItemIsEditable - flags |= Qt.Qt.ItemIsEditable else: flags |= Qt.Qt.ItemIsEditable return flags @@ -527,18 +524,17 @@ def data(self, index, role=Qt.Qt.DisplayRole): taurus_role = self.role(index.column()) if taurus_role == ChannelView.Synchronization: ch_name, ch_data = index.internalPointer().itemData() - unitdict = self.getPyData(ctrlname=ch_data['_controller_name']) + ctrlname = ch_data['_controller_name'] + if ctrlname.startswith("__"): + return None + ch_info = self.getAvailableChannels()[ch_name] + if ch_info['type'] not in ('CTExpChannel', + 'OneDExpChannel', + 'TwoDExpChannel'): + return None + unitdict = self.getPyData(ctrlname=ctrlname) key = self.data_keys_map[taurus_role] - try: - synchronization = unitdict[key] - except KeyError: - # backwards compatibility for configurations before SEP6 - synchronization = unitdict.get('trigger_type', None) - if synchronization is not None: - msg = ("trigger_type configuration parameter is deprecated" - " in favor of synchronization. Re-apply" - " configuration in order to upgrade.") - self.warning(msg) + synchronization = unitdict[key] return AcqSynchType[synchronization] elif taurus_role in (ChannelView.Timer, ChannelView.Monitor): ch_name, ch_data = index.internalPointer().itemData() @@ -631,7 +627,7 @@ def addChannel(self, chname=None, chinfo=None, ctrlname=None, external=False): # update the internal data self.beginResetModel() # we are altering the internal data here, so we need to protect it ctrlsdict = self.dataSource()['controllers'] - if not ctrlsdict.has_key(ctrlname): + if ctrlname not in ctrlsdict: ctrlsdict[ctrlname] = ctrl = {'channels': {}} if not external and chinfo['type'] in ('CTExpChannel', 'OneDExpChannel', 'TwoDExpChannel'): ctrl['timer'] = chname @@ -641,7 +637,7 @@ def addChannel(self, chname=None, chinfo=None, ctrlname=None, external=False): else: ctrl = ctrlsdict[ctrlname] channelsdict = ctrl['channels'] - if channelsdict.has_key(chname): + if chname in channelsdict: self.error( 'Channel "%s" is already in the measurement group. It will not be added again' % chname) return @@ -809,11 +805,11 @@ def setEditorData(self, editor, index): dataSource = model.dataSource() taurus_role = model.role(index.column()) if taurus_role == ChannelView.PlotType: - editor.addItems(PlotType.keys()) + editor.addItems(list(PlotType.keys())) current = model.data(index) editor.setCurrentIndex(editor.findText(current)) elif taurus_role == ChannelView.Normalization: - editor.addItems(Normalization.keys()) + editor.addItems(list(Normalization.keys())) current = model.data(index) editor.setCurrentIndex(editor.findText(current)) elif taurus_role in (ChannelView.Timer, ChannelView.Monitor): @@ -830,14 +826,14 @@ def setEditorData(self, editor, index): current = model.data(index) editor.setCurrentIndex(editor.findText(current)) else: - for ctrl_data in dataSource['controllers'].values(): + for ctrl_data in list(dataSource['controllers'].values()): if key in ctrl_data: channel = all_channels[ctrl_data[key]] editor.addItem(channel['name'], channel['full_name']) current = dataSource.get(key) # current global timer/monitor editor.setCurrentIndex(editor.findData(current)) elif taurus_role == ChannelView.Synchronization: - editor.addItems(AcqSynchType.keys()) + editor.addItems(list(AcqSynchType.keys())) current = model.data(index) editor.setCurrentIndex(editor.findText(current)) elif taurus_role == ChannelView.PlotAxes: @@ -849,7 +845,7 @@ def setEditorData(self, editor, index): elif taurus_role == ChannelView.Synchronizer: # add the triggergates to the editor all_triggers = model.getAvailableTriggers() - for full_name, tg_data in all_triggers.items(): + for full_name, tg_data in list(all_triggers.items()): editor.addItem(tg_data['name'], full_name) current = model.data(index) editor.setCurrentIndex(editor.findText(current)) @@ -950,7 +946,7 @@ def setModelData(self, editor, model, index): # get the affected channels affected = [] channels = ctrl_data.get('channels') - for _, ch_data in channels.items(): + for _, ch_data in list(channels.items()): affected.append(ch_data['name']) if len(affected) > 1: @@ -974,7 +970,7 @@ class MntGrpChannelEditor(TaurusBaseTableWidget): KnownPerspectives = { "Channel": { "label": "Channels", - "icon": ":/actions/system-shutdown.svg", + "icon": "actions:system-shutdown.svg", "tooltip": "View by channel", "model": [BaseMntGrpChannelModel, ], }, @@ -1047,7 +1043,8 @@ def addChannel(self, channel=None): if channel is None: shown = [n for n, d in getChannelConfigs(dataSource)] avail_channels = qmodel.getAvailableChannels() - clist = [ch_info['name'] for ch_name, ch_info in avail_channels.items() + clist = [ch_info['name'] for ch_name, ch_info + in list(avail_channels.items()) if ch_name not in shown] clist = sorted(clist) + ['(Other...)'] chname, ok = Qt.QInputDialog.getItem( @@ -1064,7 +1061,7 @@ def addChannel(self, channel=None): qmodel.addChannel( chname=m, ctrlname='__tango__', external=True) else: - for ch_info in avail_channels.values(): + for ch_info in list(avail_channels.values()): if ch_info['name'] == chname: qmodel.addChannel(chinfo=ch_info) diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py new file mode 100644 index 0000000000..54d8535170 --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################## +## +# This file is part of Sardana +## +# http://www.sardana-controls.org/ +## +# Copyright 2020 DESY +## +# Sardana is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Sardana is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Sardana. If not, see . +## +############################################################################## + +"""A RichJupyterWidget that loads a spock profile. + +.. note:: + The `qtspock` module has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including its removal) may occur if + deemed necessary by the core developers. +""" +import sys +import pickle +import ast + +from IPython.core.profiledir import ProfileDirError, ProfileDir + +from taurus.external.qt import Qt +from taurus import info, error + +from qtconsole.rich_jupyter_widget import RichJupyterWidget +from qtconsole.manager import QtKernelManager + +from taurus.qt.qtgui.base import TaurusBaseWidget +from taurus.qt.qtgui.container import TaurusMainWindow +from taurus.qt.qtgui.resource import getThemeIcon + +from sardana import release +from sardana.spock.ipython_01_00.genutils import get_profile_metadata, \ + get_ipython_dir, from_name_to_tango, get_macroserver_for_door +from sardana.taurus.qt.qtgui.extra_macroexecutor import \ + TaurusMacroConfigurationDialog + + +def get_spock_profile_dir(profile): + """Return the path to the profile with the given name.""" + try: + profile_dir = ProfileDir.find_profile_dir_by_name( + get_ipython_dir(), profile) + except ProfileDirError: + return None + return profile_dir.location + + +def check_spock_profile(profile): + """Check if the profile exists and has the correct value""" + profile_dir = get_spock_profile_dir(profile) + if profile_dir: + profile_version_str, door_name = get_profile_metadata(profile_dir) + if profile_version_str == release.version: + return True + return False + + +class SpockKernelManager(QtKernelManager): + """ + A kernel manager that checks the spock profile before starting a kernel. + + .. note:: + The `SpockKernelManager` class has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including its removal) may occur if + deemed necessary by the core developers. + + If the check fails, i.e., the profile does not exist or has a different + version, an ipython kernel without spock functionality is started instead + and the attribute `valid_spock_profile` is set to `False`. + """ + kernel_about_to_launch = Qt.pyqtSignal() + + def _launch_kernel(self, kernel_cmd, **kw): + try: + profile = kernel_cmd[kernel_cmd.index("--profile") + 1] + except ValueError: + self.is_valid_spock_profile = False + else: + if check_spock_profile(profile): + self.is_valid_spock_profile = True + else: + index = kernel_cmd.index("--profile") + del kernel_cmd[index] + del kernel_cmd[index] + for arg in kernel_cmd[:]: + if arg.startswith("--Spock"): + kernel_cmd.remove(arg) + self.is_valid_spock_profile = False + error("Checking spock profile failed.") + info('Starting kernel...') + self.kernel_about_to_launch.emit() + return super()._launch_kernel(kernel_cmd, **kw) + + +class QtSpockWidget(RichJupyterWidget, TaurusBaseWidget): + """A RichJupyterWidget that starts a kernel with a spock profile. + + .. note:: + The `QtSpockWidget` class has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including its removal) may occur if + deemed necessary by the core developers. + + It is important to call `shutdown_kernel` to gracefully clean up the + started subprocesses. + + Useful methods of the base class include execute, interrupt_kernel, + restart_kernel, and clear. + + :param profile: + The name of the spock profile to use. The default is 'spockdoor'. + :type profile: str + :param kernel: + The name of the kernel to use. The default is 'python3'. + :type kernel: str + :param use_model_from_profile: + If true, the door name is taken from the spock profile, otherwise it + has to be set via setModel. + :type use_model_from_profile: bool + :param kwargs: + All remaining keywords are passed to the RichJupyterWidget base class + + Examples:: + + from taurus.external.qt import Qt + from sardana.taurus.qt.qtgui.extra_sardana.qtspock import QtSpockWidget + app = Qt.QApplication([]) + widget = QtSpockWidget() + widget.show() + app.aboutToQuit.connect(widget.shutdown_kernel) + app.exec_() + """ + def __init__( + self, + parent=None, + profile='spockdoor', + use_model_from_profile=False, + extensions=None, + kernel='python3', + **kw): + RichJupyterWidget.__init__(self, parent=parent, **kw) + TaurusBaseWidget.__init__(self) + self.setObjectName(self.__class__.__name__) + self.setModelInConfig(True) + + self._profile = profile + self.use_model_from_profile = use_model_from_profile + + if extensions is None: + extensions = [] + extensions.insert( + 0, "sardana.taurus.qt.qtgui.extra_sardana.qtspock_ext") + + self._extensions = extensions + self._kernel_name = kernel + + self._macro_server_name = None + self._macro_server_alias = None + self._door_name = None + self._door_alias = None + + self.kernel_manager = SpockKernelManager(kernel_name=kernel) + self.kernel_manager.kernel_about_to_launch.connect( + self._handle_kernel_lauched) + + def start_kernel(self): + """Start the kernel + + A normal IPython kernel is started if no model is set via `setModel` or + `use_model_from_profile`. + """ + if not self.kernel_manager.has_kernel: + self.kernel_manager.start_kernel( + extra_arguments=self._extra_arguments()) + kernel_client = self.kernel_manager.client() + kernel_client.start_channels() + self.kernel_client = kernel_client + + def _extra_arguments(self): + extra_arguments = ["--profile", self._profile] + for ext in self._extensions: + extra_arguments.extend(["--ext", ext]) + + if not self.use_model_from_profile: + if self._macro_server_name and self._door_name: + extra_arguments.append("--Spock.macro_server={}".format( + self._macro_server_name)) + extra_arguments.append("--Spock.macro_server_alias={}".format( + self._macro_server_alias)) + extra_arguments.append("--Spock.door_name={}".format( + self._door_name)) + extra_arguments.append("--Spock.door_alias={}".format( + self._door_alias)) + else: + # Loading the spock profile would use the macro server and door + # configured there. Instead, use no extra arguments for an + # ipython kernel without Spock functionality + extra_arguments = [] + + return extra_arguments + + def setModel(self, door): + """Set a door as the model + + An empty string or None will start a normal IPython kernel without + spock functionality. + """ + old_door_name = self._door_name + old_macroserver_name = self._macro_server_name + self._set_door_name(door) + self._set_macro_server_name(door) + + if (self._door_name == old_door_name + and self._macro_server_name == old_macroserver_name): + return + + if self.kernel_manager.has_kernel: + # RichJupyterWidget.restart_kernel does not support extra arguments + self.kernel_manager.restart_kernel( + extra_arguments=self._extra_arguments()) + self._kernel_restarted_message(died=False) + else: + self.start_kernel() + + def getModel(self): + return self._door_name + + def _set_door_name(self, door): + if door: + full_door_tg_name, door_tg_name, door_tg_alias = ( + from_name_to_tango(door)) + door_alias = door_tg_alias or door_tg_name + self._door_name = full_door_tg_name + self._door_alias = door_alias + else: + self._door_name = None + self._door_alias = None + + def _set_macro_server_name(self, door): + if door: + macro_server = get_macroserver_for_door(door) + full_ms_tg_name, ms_tg_name, ms_tg_alias = ( + from_name_to_tango(macro_server)) + ms_alias = ms_tg_alias or ms_tg_name + self._macro_server_name = full_ms_tg_name + self._macro_server_alias = ms_alias + else: + self._macro_server_name = None + self._macro_server_alias = None + + def _set_prompts(self): + var = "get_ipython().config.Spock.door_alias" + self._silent_exec_callback( + var, self._set_prompts_callback) + + def _set_prompts_callback(self, msg): + in_prefix = 'In' + if msg['status'] == 'ok': + output_bytes = msg['data']['text/plain'] + try: + in_prefix = ast.literal_eval(output_bytes) + except SyntaxError: + pass + + if not self.kernel_manager.is_valid_spock_profile: + self._print_ipython_warning() + + self.in_prompt = ( + in_prefix + ' [%i]:') + self.out_prompt = ( + 'Result [%i]:') + + def _print_ipython_warning(self): + if self.use_model_from_profile or self._extra_arguments(): + self.append_stream( + "\nSpock profile error: please run spock in the terminal" + " and restart the kernel.\n") + else: + self.append_stream( + "\nNo door selected. Please select a valid door.\n") + self.append_stream( + "\nThis is a normal ipython kernel. " + "Spock functionality is not available.\n") + + def runMacro(self, macro_node): + self.execute(macro_node.toSpockCommand()) + + # Adapted from + # https://github.com/moble/remote_exec/blob/master/remote_exec.py#L61 + def get_value(self, var, timeout=None): + """Retrieve a value from the user namespace through a blocking call. + + The value must be able to be pickled on the kernel side and unpickled + on the frontend side. + + The command will import the pickle module in the user namespace. This + may overwrite a user defined variable with the same name. + + :param var: + The name of the variable to be retrieved + :type var: str + :param timeout: + Number of seconds to wait for reply. If no reply is recieved, a + `Queue.Empty` exception is thrown. The default is to wait + indefinitely + :type timeout: int or None + :return: + The value of the variable from the user namespace + """ + pickle_dumps = 'pickle.dumps({})'.format(var) + msg_id = self.blocking_client.execute( + "import pickle", silent=True, + user_expressions={'output': pickle_dumps}) + reply = self.blocking_client.get_shell_msg(msg_id, timeout=timeout) + if reply['content']['status'] != "ok": + raise RuntimeError("{}: {}".format( + reply['content']['ename'], reply['content']['evalue'])) + output = reply['content']['user_expressions']['output'] + if output['status'] != "ok": + raise RuntimeError("{}: {}".format( + output['ename'], output['evalue'])) + output_bytes = output['data']['text/plain'] + output_bytes = ast.literal_eval(output_bytes) + return pickle.loads(output_bytes) + + def shutdown_kernel(self): + """Cleanly shut down the kernel and client subprocesses""" + info('Shutting down kernel...') + if self.kernel_client: + self.kernel_client.stop_channels() + if self.kernel_manager and self.kernel_manager.kernel: + self.kernel_manager.shutdown_kernel() + + def _handle_kernel_lauched(self): + if self.kernel_client: + self.kernel_client.kernel_info() + + def _handle_kernel_info_reply(self, rep): + self._set_prompts() + is_starting = self._starting + super()._handle_kernel_info_reply(rep) + if not is_starting: + # The base method did not print the banner and reset the prompt. + # As the profile might have changed, do it here. + self._append_plain_text("\n\n") + self._append_plain_text(self.kernel_banner) + self.reset() + + +class QtSpock(TaurusMainWindow): + """A standalone QtSpock window + + .. note:: + The `QtSpock` class has been included in Sardana + on a provisional basis. Backwards incompatible changes + (up to and including its removal) may occur if + deemed necessary by the core developers. + """ + def __init__(self, parent=None, designMode=False): + super().__init__(parent, designMode) + self.spockWidget = QtSpockWidget(parent=self) + self.registerConfigDelegate(self.spockWidget) + self.spockWidget.setModelInConfig(True) + self.setCentralWidget(self.spockWidget) + self.configureAction = self.createConfigureAction() + self.taurusMenu.addAction(self.configureAction) + self.statusBar().showMessage("QtSpock ready") + self.loadSettings() + + def createConfigureAction(self): + configureAction = Qt.QAction(getThemeIcon( + "preferences-system-session"), "Change configuration", self) + configureAction.triggered.connect(self.changeConfiguration) + configureAction.setToolTip("Configuring MacroServer and Door") + configureAction.setShortcut("F10") + return configureAction + + def changeConfiguration(self): + """This method is used to change macroserver as a model of application. + It shows dialog with list of all macroservers on tango host, if the + user Cancel dialog it doesn't do anything.""" + dialog = TaurusMacroConfigurationDialog( + self, self.spockWidget._macro_server_name, + self.modelName) + if dialog.exec_(): + self.spockWidget.setModel(str(dialog.doorComboBox.currentText())) + else: + return + + +def main(): + from taurus.qt.qtgui.application import TaurusApplication + app = TaurusApplication() + app.setOrganizationName("Taurus") + app.setApplicationName("QtSpock") + window = QtSpock() + window.show() + app.aboutToQuit.connect(window.spockWidget.shutdown_kernel) + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() diff --git a/src/sardana/spock/ipython_00_10/__init__.py b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock_ext.py similarity index 83% rename from src/sardana/spock/ipython_00_10/__init__.py rename to src/sardana/taurus/qt/qtgui/extra_sardana/qtspock_ext.py index 7d0819f924..6013f51ac9 100644 --- a/src/sardana/spock/ipython_00_10/__init__.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/qtspock_ext.py @@ -7,7 +7,7 @@ ## # http://www.sardana-controls.org/ ## -# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +# Copyright 2020 DESY ## # Sardana is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -24,4 +24,9 @@ ## ############################################################################## -"""This package provides the spock generic utilities for ipython 0.10""" +from IPython.core.magic import register_line_magic + + +@register_line_magic +def edmac(self, parameter_s=''): + return "edmac macro not implemented for qtspock" diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/sardanabasewizard.py b/src/sardana/taurus/qt/qtgui/extra_sardana/sardanabasewizard.py index 5d19af1efe..000627a303 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/sardanabasewizard.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/sardanabasewizard.py @@ -64,7 +64,7 @@ def __getitem__(self, name): if isinstance(p, SardanaBasePage): try: return p[name]() - except Exception, e: + except Exception: pass return self._item_funcs[name]() return None diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/sardanaeditor.py b/src/sardana/taurus/qt/qtgui/extra_sardana/sardanaeditor.py index 5bb434590a..25c6593fa8 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/sardanaeditor.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/sardanaeditor.py @@ -44,13 +44,13 @@ # consider adding an utility in taurusedirot to create actions from spyder.utils.qthelpers import create_action -from macrotree import MacroSelectionDialog -from elementtree import SardanaElementTreeWidget +from .macrotree import MacroSelectionDialog +from .elementtree import SardanaElementTreeWidget from sardana.taurus.qt.qtcore.tango.sardana.model import SardanaBaseProxyModel, \ SardanaElementTypeModel, SardanaTypeTreeItem, SardanaRootTreeItem -from sardanabasewizard import SardanaBaseWizard, SardanaBasePage +from .sardanabasewizard import SardanaBaseWizard, SardanaBasePage _MACRO_LIB_TEMPLATE = """#!/usr/bin/env python {copyright} @@ -289,7 +289,7 @@ def new_macro(self, template): self.debug("Creating local file %s...", local_filename) fname, lib_code, line = macro_server.GetMacroCode( (macro_lib_name,)) - fd = file(local_filename, "w") + fd = open(local_filename, "w") fd.write(lib_code) fd.close() self.debug("Loading local file %s...", local_filename) @@ -345,7 +345,7 @@ def new_macro_library(self): # transform into relative path rel_filename = filename[filename.index(osp.sep) + 1:] local_filename = osp.join(self._tmp_dir, rel_filename) - f = file(local_filename, "w") + f = open(local_filename, "w") # TODO: ask for additional imports # TODO: check if door environment has copyright variable @@ -407,7 +407,7 @@ def open_macros(self, macros): _, code, line = self.get_macro_code(module, name) line = int(line) self.debug("Creating local file %s...", local_filename) - fd = file(local_filename, "w") + fd = open(local_filename, "w") fd.write(code) fd.close() if idx is None: @@ -460,7 +460,7 @@ def open_macro_libraries(self, macro_libraries): _, code, line = self.get_macro_code(module) line = int(line) self.debug("Creating local file %s...", local_filename) - fd = file(local_filename, "w") + fd = open(local_filename, "w") fd.write(code) fd.close() if idx is None: @@ -484,7 +484,7 @@ def on_save(self, checked, apply=True): if not res: return local_filename = file_info.filename - fd = file(local_filename, "r") + fd = open(local_filename, "r") code = fd.read() fd.close() remote_filename = local_filename[len(self._tmp_dir):] @@ -567,6 +567,7 @@ def main(): import taurus.core.util.argparse parser = taurus.core.util.argparse.get_taurus_parser() parser.usage = "%prog [options] " + app = Application(sys.argv, cmd_line_parser=parser, app_name="Macro editor demo", app_version="1.0", org_domain="Sardana", org_name="Tango community") diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py index 4c2e3dcd1c..102eab1aae 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/showscanonline.py @@ -27,9 +27,11 @@ __all__ = ["ShowScanOnline"] -from sardana.taurus.qt.qtgui.macrolistener import \ - DynamicPlotManager +import click + from taurus.qt.qtgui.taurusgui import TaurusGui +from sardana.taurus.qt.qtgui.macrolistener import (DynamicPlotManager, + assertPlotAvailability) class ShowScanOnline(DynamicPlotManager): @@ -83,31 +85,34 @@ class TaurusGuiLite(TaurusGui): SPLASH_LOGO_NAME = None -def main(): +@click.command() +@click.option('--group', default='x-axis', + type=click.Choice(['single', 'x-axis']), + help='group curves') +@click.option('--taurus-log-level', + type=click.Choice(['critical', 'error', 'warning', 'info', + 'debug', 'trace']), + default='error', show_default=True, + help='Show only logs with priority LEVEL or above') +@click.argument('door') +def main(group, taurus_log_level, door): + import taurus + taurus.setLogLevel(getattr(taurus, taurus_log_level.capitalize())) from taurus.qt.qtgui.application import TaurusApplication - import sys - - from taurus.core.util.argparse import get_taurus_parser - parser = get_taurus_parser() - parser.set_usage("python showscanonline.py [door_name]") app = TaurusApplication(app_name='Showscan Online', org_domain="Sardana", - org_name="Tango communinity", - cmd_line_parser=parser) + org_name="Tango communinity", cmd_line_parser=None) - gui = TaurusGuiLite() - args = app.get_command_line_args() + assertPlotAvailability() - if len(args) < 1: - parser.print_help(sys.stderr) - sys.exit(1) + gui = TaurusGuiLite() - door_name = args[0] widget = ShowScanOnline(gui) - widget.setModel(door_name) + widget.setModel(door) + widget.setGroupMode(group) gui.show() - sys.exit(app.exec_()) + return app.exec_() if __name__ == "__main__": diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/startup.py b/src/sardana/taurus/qt/qtgui/extra_sardana/startup.py index 59fe225040..eb8f8f7eb6 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/startup.py +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/startup.py @@ -36,7 +36,7 @@ def __run_pythonstartup_script(): import os filename = os.environ.get('PYTHONSTARTUP') if filename and os.path.isfile(filename): - execfile(filename) + exec(compile(open(filename).read(), filename, 'exec')) def __run_init_commands(): @@ -70,7 +70,7 @@ def __init__(self, namelist=None, pathlist=None): if pathlist is None: pathlist = [] self.pathlist = pathlist - self.previous_modules = sys.modules.keys() + self.previous_modules = list(sys.modules.keys()) def is_module_blacklisted(self, modname, modpath): for path in [sys.prefix] + self.pathlist: @@ -88,7 +88,7 @@ def run(self, verbose=False): Do not del C modules """ log = [] - for modname, module in sys.modules.items(): + for modname, module in list(sys.modules.items()): if modname not in self.previous_modules: modpath = getattr(module, '__file__', None) if modpath is None: @@ -100,8 +100,8 @@ def run(self, verbose=False): log.append(modname) del sys.modules[modname] if verbose and log: - print "\x1b[4;33m%s\x1b[24m%s\x1b[0m" % ("UMD has deleted", - ": " + ", ".join(log)) + print("\x1b[4;33m%s\x1b[24m%s\x1b[0m" % ("UMD has deleted", + ": " + ", ".join(log))) __umd__ = None @@ -123,7 +123,7 @@ def runfile(filename, args=None, wdir=None): else: verbose = os.environ.get("UMD_VERBOSE", "").lower() == "true" __umd__.run(verbose=verbose) - if args is not None and not isinstance(args, basestring): + if args is not None and not isinstance(args, str): raise TypeError("expected a character buffer object") glbs = globals() if '__ipythonshell__' in glbs: @@ -135,7 +135,7 @@ def runfile(filename, args=None, wdir=None): sys.argv.append(arg) if wdir is not None: os.chdir(wdir) - execfile(filename, glbs) + exec(compile(open(filename).read(), filename, 'exec'), glbs) sys.argv = [''] glbs.pop('__file__') @@ -161,7 +161,7 @@ def debugfile(filename, args=None, wdir=None): __commands__ = __run_init_commands() if __commands__: for command in __commands__.split(';'): - exec command + exec(command) else: __run_pythonstartup_script() diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/test/__init__.py b/src/sardana/taurus/qt/qtgui/extra_sardana/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py new file mode 100644 index 0000000000..126bb30a4c --- /dev/null +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/test/test_qtspock.py @@ -0,0 +1,342 @@ +import re +import os +import tempfile +import numpy as np +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from taurus.external.unittest import TestCase, main +from taurus.external import qt +from taurus.external.qt import Qt +from sardana.spock.ipython_01_00.genutils import _create_config_file, \ + from_name_to_tango +from sardana.sardanacustomsettings import UNITTEST_DOOR_NAME +from sardana.taurus.qt.qtgui.extra_sardana.qtspock import QtSpockWidget, \ + get_spock_profile_dir + +if qt.PYQT4: + from PyQt4.QtTest import QTest +elif qt.PYQT5: + from PyQt5.QtTest import QTest +elif qt.PYSIDE: + from PySide.QtTest import QTest + + +app = Qt.QApplication.instance() +if not app: + app = Qt.QApplication([]) + + +def waitFor(predicate, timeout): + """Process events until predicate is true or timeout seconds passed + + This seems to not handle the destruction of widget correctly, use + waitForLoop in such cases. + """ + if predicate(): + return True + + timer = Qt.QElapsedTimer() + timer.start() + + while not timer.hasExpired(timeout): + QTest.qWait(min(100, timeout - timer.elapsed())) + if predicate(): + return True + + return predicate() + + +def waitForLoop(predicate, timeout): + """Run event loop and periodically check for predicate""" + if predicate(): + return True + + timer = Qt.QElapsedTimer() + timer.start() + + loop = Qt.QEventLoop() + + while not timer.hasExpired(timeout): + Qt.QTimer.singleShot(min(100, timeout - timer.elapsed()), loop.quit) + loop.exec_() + if predicate(): + return True + + return predicate() + + +class QtSpockBaseTestCase(TestCase): + @classmethod + def setUpClass(cls): + # Use setUpClass instead of setUp because starting QtSpock + # takes a relatively long time. + cls.test_ipython_dir = tempfile.mkdtemp() + cls._create_profile() + os.environ["IPYTHONDIR"] = cls.test_ipython_dir + cls._isDestroyed = False + + @classmethod + def _create_profile(cls): + cls.test_spockdoor_dir = os.path.join( + cls.test_ipython_dir, "profile_spockdoor") + os.mkdir(cls.test_spockdoor_dir) + _create_config_file(cls.test_spockdoor_dir, UNITTEST_DOOR_NAME) + + @classmethod + def _handle_destroyed(cls): + cls._isDestroyed = True + + @classmethod + def tearDownClass(cls): + cls.widget.shutdown_kernel() + cls.widget.setAttribute(Qt.Qt.WA_DeleteOnClose) + cls.widget.destroyed.connect(cls._handle_destroyed) + cls.widget.close() + cls.widget = None + waitForLoop(lambda: cls._isDestroyed, 5000) + + +class QtSpockTestCase(QtSpockBaseTestCase): + @classmethod + def setUpClass(cls): + super(QtSpockTestCase, cls).setUpClass() + cls.widget = QtSpockWidget(use_model_from_profile=True) + cls.widget.start_kernel() + cls.widget.show() + + +class CorrectProfileOutputMixin(object): + def test_spock_banner(self): + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall(r"^Spock \d\.\d", text, re.MULTILINE) + return len(matches) == 1 + self.assertTrue(waitFor(predicate, 10000)) + + def test_spock_prompt(self): + full_name, name, alias = from_name_to_tango(UNITTEST_DOOR_NAME) + alias = alias or name + + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall( + r"^{}.*\[(\d)\]".format(alias), + text, re.MULTILINE) + return len(matches) == 1 and matches[0] == "1" + self.assertTrue(waitFor(predicate, 10000)) + + +class CorrectProfileTestCase(QtSpockTestCase, CorrectProfileOutputMixin): + def test_get_value(self): + msg_id = self.widget.blocking_client.execute( + "a = arange(3)", silent=True) + self.widget.blocking_client.get_shell_msg(msg_id) + self.assertTrue( + np.array_equal(self.widget.get_value("a"), np.arange(3))) + + def test_find_spock_profile(self): + self.assertEqual( + get_spock_profile_dir("spockdoor"), self.test_spockdoor_dir) + + def test_is_valid_spock_profile(self): + self.assertTrue(self.widget.kernel_manager.is_valid_spock_profile) + + +class ProfileErrorOutputMixin(object): + def test_ipython_banner(self): + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall(r"^IPython \d\.\d", text, re.MULTILINE) + return len(matches) == 1 + self.assertTrue(waitFor(predicate, 5000)) + + def test_ipython_prompt(self): + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall(r"^In.*\[(\d)\]", text, re.MULTILINE) + return len(matches) == 1 and matches[0] == "1" + self.assertTrue(waitFor(predicate, 5000)) + + def test_profile_error_info(self): + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall(r"^Spock profile error", text, re.MULTILINE) + return len(matches) == 1 + self.assertTrue(waitFor(predicate, 5000)) + + +class MissingProfileTestCase(QtSpockTestCase, ProfileErrorOutputMixin): + @classmethod + def setUpClass(cls): + cls.test_ipython_dir = tempfile.mkdtemp() + os.environ["IPYTHONDIR"] = cls.test_ipython_dir + cls.widget = QtSpockWidget(use_model_from_profile=True) + cls.widget.start_kernel() + cls.widget.show() + cls._isDestroyed = False + + def test_is_valid_spock_profile(self): + self.assertTrue(not self.widget.kernel_manager.is_valid_spock_profile) + + +class CorrectProfileAfterRestartTestCase( + MissingProfileTestCase, CorrectProfileOutputMixin): + @classmethod + def setUpClass(cls): + super(CorrectProfileAfterRestartTestCase, cls).setUpClass() + + # Wait until startup finished + def predicate(): + text = cls.widget._control.toPlainText() + matches = re.findall(r"\[1\]: $", text, re.MULTILINE) + return len(matches) == 1 + assert waitFor(predicate, 5000) + + cls._create_profile() + + # Restart kernel + def acceptDialog(): + topLevelWidgets = Qt.QApplication.topLevelWidgets() + dialog = None + for widget in topLevelWidgets: + if isinstance(widget, Qt.QMessageBox): + dialog = widget + dialog.button(Qt.QMessageBox.Yes).click() + + Qt.QTimer.singleShot(10, acceptDialog) + cls.widget.restart_kernel("Restart?") + + def test_is_valid_spock_profile(self): + self.assertTrue(self.widget.kernel_manager.is_valid_spock_profile) + + +class ProfileErrorAfterRestartTestCase( + QtSpockTestCase, ProfileErrorOutputMixin): + @classmethod + def setUpClass(cls): + super(ProfileErrorAfterRestartTestCase, cls).setUpClass() + + # Wait until startup finished + def predicate(): + text = cls.widget._control.toPlainText() + matches = re.findall(r"\[1\]: $", text, re.MULTILINE) + return len(matches) == 1 + assert waitFor(predicate, 5000) + + # "Update" profile + config_file = os.path.join( + cls.test_spockdoor_dir, "ipython_config.py") + with open(config_file) as f: + config = f.read() + config = re.sub( + r"^# spock_creation_version = \d\.\d.*", + "# spock_creation_version = 9.9.9-gamma", + config, flags=re.MULTILINE) + with open(config_file, 'w') as f: + f.write(config) + + # Restart kernel + cls.widget.kernel_manager.restart_kernel() + + def test_is_valid_spock_profile(self): + self.assertTrue(not self.widget.kernel_manager.is_valid_spock_profile) + + +class QtSpockNoModelTestCase(QtSpockBaseTestCase, ProfileErrorOutputMixin): + @classmethod + def setUpClass(cls): + super(QtSpockNoModelTestCase, cls).setUpClass() + cls.widget = QtSpockWidget() + cls.widget.start_kernel() + cls.widget.show() + + def test_is_valid_spock_profile(self): + self.assertTrue(not self.widget.kernel_manager.is_valid_spock_profile) + + def test_profile_error_info(self): + def predicate(): + text = self.widget._control.toPlainText() + matches = re.findall(r"^No door selected", text, re.MULTILINE) + return len(matches) == 1 + self.assertTrue(waitFor(predicate, 5000)) + + +class QtSpockModelTestCase(QtSpockBaseTestCase, CorrectProfileOutputMixin): + @classmethod + def setUpClass(cls): + super(QtSpockModelTestCase, cls).setUpClass() + cls.widget = QtSpockWidget() + cls.widget.setModel(UNITTEST_DOOR_NAME) + cls.widget.show() + + def test_is_valid_spock_profile(self): + self.assertTrue(self.widget.kernel_manager.is_valid_spock_profile) + + def test_setModel_twice_no_restart(self): + with patch.object( + self.widget.kernel_manager, "restart_kernel", spec=True) as m: + self.widget.setModel(UNITTEST_DOOR_NAME) + self.assertTrue(not m.called) + + +class QtSpockModelAfterRestartTestCase( + QtSpockBaseTestCase, CorrectProfileOutputMixin): + @classmethod + def setUpClass(cls): + super(QtSpockModelAfterRestartTestCase, cls).setUpClass() + cls.widget = QtSpockWidget() + cls.widget.start_kernel() + cls.widget.show() + + # Wait until startup finished + def predicate(): + text = cls.widget._control.toPlainText() + matches = re.findall(r"\[1\]: $", text, re.MULTILINE) + return len(matches) == 1 + assert waitFor(predicate, 5000) + + cls.widget.setModel(UNITTEST_DOOR_NAME) + + def test_is_valid_spock_profile(self): + self.assertTrue(self.widget.kernel_manager.is_valid_spock_profile) + + +class QtSpockNoModelAfterRestartTestCase(QtSpockNoModelTestCase): + @classmethod + def setUpClass(cls): + # Do only call the original base class constructor + super(QtSpockNoModelTestCase, cls).setUpClass() + cls.widget = QtSpockWidget() + cls.widget.setModel(UNITTEST_DOOR_NAME) + cls.widget.show() + + # Wait until startup finished + def predicate(): + text = cls.widget._control.toPlainText() + matches = re.findall(r"\[1\]: $", text, re.MULTILINE) + return len(matches) == 1 + assert waitFor(predicate, 5000) + + cls.widget.setModel("") + + +class QtSpockModelMissingProfileTestCase( + QtSpockTestCase, ProfileErrorOutputMixin): + @classmethod + def setUpClass(cls): + cls.test_ipython_dir = tempfile.mkdtemp() + os.environ["IPYTHONDIR"] = cls.test_ipython_dir + cls.widget = QtSpockWidget() + cls.widget.setModel(UNITTEST_DOOR_NAME) + cls.widget.show() + cls._isDestroyed = False + + def test_is_valid_spock_profile(self): + self.assertTrue(not self.widget.kernel_manager.is_valid_spock_profile) + + +if __name__ == '__main__': + main() diff --git a/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ExpDescriptionEditor.ui b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ExpDescriptionEditor.ui index 206409196f..b4cc99cfdf 100644 --- a/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ExpDescriptionEditor.ui +++ b/src/sardana/taurus/qt/qtgui/extra_sardana/ui/ExpDescriptionEditor.ui @@ -72,19 +72,6 @@ - - - - If checked, the configured plots are shown - - - plots - - - true - - - diff --git a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py index 49809d72b9..f06fbbe6a3 100644 --- a/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py +++ b/src/sardana/taurus/qt/qtgui/macrolistener/macrolistener.py @@ -35,28 +35,146 @@ `taurus.qt.qtgui.taurusgui.macrolistener` """ -from __future__ import print_function + from builtins import object import datetime +import collections + +import numpy + +try: + import pyqtgraph +except ImportError: + pyqtgraph = None -from taurus.core.util.containers import CaselessList from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseComponent +from taurus.core.util.containers import ArrayBuffer, LoopList + +from sardana.taurus.core.tango.sardana import PlotType -__all__ = ['MacroBroker', 'DynamicPlotManager'] +__all__ = ['MacroBroker', 'DynamicPlotManager', 'assertPlotAvailability'] + __docformat__ = 'restructuredtext' +COLORS = [Qt.QColor(Qt.Qt.red), + Qt.QColor(Qt.Qt.green), + Qt.QColor(Qt.Qt.magenta), + Qt.QColor(Qt.Qt.blue), + Qt.QColor(Qt.Qt.cyan), + Qt.QColor(Qt.Qt.yellow), + Qt.QColor(Qt.Qt.white)] + +SYMBOLS = ['x', 'o', 't', 't1', 't2', 't3', 's', 'p'] + +CURVE_STYLES = [(color, symbol) for symbol in SYMBOLS for color in COLORS] -class ChannelFilter(object): +NO_PLOT_MESSAGE = 'Plots cannot be displayed (pyqtgraph not installed)' - def __init__(self, chlist): - self.chlist = tuple(chlist) - def __call__(self, x): - return x in self.chlist +def assertPlotAvailability(exit_on_error=True): + if pyqtgraph is None: + Qt.QMessageBox.critical(None, 'Plot error', NO_PLOT_MESSAGE) + if exit_on_error: + exit(1) + + +def empty_data(nb_points): + return ArrayBuffer(numpy.full(nb_points, numpy.nan)) + + +class MultiPlotWidget(Qt.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + layout = Qt.QVBoxLayout(self) + self.win = pyqtgraph.GraphicsLayoutWidget() + layout.addWidget(self.win) + self._plots = {} + self._timer = None + self._event_nb = 0 + self._last_event_nb = 0 + + # plots: a list of plots + # each plot is: + # dict: { x_axis: { name: axis-name, label: axis-label + # curves: [{ name: curve_name, label: curve_label }] } + + def prepare(self, plots, nb_points=None): + self.win.clear() + plot_widgets = {} + nb_points = 2**16 if nb_points is None else nb_points + plots_per_row = int(len(plots)**0.5) + for idx, plot in enumerate(plots): + plot_curves = {} + x_axis, curves = plot['x_axis'], plot['curves'] + if idx % plots_per_row == 0: + self.win.nextRow() + nb_curves = len(curves) + labels = dict(bottom=x_axis['label']) + if nb_curves == 1: + labels['left'] = curves[0]['label'] + plot_widget = self.win.addPlot(labels=labels) + plot_widget.showGrid(x=True, y=True) + if nb_curves > 1: + plot_widget.addLegend() + plot_widget.x_axis = dict(x_axis, data=empty_data(nb_points)) + styles = LoopList(CURVE_STYLES) + for curve in curves: + pen, symbol = styles.current() + styles.next() + style = dict(pen=pen, symbol=symbol, + symbolSize=5, symbolPen=pen, symbolBrush=pen) + curve_item = plot_widget.plot(name=curve['label'], **style) + curve_item.curve_data = empty_data(nb_points) + plot_curves[curve['name']] = curve_item + plot_widgets[plot_widget] = plot_curves + self._plots = plot_widgets + self._event_nb = 0 + self._last_event_nb = 0 + self._start_update() + + def onNewPoint(self, data): + # update internal data and mark the new event + for plot_widget, curves in self._plots.items(): + x_axis = plot_widget.x_axis + x_data = x_axis['data'] + x_data.append(data[x_axis['name']]) + for curve_name, curve_item in curves.items(): + y_data = curve_item.curve_data + y_data.append(data[curve_name]) + self._event_nb += 1 + + def onEnd(self, data): + self.do_update() + self._end_update() + + def do_update(self): + if self._event_nb == self._last_event_nb: + return + self._last_event_nb = self._event_nb + for plot_widget, curves in self._plots.items(): + x_axis = plot_widget.x_axis + x_data = x_axis['data'] + for curve_name, curve_item in curves.items(): + y_data = curve_item.curve_data + curve_item.setData(x_data.contents(), y_data.contents()) + + def _start_update(self): + self._end_update() + timer = Qt.QTimer() + timer.timeout.connect(self.do_update) + # refresh curves at a fix rate of ~5Hz + timer.start(200) + self._timer = timer + + def _end_update(self): + if self._timer: + self._timer.stop() + self._timer = None class DynamicPlotManager(Qt.QObject, TaurusBaseComponent): @@ -71,20 +189,30 @@ class DynamicPlotManager(Qt.QObject, TaurusBaseComponent): used. ''' + plots_available = pyqtgraph is not None + newShortMessage = Qt.pyqtSignal('QString') + Single = 'single' # each curve has its own plot + XAxis = 'x-axis' # group curves with same X-Axis + def __init__(self, parent=None): Qt.QObject.__init__(self, parent) TaurusBaseComponent.__init__(self, self.__class__.__name__) - self.__panels = {} + self._group_mode = self.XAxis + Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') + + self._plot = MultiPlotWidget() + self.createPanel( + self._plot, 'Scan plot', registerconfig=False, permanent=False) - self._trends1d = {} - self._trends2d = {} + def setGroupMode(self, group): + assert group in (self.Single, self.XAxis) + self._group_mode = group - self._parent_can_notify_changes = False - if hasattr(parent, 'experimentConfigurationChanged'): - self._parent_can_notify_changes = True + def groupMode(self): + return self._group_mode def setModel(self, doorname): '''reimplemented from :meth:`TaurusBaseComponent` @@ -105,18 +233,8 @@ def setModel(self, doorname): self._checkJsonRecorder() - # read the expconf - expconf = self.door.getExperimentConfiguration() - self.onExpConfChanged(expconf) - - # Connect experimentConfigurationChanged signal, - # The event can be emitted by parents like expconf or emitted by QDoor - if self._parent_can_notify_changes: - self.parent().experimentConfigurationChanged.connect( - self.onExpConfChanged) - else: - self.door.recordDataUpdated.connect(self.onRecordDataUpdated) - self.old_arg = None + self.door.recordDataUpdated.connect(self.onRecordDataUpdated) + self.message_template = 'Ready!' def _checkJsonRecorder(self): '''Checks if JsonRecorder env var is set and offers to set it''' @@ -145,200 +263,104 @@ def onRecordDataUpdated(self, arg): :param arg: RecordData Tuple :return: """ - - # Filter events sent by itself - if arg == self.old_arg: - return - - self.old_arg = arg - data = arg[1] if 'type' in data: - if data['type'] == 'data_desc': - expconf = self.door.getExperimentConfiguration() - self.onExpConfChanged(expconf) - self.door.recordDataUpdated.emit(arg) - - def onExpConfChanged(self, expconf): - ''' - Slot to be called when experimental configuration changes. It should - remove the temporary panels and create the new ones needed. - - :param expconf: (dict) An Experiment Description dictionary. See - :meth:`sardana.taurus.qt.qtcore.tango.sardana. - QDoor.getExperimentDescription` - for more details - ''' - - from sardana.taurus.core.tango.sardana import PlotType - from sardana.taurus.core.tango.sardana.pool import getChannelConfigs - activeMntGrp = expconf['ActiveMntGrp'] - if activeMntGrp is None: - return - if activeMntGrp not in expconf['MntGrpConfigs']: - self.warning( - "ActiveMntGrp '%s' is not defined" % - activeMntGrp) - return - mgconfig = expconf['MntGrpConfigs'][activeMntGrp] - channels = dict(getChannelConfigs(mgconfig, sort=False)) - - # classify by type of plot: - trends1d = {} - trends2d = {} - - for chname, chdata in channels.items(): - ptype = chdata['plot_type'] + event_type = data['type'] + if event_type == 'data_desc': + self.prepare(data) + elif event_type == 'record_data': + self.newPoint(data) + elif event_type == 'record_end': + self.end(data) + + def prepare(self, data_desc): + """ + Prepare UI for a new scan. Rebuilds plots as necessary to adapt to the + new scan channels and moveables + """ + data = data_desc['data'] + col_map = {column['name']: column for column in data['column_desc']} + + # Build list of curves. Each curve has the same column info from the + # event and additionally the x-axis it should be plot against + curves = [] + for column in data['column_desc']: + ch_name = column['name'] + ptype = column.get('plot_type', PlotType.No) if ptype == PlotType.No: continue - elif ptype == PlotType.Spectrum: - axes = tuple(chdata['plot_axes']) - # TODO: get default value from the channel. - ndim = chdata.get('ndim', 0) or 0 - if ndim == 0: # this is a trend - if axes in trends1d: - trends1d[axes].append(chname) - else: - trends1d[axes] = CaselessList([chname]) - elif ndim == 1: # a 1D plot (e.g. a spectrum) - pass # TODO: implement - else: - self.warning('Cannot create plot for %s', chname) - elif ptype == PlotType.Image: - axes = tuple(chdata['plot_axes']) - # TODO: get default value from the channel. - ndim = chdata.get('ndim', 1) - if ndim == 0: # a mesh-like plot? - pass # TODO implement - elif ndim == 1: # a 2D trend - if axes in trends2d: - trends2d[axes].append(chname) - else: - trends2d[axes] = CaselessList([chname]) - elif ndim == 2: # a 2D plot (e.g. an image) - pass # TODO: implement - else: - self.warning('Cannot create plot for %s', chname) - try: - # TODO: adapt _updateTemporaryTrends1D to use tpg - new1d, removed1d = self._updateTemporaryTrends1D(trends1d) - self.newShortMessage.emit("Changed panels (%i new, %i removed)" - % (len(new1d), len(removed1d))) - except Exception as e: - self.warning( - 'Plots cannot be updated. Only qwt5 is supported for now' - ) - self.error(e) - - # try: - # # TODO: adapt _updateTemporaryTrends2D to use tpg - # print(trends2d) - # new2d, removed2d = self._updateTemporaryTrends2D(trends2d) - # self.newShortMessage.emit("Changed panels (%i new, %i removed)" - # % (len(new2d), len(removed2d))) - # except Exception as e: - # self.warning( - # 'Plots 2d cannot be updated. Only qwt5 is supported for now' - # ) - # self.error(e) - - def _updateTemporaryTrends1D(self, trends1d): - '''adds necessary trend1D panels and removes no longer needed ones - - :param trends1d: (dict) A dict whose keys are tuples of axes and - whose values are list of model names to plot - - :returns: (tuple) two lists new,rm:new contains the names of the new - panels and rm contains the names of the removed panels - ''' - from taurus.qt.qtgui.plot import TaurusTrend # TODO: use tpg instead! - newpanels = [] - for axes, plotables in trends1d.items(): - if not axes: + self.warning('Unsupported image plot for %s', ch_name) continue - if axes not in self._trends1d: - w = TaurusTrend() - w.setXIsTime(False) - w.setScanDoor(self.getModelObj().getFullName()) - # TODO: use a standard key for and - w.setScansXDataKey(axes[0]) - pname = u'Trend1D - %s' % ":".join(axes) - panel = self.createPanel(w, pname, registerconfig=False, - permanent=False) - try: # if the panel is a dockwidget, raise it - panel.raise_() - except Exception: - pass - self._trends1d[axes] = pname - newpanels.append(pname) - - widget = self.getPanelWidget(self._trends1d[axes]) - flt = ChannelFilter(plotables) - widget.onScanPlotablesFilterChanged(flt) - - # remove trends that are no longer configured - removedpanels = [] - olditems = list(self._trends1d.items()) - for axes, name in olditems: - if axes not in trends1d: - removedpanels.append(name) - self.removePanel(name) - self._trends1d.pop(axes) - - return newpanels, removedpanels - - def _updateTemporaryTrends2D(self, trends2d): - '''adds necessary trend2D panels and removes no longer needed ones - - :param trends2d: (dict) A dict whose keys are tuples of axes and - whose values are list of model names to plot - - :returns: (tuple) two lists new,rm:new contains the names of the new - panels and rm contains the names of the removed panels - - ..note:: Not fully implemented yet - ''' - try: - from taurus.qt.qtgui.extra_guiqwt.taurustrend2d import \ - TaurusTrend2DDialog - from taurus.qt.qtgui.extra_guiqwt.image import ( - TaurusTrend2DScanItem) - except Exception: - self.info('guiqwt extension cannot be loaded. ' - + '2D Trends will not be created') - raise - return - - newpanels = [] - for axes, plotables in trends2d.items(): - for chname in plotables: - pname = u'Trend2D - %s' % chname - if pname in self._trends2d: - self._trends2d[pname].widget().trendItem.clearTrend() + x_axes = ['point_nb' if axis == '' else axis + for axis in column.get('plot_axes', ())] + ndim = column.get('ndim', 0) or 0 + if ptype == PlotType.Spectrum: + if ndim == 0: # this is a trend + for x_axis in x_axes: + x_label = col_map[x_axis]['label'] + curve = dict(column, x_axis=x_axis, + x_axis_label=x_label) + curves.append(curve) else: - axis = axes[0] - w = TaurusTrend2DDialog(stackMode='event') - plot = w.get_plot() - name = self.getModelObj().getFullName() - t2d = TaurusTrend2DScanItem(chname, axis, - name) - plot.add_item(t2d) - self.createPanel(w, pname, registerconfig=False, - permanent=False) - self._trends2d[(axes, chname)] = pname - newpanels.append(pname) - - # remove trends that are no longer configured - removedpanels = [] - olditems = list(self._trends2d.items()) - for axes, name in olditems: - if axes not in trends2d: - removedpanels.append(name) - self.removePanel(name) - self._trends2d.pop(axes) - - return newpanels, removedpanels + self.warning('Cannot create spectrum plot for %d dims ' + 'channel %r', ndim, ch_name) + + if self._group_mode == self.Single: + plots = [] + for curve in curves: + plot = dict(x_axis=dict(name=curve['x_axis'], + label=curve['x_axis_label']), + curves=[curve]) + plots.append(plot) + elif self._group_mode == self.XAxis: + plot_map = {} + for curve in curves: + x_axis = curve['x_axis'] + plot = plot_map.get(x_axis) + if plot is None: + plot = dict(x_axis=dict(name=curve['x_axis'], + label=curve['x_axis_label']), + curves=[]) + plot_map[x_axis] = plot + plot['curves'].append(curve) + plots = tuple(plot_map.values()) + else: + raise NotImplementedError + + nb_points = data.get('total_scan_intervals', 2**16 - 1) + 1 + self._plot.prepare(plots, nb_points=nb_points) + + # build status message + serialno = 'Scan #{}'.format(data.get('serialno', '?')) + title = data.get('title', 'unnamed operation') + if data.get('scandir') and data.get('scanfile'): + scan_file = data['scanfile'] + if isinstance(scan_file, (list, tuple)): + scan_file = '&'.join(data['scanfile']) + saving = data['scandir'] + '/' + scan_file + else: + saving = 'no saving!' + started = 'Started ' + data.get('starttime', '?') + progress = '{progress}' + self.message_template = ' | '.join((serialno, title, started, + progress, saving)) + self.newShortMessage.emit( + self.message_template.format(progress='Preparing...')) + + def newPoint(self, point): + data = point['data'] + self._plot.onNewPoint(data) + point_nb = 'Point #{}'.format(data['point_nb']) + msg = self.message_template.format(progress=point_nb) + self.newShortMessage.emit(msg) + + def end(self, end_data): + data = end_data['data'] + self._plot.onEnd(data) + progress = 'Ended {}'.format(data['endtime']) + msg = self.message_template.format(progress=progress) + self.newShortMessage.emit(msg) def createPanel(self, widget, name, **kwargs): '''Creates a "panel" from a widget. In this basic implementation this @@ -373,19 +395,6 @@ def removePanel(self, name): widget.setParent(None) widget.close() - def removePanels(self, names=None): - '''removes panels. - - :param names: (seq) names of the panels to be removed. If None is - given (default), all the panels are removed. - ''' - if names is None: - names = (list(self._trends1d.values()) - + list(self._trends2d.values())) - # TODO: do the same for other temporary panels - for pname in names: - self.removePanel(pname) - class MacroBroker(DynamicPlotManager): '''A manager of all macro-related panels of a TaurusGui. @@ -411,8 +420,6 @@ def __init__(self, parent): # connect the broker to shared data Qt.qApp.SDM.connectReader("doorName", self.setModel) - Qt.qApp.SDM.connectReader("expConfChanged", self.onExpConfChanged) - Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') def setModel(self, doorname): ''' Reimplemented from :class:`DynamicPlotManager`.''' @@ -506,7 +513,7 @@ def _createPermanentPanels(self): 'doorNameChanged') # Create ExpDescriptionEditor dialog - self.__expDescriptionEditor = ExpDescriptionEditor(plotsButton=False) + self.__expDescriptionEditor = ExpDescriptionEditor() SDM.connectReader("doorName", self.__expDescriptionEditor.setModel) mainwindow.createPanel(self.__expDescriptionEditor, 'Experiment Config', @@ -576,14 +583,9 @@ def _createPermanentPanels(self): self.__doorOutput.onDoorWarningChanged) SDM.connectReader("doorErrorChanged", self.__doorOutput.onDoorErrorChanged) - mainwindow.createPanel(self.__doorOutput, 'DoorOutput', - registerconfig=False, permanent=True) - - # puts doorDebug - self.__doorDebug = DoorDebug() SDM.connectReader("doorDebugChanged", - self.__doorDebug.onDoorDebugChanged) - mainwindow.createPanel(self.__doorDebug, 'DoorDebug', + self.__doorOutput.onDoorDebugChanged) + mainwindow.createPanel(self.__doorOutput, 'DoorOutput', registerconfig=False, permanent=True) # puts doorResult @@ -632,7 +634,7 @@ def __onDoorAbort(self): door.command_inout('abort') # send stop/abort to all pools pools = door.macro_server.getElementsOfType('Pool') - for pool in pools.values(): + for pool in list(pools.values()): self.info('Sending %s command to %s' % (cmd, pool.getFullName())) try: pool.getObj().command_inout(cmd) diff --git a/src/sardana/test/test_sardanabuffer.py b/src/sardana/test/test_sardanabuffer.py index 60e5a16991..1a645b93e1 100644 --- a/src/sardana/test/test_sardanabuffer.py +++ b/src/sardana/test/test_sardanabuffer.py @@ -23,7 +23,7 @@ ## ############################################################################## -from taurus.external.unittest import TestCase +from unittest import TestCase from sardana.sardanabuffer import SardanaBuffer diff --git a/src/sardana/test/test_sardanavalue.py b/src/sardana/test/test_sardanavalue.py index 08a287bb5c..eefcfa0ba9 100644 --- a/src/sardana/test/test_sardanavalue.py +++ b/src/sardana/test/test_sardanavalue.py @@ -25,7 +25,7 @@ """Unit tests for sardanavalue module""" -from taurus.external import unittest +import unittest from sardana.sardanavalue import SardanaValue @@ -72,7 +72,7 @@ def testSardanaValueWithExceptionInfo(self): self.assertEqual(sar_val.error, True, 'The error attribute should be True.') - self.assertRegexpMatches(representation, ".*.*", + self.assertRegex(representation, ".*.*", 'The SardanaValue representation does not contain .') def testSardanaValueWithNoExceptionInfo(self): @@ -84,7 +84,7 @@ def testSardanaValueWithNoExceptionInfo(self): sar_val = SardanaValue(value=value) returned_string = sar_val.__repr__() - self.assertRegexpMatches(returned_string, repr(value), + self.assertRegex(returned_string, repr(value), 'The SardanaValue representation does not contain its value') self.assertEqual(sar_val.error, False, diff --git a/src/sardana/test/testsuite.py b/src/sardana/test/testsuite.py index 087b7192a6..cceb776e6b 100644 --- a/src/sardana/test/testsuite.py +++ b/src/sardana/test/testsuite.py @@ -24,102 +24,15 @@ ############################################################################## """ -This module defines the test suite for the whole sardana package -Usage:: - - from sardana.test import testsuite - testsuite.run() - +DEPRECATED """ __docformat__ = 'restructuredtext' -import os -import re -from taurus.external import unittest -import sardana - - -def _filter_suite(suite, exclude_pattern, ret=None): - """removes TestCases from a suite based on regexp matching on the Test id""" - if ret is None: - ret = unittest.TestSuite() - for e in suite: - if isinstance(e, unittest.TestCase): - if re.match(exclude_pattern, e.id()): - print "Excluded %s" % e.id() - continue - ret.addTest(e) - else: - _filter_suite(e, exclude_pattern, ret=ret) - return ret - - -def get_sardana_suite(exclude_pattern='(?!)'): - """discover all tests in sardana, except those matching `exclude_pattern`""" - loader = unittest.defaultTestLoader - start_dir = os.path.dirname(sardana.__file__) - suite = loader.discover( - start_dir, top_level_dir=os.path.dirname(start_dir)) - return _filter_suite(suite, exclude_pattern) - - -def get_sardana_unitsuite(): - """Provide test suite with only unit tests. These exclude: - - functional tests of macros that requires the "sar_demo environment" - """ - pattern = 'sardana\.macroserver\.macros\.test*|' +\ - 'sardana\.tango\.pool\.test*' - return get_sardana_suite(exclude_pattern=pattern) - - -def run(exclude_pattern='(?!)'): - '''Runs all tests for the sardana package - - :returns: the test runner result - :rtype: unittest.result.TestResult - ''' - # discover all tests within the sardana/src directory - suite = get_sardana_suite(exclude_pattern=exclude_pattern) - # use the basic text test runner that outputs to sys.stderr - runner = unittest.TextTestRunner(descriptions=True, verbosity=2) - # run the test suite - result = runner.run(suite) - return result - def main(): + print("sardanatestsuite was removed in Sardana 3.0.3. " + "Use pytest to run tests.") import sys - from taurus.external import argparse - from sardana import Release - - parser = argparse.ArgumentParser(description='Main test suite for Sardana') - # TODO: Define the default exclude patterns as a sardanacustomsettings - # variable. - help = """regexp pattern matching test ids to be excluded. - (e.g. 'sardana\.pool\..*' would exclude sardana.pool tests) - """ - parser.add_argument('-e', '--exclude-pattern', - dest='exclude_pattern', - default='(?!)', - help=help) - parser.add_argument('--version', action='store_true', default=False, - help="show program's version number and exit") - args = parser.parse_args() - - if args.version: - print Release.version - sys.exit(0) - - ret = run(exclude_pattern=args.exclude_pattern) - - # calculate exit code (0 if OK and 1 otherwise) - if ret.wasSuccessful(): - exit_code = 0 - else: - exit_code = 1 - sys.exit(exit_code) - + sys.exit(1) -if __name__ == '__main__': - main() diff --git a/src/sardana/tools/config/fods_to_sar.py b/src/sardana/tools/config/fods_to_sar.py index 579c1c7c6d..5e1b6b92bb 100644 --- a/src/sardana/tools/config/fods_to_sar.py +++ b/src/sardana/tools/config/fods_to_sar.py @@ -21,7 +21,7 @@ def transform(f): xslt_filename = os.path.join(directory, "FODS_TO_SAR.xslt") t = etree.XSLT(etree.parse(xslt_filename)) - if type(f) in types.StringTypes: + if isinstance(f, str): doc = etree.parse(f) else: doc = f @@ -30,12 +30,12 @@ def transform(f): def main(): if len(sys.argv) < 2: - print __doc__ + print(__doc__) sys.exit(1) filename = sys.argv[1] t = transform(filename) - print etree.tostring(t, pretty_print=True) + print(etree.tostring(t, pretty_print=True)) if __name__ == "__main__": main() diff --git a/src/sardana/tools/config/get_pool_config.py b/src/sardana/tools/config/get_pool_config.py index 903441f60d..72865d27c4 100644 --- a/src/sardana/tools/config/get_pool_config.py +++ b/src/sardana/tools/config/get_pool_config.py @@ -37,8 +37,8 @@ def checkPoolElements(pool): try: ctrl_library = pool_ctrl_classes[ctrl_class][0] except KeyError: - print ("#WARNING: There is no controller class %s for controller %s" % - (ctrl_class, ctrl_name)) + print(("#WARNING: There is no controller class %s for " + "controller %s" % (ctrl_class, ctrl_name))) continue ctrl_type = str(info['main_type']) # sardana script is not compatible with the new type CTExpChannel @@ -80,10 +80,10 @@ def checkPoolElements(pool): config = json.loads(config) controllers = config['controllers'] elements = {} - for ctrl, c_data in controllers.items(): - if c_data.has_key('units'): + for ctrl, c_data in list(controllers.items()): + if 'units' in c_data: c_data = c_data['units']['0'] - for _, data in c_data['channels'].items(): + for _, data in list(c_data['channels'].items()): index = int(data['index']) if ctrl == '__tango__': elements[index] = data['full_name'] @@ -103,7 +103,7 @@ def checkPoolElements(pool): db = taurus.Database() pool_elements_detail = {} - for element_type in pool_elements.keys(): + for element_type in list(pool_elements.keys()): elements = pool_elements[element_type] for info in elements: info_splitted = json.loads(info) @@ -133,13 +133,15 @@ def checkPoolElements(pool): attrs = element_dev.get_attribute_list() for attr, attr_dict in db.get_device_attribute_property( normal_name, - map(str, attrs) - ).iteritems(): + list(map(str, attrs)) + ).items(): if len(attr_dict) > 0: pool_elements_detail[alias]['attr_dicts'][attr] = attr_dict else: if attr.lower() in ['position', 'value']: - print '***', specific_element_type, alias, attr, 'NO MEMORIZED ATTRIBUTES OR ATTRIBUTE CONFIGURATIONS ***' + print('***', specific_element_type, alias, attr, + 'NO MEMORIZED ATTRIBUTES OR ATTRIBUTE ' + 'CONFIGURATIONS ***') # print '\n' # print '----------------------------------------------------------------' @@ -148,7 +150,7 @@ def checkPoolElements(pool): # print pool_instruments # CHECK ELEMENTS WITHOUT INSTRUMENT - for element_type in pool_elements.keys(): + for element_type in list(pool_elements.keys()): elements = pool_elements[element_type] elements_with_no_instrument = [] for info in elements: @@ -164,8 +166,8 @@ def checkPoolElements(pool): try: ctrl = pool_controllers[ctrl_name] except KeyError: - print ("#WARNING: There is no controller %s for element %s" % - (ctrl_name, alias)) + print(("#WARNING: There is no controller %s for element %s" % + (ctrl_name, alias))) continue ctrl['ctrl_pool_elements'].append(alias) if specific_element_type in ['PseudoMotor', 'PseudoCounter']: @@ -244,7 +246,7 @@ def checkPoolElements(pool): row = '\t'.join(columns) parameters_sheet += row + '\n' - for ctrl_type, controllers in pool_controllers_by_type.iteritems(): + for ctrl_type, controllers in pool_controllers_by_type.items(): if len(controllers) == 0: continue for ctrl in controllers: @@ -260,7 +262,7 @@ def checkPoolElements(pool): ctrl_details['properties'] = '' else: properties = [] - for k, v in ctrl_details['properties'].iteritems(): + for k, v in ctrl_details['properties'].items(): properties.append(k + ':' + v) ctrl_details['properties'] = ';'.join(properties) @@ -279,9 +281,9 @@ def checkPoolElements(pool): elem_type = elem_details['type'] attr_dicts = elem_details['attr_dicts'] attribute_values = [] - for attr in attr_dicts.keys(): + for attr in list(attr_dicts.keys()): attr_dict = attr_dicts[attr] - if attr_dict.has_key('__value'): + if '__value' in attr_dict: # skip memorized values of DialPosition and Position # DialPosition because it is read only attribute and the # current version of sardana script would not be able to se it @@ -317,7 +319,7 @@ def checkPoolElements(pool): 'event_period', [''])[0] elem_params['event'] = attr_dict.get('abs_change', [''])[0] - for k, v in elem_params.iteritems(): + for k, v in elem_params.items(): if v != '' and k not in ['pool', 'element', 'parameter']: params_row_template = '{pool}\t{element}\t{parameter}\t{label}\t{format}\t{min_value}\t{min_alarm}\t{min_warning}\t{max_warning}\t{max_alarm}\t{max_value}\t{unit}\t{polling}\t{event}' row = params_row_template.format(**elem_params) @@ -346,7 +348,7 @@ def checkPoolElements(pool): instruments_sheet += row + '\n' acq_row_template = '{type}\t{pool}\t{name}\tAutomatic\t{channels}' - for mg_name, mg_channels in pool_measurement_groups.iteritems(): + for mg_name, mg_channels in pool_measurement_groups.items(): mg_details = {} mg_details['type'] = 'MeasurementGroup' mg_details['pool'] = pool @@ -355,46 +357,53 @@ def checkPoolElements(pool): row = acq_row_template.format(**mg_details) acquisition_sheet += row + '\n' - print '\n' * 2 - print '################################ CONTROLLERS ################################\n' * 4 - print '\n' * 2 - print controllers_sheet - print '\n' * 2 - print '################################ INSTRUMENTS ################################\n' * 4 - print '\n' * 2 - print instruments_sheet - print '\n' * 2 - print '################################ MOTORS ################################\n' * 4 - print '\n' * 2 - print motors_sheet - print '\n' * 2 - print '################################ IOREGS ################################\n' * 4 - print '\n' * 2 - print ioregs_sheet - print '\n' * 2 - print '################################ CHANNELS ################################\n' * 4 - print '\n' * 2 - print channels_sheet - print '\n' * 2 - print '################################ ACQUISITION ################################\n' * 4 - print '\n' * 2 - print acquisition_sheet - print '\n' * 2 - print '################################ PARAMETERS ################################\n' * 4 - print '\n' * 2 - print parameters_sheet + print('\n' * 2) + print('################################ CONTROLLERS ' + '################################\n' * 4) + print('\n' * 2) + print(controllers_sheet) + print('\n' * 2) + print('################################ INSTRUMENTS ' + '################################\n' * 4) + print('\n' * 2) + print(instruments_sheet) + print('\n' * 2) + print('################################ MOTORS ' + '################################\n' * 4) + print('\n' * 2) + print(motors_sheet) + print('\n' * 2) + print('################################ IOREGS ' + '################################\n' * 4) + print('\n' * 2) + print(ioregs_sheet) + print('\n' * 2) + print('################################ CHANNELS ' + '################################\n' * 4) + print('\n' * 2) + print(channels_sheet) + print('\n' * 2) + print('################################ ACQUISITION ' + '################################\n' * 4) + print('\n' * 2) + print(acquisition_sheet) + print('\n' * 2) + print('################################ PARAMETERS ' + '################################\n' * 4) + print('\n' * 2) + print(parameters_sheet) if __name__ == '__main__': if len(sys.argv) != 2 or sys.argv[1] == '?': - print '----------------------------------------' - print 'Invalid number of arguments.' - print '' - print 'Example of usage:' - print ' python get_pool_config pool' - print '' - print ' where pool is the device name of the pool' - print '----------------------------------------' + print('----------------------------------------') + print('Invalid number of arguments.') + print('') + print('Example of usage:') + print(' python get_pool_config pool') + print('') + print(' where pool is the device name of the pool') + print('----------------------------------------') pool = sys.argv[1] checkPoolElements(pool) diff --git a/src/sardana/tools/config/pexpect23.py b/src/sardana/tools/config/pexpect23.py index efe158093b..49e363835c 100644 --- a/src/sardana/tools/config/pexpect23.py +++ b/src/sardana/tools/config/pexpect23.py @@ -68,7 +68,6 @@ import sys import time import select - import string import re import struct import resource @@ -80,7 +79,7 @@ import errno import traceback import signal -except ImportError, e: +except ImportError as e: raise ImportError (str(e) + """ A critical module was not found. Probably this operating system does not @@ -225,8 +224,8 @@ def print_ticks(d): child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, cwd=cwd, env=env) if events is not None: - patterns = events.keys() - responses = events.values() + patterns = list(events.keys()) + responses = list(events.values()) else: patterns = None # We assume that EOF or TIMEOUT will save us. responses = None @@ -235,16 +234,16 @@ def print_ticks(d): while True: try: index = child.expect(patterns) - if type(child.after) in types.StringTypes: + if isinstance(child.after, str): child_result_list.append(child.before + child.after) else: # child.after may have been a TIMEOUT or EOF, so don't cat those. child_result_list.append(child.before) - if type(responses[index]) in types.StringTypes: + if isinstance(responses[index], str): child.send(responses[index]) elif isinstance(responses[index], types.FunctionType): callback_result = responses[index](locals()) sys.stdout.flush() - if type(callback_result) in types.StringTypes: + if isinstance(callback_result, str): child.send(callback_result) elif callback_result: break @@ -252,10 +251,10 @@ def print_ticks(d): raise TypeError( 'The callback must be a string or function type.') event_count = event_count + 1 - except TIMEOUT, e: + except TIMEOUT: child_result_list.append(child.before) break - except EOF, e: + except EOF: child_result_list.append(child.before) break child_result = ''.join(child_result_list) @@ -540,7 +539,7 @@ def _spawn(self, command, args=[]): if self.use_native_pty_fork: try: self.pid, self.child_fd = pty.fork() - except OSError, e: + except OSError as e: raise ExceptionPexpect('Error! pty.fork() failed: ' + str(e)) else: # Use internal __fork_pty self.pid, self.child_fd = self.__fork_pty() @@ -595,11 +594,12 @@ def __fork_pty(self): parent_fd, child_fd = os.openpty() if parent_fd < 0 or child_fd < 0: - raise ExceptionPexpect, "Error! Could not open pty with os.openpty()." + raise ExceptionPexpect( + "Error! Could not open pty with os.openpty().") pid = os.fork() if pid < 0: - raise ExceptionPexpect, "Error! Failed os.fork()." + raise ExceptionPexpect("Error! Failed os.fork().") elif pid == 0: # Child. os.close(parent_fd) @@ -636,7 +636,8 @@ def __pty_make_controlling_tty(self, tty_fd): fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) if fd >= 0: os.close(fd) - raise ExceptionPexpect, "Error! We are not disconnected from a controlling tty." + raise ExceptionPexpect( + "Error! We are not disconnected from a controlling tty.") except: # Good! We are disconnected from a controlling tty. pass @@ -644,14 +645,16 @@ def __pty_make_controlling_tty(self, tty_fd): # Verify we can open child pty. fd = os.open(child_name, os.O_RDWR) if fd < 0: - raise ExceptionPexpect, "Error! Could not open child pty, " + child_name + raise ExceptionPexpect( + "Error! Could not open child pty, " + child_name) else: os.close(fd) # Verify we now have a controlling tty. fd = os.open("/dev/tty", os.O_WRONLY) if fd < 0: - raise ExceptionPexpect, "Error! Could not open controlling tty, /dev/tty" + raise ExceptionPexpect( + "Error! Could not open controlling tty, /dev/tty") else: os.close(fd) @@ -829,26 +832,30 @@ def read_nonblocking(self, size=1, timeout=-1): if not r: if not self.isalive(): - # Some platforms, such as Irix, will claim that their processes are alive; + # Some platforms, such as Irix, will claim that their + # processes are alive; # then timeout on the select; and then finally admit that they # are not alive. self.flag_eof = True raise EOF( - 'End of File (EOF) in read_nonblocking(). Very pokey platform.') + 'End of File (EOF) in read_nonblocking(). ' + 'Very pokey platform.') else: raise TIMEOUT('Timeout exceeded in read_nonblocking().') if self.child_fd in r: try: s = os.read(self.child_fd, size) - except OSError, e: # Linux does this + except OSError: # Linux does this self.flag_eof = True raise EOF( - 'End Of File (EOF) in read_nonblocking(). Exception style platform.') + 'End Of File (EOF) in read_nonblocking(). ' + 'Exception style platform.') if s == '': # BSD style self.flag_eof = True raise EOF( - 'End Of File (EOF) in read_nonblocking(). Empty string style platform.') + 'End Of File (EOF) in read_nonblocking(). ' + 'Empty string style platform.') if self.logfile is not None: self.logfile.write(s) @@ -913,7 +920,7 @@ def __iter__(self): # File-like object. return self - def next(self): # File-like object. + def __next__(self): # File-like object. """This is to support iterators over a file-like object. """ @@ -1076,7 +1083,7 @@ def terminate(self, force=False): else: return False return False - except OSError, e: + except OSError: # I think there are kernel timing issues that sometimes cause # this to happen. I think isalive() reports True, but the # process is dead to the kernel. @@ -1125,17 +1132,18 @@ def isalive(self): return False if self.flag_eof: - # This is for Linux, which requires the blocking form of waitpid to get - # status of a defunct process. This is super-lame. The flag_eof would have - # been set in read_nonblocking(), so this should be safe. + # This is for Linux, which requires the blocking form of waitpid + # to get status of a defunct process. This is super-lame. The + # flag_eof would have been set in read_nonblocking(), so this + # should be safe. waitpid_options = 0 else: waitpid_options = os.WNOHANG try: pid, status = os.waitpid(self.pid, waitpid_options) - except OSError, e: # No child processes - if e[0] == errno.ECHILD: + except OSError as e: # No child processes + if e.args[0] == errno.ECHILD: raise ExceptionPexpect( 'isalive() encountered condition where "terminated" is 0, but there was no child process. Did someone else call waitpid() on our process?') else: @@ -1148,8 +1156,8 @@ def isalive(self): try: # os.WNOHANG) # Solaris! pid, status = os.waitpid(self.pid, waitpid_options) - except OSError, e: # This should never happen... - if e[0] == errno.ECHILD: + except OSError as e: # This should never happen... + if e.args[0] == errno.ECHILD: raise ExceptionPexpect( 'isalive() encountered condition that should never happen. There was no child process. Did someone else call waitpid() on our process?') else: @@ -1216,7 +1224,7 @@ def compile_pattern_list(self, patterns): if patterns is None: return [] - if not isinstance(patterns, types.ListType): + if not isinstance(patterns, list): patterns = [patterns] compile_flags = re.DOTALL # Allow dot to match \n @@ -1224,7 +1232,7 @@ def compile_pattern_list(self, patterns): compile_flags = compile_flags | re.IGNORECASE compiled_pattern_list = [] for p in patterns: - if type(p) in types.StringTypes: + if isinstance(p, str): compiled_pattern_list.append(re.compile(p, compile_flags)) elif p is EOF: compiled_pattern_list.append(EOF) @@ -1343,7 +1351,7 @@ def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1): This method is also useful when you don't want to have to worry about escaping regular expression characters that you want to match.""" - if type(pattern_list) in types.StringTypes or pattern_list in (TIMEOUT, EOF): + if isinstance(pattern_list, str) or pattern_list in (TIMEOUT, EOF): pattern_list = [pattern_list] return self.expect_loop(searcher_string(pattern_list), timeout, searchwindowsize) @@ -1385,7 +1393,7 @@ def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1): incoming = incoming + c if timeout is not None: timeout = end_time - time.time() - except EOF, e: + except EOF as e: self.buffer = '' self.before = incoming self.after = EOF @@ -1398,7 +1406,7 @@ def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1): self.match = None self.match_index = None raise EOF(str(e) + '\n' + str(self)) - except TIMEOUT, e: + except TIMEOUT as e: self.buffer = incoming self.before = incoming self.after = TIMEOUT @@ -1422,7 +1430,7 @@ def getwinsize(self): """This returns the terminal window size of the child tty. The return value is a tuple of (rows, cols). """ - TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912L) + TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912) s = struct.pack('HHHH', 0, 0, 0, 0) x = fcntl.ioctl(self.fileno(), TIOCGWINSZ, s) return struct.unpack('HHHH', x)[0:2] @@ -1443,7 +1451,7 @@ def setwinsize(self, r, c): # Newer versions of Linux have totally different values for TIOCSWINSZ. # Note that this fix is a hack. TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) - if TIOCSWINSZ == 2148037735L: # L is not required in Python >= 2.2. + if TIOCSWINSZ == 2148037735: # L is not required in Python >= 2.2. TIOCSWINSZ = -2146929561 # Same bits, but with sign. # Note, assume ws_xpixel and ws_ypixel are zero. s = struct.pack('HHHH', r, c, 0, 0) @@ -1521,6 +1529,7 @@ def __interact_copy(self, escape_character=None, input_filter=None, output_filte if self.logfile is not None: self.logfile.write(data) self.logfile.flush() + # TODO: This could be broken!!! Decide what to do... os.write(self.STDOUT_FILENO, data) if self.STDIN_FILENO in r: data = self.__interact_read(self.STDIN_FILENO) @@ -1546,8 +1555,8 @@ def __select(self, iwtd, owtd, ewtd, timeout=None): while True: try: return select.select(iwtd, owtd, ewtd, timeout) - except select.error, e: - if e[0] == errno.EINTR: + except select.error as e: + if e.args[0] == errno.EINTR: # if we loop back we have to subtract the amount of time we # already waited. if timeout is not None: @@ -1603,7 +1612,7 @@ def __init__(self, strings): self.eof_index = -1 self.timeout_index = -1 self._strings = [] - for n, s in zip(range(len(strings)), strings): + for n, s in zip(list(range(len(strings))), strings): if s is EOF: self.eof_index = n continue @@ -1624,7 +1633,7 @@ def __str__(self): ss.append((self.timeout_index, ' %d: TIMEOUT' % self.timeout_index)) ss.sort() - ss = zip(*ss)[1] + ss = list(zip(*ss))[1] return '\n'.join(ss) def search(self, buffer, freshlen, searchwindowsize=None): @@ -1700,7 +1709,7 @@ def __init__(self, patterns): self.eof_index = -1 self.timeout_index = -1 self._searches = [] - for n, s in zip(range(len(patterns)), patterns): + for n, s in zip(list(range(len(patterns))), patterns): if s is EOF: self.eof_index = n continue @@ -1722,7 +1731,7 @@ def __str__(self): ss.append((self.timeout_index, ' %d: TIMEOUT' % self.timeout_index)) ss.sort() - ss = zip(*ss)[1] + ss = list(zip(*ss))[1] return '\n'.join(ss) def search(self, buffer, freshlen, searchwindowsize=None): @@ -1770,7 +1779,7 @@ def which(filename): if os.access(filename, os.X_OK): return filename - if not os.environ.has_key('PATH') or os.environ['PATH'] == '': + if 'PATH' not in os.environ or os.environ['PATH'] == '': p = os.defpath else: p = os.environ['PATH'] @@ -1778,7 +1787,7 @@ def which(filename): # Oddly enough this was the one line that made Pexpect # incompatible with Python 1.5.2. #pathlist = p.split (os.pathsep) - pathlist = string.split(p, os.pathsep) + pathlist = str.split(p, os.pathsep) for path in pathlist: f = os.path.join(path, filename) diff --git a/src/sardana/tools/config/sar_to_fods.py b/src/sardana/tools/config/sar_to_fods.py index af46c270b1..3dbfc810f2 100644 --- a/src/sardana/tools/config/sar_to_fods.py +++ b/src/sardana/tools/config/sar_to_fods.py @@ -7,7 +7,7 @@ def transform(f): t = etree.XSLT(etree.parse("SAR_TO_FODS.xslt")) - if type(f) in types.StringTypes: + if isinstance(f, str): doc = etree.parse(f) else: doc = f @@ -17,7 +17,7 @@ def transform(f): def main(): filename = sys.argv[1] t = transform(filename) - print etree.tostring(t, pretty_print=True) + print(etree.tostring(t, pretty_print=True)) if __name__ == "__main__": main() diff --git a/src/sardana/tools/config/sardana.py b/src/sardana/tools/config/sardana.py index 6f31ee6e69..7c33fe8487 100644 --- a/src/sardana/tools/config/sardana.py +++ b/src/sardana/tools/config/sardana.py @@ -76,11 +76,12 @@ except: try: import pexpect23 as pexpect - print "[WARNING]: pexpect module not found. Using local pexpect 2.3" - except Exception, e: - print e - print "The Sardana requires pexpect python module which was not found." - print "This module can be found at http://www.noah.org/wiki/Pexpect" + print("[WARNING]: pexpect module not found. Using local pexpect 2.3") + except Exception as e: + print(e) + print("The Sardana requires pexpect python module which was" + "not found.") + print("This module can be found at http://www.noah.org/wiki/Pexpect") sys.exit(2) @@ -94,7 +95,8 @@ class Process: MaxStartupTime = 30 MaxShutdownTime = 30 - def __init__(self, executable, args, name="Process", instance=None, logfile=None, env=None): + def __init__(self, executable, args, name="Process", instance=None, + logfile=None, env=None): self._start_time = -1 self._stop_time = -1 self._process = None @@ -148,7 +150,7 @@ def start(self): idx = self._process.expect([Process.ReadyMsg, Process.AlreadyRunning, pexpect.EOF, pexpect.TIMEOUT], timeout=self.getMaxStartupTime()) - except Exception, e: + except Exception as e: self.on("[FAILED]") raise e @@ -202,9 +204,10 @@ def stop(self, max_shutdown_time=None): try: res = self.terminate() - idx = self._process.expect(['Exiting', 'Exited', pexpect.EOF, pexpect.TIMEOUT], + idx = self._process.expect(['Exiting', 'Exited', + pexpect.EOF, pexpect.TIMEOUT], timeout=max_shutdown_time) - except Exception, e: + except Exception as e: self.on("[FAILED]") raise e @@ -222,9 +225,10 @@ def stop(self, max_shutdown_time=None): return try: - idx = self._process.expect(['Exited', pexpect.EOF, pexpect.TIMEOUT], + idx = self._process.expect(['Exited', pexpect.EOF, + pexpect.TIMEOUT], timeout=5) - except Exception, e: + except Exception as e: self.on("[FAILED]") raise e @@ -240,7 +244,7 @@ def stop(self, max_shutdown_time=None): try: idx = self._process.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5) - except Exception, e: + except Exception as e: self.on("[FAILED]") raise e @@ -253,7 +257,7 @@ def stop(self, max_shutdown_time=None): self.o(" (shutdown time exceeded). Forcing... ") self.kill() - except KeyboardInterrupt, ki: + except KeyboardInterrupt: self.o("(Ctrl-C during stop). Forcing... ") self.kill() @@ -315,7 +319,7 @@ def __init__(self, instname, db=None, logfile=None): f, path, desc = imp.find_module('SimuMotor') if f: f.close() - except exceptions.ImportError, e: + except exceptions.ImportError: msg = "Could not find %s executable.\n" \ "Make sure PYTHONPATH points to the directory(ies) where " \ "SimuMotorCtrl.py and SimuMotor.py files are installed" % name @@ -336,7 +340,7 @@ def __init__(self, instname, db=None, logfile=None): f, fname, desc = imp.find_module('SimuCoTiCtrl') if f: f.close() - except exceptions.ImportError, e: + except exceptions.ImportError: msg = "Could not find %s executable.\n" \ "Make sure PYTHONPATH points to the directory(ies) where " \ "SimuCoTiCtrl.py file is installed" % name @@ -357,7 +361,7 @@ def __init__(self, instname, db=None, logfile=None): f, fname, desc = imp.find_module('PySignalSimulator') if f: f.close() - except exceptions.ImportError, e: + except exceptions.ImportError: msg = "Could not find %s executable.\n" \ "Make sure PYTHONPATH points to the directory where " \ "PySignalSimulator.py is installed" % name @@ -448,30 +452,37 @@ def __init__(self, servNode, bl, createProc=False, log=False): import socket server_host_ip = socket.gethostbyname_ex(server_host)[2][0] pytango_host_ip = socket.gethostbyname_ex(pytango_host)[2][0] - if (server_host_ip != pytango_host_ip) or (server_port != pytango_port): - print '\t!!! WARNING !!! %s TANGO_HOST is not the PyTango default. You may erase the WRONG sardana definition.' % self._complete_name - print '\tServer: %s PyTango: %s' % (server_tango_host, pytango_tango_host) - ans = raw_input('\tDo you _really_ want to continue? [y|N] ') + if (server_host_ip != pytango_host_ip) \ + or (server_port != pytango_port): + print('\t!!! WARNING !!! %s TANGO_HOST is not the PyTango ' + 'default. You may erase the WRONG sardana definition.' % + self._complete_name) + print('\tServer: %s PyTango: %s' % + (server_tango_host, pytango_tango_host)) + ans = input('\tDo you _really_ want to continue? [y|N] ') if ans.lower() not in ['y', 'yes']: raise Exception( - 'User cancelled the creation of %s server' % self._complete_name) - ####################################################################### - - ####################################################################### - # Before erasing the content in the database, we will also create a backup - # of all the tango devices's properties and memorized attributes "a-la jive". - # There's an script called jive-save-config that given the parameters - # and it saves the config into the specified file the - # same way you can right-click an instance within jive and select the - # option 'Save server data'. + 'User cancelled the creation of %s server' % + self._complete_name) + ###################################################################### + + ###################################################################### + # Before erasing the content in the database, we will also create a + # backup of all the tango devices's properties and memorized + # attributes "a-la jive". There's an script called jive-save-config + # that given the parameters and it saves + # the config into the specified file the same way you can + # right-click an instance within jive and select the option 'Save + # server data'. try: config_file_name = self._klass_name + '-' + self._inst_name + \ '-' + time.strftime('%Y%m%d_%H%M%S') + '.jive' cmd = 'TANGO_HOST=%s jive-save-config %s %s &>/dev/null' % ( server_tango_host, self._complete_name, config_file_name) os.system(cmd) - print 'There is a backup of the deleted server config in: %s' % config_file_name - except: + print('There is a backup of the deleted server config in: %s' % + config_file_name) + except Exception: pass ####################################################################### @@ -690,9 +701,11 @@ def handle_attributes(self, dev_name, node): v = self._item_node_to_value(tango_attr, v_node) try: dev.write_attribute(name, v) - except Exception, ex: - print 'SOME PROBLEMS SETTING ATTRIBUTE VALUE FOR DEVICE', dev_name, 'ATTRIBUTE', tango_attr.name, 'VALUE', str(v) - print 'EXCEPTION:', ex + except Exception as ex: + print('SOME PROBLEMS SETTING ATTRIBUTE VALUE FOR ' + 'DEVICE', dev_name, 'ATTRIBUTE', + tango_attr.name, 'VALUE', str(v)) + print('EXCEPTION:', ex) c_node = attr.find("Configuration") if not c_node is None: @@ -739,7 +752,7 @@ def handle_attributes(self, dev_name, node): attr_info.events.ch_event.rel_change = rel p_node = attr.find("Polling") - if not p_node is None: + if p_node is not None: polled = p_node.get("polled") or 'False' polled = not (polled.lower() in ( 'false', 'no', 'n', '0')) @@ -748,14 +761,15 @@ def handle_attributes(self, dev_name, node): period = int(p_node.get("period") or 0) dev.poll_attribute(name, period) except: - print dev_name, tango_attr.name + print(dev_name, tango_attr.name) try: dev.set_attribute_config(attr_info) - except Exception, e: - print 'COULD NOT SET THE FOLLOWING CONFIG FOR DEVICE', dev_name, 'ATTR', tango_attr.name - print 'ATTRIBUTE INFO:', attr_info - print 'EXCEPTION:', e + except Exception as e: + print('COULD NOT SET THE FOLLOWING CONFIG FOR DEVICE', + dev_name, 'ATTR', tango_attr.name) + print('ATTRIBUTE INFO:', attr_info) + print('EXCEPTION:', e) intrument_node = node.find("InstrumentRef") if not intrument_node is None: @@ -764,9 +778,10 @@ def handle_attributes(self, dev_name, node): try: value = value.strip() dev.write_attribute('Instrument', value) - except Exception, ex: - print 'SOME PROBLEMS SETTING INSTRUMENT VALUE FOR DEVICE', dev_name, 'VALUE', value - print 'EXCEPTION:', ex + except Exception as ex: + print('SOME PROBLEMS SETTING INSTRUMENT VALUE FOR DEVICE', + dev_name, 'VALUE', value) + print('EXCEPTION:', ex) def loadPool(self): start_load_time = datetime.datetime.now() @@ -783,8 +798,7 @@ def loadPool(self): pool_dp = PyTango.DeviceProxy(pool_dev_name) factory = CodecFactory() - elements = factory.decode( - pool_dp.elements, ensure_ascii='True')['new'] + elements = factory.decode(pool_dp.elements) ctrl_classes_info = {} for elem in elements: @@ -842,7 +856,7 @@ def loadPool(self): pars.append(p.get("name")) pars.append(p.text or '\n'.join( [i.text for i in p.findall("Item")])) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateController", pars) # to flush any output generated by the pool self.run(step=True) @@ -876,7 +890,7 @@ def loadPool(self): pars = ["Motor", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) @@ -914,7 +928,7 @@ def loadPool(self): pars = ["CTExpChannel", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) @@ -950,13 +964,13 @@ def loadPool(self): pars = ["ZeroDExpChannel", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) try: pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) - except PyTango.DevFailed, df: + except PyTango.DevFailed as df: self.on("Exception creating %s: %s" % (aliasName, str(df))) # to flush any output generated by the pool @@ -990,7 +1004,7 @@ def loadPool(self): pars = ["OneDExpChannel", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) @@ -1026,7 +1040,7 @@ def loadPool(self): pars = ["TwoDExpChannel", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) @@ -1063,7 +1077,7 @@ def loadPool(self): pars = ["IORegister", name, axis, aliasName] if deviceName.count('/') == 2: pars.append(deviceName) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateElement", pars) self.handle_attributes(aliasName, e) @@ -1124,7 +1138,7 @@ def loadPool(self): pars.append(p.text or '\n'.join( [i.text for i in p.findall("Item")])) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateController", pars) pm_ctrl_count += 1 @@ -1136,7 +1150,7 @@ def loadPool(self): self.run(step=True) except: self.on("[FAILED]") - print ctrl_class_info + print(ctrl_class_info) raise self.on(" (%d ctrls; %d pmotors) [DONE]" % ( pm_ctrl_count, pm_count)) @@ -1182,7 +1196,7 @@ def loadPool(self): pars.append(p.text or '\n'.join( [i.text for i in p.findall("Item")])) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateController", pars) pc_ctrl_count += 1 @@ -1214,7 +1228,7 @@ def loadPool(self): pars = [aliasName] for channel in channels: pars.append(channel.get("name")) - pars = map(str.strip, pars) + pars = list(map(str.strip, pars)) pool_dp.command_inout("CreateMeasurementGroup", pars) self.handle_attributes(aliasName, mg) @@ -1280,7 +1294,7 @@ class Sardana: def __init__(self, source, simulation="Best", cleanup=True, log=False): - if type(source) in types.StringTypes: + if isinstance(source, str): self._filename = source self._xmldoc = None else: @@ -1339,7 +1353,7 @@ def _preprocess(self): self.o("Preprocessing input... ") try: - if not self.SimulationModes.has_key(self._simulation): + if self._simulation not in self.SimulationModes: return motSimNb = 0 @@ -1362,17 +1376,17 @@ def _preprocess(self): SimuIOLib, SimuIOClass = simType["IORegister"] simuMotorLibs = [mode["Motor"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] simuCoTiLibs = [mode["CounterTimer"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] simu0DLibs = [mode["ZeroDExpChannel"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] simu1DLibs = [mode["OneDExpChannel"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] simu2DLibs = [mode["TwoDExpChannel"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] simuIOLibs = [mode["IORegister"][0] - for mode in self.SimulationModes.values()] + for mode in list(self.SimulationModes.values())] for poolserver in poolservers: simuMotorList = [] @@ -1424,7 +1438,7 @@ def _preprocess(self): # change the pool XML nodes to refer to simulator lib # instead of real lib - map(ctrl.remove, ctrl.findall("Property")) + list(map(ctrl.remove, ctrl.findall("Property"))) ctrl.set("lib", SimuMotorLib) ctrl.set("class", SimuMotorClass) @@ -1505,7 +1519,7 @@ def _preprocess(self): # change the pool XML nodes to refer to simulator lib # instead of real lib - map(ctrl.remove, ctrl.findall("Property")) + list(map(ctrl.remove, ctrl.findall("Property"))) ctrl.set("lib", SimuCoTiLib) ctrl.set("class", SimuCoTiClass) if self._simulation == "Best": @@ -1553,22 +1567,24 @@ def _preprocess(self): simu0DCtrl, "Property") pSimAttributes.set("name", "DynamicAttributes") pSimAttributes.set("type", "DevVarStringArray") - simAttrTempl = "zerod%03d=float(100.0+10.0*random())" - for i in xrange(len(zerods)): + simAttrTempl = \ + "zerod%03d=float(100.0+10.0*random())" + for i in range(len(zerods)): pSimAttributeItem = etree.SubElement( pSimAttributes, "Item") - pSimAttributeItem.text = simAttrTempl % (i + 1) + pSimAttributeItem.text = \ + simAttrTempl % (i + 1) # change the pool XML nodes to refer to simulator lib # instead of real lib - map(ctrl.remove, ctrl.findall("Property")) + list(map(ctrl.remove, ctrl.findall("Property"))) ctrl.set("lib", Simu0DLib) ctrl.set("class", Simu0DClass) if self._simulation == "Best": attributeNames = etree.SubElement(ctrl, "Property") attributeNames.set("name", "AttributeNames") simAttrTempl = "%s/zerod%%03d" % simu0DCtrlName - for i in xrange(len(zerods)): + for i in range(len(zerods)): attributeNameItem = etree.SubElement( attributeNames, "Item") attributeNameItem.text = simAttrTempl % (i + 1) @@ -1602,23 +1618,26 @@ def _preprocess(self): sarName, pySigSimNb) simu1DCtrl.set("deviceName", simu1DCtrlName) - onedds = ctrl.findall("OneDExpChannel") + oneds = ctrl.findall("OneDExpChannel") - if len(onedds) > 0: + if len(oneds) > 0: pSimAttributes = etree.SubElement( simu1DCtrl, "Property") pSimAttributes.set("name", "DynamicAttributes") pSimAttributes.set("type", "DevVarStringArray") - simAttrTempl = "oned%03d=DevVarLongArray([10*sin(0.01*x) for x in xrange(100)])" - for i in xrange(len(oneds)): + simAttrTempl = \ + ("oned%03d=DevVarLongArray([10*sin(0.01*x) " + "for x in xrange(100)])") + for i in range(len(oneds)): pSimAttributeItem = etree.SubElement( pSimAttributes, "Item") - pSimAttributeItem.text = simAttrTempl % (i + 1) + pSimAttributeItem.text =\ + simAttrTempl % (i + 1) # change the pool XML nodes to refer to simulator lib # instead of real lib - map(ctrl.remove, ctrl.findall("Property")) + list(map(ctrl.remove, ctrl.findall("Property"))) ctrl.set("lib", Simu1DLib) ctrl.set("class", Simu1DClass) if self._simulation == "Best": @@ -1627,7 +1646,7 @@ def _preprocess(self): attributeNamesV = etree.SubElement( attributeNames, "Item") simAttrTempl = "%s/oned%%03d" % simu1DCtrlName - for i in xrange(len(oneds)): + for i in range(len(oneds)): attributeNameItem = etree.SubElement( attributeNames, "Item") attributeNameItem.text = simAttrTempl % (i + 1) @@ -1670,7 +1689,7 @@ def _preprocess(self): pSimAttributes.set("type", "DevVarStringArray") simAttrTempl = "ior%03d=int(READ and VAR('ior%03d') or WRITE and VAR('ior%03d',VALUE))" - for i in xrange(len(iors)): + for i in range(len(iors)): pSimAttributeItem = etree.SubElement( pSimAttributes, "Item") pSimAttributeItem.text = simAttrTempl % ( @@ -1678,14 +1697,14 @@ def _preprocess(self): # change the pool XML nodes to refer to simulator lib # instead of real lib - map(ctrl.remove, ctrl.findall("Property")) + list(map(ctrl.remove, ctrl.findall("Property"))) ctrl.set("lib", SimuIOLib) ctrl.set("class", SimuIOClass) if self._simulation == "Best": attributeNames = etree.SubElement(ctrl, "Property") attributeNames.set("name", "AttributeNames") simAttrTempl = "%s/ior%%03d" % simuIOCtrlName - for i in xrange(len(iors)): + for i in range(len(iors)): attributeNameItem = etree.SubElement( attributeNames, "Item") attributeNameItem.text = simAttrTempl % (i + 1) @@ -1788,19 +1807,19 @@ def prepareMSs(self): self._macServs[serv.getInstanceName()] = serv def _getServsShutdownOrder(self): - servs = self._macServs.values() - servs += self._devicePools.values() - servs += self._signalSims.values() - servs += self._coTiSims.values() - servs += self._motorSims.values() + servs = list(self._macServs.values()) + servs += list(self._devicePools.values()) + servs += list(self._signalSims.values()) + servs += list(self._coTiSims.values()) + servs += list(self._motorSims.values()) return servs def _getServsStartupOrder(self): - servs = self._motorSims.values() - servs += self._coTiSims.values() - servs += self._signalSims.values() - servs += self._devicePools.values() - servs += self._macServs.values() + servs = list(self._motorSims.values()) + servs += list(self._coTiSims.values()) + servs += list(self._signalSims.values()) + servs += list(self._devicePools.values()) + servs += list(self._macServs.values()) return servs def cleanUp(self): @@ -1847,15 +1866,15 @@ def getRoot(self): try: opts, pargs = getopt.getopt( sys.argv[1:], 'vl', ['simulation=', 'cleanup=']) - except Exception, e: - print "ERROR:", str(e) - print - print __doc__ + except Exception as e: + print("ERROR:", str(e)) + print() + print(__doc__) sys.exit(3) if not len(pargs): - print "ERROR: Please provide XML filename" - print + print("ERROR: Please provide XML filename") + print() sys.exit(3) filename = pargs[0] @@ -1875,32 +1894,33 @@ def getRoot(self): elif opt == '-v': just_output_and_exit = True else: - print __doc__ + print(__doc__) sys.exit(3) try: import to_sar sar_doc = to_sar.transform(filename) - except Exception, e: - print 'Sorry, but some problems found when trying to convert to SARDANA xml:' - print str(e) + except Exception as e: + print('Sorry, but some problems found when trying to convert to ' + 'SARDANA xml:') + print(str(e)) sardana = Sardana(sar_doc, simulation=simulation, log=activate_logging, cleanup=cleanup) if just_output_and_exit: sardana.prepare() - print etree.tostring(sardana.getRoot(), pretty_print=True) + print(etree.tostring(sardana.getRoot(), pretty_print=True)) sys.exit(0) try: sardana.setUp() - print "Ready!" + print("Ready!") sardana.run() - except KeyboardInterrupt, e: - print "User pressed Ctrl+C..." - except Exception, e: + except KeyboardInterrupt: + print("User pressed Ctrl+C...") + except Exception: traceback.print_exc() - print "Shutting down!" + print("Shutting down!") sardana.tearDown() diff --git a/src/sardana/tools/config/to_sar.py b/src/sardana/tools/config/to_sar.py index 434e08b33b..a5a4c87007 100644 --- a/src/sardana/tools/config/to_sar.py +++ b/src/sardana/tools/config/to_sar.py @@ -8,7 +8,7 @@ def transform(f): - if type(f) in types.StringTypes: + if isinstance(f, str): doc = etree.parse(f) else: doc = f @@ -31,7 +31,7 @@ def transform(f): def main(): filename = sys.argv[1] t = transform(filename) - print etree.tostring(t, pretty_print=True) + print(etree.tostring(t, pretty_print=True)) if __name__ == "__main__": main() diff --git a/src/sardana/tools/config/xls_to_sar.py b/src/sardana/tools/config/xls_to_sar.py index 1ee2a59a9d..c12fb79710 100644 --- a/src/sardana/tools/config/xls_to_sar.py +++ b/src/sardana/tools/config/xls_to_sar.py @@ -21,7 +21,7 @@ def transform(f): xslt_filename = os.path.join(directory, "XLS_TO_SAR.xslt") t = etree.XSLT(etree.parse(xslt_filename)) - if type(f) in types.StringTypes: + if isinstance(f, str): doc = etree.parse(f) else: doc = f @@ -30,12 +30,12 @@ def transform(f): def main(): if len(sys.argv) < 2: - print __doc__ + print(__doc__) sys.exit(1) filename = sys.argv[1] t = transform(filename) - print etree.tostring(t, pretty_print=True) + print(etree.tostring(t, pretty_print=True)) if __name__ == "__main__": main() diff --git a/src/sardana/util/deepreload.py b/src/sardana/util/deepreload.py index 5a38302a8f..c2c84a318e 100644 --- a/src/sardana/util/deepreload.py +++ b/src/sardana/util/deepreload.py @@ -42,7 +42,7 @@ re-implementation of hierarchical module import. """ -import __builtin__ +import builtins from contextlib import contextmanager import imp import sys @@ -50,26 +50,26 @@ from types import ModuleType from warnings import warn -original_import = __builtin__.__import__ +original_import = builtins.__import__ class DeepReload(object): def __enter__(self): - __builtin__.reload = reload + builtins.reload = reload def __exit__(self, etype, evalue, etraceback): - __builtin__.reload = original_reload + builtins.reload = original_reload @contextmanager def replace_import_hook(new_import): - saved_import = __builtin__.__import__ - __builtin__.__import__ = new_import + saved_import = builtins.__import__ + builtins.__import__ = new_import try: yield finally: - __builtin__.__import__ = saved_import + builtins.__import__ = saved_import def get_parent(globals, level): @@ -120,7 +120,7 @@ def get_parent(globals, level): globals['__package__'] = name = modname[:lastdot] dot = len(name) - for x in xrange(level, 1, -1): + for x in range(level, 1, -1): try: dot = name.rindex('.', 0, dot) except ValueError: @@ -198,7 +198,7 @@ def import_submodule(mod, subname, fullname): if fullname in found_now and fullname in sys.modules: m = sys.modules[fullname] else: - print 'Reloading', fullname + print('Reloading', fullname) found_now[fullname] = 1 oldm = sys.modules.get(fullname, None) @@ -351,7 +351,7 @@ def deep_reload_hook(m): # Save the original hooks try: - original_reload = __builtin__.reload + original_reload = builtins.reload except AttributeError: original_reload = imp.reload # Python 3 diff --git a/src/sardana/util/funcgenerator.py b/src/sardana/util/funcgenerator.py index f72bac46a7..ff3f771e35 100644 --- a/src/sardana/util/funcgenerator.py +++ b/src/sardana/util/funcgenerator.py @@ -209,7 +209,7 @@ def sleep(self, period): nap = 0 else: nap = period / necessary_naps - for _ in xrange(necessary_naps): + for _ in range(necessary_naps): if self.is_stopped(): break time.sleep(nap) @@ -348,7 +348,7 @@ def set_configuration(self, configuration): total_param = group[Total] total_in_initial_domain = total_param[initial_domain_in_use] total_in_active_domain = total_param[active_domain_in_use] - for _ in xrange(repeats): + for _ in range(repeats): passive_event = active_event_in_active_domain + active active_events.append(active_event_in_initial_domain) passive_events.append(passive_event) diff --git a/src/sardana/util/motion/motion.py b/src/sardana/util/motion/motion.py index fab92e0eff..4fb0e93294 100644 --- a/src/sardana/util/motion/motion.py +++ b/src/sardana/util/motion/motion.py @@ -251,27 +251,27 @@ def _calculateMotionPath(self): self.duration = duration def info(self): - print "Small movement =", self.small_motion - print "length =", self.displacement - print "position where maximum velocity will be reached =", \ - self.max_vel_pos - print "necessary displacement to reach maximum velocity =", \ - self.displacement_reach_max_vel - print "necessary displacement to stop from maximum velocity =", \ - self.displacement_reach_min_vel - print "maximum velocity possible =", self.max_vel - print "time at top velocity =", self.at_max_vel_time - print "displacement at top velocity =", self.at_max_vel_displacement - print "time to reach maximum velocity =", self.max_vel_time - print "time to reach minimum velocity =", self.min_vel_time - print "time the motion will take =", self.duration - print "" - print "For long movements (where top vel is possible), necessary " \ + print("Small movement =", self.small_motion) + print("length =", self.displacement) + print("position where maximum velocity will be reached =", + self.max_vel_pos) + print("necessary displacement to reach maximum velocity =", + self.displacement_reach_max_vel) + print("necessary displacement to stop from maximum velocity =", + self.displacement_reach_min_vel) + print("maximum velocity possible =", self.max_vel) + print("time at top velocity =", self.at_max_vel_time) + print("displacement at top velocity =", self.at_max_vel_displacement) + print("time to reach maximum velocity =", self.max_vel_time) + print("time to reach minimum velocity =", self.min_vel_time) + print("time the motion will take =", self.duration) + print("") + print("For long movements (where top vel is possible), necessary " "displacement to reach maximum velocity =", \ - self.displacement_reach_max_vel - print "For long movements (where top vel is possible), necessary " \ - "displacement to stop from maximum velocity =", \ - self.displacement_reach_min_vel + self.displacement_reach_max_vel) + print("For long movements (where top vel is possible), necessary " + "displacement to stop from maximum velocity =", + self.displacement_reach_min_vel) class Motion(object): @@ -536,7 +536,7 @@ def setPower(self, power): def info(self): if self.current_motion is not None: - print self.current_motion.info() + print(self.current_motion.info()) class Motor(BaseMotor): @@ -698,8 +698,8 @@ def fromMotor(motor): decel_time = motor.getDeceleration() return Motor(min_vel=min_vel, max_vel=max_vel, accel_time=accel_time, decel_time=decel_time) - except Exception, e: - print e + except Exception as e: + print(e) return Motor._fromTangoMotor(motor) @staticmethod diff --git a/src/sardana/util/test/test_funcgenerator.py b/src/sardana/util/test/test_funcgenerator.py index b117fec21d..d816b8e76c 100644 --- a/src/sardana/util/test/test_funcgenerator.py +++ b/src/sardana/util/test/test_funcgenerator.py @@ -27,7 +27,7 @@ import numpy from threading import Event, Timer -from taurus.external.unittest import TestCase +from unittest import TestCase from taurus.core.util import ThreadPool from sardana.pool.pooldefs import SynchDomain, SynchParam @@ -127,7 +127,7 @@ def test_run_time(self): self.thread_pool.add(self.func_generator.run, self._done) self.event.wait(100) active_event_ids = self.listener.active_event_ids - active_event_ids_ok = range(0, 10) + active_event_ids_ok = list(range(0, 10)) msg = "Received active event ids: %s, expected: %s" % ( active_event_ids, active_event_ids_ok) self.assertListEqual(active_event_ids, active_event_ids_ok, msg) @@ -163,7 +163,7 @@ def test_run_position_negative(self): self.event.wait(3) position.remove_listener(self.func_generator) active_event_ids = self.listener.active_event_ids - active_event_ids_ok = range(0, 10) + active_event_ids_ok = list(range(0, 10)) msg = "Received active event ids: %s, expected: %s" % (active_event_ids, active_event_ids_ok) self.assertListEqual(active_event_ids, active_event_ids_ok, msg) @@ -183,7 +183,7 @@ def test_run_position_positive(self): self.event.wait(3) position.remove_listener(self.func_generator) active_event_ids = self.listener.active_event_ids - active_event_ids_ok = range(0, 10) + active_event_ids_ok = list(range(0, 10)) msg = "Received active event ids: %s, expected: %s" % (active_event_ids, active_event_ids_ok) self.assertListEqual(active_event_ids, active_event_ids_ok, msg) diff --git a/src/sardana/util/test/test_parser.py b/src/sardana/util/test/test_parser.py index 2fe88d9358..cb889fa02c 100644 --- a/src/sardana/util/test/test_parser.py +++ b/src/sardana/util/test/test_parser.py @@ -25,7 +25,7 @@ """Tests for parser utilities.""" -from taurus.external import unittest +import unittest from taurus.test import insertTest from sardana.util.parser import ParamParser diff --git a/src/sardana/util/test/test_thread.py b/src/sardana/util/test/test_thread.py index 61e45b3721..7099754675 100644 --- a/src/sardana/util/test/test_thread.py +++ b/src/sardana/util/test/test_thread.py @@ -26,7 +26,7 @@ from sardana.sardanathreadpool import get_thread_pool from sardana.util.thread import CountLatch -from taurus.external.unittest import TestCase +from unittest import TestCase def job(i, duration): diff --git a/src/sardana/util/thread.py b/src/sardana/util/thread.py index 2070afbea9..678e5e20de 100644 --- a/src/sardana/util/thread.py +++ b/src/sardana/util/thread.py @@ -21,6 +21,8 @@ ## ############################################################################## +import time +import ctypes from threading import Condition @@ -59,3 +61,42 @@ def wait(self): while self.count > 0: self.condition.wait() self.condition.release() + + +_asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc +# first define the async exception function args. This is +# absolutely necessary for 64 bits machines. +_asyncexc.argtypes = (ctypes.c_long, ctypes.py_object) + + +def raise_in_thread(exception, thread, logger=None): + """Raise exception in a thread. + + Inspired on :meth:sardana.macroserver.macro.Macro.abort + + :param exception: Exception to be raised + :param thread: thread in which raise the exception. + """ + ret, i = 0, 0 + while ret != 1: + th_id = ctypes.c_long(thread.ident) + if logger: + logger.debug("Sending AbortException to %s", thread.name) + ret = _asyncexc(th_id, ctypes.py_object(exception)) + i += 1 + if ret == 0: + # try again + if i > 2: + if logger: + logger.error("Failed to abort after three tries!") + break + time.sleep(0.1) + if ret > 1: + # if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the + # effect + _asyncexc(th_id, None) + if logger: + logger.error("Failed to abort (unknown error code {})". + format(ret)) + break diff --git a/src/sardana/spock/ipython_00_11/__init__.py b/src/sardana/util/whichpython.py similarity index 66% rename from src/sardana/spock/ipython_00_11/__init__.py rename to src/sardana/util/whichpython.py index 75fadda7c9..198cb76b62 100644 --- a/src/sardana/spock/ipython_00_11/__init__.py +++ b/src/sardana/util/whichpython.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- ############################################################################## ## @@ -24,4 +23,22 @@ ## ############################################################################## -"""This package provides the spock generic utilities for ipython > 0.10""" +"""""" + +__all__ = ["which_python_executable"] + + +from taurus.core.util.whichexecutable import whichfile + + +def which_python_executable(): + """Return full path to python executable. + + On some OS Python 3 is executed with python3 but in conda environments it + is executed with python. Return Python 3 executable regardless of the + Python installation. + """ + executable = whichfile("python3") + if executable is None: + executable = whichfile("python") + return executable diff --git a/test/HTMLTestRunner.py b/test/HTMLTestRunner.py index b16e553ea7..6adba0b80b 100644 --- a/test/HTMLTestRunner.py +++ b/test/HTMLTestRunner.py @@ -87,10 +87,10 @@ # TODO: simplify javascript using ,ore than 1 class in the class attribute? import datetime -import StringIO +import io import sys import time -from taurus.external import unittest +import unittest from xml.sax import saxutils @@ -477,7 +477,7 @@ def __init__(self, verbosity=1): def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr - self.outputBuffer = StringIO.StringIO() + self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout @@ -567,8 +567,8 @@ def run(self, test): test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) - print >>sys.stderr, '\nTime Elapsed: %s' % ( - self.stopTime - self.startTime) + print('\nTime Elapsed: %s' % ( + self.stopTime - self.startTime), file=sys.stderr) return result def sortResult(self, result_list): diff --git a/test/pool.py b/test/pool.py index 136da2f245..de91cae440 100644 --- a/test/pool.py +++ b/test/pool.py @@ -4,7 +4,7 @@ """ import os -from taurus.external import unittest +import unittest import poolunittest import HTMLTestRunner import time diff --git a/test/poolunittest.py b/test/poolunittest.py index 5d26fc2f2f..11920c5db3 100644 --- a/test/poolunittest.py +++ b/test/poolunittest.py @@ -1,22 +1,22 @@ """ An extension to the original PyUnit providing specific Device Pool test utilities """ -from taurus.external import unittest +import logging +import unittest import PyTango import sys import os -import user import subprocess import time import signal -import exceptions import imp try: import pexpect except: - print "The Pool Unit test requires pexpect python module which was not found." - print "This module can be found at http://www.noah.org/wiki/Pexpect" + print("The Pool Unit test requires pexpect python module " + "which was not found.") + print("This module can be found at http://www.noah.org/wiki/Pexpect") sys.exit(-1) @@ -84,7 +84,7 @@ def check_empty_attribute(self, dev, att_name): if len(c_list.value) != 0: self.assert_(False, "The %s attribute is not empty !! It contains: %s" % ( att_name, c_list.value)) - except PyTango.DevFailed, e: + except PyTango.DevFailed as e: except_value = sys.exc_info()[1] self.assertEqual( except_value[0]["reason"], "API_EmptyDeviceAttribute") @@ -103,7 +103,7 @@ def attribute_error(self, dev, att_name, err, pr=False): c_list = dev.read_attribute(att_name) self.assert_( False, "The %s attribute is not in fault!!" % (att_name)) - except PyTango.DevFailed, e: + except PyTango.DevFailed as e: except_value = sys.exc_info()[1] if pr: self._printException(except_value) @@ -122,7 +122,7 @@ def wr_attribute_error(self, dev, att_val, err, pr=False): dev.write_attribute(att_val) self.assert_(False, "The %s attribute is not in fault!!" % (att_val.name)) - except PyTango.DevFailed, e: + except PyTango.DevFailed as e: except_value = sys.exc_info()[1] if pr: self._printException(except_value) @@ -140,8 +140,9 @@ def wrong_argument(self, dev, cmd_name, arg_list, err, pr=False): try: dev.command_inout(cmd_name, arg_list) self.assert_( - False, "The %s command succeed with wrong arguments!!" % (cmd_name)) - except PyTango.DevFailed, e: + False, "The %s command succeed " + "with wrong arguments!!" % cmd_name) + except PyTango.DevFailed as e: except_value = sys.exc_info()[1] if pr: self._printException(except_value) @@ -159,14 +160,15 @@ def _write_attribute(self, dev, att_name, att_val): dev.write_attribute(val) def _printException(self, except_value): - print "\nERROR desc" - print "origin =", except_value[0]["origin"] - print "desc =", except_value[0]["desc"] - print "origin =", except_value[0]['origin'] - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # Default setup. Overwrite this methods in each test scenario when necessary - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + print("\nERROR desc") + print("origin =", except_value[0]["origin"]) + print("desc =", except_value[0]["desc"]) + print("origin =", except_value[0]['origin']) + + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ + # Default setup. Overwrite this methods in each + # test scenario when necessary + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def getPoolPath(self): """getPoolPath() -> list """ @@ -197,7 +199,7 @@ def getMotorSimulators(self): def getCounterTimerSimulators(self): ret = [] - for i in xrange(10): + for i in range(10): mot_name = "%s/simmot/test%03d" % (self.username, i + 1) ret.append({"properties": {"Average": ['1.0'], "Sigma": ['250.0'], @@ -205,9 +207,9 @@ def getCounterTimerSimulators(self): },) return ret - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Generic methods - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def deleteFromDB(self, server_name, server_instance): server_name = server_name.lower() @@ -226,9 +228,9 @@ def deleteFromDB(self, server_name, server_instance): self.tango_db.delete_server(server) - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Test requirements. Overwrite as necessary - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def needsPool(self): return True @@ -239,9 +241,9 @@ def needsMotorSimulator(self): def needsCounterTimerSimulator(self): return False - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Pre and Post test methods - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def setUp(self): """Default setUp.""" @@ -299,8 +301,12 @@ def prepareMotorSimulator(self): f.close() f, path, desc = imp.find_module('SimuMotor') f.close() - except exceptions.ImportError, e: - self.assert_(False, e.message) + except ImportError as e: + self.assertTrue(False, e) + logging.log_exception(e) + # Include the name and path attributes in output. + logging.log('error.name: {e.name}') + logging.log('error.path: {e.path}') # Cleanup the database self.deleteMotorSimulatorFromDB() @@ -365,8 +371,12 @@ def prepareCounterTimerSimulator(self): f.close() f, path, desc = imp.find_module('SimuCounter') f.close() - except exceptions.ImportError, e: - self.assert_(False, e.message) + except ImportError as e: + self.assert_(False, e) + logging.log_exception(e) + # Include the name and path attributes in output. + logging.log('error.name: {e.name}') + logging.log('error.path: {e.path}') self.ctsim_exec = self.ctsim_exec @@ -429,8 +439,9 @@ def prepareDevicePool(self): self.pool_exec = path break - self.failIf(self.pool_exec is None, - "Could not find Pool executable. Make sure it is in the PATH") + self.assertFalse(self.pool_exec is None, + "Could not find Pool executable. " + "Make sure it is in the PATH") self.pool_bin_dir = os.path.dirname(self.pool_exec) @@ -529,11 +540,11 @@ def startPool(self): if idx == 0: return elif idx == 1: - self.assert_(False, PoolTestCase.PoolAlreadyRunning) + self.assertTrue(False, PoolTestCase.PoolAlreadyRunning) elif idx == 2: - self.assert_(False, "Device Pool terminated unexpectedly") + self.assertTrue(False, "Device Pool terminated unexpectedly") elif idx == 3: - self.assert_(False, "Device Pool startup time exceeded") + self.assertTrue(False, "Device Pool startup time exceeded") def stopPool(self): """Stops the Device Pool""" @@ -686,7 +697,7 @@ def startPool_PopenStyle(self): self.pool_ds_instance], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - except exceptions.OSError, e: + except OSError as e: if e.strerror == PoolTestCase.PoolExecNotFound: self.assert_( False, "Could not find Pool executable. Make sure it is in the PATH")