PyMieSimX 0.1.0__tar.gz → 0.1.3__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.0 → pymiesimx-0.1.3}/.coveragerc +1 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.github/workflows/deploy_PyPi.yml +5 -7
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.github/workflows/deploy_anaconda.yml +8 -9
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.github/workflows/deploy_coverage.yml +5 -2
- pymiesimx-0.1.3/.github/workflows/quality.yml +47 -0
- pymiesimx-0.1.3/.github/workflows/tests.yml +22 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.gitignore +7 -0
- pymiesimx-0.1.3/CONTRIBUTING.md +7 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/Dockerfile +2 -1
- pymiesimx-0.1.3/PKG-INFO +153 -0
- pymiesimx-0.1.3/PyMieSimX/__init__.py +58 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/__main__.py +19 -5
- pymiesimx-0.1.3/PyMieSimX/api.py +39 -0
- pymiesimx-0.1.3/PyMieSimX/application/__init__.py +1 -0
- pymiesimx-0.1.3/PyMieSimX/application/wsgi.py +9 -0
- pymiesimx-0.1.3/PyMieSimX/gui/__init__.py +12 -0
- pymiesimx-0.1.3/PyMieSimX/gui/admin.py +122 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/dashboard.css +572 -28
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/default-settings.json +3 -3
- pymiesimx-0.1.3/PyMieSimX/gui/assets/home-spf-radial.gif +0 -0
- pymiesimx-0.1.3/PyMieSimX/gui/assets/home-spf-radial.png +0 -0
- pymiesimx-0.1.3/PyMieSimX/gui/assets/materials-stock.json +142 -0
- pymiesimx-0.1.3/PyMieSimX/gui/callback_helpers.py +129 -0
- pymiesimx-0.1.0/PyMieSimX/gui/interface.py → pymiesimx-0.1.3/PyMieSimX/gui/callbacks.py +314 -157
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/components/cards.py +0 -1
- pymiesimx-0.1.0/PyMieSimX/gui/services.py → pymiesimx-0.1.3/PyMieSimX/gui/computation.py +396 -26
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/defaults.py +0 -1
- pymiesimx-0.1.3/PyMieSimX/gui/experiment_service.py +23 -0
- pymiesimx-0.1.3/PyMieSimX/gui/interface.py +105 -0
- pymiesimx-0.1.3/PyMieSimX/gui/jobs.py +118 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/layout.py +164 -7
- pymiesimx-0.1.3/PyMieSimX/gui/material_catalog.py +127 -0
- pymiesimx-0.1.3/PyMieSimX/gui/models.py +64 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/__init__.py +3 -1
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/documentation.py +3 -60
- pymiesimx-0.1.3/PyMieSimX/gui/pages/experiment/page.py +62 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/experiment/sections/configuration.py +47 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/field_syntax.py +77 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/home.py +175 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/install_local.py +7 -4
- pymiesimx-0.1.3/PyMieSimX/gui/pages/sellmeier.py +169 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/settings.py +0 -1
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/page.py +57 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/sections/representations.py +37 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/sections/setup.py +31 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/parsing.py +44 -7
- pymiesimx-0.1.3/PyMieSimX/gui/plotting.py +19 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/schemas.py +7 -7
- pymiesimx-0.1.3/PyMieSimX/gui/services.py +17 -0
- pymiesimx-0.1.3/PyMieSimX/gui/single_service.py +14 -0
- pymiesimx-0.1.3/PyMieSimX/gui/usage_metrics.py +173 -0
- pymiesimx-0.1.3/PyMieSimX/gui/validation.py +18 -0
- pymiesimx-0.1.3/PyMieSimX.egg-info/PKG-INFO +153 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX.egg-info/SOURCES.txt +34 -2
- pymiesimx-0.1.3/PyMieSimX.egg-info/requires.txt +21 -0
- pymiesimx-0.1.3/PyMieSimX.egg-info/scm_file_list.json +85 -0
- pymiesimx-0.1.3/PyMieSimX.egg-info/scm_version.json +8 -0
- pymiesimx-0.1.3/README.rst +123 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3/conda.recipe}/meta.yaml +8 -6
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/makefile +14 -2
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/pyproject.toml +20 -9
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/pytest.ini +2 -0
- pymiesimx-0.1.3/tests/browser/test_dashboard.py +28 -0
- pymiesimx-0.1.3/tests/gui/cli.py +57 -0
- pymiesimx-0.1.3/tests/gui/test_admin.py +77 -0
- pymiesimx-0.1.3/tests/gui/test_api.py +59 -0
- pymiesimx-0.1.3/tests/gui/test_dash_callbacks.py +161 -0
- pymiesimx-0.1.3/tests/gui/test_services.py +240 -0
- pymiesimx-0.1.3/tests/gui/test_usage_metrics.py +31 -0
- pymiesimx-0.1.3/tools/generate_home_spf.py +100 -0
- pymiesimx-0.1.3/tools/next_release_version.py +24 -0
- pymiesimx-0.1.3/tools/release_tag.py +27 -0
- pymiesimx-0.1.0/.github/workflows/quality.yml +0 -30
- pymiesimx-0.1.0/PKG-INFO +0 -90
- pymiesimx-0.1.0/PyMieSimX/__init__.py +0 -5
- pymiesimx-0.1.0/PyMieSimX/gui/__init__.py +0 -5
- pymiesimx-0.1.0/PyMieSimX/gui/pages/experiment/page.py +0 -49
- pymiesimx-0.1.0/PyMieSimX/gui/pages/experiment/sections/configuration.py +0 -42
- pymiesimx-0.1.0/PyMieSimX/gui/pages/home.py +0 -152
- pymiesimx-0.1.0/PyMieSimX/gui/pages/single/page.py +0 -28
- pymiesimx-0.1.0/PyMieSimX/gui/pages/single/sections/representations.py +0 -38
- pymiesimx-0.1.0/PyMieSimX/gui/pages/single/sections/setup.py +0 -27
- pymiesimx-0.1.0/PyMieSimX.egg-info/PKG-INFO +0 -90
- pymiesimx-0.1.0/PyMieSimX.egg-info/requires.txt +0 -16
- pymiesimx-0.1.0/README.rst +0 -43
- pymiesimx-0.1.0/tests/gui/cli.py +0 -31
- pymiesimx-0.1.0/tests/gui/test_services.py +0 -87
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.flake8 +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.github/dependabot.yml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/.pre-commit-config.yaml +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/LICENSE +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/card-state.js +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/logo.png +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-favicon.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-logo-dark.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-logo.svg +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/components/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/citation.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/experiment/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/experiment/sections/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/single/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/single/sections/__init__.py +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX.egg-info/dependency_links.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX.egg-info/entry_points.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/PyMieSimX.egg-info/top_level.txt +0 -0
- {pymiesimx-0.1.0 → pymiesimx-0.1.3}/setup.cfg +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"]
|
pymiesimx-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyMieSimX
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Graphical Dash interface for PyMieSim.
|
|
5
|
+
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/MartinPdeS/PyMieSimX
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/x-rst
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: PyMieSim<6,>=5.6.4
|
|
12
|
+
Requires-Dist: PyOptik<4,>=3.2.1
|
|
13
|
+
Requires-Dist: dash<5,>=4.1.0
|
|
14
|
+
Requires-Dist: gunicorn<24,>=23.0.0
|
|
15
|
+
Requires-Dist: numpy<3,>=2.0
|
|
16
|
+
Requires-Dist: pandas<3,>=2.2
|
|
17
|
+
Requires-Dist: plotly<7,>=5.24
|
|
18
|
+
Requires-Dist: psycopg[binary]<4,>=3.2.9
|
|
19
|
+
Provides-Extra: testing
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == "testing"
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0; extra == "testing"
|
|
22
|
+
Requires-Dist: coverage>=7.0; extra == "testing"
|
|
23
|
+
Provides-Extra: browser
|
|
24
|
+
Requires-Dist: dash[testing]<5,>=4.1; extra == "browser"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff==0.11.2; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
PyMieSimX
|
|
32
|
+
=========
|
|
33
|
+
|
|
34
|
+
PyMieSimX is the standalone graphical interface for `PyMieSim
|
|
35
|
+
<https://github.com/MartinPdeS/PyMieSim>`_. It provides a Dash application for
|
|
36
|
+
configuring optical setups, running parameter sweeps, exploring individual
|
|
37
|
+
particles, and exporting results.
|
|
38
|
+
|
|
39
|
+
Source-model note
|
|
40
|
+
-----------------
|
|
41
|
+
|
|
42
|
+
The Gaussian source exposed by PyMieSimX is not a generalized Lorenz--Mie
|
|
43
|
+
theory (GLMT) implementation. It is a convenience object for defining a
|
|
44
|
+
Gaussian illumination through its numerical aperture and optical power in
|
|
45
|
+
watts. The ``Gaussian`` and ``GaussianSet`` source options should therefore be
|
|
46
|
+
interpreted as a practical source parameterization, not as a separate GLMT
|
|
47
|
+
solver.
|
|
48
|
+
|
|
49
|
+
Installation
|
|
50
|
+
------------
|
|
51
|
+
|
|
52
|
+
Install the GUI with PyMieSim and PyOptik with::
|
|
53
|
+
|
|
54
|
+
pip install PyMieSimX
|
|
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
|
+
|
|
61
|
+
Launch the dashboard with::
|
|
62
|
+
|
|
63
|
+
pymiesimx
|
|
64
|
+
|
|
65
|
+
Use ``pymiesimx --help`` to see the available host, port, browser, and debug
|
|
66
|
+
options.
|
|
67
|
+
|
|
68
|
+
Development
|
|
69
|
+
-----------
|
|
70
|
+
|
|
71
|
+
Install an editable checkout with::
|
|
72
|
+
|
|
73
|
+
pip install -e .
|
|
74
|
+
|
|
75
|
+
The GUI source lives in ``PyMieSimX/gui``. Scientific calculations are provided
|
|
76
|
+
by the installed ``PyMieSim`` package rather than duplicated here.
|
|
77
|
+
|
|
78
|
+
Testing and automation
|
|
79
|
+
----------------------
|
|
80
|
+
|
|
81
|
+
Run the GUI test suite with::
|
|
82
|
+
|
|
83
|
+
pip install -e ".[testing]"
|
|
84
|
+
python -m pytest
|
|
85
|
+
|
|
86
|
+
GitHub Actions includes quality checks, GUI tests, PyPI publishing, Conda
|
|
87
|
+
recipe publishing, and coverage deployment. The Conda recipe is in
|
|
88
|
+
``meta.yaml`` and the container entry point is defined in ``Dockerfile``.
|
|
89
|
+
|
|
90
|
+
Python API
|
|
91
|
+
----------
|
|
92
|
+
|
|
93
|
+
The computational API can be used without constructing the Dash application::
|
|
94
|
+
|
|
95
|
+
from PyMieSimX import run_experiment
|
|
96
|
+
|
|
97
|
+
result = run_experiment(
|
|
98
|
+
source_type="GaussianSet",
|
|
99
|
+
source_values={
|
|
100
|
+
"wavelength": "650",
|
|
101
|
+
"polarization": "0",
|
|
102
|
+
"optical_power": "1e-3",
|
|
103
|
+
"numerical_aperture": "0.2",
|
|
104
|
+
},
|
|
105
|
+
scatterer_type="SphereSet",
|
|
106
|
+
scatterer_values={"diameter": "500", "material": "1.4", "medium": "1.0"},
|
|
107
|
+
detector_type="None",
|
|
108
|
+
detector_values={},
|
|
109
|
+
measure="Qsca",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
``PyMieSimX.create_dash_app`` and ``PyMieSimX.OpticalSetupGUI`` remain
|
|
113
|
+
available for applications that need the graphical interface.
|
|
114
|
+
|
|
115
|
+
Background calculations and limits
|
|
116
|
+
-----------------------------------
|
|
117
|
+
|
|
118
|
+
Parameter sweeps are submitted to a bounded background worker and reported to
|
|
119
|
+
the dashboard through a polling status indicator. Results are guarded by both
|
|
120
|
+
an in-memory dataframe limit and a serialized Dash-payload limit. Large-result
|
|
121
|
+
warnings are logged before execution; oversized results are rejected with an
|
|
122
|
+
actionable message asking you to reduce the sweep.
|
|
123
|
+
|
|
124
|
+
Run the command-line launcher with ``--debug`` to enable detailed logs. Log
|
|
125
|
+
messages use the format ``timestamp | level | logger | message`` and include
|
|
126
|
+
experiment job identifiers, input configuration, queue transitions, dataframe
|
|
127
|
+
memory usage, and serialized result sizes.
|
|
128
|
+
|
|
129
|
+
Server-side usage metrics
|
|
130
|
+
-------------------------
|
|
131
|
+
|
|
132
|
+
The dashboard can share a PostgreSQL metrics database with RosettaX. Configure
|
|
133
|
+
the Render service with::
|
|
134
|
+
|
|
135
|
+
PYMIESIMX_USAGE_METRICS_BACKEND=postgres
|
|
136
|
+
DATABASE_URL=<the shared PostgreSQL URL>
|
|
137
|
+
|
|
138
|
+
PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
139
|
+
``pymiesimx_home_page_visit_count``, ``pymiesimx_experiment_run_count``, and
|
|
140
|
+
``pymiesimx_single_run_count``. Metrics are collected only when the PostgreSQL
|
|
141
|
+
backend is explicitly configured and available. Local installations do not
|
|
142
|
+
persist usage counters.
|
|
143
|
+
|
|
144
|
+
Administration page
|
|
145
|
+
-------------------
|
|
146
|
+
|
|
147
|
+
Usage counters are shown only on the hidden, token-protected administration
|
|
148
|
+
page. Configure a server-side token with::
|
|
149
|
+
|
|
150
|
+
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
151
|
+
|
|
152
|
+
Then open ``/admin?token=<the same value>``. The page is intentionally absent
|
|
153
|
+
from the public sidebar and refreshes its counters every 30 seconds.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Python API and optional Dash graphical interface for PyMieSim."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from PyMieSimX.api import (
|
|
6
|
+
MAX_SWEEP_COMBINATIONS,
|
|
7
|
+
MAX_RESULT_FRAME_BYTES,
|
|
8
|
+
MAX_RESULT_PAYLOAD_BYTES,
|
|
9
|
+
ExperimentValidationError,
|
|
10
|
+
ResultSizeEstimate,
|
|
11
|
+
ResultSizeLimitError,
|
|
12
|
+
ValidationIssue,
|
|
13
|
+
available_measures,
|
|
14
|
+
build_figure,
|
|
15
|
+
build_single_figure,
|
|
16
|
+
estimate_sweep_size,
|
|
17
|
+
export_result_to_csv,
|
|
18
|
+
export_single_result_to_csv,
|
|
19
|
+
estimate_result_size,
|
|
20
|
+
run_experiment,
|
|
21
|
+
validate_experiment_inputs,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
__version__ = version("PyMieSimX")
|
|
26
|
+
except PackageNotFoundError:
|
|
27
|
+
__version__ = "0+unknown"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"MAX_SWEEP_COMBINATIONS",
|
|
31
|
+
"MAX_RESULT_FRAME_BYTES",
|
|
32
|
+
"MAX_RESULT_PAYLOAD_BYTES",
|
|
33
|
+
"ExperimentValidationError",
|
|
34
|
+
"ResultSizeEstimate",
|
|
35
|
+
"ResultSizeLimitError",
|
|
36
|
+
"ValidationIssue",
|
|
37
|
+
"available_measures",
|
|
38
|
+
"build_figure",
|
|
39
|
+
"build_single_figure",
|
|
40
|
+
"estimate_sweep_size",
|
|
41
|
+
"export_result_to_csv",
|
|
42
|
+
"export_single_result_to_csv",
|
|
43
|
+
"estimate_result_size",
|
|
44
|
+
"run_experiment",
|
|
45
|
+
"validate_experiment_inputs",
|
|
46
|
+
"__version__",
|
|
47
|
+
"OpticalSetupGUI",
|
|
48
|
+
"create_dash_app",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def __getattr__(name: str):
|
|
53
|
+
"""Load the optional Dash interface only when it is explicitly requested."""
|
|
54
|
+
if name in {"OpticalSetupGUI", "create_dash_app"}:
|
|
55
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI, create_dash_app
|
|
56
|
+
|
|
57
|
+
return {"OpticalSetupGUI": OpticalSetupGUI, "create_dash_app": create_dash_app}[name]
|
|
58
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import logging
|
|
5
|
+
import sys
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
@@ -16,13 +18,25 @@ def _build_argument_parser() -> argparse.ArgumentParser:
|
|
|
16
18
|
return parser
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
21
|
-
|
|
21
|
+
def configure_logging(*, debug: bool, log_level: str = "INFO") -> int:
|
|
22
|
+
"""Configure application logging using RosettaX's console format."""
|
|
23
|
+
resolved_level = logging.DEBUG if debug else getattr(logging, str(log_level).upper(), logging.INFO)
|
|
22
24
|
logging.basicConfig(
|
|
23
|
-
level=
|
|
25
|
+
level=resolved_level,
|
|
24
26
|
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
27
|
+
stream=sys.stdout,
|
|
28
|
+
force=True,
|
|
25
29
|
)
|
|
30
|
+
logging.getLogger("PyMieSimX").setLevel(resolved_level)
|
|
31
|
+
return resolved_level
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(argv: list[str] | None = None) -> None:
|
|
35
|
+
"""Launch the PyMieSimX dashboard from a console entry point."""
|
|
36
|
+
args = _build_argument_parser().parse_args(argv)
|
|
37
|
+
configure_logging(debug=args.debug)
|
|
38
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI
|
|
39
|
+
|
|
26
40
|
OpticalSetupGUI().run(
|
|
27
41
|
host=args.host,
|
|
28
42
|
port=args.port,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Stable, GUI-independent Python API for PyMieSimX computations."""
|
|
2
|
+
|
|
3
|
+
from PyMieSimX.gui.services import (
|
|
4
|
+
MAX_SWEEP_COMBINATIONS,
|
|
5
|
+
MAX_RESULT_FRAME_BYTES,
|
|
6
|
+
MAX_RESULT_PAYLOAD_BYTES,
|
|
7
|
+
ExperimentValidationError,
|
|
8
|
+
ResultSizeEstimate,
|
|
9
|
+
ResultSizeLimitError,
|
|
10
|
+
ValidationIssue,
|
|
11
|
+
available_measures,
|
|
12
|
+
build_figure,
|
|
13
|
+
build_single_figure,
|
|
14
|
+
estimate_sweep_size,
|
|
15
|
+
export_result_to_csv,
|
|
16
|
+
export_single_result_to_csv,
|
|
17
|
+
estimate_result_size,
|
|
18
|
+
run_experiment,
|
|
19
|
+
validate_experiment_inputs,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"MAX_SWEEP_COMBINATIONS",
|
|
24
|
+
"MAX_RESULT_FRAME_BYTES",
|
|
25
|
+
"MAX_RESULT_PAYLOAD_BYTES",
|
|
26
|
+
"ExperimentValidationError",
|
|
27
|
+
"ResultSizeEstimate",
|
|
28
|
+
"ResultSizeLimitError",
|
|
29
|
+
"ValidationIssue",
|
|
30
|
+
"available_measures",
|
|
31
|
+
"build_figure",
|
|
32
|
+
"build_single_figure",
|
|
33
|
+
"estimate_sweep_size",
|
|
34
|
+
"export_result_to_csv",
|
|
35
|
+
"export_single_result_to_csv",
|
|
36
|
+
"estimate_result_size",
|
|
37
|
+
"run_experiment",
|
|
38
|
+
"validate_experiment_inputs",
|
|
39
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""WSGI compatibility entry points for the PyMieSimX dashboard."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Dash dashboard components for PyMieSimX."""
|
|
2
|
+
|
|
3
|
+
__all__ = ["OpticalSetupGUI", "create_dash_app"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def __getattr__(name: str):
|
|
7
|
+
"""Load the Dash application lazily for computational-only imports."""
|
|
8
|
+
if name in __all__:
|
|
9
|
+
from PyMieSimX.gui.interface import OpticalSetupGUI, create_dash_app
|
|
10
|
+
|
|
11
|
+
return {"OpticalSetupGUI": OpticalSetupGUI, "create_dash_app": create_dash_app}[name]
|
|
12
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -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
|
+
]
|