PyMieSimX 0.1.1__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.coveragerc +1 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.github/workflows/deploy_PyPi.yml +5 -7
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.github/workflows/deploy_anaconda.yml +8 -9
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.github/workflows/deploy_coverage.yml +5 -2
- pymiesimx-0.1.4/.github/workflows/quality.yml +47 -0
- pymiesimx-0.1.4/.github/workflows/tests.yml +22 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.gitignore +7 -0
- pymiesimx-0.1.4/CONTRIBUTING.md +7 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/Dockerfile +2 -1
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PKG-INFO +46 -10
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/__main__.py +3 -0
- pymiesimx-0.1.4/PyMieSimX/gui/admin.py +122 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/dashboard.css +494 -44
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/default-settings.json +3 -3
- pymiesimx-0.1.4/PyMieSimX/gui/assets/home-spf-radial.gif +0 -0
- pymiesimx-0.1.4/PyMieSimX/gui/assets/home-spf-radial.png +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/callback_helpers.py +21 -4
- pymiesimx-0.1.1/PyMieSimX/gui/interface.py → pymiesimx-0.1.4/PyMieSimX/gui/callbacks.py +214 -181
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/components/cards.py +0 -1
- pymiesimx-0.1.1/PyMieSimX/gui/services.py → pymiesimx-0.1.4/PyMieSimX/gui/computation.py +230 -30
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/defaults.py +0 -1
- pymiesimx-0.1.4/PyMieSimX/gui/experiment_service.py +23 -0
- pymiesimx-0.1.4/PyMieSimX/gui/interface.py +105 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/jobs.py +3 -3
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/layout.py +104 -15
- pymiesimx-0.1.4/PyMieSimX/gui/material_catalog.py +127 -0
- pymiesimx-0.1.4/PyMieSimX/gui/models.py +65 -0
- pymiesimx-0.1.4/PyMieSimX/gui/pages/experiment/page.py +68 -0
- pymiesimx-0.1.4/PyMieSimX/gui/pages/experiment/sections/configuration.py +47 -0
- pymiesimx-0.1.4/PyMieSimX/gui/pages/home.py +175 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/settings.py +0 -1
- pymiesimx-0.1.4/PyMieSimX/gui/pages/single/page.py +57 -0
- pymiesimx-0.1.4/PyMieSimX/gui/pages/single/sections/representations.py +37 -0
- pymiesimx-0.1.4/PyMieSimX/gui/pages/single/sections/setup.py +31 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/parsing.py +3 -1
- pymiesimx-0.1.4/PyMieSimX/gui/plotting.py +19 -0
- pymiesimx-0.1.4/PyMieSimX/gui/services.py +17 -0
- pymiesimx-0.1.4/PyMieSimX/gui/single_service.py +14 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/usage_metrics.py +18 -70
- pymiesimx-0.1.4/PyMieSimX/gui/validation.py +19 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/PKG-INFO +46 -10
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/SOURCES.txt +20 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/requires.txt +5 -1
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/scm_file_list.json +61 -43
- pymiesimx-0.1.4/PyMieSimX.egg-info/scm_version.json +8 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/README.rst +40 -7
- {pymiesimx-0.1.1 → pymiesimx-0.1.4/conda.recipe}/meta.yaml +3 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/makefile +14 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/pyproject.toml +9 -3
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/pytest.ini +2 -0
- pymiesimx-0.1.4/tests/browser/test_dashboard.py +50 -0
- pymiesimx-0.1.4/tests/gui/cli.py +57 -0
- pymiesimx-0.1.4/tests/gui/test_admin.py +77 -0
- pymiesimx-0.1.4/tests/gui/test_dash_callbacks.py +277 -0
- pymiesimx-0.1.4/tests/gui/test_services.py +358 -0
- pymiesimx-0.1.4/tests/gui/test_usage_metrics.py +31 -0
- pymiesimx-0.1.4/tools/generate_home_spf.py +100 -0
- pymiesimx-0.1.4/tools/next_release_version.py +24 -0
- pymiesimx-0.1.4/tools/release_tag.py +27 -0
- pymiesimx-0.1.1/.github/workflows/quality.yml +0 -30
- pymiesimx-0.1.1/PyMieSimX/gui/material_catalog.py +0 -90
- pymiesimx-0.1.1/PyMieSimX/gui/pages/experiment/page.py +0 -49
- pymiesimx-0.1.1/PyMieSimX/gui/pages/experiment/sections/configuration.py +0 -42
- pymiesimx-0.1.1/PyMieSimX/gui/pages/home.py +0 -152
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/page.py +0 -28
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/sections/representations.py +0 -38
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/sections/setup.py +0 -27
- pymiesimx-0.1.1/PyMieSimX.egg-info/scm_version.json +0 -8
- pymiesimx-0.1.1/tests/gui/cli.py +0 -31
- pymiesimx-0.1.1/tests/gui/test_services.py +0 -221
- pymiesimx-0.1.1/tests/gui/test_usage_metrics.py +0 -30
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.flake8 +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.github/dependabot.yml +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/.pre-commit-config.yaml +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/LICENSE +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/api.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/application/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/application/wsgi.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/card-state.js +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/logo.png +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/materials-stock.json +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/pymiesim-favicon.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/pymiesim-logo-dark.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/assets/pymiesim-logo.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/components/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/citation.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/documentation.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/experiment/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/experiment/sections/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/field_syntax.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/install_local.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/sellmeier.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/single/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/pages/single/sections/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX/gui/schemas.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/dependency_links.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/entry_points.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/PyMieSimX.egg-info/top_level.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/setup.cfg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.4}/tests/gui/test_api.py +0 -0
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Publish to PyPI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
tags: ["*"]
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: ["master"]
|
|
5
|
+
tags: ["v*"]
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
8
|
contents: read
|
|
12
9
|
|
|
13
10
|
jobs:
|
|
14
|
-
|
|
15
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@
|
|
11
|
+
publish:
|
|
12
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@v5
|
|
16
13
|
with:
|
|
17
14
|
python-version: "3.11"
|
|
15
|
+
test-command: python -c 'import PyMieSimX'
|
|
18
16
|
secrets:
|
|
19
17
|
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Publish Conda package
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
tags: '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: [ "master" ]
|
|
5
|
+
tags: ["v*"]
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
|
-
|
|
8
|
+
contents: read
|
|
12
9
|
|
|
13
10
|
jobs:
|
|
14
|
-
|
|
15
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@
|
|
11
|
+
publish:
|
|
12
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@v5
|
|
16
13
|
with:
|
|
17
14
|
python_versions: '["3.11", "3.12", "3.13"]'
|
|
18
|
-
apt_package: ""
|
|
19
15
|
os_list: '["ubuntu-latest", "macos-latest", "windows-latest"]'
|
|
16
|
+
package_name: pymiesimx
|
|
17
|
+
recipe_path: conda.recipe
|
|
18
|
+
test_import: PyMieSimX
|
|
20
19
|
secrets:
|
|
21
20
|
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
name: Run GUI tests and publish coverage
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
20
20
|
with:
|
|
21
21
|
fetch-depth: 0
|
|
22
22
|
|
|
23
|
-
- uses: actions/setup-python@
|
|
23
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
24
24
|
with:
|
|
25
25
|
python-version: "3.11"
|
|
26
26
|
|
|
@@ -29,6 +29,9 @@ jobs:
|
|
|
29
29
|
python -m pip install --upgrade pip wheel build
|
|
30
30
|
python -m pip install -e ".[testing]"
|
|
31
31
|
|
|
32
|
+
- name: Initialize material catalog
|
|
33
|
+
run: python -m PyOptik setup --no-progress
|
|
34
|
+
|
|
32
35
|
- name: Run GUI tests with coverage
|
|
33
36
|
run: |
|
|
34
37
|
coverage erase
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Quality and GUI tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["master"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["master"]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
python-quality:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
20
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- name: Install package and quality tools
|
|
24
|
+
run: python -m pip install --upgrade pip && python -m pip install -e ".[testing,dev]"
|
|
25
|
+
- name: Initialize material catalog
|
|
26
|
+
run: python -m PyOptik setup --no-progress
|
|
27
|
+
- name: Ruff
|
|
28
|
+
run: python -m ruff check PyMieSimX tests
|
|
29
|
+
- name: Mypy
|
|
30
|
+
run: python -m mypy PyMieSimX/gui/parsing.py PyMieSimX/gui/schemas.py PyMieSimX/gui/models.py PyMieSimX/gui/jobs.py PyMieSimX/gui/callback_helpers.py PyMieSimX/gui/admin.py
|
|
31
|
+
- name: GUI tests
|
|
32
|
+
run: python -m pytest
|
|
33
|
+
|
|
34
|
+
browser-tests:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
38
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.11"
|
|
41
|
+
- name: Install package and browser test tools
|
|
42
|
+
run: python -m pip install --upgrade pip && python -m pip install -e ".[testing,browser]"
|
|
43
|
+
- name: Initialize material catalog
|
|
44
|
+
run: python -m PyOptik setup --no-progress
|
|
45
|
+
- name: Browser workflow tests
|
|
46
|
+
# The full GUI suite enforces the repository-wide coverage threshold.
|
|
47
|
+
run: python -m pytest tests/browser --config-file=pytest.ini --headless --cov=PyMieSimX --cov-report=term --cov-fail-under=0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
tests:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
17
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
- run: python -m pip install --upgrade pip && python -m pip install -e ".[testing]"
|
|
21
|
+
- run: python -m PyOptik setup --no-progress
|
|
22
|
+
- run: python -m pytest -q
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Contributing to PyMieSimX
|
|
2
|
+
|
|
3
|
+
Use Python 3.10 or newer. Install development dependencies with `python -m pip install -e ".[testing,dev]"`, then run `make quality` and `make test`.
|
|
4
|
+
|
|
5
|
+
Releases use semantic version tags. Run `make patch`, `make minor`, or `make major` from a clean `master` checkout; the command creates the release commit, tags it, and pushes both. Publishing workflows run only for `v*` tags.
|
|
6
|
+
|
|
7
|
+
The Conda recipe is at `conda.recipe/meta.yaml`. Do not commit build artefacts, virtual environments, or caches.
|
|
@@ -7,6 +7,7 @@ WORKDIR /app
|
|
|
7
7
|
COPY . .
|
|
8
8
|
|
|
9
9
|
RUN python -m pip install --upgrade pip \
|
|
10
|
-
&& python -m pip install --no-cache-dir .
|
|
10
|
+
&& python -m pip install --no-cache-dir . \
|
|
11
|
+
&& python -m PyOptik setup --no-progress
|
|
11
12
|
|
|
12
13
|
CMD ["sh", "-c", "exec gunicorn PyMieSimX.gui.interface:app --bind 0.0.0.0:${PORT:-8050} --workers 1 --threads 2 --timeout 3600"]
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyMieSimX
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Graphical Dash interface for PyMieSim.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/MartinPdeS/PyMieSimX
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/x-rst
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: PyMieSim<6,>=5.
|
|
11
|
+
Requires-Dist: PyMieSim<6,>=5.6.4
|
|
12
|
+
Requires-Dist: PyOptik<4,>=3.2.1
|
|
12
13
|
Requires-Dist: dash<5,>=4.1.0
|
|
13
14
|
Requires-Dist: gunicorn<24,>=23.0.0
|
|
14
15
|
Requires-Dist: numpy<3,>=2.0
|
|
@@ -19,6 +20,8 @@ Provides-Extra: testing
|
|
|
19
20
|
Requires-Dist: pytest>=8.0; extra == "testing"
|
|
20
21
|
Requires-Dist: pytest-cov>=5.0; extra == "testing"
|
|
21
22
|
Requires-Dist: coverage>=7.0; extra == "testing"
|
|
23
|
+
Provides-Extra: browser
|
|
24
|
+
Requires-Dist: dash[testing]<5,>=4.1; extra == "browser"
|
|
22
25
|
Provides-Extra: dev
|
|
23
26
|
Requires-Dist: build>=1.2; extra == "dev"
|
|
24
27
|
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
@@ -46,10 +49,15 @@ solver.
|
|
|
46
49
|
Installation
|
|
47
50
|
------------
|
|
48
51
|
|
|
49
|
-
Install the GUI
|
|
52
|
+
Install the GUI with PyMieSim and PyOptik with::
|
|
50
53
|
|
|
51
54
|
pip install PyMieSimX
|
|
52
55
|
|
|
56
|
+
The first ``pymiesimx`` launch downloads PyOptik's complete
|
|
57
|
+
RefractiveIndex.INFO snapshot. It can also be initialized explicitly with::
|
|
58
|
+
|
|
59
|
+
python -m PyOptik setup --no-progress
|
|
60
|
+
|
|
53
61
|
Launch the dashboard with::
|
|
54
62
|
|
|
55
63
|
pymiesimx
|
|
@@ -104,14 +112,30 @@ The computational API can be used without constructing the Dash application::
|
|
|
104
112
|
``PyMieSimX.create_dash_app`` and ``PyMieSimX.OpticalSetupGUI`` remain
|
|
105
113
|
available for applications that need the graphical interface.
|
|
106
114
|
|
|
115
|
+
Multiple measures
|
|
116
|
+
-----------------
|
|
117
|
+
|
|
118
|
+
The Parameter Sweep Measures selector accepts several quantities in one run.
|
|
119
|
+
For example, select ``Qsca``, ``Qabs``, and ``Qext`` to compare scattering,
|
|
120
|
+
absorption, and extinction efficiencies. Measures with the same units share a
|
|
121
|
+
plot; different units use separate panels with a shared X axis. CSV exports
|
|
122
|
+
contain a column for each computed measure.
|
|
123
|
+
|
|
124
|
+
The Python API accepts either a single name or a list, for example
|
|
125
|
+
``run_experiment(..., measure=["Qsca", "Qabs", "Qext"])``. Results include
|
|
126
|
+
``measures`` with every computed name; ``measure`` retains the first name for
|
|
127
|
+
compatibility with existing single-measure callers. Size validation accounts
|
|
128
|
+
for the additional result columns.
|
|
129
|
+
|
|
107
130
|
Background calculations and limits
|
|
108
131
|
-----------------------------------
|
|
109
132
|
|
|
110
133
|
Parameter sweeps are submitted to a bounded background worker and reported to
|
|
111
|
-
the dashboard
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
the dashboard with visible queued, running, completed, and error messages beside
|
|
135
|
+
Run. The completed-run card shows the number of result rows. Oversized sweeps are
|
|
136
|
+
rejected with guidance when you click Run. Results are guarded by both an
|
|
137
|
+
in-memory dataframe limit and a serialized Dash-payload limit, with estimated
|
|
138
|
+
size validation before execution and actual size checks after computation.
|
|
115
139
|
|
|
116
140
|
Run the command-line launcher with ``--debug`` to enable detailed logs. Log
|
|
117
141
|
messages use the format ``timestamp | level | logger | message`` and include
|
|
@@ -129,5 +153,17 @@ the Render service with::
|
|
|
129
153
|
|
|
130
154
|
PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
131
155
|
``pymiesimx_home_page_visit_count``, ``pymiesimx_experiment_run_count``, and
|
|
132
|
-
``pymiesimx_single_run_count``.
|
|
133
|
-
|
|
156
|
+
``pymiesimx_single_run_count``. Metrics are collected only when the PostgreSQL
|
|
157
|
+
backend is explicitly configured and available. Local installations do not
|
|
158
|
+
persist usage counters.
|
|
159
|
+
|
|
160
|
+
Administration page
|
|
161
|
+
-------------------
|
|
162
|
+
|
|
163
|
+
Usage counters are shown only on the hidden, token-protected administration
|
|
164
|
+
page. Configure a server-side token with::
|
|
165
|
+
|
|
166
|
+
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
167
|
+
|
|
168
|
+
Then open ``/admin?token=<the same value>``. The page is intentionally absent
|
|
169
|
+
from the public sidebar and refreshes its counters every 30 seconds.
|
|
@@ -5,6 +5,9 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
9
12
|
"""Create the CLI parser for the dashboard launcher."""
|
|
10
13
|
parser = argparse.ArgumentParser(description="Launch the PyMieSimX dashboard.")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Token-gated administration dashboard for server-side usage counters."""
|
|
2
|
+
|
|
3
|
+
import hmac
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from dash import dcc, html
|
|
10
|
+
|
|
11
|
+
from PyMieSimX.gui import usage_metrics
|
|
12
|
+
from PyMieSimX.gui.components import Card
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ADMIN_TOKEN_ENV_VAR = "PYMIESIMX_ADMIN_TOKEN"
|
|
16
|
+
REFRESH_INTERVAL_MILLISECONDS = 30_000
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class AdminDashboardData:
|
|
21
|
+
"""Snapshot displayed by the administration dashboard."""
|
|
22
|
+
|
|
23
|
+
metrics: usage_metrics.UsageMetrics = field(default_factory=usage_metrics.UsageMetrics)
|
|
24
|
+
generated_at: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_admin_access_granted(provided_token: Any) -> bool:
|
|
28
|
+
"""Return whether the provided token exactly matches server configuration."""
|
|
29
|
+
configured_token = os.getenv(ADMIN_TOKEN_ENV_VAR, "").strip()
|
|
30
|
+
candidate_token = str(provided_token or "").strip()
|
|
31
|
+
return bool(configured_token and candidate_token and hmac.compare_digest(configured_token, candidate_token))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def collect_admin_dashboard_data() -> AdminDashboardData:
|
|
35
|
+
"""Load the current server-side usage counters."""
|
|
36
|
+
return AdminDashboardData(
|
|
37
|
+
metrics=usage_metrics.load_usage_metrics(),
|
|
38
|
+
generated_at=f"{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def metric_text(value: object) -> str:
|
|
43
|
+
"""Format a metric for display, including unavailable local values."""
|
|
44
|
+
if isinstance(value, float) and value != value:
|
|
45
|
+
return "NaN"
|
|
46
|
+
return str(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def dashboard_values(data: AdminDashboardData) -> tuple[str, str, str, str]:
|
|
50
|
+
"""Return callback-ready metric values and update timestamp."""
|
|
51
|
+
metrics = data.metrics
|
|
52
|
+
return (
|
|
53
|
+
metric_text(metrics.home_page_visit_count),
|
|
54
|
+
metric_text(metrics.experiment_run_count),
|
|
55
|
+
metric_text(metrics.single_run_count),
|
|
56
|
+
f"Last updated: {data.generated_at}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_admin_page(token: str | None) -> html.Div:
|
|
61
|
+
"""Build the hidden dashboard or an intentionally generic denial page."""
|
|
62
|
+
if not is_admin_access_granted(token):
|
|
63
|
+
return html.Div(
|
|
64
|
+
html.Section(
|
|
65
|
+
className=Card.classes(color="blue", extra="admin-access-card"),
|
|
66
|
+
children=[
|
|
67
|
+
html.H2("Page not available"),
|
|
68
|
+
html.P("The requested page is not available on this server."),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
71
|
+
className="page-content-stack admin-page",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
values = dashboard_values(collect_admin_dashboard_data())
|
|
75
|
+
tiles = (
|
|
76
|
+
("admin-home-page-visits", values[0], "Home page visits"),
|
|
77
|
+
("admin-experiment-runs", values[1], "Parameter sweeps"),
|
|
78
|
+
("admin-single-runs", values[2], "Particle explorations"),
|
|
79
|
+
)
|
|
80
|
+
return html.Div(
|
|
81
|
+
className="page-content-stack admin-page",
|
|
82
|
+
children=[
|
|
83
|
+
dcc.Interval(id="admin-refresh-interval", interval=REFRESH_INTERVAL_MILLISECONDS, n_intervals=0),
|
|
84
|
+
html.Section(
|
|
85
|
+
className="page-hero",
|
|
86
|
+
children=[
|
|
87
|
+
html.H1("Administration"),
|
|
88
|
+
html.P("Server-side PyMieSimX usage metrics.", className="hero-text"),
|
|
89
|
+
html.Div(
|
|
90
|
+
[
|
|
91
|
+
html.Span(values[3], id="admin-last-updated"),
|
|
92
|
+
html.Button("Refresh", id="admin-refresh-button", n_clicks=0, className="run-button"),
|
|
93
|
+
],
|
|
94
|
+
className="admin-toolbar",
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
),
|
|
98
|
+
html.Div(
|
|
99
|
+
[
|
|
100
|
+
html.Section(
|
|
101
|
+
className=Card.classes(color="blue", extra="admin-metric-card"),
|
|
102
|
+
children=[
|
|
103
|
+
html.Div(value, id=value_id, className="admin-metric-value"),
|
|
104
|
+
html.Div(label, className="admin-metric-label"),
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
for value_id, value, label in tiles
|
|
108
|
+
],
|
|
109
|
+
className="admin-metric-grid",
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
__all__ = [
|
|
116
|
+
"ADMIN_TOKEN_ENV_VAR",
|
|
117
|
+
"AdminDashboardData",
|
|
118
|
+
"build_admin_page",
|
|
119
|
+
"collect_admin_dashboard_data",
|
|
120
|
+
"dashboard_values",
|
|
121
|
+
"is_admin_access_granted",
|
|
122
|
+
]
|