├── .coveragerc ├── .env.template ├── .github └── workflows │ ├── build_deploy.yml │ └── test.yml ├── .gitignore ├── .pylintrc ├── .readthedocs.yaml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── doc ├── _static │ └── favicon.ico ├── autodoc │ ├── autodoc_example.py │ └── index.rst ├── basic_example.py ├── changelog.rst ├── conf.py ├── dev │ ├── contribute.rst │ ├── howto.rst │ ├── index.rst │ ├── platform.rst │ ├── v2_to_v3.csv │ └── v2_to_v3.rst ├── examples.rst ├── images │ ├── basic_example.png │ ├── guidata-banner.png │ ├── guidata-vertical.png │ ├── layout_example.png │ └── screenshots │ │ ├── __init__.png │ │ ├── activable_dataset.png │ │ ├── all_features.png │ │ ├── all_items.png │ │ ├── arrayeditor.png │ │ ├── bool_selector.png │ │ ├── codeeditor.png │ │ ├── collectioneditor.png │ │ ├── console.png │ │ ├── dataframeeditor.png │ │ ├── datasetgroup.png │ │ ├── editgroupbox.png │ │ └── importwizard.png ├── index.rst ├── installation.rst ├── overview.rst ├── reference │ ├── dataset │ │ ├── conv.rst │ │ ├── dataitems.rst │ │ ├── datatypes.rst │ │ ├── index.rst │ │ ├── io.rst │ │ └── qtwidgets.rst │ ├── guitest.rst │ ├── index.rst │ ├── userconfig.rst │ ├── utils.rst │ └── widgets.rst ├── requirements.rst ├── update_requirements.py └── widgets.rst ├── guidata-tests.desktop ├── guidata ├── __init__.py ├── config.py ├── configtools.py ├── data │ └── icons │ │ ├── apply.png │ │ ├── arredit.png │ │ ├── busy.png │ │ ├── cell_edit.png │ │ ├── copy.png │ │ ├── copy_all.svg │ │ ├── delete.png │ │ ├── dictedit.png │ │ ├── dtype.png │ │ ├── edit.png │ │ ├── editors │ │ ├── copywop.png │ │ ├── edit.png │ │ ├── edit_add.png │ │ ├── editclear.png │ │ ├── editcopy.png │ │ ├── editcut.png │ │ ├── editdelete.png │ │ ├── editpaste.png │ │ ├── fileimport.png │ │ ├── filesave.png │ │ ├── imshow.png │ │ ├── insert.png │ │ ├── plot.png │ │ ├── rename.png │ │ └── selectall.png │ │ ├── exit.png │ │ ├── expander_down.png │ │ ├── expander_right.png │ │ ├── export.svg │ │ ├── file.png │ │ ├── fileclose.png │ │ ├── fileimport.png │ │ ├── filenew.png │ │ ├── fileopen.png │ │ ├── filesave.png │ │ ├── filesaveas.png │ │ ├── filetypes │ │ ├── doc.png │ │ ├── gif.png │ │ ├── html.png │ │ ├── jpg.png │ │ ├── pdf.png │ │ ├── png.png │ │ ├── pps.png │ │ ├── ps.png │ │ ├── tar.png │ │ ├── tgz.png │ │ ├── tif.png │ │ ├── txt.png │ │ ├── xls.png │ │ └── zip.png │ │ ├── format.svg │ │ ├── guidata-banner.svg │ │ ├── guidata-vertical.svg │ │ ├── guidata.svg │ │ ├── hist.png │ │ ├── max.png │ │ ├── min.png │ │ ├── none.png │ │ ├── not_found.png │ │ ├── python.png │ │ ├── quickview.png │ │ ├── resize.svg │ │ ├── save_all.png │ │ ├── selection.png │ │ ├── settings.png │ │ ├── shape.png │ │ ├── xmax.png │ │ └── xmin.png ├── dataset │ ├── __init__.py │ ├── autodoc.py │ ├── conv.py │ ├── dataitems.py │ ├── datatypes.py │ ├── io.py │ ├── note_directive.py │ ├── qtitemwidgets.py │ ├── qtwidgets.py │ └── textedit.py ├── env.py ├── external │ ├── __init__.py │ └── darkdetect │ │ ├── __init__.py │ │ ├── _dummy.py │ │ ├── _linux_detect.py │ │ ├── _mac_detect.py │ │ └── _windows_detect.py ├── guitest.py ├── io │ ├── __init__.py │ ├── base.py │ ├── h5fmt.py │ ├── inifmt.py │ └── jsonfmt.py ├── locale │ └── fr │ │ └── LC_MESSAGES │ │ └── guidata.po ├── qthelpers.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── data │ │ └── genreqs │ │ │ ├── pyproject.toml │ │ │ ├── requirements.rst │ │ │ └── setup.cfg │ ├── dataset │ │ ├── __init__.py │ │ ├── test_activable_dataset.py │ │ ├── test_activable_items.py │ │ ├── test_all_features.py │ │ ├── test_all_items.py │ │ ├── test_all_items_readonly.py │ │ ├── test_bool_selector.py │ │ ├── test_callbacks.py │ │ ├── test_datasetgroup.py │ │ ├── test_editgroupbox.py │ │ ├── test_inheritance.py │ │ ├── test_item_order.py │ │ ├── test_loadsave_hdf5.py │ │ ├── test_loadsave_json.py │ │ └── test_rotatedlabel.py │ ├── unit │ │ ├── __init__.py │ │ ├── test_config.py │ │ ├── test_data.py │ │ ├── test_dataset_from_dict.py │ │ ├── test_dataset_from_func.py │ │ ├── test_genreqs.py │ │ ├── test_h5fmt.py │ │ ├── test_jsonfmt.py │ │ ├── test_no_qt.py │ │ ├── test_text.py │ │ ├── test_translations.py │ │ ├── test_updaterestoredataset.py │ │ └── test_userconfig_app.py │ └── widgets │ │ ├── __init__.py │ │ ├── test_arrayeditor.py │ │ ├── test_arrayeditor_unit.py │ │ ├── test_codeeditor.py │ │ ├── test_collectionseditor.py │ │ ├── test_console.py │ │ ├── test_dataframeeditor.py │ │ ├── test_importwizard.py │ │ ├── test_objecteditor.py │ │ └── test_theme.py ├── userconfig.py ├── utils │ ├── __init__.py │ ├── encoding.py │ ├── genreqs.py │ ├── gettext_helpers.py │ └── misc.py └── widgets │ ├── __init__.py │ ├── about.py │ ├── arrayeditor │ ├── __init__.py │ ├── arrayeditor.py │ ├── arrayhandler.py │ ├── datamodel.py │ ├── editorwidget.py │ └── utils.py │ ├── codeeditor.py │ ├── collectionseditor.py │ ├── console │ ├── __init__.py │ ├── base.py │ ├── calltip.py │ ├── dochelpers.py │ ├── internalshell.py │ ├── interpreter.py │ ├── mixins.py │ ├── shell.py │ └── terminal.py │ ├── dataframeeditor.py │ ├── dockable.py │ ├── importwizard.py │ ├── nsview.py │ ├── objecteditor.py │ ├── rotatedlabel.py │ ├── syntaxhighlighters.py │ └── texteditor.py ├── pyproject.toml ├── requirements.txt └── scripts ├── build_dist.bat ├── build_doc.bat ├── clean_up.bat ├── gettext.bat ├── run_coverage.bat ├── run_pylint.bat ├── run_pytest.bat ├── run_ruff.bat ├── run_test_launcher.bat ├── upgrade_env.bat └── utils.bat /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | concurrency = multiprocessing,thread 4 | omit = 5 | */guidata/tests/* 6 | */guidata/external/* 7 | */guidata/widgets/* 8 | 9 | [report] 10 | exclude_lines = 11 | if __name__ == .__main__.: 12 | if TYPE_CHECKING: -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | PYTHONPATH=. -------------------------------------------------------------------------------- /.github/workflows/build_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package 28 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | # Inspired from https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html#github-actions 5 | 6 | name: Build, install and test 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | pull_request: 12 | branches: [ "master" ] 13 | 14 | jobs: 15 | build: 16 | 17 | env: 18 | DISPLAY: ':99.0' 19 | 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | python-version: ["3.9", "3.11", "3.13"] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install dependencies 33 | run: | 34 | sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils 35 | /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX 36 | python -m pip install --upgrade pip 37 | python -m pip install ruff pytest 38 | pip install PyQt5 39 | pip install . 40 | - name: Lint with Ruff 41 | run: ruff check --output-format=github guidata 42 | - name: Test with pytest 43 | run: | 44 | pytest 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .spyderproject 2 | .spyproject 3 | doc.zip 4 | Thumbs.db 5 | doctmp/ 6 | *.h5 7 | *.stats 8 | test.json 9 | doc/install_requires.txt 10 | doc/extras_require-dev.txt 11 | doc/extras_require-doc.txt 12 | *.bak 13 | 14 | # Visual Studio Code 15 | .env 16 | .venv 17 | 18 | # Created by https://www.gitignore.io/api/python 19 | 20 | ### Python ### 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | 29 | # Distribution / packaging 30 | .Python 31 | env/ 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | *.egg-info/ 44 | .installed.cfg 45 | *.egg 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | .hypothesis/ 59 | htmlcov/ 60 | .tox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *,cover 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | *.chm 78 | 79 | # PyBuilder 80 | target/ 81 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | # Essential to be able to compare code side-by-side (`black` default setting) 3 | # and best compromise to minimize file size 4 | max-line-length=88 5 | 6 | [TYPECHECK] 7 | ignored-modules=qtpy.QtWidgets,qtpy.QtCore,qtpy.QtGui 8 | 9 | [MESSAGES CONTROL] 10 | disable=wrong-import-order 11 | 12 | [DESIGN] 13 | max-args=8 # default: 5 14 | max-attributes=12 # default: 7 15 | max-branches=17 # default: 12 16 | max-locals=20 # default: 15 17 | min-public-methods=0 # default: 2 18 | max-public-methods=25 # default: 20 -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: "3.11" 9 | sphinx: 10 | configuration: doc/conf.py 11 | formats: 12 | - pdf 13 | python: 14 | install: 15 | - method: pip 16 | path: . 17 | extra_requirements: 18 | - doc 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Run Test Launcher", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/guidata/tests/__init__.py", 12 | "console": "integratedTerminal", 13 | "envFile": "${workspaceFolder}/.env", 14 | "python": "${config:python.defaultInterpreterPath}", 15 | "justMyCode": true, 16 | "env": { 17 | "QT_COLOR_MODE": "light", 18 | } 19 | }, 20 | { 21 | "name": "Run current file", 22 | "type": "debugpy", 23 | "request": "launch", 24 | "program": "${file}", 25 | "console": "integratedTerminal", 26 | "envFile": "${workspaceFolder}/.env", 27 | "python": "${config:python.defaultInterpreterPath}", 28 | "pythonArgs": [ 29 | "-W error::DeprecationWarning", 30 | "-W error::RuntimeWarning", 31 | ], 32 | "justMyCode": false, 33 | "args": [], 34 | "env": {} 35 | }, 36 | { 37 | "name": "Run current file (unattended)", 38 | "type": "debugpy", 39 | "request": "launch", 40 | "program": "${file}", 41 | "console": "integratedTerminal", 42 | "envFile": "${workspaceFolder}/.env", 43 | "python": "${config:python.defaultInterpreterPath}", 44 | "justMyCode": false, 45 | "args": [ 46 | "--unattended" 47 | ], 48 | "env": { 49 | "GUIDATA_PARSE_ARGS": "1", 50 | } 51 | }, 52 | ] 53 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[bat]": { 3 | "files.encoding": "cp850", 4 | }, 5 | "editor.rulers": [ 6 | 88 7 | ], 8 | "files.exclude": { 9 | "**/__pycache__": true, 10 | "**/.pytest_cache": true, 11 | "**/.hypothesis": true, 12 | "**/*.pyc": true, 13 | "**/*.pyo": true, 14 | "**/*.pyd": true, 15 | ".venv": true 16 | }, 17 | "files.trimFinalNewlines": true, 18 | "files.trimTrailingWhitespace": true, 19 | "python.defaultInterpreterPath": "${env:PPSTACK_PYTHONEXE}", 20 | "editor.formatOnSave": true, 21 | "python.analysis.autoFormatStrings": true, 22 | "python.testing.unittestEnabled": false, 23 | "python.testing.pytestEnabled": true, 24 | "python.testing.pytestPath": "pytest", 25 | "python.testing.pytestArgs": [], 26 | "[python]": { 27 | "editor.defaultFormatter": "charliermarsh.ruff" 28 | }, 29 | "editor.codeActionsOnSave": { 30 | "source.organizeImports": "explicit", 31 | }, 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, CEA-Codra, Pierre Raybaut. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft doc 2 | include *.desktop 3 | include CHANGELOG.md 4 | include requirements.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # guidata: Automatic GUI generation for easy dataset editing and display with Python 2 | 3 | [![pypi version](https://img.shields.io/pypi/v/guidata.svg)](https://pypi.org/project/guidata/) 4 | [![PyPI status](https://img.shields.io/pypi/status/guidata.svg)](https://github.com/PlotPyStack/guidata/) 5 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/guidata.svg)](https://pypi.python.org/pypi/guidata/) 6 | [![download count](https://img.shields.io/conda/dn/conda-forge/guidata.svg)](https://www.anaconda.com/download/) 7 | 8 | ℹ️ Created in 2009 by [Pierre Raybaut](https://github.com/PierreRaybaut) and maintained by the [PlotPyStack](https://github.com/PlotPyStack) organization. 9 | 10 | ## Overview 11 | 12 | The `guidata` package is a Python library generating Qt graphical user interfaces. 13 | It is part of the [PlotPyStack](https://github.com/PlotPyStack) project, aiming at 14 | providing a unified framework for creating scientific GUIs with Python and Qt. 15 | 16 | Simple example of `guidata` datasets embedded in an application window: 17 | 18 | ![Example](https://raw.githubusercontent.com/PlotPyStack/guidata/master/doc/images/screenshots/editgroupbox.png) 19 | 20 | See [documentation](https://guidata.readthedocs.io/en/latest/) for more details on 21 | the library and [changelog](https://github.com/PlotPyStack/guidata/blob/master/CHANGELOG.md) for recent history of changes. 22 | 23 | Copyrights and licensing: 24 | 25 | * Copyright © 2023 [CEA](https://www.cea.fr), [Codra](https://codra.net/), [Pierre Raybaut](https://github.com/PierreRaybaut). 26 | * Licensed under the terms of the BSD 3-Clause (see [LICENSE](https://github.com/PlotPyStack/guidata/blob/master/LICENSE)). 27 | 28 | ## Features 29 | 30 | Based on the Qt library, `guidata` is a Python library generating graphical user 31 | interfaces for easy dataset editing and display. It also provides helpers and 32 | application development tools for Qt (PyQt5, PySide2, PyQt6, PySide6). 33 | 34 | Generate GUIs to edit and display all kind of objects regrouped in datasets: 35 | 36 | * Integers, floats, strings 37 | * Lists (single/multiple choices) 38 | * Dictionaries 39 | * `ndarrays` (NumPy's N-dimensional arrays) 40 | * Etc. 41 | 42 | Save and load datasets to/from HDF5, JSON or INI files. 43 | 44 | Application development tools: 45 | 46 | * Data model (internal data structure, serialization, etc.) 47 | * Configuration management 48 | * Internationalization (`gettext`) 49 | * Deployment tools 50 | * HDF5, JSON and INI I/O helpers 51 | * Qt helpers 52 | * Ready-to-use Qt widgets: Python console, source code editor, array editor, etc. 53 | 54 | ## Dependencies and installation 55 | 56 | ### Supported Qt versions and bindings 57 | 58 | The whole PlotPyStack set of libraries relies on the [Qt](https://doc.qt.io/) GUI toolkit, thanks to [QtPy](https://pypi.org/project/QtPy/), an abstraction layer which allows to use the same API to interact with different Python-to-Qt bindings (PyQt5, PyQt6, PySide2, PySide6). 59 | 60 | Compatibility table: 61 | 62 | | guidata version | PyQt5 | PyQt6 | PySide2 | PySide6 | 63 | |----------------|-------|-------|---------|---------| 64 | | 3.0-3.5 | ✅ | ⚠️ | ❌ | ⚠️ | 65 | | Latest | ✅ | ✅ | ❌ | ✅ | 66 | 67 | ### Other dependencies and installation 68 | 69 | See [Installation](https://guidata.readthedocs.io/en/latest/installation.html) 70 | section in the documentation for more details. 71 | -------------------------------------------------------------------------------- /doc/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/_static/favicon.ico -------------------------------------------------------------------------------- /doc/autodoc/autodoc_example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import atexit 4 | import datetime 5 | import shutil 6 | import tempfile 7 | 8 | import guidata.dataset as gds 9 | 10 | # Creating temporary files and registering cleanup functions 11 | TEMPDIR = tempfile.mkdtemp(prefix="test_") 12 | atexit.register(shutil.rmtree, TEMPDIR) 13 | FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR) 14 | atexit.register(FILE_ETA.close) 15 | FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR) 16 | atexit.register(FILE_CSV.close) 17 | 18 | 19 | class AutodocExampleParam1(gds.DataSet): 20 | """Example of a complete dataset with all possible items. Used as an autodoc 21 | example.""" 22 | 23 | dir = gds.DirectoryItem("Directory", TEMPDIR) 24 | a = gds.FloatItem("Parameter #1", default=2.3) 25 | b = gds.IntItem("Parameter #2", min=0, max=10, default=5) 26 | c = gds.StringItem("Parameter #3", default="default value") 27 | type = gds.ChoiceItem("Processing algorithm", ("type 1", "type 2", "type 3")) 28 | fname = gds.FileOpenItem("Open file", ("csv", "eta"), FILE_CSV.name) 29 | fnames = gds.FilesOpenItem("Open files", "csv", FILE_CSV.name) 30 | fname_s = gds.FileSaveItem("Save file", "eta", FILE_ETA.name) 31 | string = gds.StringItem("String") 32 | text = gds.TextItem("Text") 33 | float_slider = gds.FloatItem( 34 | "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True 35 | ) 36 | integer = gds.IntItem("Integer", default=5, min=3, max=16, slider=True).set_pos( 37 | col=1 38 | ) 39 | dtime = gds.DateTimeItem("Date/time", default=datetime.datetime(2010, 10, 10)) 40 | date = gds.DateItem("Date", default=datetime.date(2010, 10, 10)).set_pos(col=1) 41 | bool1 = gds.BoolItem("Boolean option without label") 42 | bool2 = gds.BoolItem("Boolean option with label", "Label") 43 | _bg = gds.BeginGroup("A sub group") 44 | color = gds.ColorItem("Color", default="red") 45 | choice = gds.ChoiceItem( 46 | "Single choice 1", 47 | [ 48 | ("16", "first choice"), 49 | ("32", "second choice"), 50 | ("64", "third choice"), 51 | (128, "fourth choice"), 52 | ], 53 | ) 54 | mchoice2 = gds.ImageChoiceItem( 55 | "Single choice 2", 56 | [ 57 | ("rect", "first choice", "gif.png"), 58 | ("ell", "second choice", "txt.png"), 59 | ("qcq", "third choice", "file.png"), 60 | ], 61 | ) 62 | _eg = gds.EndGroup("A sub group") 63 | floatarray = gds.FloatArrayItem("Float array", format=" %.2e ").set_pos(col=1) 64 | mchoice3 = gds.MultipleChoiceItem( 65 | "MC type 1", [str(i) for i in range(12)] 66 | ).horizontal(4) 67 | mchoice1 = ( 68 | gds.MultipleChoiceItem( 69 | "MC type 2", ["first choice", "second choice", "third choice"] 70 | ) 71 | .vertical(1) 72 | .set_pos(col=1) 73 | ) 74 | dictionary = gds.DictItem( 75 | "Dictionary", 76 | help="This is a dictionary", 77 | ) 78 | 79 | def doc_test(self, a: int, b: float, c: str) -> str: 80 | """Test method for autodoc. 81 | 82 | Args: 83 | a: first parameter. 84 | b: second parameter. 85 | c: third parameter. 86 | 87 | Returns: 88 | Concatenation of c and (a + b). 89 | """ 90 | return c + str(a + b) 91 | 92 | 93 | class AutodocExampleParam2(AutodocExampleParam1): 94 | pass 95 | -------------------------------------------------------------------------------- /doc/autodoc/index.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Sphinx autodoc extension 4 | ======================== 5 | 6 | Extension 7 | --------- 8 | 9 | The :mod:`guidata` library provides a Sphinx extension to automatically document 10 | data set classes (:py:class:`guidata.dataset.datatypes.DataSet`). 11 | This extension is based on the Sphinx autodoc extension. 12 | 13 | Three directives are provided: 14 | 15 | * :code:`.. autodataset_create:: [module.dataset].create` to document the 16 | :code:`create()` classmethod of a :code:`DataSet` using its :code:`DataItem`. 17 | * :code:`.. datasetnote:: [module.dataset] [n]` to display a note on how to 18 | instanciate a dataset. Optional parameter :code:`n` gives the number of items 19 | to show. 20 | * :code:`.. autodataset:: [module.dataset]` used to document a dataset class. 21 | It is derived from the :code:`.. autoclass::` directive and therefore has the 22 | same options. By default, it will document a dataset without its constructor 23 | signature but will document its attributes and the :code:`create()` class method 24 | using the :code:`autodataset_create` directive. Several additional options are 25 | available to more finely tune the documentation (see examples below). 26 | 27 | Example dataset 28 | --------------- 29 | 30 | .. literalinclude:: autodoc_example.py 31 | 32 | Generated documentation 33 | ----------------------- 34 | 35 | Basic usage 36 | ~~~~~~~~~~~ 37 | 38 | In most cases, the :code:`.. autodataset::` directive should be sufficient to document 39 | a dataset. However, it might be useful to display examples on how to instanciate the 40 | given dataset. This can be done using the :code:`:shownote:` option (or the 41 | :code:`.. datasetnote::` directive). 42 | 43 | .. code-block:: rst 44 | 45 | .. autodataset:: autodoc_example.AutodocExampleParam1 46 | 47 | .. autodataset:: autodoc_example.AutodocExampleParam1 48 | :shownote: 49 | 50 | 51 | The second example line would result in the following documentation: 52 | 53 | .. autodataset:: autodoc_example.AutodocExampleParam1 54 | :shownote: 55 | 56 | Advanced usage 57 | ~~~~~~~~~~~~~~ 58 | 59 | The :code:`.. autodataset::` directive behavior can be modified using all 60 | :code:`.. autoclass::` options, as well as the the following ones: 61 | 62 | * :code:`:showsig:` to show the constructor signature 63 | * :code:`:hideattr:` to hide the dataset attributes 64 | * :code:`:shownote: [n]` to add a note on how to instanciate the dataset with the 65 | first :code:`n` items. If :code:`n` is not provided, all items will be shown. 66 | * :code:`:hidecreate:` to hide the :code:`create()` method documentation which is 67 | shown by default. 68 | 69 | The following reST example shows how these options can be used. 70 | 71 | .. code-block:: rst 72 | 73 | .. autodataset:: autodoc_example.AutodocExampleParam2 74 | :showsig: 75 | :hideattr: 76 | :hidecreate: 77 | :shownote: 5 78 | :members: 79 | 80 | .. autodataset:: autodoc_example.AutodocExampleParam2 81 | :showsig: 82 | :hideattr: 83 | :hidecreate: 84 | :shownote: 5 85 | :members: -------------------------------------------------------------------------------- /doc/basic_example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import guidata 4 | import guidata.dataset as gds 5 | 6 | # Note: the following line is not required if a QApplication has already been created 7 | _app = guidata.qapplication() 8 | 9 | 10 | class Processing(gds.DataSet): 11 | """Example""" 12 | 13 | a = gds.FloatItem("Parameter #1", default=2.3) 14 | b = gds.IntItem("Parameter #2", min=0, max=10, default=5) 15 | type = gds.ChoiceItem("Processing algorithm", ("type 1", "type 2", "type 3")) 16 | 17 | 18 | param = Processing() 19 | param.edit() 20 | print(param) # Showing param contents 21 | param.b = 4 # Modifying item value 22 | param.view() 23 | 24 | # Alternative way for creating a DataSet instance: 25 | param = Processing.create(a=7.323, b=4) 26 | print(param) 27 | -------------------------------------------------------------------------------- /doc/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.md 2 | :parser: myst_parser.sphinx_ 3 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | sys.path.insert(0, os.path.abspath("..")) 7 | sys.path.insert(0, os.path.abspath("autodoc")) 8 | 9 | import guidata # noqa: E402 10 | 11 | creator = "Pierre Raybaut" 12 | project = "guidata" 13 | copyright = "2009 CEA, " + creator 14 | version = ".".join(guidata.__version__.split(".")[:2]) 15 | release = guidata.__version__ 16 | 17 | extensions = [ 18 | "sphinx.ext.autodoc", 19 | "myst_parser", 20 | "sphinx.ext.intersphinx", 21 | "sphinx.ext.doctest", 22 | "sphinx_copybutton", 23 | "sphinx.ext.napoleon", 24 | "sphinx_qt_documentation", 25 | "guidata.dataset.autodoc", 26 | ] 27 | if "htmlhelp" in sys.argv: 28 | extensions += ["sphinx.ext.imgmath"] 29 | else: 30 | extensions += ["sphinx.ext.mathjax"] 31 | templates_path = ["_templates"] 32 | source_suffix = ".rst" 33 | master_doc = "index" 34 | 35 | exclude_trees = [] 36 | pygments_style = "sphinx" 37 | modindex_common_prefix = ["guidata."] 38 | autodoc_member_order = "bysource" 39 | 40 | intersphinx_mapping = { 41 | "python": ("https://docs.python.org/3", None), 42 | "numpy": ("https://numpy.org/doc/stable/", None), 43 | "h5py": ("https://docs.h5py.org/en/stable/", None), 44 | } 45 | # nitpicky = True # Uncomment to warn about all broken links 46 | 47 | if "htmlhelp" in sys.argv: 48 | html_theme = "classic" 49 | else: 50 | html_theme = "python_docs_theme" 51 | html_title = "%s %s Manual" % (project, version) 52 | html_short_title = "%s Manual" % project 53 | html_logo = "images/guidata-vertical.png" 54 | html_favicon = "_static/favicon.ico" 55 | html_static_path = ["_static"] 56 | html_use_modindex = True 57 | htmlhelp_basename = "guidata" 58 | latex_documents = [ 59 | ("index", "guidata.tex", "guidata Manual", creator, "manual"), 60 | ] 61 | -------------------------------------------------------------------------------- /doc/dev/contribute.rst: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ----------------- 3 | 4 | Coding guidelines 5 | ^^^^^^^^^^^^^^^^^ 6 | 7 | In general, we try to follow the standard Python coding guidelines, which cover 8 | all the important coding aspects (docstrings, comments, naming conventions, 9 | import statements, ...) as described here: 10 | 11 | * `Style Guide for Python Code `_ 12 | 13 | The easiest way to check that your code is following those guidelines is to 14 | run `pylint` (a note greater than 9/10 seems to be a reasonable goal). 15 | 16 | Moreover, the following guidelines should be followed: 17 | 18 | * Write docstrings for all classes, methods and functions. The docstrings 19 | should follow the `Google style `_. 20 | 21 | * Add typing annotations for all functions and methods. The annotations should 22 | use the future syntax ``from __future__ import annotations`` (see 23 | `PEP 563 `_) 24 | and the ``if TYPE_CHECKING`` pattern to avoid circular imports (see 25 | `PEP 484 `_). 26 | 27 | .. note:: 28 | 29 | To ensure that types are properly referenced by ``sphinx`` in the 30 | documentation, you may need to import the individual types for Qt 31 | (e.g. ``from qtpy.QtCore import QRectF``) instead of importing the whole 32 | module (e.g. ``from qtpy import QtCore as QC``): this is a limitation of 33 | ``sphinx-qt-documentation`` extension. 34 | 35 | * Try to keep the code as simple as possible. If you have to write a complex 36 | piece of code, try to split it into several functions or classes. 37 | 38 | * Add as many comments as possible. The code should be self-explanatory, but 39 | it is always useful to add some comments to explain the general idea of the 40 | code, or to explain some tricky parts. 41 | 42 | * Do not use ``from module import *`` statements, even in the ``__init__`` 43 | module of a package. 44 | 45 | * Avoid using mixins (multiple inheritance) when possible. It is often 46 | possible to use composition instead of inheritance. 47 | 48 | * Avoid using ``__getattr__`` and ``__setattr__`` methods. They are often used 49 | to implement lazy initialization, but this can be done in a more explicit 50 | way. 51 | 52 | 53 | Submitting patches 54 | ^^^^^^^^^^^^^^^^^^ 55 | 56 | Check-list 57 | ~~~~~~~~~~ 58 | 59 | Before submitting a patch, please check the following points: 60 | 61 | * The code follows the coding guidelines described above. 62 | 63 | * Build the documentation and check that it is correctly generated. *No warning 64 | should be displayed.* 65 | 66 | * Run pylint on the code and check that there is no error: 67 | 68 | .. code-block:: bash 69 | 70 | pylint --disable=fixme,C,R,W guidata 71 | 72 | * Run the tests and check that they all pass: 73 | 74 | .. code-block:: bash 75 | 76 | pytest 77 | 78 | Pull request 79 | ~~~~~~~~~~~~ 80 | 81 | If you want to contribute to the project, you can submit a patch. The 82 | recommended way to do this is to fork the project on GitHub, create a branch 83 | for your modifications and then send a pull request. The pull request will be 84 | reviewed and merged if it is accepted. 85 | 86 | Setting up development environment 87 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 88 | 89 | If you want to contribute to the project, you will probably want to set up a 90 | development environment. The easiest way to do this is to use `virtualenv 91 | `_ and `pip 92 | `_. 93 | 94 | Some parameters are required to be set before using the project from within 95 | Visual Studio Code (used in `launch.json` and `tasks.json`). These are: 96 | 97 | * ``PPSTACK_PYTHONEXE``: The path to the Python interpreter to use. This is 98 | used to launch the application from within Visual Studio Code. If not set, 99 | the default Python interpreter will be used. 100 | 101 | * `.env` file: This file is used to set environment variables for the 102 | application. It is used to set the ``PYTHONPATH`` environment variable to 103 | the root of the project. This is required to be able to import the project 104 | modules from within Visual Studio Code. To create this file, copy the 105 | ``.env.template`` file to ``.env`` (and eventually add your own paths). 106 | -------------------------------------------------------------------------------- /doc/dev/howto.rst: -------------------------------------------------------------------------------- 1 | How to build, test and deploy 2 | ----------------------------- 3 | 4 | Build instructions 5 | ^^^^^^^^^^^^^^^^^^ 6 | 7 | To build the package, you need to run the following command:: 8 | 9 | python -m build 10 | 11 | It should generate a source package (``.tar.gz`` file) and a Wheel package 12 | (``.whl`` file) in the `dist` directory. 13 | 14 | 15 | Running unittests 16 | ^^^^^^^^^^^^^^^^^ 17 | 18 | To run the unittests, you need: 19 | 20 | * Python 21 | * pytest 22 | * coverage (optional) 23 | 24 | Then run the following command:: 25 | 26 | pytest 27 | 28 | To run test with coverage support, use the following command:: 29 | 30 | pytest -v --cov --cov-report=html guidata 31 | 32 | 33 | Code formatting 34 | ^^^^^^^^^^^^^^^ 35 | 36 | The code is formatted with `ruff `_. 37 | 38 | If you are using `Visual Studio Code `_, 39 | the formatting is done automatically when you save a file, thanks to the 40 | project settings in the `.vscode` directory. 41 | -------------------------------------------------------------------------------- /doc/dev/index.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | contribute 9 | howto 10 | v2_to_v3 11 | platform 12 | -------------------------------------------------------------------------------- /doc/dev/platform.rst: -------------------------------------------------------------------------------- 1 | Reference test platforms 2 | ------------------------ 3 | 4 | About requirements 5 | ^^^^^^^^^^^^^^^^^^ 6 | 7 | The ``requirements.txt`` mentioned in the following sections is a text file which 8 | contains the list of all the Python packages required for building up the projet 9 | environment. It is used by the ``pip`` command to install all the dependencies. 10 | 11 | The ``requirements.txt`` file is generated automatically by the 12 | ``toml-to-requirements`` tool. It is based on the ``pyproject.toml`` file 13 | which is the reference file for the project dependencies. 14 | 15 | .. warning:: 16 | 17 | Please note that the generation is not systematic and the ``requirements.txt`` 18 | file may not be up-to-date. 19 | 20 | To update the ``requirements.txt`` file, you need to install the 21 | ``toml-to-requirements`` and execute the following command: 22 | 23 | .. code-block:: bash 24 | 25 | toml-to-req --toml-file .\pyproject.toml --include-optional 26 | 27 | 28 | Microsoft Windows 10 29 | ^^^^^^^^^^^^^^^^^^^^ 30 | 31 | First, install the latest version of Python 3.10 from the WinPython project. 32 | 33 | .. note:: 34 | 35 | At the time of writing, the latest version is 3.10.11.1 which can be 36 | download from `here `_. 37 | 38 | Then install all the requirements using the following command from the WinPython 39 | command prompt: 40 | 41 | .. code-block:: bash 42 | 43 | pip install -r requirements.txt 44 | 45 | That's it, you can now run the tests using the following command: 46 | 47 | .. code-block:: bash 48 | 49 | pytest 50 | 51 | If you want to rely on Visual Studio Code for editing and take advantage of the 52 | project settings and tasks, you will need to set the following environment variable: 53 | 54 | .. code-block:: bash 55 | 56 | set PPSTACK_PYTHONEXE=C:\WPy64-31110\python-3.11.1.amd64\python.exe 57 | 58 | CentOS Stream 8.8 59 | ^^^^^^^^^^^^^^^^^ 60 | 61 | .. note:: 62 | 63 | The following instructions have been tested on CentOS Stream which is the 64 | reference platform for the project. However, they should work on 65 | any other Linux distribution relying on the ``yum`` package manager. 66 | As for the other distributions, you may need to adapt the instructions 67 | to your specific environment (e.g. use ``apt-get`` instead of ``yum``). 68 | 69 | First, install the prerequisites: 70 | 71 | .. code-block:: bash 72 | 73 | sudo yum install groupinstall "Development Tools" -y 74 | sudo yum install openssl-devel.i686 libffi-devel.i686 bzip2-devel.i686 sqlite-devel -y 75 | 76 | Check that ``gcc`` is installed and available in the ``PATH`` environment variable: 77 | 78 | .. code-block:: bash 79 | 80 | gcc --version 81 | 82 | Install OpenSSL 1.1.1: 83 | 84 | .. code-block:: bash 85 | 86 | wget https://www.openssl.org/source/openssl-1.1.1v.tar.gz 87 | tar -xvf openssl-1.1.1v.tar.gz 88 | cd openssl-1.1.1v 89 | ./config --prefix=/usr --openssldir=/etc/ssl --libdir=lib no-shared zlib-dynamic 90 | make 91 | sudo make install 92 | openssl version 93 | which openssl 94 | cd .. 95 | 96 | Install Python 3.10.13 (the latest 3.10 version at the time of writing): 97 | 98 | .. code-block:: bash 99 | 100 | wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz 101 | tar -xvf Python-3.10.13.tgz 102 | cd Python-3.10.13 103 | ./configure --enable-optimizations --with-openssl=/usr --enable-loadable-sqlite-extensions 104 | sudo make altinstall 105 | cd .. 106 | 107 | Eventually add the ``/usr/local/bin`` directory to the ``PATH`` environment variable 108 | if Python has warned you about it: 109 | 110 | .. code-block:: bash 111 | 112 | sudo echo 'pathmunge /usr/local/bin' > /etc/profile.d/py310.sh 113 | chmod +x /etc/profile.d/py310.sh 114 | . /etc/profile # or logout and login again (reload the environment variables) 115 | echo $PATH # check that /usr/local/bin is in the PATH 116 | 117 | Create a virtual environment and install the requirements: 118 | 119 | .. code-block:: bash 120 | 121 | python3.10 -m venv guidata-venv 122 | source guidata-venv/bin/activate 123 | pip install --upgrade pip 124 | pip install -r requirements.txt 125 | 126 | That's it, you can now run the tests using the following command: 127 | 128 | .. code-block:: bash 129 | 130 | pytest -------------------------------------------------------------------------------- /doc/dev/v2_to_v3.csv: -------------------------------------------------------------------------------- 1 | Version 2,Version 3 2 | ``.userconfigio.BaseIOHandler``,``.io.BaseIOHandler`` 3 | ``.userconfigio.WriterMixin``,``.io.WriterMixin`` 4 | ``.userconfigio.UserConfigIOHandler``,``.io.INIHandler`` 5 | ``.userconfigio.UserConfigWriter``,``.io.INIWriter`` 6 | ``.userconfigio.UserConfigReader``,``.io.INIReader`` 7 | ``.jsonio.JSONHandler``,``.io.JSONHandler`` 8 | ``.jsonio.JSONReader``,``.io.JSONReader`` 9 | ``.jsonio.JSONWriter``,``.io.JSONWriter`` 10 | ``.hdf5io.HDF5Handler``,``.io.HDF5Handler`` 11 | ``.hdf5io.HDF5Reader``,``.io.HDF5Reader`` 12 | ``.hdf5io.HDF5Writer``,``.io.HDF5Writer`` 13 | ``.gettext_helpers``,``.utils.gettext_helpers`` 14 | ``.disthelpers``,*removed* 15 | ``.encoding``,``.utils.encoding`` 16 | ``.encoding.transcode``,*removed* 17 | ``.encoding.getfilesystemencoding``,*removed* 18 | ``.qthelpers.text_to_qcolor``,*removed* 19 | ``.utils.update_dataset``,``.dataset.update_dataset`` 20 | ``.utils.restore_dataset``,``.dataset.restore_dataset`` 21 | ``.utils.to_string``,``.utils.misc.to_string.misc`` 22 | ``.utils.decode_fs_string``,``.utils.misc.decode_fs_string.misc`` 23 | ``.utils.assert_interfaces_valid``,``.utils.misc.assert_interfaces_valid.misc`` 24 | ``.utils.get_module_path``,``.utils.misc.get_module_path.misc`` 25 | ``.utils.is_program_installed``,``.utils.misc.is_program_installed.misc`` 26 | ``.utils.run_program``,``.utils.misc.run_program.misc`` 27 | ``.utils.run_shell_command``,``.utils.misc.run_shell_command.misc`` 28 | ``.utils.getcwd_or_home``,``.utils.misc.getcwd_or_home.misc`` 29 | ``.utils.remove_backslashes``,``.utils.misc.remove_backslashes.misc`` 30 | ``.qtwidgets.RotatedLabel``,``.widgets.rotatedlabel.RotatedLabel`` 31 | ``.qtwidgets.DockableWidgetMixin``,``.widgets.dockable.DockableWidgetMixin`` 32 | ``.qtwidgets.DockableWidget``,``.widgets.dockable.DockableWidget`` 33 | ``.utils.min_equals_max``,*removed* 34 | ``.utils.pairs``,*removed* 35 | ``.utils.add_extension``,*removed* 36 | ``.utils.bind``,*removed* 37 | ``.utils.trace``,*removed* 38 | ``.utils.utf8_to_unicode``,*removed* 39 | ``.utils.unicode_to_stdout``,*removed* 40 | ``.utils.localtime_to_isodate``,*removed* 41 | ``.utils.isodate_to_localtime``,*removed* 42 | ``.utils.FormatTime``,*removed* 43 | ``.utils.Timer``,*removed* 44 | ``.utils.tic``,*removed* 45 | ``.utils.toc``,*removed* 46 | ``.utils.is_module_available``,*removed* 47 | ``.utils.get_package_data``,*removed* 48 | ``.utils.get_subpackages``,*removed* 49 | ``.utils.cythonize_all``,*removed* 50 | -------------------------------------------------------------------------------- /doc/dev/v2_to_v3.rst: -------------------------------------------------------------------------------- 1 | Migrating from version 2 to version 3 2 | ===================================== 3 | 4 | Version 3 is a new major version of the library which brings many new features, 5 | fixes bugs and improves the API. However, it is not fully backward compatible 6 | with the previous version. 7 | 8 | The main changes are: 9 | 10 | * New automated test suite 11 | * New documentation 12 | * `guidata.guitest`: 13 | 14 | * Added support for subpackages 15 | * New comment directive (``# guitest: show``) to add test module to test suite or 16 | to show test module in test launcher (this replaces the old ``SHOW = True`` line) 17 | 18 | * `guidata.dataset.datatypes.DataSet`: new `create` class method for concise 19 | dataset creation 20 | 21 | This section describes the steps to migrate your code from :mod:`guidata` 22 | version 2 to version 3. 23 | 24 | The following table gives the equivalence between version 2 and version 3 imports. 25 | 26 | For most of them, the change in the module path is the only difference (only 27 | the import statement have to be updated in your client code). For others, the 28 | third column of this table gives more details about the changes that may be 29 | required in your code. 30 | 31 | .. csv-table:: Compatibility table 32 | :file: v2_to_v3.csv 33 | -------------------------------------------------------------------------------- /doc/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Data set examples 4 | ================= 5 | 6 | Basic example 7 | ------------- 8 | 9 | Source code : 10 | 11 | .. literalinclude:: basic_example.py 12 | 13 | .. image:: images/basic_example.png 14 | 15 | Other examples 16 | -------------- 17 | 18 | A lot of examples are available in the :mod:`guidata` test module :: 19 | 20 | from guidata import tests 21 | tests.run() 22 | 23 | The two lines above execute the `guidata test launcher` : 24 | 25 | .. image:: images/screenshots/__init__.png 26 | 27 | All :mod:`guidata` items demo 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | .. literalinclude:: ../guidata/tests/dataset/test_all_items.py 31 | :start-after: guitest: 32 | 33 | .. image:: images/screenshots/all_items.png 34 | 35 | All (GUI-related) :mod:`guidata` features demo 36 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 37 | 38 | .. literalinclude:: ../guidata/tests/dataset/test_all_features.py 39 | :start-after: guitest: 40 | 41 | .. image:: images/screenshots/all_features.png 42 | 43 | Embedding guidata objects in GUI layouts 44 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 45 | 46 | .. literalinclude:: ../guidata/tests/dataset/test_editgroupbox.py 47 | :start-after: guitest: 48 | 49 | .. image:: images/screenshots/editgroupbox.png 50 | 51 | Data item groups and group selection 52 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 53 | 54 | .. literalinclude:: ../guidata/tests/dataset/test_bool_selector.py 55 | :start-after: guitest: 56 | 57 | .. image:: images/screenshots/bool_selector.png 58 | 59 | Activable data sets 60 | ^^^^^^^^^^^^^^^^^^^ 61 | 62 | .. literalinclude:: ../guidata/tests/dataset/test_activable_dataset.py 63 | :start-after: guitest: 64 | 65 | .. image:: images/screenshots/activable_dataset.png 66 | 67 | Data set groups 68 | ^^^^^^^^^^^^^^^ 69 | 70 | .. literalinclude:: ../guidata/tests/dataset/test_datasetgroup.py 71 | :start-after: guitest: 72 | 73 | .. image:: images/screenshots/datasetgroup.png 74 | 75 | Utilities 76 | ^^^^^^^^^ 77 | 78 | Update/restore a dataset from/to a dictionary 79 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | .. literalinclude:: ../guidata/tests/unit/test_updaterestoredataset.py 82 | :start-after: guitest: 83 | 84 | Create a dataset class from a function signature 85 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | 87 | .. literalinclude:: ../guidata/tests/unit/test_dataset_from_func.py 88 | :start-after: guitest: 89 | 90 | Create a dataset class from a function dictionary 91 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 92 | 93 | .. literalinclude:: ../guidata/tests/unit/test_dataset_from_dict.py 94 | :start-after: guitest: 95 | 96 | Data set HDF5 serialization/deserialization 97 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 98 | 99 | .. literalinclude:: ../guidata/tests/dataset/test_loadsave_hdf5.py 100 | :start-after: guitest: 101 | 102 | Data set JSON serialization/deserialization 103 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 104 | 105 | .. literalinclude:: ../guidata/tests/dataset/test_loadsave_json.py 106 | :start-after: guitest: -------------------------------------------------------------------------------- /doc/images/basic_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/basic_example.png -------------------------------------------------------------------------------- /doc/images/guidata-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/guidata-banner.png -------------------------------------------------------------------------------- /doc/images/guidata-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/guidata-vertical.png -------------------------------------------------------------------------------- /doc/images/layout_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/layout_example.png -------------------------------------------------------------------------------- /doc/images/screenshots/__init__.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/__init__.png -------------------------------------------------------------------------------- /doc/images/screenshots/activable_dataset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/activable_dataset.png -------------------------------------------------------------------------------- /doc/images/screenshots/all_features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/all_features.png -------------------------------------------------------------------------------- /doc/images/screenshots/all_items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/all_items.png -------------------------------------------------------------------------------- /doc/images/screenshots/arrayeditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/arrayeditor.png -------------------------------------------------------------------------------- /doc/images/screenshots/bool_selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/bool_selector.png -------------------------------------------------------------------------------- /doc/images/screenshots/codeeditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/codeeditor.png -------------------------------------------------------------------------------- /doc/images/screenshots/collectioneditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/collectioneditor.png -------------------------------------------------------------------------------- /doc/images/screenshots/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/console.png -------------------------------------------------------------------------------- /doc/images/screenshots/dataframeeditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/dataframeeditor.png -------------------------------------------------------------------------------- /doc/images/screenshots/datasetgroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/datasetgroup.png -------------------------------------------------------------------------------- /doc/images/screenshots/editgroupbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/editgroupbox.png -------------------------------------------------------------------------------- /doc/images/screenshots/importwizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/doc/images/screenshots/importwizard.png -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to :mod:`guidata`'s documentation! 2 | ========================================== 3 | 4 | .. image:: images/guidata-banner.png 5 | :align: center 6 | 7 | Based on the Qt library :mod:`guidata` is a Python library generating graphical 8 | user interfaces for easy dataset editing and display. It also provides :ref:`widgets` 9 | (Python console, code editor, array editor, etc. - see ), helpers and application 10 | development tools for Qt. 11 | 12 | .. figure:: images/layout_example.png 13 | :class: invert-in-dark-mode 14 | 15 | Simple example of layout generated by :mod:`guidata` (see :ref:`examples`). 16 | 17 | :mod:`guidata` is part of the `PlotPyStack`_ project, which aims at providing 18 | a full set of Python libraries for data plotting and data analysis. 19 | 20 | External resources: 21 | 22 | * Python Package Index: `PyPI`_ 23 | 24 | * Bug reports and feature requests: `GitHub`_ 25 | 26 | .. _PyPI: https://pypi.python.org/pypi/guidata 27 | .. _GitHub: https://github.com/PlotPyStack/guidata/ 28 | .. _PlotPyStack: https://github.com/PlotPyStack 29 | 30 | .. module:: guidata 31 | 32 | Table of contents 33 | ----------------- 34 | 35 | .. toctree:: 36 | :maxdepth: 2 37 | 38 | overview 39 | installation 40 | examples 41 | widgets 42 | autodoc/index 43 | dev/index 44 | reference/index 45 | changelog 46 | 47 | * :ref:`genindex` -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Dependencies 5 | ------------ 6 | 7 | .. include:: requirements.rst 8 | 9 | Installation using pip 10 | ---------------------- 11 | 12 | The easiest way to install guidata is using `pip `_:: 13 | 14 | pip install guidata 15 | 16 | Installation from source 17 | ------------------------ 18 | 19 | To install from source, clone the repository or download the source package 20 | from `PyPI `_. 21 | 22 | Then run the following command (using `build `_):: 23 | 24 | python -m build 25 | -------------------------------------------------------------------------------- /doc/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | When developping scientific software, from the simplest script to the 5 | most complex application, one systematically needs to manipulate data sets 6 | (e.g. parameters for a data processing feature). 7 | These data sets may consist of various data types: real numbers (e.g. physical 8 | quantities), integers (e.g. array indexes), strings (e.g. filenames), 9 | booleans (e.g. enable/disable an option), and so on. 10 | 11 | Most of the time, the programmer will need the following features: 12 | 13 | * allow the user to enter each parameter through a graphical user interface, 14 | using widgets which are adapted to data types (e.g. a single combo box or 15 | check boxes are suitable for presenting an option selection among 16 | multiple choices) 17 | 18 | * entered values have to be stored by the program with a convention which 19 | is again adapted to data types (e.g. when storing a combo box selection 20 | value, should we store the option string, the list index or an 21 | associated key?) 22 | 23 | * showing the stored values in a dialog box or within a graphical user 24 | interface layout, again with widgets adapted to data types 25 | 26 | * using the stored values easily (e.g. for data processing) by regrouping 27 | parameters in data structures 28 | 29 | * using those data structures to easily construct application data models 30 | (e.g. for storing application settings or data processing parameters) 31 | and to serialize and deserialize them (i.e. save and load them to/from 32 | HDF5, JSON or INI files) 33 | 34 | * update and restore a data set to/from a dictionary 35 | 36 | * generate a data set from a function signature (i.e. a function prototype) 37 | and use it to automatically generate a graphical user interface for 38 | calling the function 39 | 40 | This library aims to provide these features thanks to automatic graphical user 41 | interface generation for data set editing and display. Widgets inside GUIs are 42 | automatically generated depending on each data item type. 43 | 44 | The :mod:`guidata` library provides the following modules: 45 | 46 | * :py:mod:`guidata.dataset`: data set definition and manipulation 47 | * :py:mod:`guidata.widgets`: ready-to-use Qt widgets (console, code editor, array editor, etc.) 48 | * :py:mod:`guidata.qthelpers`: Qt helpers 49 | * :py:mod:`guidata.configtools`: library/application data management 50 | * :py:mod:`guidata.guitest`: automatic GUI-based test launcher 51 | * :py:mod:`guidata.utils`: utilities 52 | -------------------------------------------------------------------------------- /doc/reference/dataset/conv.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.dataset.conv 4 | -------------------------------------------------------------------------------- /doc/reference/dataset/dataitems.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.dataset.dataitems 4 | -------------------------------------------------------------------------------- /doc/reference/dataset/datatypes.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.dataset.datatypes 4 | -------------------------------------------------------------------------------- /doc/reference/dataset/index.rst: -------------------------------------------------------------------------------- 1 | Data set features 2 | ================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | datatypes 9 | dataitems 10 | conv 11 | io 12 | qtwidgets -------------------------------------------------------------------------------- /doc/reference/dataset/io.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.io -------------------------------------------------------------------------------- /doc/reference/dataset/qtwidgets.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.dataset.qtwidgets 4 | -------------------------------------------------------------------------------- /doc/reference/guitest.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.guitest 4 | -------------------------------------------------------------------------------- /doc/reference/index.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | --------- 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | dataset/index 9 | utils 10 | widgets 11 | userconfig 12 | guitest 13 | -------------------------------------------------------------------------------- /doc/reference/userconfig.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | .. automodule:: guidata.userconfig -------------------------------------------------------------------------------- /doc/reference/utils.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Utilities 4 | ========= 5 | 6 | .. automodule:: guidata.utils 7 | 8 | .. automodule:: guidata.utils.misc 9 | 10 | .. automodule:: guidata.configtools -------------------------------------------------------------------------------- /doc/reference/widgets.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Widgets and Qt helpers 4 | ====================== 5 | 6 | .. automodule:: guidata.qthelpers 7 | 8 | .. automodule:: guidata.widgets 9 | -------------------------------------------------------------------------------- /doc/requirements.rst: -------------------------------------------------------------------------------- 1 | The :mod:`guidata` package requires the following Python modules: 2 | 3 | .. list-table:: 4 | :header-rows: 1 5 | :align: left 6 | 7 | * - Name 8 | - Version 9 | - Summary 10 | * - Python 11 | - >=3.9, <4 12 | - Python programming language 13 | * - h5py 14 | - >=3.1 15 | - Read and write HDF5 files from Python 16 | * - NumPy 17 | - >=1.19 18 | - Fundamental package for array computing in Python 19 | * - QtPy 20 | - >=1.9 21 | - Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6). 22 | * - requests 23 | - 24 | - Python HTTP for Humans. 25 | * - tomli 26 | - 27 | - A lil' TOML parser 28 | * - PyQt5 29 | - >=5.11 30 | - Python bindings for the Qt cross platform application toolkit 31 | 32 | Optional modules for development: 33 | 34 | .. list-table:: 35 | :header-rows: 1 36 | :align: left 37 | 38 | * - Name 39 | - Version 40 | - Summary 41 | * - ruff 42 | - 43 | - An extremely fast Python linter and code formatter, written in Rust. 44 | * - pylint 45 | - 46 | - python code static checker 47 | * - Coverage 48 | - 49 | - Code coverage measurement for Python 50 | 51 | Optional modules for building the documentation: 52 | 53 | .. list-table:: 54 | :header-rows: 1 55 | :align: left 56 | 57 | * - Name 58 | - Version 59 | - Summary 60 | * - PyQt5 61 | - 62 | - Python bindings for the Qt cross platform application toolkit 63 | * - pillow 64 | - 65 | - Python Imaging Library (Fork) 66 | * - pandas 67 | - 68 | - Powerful data structures for data analysis, time series, and statistics 69 | * - sphinx 70 | - >6 71 | - Python documentation generator 72 | * - myst_parser 73 | - 74 | - An extended [CommonMark](https://spec.commonmark.org/) compliant parser, 75 | * - sphinx-copybutton 76 | - 77 | - Add a copy button to each of your code cells. 78 | * - sphinx_qt_documentation 79 | - 80 | - Plugin for proper resolve intersphinx references for Qt elements 81 | * - python-docs-theme 82 | - 83 | - The Sphinx theme for the CPython docs and related projects 84 | 85 | Optional modules for running test suite: 86 | 87 | .. list-table:: 88 | :header-rows: 1 89 | :align: left 90 | 91 | * - Name 92 | - Version 93 | - Summary 94 | * - pytest 95 | - 96 | - pytest: simple powerful testing with Python 97 | * - pytest-xvfb 98 | - 99 | - A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. -------------------------------------------------------------------------------- /doc/update_requirements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Update requirements.rst file from pyproject.toml or setup.cfg file 4 | 5 | Warning: this has to be done manually at release time. 6 | It is not done automatically by the sphinx 'conf.py' file because it 7 | requires an internet connection to fetch the dependencies metadata - this 8 | is not always possible (e.g., when building the documentation on a machine 9 | without internet connection like the Debian package management infrastructure). 10 | """ 11 | 12 | import guidata 13 | from guidata.utils.genreqs import gen_module_req_rst # noqa: E402 14 | 15 | if __name__ == "__main__": 16 | print("Updating requirements.rst file...", end=" ") 17 | gen_module_req_rst(guidata, ["Python>=3.8", "PyQt5>=5.11"]) 18 | print("done.") 19 | -------------------------------------------------------------------------------- /doc/widgets.rst: -------------------------------------------------------------------------------- 1 | .. _widgets: 2 | 3 | Ready-to-use widgets 4 | ==================== 5 | 6 | As a complement to the :py:mod:`guidata.dataset` module which focus on automatic 7 | GUI generation for data sets editing and display, the :py:mod:`guidata.widgets` 8 | module provides a set of ready-to-use widgets for building interactive GUIs. 9 | 10 | .. note:: Most of the widgets originally come from the `Spyder`_ project 11 | (Copyright © Spyder Project Contributors, MIT-licensed). They were 12 | adapted to be used outside of the Spyder IDE and to be compatible with 13 | guidata internals. 14 | 15 | .. _Spyder: https://github.com/spyder-ide/spyder 16 | 17 | Python console 18 | -------------- 19 | 20 | .. literalinclude:: ../guidata/tests/widgets/test_console.py 21 | :start-after: guitest: 22 | 23 | .. image:: images/screenshots/console.png 24 | 25 | Code editor 26 | ----------- 27 | 28 | .. literalinclude:: ../guidata/tests/widgets/test_codeeditor.py 29 | :start-after: guitest: 30 | 31 | .. image:: images/screenshots/codeeditor.png 32 | 33 | Array editor 34 | ------------ 35 | 36 | .. literalinclude:: ../guidata/tests/widgets/test_arrayeditor.py 37 | :start-after: guitest: 38 | 39 | .. image:: images/screenshots/arrayeditor.png 40 | 41 | Collection editor 42 | ----------------- 43 | 44 | .. literalinclude:: ../guidata/tests/widgets/test_collectionseditor.py 45 | :start-after: guitest: 46 | 47 | .. image:: images/screenshots/collectioneditor.png 48 | 49 | Dataframe editor 50 | ---------------- 51 | 52 | .. literalinclude:: ../guidata/tests/widgets/test_dataframeeditor.py 53 | :start-after: guitest: 54 | 55 | .. image:: images/screenshots/dataframeeditor.png 56 | 57 | Import wizard 58 | ------------- 59 | 60 | .. literalinclude:: ../guidata/tests/widgets/test_importwizard.py 61 | :start-after: guitest: 62 | 63 | .. image:: images/screenshots/importwizard.png 64 | -------------------------------------------------------------------------------- /guidata-tests.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=guidata-tests 5 | GenericName=guidata Test launcher 6 | Comment=The guidata Python library provides GUIs for easy dataset editing and display 7 | TryExec=guidata-tests 8 | Exec=guidata-tests 9 | Icon=guidata.svg 10 | Categories=Education;Science;Physics; 11 | -------------------------------------------------------------------------------- /guidata/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | guidata 4 | ======= 5 | 6 | Based on the Qt library :mod:`guidata` is a Python library generating graphical 7 | user interfaces for easy dataset editing and display. It also provides helpers 8 | and application development tools for Qt. 9 | """ 10 | 11 | __version__ = "3.9.0" 12 | 13 | 14 | # Dear (Debian, RPM, ...) package makers, please feel free to customize the 15 | # following path to module's data (images) and translations: 16 | DATAPATH = LOCALEPATH = "" 17 | 18 | 19 | import guidata.config # noqa: E402, F401 20 | 21 | 22 | def qapplication(): 23 | """ 24 | Return QApplication instance 25 | Creates it if it doesn't already exist 26 | """ 27 | from qtpy.QtWidgets import QApplication 28 | 29 | app = QApplication.instance() 30 | if not app: 31 | app = QApplication([]) 32 | install_translator(app) 33 | from guidata import qthelpers 34 | 35 | qthelpers.set_color_mode() 36 | return app 37 | 38 | 39 | QT_TRANSLATOR = None 40 | 41 | 42 | def install_translator(qapp): 43 | """Install Qt translator to the QApplication instance""" 44 | global QT_TRANSLATOR 45 | if QT_TRANSLATOR is None: 46 | from qtpy.QtCore import QLibraryInfo, QLocale, QTranslator 47 | 48 | locale = QLocale.system().name() 49 | # Qt-specific translator 50 | qt_translator = QTranslator() 51 | paths = QLibraryInfo.location(QLibraryInfo.TranslationsPath) 52 | for prefix in ("qt", "qtbase"): 53 | if qt_translator.load(prefix + "_" + locale, paths): 54 | QT_TRANSLATOR = qt_translator # Keep reference alive 55 | break 56 | if QT_TRANSLATOR is not None: 57 | qapp.installTranslator(QT_TRANSLATOR) 58 | -------------------------------------------------------------------------------- /guidata/data/icons/apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/apply.png -------------------------------------------------------------------------------- /guidata/data/icons/arredit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/arredit.png -------------------------------------------------------------------------------- /guidata/data/icons/busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/busy.png -------------------------------------------------------------------------------- /guidata/data/icons/cell_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/cell_edit.png -------------------------------------------------------------------------------- /guidata/data/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/copy.png -------------------------------------------------------------------------------- /guidata/data/icons/copy_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 32 | 33 | 35 | 37 | 39 | 41 | 43 | 45 | 51 | 52 | -------------------------------------------------------------------------------- /guidata/data/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/delete.png -------------------------------------------------------------------------------- /guidata/data/icons/dictedit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/dictedit.png -------------------------------------------------------------------------------- /guidata/data/icons/dtype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/dtype.png -------------------------------------------------------------------------------- /guidata/data/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/edit.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/copywop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/copywop.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/edit.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/edit_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/edit_add.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/editclear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/editclear.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/editcopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/editcopy.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/editcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/editcut.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/editdelete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/editdelete.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/editpaste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/editpaste.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/fileimport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/fileimport.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/filesave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/filesave.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/imshow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/imshow.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/insert.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/plot.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/rename.png -------------------------------------------------------------------------------- /guidata/data/icons/editors/selectall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/editors/selectall.png -------------------------------------------------------------------------------- /guidata/data/icons/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/exit.png -------------------------------------------------------------------------------- /guidata/data/icons/expander_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/expander_down.png -------------------------------------------------------------------------------- /guidata/data/icons/expander_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/expander_right.png -------------------------------------------------------------------------------- /guidata/data/icons/export.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /guidata/data/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/file.png -------------------------------------------------------------------------------- /guidata/data/icons/fileclose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/fileclose.png -------------------------------------------------------------------------------- /guidata/data/icons/fileimport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/fileimport.png -------------------------------------------------------------------------------- /guidata/data/icons/filenew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filenew.png -------------------------------------------------------------------------------- /guidata/data/icons/fileopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/fileopen.png -------------------------------------------------------------------------------- /guidata/data/icons/filesave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filesave.png -------------------------------------------------------------------------------- /guidata/data/icons/filesaveas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filesaveas.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/doc.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/gif.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/html.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/jpg.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/pdf.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/png.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/pps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/pps.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/ps.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/tar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/tar.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/tgz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/tgz.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/tif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/tif.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/txt.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/xls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/xls.png -------------------------------------------------------------------------------- /guidata/data/icons/filetypes/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/filetypes/zip.png -------------------------------------------------------------------------------- /guidata/data/icons/format.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /guidata/data/icons/hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/hist.png -------------------------------------------------------------------------------- /guidata/data/icons/max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/max.png -------------------------------------------------------------------------------- /guidata/data/icons/min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/min.png -------------------------------------------------------------------------------- /guidata/data/icons/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/none.png -------------------------------------------------------------------------------- /guidata/data/icons/not_found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/not_found.png -------------------------------------------------------------------------------- /guidata/data/icons/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/python.png -------------------------------------------------------------------------------- /guidata/data/icons/quickview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/quickview.png -------------------------------------------------------------------------------- /guidata/data/icons/resize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 20 | 22 | 28 | 30 | 35 | 37 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /guidata/data/icons/save_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/save_all.png -------------------------------------------------------------------------------- /guidata/data/icons/selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/selection.png -------------------------------------------------------------------------------- /guidata/data/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/settings.png -------------------------------------------------------------------------------- /guidata/data/icons/shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/shape.png -------------------------------------------------------------------------------- /guidata/data/icons/xmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/xmax.png -------------------------------------------------------------------------------- /guidata/data/icons/xmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/data/icons/xmin.png -------------------------------------------------------------------------------- /guidata/dataset/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | # flake8: noqa 7 | 8 | from .conv import ( 9 | create_dataset_from_dict, 10 | create_dataset_from_func, 11 | restore_dataset, 12 | update_dataset, 13 | ) 14 | from .dataitems import ( 15 | BoolItem, 16 | ButtonItem, 17 | ChoiceItem, 18 | ColorItem, 19 | DateItem, 20 | DateTimeItem, 21 | DictItem, 22 | DirectoryItem, 23 | FileOpenItem, 24 | FileSaveItem, 25 | FilesOpenItem, 26 | FloatArrayItem, 27 | FloatItem, 28 | FontFamilyItem, 29 | ImageChoiceItem, 30 | IntItem, 31 | MultipleChoiceItem, 32 | StringItem, 33 | TextItem, 34 | ) 35 | from .datatypes import ( 36 | ActivableDataSet, 37 | AnyDataSet, 38 | BeginGroup, 39 | BeginTabGroup, 40 | DataItem, 41 | DataItemProxy, 42 | DataItemVariable, 43 | DataSet, 44 | DataSetGroup, 45 | DataSetMeta, 46 | EndGroup, 47 | EndTabGroup, 48 | FormatProp, 49 | FuncProp, 50 | GetAttrProp, 51 | GroupItem, 52 | ItemProperty, 53 | NoDefault, 54 | NotProp, 55 | Obj, 56 | ObjectItem, 57 | TabGroupItem, 58 | ValueProp, 59 | ) 60 | -------------------------------------------------------------------------------- /guidata/dataset/io.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | # Compatibility imports with guidata <= 3.3 4 | from guidata.io.base import BaseIOHandler, GroupContext, WriterMixin # noqa 5 | from guidata.io.h5fmt import HDF5Handler, HDF5Reader, HDF5Writer # noqa 6 | from guidata.io.inifmt import INIHandler, INIReader, INIWriter # noqa 7 | from guidata.io.jsonfmt import JSONHandler, JSONReader, JSONWriter # noqa 8 | 9 | warnings.warn( 10 | "guidata.dataset.io module is deprecated and will be removed in a future release. " 11 | "Please use guidata.io instead.", 12 | DeprecationWarning, 13 | stacklevel=2, 14 | ) 15 | -------------------------------------------------------------------------------- /guidata/dataset/note_directive.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the terms of the BSD 3-Clause 3 | # (see guidata/LICENSE for details) 4 | 5 | """Sphinx directive to display a note about how to instanciate a dataset class""" 6 | 7 | from __future__ import annotations 8 | 9 | from typing import TYPE_CHECKING, Type 10 | 11 | from docutils import nodes 12 | from sphinx.util import logging 13 | from sphinx.util.docutils import SphinxDirective 14 | 15 | import guidata.dataset as gds 16 | from guidata import __version__ 17 | 18 | if TYPE_CHECKING: 19 | import sphinx.application 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class DatasetNoteDirective(SphinxDirective): 25 | """Directive to display a note about how to instanciate a dataset class""" 26 | 27 | required_arguments = 1 # the class name is a required argument 28 | optional_arguments = 1 # the number of example lines to display is optional 29 | final_argument_whitespace = True 30 | has_content = True 31 | 32 | def run(self): 33 | class_name = self.arguments[0] 34 | 35 | example_lines: int | None 36 | if len(self.arguments) > self.required_arguments: 37 | example_lines = int(self.arguments[self.required_arguments]) 38 | else: 39 | example_lines = None 40 | 41 | cls: Type[gds.DataSet] 42 | try: 43 | # Try to import the class 44 | module_name, class_name = class_name.rsplit(".", 1) 45 | module = __import__(module_name, fromlist=[class_name]) 46 | cls = getattr(module, class_name) 47 | 48 | except Exception as e: 49 | logger.warning(f"Failed to import class {class_name}: {e}") 50 | instance_str = f"Failed to import class {class_name}" 51 | note_node = nodes.error(instance_str) 52 | return [note_node] 53 | 54 | # Create an instance of the class and get its string representation 55 | instance = cls() 56 | instance_str = str(instance) 57 | items = instance.get_items() 58 | 59 | formated_args = ", ".join( 60 | f"{item.get_name()}={item._default}" for item in instance.get_items() 61 | ) 62 | note_node = nodes.note() 63 | paragraph1 = nodes.paragraph() 64 | paragraph1 += nodes.Text("To instanciate a new ") 65 | paragraph1 += nodes.literal(text=f"{cls.__name__}") 66 | paragraph1 += nodes.Text(" , you can use the create() classmethod like this:") 67 | paragraph1 += nodes.literal_block( 68 | text=f"{cls.__name__}.create({formated_args})", language="python" 69 | ) 70 | note_node += paragraph1 71 | 72 | paragraph2 = nodes.paragraph() 73 | paragraph2 += nodes.Text("You can also first instanciate a default ") 74 | paragraph2 += nodes.literal(text=f" {cls.__name__}") 75 | paragraph2 += nodes.Text(" and then set the fields like this:") 76 | 77 | example_lines = min(len(items), example_lines) if example_lines else len(items) 78 | code_lines = [ 79 | f"dataset = {cls.__name__}()", 80 | *( 81 | f"dataset.{items[i].get_name()} = {repr(items[i]._default)}" 82 | for i in range(example_lines) 83 | ), 84 | ] 85 | if len(items) > example_lines: 86 | code_lines.append("...") 87 | 88 | paragraph2 += nodes.literal_block( 89 | text="\n".join(code_lines), 90 | language="python", 91 | ) 92 | note_node += paragraph2 93 | 94 | # Create a note node 95 | 96 | return [note_node] 97 | 98 | 99 | def setup(app: sphinx.application.Sphinx) -> dict[str, object]: 100 | """Initialize the Sphinx extension""" 101 | app.add_directive("datasetnote", DatasetNoteDirective) 102 | 103 | return { 104 | "version": __version__, 105 | "parallel_read_safe": True, 106 | "parallel_write_safe": True, 107 | } 108 | -------------------------------------------------------------------------------- /guidata/dataset/textedit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Text visitor for DataItem objects 8 | (for test purpose only) 9 | """ 10 | 11 | 12 | def prompt(item): 13 | """Get item value""" 14 | return input(item.get_prop("display", "label") + " ? ") 15 | 16 | 17 | class TextEditVisitor: 18 | """Text visitor""" 19 | 20 | def __init__(self, instance): 21 | self.instance = instance 22 | 23 | def visit_generic(self, item): 24 | """Generic visitor""" 25 | while True: 26 | value = prompt(item) 27 | item.set_from_string(self.instance, value) 28 | if item.check_item(self.instance): 29 | break 30 | print("Incorrect value!") 31 | 32 | visit_FloatItem = visit_generic 33 | visit_IntItem = visit_generic 34 | visit_StringItem = visit_generic 35 | -------------------------------------------------------------------------------- /guidata/external/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /guidata/external/darkdetect/__init__.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Copyright (C) 2019 Alberto Sottile 3 | # 4 | # Distributed under the terms of the 3-clause BSD License. 5 | # ----------------------------------------------------------------------------- 6 | 7 | __version__ = "0.5.2" # Patched (see pull request below) 8 | 9 | import sys 10 | import platform 11 | 12 | if sys.platform == "darwin": 13 | from distutils.version import LooseVersion as V 14 | 15 | if V(platform.mac_ver()[0]) < V("10.14"): 16 | from ._dummy import * 17 | else: 18 | from ._mac_detect import * 19 | del V 20 | # Patch: https://github.com/albertosottile/darkdetect/pull/21 21 | elif ( 22 | sys.platform == "win32" 23 | and platform.release().isdigit() 24 | and int(platform.release()) >= 10 25 | ): 26 | # Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER. The getwindowsversion method returns a tuple. 27 | # The third item is the build number that we can use to check if the user has a new enough version of Windows. 28 | winver = int(platform.version().split(".")[2]) 29 | if winver >= 14393: 30 | from ._windows_detect import * 31 | else: 32 | from ._dummy import * 33 | elif sys.platform == "linux": 34 | from ._linux_detect import * 35 | else: 36 | from ._dummy import * 37 | 38 | del sys, platform 39 | -------------------------------------------------------------------------------- /guidata/external/darkdetect/_dummy.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Copyright (C) 2019 Alberto Sottile 3 | # 4 | # Distributed under the terms of the 3-clause BSD License. 5 | #----------------------------------------------------------------------------- 6 | 7 | def theme(): 8 | return None 9 | 10 | def isDark(): 11 | return None 12 | 13 | def isLight(): 14 | return None 15 | -------------------------------------------------------------------------------- /guidata/external/darkdetect/_linux_detect.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Copyright (C) 2019 Alberto Sottile, Eric Larson 3 | # 4 | # Distributed under the terms of the 3-clause BSD License. 5 | #----------------------------------------------------------------------------- 6 | 7 | import subprocess 8 | 9 | 10 | def theme(): 11 | # Here we just triage to GTK settings for now 12 | try: 13 | out = subprocess.run( 14 | ['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'], 15 | capture_output=True) 16 | stdout = out.stdout.decode() 17 | except Exception: 18 | return 'Light' 19 | # we have a string, now remove start and end quote 20 | theme = stdout.lower().strip()[1:-1] 21 | if theme.endswith('-dark'): 22 | return 'Dark' 23 | else: 24 | return 'Light' 25 | 26 | def isDark(): 27 | return theme() == 'Dark' 28 | 29 | def isLight(): 30 | return theme() == 'Light' 31 | -------------------------------------------------------------------------------- /guidata/external/darkdetect/_mac_detect.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Copyright (C) 2019 Alberto Sottile 3 | # 4 | # Distributed under the terms of the 3-clause BSD License. 5 | #----------------------------------------------------------------------------- 6 | 7 | import ctypes 8 | import ctypes.util 9 | 10 | try: 11 | # macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries" 12 | appkit = ctypes.cdll.LoadLibrary('AppKit.framework/AppKit') 13 | objc = ctypes.cdll.LoadLibrary('libobjc.dylib') 14 | except OSError: 15 | # revert to full path for older OS versions and hardened programs 16 | appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit')) 17 | objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) 18 | 19 | void_p = ctypes.c_void_p 20 | ull = ctypes.c_uint64 21 | 22 | objc.objc_getClass.restype = void_p 23 | objc.sel_registerName.restype = void_p 24 | 25 | # See https://docs.python.org/3/library/ctypes.html#function-prototypes for arguments description 26 | MSGPROTOTYPE = ctypes.CFUNCTYPE(void_p, void_p, void_p, void_p) 27 | msg = MSGPROTOTYPE(('objc_msgSend', objc), ((1 ,'', None), (1, '', None), (1, '', None))) 28 | 29 | def _utf8(s): 30 | if not isinstance(s, bytes): 31 | s = s.encode('utf8') 32 | return s 33 | 34 | def n(name): 35 | return objc.sel_registerName(_utf8(name)) 36 | 37 | def C(classname): 38 | return objc.objc_getClass(_utf8(classname)) 39 | 40 | def theme(): 41 | NSAutoreleasePool = objc.objc_getClass('NSAutoreleasePool') 42 | pool = msg(NSAutoreleasePool, n('alloc')) 43 | pool = msg(pool, n('init')) 44 | 45 | NSUserDefaults = C('NSUserDefaults') 46 | stdUserDef = msg(NSUserDefaults, n('standardUserDefaults')) 47 | 48 | NSString = C('NSString') 49 | 50 | key = msg(NSString, n("stringWithUTF8String:"), _utf8('AppleInterfaceStyle')) 51 | appearanceNS = msg(stdUserDef, n('stringForKey:'), void_p(key)) 52 | appearanceC = msg(appearanceNS, n('UTF8String')) 53 | 54 | if appearanceC is not None: 55 | out = ctypes.string_at(appearanceC) 56 | else: 57 | out = None 58 | 59 | msg(pool, n('release')) 60 | 61 | if out is not None: 62 | return out.decode('utf-8') 63 | else: 64 | return 'Light' 65 | 66 | def isDark(): 67 | return theme() == 'Dark' 68 | 69 | def isLight(): 70 | return theme() == 'Light' 71 | -------------------------------------------------------------------------------- /guidata/external/darkdetect/_windows_detect.py: -------------------------------------------------------------------------------- 1 | from winreg import HKEY_CURRENT_USER as hkey, QueryValueEx as getSubkeyValue, OpenKey as getKey 2 | 3 | def theme(): 4 | """ Uses the Windows Registry to detect if the user is using Dark Mode """ 5 | # Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting. 6 | valueMeaning = {0: "Dark", 1: "Light"} 7 | # In HKEY_CURRENT_USER, get the Personalisation Key. 8 | try: 9 | key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize") 10 | # In the Personalisation Key, get the AppsUseLightTheme subkey. This returns a tuple. 11 | # The first item in the tuple is the result we want (0 or 1 indicating Dark Mode or Light Mode); the other value is the type of subkey e.g. DWORD, QWORD, String, etc. 12 | subkey = getSubkeyValue(key, "AppsUseLightTheme")[0] 13 | except FileNotFoundError: 14 | # some headless Windows instances (e.g. GitHub Actions or Docker images) do not have this key 15 | return None 16 | return valueMeaning[subkey] 17 | 18 | def isDark(): 19 | if theme() is not None: 20 | return theme() == 'Dark' 21 | 22 | def isLight(): 23 | if theme() is not None: 24 | return theme() == 'Light' -------------------------------------------------------------------------------- /guidata/io/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Data serialization and deserialization 8 | -------------------------------------- 9 | 10 | The ``guidata.io`` package provides the core features for data 11 | (:py:class:`guidata.dataset.DataSet` or other objects) serialization and 12 | deserialization. 13 | 14 | Base classes for I/O handlers 15 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 16 | 17 | Base classes for writing custom readers and writers: 18 | 19 | * :py:class:`BaseIOHandler` 20 | * :py:class:`WriterMixin` 21 | 22 | .. autoclass:: GroupContext 23 | 24 | .. autoclass:: BaseIOHandler 25 | :members: 26 | 27 | .. autoclass:: WriterMixin 28 | :members: 29 | 30 | Configuration files (.ini) 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | 33 | Reader and writer for the serialization of data sets into .ini files: 34 | 35 | * :py:class:`INIReader` 36 | * :py:class:`INIWriter` 37 | 38 | .. autoclass:: INIReader 39 | :members: 40 | 41 | .. autoclass:: INIWriter 42 | :members: 43 | 44 | JSON files (.json) 45 | ^^^^^^^^^^^^^^^^^^ 46 | 47 | Reader and writer for the serialization of data sets into .json files: 48 | 49 | * :py:class:`JSONReader` 50 | * :py:class:`JSONWriter` 51 | 52 | .. autoclass:: JSONReader 53 | :members: 54 | 55 | .. autoclass:: JSONWriter 56 | :members: 57 | 58 | HDF5 files (.h5) 59 | ^^^^^^^^^^^^^^^^ 60 | 61 | Reader and writer for the serialization of data sets into .h5 files: 62 | 63 | * :py:class:`HDF5Reader` 64 | * :py:class:`HDF5Writer` 65 | 66 | .. autoclass:: HDF5Reader 67 | :members: 68 | 69 | .. autoclass:: HDF5Writer 70 | :members: 71 | """ 72 | 73 | # pylint: disable=unused-import 74 | from .base import BaseIOHandler, GroupContext, WriterMixin # noqa 75 | from .h5fmt import HDF5Handler, HDF5Reader, HDF5Writer # noqa 76 | from .inifmt import INIHandler, INIReader, INIWriter # noqa 77 | from .jsonfmt import JSONHandler, JSONReader, JSONWriter # noqa 78 | -------------------------------------------------------------------------------- /guidata/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | guidata test package 8 | ==================== 9 | """ 10 | 11 | import os.path as osp 12 | 13 | import guidata 14 | from guidata.configtools import get_module_data_path 15 | 16 | TESTDATAPATH = get_module_data_path("guidata", osp.join("tests", "data")) 17 | 18 | 19 | def get_path(filename: str) -> str: 20 | """Return absolute path of test file""" 21 | return osp.join(TESTDATAPATH, filename) 22 | 23 | 24 | def run(): 25 | """Run guidata test launcher""" 26 | from guidata.guitest import run_testlauncher 27 | 28 | run_testlauncher(guidata) 29 | 30 | 31 | if __name__ == "__main__": 32 | run() 33 | -------------------------------------------------------------------------------- /guidata/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # content of conftest.py 2 | 3 | import qtpy 4 | 5 | import guidata 6 | from guidata.env import execenv 7 | 8 | # Turn on unattended mode for executing tests without user interaction 9 | execenv.unattended = True 10 | execenv.verbose = "quiet" 11 | 12 | 13 | def pytest_report_header(config): 14 | """Add additional information to the pytest report header.""" 15 | qtbindings_version = qtpy.PYSIDE_VERSION 16 | if qtbindings_version is None: 17 | qtbindings_version = qtpy.PYQT_VERSION 18 | return [ 19 | f"guidata {guidata.__version__}, " 20 | f"{qtpy.API_NAME} {qtbindings_version} [Qt version: {qtpy.QT_VERSION}]", 21 | ] 22 | -------------------------------------------------------------------------------- /guidata/tests/data/genreqs/pyproject.toml: -------------------------------------------------------------------------------- 1 | # guidata setup configuration file 2 | 3 | # Important note: 4 | # Requirements (see [options]) are parsed by utils\genreqs.py to generate documentation 5 | 6 | [build-system] 7 | requires = ["setuptools"] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [project] 11 | name = "guidata" 12 | authors = [{ name = "Codra", email = "p.raybaut@codra.fr" }] 13 | description = "Signal and image processing software" 14 | readme = "README.md" 15 | license = { file = "LICENSE" } 16 | classifiers = [ 17 | "Topic :: Scientific/Engineering", 18 | "Topic :: Software Development :: Libraries :: Python Modules", 19 | "Topic :: Utilities", 20 | "Topic :: Scientific/Engineering", 21 | "Topic :: Scientific/Engineering :: Human Machine Interfaces", 22 | "Topic :: Software Development :: User Interfaces", 23 | "Operating System :: MacOS", 24 | "Operating System :: Microsoft :: Windows", 25 | "Operating System :: OS Independent", 26 | "Operating System :: POSIX", 27 | "Operating System :: Unix", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | ] 32 | requires-python = ">=3.9, <4" 33 | dependencies = ["h5py>=3.0", "NumPy>=1.21", "QtPy>=1.9"] 34 | dynamic = ["version"] 35 | 36 | [project.urls] 37 | Homepage = "https://github.com/PlotPyStack/guidata/" 38 | Documentation = "https://guidata.readthedocs.io/en/latest/" 39 | 40 | [project.scripts] 41 | 42 | [project.optional-dependencies] 43 | dev = ["ruff", "pylint", "Coverage"] 44 | doc = [ 45 | "PyQt5", 46 | "pillow", 47 | "pandas", 48 | "sphinx", 49 | "sphinx-copybutton", 50 | "sphinx_qt_documentation", 51 | "python-docs-theme", 52 | ] 53 | test = ["pytest", "pytest-cov", "pytest-qt", "pytest-xvfb"] 54 | 55 | [tool.setuptools] 56 | include-package-data = false 57 | 58 | [tool.setuptools.package-data] 59 | "*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml", "*.rst"] 60 | 61 | [tool.setuptools.dynamic] 62 | version = { attr = "guidata.__version__" } 63 | -------------------------------------------------------------------------------- /guidata/tests/data/genreqs/requirements.rst: -------------------------------------------------------------------------------- 1 | The :mod:`guidata` package requires the following Python modules: 2 | 3 | .. list-table:: 4 | :header-rows: 1 5 | :align: left 6 | 7 | * - Name 8 | - Version 9 | - Summary 10 | * - Python 11 | - >=3.8, <4 12 | - 13 | * - h5py 14 | - >=3.0 15 | - Read and write HDF5 files from Python 16 | * - NumPy 17 | - >=1.21 18 | - Fundamental package for array computing in Python 19 | * - QtPy 20 | - >=1.9 21 | - Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6). 22 | * - PyQt 23 | - >=5.11 24 | - 25 | 26 | Optional modules for development: 27 | 28 | .. list-table:: 29 | :header-rows: 1 30 | :align: left 31 | 32 | * - Name 33 | - Version 34 | - Summary 35 | * - black 36 | - 37 | - The uncompromising code formatter. 38 | * - isort 39 | - 40 | - A Python utility / library to sort Python imports. 41 | * - pylint 42 | - 43 | - python code static checker 44 | * - Coverage 45 | - 46 | - Code coverage measurement for Python 47 | 48 | Optional modules for building the documentation: 49 | 50 | .. list-table:: 51 | :header-rows: 1 52 | :align: left 53 | 54 | * - Name 55 | - Version 56 | - Summary 57 | * - PyQt5 58 | - 59 | - Python bindings for the Qt cross platform application toolkit 60 | * - pillow 61 | - 62 | - Python Imaging Library (Fork) 63 | * - pandas 64 | - 65 | - Powerful data structures for data analysis, time series, and statistics 66 | * - sphinx 67 | - 68 | - Python documentation generator 69 | * - sphinx-copybutton 70 | - 71 | - Add a copy button to each of your code cells. 72 | * - sphinx_qt_documentation 73 | - 74 | - Plugin for proper resolve intersphinx references for Qt elements 75 | * - python-docs-theme 76 | - 77 | - The Sphinx theme for the CPython docs and related projects 78 | 79 | Optional modules for running test suite: 80 | 81 | .. list-table:: 82 | :header-rows: 1 83 | :align: left 84 | 85 | * - Name 86 | - Version 87 | - Summary 88 | * - pytest 89 | - 90 | - pytest: simple powerful testing with Python 91 | * - pytest-cov 92 | - 93 | - Pytest plugin for measuring coverage. 94 | * - pytest-qt 95 | - 96 | - pytest support for PyQt and PySide applications 97 | * - pytest-xvfb 98 | - 99 | - A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. -------------------------------------------------------------------------------- /guidata/tests/data/genreqs/setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | [metadata] 3 | name = guidata 4 | version = attr: guidata.__version__ 5 | author = Codra 6 | author_email = p.raybaut@codra.fr 7 | url = https://github.com/PlotPyStack/guidata/ 8 | description = Signal and image processing software 9 | long_description = file: README.md 10 | long_description_content_type = text/markdown 11 | license = BSD 3-Clause License 12 | classifiers = 13 | Topic :: Scientific/Engineering 14 | Topic :: Software Development :: Libraries :: Python Modules 15 | Topic :: Utilities 16 | Topic :: Scientific/Engineering 17 | Topic :: Scientific/Engineering :: Human Machine Interfaces 18 | Topic :: Software Development :: User Interfaces 19 | Operating System :: MacOS 20 | Operating System :: Microsoft :: Windows 21 | Operating System :: OS Independent 22 | Operating System :: POSIX 23 | Operating System :: Unix 24 | Programming Language :: Python :: 3.9 25 | Programming Language :: Python :: 3.10 26 | Programming Language :: Python :: 3.11 27 | 28 | [options] 29 | python_requires = >=3.9, <4 30 | install_requires = 31 | h5py>=3.0 32 | NumPy>=1.21 33 | QtPy>=1.9 34 | packages = find_namespace: 35 | include_package_data = True 36 | 37 | [options.packages.find] 38 | include = guidata* 39 | 40 | [options.extras_require] 41 | dev = 42 | ruff 43 | pylint 44 | Coverage 45 | doc = 46 | PyQt5 47 | pillow 48 | pandas 49 | sphinx 50 | sphinx-copybutton 51 | sphinx_qt_documentation 52 | python-docs-theme 53 | test = 54 | pytest 55 | pytest-cov 56 | pytest-qt 57 | pytest-xvfb 58 | -------------------------------------------------------------------------------- /guidata/tests/dataset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/tests/dataset/__init__.py -------------------------------------------------------------------------------- /guidata/tests/dataset/test_activable_dataset.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | ActivableDataSet example 8 | 9 | Warning: ActivableDataSet objects were made to be integrated inside GUI layouts. 10 | So this example with dialog boxes may be confusing. 11 | --> see tests/editgroupbox.py to understand the activable dataset usage 12 | """ 13 | 14 | # When editing, all items are shown. 15 | # When showing dataset in read-only mode (e.g. inside another layout), all items 16 | # are shown except the enable item. 17 | 18 | import guidata.dataset as gds 19 | from guidata.env import execenv 20 | from guidata.qthelpers import qt_app_context 21 | 22 | # guitest: show 23 | 24 | 25 | class Parameters(gds.ActivableDataSet): 26 | """ 27 | Example 28 | Activable dataset example 29 | """ 30 | 31 | def __init__(self, title=None, comment=None, icon=""): 32 | gds.ActivableDataSet.__init__(self, title, comment, icon) 33 | 34 | enable = gds.BoolItem( 35 | "Enable parameter set", 36 | help="If disabled, the following parameters will be ignored", 37 | default=False, 38 | ) 39 | param0 = gds.ChoiceItem("Param 0", ["choice #1", "choice #2", "choice #3"]) 40 | param1 = gds.FloatItem("Param 1", default=0, min=0) 41 | param2 = gds.FloatItem("Param 2", default=0.93) 42 | color = gds.ColorItem("Color", default="red") 43 | 44 | 45 | Parameters.active_setup() 46 | 47 | 48 | def test_activable_dataset(): 49 | """Test activable dataset""" 50 | with qt_app_context(): 51 | prm = Parameters() 52 | prm.set_activable(True) 53 | prm.edit() 54 | prm.set_activable(False) 55 | prm.edit() 56 | prm.set_readonly() 57 | prm.edit() 58 | prm.set_readonly(False) 59 | prm.edit() 60 | execenv.print("OK") 61 | 62 | 63 | if __name__ == "__main__": 64 | test_activable_dataset() 65 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_activable_items.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Example with activable items: items which active state is changed depending 8 | on another item's value. 9 | """ 10 | 11 | # guitest: show 12 | 13 | from guidata.dataset import ChoiceItem, DataSet, FloatItem, FuncProp, GetAttrProp 14 | from guidata.env import execenv 15 | from guidata.qthelpers import qt_app_context 16 | 17 | choices = (("A", "Choice #1: A"), ("B", "Choice #2: B"), ("C", "Choice #3: C")) 18 | 19 | 20 | class Parameters(DataSet): 21 | """Example dataset""" 22 | 23 | _prop = GetAttrProp("choice") 24 | choice = ChoiceItem("Choice", choices).set_prop("display", store=_prop) 25 | x1 = FloatItem("x1") 26 | x2 = FloatItem("x2").set_prop("display", active=FuncProp(_prop, lambda x: x == "B")) 27 | x3 = FloatItem("x3").set_prop("display", active=FuncProp(_prop, lambda x: x == "C")) 28 | 29 | 30 | def test_activable_items(): 31 | """Test activable items""" 32 | with qt_app_context(): 33 | test = Parameters() 34 | test.edit() 35 | execenv.print("OK") 36 | 37 | 38 | if __name__ == "__main__": 39 | test_activable_items() 40 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_all_items.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | All guidata DataItem objects demo 8 | 9 | A DataSet object is a set of parameters of various types (integer, float, 10 | boolean, string, etc.) which may be edited in a dialog box thanks to the 11 | 'edit' method. Parameters are defined by assigning DataItem objects to a 12 | DataSet class definition: each parameter type has its own DataItem class 13 | (IntItem for integers, FloatItem for floats, StringItem for strings, etc.) 14 | """ 15 | 16 | # guitest: show 17 | 18 | import atexit 19 | import datetime 20 | import shutil 21 | import tempfile 22 | 23 | import numpy as np 24 | 25 | import guidata.dataset as gds 26 | from guidata.env import execenv 27 | from guidata.qthelpers import qt_app_context 28 | 29 | # Creating temporary files and registering cleanup functions 30 | TEMPDIR = tempfile.mkdtemp(prefix="test_") 31 | atexit.register(shutil.rmtree, TEMPDIR) 32 | FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR) 33 | atexit.register(FILE_ETA.close) 34 | FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR) 35 | atexit.register(FILE_CSV.close) 36 | 37 | 38 | class Parameters(gds.DataSet): 39 | """ 40 | DataSet test 41 | The following text is the DataSet 'comment':
Plain text or 42 | rich text2 are both supported, 43 | as well as special characters (α, β, γ, δ, ...) 44 | """ 45 | 46 | dir = gds.DirectoryItem("Directory", TEMPDIR) 47 | fname = gds.FileOpenItem("Open file", ("csv", "eta"), FILE_CSV.name) 48 | fnames = gds.FilesOpenItem("Open files", "csv", FILE_CSV.name) 49 | fname_s = gds.FileSaveItem("Save file", "eta", FILE_ETA.name) 50 | string = gds.StringItem("String") 51 | text = gds.TextItem("Text") 52 | float_slider = gds.FloatItem( 53 | "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True 54 | ) 55 | integer = gds.IntItem("Integer", default=5, min=3, max=16, slider=True).set_pos( 56 | col=1 57 | ) 58 | dtime = gds.DateTimeItem("Date/time", default=datetime.datetime(2010, 10, 10)) 59 | date = gds.DateItem("Date", default=datetime.date(2010, 10, 10)).set_pos(col=1) 60 | bool1 = gds.BoolItem("Boolean option without label") 61 | bool2 = gds.BoolItem("Boolean option with label", "Label") 62 | _bg = gds.BeginGroup("A sub group") 63 | color = gds.ColorItem("Color", default="red") 64 | choice = gds.ChoiceItem( 65 | "Single choice 1", 66 | [("16", "first choice"), ("32", "second choice"), ("64", "third choice")], 67 | ) 68 | mchoice2 = gds.ImageChoiceItem( 69 | "Single choice 2", 70 | [ 71 | ("rect", "first choice", "gif.png"), 72 | ("ell", "second choice", "txt.png"), 73 | ("qcq", "third choice", "file.png"), 74 | ], 75 | ) 76 | _eg = gds.EndGroup("A sub group") 77 | floatarray = gds.FloatArrayItem( 78 | "Float array", default=np.ones((50, 5), float), format=" %.2e " 79 | ).set_pos(col=1) 80 | mchoice3 = gds.MultipleChoiceItem( 81 | "MC type 1", [str(i) for i in range(12)] 82 | ).horizontal(4) 83 | mchoice1 = ( 84 | gds.MultipleChoiceItem( 85 | "MC type 2", ["first choice", "second choice", "third choice"] 86 | ) 87 | .vertical(1) 88 | .set_pos(col=1) 89 | ) 90 | dictionary = gds.DictItem( 91 | "Dictionary", 92 | default={ 93 | "lkl": 2, 94 | "tototo": 3, 95 | "zzzz": "lklk", 96 | "bool": True, 97 | "float": 1.234, 98 | "list": [1, 2.5, 3, "str", False, 5, {"lkl": 2, "l": [1, 2, 3]}], 99 | }, 100 | help="This is a dictionary", 101 | ) 102 | 103 | 104 | def test_all_items(): 105 | """Test all DataItem objects""" 106 | with qt_app_context(): 107 | prm = Parameters() 108 | 109 | prm.floatarray[:, 0] = np.linspace(-5, 5, 50) 110 | execenv.print(prm) 111 | if prm.edit(): 112 | execenv.print(prm) 113 | prm.view() 114 | execenv.print("OK") 115 | 116 | 117 | if __name__ == "__main__": 118 | test_all_items() 119 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_all_items_readonly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Same as test_all_items.py but with readonly=True to check that the readonly mode works 8 | on all DataItem types. 9 | """ 10 | 11 | # guitest: show 12 | 13 | 14 | import numpy as np 15 | 16 | from guidata.env import execenv 17 | from guidata.qthelpers import qt_app_context 18 | from guidata.tests.dataset.test_all_items import Parameters 19 | 20 | # check if array variable_size is ignored thanks to readonly 21 | Parameters.floatarray.set_prop("edit", variable_size=True) 22 | 23 | 24 | def test_all_features(): 25 | """Test all guidata item/group features""" 26 | with qt_app_context(): 27 | prm1 = Parameters(readonly=True) 28 | prm1.floatarray[:, 0] = np.linspace(-5, 5, 50) 29 | execenv.print(prm1) 30 | 31 | if prm1.edit(): 32 | prm1.edit() 33 | execenv.print(prm1) 34 | prm1.view() 35 | 36 | prm2 = Parameters.create(integer=10101010, string="Using create class method") 37 | print(prm2) 38 | 39 | execenv.print("OK") 40 | 41 | 42 | if __name__ == "__main__": 43 | test_all_features() 44 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_bool_selector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | DataItem groups and group selection 8 | 9 | DataSet items may be included in groups (these items are then shown in group 10 | box widgets in the editing dialog box) and item groups may be enabled/disabled 11 | using one group parameter (a boolean item). 12 | """ 13 | 14 | # guitest: show 15 | 16 | import guidata.dataset as gds 17 | from guidata.env import execenv 18 | from guidata.qthelpers import qt_app_context 19 | 20 | prop1 = gds.ValueProp(False) 21 | prop2 = gds.ValueProp(False) 22 | 23 | 24 | class Parameters(gds.DataSet): 25 | """ 26 | Group selection test 27 | Group selection example: 28 | """ 29 | 30 | g1 = gds.BeginGroup("group 1") 31 | enable1 = gds.BoolItem( 32 | "Enable parameter set #1", 33 | help="If disabled, the following parameters will be ignored", 34 | default=False, 35 | ).set_prop("display", store=prop1) 36 | prm11 = gds.FloatItem("Prm 1.1", default=0, min=0).set_prop("display", active=prop1) 37 | prm12 = gds.FloatItem("Prm 1.2", default=0.93).set_prop("display", active=prop1) 38 | _g1 = gds.EndGroup("group 1") 39 | g2 = gds.BeginGroup("group 2") 40 | enable2 = gds.BoolItem( 41 | "Enable parameter set #2", 42 | help="If disabled, the following parameters will be ignored", 43 | default=True, 44 | ).set_prop("display", store=prop2) 45 | prm21 = gds.FloatItem("Prm 2.1", default=0, min=0).set_prop("display", active=prop2) 46 | prm22 = gds.FloatItem("Prm 2.2", default=0.93).set_prop("display", active=prop2) 47 | _g2 = gds.EndGroup("group 2") 48 | 49 | 50 | def test_bool_selector(): 51 | """Test bool selector""" 52 | with qt_app_context(): 53 | prm = Parameters() 54 | prm.edit() 55 | execenv.print(prm) 56 | execenv.print("OK") 57 | 58 | 59 | if __name__ == "__main__": 60 | test_bool_selector() 61 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_callbacks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Demonstrates how items may trigger callbacks when activated, or how 8 | the list of choices may be dynamically changed. 9 | """ 10 | 11 | # guitest: show 12 | 13 | import guidata.dataset as gds 14 | from guidata.env import execenv 15 | from guidata.qthelpers import qt_app_context 16 | 17 | 18 | class Parameters(gds.DataSet): 19 | """Example dataset""" 20 | 21 | def cb_example(self, item, value): 22 | execenv.print("\nitem: ", item, "\nvalue:", value) 23 | if self.results is None: 24 | self.results = "" 25 | self.results += str(value) + "\n" 26 | execenv.print("results:", self.results) 27 | 28 | def update_x1plusx2(self, item, value): 29 | execenv.print("\nitem: ", item, "\nvalue:", value) 30 | if self.x1 is not None and self.x2 is not None: 31 | self.x1plusx2 = self.x1 + self.x2 32 | else: 33 | self.x1plusx2 = None 34 | 35 | string = gds.StringItem("String", default="foobar").set_prop( 36 | "display", callback=cb_example 37 | ) 38 | x1 = gds.FloatItem("x1").set_prop("display", callback=update_x1plusx2) 39 | x2 = gds.FloatItem("x2").set_prop("display", callback=update_x1plusx2) 40 | x1plusx2 = gds.FloatItem("x1+x2").set_prop("display", active=False) 41 | boolean = gds.BoolItem("Boolean", default=True).set_prop( 42 | "display", callback=cb_example 43 | ) 44 | color = gds.ColorItem("Color", default="red").set_prop( 45 | "display", callback=cb_example 46 | ) 47 | 48 | def choices_callback(self, item, value): 49 | """Choices callback: this demonstrates how to dynamically change 50 | the list of choices... even if it is not very useful in this case 51 | 52 | Note that `None` is systematically added as the third element of 53 | the returned tuples: that is to ensure the compatibility between 54 | `ChoiceItem` and `ImageChoiceItem` (see `guidata.dataset.dataitems`) 55 | """ 56 | execenv.print(f"[choices_callback]: item={item}, value={value}") 57 | return [ 58 | (16, "first choice", None), 59 | (32, "second choice", None), 60 | (64, "third choice", None), 61 | ] 62 | 63 | choice = ( 64 | gds.ChoiceItem( 65 | "Single choice", 66 | choices_callback, 67 | default=64, 68 | ) 69 | .set_pos(col=1, colspan=2) 70 | .set_prop("display", callback=cb_example) 71 | ) 72 | results = gds.TextItem("Results").set_prop("display", callback=cb_example) 73 | 74 | 75 | def test_callbacks(): 76 | """Test callbacks""" 77 | with qt_app_context(): 78 | prm = Parameters() 79 | execenv.print(prm) 80 | if prm.edit(): 81 | execenv.print(prm) 82 | prm.view() 83 | execenv.print("OK") 84 | 85 | 86 | if __name__ == "__main__": 87 | test_callbacks() 88 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_datasetgroup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | DataSetGroup demo 8 | 9 | DataSet objects may be grouped into DataSetGroup, allowing them to be edited 10 | in a single dialog box (with one tab per DataSet object). This code tests both the 11 | normal dataset group mode and the table mode (with one tab per DataSet object). 12 | """ 13 | 14 | # guitest: show 15 | 16 | from guidata.dataset import DataSetGroup 17 | from guidata.env import execenv 18 | from guidata.qthelpers import qt_app_context 19 | from guidata.tests.dataset.test_all_features import Parameters 20 | 21 | 22 | def test_dataset_group(): 23 | """Test DataSetGroup""" 24 | with qt_app_context(): 25 | e1 = Parameters("DataSet #1") 26 | e2 = Parameters("DataSet #2") 27 | 28 | g = DataSetGroup([e1, e2], title="Parameters group") 29 | g.edit() 30 | execenv.print(e1) 31 | g.edit() 32 | execenv.print("OK") 33 | 34 | g = DataSetGroup([e1, e2], title="Parameters group in table mode") 35 | g.edit(mode="table") 36 | execenv.print(e1) 37 | g.edit() 38 | execenv.print("OK") 39 | 40 | g.edit() 41 | execenv.print(e1) 42 | g.edit() 43 | execenv.print("OK") 44 | 45 | 46 | if __name__ == "__main__": 47 | test_dataset_group() 48 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_inheritance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | DataSet objects inheritance test 8 | 9 | From time to time, it may be useful to derive a DataSet from another. The main 10 | application is to extend a parameter set with additionnal parameters. 11 | """ 12 | 13 | # guitest: show 14 | 15 | import guidata.dataset as gds 16 | from guidata.env import execenv 17 | from guidata.qthelpers import qt_app_context 18 | 19 | 20 | class OriginalDataset(gds.DataSet): 21 | """Original dataset 22 | This is the original dataset""" 23 | 24 | bool = gds.BoolItem("Boolean") 25 | string = gds.StringItem("String") 26 | text = gds.TextItem("Text") 27 | float = gds.FloatItem("Float", default=0.5, min=0, max=1, step=0.01, slider=True) 28 | 29 | 30 | class DerivedDataset(OriginalDataset): 31 | """Derived dataset 32 | This is the derived dataset""" 33 | 34 | bool = gds.BoolItem("Boolean (modified in derived dataset)") 35 | a = gds.FloatItem("Level 1 (added in derived dataset)", default=0) 36 | b = gds.FloatItem("Level 2 (added in derived dataset)", default=0) 37 | c = gds.FloatItem("Level 3 (added in derived dataset)", default=0) 38 | 39 | 40 | def test_inheritance(): 41 | """Test DataSet inheritance""" 42 | with qt_app_context(): 43 | e = OriginalDataset() 44 | e.edit() 45 | execenv.print(e) 46 | 47 | e = DerivedDataset() 48 | e.edit() 49 | execenv.print(e) 50 | execenv.print("OK") 51 | 52 | 53 | if __name__ == "__main__": 54 | test_inheritance() 55 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_item_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | DataSet item order test 8 | 9 | From time to time, it may be useful to change the item order, 10 | for example when deriving a dataset from another. 11 | """ 12 | 13 | # guitest: show 14 | 15 | import guidata.dataset as gds 16 | from guidata.env import execenv 17 | from guidata.qthelpers import qt_app_context 18 | 19 | 20 | class OriginalDataset(gds.DataSet): 21 | """Original dataset 22 | This is the original dataset""" 23 | 24 | param1 = gds.BoolItem("P1 | Boolean") 25 | param2 = gds.StringItem("P2 | String") 26 | param3 = gds.TextItem("P3 | Text") 27 | param4 = gds.FloatItem("P4 | Float", default=0) 28 | 29 | 30 | class DerivedDataset(OriginalDataset): 31 | """Derived dataset 32 | This is the derived dataset, with modified item order""" 33 | 34 | param5 = gds.IntItem("P5 | Int", default=0).set_pos(row=2) 35 | param6 = gds.DateItem("P6 | Date", default=0).set_pos(row=4) 36 | 37 | 38 | def test_item_order(): 39 | """Test DataSet item order""" 40 | with qt_app_context(): 41 | e = OriginalDataset() 42 | e.edit() 43 | execenv.print(e) 44 | 45 | e = DerivedDataset() 46 | e.edit() 47 | execenv.print(e) 48 | execenv.print("OK") 49 | 50 | 51 | if __name__ == "__main__": 52 | test_item_order() 53 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_loadsave_hdf5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | HDF5 I/O demo 8 | 9 | DataSet objects may be saved in HDF5 files, a universal hierarchical dataset 10 | file type. This script shows how to save in and then reload data from a HDF5 11 | file. 12 | """ 13 | 14 | # guitest: show 15 | 16 | import os 17 | 18 | from guidata.env import execenv 19 | from guidata.io import HDF5Reader, HDF5Writer 20 | from guidata.qthelpers import qt_app_context 21 | from guidata.tests.dataset.test_all_items import Parameters 22 | 23 | 24 | def test_loadsave_hdf5(): 25 | """Test HDF5 I/O""" 26 | fname = "test.h5" 27 | with qt_app_context(): 28 | if os.path.exists(fname): 29 | os.unlink(fname) 30 | 31 | e = Parameters() 32 | if execenv.unattended or e.edit(): 33 | writer = HDF5Writer(fname) 34 | e.serialize(writer) 35 | writer.close() 36 | 37 | e = Parameters() 38 | reader = HDF5Reader(fname) 39 | e.deserialize(reader) 40 | reader.close() 41 | e.edit() 42 | os.unlink(fname) 43 | execenv.print("OK") 44 | 45 | 46 | if __name__ == "__main__": 47 | test_loadsave_hdf5() 48 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_loadsave_json.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | JSON I/O demo 8 | 9 | DataSet objects may be saved in JSON files. 10 | This script shows how to save in and then reload data from a JSON file. 11 | """ 12 | 13 | # guitest: show 14 | 15 | import os 16 | 17 | from guidata.env import execenv 18 | from guidata.io import JSONReader, JSONWriter 19 | from guidata.qthelpers import qt_app_context 20 | from guidata.tests.dataset.test_all_items import Parameters 21 | 22 | 23 | def test_loadsave_json(): 24 | """Test JSON I/O""" 25 | fname = "test.json" 26 | with qt_app_context(): 27 | if os.path.exists(fname): 28 | os.unlink(fname) 29 | 30 | e = Parameters() 31 | if execenv.unattended or e.edit(): 32 | writer = JSONWriter(fname) 33 | e.serialize(writer) 34 | writer.save() 35 | 36 | e = Parameters() 37 | reader = JSONReader(fname) 38 | e.deserialize(reader) 39 | e.edit() 40 | os.unlink(fname) 41 | execenv.print("OK") 42 | 43 | 44 | if __name__ == "__main__": 45 | test_loadsave_json() 46 | -------------------------------------------------------------------------------- /guidata/tests/dataset/test_rotatedlabel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | RotatedLabel test 8 | 9 | RotatedLabel is derived from QLabel: it provides rotated text display. 10 | """ 11 | 12 | # guitest: show 13 | 14 | from qtpy.QtCore import Qt 15 | from qtpy.QtWidgets import QFrame, QGridLayout 16 | 17 | from guidata.env import execenv 18 | from guidata.qthelpers import qt_app_context, win32_fix_title_bar_background 19 | from guidata.widgets.rotatedlabel import RotatedLabel 20 | 21 | 22 | class Frame(QFrame): 23 | """Test frame""" 24 | 25 | def __init__(self, parent=None): 26 | QFrame.__init__(self, parent) 27 | win32_fix_title_bar_background(self) 28 | layout = QGridLayout() 29 | self.setLayout(layout) 30 | angle = 0 31 | for row in range(7): 32 | for column in range(7): 33 | layout.addWidget( 34 | RotatedLabel( 35 | "Label %03d°" % angle, angle=angle, color=Qt.blue, bold=True 36 | ), 37 | row, 38 | column, 39 | Qt.AlignCenter, 40 | ) 41 | angle += 10 42 | 43 | 44 | def test_rotatedlabel(): 45 | """Test RotatedLabel""" 46 | with qt_app_context(exec_loop=True): 47 | frame = Frame() 48 | frame.show() 49 | execenv.print("OK") 50 | 51 | 52 | if __name__ == "__main__": 53 | test_rotatedlabel() 54 | -------------------------------------------------------------------------------- /guidata/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/tests/unit/__init__.py -------------------------------------------------------------------------------- /guidata/tests/unit/test_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """Config test""" 7 | 8 | import pytest 9 | 10 | from guidata.config import UserConfig 11 | from guidata.tests.dataset.test_all_features import Parameters 12 | 13 | 14 | @pytest.fixture() 15 | def config(): 16 | """Create a config object""" 17 | CONF = UserConfig({}) 18 | eta = Parameters() 19 | eta.write_config(CONF, "TestParameters", "") 20 | yield CONF 21 | 22 | 23 | def test_load(config): 24 | """Test load config""" 25 | eta = Parameters() 26 | eta.read_config(config, "TestParameters", "") 27 | 28 | 29 | def test_default(config): 30 | """Test default config""" 31 | eta = Parameters() 32 | eta.write_config(config, "etagere2", "") 33 | eta = Parameters() 34 | eta.read_config(config, "etagere2", "") 35 | 36 | 37 | def test_restore(config): 38 | """Test restore config""" 39 | eta = Parameters() 40 | eta.fl2 = 2 41 | eta.integer = 6 42 | eta.write_config(config, "etagere3", "") 43 | 44 | eta = Parameters() 45 | eta.read_config(config, "etagere3", "") 46 | 47 | assert eta.fl2 == 2.0 48 | assert eta.integer == 6 49 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """Unit tests""" 7 | 8 | 9 | import unittest 10 | 11 | import guidata.dataset as gds 12 | from guidata.dataset.conv import update_dataset 13 | from guidata.env import execenv 14 | 15 | 16 | class Parameters(gds.DataSet): 17 | """Example dataset""" 18 | 19 | float1 = gds.FloatItem("float #1", min=1, max=250, help="height in cm") 20 | float2 = gds.FloatItem("float #2", min=1, max=250, help="width in cm") 21 | number = gds.IntItem("number", min=3, max=20) 22 | 23 | 24 | class TestCheck(unittest.TestCase): 25 | def test_range(self): 26 | """Test range checking of FloatItem""" 27 | e = Parameters() 28 | e.float1 = 150.0 29 | e.float2 = 400.0 30 | e.number = 4 31 | errors = e.check() 32 | self.assertEqual(errors, ["float2"]) 33 | 34 | def test_typechecking(self): 35 | """Test type checking of FloatItem""" 36 | e = Parameters() 37 | e.float1 = 150 38 | e.float2 = 400 39 | e.number = 4.0 40 | errors = e.check() 41 | self.assertEqual(errors, ["float1", "float2", "number"]) 42 | 43 | def test_update(self): 44 | e1 = Parameters() 45 | e2 = Parameters() 46 | e1.float1 = 23 47 | update_dataset(e2, e1) 48 | self.assertEqual(e2.float1, 23) 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | execenv.print("OK") 54 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_dataset_from_dict.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Generate a dataset class from a dictionary 8 | """ 9 | 10 | # guitest: show 11 | 12 | import numpy as np 13 | 14 | from guidata.dataset import create_dataset_from_dict 15 | from guidata.env import execenv 16 | 17 | TEST_DICT1 = { 18 | "a": 1, 19 | "b": 2.0, 20 | "c": "test", 21 | "d": {"x": 1, "y": 3}, 22 | "data": np.array([1, 2, 3]), 23 | } 24 | TEST_DICT2 = { 25 | "a": 1, 26 | "unsupported": [2.0, 3.0], 27 | } 28 | 29 | 30 | def test_dataset_from_dict(): 31 | """Test generate dataset class from a dictionary""" 32 | for dictionary in (TEST_DICT1,): 33 | execenv.print(dictionary) 34 | dataset = create_dataset_from_dict(dictionary) 35 | execenv.print(dataset) 36 | execenv.print(dataset.create()) 37 | execenv.print("") 38 | try: 39 | create_dataset_from_dict(TEST_DICT2) 40 | assert False, "Should have raised a ValueError" 41 | except ValueError: 42 | # This is expected 43 | pass 44 | 45 | 46 | if __name__ == "__main__": 47 | test_dataset_from_dict() 48 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_dataset_from_func.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Generate a dataset class from a function signature 8 | 9 | This function is used to generate a dataset from a function signature which has 10 | type annotations. See the example below. 11 | """ 12 | 13 | # guitest: show 14 | 15 | from __future__ import annotations 16 | 17 | import numpy as np 18 | 19 | from guidata.dataset import create_dataset_from_func 20 | from guidata.env import execenv 21 | 22 | 23 | def func_ok( 24 | a: int, b: float, c: str = "test", d: dict[str, int] = {"x": 1, "y": 3} 25 | ) -> None: 26 | """A function with type annotations""" 27 | pass 28 | 29 | 30 | def func_no_type(a, b, c="test"): 31 | """A function without type annotations""" 32 | pass 33 | 34 | 35 | def func_no_default(a: int, b: float, c: str, data: np.ndarray) -> None: 36 | """A function without default values""" 37 | pass 38 | 39 | 40 | def test_dataset_from_func(): 41 | """Test generate dataset class from function""" 42 | for func in (func_ok, func_no_default): 43 | execenv.print(func.__name__) 44 | dataset = create_dataset_from_func(func) 45 | execenv.print(dataset) 46 | execenv.print(dataset.create(a=1, b=2.0)) 47 | execenv.print("") 48 | func = func_no_type 49 | try: 50 | create_dataset_from_func(func) 51 | assert False, "Should have raised a ValueError" 52 | except ValueError: 53 | # This is expected 54 | pass 55 | 56 | 57 | if __name__ == "__main__": 58 | test_dataset_from_func() 59 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_genreqs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """Generate install requirements RST table.""" 7 | 8 | import pytest 9 | 10 | from guidata.tests import get_path 11 | from guidata.utils import genreqs 12 | 13 | GR_PATH = get_path("genreqs") 14 | 15 | 16 | def test_compare_cfg_toml(): 17 | """Compare requirements generated from setup.cfg and pyproject.toml.""" 18 | req_toml = genreqs.extract_requirements_from_toml(GR_PATH) 19 | req_cfg = genreqs.extract_requirements_from_cfg(GR_PATH) 20 | assert req_toml == req_cfg 21 | 22 | 23 | @pytest.mark.skip(reason="This test should be run manually (development only)") 24 | def test_generate_requirement_tables(): 25 | """Test generate_requirement_tables.""" 26 | genreqs.gen_path_req_rst(GR_PATH, "guidata", ["Python>=3.9", "PyQt>=5.11"], GR_PATH) 27 | 28 | 29 | if __name__ == "__main__": 30 | test_compare_cfg_toml() 31 | test_generate_requirement_tables() 32 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_no_qt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Test if some guidata features work without Qt 8 | (and they should!) 9 | """ 10 | 11 | import os 12 | 13 | 14 | def test_imports_without_qt(): 15 | """Test if some guidata features work without Qt""" 16 | os.environ["QT_API"] = "invalid_value" # Invalid Qt API 17 | try: 18 | # pylint: disable=unused-import 19 | # pylint: disable=import-outside-toplevel 20 | import guidata.dataset.dataitems # noqa: F401 21 | import guidata.dataset.datatypes # noqa: F401 22 | except ValueError as exc: 23 | raise AssertionError("guidata imports failed without Qt") from exc 24 | 25 | 26 | if __name__ == "__main__": 27 | test_imports_without_qt() 28 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_text.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """Test in text mode""" 7 | 8 | 9 | import pytest 10 | 11 | import guidata.dataset as gds 12 | from guidata.env import execenv 13 | 14 | 15 | class Parameters(gds.DataSet): 16 | """Example dataset""" 17 | 18 | height = gds.FloatItem("Height", min=1, max=250, help="height in cm") 19 | width = gds.FloatItem("Width", min=1, max=250, help="width in cm") 20 | number = gds.IntItem("Number", min=3, max=20) 21 | 22 | 23 | @pytest.mark.skip(reason="interactive text mode: not suitable for automated testing") 24 | def test_text(): 25 | """Test text mode""" 26 | prm = Parameters() 27 | prm.text_edit() 28 | execenv.print(prm) 29 | execenv.print("OK") 30 | 31 | 32 | if __name__ == "__main__": 33 | test_text() 34 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_translations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """Little translation test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.config import _ 11 | from guidata.env import execenv 12 | 13 | translations = (_("Some required entries are incorrect"),) 14 | 15 | 16 | def test_translations(): 17 | """Test translations""" 18 | for text in translations: 19 | execenv.print(text) 20 | execenv.print("OK") 21 | 22 | 23 | if __name__ == "__main__": 24 | test_translations() 25 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_updaterestoredataset.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Update/Restore dataset from/to another dataset or dictionary 8 | """ 9 | 10 | # guitest: show 11 | 12 | import numpy as np 13 | 14 | from guidata.dataset.conv import restore_dataset, update_dataset 15 | from guidata.tests.dataset.test_all_items import Parameters 16 | 17 | 18 | def test_update_restore_dataset(): 19 | """Test update/restore dataset from/to another dataset or dictionary""" 20 | dataset = Parameters() 21 | dsdict = { 22 | "integer": 1, 23 | "float_slider": 1.0, 24 | "bool1": True, 25 | "string": "test", 26 | "floatarray": np.array([1, 2, 3]), 27 | "dictionary": {"a": 1, "b": 2}, 28 | } 29 | # Update dataset from dictionary 30 | update_dataset(dataset, dsdict) 31 | # Check dataset values 32 | assert dataset.integer == dsdict["integer"] 33 | assert dataset.float_slider == dsdict["float_slider"] 34 | assert dataset.bool1 == dsdict["bool1"] 35 | assert dataset.string == dsdict["string"] 36 | assert np.all(dataset.floatarray == dsdict["floatarray"]) 37 | assert dataset.dictionary == dsdict["dictionary"] 38 | # Update dataset from another dataset 39 | dataset2 = Parameters() 40 | update_dataset(dataset2, dataset) 41 | # Check dataset values 42 | assert dataset2.integer == dataset.integer 43 | assert dataset2.float_slider == dataset.float_slider 44 | assert dataset2.bool1 == dataset.bool1 45 | assert dataset2.string == dataset.string 46 | assert np.all(dataset2.floatarray == dataset.floatarray) 47 | assert dataset2.dictionary == dataset.dictionary 48 | # Restore dataset from dictionary 49 | restore_dataset(dataset, dsdict) 50 | # Check dataset values 51 | assert dataset.integer == dsdict["integer"] 52 | assert dataset.float_slider == dsdict["float_slider"] 53 | assert dataset.bool1 == dsdict["bool1"] 54 | assert dataset.string == dsdict["string"] 55 | assert np.all(dataset.floatarray == dsdict["floatarray"]) 56 | assert dataset.dictionary == dsdict["dictionary"] 57 | 58 | 59 | if __name__ == "__main__": 60 | test_update_restore_dataset() 61 | -------------------------------------------------------------------------------- /guidata/tests/unit/test_userconfig_app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | userconfig 5 | 6 | Application settings example 7 | 8 | This should create a file named ".app.ini" in your HOME directory containing: 9 | 10 | [main] 11 | version = 1.0.0 12 | 13 | [a] 14 | b/f = 1.0 15 | """ 16 | 17 | import guidata.dataset as gds 18 | from guidata import userconfig 19 | from guidata.env import execenv 20 | 21 | 22 | class DS(gds.DataSet): 23 | """Example dataset""" 24 | 25 | f = gds.FloatItem("F", 1.0) 26 | 27 | 28 | def test_userconfig_app(): 29 | """Test userconfig""" 30 | ds = DS("") 31 | uc = userconfig.UserConfig({}) 32 | uc.set_application("app", "1.0.0") 33 | ds.write_config(uc, "a", "b") 34 | 35 | print("Settings saved in: ", uc.filename()) 36 | execenv.print("OK") 37 | 38 | 39 | if __name__ == "__main__": 40 | test_userconfig_app() 41 | -------------------------------------------------------------------------------- /guidata/tests/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/guidata/ce320fdabfc3888c867c8716dda127a7dbd65bbe/guidata/tests/widgets/__init__.py -------------------------------------------------------------------------------- /guidata/tests/widgets/test_arrayeditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for arrayeditor.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | import numpy as np 14 | 15 | from guidata.env import execenv 16 | from guidata.qthelpers import exec_dialog, qt_app_context 17 | from guidata.widgets.arrayeditor import ArrayEditor 18 | 19 | 20 | def launch_arrayeditor(data, title="", xlabels=None, ylabels=None, variable_size=False): 21 | """Helper routine to launch an arrayeditor and return its result""" 22 | dlg = ArrayEditor() 23 | dlg.setup_and_check( 24 | data, title, xlabels=xlabels, ylabels=ylabels, variable_size=variable_size 25 | ) 26 | exec_dialog(dlg) 27 | return dlg.get_value() 28 | 29 | 30 | def test_arrayeditor(): 31 | """Test array editor for all supported data types""" 32 | with qt_app_context(): 33 | # Variable size version 34 | for title, data in ( 35 | ("string array", np.array(["kjrekrjkejr"])), 36 | ( 37 | "masked array", 38 | np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]), 39 | ), 40 | ("int array", np.array([1, 2, 3], dtype="int8")), 41 | ): 42 | launch_arrayeditor(data, "[Variable size] " + title, variable_size=True) 43 | for title, data in ( 44 | ("string array", np.array(["kjrekrjkejr"])), 45 | ("unicode array", np.array(["ñññéáíó"])), 46 | ( 47 | "masked array", 48 | np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]), 49 | ), 50 | ( 51 | "record array", 52 | np.zeros( 53 | (2, 2), 54 | { 55 | "names": ("red", "green", "blue"), 56 | "formats": (np.float32, np.float32, np.float32), 57 | }, 58 | ), 59 | ), 60 | ( 61 | "record array with titles", 62 | np.array( 63 | [(0, 0.0), (0, 0.0), (0, 0.0)], 64 | dtype=[(("title 1", "x"), "|i1"), (("title 2", "y"), ">f4")], 65 | ), 66 | ), 67 | ("bool array", np.array([True, False, True])), 68 | ("int array", np.array([1, 2, 3], dtype="int8")), 69 | ("float16 array", np.zeros((5, 5), dtype=np.float16)), 70 | ): 71 | launch_arrayeditor(data, title) 72 | for title, data, xlabels, ylabels in ( 73 | ("float array", np.random.rand(5, 5), ["a", "b", "c", "d", "e"], None), 74 | ( 75 | "complex array", 76 | np.round(np.random.rand(5, 5) * 10) 77 | + np.round(np.random.rand(5, 5) * 10) * 1j, 78 | np.linspace(-12, 12, 5), 79 | np.linspace(-12, 12, 5), 80 | ), 81 | ): 82 | launch_arrayeditor(data, title, xlabels, ylabels) 83 | 84 | arr = np.zeros((3, 3, 4)) 85 | arr[0, 0, 0] = 1 86 | arr[0, 0, 1] = 2 87 | arr[0, 0, 2] = 3 88 | launch_arrayeditor(arr, "3D array") 89 | 90 | execenv.print("OK") 91 | 92 | 93 | if __name__ == "__main__": 94 | test_arrayeditor() 95 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_codeeditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for codeeditor.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | from guidata.configtools import get_icon 14 | from guidata.env import execenv 15 | from guidata.qthelpers import qt_app_context 16 | from guidata.widgets import codeeditor 17 | 18 | 19 | def test_codeeditor(): 20 | """Test Code editor.""" 21 | with qt_app_context(exec_loop=True): 22 | widget = codeeditor.CodeEditor(language="python") 23 | widget.set_text_from_file(codeeditor.__file__) 24 | widget.resize(800, 600) 25 | widget.setWindowTitle("Code editor") 26 | widget.setWindowIcon(get_icon("guidata.svg")) 27 | widget.show() 28 | execenv.print("OK") 29 | 30 | 31 | if __name__ == "__main__": 32 | test_codeeditor() 33 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_collectionseditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for collectionseditor.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | import datetime 14 | 15 | import numpy as np 16 | 17 | from guidata.env import execenv 18 | from guidata.qthelpers import qt_app_context 19 | from guidata.widgets.collectionseditor import CollectionsEditor 20 | 21 | try: 22 | from PIL import Image as PILImage 23 | except ImportError: 24 | # PIL is not installed 25 | PILImage = None 26 | 27 | 28 | def get_test_data(): 29 | """Create test data.""" 30 | testdict = {"d": 1, "a": np.random.rand(10, 10), "b": [1, 2]} 31 | testdate = datetime.date(1945, 5, 8) 32 | test_timedelta = datetime.timedelta(days=-1, minutes=42, seconds=13) 33 | 34 | try: 35 | import pandas as pd 36 | except (ModuleNotFoundError, ImportError): 37 | test_timestamp = None 38 | test_pd_td = None 39 | test_dtindex = None 40 | test_series = None 41 | test_df = None 42 | else: 43 | test_timestamp = pd.Timestamp("1945-05-08T23:01:00.12345") 44 | test_pd_td = pd.Timedelta(days=2193, hours=12) 45 | test_dtindex = pd.date_range(start="1939-09-01T", end="1939-10-06", freq="12h") 46 | test_series = pd.Series({"series_name": [0, 1, 2, 3, 4, 5]}) 47 | test_df = pd.DataFrame( 48 | { 49 | "string_col": ["a", "b", "c", "d"], 50 | "int_col": [0, 1, 2, 3], 51 | "float_col": [1.1, 2.2, 3.3, 4.4], 52 | "bool_col": [True, False, False, True], 53 | } 54 | ) 55 | 56 | class Foobar: 57 | """ """ 58 | 59 | def __init__(self): 60 | self.text = "toto" 61 | self.testdict = testdict 62 | self.testdate = testdate 63 | 64 | foobar = Foobar() 65 | test_data = { 66 | "object": foobar, 67 | "module": np, 68 | "str": "kjkj kj k j j kj k jkj", 69 | "unicode": "éù", 70 | "list": [1, 3, [sorted, 5, 6], "kjkj", None], 71 | "tuple": ([1, testdate, testdict, test_timedelta], "kjkj", None), 72 | "dict": testdict, 73 | "float": 1.2233, 74 | "int": 223, 75 | "bool": True, 76 | "array": np.random.rand(10, 10).astype(np.int64), 77 | "masked_array": np.ma.array( 78 | [[1, 0], [1, 0]], mask=[[True, False], [False, False]] 79 | ), 80 | "1D-array": np.linspace(-10, 10).astype(np.float16), 81 | "3D-array": np.random.randint(2, size=(5, 5, 5)).astype(np.bool_), 82 | "empty_array": np.array([]), 83 | "date": testdate, 84 | "datetime": datetime.datetime(1945, 5, 8, 23, 1, 0, int(1.5e5)), 85 | "timedelta": test_timedelta, 86 | "complex": 2 + 1j, 87 | "complex64": np.complex64(2 + 1j), 88 | "complex128": np.complex128(9j), 89 | "int8_scalar": np.int8(8), 90 | "int16_scalar": np.int16(16), 91 | "int32_scalar": np.int32(32), 92 | "int64_scalar": np.int64(64), 93 | "float16_scalar": np.float16(16), 94 | "float32_scalar": np.float32(32), 95 | "float64_scalar": np.float64(64), 96 | "bool_scalar": bool, 97 | "bool__scalar": np.bool_(8), 98 | "timestamp": test_timestamp, 99 | "timedelta_pd": test_pd_td, 100 | "datetimeindex": test_dtindex, 101 | "series": test_series, 102 | "ddataframe": test_df, 103 | "None": None, 104 | "unsupported1": np.arccos, 105 | # Test for Issue #3518 106 | "big_struct_array": np.zeros( 107 | 1000, dtype=[("ID", "f8"), ("param1", "f8", 5000)] 108 | ), 109 | } 110 | if PILImage is not None: 111 | image = PILImage.fromarray(np.random.randint(256, size=(100, 100)), mode="P") 112 | test_data["image"] = image 113 | return test_data 114 | 115 | 116 | def test_collectionseditor(): 117 | """Test Collections editor.""" 118 | with qt_app_context(exec_loop=True): 119 | dialog = CollectionsEditor() 120 | dialog.setup(get_test_data()) 121 | dialog.show() 122 | execenv.print("OK") 123 | 124 | 125 | if __name__ == "__main__": 126 | test_collectionseditor() 127 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_console.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for codeeditor.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | from guidata.configtools import get_icon 14 | from guidata.env import execenv 15 | from guidata.qthelpers import qt_app_context 16 | from guidata.widgets.console import Console 17 | 18 | 19 | def test_console(): 20 | """Test Console widget.""" 21 | with qt_app_context(exec_loop=True): 22 | widget = Console(debug=False, multithreaded=True) 23 | widget.resize(800, 600) 24 | widget.setWindowTitle("Console") 25 | widget.setWindowIcon(get_icon("guidata.svg")) 26 | widget.show() 27 | execenv.print("OK") 28 | 29 | 30 | if __name__ == "__main__": 31 | test_console() 32 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_dataframeeditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for dataframeeditor.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | import numpy as np 14 | import pytest 15 | 16 | try: 17 | import pandas as pd 18 | import pandas.testing as pdt 19 | 20 | from guidata.widgets.dataframeeditor import DataFrameEditor 21 | except ImportError: 22 | # pandas is not installed 23 | pd = pdt = DataFrameEditor = None 24 | 25 | from guidata.env import execenv 26 | from guidata.qthelpers import exec_dialog, qt_app_context 27 | 28 | 29 | @pytest.mark.skipif(pd is None, reason="pandas is not installed") 30 | def test_dataframeeditor(): 31 | """DataFrame editor test""" 32 | 33 | def test_edit(data, title="", parent=None): 34 | """Test subroutine""" 35 | dlg = DataFrameEditor(parent=parent) 36 | 37 | if dlg.setup_and_check(data, title=title): 38 | exec_dialog(dlg) 39 | return dlg.get_value() 40 | else: 41 | import sys 42 | 43 | sys.exit(1) 44 | 45 | with qt_app_context(): 46 | df1 = pd.DataFrame( 47 | [ 48 | [True, "bool"], 49 | [1 + 1j, "complex"], 50 | ["test", "string"], 51 | [1.11, "float"], 52 | [1, "int"], 53 | [np.random.rand(3, 3), "Unkown type"], 54 | ["Large value", 100], 55 | ["áéí", "unicode"], 56 | ], 57 | index=["a", "b", np.nan, np.nan, np.nan, "c", "Test global max", "d"], 58 | columns=[np.nan, "Type"], 59 | ) 60 | out = test_edit(df1) 61 | pdt.assert_frame_equal(df1, out) 62 | 63 | result = pd.Series([True, "bool"], index=[np.nan, "Type"], name="a") 64 | out = test_edit(df1.iloc[0]) 65 | pdt.assert_series_equal(result, out) 66 | 67 | df1 = pd.DataFrame(np.random.rand(100100, 10)) 68 | out = test_edit(df1) 69 | pdt.assert_frame_equal(out, df1) 70 | 71 | series = pd.Series(np.arange(10), name=0) 72 | out = test_edit(series) 73 | pdt.assert_series_equal(series, out) 74 | 75 | execenv.print("OK") 76 | 77 | 78 | if __name__ == "__main__": 79 | test_dataframeeditor() 80 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_importwizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | 6 | 7 | """ 8 | Tests for importwizard.py 9 | """ 10 | 11 | # guitest: show 12 | 13 | import pytest 14 | 15 | from guidata.env import execenv 16 | from guidata.qthelpers import exec_dialog, qt_app_context 17 | from guidata.widgets.importwizard import ImportWizard 18 | 19 | 20 | @pytest.fixture() 21 | def text(): 22 | """Return text to test""" 23 | return "17/11/1976\t1.34\n14/05/09\t3.14" 24 | 25 | 26 | def test_importwizard(text): 27 | """Test""" 28 | with qt_app_context(): 29 | dialog = ImportWizard(None, text) 30 | if exec_dialog(dialog): 31 | execenv.print(dialog.get_data()) 32 | execenv.print("OK") 33 | 34 | 35 | if __name__ == "__main__": 36 | test_importwizard("17/11/1976\t1.34\n14/05/09\t3.14") 37 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_objecteditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | """ 6 | Tests for objecteditor.py 7 | """ 8 | 9 | # guitest: show 10 | 11 | import datetime 12 | 13 | import numpy as np 14 | 15 | try: 16 | from PIL import Image 17 | except ImportError: 18 | # PIL is not installed 19 | Image = None 20 | 21 | from guidata.env import execenv 22 | from guidata.qthelpers import qt_app_context 23 | from guidata.widgets.objecteditor import oedit 24 | 25 | 26 | def test_objecteditor(): 27 | """Run object editor test""" 28 | with qt_app_context(): 29 | example = { 30 | "str": "kjkj kj k j j kj k jkj", 31 | "list": [1, 3, 4, "kjkj", None], 32 | "dict": {"d": 1, "a": np.random.rand(10, 10), "b": [1, 2]}, 33 | "float": 1.2233, 34 | "array": np.random.rand(10, 10), 35 | "date": datetime.date(1945, 5, 8), 36 | "datetime": datetime.datetime(1945, 5, 8), 37 | } 38 | if Image is not None: 39 | data = np.random.randint(255, size=(100, 100)).astype("uint8") 40 | image = Image.fromarray(data) 41 | example["image"] = image 42 | image = oedit(image) 43 | 44 | class Foobar: 45 | """ """ 46 | 47 | def __init__(self): 48 | self.text = "toto" 49 | 50 | foobar = Foobar() 51 | execenv.print(oedit(foobar)) 52 | execenv.print(oedit(example)) 53 | execenv.print(oedit(np.random.rand(10, 10))) 54 | execenv.print(oedit(oedit.__doc__)) 55 | execenv.print(example) 56 | execenv.print("OK") 57 | 58 | 59 | if __name__ == "__main__": 60 | test_objecteditor() 61 | -------------------------------------------------------------------------------- /guidata/tests/widgets/test_theme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Test dark/light theme switching 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | import os 13 | import sys 14 | from typing import Literal 15 | 16 | import pytest 17 | from qtpy import QtCore as QC 18 | from qtpy import QtWidgets as QW 19 | 20 | from guidata import qthelpers as qth 21 | from guidata.widgets.codeeditor import CodeEditor 22 | from guidata.widgets.console import Console 23 | 24 | 25 | class BaseColorModeWidget(QW.QWidget): 26 | """Base class for testing dark/light theme switching""" 27 | 28 | SIZE = (1200, 600) 29 | 30 | def __init__(self, default: Literal["light", "dark", "auto"] = qth.AUTO) -> None: 31 | super(BaseColorModeWidget, self).__init__() 32 | self.resize(*self.SIZE) 33 | self.default_theme = default 34 | self.combo: QW.QComboBox | None = None 35 | self.setWindowTitle(self.__doc__) 36 | self.grid_layout = QW.QGridLayout() 37 | self.setLayout(self.grid_layout) 38 | self.setup_widgets() 39 | if default != qth.AUTO: 40 | self.change_color_mode(default) 41 | 42 | def setup_widgets(self): 43 | """Setup widgets""" 44 | label = QW.QLabel("Select color mode:") 45 | self.combo = QW.QComboBox() 46 | self.combo.addItems(qth.COLOR_MODES) 47 | self.combo.setCurrentText(self.default_theme) 48 | self.combo.currentTextChanged.connect(self.change_color_mode) 49 | self.combo.setSizePolicy(QW.QSizePolicy.Expanding, QW.QSizePolicy.Minimum) 50 | self.combo.setToolTip( 51 | "Select color mode:" 52 | "
  • auto: follow system settings
  • " 53 | "
  • light: use light theme
  • " 54 | "
  • dark: use dark theme
" 55 | ) 56 | hlayout = QW.QHBoxLayout() 57 | hlayout.addWidget(label) 58 | hlayout.addWidget(self.combo) 59 | self.grid_layout.addLayout(hlayout, 0, 0, 1, -1) 60 | 61 | def change_color_mode(self, mode: str) -> None: 62 | """Change color mode""" 63 | qth.set_color_mode(mode) 64 | 65 | def closeEvent(self, event): 66 | """Close event""" 67 | self.console.close() 68 | event.accept() 69 | 70 | 71 | class ColorModeWidget(BaseColorModeWidget): 72 | """Testing color mode switching for guidata's widgets: code editor and console""" 73 | 74 | def __init__(self, default: Literal["light", "dark", "auto"] = qth.AUTO) -> None: 75 | self.editor: CodeEditor | None = None 76 | self.console: Console | None = None 77 | super().__init__(default) 78 | qth.win32_fix_title_bar_background(self) 79 | 80 | def setup_widgets(self): 81 | """Setup widgets""" 82 | super().setup_widgets() 83 | self.editor = CodeEditor(self) 84 | self.console = Console(self, debug=False) 85 | for widget in (self.editor, self.console): 86 | widget.setSizePolicy(QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding) 87 | self.editor.setPlainText("Change theme using the combo box above" + os.linesep) 88 | self.add_info_to_codeeditor() 89 | self.console.execute_command("print('Console output')") 90 | self.grid_layout.addWidget(self.editor, 1, 0) 91 | self.grid_layout.addWidget(self.console, 1, 1) 92 | 93 | def add_info_to_codeeditor(self): 94 | """Add current color mode and theme to the code editor, with a prefix with 95 | date and time""" 96 | self.editor.setPlainText( 97 | os.linesep.join( 98 | [ 99 | self.editor.toPlainText(), 100 | "", 101 | f"{QC.QDateTime.currentDateTime().toString()}:", 102 | f" Current color mode: {qth.get_color_mode()}", 103 | f" Current theme: {qth.get_color_theme()}", 104 | ] 105 | ) 106 | ) 107 | 108 | def change_color_mode(self, mode: str) -> None: 109 | """Change color mode""" 110 | super().change_color_mode(mode) 111 | for widget in (self.editor, self.console): 112 | widget.update_color_mode() 113 | self.add_info_to_codeeditor() 114 | 115 | 116 | @pytest.mark.skipif(reason="Not suitable for automated testing") 117 | def test_dark_light_themes( 118 | default: Literal["light", "dark", "auto"] | None = None, 119 | ) -> None: 120 | """Test dark/light theme switching""" 121 | with qth.qt_app_context(exec_loop=True): 122 | widget = ColorModeWidget(default=qth.AUTO if default is None else default) 123 | widget.show() 124 | 125 | 126 | if __name__ == "__main__": 127 | test_dark_light_themes(None if len(sys.argv) < 2 else sys.argv[1]) 128 | -------------------------------------------------------------------------------- /guidata/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /guidata/utils/gettext_helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | import os 7 | import os.path as osp 8 | import subprocess 9 | import sys 10 | 11 | if os.name == "nt": 12 | # Find pygettext.py source on a windows install 13 | pygettext = ["python", osp.join(sys.prefix, "Tools", "i18n", "pygettext.py")] 14 | msgfmt = ["python", osp.join(sys.prefix, "Tools", "i18n", "msgfmt.py")] 15 | else: 16 | pygettext = ["pygettext"] 17 | msgfmt = ["msgfmt"] 18 | 19 | 20 | def get_files(modname): 21 | if not osp.isdir(modname): 22 | return [modname] 23 | files = [] 24 | for dirname, _dirnames, filenames in os.walk(modname): 25 | files += [ 26 | osp.join(dirname, f) 27 | for f in filenames 28 | if f.endswith(".py") or f.endswith(".pyw") 29 | ] 30 | for dirname, _dirnames, filenames in os.walk("tests"): 31 | files += [ 32 | osp.join(dirname, f) 33 | for f in filenames 34 | if f.endswith(".py") or f.endswith(".pyw") 35 | ] 36 | return files 37 | 38 | 39 | def get_lang(modname): 40 | localedir = osp.join(modname, "locale") 41 | for _dirname, dirnames, _filenames in os.walk(localedir): 42 | break # we just want the list of first level directories 43 | return dirnames 44 | 45 | 46 | def do_rescan(modname): 47 | files = get_files(modname) 48 | dirname = modname 49 | do_rescan_files(files, modname, dirname) 50 | 51 | 52 | def do_rescan_files(files, modname, dirname): 53 | localedir = osp.join(dirname, "locale") 54 | potfile = modname + ".pot" 55 | subprocess.call( 56 | pygettext 57 | + [ 58 | ##"-D", # Extract docstrings 59 | "-o", 60 | potfile, # Nom du fichier pot 61 | "-p", 62 | localedir, # dest 63 | "--no-location", 64 | ] 65 | + files 66 | ) 67 | for lang in get_lang(dirname): 68 | pofilepath = osp.join(localedir, lang, "LC_MESSAGES", modname + ".po") 69 | potfilepath = osp.join(localedir, potfile) 70 | print("Updating...", pofilepath) 71 | if not osp.exists(osp.join(localedir, lang, "LC_MESSAGES")): 72 | os.mkdir(osp.join(localedir, lang, "LC_MESSAGES")) 73 | if not osp.exists(pofilepath): 74 | outf = open(pofilepath, "w") 75 | outf.write("# -*- coding: utf-8 -*-\n") 76 | data = open(potfilepath).read() 77 | data = data.replace("charset=CHARSET", "charset=utf-8") 78 | data = data.replace( 79 | "Content-Transfer-Encoding: ENCODING", 80 | "Content-Transfer-Encoding: utf-8", 81 | ) 82 | outf.write(data) 83 | else: 84 | print("merge...") 85 | subprocess.call(["msgmerge", "--update", pofilepath, potfilepath]) 86 | 87 | 88 | def do_compile(modname, dirname=None): 89 | if dirname is None: 90 | dirname = modname 91 | localedir = osp.join(dirname, "locale") 92 | for lang in get_lang(dirname): 93 | pofilepath = osp.join(localedir, lang, "LC_MESSAGES", modname + ".po") 94 | subprocess.call(msgfmt + [pofilepath]) 95 | 96 | 97 | def main(modname): 98 | if len(sys.argv) < 2: 99 | cmd = "help" 100 | else: 101 | cmd = sys.argv[1] 102 | # lang = get_lang( modname ) 103 | if cmd == "help": 104 | print("Available commands:") 105 | print(" help : this message") 106 | print(" help_gettext : pygettext --help") 107 | print(" help_msgfmt : msgfmt --help") 108 | print(" scan : rescan .py files and updates existing .po files") 109 | print(" compile : recompile .po files") 110 | print() 111 | print("Pour fonctionner ce programme doit être lancé depuis") 112 | print("la racine du module") 113 | print("Traductions disponibles:") 114 | for i in get_lang(modname): 115 | print(i) 116 | elif cmd == "help_gettext": 117 | subprocess.call(pygettext + ["--help"]) 118 | elif cmd == "help_msgfmt": 119 | subprocess.call(msgfmt + ["--help"]) 120 | elif cmd == "scan": 121 | print("Updating pot files") 122 | do_rescan(modname) 123 | elif cmd == "compile": 124 | print("Builtin .mo files") 125 | do_compile(modname) 126 | -------------------------------------------------------------------------------- /guidata/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | Ready-to-use Qt widgets 8 | ----------------------- 9 | 10 | Data editors 11 | ^^^^^^^^^^^^ 12 | 13 | .. autoclass:: guidata.widgets.arrayeditor.ArrayEditor 14 | 15 | .. autoclass:: guidata.widgets.collectionseditor.CollectionsEditor 16 | 17 | .. autoclass:: guidata.widgets.dataframeeditor.DataFrameEditor 18 | 19 | .. autoclass:: guidata.widgets.texteditor.TextEditor 20 | 21 | .. autofunction:: guidata.widgets.objecteditor.oedit 22 | 23 | Console and code editor 24 | ^^^^^^^^^^^^^^^^^^^^^^^ 25 | 26 | .. autoclass:: guidata.widgets.console.Console 27 | 28 | .. autoclass:: guidata.widgets.console.DockableConsole 29 | 30 | .. autoclass:: guidata.widgets.codeeditor.CodeEditor 31 | 32 | """ 33 | -------------------------------------------------------------------------------- /guidata/widgets/arrayeditor/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | # 6 | # The array editor subpackage was derived from Spyder's arrayeditor.py module 7 | # which is licensed under the terms of the MIT License (see spyder/__init__.py 8 | # for details), copyright © Spyder Project Contributors 9 | 10 | """guidata.widgets.arrayeditor 11 | =========================== 12 | 13 | This package provides a NumPy Array Editor Dialog based on Qt. 14 | 15 | .. autoclass:: ArrayEditor 16 | :show-inheritance: 17 | :members: 18 | 19 | """ 20 | 21 | from .arrayeditor import ArrayEditor # noqa: F401 22 | -------------------------------------------------------------------------------- /guidata/widgets/arrayeditor/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | # 6 | # The array editor subpackage was derived from Spyder's arrayeditor.py module 7 | # which is licensed under the terms of the MIT License (see spyder/__init__.py 8 | # for details), copyright © Spyder Project Contributors 9 | 10 | # Note: string and unicode data types will be formatted with '%s' (see below) 11 | 12 | """Basic utilitarian functions and variables for the various array editor classes""" 13 | 14 | import numpy as np 15 | 16 | SUPPORTED_FORMATS = { 17 | "single": "%.6g", 18 | "double": "%.6g", 19 | "float_": "%.6g", 20 | "longfloat": "%.6g", 21 | "float16": "%.6g", 22 | "float32": "%.6g", 23 | "float64": "%.6g", 24 | "float96": "%.6g", 25 | "float128": "%.6g", 26 | "csingle": "%r", 27 | "complex_": "%r", 28 | "clongfloat": "%r", 29 | "complex64": "%r", 30 | "complex128": "%r", 31 | "complex192": "%r", 32 | "complex256": "%r", 33 | "byte": "%d", 34 | "bytes8": "%s", 35 | "short": "%d", 36 | "intc": "%d", 37 | "int_": "%d", 38 | "longlong": "%d", 39 | "intp": "%d", 40 | "int8": "%d", 41 | "int16": "%d", 42 | "int32": "%d", 43 | "int64": "%d", 44 | "ubyte": "%d", 45 | "ushort": "%d", 46 | "uintc": "%d", 47 | "uint": "%d", 48 | "ulonglong": "%d", 49 | "uintp": "%d", 50 | "uint8": "%d", 51 | "uint16": "%d", 52 | "uint32": "%d", 53 | "uint64": "%d", 54 | "bool_": "%r", 55 | "bool8": "%r", 56 | "bool": "%r", 57 | } 58 | 59 | 60 | LARGE_SIZE = 5e5 61 | LARGE_NROWS = 1e5 62 | LARGE_COLS = 60 63 | 64 | 65 | # ============================================================================== 66 | # Utility functions 67 | # ============================================================================== 68 | def is_float(dtype: np.dtype) -> bool: 69 | """Return True if datatype dtype is a float kind 70 | 71 | Args: 72 | dtype: numpy datatype 73 | 74 | Returns: 75 | True if dtype is a float kind 76 | """ 77 | return ("float" in dtype.name) or dtype.name in ["single", "double"] 78 | 79 | 80 | def is_number(dtype: np.dtype) -> bool: 81 | """Return True is datatype dtype is a number kind 82 | 83 | Args: 84 | dtype: numpy datatype 85 | 86 | Returns: 87 | True if dtype is a number kind 88 | """ 89 | return ( 90 | is_float(dtype) 91 | or ("int" in dtype.name) 92 | or ("long" in dtype.name) 93 | or ("short" in dtype.name) 94 | ) 95 | 96 | 97 | def get_idx_rect(index_list: list) -> tuple: 98 | """Extract the boundaries from a list of indexes 99 | 100 | Args: 101 | index_list: list of indexes 102 | 103 | Returns: 104 | tuple: (min_row, max_row, min_col, max_col) 105 | """ 106 | rows, cols = list(zip(*[(i.row(), i.column()) for i in index_list])) 107 | return (min(rows), max(rows), min(cols), max(cols)) 108 | -------------------------------------------------------------------------------- /guidata/widgets/console/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | guidata.widgets.console 5 | ======================= 6 | 7 | This package provides a Python console widget. 8 | 9 | .. autoclass:: Console 10 | :show-inheritance: 11 | :members: 12 | 13 | .. autoclass:: DockableConsole 14 | :show-inheritance: 15 | :members: 16 | 17 | """ 18 | 19 | from qtpy.QtCore import Qt 20 | 21 | from guidata.config import CONF 22 | from guidata.configtools import get_font 23 | from guidata.qthelpers import win32_fix_title_bar_background 24 | from guidata.widgets.console.internalshell import InternalShell 25 | from guidata.widgets.dockable import DockableWidgetMixin 26 | 27 | 28 | class Console(InternalShell): 29 | """ 30 | Python console that run an interactive shell linked to 31 | the running process. 32 | 33 | :param parent: parent Qt widget 34 | :param namespace: available python namespace when the console start 35 | :type namespace: dict 36 | :param message: banner displayed before the first prompt 37 | :param commands: commands run when the interpreter starts 38 | :param type commands: list of string 39 | :param multithreaded: multithreaded support 40 | """ 41 | 42 | def __init__( 43 | self, 44 | parent=None, 45 | namespace=None, 46 | message=None, 47 | commands=None, 48 | multithreaded=True, 49 | debug=False, 50 | ): 51 | InternalShell.__init__( 52 | self, 53 | parent=parent, 54 | namespace=namespace, 55 | message=message, 56 | commands=commands or [], 57 | multithreaded=multithreaded, 58 | debug=debug, 59 | ) 60 | win32_fix_title_bar_background(self) 61 | self.setup() 62 | 63 | def setup(self): 64 | """Setup the calltip widget and show the console once all 65 | internal handler are ready.""" 66 | font = get_font(CONF, "console") 67 | font.setPointSize(10) 68 | self.set_font(font) 69 | self.set_codecompletion_auto(True) 70 | self.set_calltips(True) 71 | self.setup_completion(size=(300, 180), font=font) 72 | try: 73 | self.exception_occurred.connect(self.show_console) 74 | except AttributeError: 75 | pass 76 | 77 | def closeEvent(self, event): 78 | """Reimplement Qt base method""" 79 | InternalShell.closeEvent(self, event) 80 | self.exit_interpreter() 81 | event.accept() 82 | 83 | 84 | class DockableConsole(Console, DockableWidgetMixin): 85 | """ 86 | Dockable Python console that run an interactive shell linked to 87 | the running process. 88 | 89 | :param parent: parent Qt widget 90 | :param namespace: available python namespace when the console start 91 | :type namespace: dict 92 | :param message: banner displayed before the first prompt 93 | :param commands: commands run when the interpreter starts 94 | :param type commands: list of string 95 | """ 96 | 97 | LOCATION = Qt.BottomDockWidgetArea 98 | 99 | def __init__( 100 | self, parent, namespace, message, commands=None, multithreaded=True, debug=False 101 | ): 102 | DockableWidgetMixin.__init__(self) 103 | Console.__init__( 104 | self, 105 | parent=parent, 106 | namespace=namespace, 107 | message=message, 108 | commands=commands or [], 109 | multithreaded=multithreaded, 110 | debug=debug, 111 | ) 112 | 113 | def show_console(self): 114 | """Show the console widget.""" 115 | self.dockwidget.raise_() 116 | self.dockwidget.show() 117 | -------------------------------------------------------------------------------- /guidata/widgets/console/terminal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see spyder/__init__.py for details) 6 | 7 | """Terminal emulation tools""" 8 | 9 | import os 10 | 11 | 12 | class ANSIEscapeCodeHandler(object): 13 | """ANSI Escape sequences handler""" 14 | 15 | if os.name == "nt": 16 | # Windows terminal colors: 17 | ANSI_COLORS = ( # Normal, Bright/Light 18 | ("#000000", "#808080"), # 0: black 19 | ("#800000", "#ff0000"), # 1: red 20 | ("#008000", "#00ff00"), # 2: green 21 | ("#808000", "#ffff00"), # 3: yellow 22 | ("#000080", "#0000ff"), # 4: blue 23 | ("#800080", "#ff00ff"), # 5: magenta 24 | ("#008080", "#00ffff"), # 6: cyan 25 | ("#c0c0c0", "#ffffff"), # 7: white 26 | ) 27 | elif os.name == "mac": 28 | # Terminal.app colors: 29 | ANSI_COLORS = ( # Normal, Bright/Light 30 | ("#000000", "#818383"), # 0: black 31 | ("#C23621", "#FC391F"), # 1: red 32 | ("#25BC24", "#25BC24"), # 2: green 33 | ("#ADAD27", "#EAEC23"), # 3: yellow 34 | ("#492EE1", "#5833FF"), # 4: blue 35 | ("#D338D3", "#F935F8"), # 5: magenta 36 | ("#33BBC8", "#14F0F0"), # 6: cyan 37 | ("#CBCCCD", "#E9EBEB"), # 7: white 38 | ) 39 | else: 40 | # xterm colors: 41 | ANSI_COLORS = ( # Normal, Bright/Light 42 | ("#000000", "#7F7F7F"), # 0: black 43 | ("#CD0000", "#ff0000"), # 1: red 44 | ("#00CD00", "#00ff00"), # 2: green 45 | ("#CDCD00", "#ffff00"), # 3: yellow 46 | ("#0000EE", "#5C5CFF"), # 4: blue 47 | ("#CD00CD", "#ff00ff"), # 5: magenta 48 | ("#00CDCD", "#00ffff"), # 6: cyan 49 | ("#E5E5E5", "#ffffff"), # 7: white 50 | ) 51 | 52 | def __init__(self): 53 | self.intensity = 0 54 | self.italic = None 55 | self.bold = None 56 | self.underline = None 57 | self.foreground_color = None 58 | self.background_color = None 59 | self.default_foreground_color = 30 60 | self.default_background_color = 47 61 | 62 | def set_code(self, code): 63 | """ 64 | 65 | :param code: 66 | """ 67 | assert isinstance(code, int) 68 | if code == 0: 69 | # Reset all settings 70 | self.reset() 71 | elif code == 1: 72 | # Text color intensity 73 | self.intensity = 1 74 | # The following line is commented because most terminals won't 75 | # change the font weight, against ANSI standard recommendation: 76 | # self.bold = True 77 | elif code == 3: 78 | # Italic on 79 | self.italic = True 80 | elif code == 4: 81 | # Underline simple 82 | self.underline = True 83 | elif code == 22: 84 | # Normal text color intensity 85 | self.intensity = 0 86 | self.bold = False 87 | elif code == 23: 88 | # No italic 89 | self.italic = False 90 | elif code == 24: 91 | # No underline 92 | self.underline = False 93 | elif code >= 30 and code <= 37: 94 | # Text color 95 | self.foreground_color = code 96 | elif code == 39: 97 | # Default text color 98 | self.foreground_color = self.default_foreground_color 99 | elif code >= 40 and code <= 47: 100 | # Background color 101 | self.background_color = code 102 | elif code == 49: 103 | # Default background color 104 | self.background_color = self.default_background_color 105 | self.set_style() 106 | 107 | def set_style(self): 108 | """ 109 | Set font style with the following attributes: 110 | 'foreground_color', 'background_color', 'italic', 111 | 'bold' and 'underline' 112 | """ 113 | raise NotImplementedError 114 | 115 | def reset(self): 116 | """ 117 | 118 | """ 119 | self.current_format = None 120 | self.intensity = 0 121 | self.italic = False 122 | self.bold = False 123 | self.underline = False 124 | self.foreground_color = None 125 | self.background_color = None 126 | -------------------------------------------------------------------------------- /guidata/widgets/dockable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | dockable 8 | -------- 9 | 10 | The `dockable` module provides a mixin class for widgets that can be docked 11 | into a QMainWindow. 12 | """ 13 | 14 | from __future__ import annotations 15 | 16 | from qtpy.QtCore import Qt 17 | from qtpy.QtWidgets import QDockWidget, QWidget 18 | 19 | 20 | class DockableWidgetMixin: 21 | """Mixin class for widgets that can be docked into a QMainWindow""" 22 | 23 | ALLOWED_AREAS = Qt.AllDockWidgetAreas 24 | LOCATION = Qt.TopDockWidgetArea 25 | FEATURES = ( 26 | QDockWidget.DockWidgetClosable 27 | | QDockWidget.DockWidgetFloatable 28 | | QDockWidget.DockWidgetMovable 29 | ) 30 | 31 | def __init__(self): 32 | self._isvisible = False 33 | self.dockwidget: QDockWidget | None = None 34 | self._allowed_areas = self.ALLOWED_AREAS 35 | self._location = self.LOCATION 36 | self._features = self.FEATURES 37 | 38 | @property 39 | def parent_widget(self) -> QWidget | None: 40 | """Return associated QWidget parent""" 41 | return self.parent() 42 | 43 | def setup_dockwidget( 44 | self, 45 | location: Qt.DockWidgetArea | None = None, 46 | features: QDockWidget.DockWidgetFeatures | None = None, 47 | allowed_areas: Qt.DockWidgetAreas | None = None, 48 | ) -> None: 49 | """Setup dockwidget parameters 50 | 51 | Args: 52 | location (Qt.DockWidgetArea): Dockwidget location 53 | features (QDockWidget.DockWidgetFeatures): Dockwidget features 54 | allowed_areas (Qt.DockWidgetAreas): Dockwidget allowed areas 55 | """ 56 | assert ( 57 | self.dockwidget is None 58 | ), "Dockwidget must be setup before calling 'create_dockwidget'" 59 | if location is not None: 60 | self._location = location 61 | if features is not None: 62 | self._features = features 63 | if allowed_areas is not None: 64 | self._allowed_areas = allowed_areas 65 | 66 | def get_focus_widget(self) -> QWidget | None: 67 | """Return widget to focus when dockwidget is visible""" 68 | return None 69 | 70 | def create_dockwidget(self, title: str) -> tuple[QDockWidget, Qt.DockWidgetArea]: 71 | """Add to parent QMainWindow as a dock widget 72 | 73 | Args: 74 | title (str): Dockwidget title 75 | 76 | Returns: 77 | tuple[QDockWidget, Qt.DockWidgetArea]: Dockwidget and location 78 | """ 79 | dock = QDockWidget(title, self.parent_widget) 80 | dock.setObjectName(self.__class__.__name__ + "_dw") 81 | dock.setAllowedAreas(self._allowed_areas) 82 | dock.setFeatures(self._features) 83 | dock.setWidget(self) 84 | dock.visibilityChanged.connect(self.visibility_changed) 85 | self.dockwidget = dock 86 | return (dock, self._location) 87 | 88 | def is_visible(self) -> bool: 89 | """Return dockwidget visibility state""" 90 | return self._isvisible 91 | 92 | def visibility_changed(self, enable: bool) -> None: 93 | """DockWidget visibility has changed 94 | 95 | Args: 96 | enable (bool): Dockwidget visibility state 97 | """ 98 | if enable: 99 | self.dockwidget.raise_() 100 | widget = self.get_focus_widget() # pylint: disable=assignment-from-none 101 | if widget is not None: 102 | widget.setFocus() 103 | self._isvisible = enable and self.dockwidget.isVisible() 104 | 105 | 106 | class DockableWidget(QWidget, DockableWidgetMixin): 107 | """Dockable widget 108 | 109 | Args: 110 | parent (QWidget): Parent widget 111 | """ 112 | 113 | def __init__(self, parent: QWidget): 114 | QWidget.__init__(self, parent) 115 | DockableWidgetMixin.__init__(self) 116 | -------------------------------------------------------------------------------- /guidata/widgets/objecteditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see spyder/__init__.py for details) 6 | 7 | """ 8 | guidata.widgets.objecteditor 9 | ============================ 10 | 11 | This package provides a generic object editor widget. 12 | 13 | .. autofunction:: oedit 14 | 15 | """ 16 | 17 | from __future__ import annotations 18 | 19 | from typing import TYPE_CHECKING 20 | 21 | import numpy as np 22 | 23 | try: 24 | from PIL import Image as PILImage 25 | except ImportError: 26 | PILImage = None 27 | 28 | from guidata.qthelpers import exec_dialog 29 | from guidata.widgets.arrayeditor import ArrayEditor 30 | from guidata.widgets.collectionseditor import CollectionsEditor 31 | from guidata.widgets.nsview import DataFrame, FakeObject, Series, is_known_type 32 | from guidata.widgets.texteditor import TextEditor 33 | 34 | try: 35 | from guidata.widgets.dataframeeditor import DataFrameEditor 36 | except ImportError: 37 | DataFrameEditor = FakeObject() 38 | 39 | 40 | if TYPE_CHECKING: 41 | from qtpy import QtWidgets as QW 42 | 43 | 44 | def create_dialog(obj, title, parent=None): 45 | """Creates the editor dialog and returns a tuple (dialog, func) where func 46 | is the function to be called with the dialog instance as argument, after 47 | quitting the dialog box 48 | 49 | The role of this intermediate function is to allow easy monkey-patching. 50 | (uschmitt suggested this indirection here so that he can monkey patch 51 | oedit to show eMZed related data) 52 | """ 53 | 54 | def conv_func(data): 55 | """Conversion function""" 56 | return data 57 | 58 | readonly = not is_known_type(obj) 59 | if isinstance(obj, np.ndarray): 60 | dialog = ArrayEditor(parent) 61 | if not dialog.setup_and_check(obj, title=title, readonly=readonly): 62 | return 63 | elif PILImage is not None and isinstance(obj, PILImage.Image): 64 | dialog = ArrayEditor(parent) 65 | 66 | data = np.array(obj) 67 | if not dialog.setup_and_check(data, title=title, readonly=readonly): 68 | return 69 | 70 | def conv_func(data): # pylint: disable=function-redefined 71 | """Conversion function""" 72 | return PILImage.fromarray(data, mode=obj.mode) 73 | 74 | elif isinstance(obj, (DataFrame, Series)) and DataFrame is not FakeObject: 75 | dialog = DataFrameEditor(parent) 76 | if not dialog.setup_and_check(obj): 77 | return 78 | elif isinstance(obj, str): 79 | dialog = TextEditor(obj, title=title, readonly=readonly, parent=parent) 80 | else: 81 | dialog = CollectionsEditor(parent) 82 | dialog.setup(obj, title=title, readonly=readonly) 83 | 84 | def end_func(dialog): 85 | """ 86 | 87 | :param dialog: 88 | :return: 89 | """ 90 | return conv_func(dialog.get_value()) 91 | 92 | return dialog, end_func 93 | 94 | 95 | def oedit( 96 | obj: dict | list | tuple | str | np.ndarray, 97 | title: str = None, 98 | parent: QW.QWidget = None, 99 | ) -> dict | list | tuple | str | np.ndarray: 100 | """Edit the object 'obj' in a GUI-based editor and return the edited copy 101 | (if Cancel is pressed, return None) 102 | 103 | Args: 104 | obj (dict | list | tuple | str | np.ndarray): object to edit 105 | title (str): dialog title 106 | parent (QW.QWidget): parent widget 107 | 108 | Returns: 109 | dict | list | tuple | str | np.ndarray: edited object 110 | """ 111 | title = "" if title is None else title 112 | result = create_dialog(obj, title, parent) 113 | if result is None: 114 | return 115 | dialog, end_func = result 116 | if exec_dialog(dialog): 117 | return end_func(dialog) 118 | return None 119 | -------------------------------------------------------------------------------- /guidata/widgets/rotatedlabel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see guidata/LICENSE for details) 5 | 6 | """ 7 | rotatedlabel 8 | ------------ 9 | 10 | The ``guidata.widgets.rotatedlabel`` module provides a widget for displaying 11 | rotated text. 12 | """ 13 | 14 | 15 | from math import cos, pi, sin 16 | 17 | from qtpy.QtCore import QSize, Qt 18 | from qtpy.QtGui import QPainter, QPen 19 | from qtpy.QtWidgets import QLabel 20 | 21 | from guidata.configtools import get_family 22 | 23 | 24 | class RotatedLabel(QLabel): 25 | """ 26 | Rotated QLabel 27 | (rich text is not supported) 28 | Arguments: 29 | * parent: parent widget 30 | * angle=270 (int): rotation angle in degrees 31 | * family (string): font family 32 | * bold (bool): font weight 33 | * italic (bool): font italic style 34 | * color (QColor): font color 35 | """ 36 | 37 | def __init__( 38 | self, 39 | text, 40 | parent=None, 41 | angle=270, 42 | family=None, 43 | bold=False, 44 | italic=False, 45 | color=None, 46 | ): 47 | QLabel.__init__(self, text, parent) 48 | font = self.font() 49 | if family is not None: 50 | font.setFamily(get_family(family)) 51 | font.setBold(bold) 52 | font.setItalic(italic) 53 | self.setFont(font) 54 | self.color = color 55 | self.angle = angle 56 | self.setAlignment(Qt.AlignCenter) 57 | 58 | def paintEvent(self, evt): 59 | painter = QPainter(self) 60 | if self.color is not None: 61 | painter.setPen(QPen(self.color)) 62 | painter.rotate(self.angle) 63 | transform = painter.transform().inverted()[0] 64 | rct = transform.mapRect(self.rect()) 65 | painter.drawText(rct, self.alignment(), self.text()) 66 | 67 | def sizeHint(self): 68 | hint = QLabel.sizeHint(self) 69 | width, height = hint.width(), hint.height() 70 | angle = self.angle * pi / 180 71 | rotated_width = int(abs(width * cos(angle)) + abs(height * sin(angle))) 72 | rotated_height = int(abs(width * sin(angle)) + abs(height * cos(angle))) 73 | return QSize(rotated_width, rotated_height) 74 | 75 | def minimumSizeHint(self): 76 | return self.sizeHint() 77 | -------------------------------------------------------------------------------- /guidata/widgets/texteditor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see spyder/__init__.py for details) 6 | 7 | # ruff: noqa 8 | 9 | """ 10 | guidata.widgets.texteditor 11 | ========================== 12 | 13 | This package provides a text editor widget based on QtGui.QPlainTextEdit. 14 | 15 | .. autoclass:: TextEditor 16 | :show-inheritance: 17 | :members: 18 | 19 | """ 20 | 21 | 22 | from qtpy.QtCore import Qt, Slot 23 | from qtpy.QtWidgets import QDialog, QHBoxLayout, QPushButton, QTextEdit, QVBoxLayout 24 | 25 | from guidata.config import CONF, _ 26 | from guidata.configtools import get_font, get_icon 27 | from guidata.qthelpers import win32_fix_title_bar_background 28 | 29 | 30 | class TextEditor(QDialog): 31 | """Array Editor Dialog""" 32 | 33 | def __init__( 34 | self, text, title="", font=None, parent=None, readonly=False, size=(400, 300) 35 | ): 36 | QDialog.__init__(self, parent) 37 | 38 | win32_fix_title_bar_background(self) 39 | 40 | # Destroying the C++ object right after closing the dialog box, 41 | # otherwise it may be garbage-collected in another QThread 42 | # (e.g. the editor's analysis thread in Spyder), thus leading to 43 | # a segmentation fault on UNIX or an application crash on Windows 44 | self.setAttribute(Qt.WA_DeleteOnClose) 45 | 46 | self.text = None 47 | self.btn_save_and_close = None 48 | 49 | # Display text as unicode if it comes as bytes, so users see 50 | # its right representation 51 | if isinstance(text, bytes): 52 | self.is_binary = True 53 | text = str(text, "utf8") 54 | else: 55 | self.is_binary = False 56 | 57 | self.layout = QVBoxLayout() 58 | self.setLayout(self.layout) 59 | 60 | # Text edit 61 | self.edit = QTextEdit(parent) 62 | self.edit.setReadOnly(readonly) 63 | self.edit.textChanged.connect(self.text_changed) 64 | self.edit.setPlainText(text) 65 | if font is None: 66 | font = get_font(CONF, "texteditor") 67 | self.edit.setFont(font) 68 | self.layout.addWidget(self.edit) 69 | 70 | # Buttons configuration 71 | btn_layout = QHBoxLayout() 72 | btn_layout.addStretch() 73 | if not readonly: 74 | self.btn_save_and_close = QPushButton(_("Save and Close")) 75 | self.btn_save_and_close.setDisabled(True) 76 | self.btn_save_and_close.clicked.connect(self.accept) 77 | btn_layout.addWidget(self.btn_save_and_close) 78 | 79 | self.btn_close = QPushButton(_("Close")) 80 | self.btn_close.setAutoDefault(True) 81 | self.btn_close.setDefault(True) 82 | self.btn_close.clicked.connect(self.reject) 83 | btn_layout.addWidget(self.btn_close) 84 | 85 | self.layout.addLayout(btn_layout) 86 | 87 | # Make the dialog act as a window 88 | self.setWindowFlags(Qt.Window) 89 | 90 | self.setWindowIcon(get_icon("edit.png")) 91 | self.setWindowTitle( 92 | _("Text editor") + "%s" % (" - " + str(title) if str(title) else "") 93 | ) 94 | self.resize(size[0], size[1]) 95 | 96 | @Slot() 97 | def text_changed(self): 98 | """Text has changed""" 99 | # Save text as bytes, if it was initially bytes 100 | if self.is_binary: 101 | self.text = bytes(self.edit.toPlainText(), "utf8") 102 | else: 103 | self.text = str(self.edit.toPlainText()) 104 | if self.btn_save_and_close: 105 | self.btn_save_and_close.setEnabled(True) 106 | self.btn_save_and_close.setAutoDefault(True) 107 | self.btn_save_and_close.setDefault(True) 108 | 109 | def get_value(self): 110 | """Return modified text""" 111 | # It is import to avoid accessing Qt C++ object as it has probably 112 | # already been destroyed, due to the Qt.WA_DeleteOnClose attribute 113 | return self.text 114 | 115 | def setup_and_check(self, value): 116 | """Verify if TextEditor is able to display strings passed to it.""" 117 | if isinstance(value, str): 118 | return True 119 | try: 120 | str(value, "utf8") 121 | return True 122 | except: 123 | return False 124 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # guidata setup configuration file 2 | 3 | # Important note: 4 | # Requirements are parsed by utils\genreqs.py to generate documentation 5 | 6 | [build-system] 7 | requires = ["setuptools"] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [project] 11 | name = "guidata" 12 | authors = [{ name = "Codra", email = "p.raybaut@codra.fr" }] 13 | description = "Automatic GUI generation for easy dataset editing and display" 14 | readme = "README.md" 15 | license = { file = "LICENSE" } 16 | classifiers = [ 17 | "Development Status :: 5 - Production/Stable", 18 | "Intended Audience :: Developers", 19 | "Intended Audience :: Education", 20 | "Intended Audience :: Science/Research", 21 | "License :: OSI Approved :: BSD License", 22 | "Operating System :: MacOS :: MacOS X", 23 | "Operating System :: Microsoft :: Windows :: Windows 7", 24 | "Operating System :: Microsoft :: Windows :: Windows 8", 25 | "Operating System :: Microsoft :: Windows :: Windows 10", 26 | "Operating System :: Microsoft :: Windows :: Windows 11", 27 | "Operating System :: POSIX :: Linux", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | "Programming Language :: Python :: 3.13", 33 | "Topic :: Scientific/Engineering", 34 | "Topic :: Scientific/Engineering :: Human Machine Interfaces", 35 | "Topic :: Software Development :: Libraries :: Python Modules", 36 | "Topic :: Software Development :: User Interfaces", 37 | "Topic :: Software Development :: Widget Sets", 38 | "Topic :: Utilities", 39 | ] 40 | requires-python = ">=3.9, <4" 41 | dependencies = ["h5py>=3.1", "NumPy>=1.19", "QtPy>=1.9", "requests", "tomli"] 42 | dynamic = ["version"] 43 | 44 | [project.urls] 45 | Homepage = "https://github.com/PlotPyStack/guidata/" 46 | Documentation = "https://guidata.readthedocs.io/en/latest/" 47 | 48 | [project.gui-scripts] 49 | guidata-tests = "guidata.tests:run" 50 | 51 | [project.optional-dependencies] 52 | dev = ["ruff", "pylint", "Coverage"] 53 | doc = [ 54 | "PyQt5", 55 | "pillow", 56 | "pandas", 57 | "sphinx>6", 58 | "myst_parser", 59 | "sphinx-copybutton", 60 | "sphinx_qt_documentation", 61 | "python-docs-theme", 62 | ] 63 | test = ["pytest", "pytest-xvfb"] 64 | 65 | [tool.setuptools.packages.find] 66 | include = ["guidata*"] 67 | 68 | [tool.setuptools.package-data] 69 | "*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml"] 70 | 71 | [tool.setuptools.dynamic] 72 | version = { attr = "guidata.__version__" } 73 | 74 | [tool.pytest.ini_options] 75 | addopts = "guidata" 76 | 77 | [tool.ruff] 78 | exclude = [".git", ".vscode", "build", "dist", "guidata/external"] 79 | line-length = 88 # Same as Black. 80 | indent-width = 4 # Same as Black. 81 | target-version = "py39" # Assume Python 3.9. 82 | 83 | [tool.ruff.lint] 84 | # all rules can be found here: https://beta.ruff.rs/docs/rules/ 85 | select = ["E", "F", "W", "I", "NPY201"] 86 | ignore = [ 87 | "E203", # space before : (needed for how black formats slicing) 88 | ] 89 | 90 | [tool.ruff.format] 91 | quote-style = "double" # Like Black, use double quotes for strings. 92 | indent-style = "space" # Like Black, indent with spaces, rather than tabs. 93 | skip-magic-trailing-comma = false # Like Black, respect magic trailing commas. 94 | line-ending = "auto" # Like Black, automatically detect the appropriate line ending. 95 | 96 | [tool.ruff.lint.per-file-ignores] 97 | "doc/*" = ["E402"] 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Coverage 2 | NumPy>=1.21 3 | PyQt5 4 | QtPy>=1.9 5 | h5py>=3.0 6 | pandas 7 | pillow 8 | pylint 9 | ruff 10 | pytest 11 | pytest-xvfb 12 | python-docs-theme 13 | requests 14 | sphinx 15 | myst_parser 16 | sphinx-copybutton 17 | sphinx_qt_documentation 18 | tomli -------------------------------------------------------------------------------- /scripts/build_dist.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was copied from PythonQwt project 3 | REM ====================================================== 4 | REM Package build script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | call %~dp0utils GetScriptPath SCRIPTPATH 11 | call %FUNC% GetLibName LIBNAME 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | call %FUNC% UsePython 15 | call %FUNC% GetVersion VERSION 16 | 17 | set REPODIR=%SCRIPTPATH%\.. 18 | 19 | @REM Clone repository in a temporary directory 20 | set CLONEDIR=%REPODIR%\..\%LIBNAME%-tempdir 21 | if exist %CLONEDIR% ( rmdir /s /q %CLONEDIR% ) 22 | git clone -l -s . %CLONEDIR% 23 | 24 | @REM Build distribution files 25 | pushd %CLONEDIR% 26 | %PYTHON% -m build 27 | popd 28 | 29 | @REM Copy distribution files to the repository 30 | set DISTDIR=%REPODIR%\dist 31 | if not exist %DISTDIR% ( mkdir %DISTDIR% ) 32 | copy /y %CLONEDIR%\dist\%MODNAME%-%VERSION%*.whl %DISTDIR% 33 | copy /y %CLONEDIR%\dist\%MODNAME%-%VERSION%*.tar.gz %DISTDIR% 34 | 35 | @REM Clean up 36 | rmdir /s /q %CLONEDIR% 37 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/build_doc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was copied from PythonQwt project 3 | REM ====================================================== 4 | REM Documentation build script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetLibName LIBNAME 13 | call %FUNC% GetModName MODNAME 14 | call %FUNC% SetPythonPath 15 | call %FUNC% UsePython 16 | cd %SCRIPTPATH%\.. 17 | %PYTHON% doc\update_requirements.py 18 | sphinx-build -b html doc build\doc 19 | start build\doc\index.html 20 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/clean_up.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was copied from PythonQwt project 3 | REM ====================================================== 4 | REM Clean up repository 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | call %~dp0utils GetScriptPath SCRIPTPATH 11 | call %FUNC% GetLibName LIBNAME 12 | call %FUNC% GetModName MODNAME 13 | cd %SCRIPTPATH%\..\ 14 | 15 | @REM Removing files/directories related to Python/doc build process 16 | if exist %LIBNAME%.egg-info ( rmdir /s /q %LIBNAME%.egg-info ) 17 | if exist %MODNAME%.egg-info ( rmdir /s /q %MODNAME%.egg-info ) 18 | if exist MANIFEST ( del /q MANIFEST ) 19 | if exist build ( rmdir /s /q build ) 20 | if exist dist ( rmdir /s /q dist ) 21 | if exist doc\_build ( rmdir /s /q doc\_build ) 22 | 23 | @REM Removing cache files/directories related to Python execution 24 | del /s /q *.pyc 1>nul 2>&1 25 | del /s /q *.pyo 1>nul 2>&1 26 | FOR /d /r %%d IN ("__pycache__") DO @IF EXIST "%%d" rd /s /q "%%d" 27 | 28 | @REM Removing directories related to public repository upload 29 | set TEMP=%SCRIPTPATH%\..\..\%LIBNAME%_temp 30 | set PUBLIC=%SCRIPTPATH%\..\..\%LIBNAME%_public 31 | if exist %TEMP% ( rmdir /s /q %TEMP% ) 32 | if exist %PUBLIC% ( rmdir /s /q %PUBLIC% ) 33 | 34 | @REM Removing files/directories related to Coverage 35 | if exist .coverage ( del /q .coverage ) 36 | if exist coverage.xml ( del /q coverage.xml ) 37 | if exist htmlcov ( rmdir /s /q htmlcov ) 38 | del /q .coverage.* 1>nul 2>&1 39 | if exist sitecustomize.py ( del /q sitecustomize.py ) 40 | -------------------------------------------------------------------------------- /scripts/gettext.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Run gettext translation tool 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% SetPythonPath 13 | call %FUNC% UsePython 14 | call %FUNC% GetModName MODNAME 15 | %PYTHON% -c "from guidata.utils.gettext_helpers import do_%1; do_%1('%MODNAME%')" 16 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/run_coverage.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Run coverage tests 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | call %FUNC% UsePython 15 | set COVERAGE_PROCESS_START=%SCRIPTPATH%\..\.coveragerc 16 | coverage run -m pytest 17 | coverage combine 18 | coverage html 19 | start .\htmlcov\index.html 20 | call %FUNC% EndOfScript 21 | -------------------------------------------------------------------------------- /scripts/run_pylint.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Run pylint analysis 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | set PYLINT_ARG=%* 15 | if "%PYLINT_ARG%"=="" set PYLINT_ARG=--disable=fixme 16 | %PYTHON% -m pylint --rcfile=%SCRIPTPATH%\..\.pylintrc %PYLINT_ARG% %MODNAME% 17 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/run_pytest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Run pytest script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal enabledelayedexpansion 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | 15 | :: Iterate over all directories in the grandparent directory 16 | :: (WinPython base directories) 17 | call %FUNC% GetPythonExeGrandParentDir DIR0 18 | for /D %%d in ("%DIR0%*") do ( 19 | set WINPYDIRBASE=%%d 20 | call !WINPYDIRBASE!\scripts\env.bat 21 | echo Running pytest from "%%d": 22 | pytest --ff -q %MODNAME% 23 | echo ---- 24 | ) 25 | call %FUNC% EndOfScript 26 | -------------------------------------------------------------------------------- /scripts/run_ruff.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Run Ruff analysis 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | call %FUNC% UsePython 15 | ruff check 16 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/run_test_launcher.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Test launcher script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% SetPythonPath 13 | call %FUNC% UsePython 14 | call %FUNC% GetModName MODNAME 15 | python -m %MODNAME%.tests.__init__ 16 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/upgrade_env.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was derived from PythonQwt project 3 | REM ====================================================== 4 | REM Upgrade environment 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | cd %SCRIPTPATH%\.. 13 | %PYTHON% -m pip install --upgrade -r dev\requirements.txt 14 | %PYTHON% -m pip list > dev\pip_list.txt 15 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /scripts/utils.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set FUNC=%0 3 | call:%* 4 | goto Exit 5 | 6 | REM ====================================================== 7 | REM Utilities for deployment, test and build scripts 8 | REM ====================================================== 9 | REM Licensed under the terms of the MIT License 10 | REM Copyright (c) 2020 Pierre Raybaut 11 | REM (see LICENSE file for more details) 12 | REM ====================================================== 13 | 14 | :GetScriptPath 15 | set _tmp_=%~dp0 16 | if %_tmp_:~-1%==\ set %1=%_tmp_:~0,-1% 17 | EXIT /B 0 18 | 19 | :GetLibName 20 | pushd %~dp0.. 21 | for %%I in (.) do set %1=%%~nxI 22 | popd 23 | goto:eof 24 | 25 | :GetModName 26 | pushd %~dp0.. 27 | for /D %%I in (*) DO ( 28 | if exist %%I\__init__.py ( 29 | set %1=%%I 30 | goto :found_module 31 | ) 32 | ) 33 | :found_module 34 | popd 35 | goto:eof 36 | 37 | :GetVersion 38 | call:GetModName MODNAME 39 | call:SetPythonPath 40 | echo import %MODNAME%;print(%MODNAME%.__version__) | python > _tmp_.txt 41 | set /p %1=<_tmp_.txt 42 | del _tmp_.txt 43 | goto:eof 44 | 45 | :GetVersionWithoutAlphaBeta 46 | call:GetModName MODNAME 47 | call:SetPythonPath 48 | echo import %MODNAME%;ver=%MODNAME%.__version__;print(ver.split("b")[0] if "b" in ver else ver.split("a")[0] if "a" in ver else ver) | python > _tmp_.txt 49 | set /p %1=<_tmp_.txt 50 | del _tmp_.txt 51 | goto:eof 52 | 53 | :SetPythonPath 54 | set ORIGINAL_PYTHONPATH=%PYTHONPATH% 55 | cd %~dp0.. 56 | for /F "tokens=*" %%A in (.env) do ( 57 | set %%A 58 | ) 59 | set PYTHONPATH=%PYTHONPATH%;%ORIGINAL_PYTHONPATH% 60 | goto:eof 61 | 62 | :GetPythonExeGrandParentDir 63 | for %%i in (%PYTHON%) do set DIR2=%%~dpi 64 | set DIR2=%DIR2:~0,-1% 65 | for %%j in (%DIR2%) do set DIR1=%%~dpj 66 | set DIR1=%DIR1:~0,-1% 67 | for %%k in (%DIR1%) do set %1=%%~dpk 68 | goto:eof 69 | 70 | :UsePython 71 | if defined WINPYVER (goto:eof) 72 | if not defined PYTHON (goto :nopython) 73 | for %%a in ("%PYTHON%") do set "p_dir=%%~dpa" 74 | if exist "%p_dir%\activate.bat" (goto :venvpython) 75 | for %%a in (%p_dir:~0,-1%) do set "WINPYDIRBASE=%%~dpa" 76 | if exist "%WINPYDIRBASE%\scripts\env.bat" (goto :nopython) 77 | goto :python 78 | :venvpython 79 | call "%p_dir%\activate.bat" 80 | call :ShowTitle "Using Python Virtual Environment from %p_dir%" 81 | goto:eof 82 | :python 83 | set PATH=%p_dir%;%PATH% 84 | call :ShowTitle "Using Python from %p_dir%" 85 | goto:eof 86 | :nopython 87 | if defined WINPYDIRBASE ( 88 | call %WINPYDIRBASE%\scripts\env.bat 89 | call :ShowTitle "Using WinPython from %WINPYDIRBASE%" 90 | ) else ( 91 | echo Warning: WINPYDIRBASE environment variable is not defined, switching to system Python 92 | echo ******** 93 | echo (if nothing happens, that's probably because Python is not installed either: 94 | echo please set the WINPYDIRBASE variable to select WinPython directory, or install Python) 95 | ) 96 | goto:eof 97 | 98 | :ShowTitle 99 | @echo: 100 | @echo ========= %~1 ========= 101 | @echo: 102 | goto:eof 103 | 104 | :EndOfScript 105 | @echo: 106 | @echo ********************************************************************************** 107 | @echo: 108 | if not defined UNATTENDED ( 109 | @echo End of script 110 | pause 111 | ) 112 | goto:eof 113 | 114 | :Exit 115 | exit /b --------------------------------------------------------------------------------