pyobs-gui 2.0.0.dev5__tar.gz → 2.0.0.dev6__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.
Files changed (147) hide show
  1. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.claude/skills/verify/SKILL.md +1 -1
  2. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.gitignore +5 -0
  3. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/PKG-INFO +1 -1
  4. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/__init__.py +4 -0
  5. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/__main__.py +4 -4
  6. pyobs_gui-2.0.0.dev6/pyobs_gui/_nuitka_astropy_patch.py +453 -0
  7. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/mainwindow.py +3 -3
  8. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyproject.toml +4 -1
  9. pyobs_gui-2.0.0.dev6/pysidedeploy.spec +97 -0
  10. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/specs/README.md +14 -0
  11. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/uv.lock +1 -1
  12. pyobs_gui-2.0.0.dev5/DEVELOPMENT.md +0 -155
  13. pyobs_gui-2.0.0.dev5/DEV_IAcquisition.md +0 -95
  14. pyobs_gui-2.0.0.dev5/DEV_IAutoFocus.md +0 -162
  15. pyobs_gui-2.0.0.dev5/DEV_IAutoGuiding.md +0 -156
  16. pyobs_gui-2.0.0.dev5/DEV_NavbarShortcuts.md +0 -286
  17. pyobs_gui-2.0.0.dev5/DEV_qml_evaluation.md +0 -218
  18. pyobs_gui-2.0.0.dev5/DEV_telescopewidget_layout.md +0 -199
  19. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.github/dependabot.yml +0 -0
  20. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.github/workflows/pypi.yml +0 -0
  21. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.github/workflows/pyrefly.yml +0 -0
  22. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.github/workflows/ruff.yml +0 -0
  23. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.pre-commit-config.yaml +0 -0
  24. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/.readthedocs.yml +0 -0
  25. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/CHANGELOG.rst +0 -0
  26. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/LICENSE +0 -0
  27. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/README.md +0 -0
  28. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/bin/mypyuic5 +0 -0
  29. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/Makefile +0 -0
  30. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/make.bat +0 -0
  31. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/requirements.txt +0 -0
  32. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/source/_static/pyobs.gif +0 -0
  33. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/source/conf.py +0 -0
  34. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/docs/source/index.rst +0 -0
  35. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/mypy.ini +0 -0
  36. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/accounts.py +0 -0
  37. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/acquisitionwidget.py +0 -0
  38. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/autofocuswidget.py +0 -0
  39. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/autoguidingwidget.py +0 -0
  40. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/base.py +0 -0
  41. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/camerawidget.py +0 -0
  42. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/commandinputwidget.py +0 -0
  43. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/compassmovewidget.py +0 -0
  44. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/coolingwidget.py +0 -0
  45. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/datadisplaywidget.py +0 -0
  46. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/eventswidget.py +0 -0
  47. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/filterwidget.py +0 -0
  48. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/fitsheaderswidget.py +0 -0
  49. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/focuswidget.py +0 -0
  50. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/gui.py +0 -0
  51. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/login.py +0 -0
  52. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/loginwindow.py +0 -0
  53. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/logmodel.py +0 -0
  54. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/modewidget.py +0 -0
  55. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/modifiedmixin.py +0 -0
  56. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/modulegui.py +0 -0
  57. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/__init__.py +0 -0
  58. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
  59. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
  60. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/autofocuswidget.ui +0 -0
  61. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
  62. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
  63. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
  64. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/camerawidget.ui +0 -0
  65. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/camerawidget_ui.py +0 -0
  66. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/compassmoveplugin.py +0 -0
  67. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/compassmovewidget.py +0 -0
  68. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/compassmovewidget.ui +0 -0
  69. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
  70. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/compile.sh +0 -0
  71. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/coolingwidget.ui +0 -0
  72. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
  73. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/datadisplayplugin.py +0 -0
  74. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
  75. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
  76. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/designer.sh +0 -0
  77. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/eventswidget.ui +0 -0
  78. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/eventswidget_ui.py +0 -0
  79. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/filterwidget.ui +0 -0
  80. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/filterwidget_ui.py +0 -0
  81. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
  82. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
  83. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/focuswidget.ui +0 -0
  84. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/focuswidget_ui.py +0 -0
  85. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/mainwindow.ui +0 -0
  86. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/mainwindow_ui.py +0 -0
  87. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/modewidget.ui +0 -0
  88. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/modewidget_ui.py +0 -0
  89. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
  90. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
  91. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
  92. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
  93. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
  94. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
  95. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
  96. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
  97. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
  98. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
  99. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/search-solid.svg +0 -0
  100. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
  101. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources.qrc +0 -0
  102. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/resources_rc.py +0 -0
  103. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/roofwidget.ui +0 -0
  104. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/roofwidget_ui.py +0 -0
  105. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/shellwidget.ui +0 -0
  106. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/shellwidget_ui.py +0 -0
  107. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/spectrographwidget.ui +0 -0
  108. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
  109. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/telescopewidget.ui +0 -0
  110. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
  111. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
  112. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
  113. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/temperatureswidget.ui +0 -0
  114. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
  115. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/videowidget.ui +0 -0
  116. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/videowidget_ui.py +0 -0
  117. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/weatherwidget.ui +0 -0
  118. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
  119. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/roofwidget.py +0 -0
  120. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/shellwidget.py +0 -0
  121. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/spectrographwidget.py +0 -0
  122. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/statuswidget.py +0 -0
  123. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/telescopewidget.py +0 -0
  124. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/temperaturesplotwidget.py +0 -0
  125. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/temperatureswidget.py +0 -0
  126. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/utils.py +0 -0
  127. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/videowidget.py +0 -0
  128. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/watchedlabel.py +0 -0
  129. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/pyobs_gui/weatherwidget.py +0 -0
  130. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/spike_standalone/main.py +0 -0
  131. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/spike_standalone/pysidedeploy.spec +0 -0
  132. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/acquisition.yaml +0 -0
  133. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/autofocus.yaml +0 -0
  134. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/camera.yaml +0 -0
  135. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/full.yaml +0 -0
  136. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/guiding.yaml +0 -0
  137. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/mode.yaml +0 -0
  138. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/roof.yaml +0 -0
  139. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/spectrograph.yaml +0 -0
  140. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/telescope.yaml +0 -0
  141. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/telescope_acl.yaml +0 -0
  142. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/telescope_acl_denied.yaml +0 -0
  143. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/test/video.yaml +0 -0
  144. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/tests/__init__.py +0 -0
  145. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/tests/conftest.py +0 -0
  146. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/tests/test_accounts.py +0 -0
  147. {pyobs_gui-2.0.0.dev5 → pyobs_gui-2.0.0.dev6}/tests/test_loginwindow.py +0 -0
@@ -6,7 +6,7 @@ description: Drive the real pyobs-gui app headlessly against a real pyobs module
6
6
  # Verifying pyobs-gui changes
7
7
 
8
8
  pyobs-gui is a PySide6 app that talks to pyobs modules over `Comm`. There's no
9
- pytest suite (see `DEVELOPMENT.md`) — verification means running the real
9
+ pytest suite — verification means running the real
10
10
  `GUI` module against a real target module and observing widget state /
11
11
  screenshots. This works headlessly, no Xvfb needed.
12
12
 
@@ -6,3 +6,8 @@ spike_standalone/deployment/
6
6
  spike_standalone/*.dist/
7
7
  spike_standalone/build*.log
8
8
  spike_standalone/spike_results.txt
9
+
10
+ # pyside6-deploy build output for the real app -- generated, not source
11
+ deployment/
12
+ dist/
13
+ build*.log
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-gui
3
- Version: 2.0.0.dev5
3
+ Version: 2.0.0.dev6
4
4
  Summary: A remote GUI for pyobs
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -4,6 +4,10 @@ TODO: write doc
4
4
 
5
5
  __title__ = "GUI"
6
6
 
7
+ from ._nuitka_astropy_patch import patch_generic_unit_parser
8
+
9
+ patch_generic_unit_parser()
10
+
7
11
  from .gui import GUI
8
12
  from .camerawidget import CameraWidget
9
13
  from .datadisplaywidget import DataDisplayWidget
@@ -6,13 +6,13 @@ unless the user opts into "store password" in the login window.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from pyobs.application import Application
10
9
 
11
- from .gui import GUI
12
- from .login import login_and_build_gui
10
+ def main() -> None:
11
+ from pyobs.application import Application
13
12
 
13
+ from .gui import GUI
14
+ from .login import login_and_build_gui
14
15
 
15
- def main() -> None:
16
16
  app = Application(module_factory=login_and_build_gui, loop_module_class=GUI)
17
17
  app.run()
18
18
 
@@ -0,0 +1,453 @@
1
+ """Work around a Nuitka/astropy incompatibility that breaks unit-string parsing in the
2
+ compiled standalone binary (see specs/plans/gui-widget-plugins-and-packaging.md).
3
+
4
+ astropy.units.format.generic builds its PLY grammar by having ``astropy.extern.ply.yacc``/
5
+ ``lex`` walk the call stack with ``sys._getframe()`` to auto-discover the ``tokens``/``p_*``/
6
+ ``t_*`` local variables defined inside ``Generic._parser``/``Generic._lexer``. Nuitka-compiled
7
+ function frames don't populate ``f_locals`` the way CPython's interpreted frames do, so that
8
+ walk finds no grammar and the very first ``import astropy.units`` (pulled in unconditionally by
9
+ pyobs-core's ``pyobs.utils.enums``) crashes with ``YaccError: Unable to build parser``. This is a
10
+ real, unresolved upstream limitation as of 2026-07, not a pyobs bug:
11
+ https://github.com/astropy/astropy/issues/15069
12
+ https://github.com/Nuitka/Nuitka/issues/2313
13
+
14
+ Fix: patch ``astropy.extern.ply.yacc.get_caller_module_dict``/``...lex.get_caller_module_dict``
15
+ (the two functions actually doing the broken frame walk) so that whenever the walk comes back
16
+ without a ``tokens`` entry, they fall back to a namespace we rebuild here from the same grammar
17
+ (copied verbatim from astropy.units.format.generic), handed to PLY in a way that doesn't need
18
+ frame introspection at all.
19
+
20
+ This has to patch at the ``astropy.extern.ply`` level, not by importing
21
+ ``astropy.units.format.generic`` and monkeypatching ``Generic`` directly, because of a
22
+ bootstrapping deadlock: merely importing ``astropy.units.core``/``astropy.units.format.generic``
23
+ runs ``astropy/units/__init__.py``, which (via ``astropy.units.astrophys``, defining physical
24
+ constants) *already* triggers the broken parser during that very import -- before a class-level
25
+ patch could ever be installed. Patching ``get_caller_module_dict`` needs only
26
+ ``astropy.extern.ply``, which has no dependency on ``astropy.units`` at all, so it's safe to
27
+ install before anything touches ``astropy.units``. The vendored grammar's own reference to
28
+ ``astropy.units.core.Unit``/``CompositeUnit``/``Generic`` is resolved lazily, only when the
29
+ fallback actually fires -- by then we're already being called from inside
30
+ ``Generic._parser``/``_lexer``'s own call chain, so those names are guaranteed importable.
31
+
32
+ Scoped to the generic unit format only -- the one format pyobs_gui is known to hit
33
+ unconditionally at startup. astropy's cds/ogip/vounit formats and astropy.coordinates.angles use
34
+ the same PLY pattern and would need the identical treatment if/when they're actually exercised in
35
+ the compiled binary.
36
+
37
+ Must run before ``astropy.units`` is imported anywhere, i.e. as the first thing in
38
+ ``pyobs_gui/__init__.py``.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import re
44
+ import tempfile
45
+ import types
46
+ from fractions import Fraction
47
+ from pathlib import Path
48
+ from typing import Any
49
+
50
+ _generic_ns_cache: dict[str, types.ModuleType] = {}
51
+
52
+
53
+ def _build_generic_grammar_namespace() -> types.ModuleType:
54
+ """Rebuild astropy.units.format.generic's PLY grammar as a plain namespace object.
55
+
56
+ Only called lazily, from inside the ``get_caller_module_dict`` fallback below -- by that
57
+ point ``astropy.units.core``/``astropy.units.format.generic`` are already far enough along
58
+ to import from (see module docstring).
59
+ """
60
+ from astropy.units.core import CompositeUnit, Unit
61
+ from astropy.units.format.generic import Generic
62
+
63
+ cls = Generic
64
+ tokens = cls._tokens
65
+
66
+ # astropy.utils.parsing wants to write a generated tab-file cache next to __file__'s
67
+ # directory. In a frozen binary, astropy/units/format/ isn't a real directory on disk (it's
68
+ # compiled into the binary), so point this at a writable scratch directory instead --
69
+ # writing there is harmless best-effort caching, not required for parsing to work.
70
+ stub_dir = Path(tempfile.gettempdir()) / "pyobs_gui_astropy_ply_stub"
71
+ stub_dir.mkdir(parents=True, exist_ok=True)
72
+
73
+ ns = types.ModuleType("_pyobs_gui_generic_grammar")
74
+ ns.__file__ = str(stub_dir / "generic.py")
75
+ ns.tokens = tokens
76
+
77
+ ns.t_COMMA = r"\,"
78
+ ns.t_PRODUCT = "[*.]"
79
+ ns.t_DIVISION = "/"
80
+ ns.t_POWER = r"\^|(\*\*)"
81
+ ns.t_OPEN_PAREN = r"\("
82
+ ns.t_CLOSE_PAREN = r"\)"
83
+
84
+ def t_UFLOAT(t):
85
+ r"((\d+\.?\d*)|(\.\d+))([eE][+-]?\d+)?"
86
+ if not re.search(r"[eE\.]", t.value):
87
+ t.type = "UINT"
88
+ t.value = int(t.value)
89
+ elif t.value.endswith("."):
90
+ t.type = "UINT"
91
+ t.value = int(t.value[:-1])
92
+ else:
93
+ t.value = float(t.value)
94
+ return t
95
+
96
+ def t_UINT(t):
97
+ r"\d+"
98
+ t.value = int(t.value)
99
+ return t
100
+
101
+ def t_SIGN(t):
102
+ r"[+-](?=\d)"
103
+ t.value = int(t.value + "1")
104
+ return t
105
+
106
+ def t_FUNCNAME(t):
107
+ r"((sqrt)|(ln)|(exp)|(log)|(mag)|(dB)|(dex))(?=\ *\()"
108
+ return t
109
+
110
+ def t_UNIT(t):
111
+ r"[^\s\d+\-\./\*\^\(\)\,]+"
112
+ t.value = cls._get_unit(t)
113
+ return t
114
+
115
+ ns.t_ignore = " "
116
+
117
+ def t_error(t):
118
+ raise ValueError(f"Invalid character at col {t.lexpos}")
119
+
120
+ ns.t_UFLOAT = t_UFLOAT
121
+ ns.t_UINT = t_UINT
122
+ ns.t_SIGN = t_SIGN
123
+ ns.t_FUNCNAME = t_FUNCNAME
124
+ ns.t_UNIT = t_UNIT
125
+ ns.t_error = t_error
126
+
127
+ def p_main(p):
128
+ """
129
+ main : unit
130
+ | structured_unit
131
+ | structured_subunit
132
+ """
133
+ if isinstance(p[1], tuple):
134
+ p[0] = p[1][0]
135
+ else:
136
+ p[0] = p[1]
137
+
138
+ def p_structured_subunit(p):
139
+ """
140
+ structured_subunit : OPEN_PAREN structured_unit CLOSE_PAREN
141
+ """
142
+ p[0] = (p[2],)
143
+
144
+ def p_structured_unit(p):
145
+ """
146
+ structured_unit : subunit COMMA
147
+ | subunit COMMA subunit
148
+ """
149
+ from astropy.units.structured import StructuredUnit
150
+
151
+ inputs = (p[1],) if len(p) == 3 else (p[1], p[3])
152
+ units = ()
153
+ for subunit in inputs:
154
+ if isinstance(subunit, tuple):
155
+ units += subunit
156
+ elif isinstance(subunit, StructuredUnit):
157
+ units += subunit.values()
158
+ else:
159
+ units += (subunit,)
160
+
161
+ p[0] = StructuredUnit(units)
162
+
163
+ def p_subunit(p):
164
+ """
165
+ subunit : unit
166
+ | structured_unit
167
+ | structured_subunit
168
+ """
169
+ p[0] = p[1]
170
+
171
+ def p_unit(p):
172
+ """
173
+ unit : product_of_units
174
+ | factor product_of_units
175
+ | factor PRODUCT product_of_units
176
+ | division_product_of_units
177
+ | factor division_product_of_units
178
+ | factor PRODUCT division_product_of_units
179
+ | inverse_unit
180
+ | factor inverse_unit
181
+ | factor PRODUCT inverse_unit
182
+ | factor
183
+ """
184
+ if len(p) == 2:
185
+ p[0] = Unit(p[1])
186
+ elif len(p) == 3:
187
+ p[0] = CompositeUnit(p[1] * p[2].scale, p[2].bases, p[2].powers)
188
+ elif len(p) == 4:
189
+ p[0] = CompositeUnit(p[1] * p[3].scale, p[3].bases, p[3].powers)
190
+
191
+ def p_division_product_of_units(p):
192
+ """
193
+ division_product_of_units : division_product_of_units DIVISION product_of_units
194
+ | product_of_units
195
+ """
196
+ if len(p) == 4:
197
+ p[0] = Unit(p[1] / p[3])
198
+ else:
199
+ p[0] = p[1]
200
+
201
+ def p_inverse_unit(p):
202
+ """
203
+ inverse_unit : DIVISION unit_expression
204
+ """
205
+ p[0] = p[2] ** -1
206
+
207
+ def p_factor(p):
208
+ """
209
+ factor : factor_fits
210
+ | factor_float
211
+ | factor_int
212
+ """
213
+ p[0] = p[1]
214
+
215
+ def p_factor_float(p):
216
+ """
217
+ factor_float : signed_float
218
+ | signed_float UINT signed_int
219
+ | signed_float UINT POWER numeric_power
220
+ """
221
+ if cls.name == "fits":
222
+ raise ValueError("Numeric factor not supported by FITS")
223
+ if len(p) == 4:
224
+ p[0] = p[1] * p[2] ** float(p[3])
225
+ elif len(p) == 5:
226
+ p[0] = p[1] * p[2] ** float(p[4])
227
+ elif len(p) == 2:
228
+ p[0] = p[1]
229
+
230
+ def p_factor_int(p):
231
+ """
232
+ factor_int : UINT
233
+ | UINT signed_int
234
+ | UINT POWER numeric_power
235
+ | UINT UINT signed_int
236
+ | UINT UINT POWER numeric_power
237
+ """
238
+ if cls.name == "fits":
239
+ raise ValueError("Numeric factor not supported by FITS")
240
+ if len(p) == 2:
241
+ p[0] = p[1]
242
+ elif len(p) == 3:
243
+ p[0] = p[1] ** float(p[2])
244
+ elif len(p) == 4:
245
+ if isinstance(p[2], int):
246
+ p[0] = p[1] * p[2] ** float(p[3])
247
+ else:
248
+ p[0] = p[1] ** float(p[3])
249
+ elif len(p) == 5:
250
+ p[0] = p[1] * p[2] ** p[4]
251
+
252
+ def p_factor_fits(p):
253
+ """
254
+ factor_fits : UINT POWER OPEN_PAREN signed_int CLOSE_PAREN
255
+ | UINT POWER OPEN_PAREN UINT CLOSE_PAREN
256
+ | UINT POWER signed_int
257
+ | UINT POWER UINT
258
+ | UINT SIGN UINT
259
+ | UINT OPEN_PAREN signed_int CLOSE_PAREN
260
+ """
261
+ if p[1] != 10:
262
+ if cls.name == "fits":
263
+ raise ValueError("Base must be 10")
264
+ else:
265
+ return
266
+ if len(p) == 4:
267
+ if p[2] in ("**", "^"):
268
+ p[0] = 10 ** p[3]
269
+ else:
270
+ p[0] = 10 ** (p[2] * p[3])
271
+ elif len(p) == 5:
272
+ p[0] = 10 ** p[3]
273
+ elif len(p) == 6:
274
+ p[0] = 10 ** p[4]
275
+
276
+ def p_product_of_units(p):
277
+ """
278
+ product_of_units : unit_expression PRODUCT product_of_units
279
+ | unit_expression product_of_units
280
+ | unit_expression
281
+ """
282
+ if len(p) == 2:
283
+ p[0] = p[1]
284
+ elif len(p) == 3:
285
+ p[0] = p[1] * p[2]
286
+ else:
287
+ p[0] = p[1] * p[3]
288
+
289
+ def p_unit_expression(p):
290
+ """
291
+ unit_expression : function
292
+ | unit_with_power
293
+ | OPEN_PAREN product_of_units CLOSE_PAREN
294
+ """
295
+ if len(p) == 2:
296
+ p[0] = p[1]
297
+ else:
298
+ p[0] = p[2]
299
+
300
+ def p_unit_with_power(p):
301
+ """
302
+ unit_with_power : UNIT POWER numeric_power
303
+ | UNIT numeric_power
304
+ | UNIT
305
+ """
306
+ if len(p) == 2:
307
+ p[0] = p[1]
308
+ elif len(p) == 3:
309
+ p[0] = p[1] ** p[2]
310
+ else:
311
+ p[0] = p[1] ** p[3]
312
+
313
+ def p_numeric_power(p):
314
+ """
315
+ numeric_power : sign UINT
316
+ | OPEN_PAREN paren_expr CLOSE_PAREN
317
+ """
318
+ if len(p) == 3:
319
+ p[0] = p[1] * p[2]
320
+ elif len(p) == 4:
321
+ p[0] = p[2]
322
+
323
+ def p_paren_expr(p):
324
+ """
325
+ paren_expr : sign UINT
326
+ | signed_float
327
+ | frac
328
+ """
329
+ if len(p) == 3:
330
+ p[0] = p[1] * p[2]
331
+ else:
332
+ p[0] = p[1]
333
+
334
+ def p_frac(p):
335
+ """
336
+ frac : sign UINT DIVISION sign UINT
337
+ """
338
+ p[0] = Fraction(p[1] * p[2], p[4] * p[5])
339
+
340
+ def p_sign(p):
341
+ """
342
+ sign : SIGN
343
+ |
344
+ """
345
+ if len(p) == 2:
346
+ p[0] = p[1]
347
+ else:
348
+ p[0] = 1
349
+
350
+ def p_signed_int(p):
351
+ """
352
+ signed_int : SIGN UINT
353
+ """
354
+ p[0] = p[1] * p[2]
355
+
356
+ def p_signed_float(p):
357
+ """
358
+ signed_float : sign UINT
359
+ | sign UFLOAT
360
+ """
361
+ p[0] = p[1] * p[2]
362
+
363
+ def p_function(p):
364
+ """
365
+ function : FUNCNAME OPEN_PAREN main CLOSE_PAREN
366
+ """
367
+ if p[1] == "sqrt":
368
+ p[0] = p[3] ** 0.5
369
+ return
370
+ elif p[1] in ("mag", "dB", "dex"):
371
+ function_unit = cls._validate_unit(p[1])
372
+ if callable(function_unit):
373
+ p[0] = function_unit(p[3])
374
+ return
375
+
376
+ raise ValueError(f"'{p[1]}' is not a recognized function")
377
+
378
+ def p_error(p):
379
+ raise ValueError()
380
+
381
+ ns.p_main = p_main
382
+ ns.p_structured_subunit = p_structured_subunit
383
+ ns.p_structured_unit = p_structured_unit
384
+ ns.p_subunit = p_subunit
385
+ ns.p_unit = p_unit
386
+ ns.p_division_product_of_units = p_division_product_of_units
387
+ ns.p_inverse_unit = p_inverse_unit
388
+ ns.p_factor = p_factor
389
+ ns.p_factor_float = p_factor_float
390
+ ns.p_factor_int = p_factor_int
391
+ ns.p_factor_fits = p_factor_fits
392
+ ns.p_product_of_units = p_product_of_units
393
+ ns.p_unit_expression = p_unit_expression
394
+ ns.p_unit_with_power = p_unit_with_power
395
+ ns.p_numeric_power = p_numeric_power
396
+ ns.p_paren_expr = p_paren_expr
397
+ ns.p_frac = p_frac
398
+ ns.p_sign = p_sign
399
+ ns.p_signed_int = p_signed_int
400
+ ns.p_signed_float = p_signed_float
401
+ ns.p_function = p_function
402
+ ns.p_error = p_error
403
+
404
+ return ns
405
+
406
+
407
+ def _namespace_pdict(ns: types.ModuleType) -> dict[str, Any]:
408
+ return {k: getattr(ns, k) for k in dir(ns)}
409
+
410
+
411
+ def patch_generic_unit_parser() -> None:
412
+ """Patch astropy.extern.ply's frame-walking helpers to fall back to our vendored grammar.
413
+
414
+ Safe to call unconditionally (frozen or not) -- under a normal, unfrozen interpreter the
415
+ original frame walk always succeeds, so the fallback path never triggers.
416
+ """
417
+ from astropy.extern.ply import lex as ply_lex
418
+ from astropy.extern.ply import yacc as ply_yacc
419
+
420
+ original_yacc_dict = ply_yacc.get_caller_module_dict
421
+ original_lex_dict = ply_lex.get_caller_module_dict
422
+
423
+ def _fallback_pdict(pdict: dict[str, Any] | None) -> dict[str, Any]:
424
+ # Keep whatever the real (possibly broken-locals) pdict already has -- __file__/
425
+ # __package__/__name__ are module globals and come through fine even on a compiled
426
+ # frame -- and only supplement the missing tokens/p_*/t_* grammar entries. Replacing
427
+ # __file__ wholesale would point astropy's tab-file bookkeeping at the wrong directory.
428
+ if "ns" not in _generic_ns_cache:
429
+ _generic_ns_cache["ns"] = _build_generic_grammar_namespace()
430
+ merged = dict(pdict) if pdict else {}
431
+ merged.update(_namespace_pdict(_generic_ns_cache["ns"]))
432
+ return merged
433
+
434
+ def patched_yacc_dict(levels: int) -> dict[str, Any]:
435
+ try:
436
+ pdict = original_yacc_dict(levels)
437
+ except Exception:
438
+ return _fallback_pdict(None)
439
+ if "tokens" not in pdict:
440
+ return _fallback_pdict(pdict)
441
+ return pdict
442
+
443
+ def patched_lex_dict(levels: int) -> dict[str, Any]:
444
+ try:
445
+ pdict = original_lex_dict(levels)
446
+ except Exception:
447
+ return _fallback_pdict(None)
448
+ if "tokens" not in pdict:
449
+ return _fallback_pdict(pdict)
450
+ return pdict
451
+
452
+ ply_yacc.get_caller_module_dict = patched_yacc_dict
453
+ ply_lex.get_caller_module_dict = patched_lex_dict
@@ -288,7 +288,7 @@ class MainWindow(QtWidgets.QMainWindow, BaseWindow, Ui_MainWindow): # type: ign
288
288
  self._modules_header_added = False
289
289
 
290
290
  # navbar keyboard shortcuts: slot -> currently bound page name, session-only (see
291
- # DEV_NavbarShortcuts.md)
291
+ # pyobs-core/specs/plans/gui-navbar-shortcuts.md)
292
292
  self._slot_bindings: Dict[str, str] = {}
293
293
  self.listPages.setItemDelegate(NavPageItemDelegate(self._slot_bindings, self))
294
294
  self._setup_shortcuts()
@@ -469,8 +469,8 @@ class MainWindow(QtWidgets.QMainWindow, BaseWindow, Ui_MainWindow): # type: ign
469
469
  self._widgets / self._slot_bindings at press-time, since module pages come and go but the
470
470
  shortcut objects themselves live for the app's lifetime. Every shortcut requires Ctrl (or
471
471
  Ctrl+Alt) so no text/numeric-entry widget can ever mistake one for ordinary input -- see
472
- DEV_NavbarShortcuts.md for why that's a structural guarantee rather than one that needs
473
- per-widget-type verification.
472
+ pyobs-core/specs/plans/gui-navbar-shortcuts.md for why that's a structural guarantee
473
+ rather than one that needs per-widget-type verification.
474
474
  """
475
475
  self._shortcuts: List[QtGui.QShortcut] = []
476
476
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-gui"
3
- version = "2.0.0.dev5"
3
+ version = "2.0.0.dev6"
4
4
  description = "A remote GUI for pyobs"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11,<3.14"
@@ -20,6 +20,9 @@ dependencies = [
20
20
  "secretstorage>=3.5.0,<4",
21
21
  ]
22
22
 
23
+ [project.scripts]
24
+ pyobs-gui = "pyobs_gui.__main__:main"
25
+
23
26
  [dependency-groups]
24
27
  dev = [
25
28
  "black>=25.1.0,<27",
@@ -0,0 +1,97 @@
1
+ [app]
2
+
3
+ # title of your application
4
+ title = pyobs-gui
5
+
6
+ # project root directory. default = The parent directory of input_file
7
+ project_dir = .
8
+
9
+ # source file entry point path. default = main.py
10
+ input_file = pyobs_gui
11
+
12
+ # directory where the executable output is generated
13
+ exec_directory = dist
14
+
15
+ # path to the project file relative to project_dir
16
+ project_file =
17
+
18
+ # application icon
19
+ icon = /home/husser/code/pyobs/pyobs-gui/.venv/lib/python3.13/site-packages/PySide6/scripts/deploy_lib/pyside_icon.jpg
20
+
21
+ [python]
22
+
23
+ # python path
24
+ python_path = /home/husser/code/pyobs/pyobs-gui/.venv/bin/python3
25
+
26
+ # python packages to install
27
+ packages = Nuitka==4.0
28
+
29
+ # buildozer = for deploying Android application
30
+ android_packages = buildozer==1.5.0,cython==0.29.33
31
+
32
+ [qt]
33
+
34
+ # paths to required qml files. comma separated
35
+ # normally all the qml files required by the project are added automatically
36
+ # design studio projects include the qml files using qt resources
37
+ qml_files =
38
+
39
+ # excluded qml plugin binaries
40
+ excluded_qml_plugins =
41
+
42
+ # qt modules used. comma separated
43
+ modules = Core,DBus,Designer,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Xml
44
+
45
+ # qt plugins used by the application. only relevant for desktop deployment
46
+ # for qt plugins used in android application see [android][plugins]
47
+ plugins = accessiblebridge,designer,egldeviceintegrations,generic,iconengines,imageformats,networkaccess,networkinformation,platforminputcontexts,platforms,platforms/darwin,platformthemes,styles,tls,wayland-decoration-client,wayland-graphics-integration-client,wayland-shell-integration,xcbglintegrations
48
+
49
+ [android]
50
+
51
+ # path to pyside wheel
52
+ wheel_pyside =
53
+
54
+ # path to shiboken wheel
55
+ wheel_shiboken =
56
+
57
+ # plugins to be copied to libs folder of the packaged application. comma separated
58
+ plugins =
59
+
60
+ [nuitka]
61
+
62
+ # usage description for permissions requested by the app as found in the info.plist file
63
+ # of the app bundle. comma separated
64
+ # eg = extra_args = --show-modules --follow-stdlib
65
+ macos.permissions =
66
+
67
+ # mode of using nuitka. accepts standalone or onefile. default = onefile
68
+ mode = standalone
69
+
70
+ # specify any extra nuitka arguments
71
+ extra_args = --quiet --noinclude-qt-translations --clang --python-flag=-m --include-package=astropy.units.format --include-package=astropy.units.format.generic_parsetab --include-package=astropy.units.format.generic_lextab --include-package=astropy.units.format.cds_parsetab --include-package=astropy.units.format.cds_lextab --include-package=astropy.units.format.ogip_parsetab --include-package=astropy.units.format.ogip_lextab --include-package=astropy.coordinates.angles --include-package=astropy.coordinates.angles.angle_parsetab --include-package=astropy.coordinates.angles.angle_lextab --include-package=astropy.extern.ply --include-package-data=asdf --include-package-data=astropy --include-package-data=sunpy --include-package-data=matplotlib --include-package-data=qfitswidget --include-package-data=astropy_iers_data --include-package=pyobs.vfs --include-package-data=astroquery --include-package=slixmpp --noinclude-dlls=*webengine* --noinclude-dlls=*WebEngine* --include-distribution-metadata=astropy --include-distribution-metadata=numpy --include-distribution-metadata=scipy --include-distribution-metadata=PyYAML --include-distribution-metadata=sunpy --include-distribution-metadata=matplotlib --include-distribution-metadata=qfitswidget --include-distribution-metadata=astroquery --include-distribution-metadata=asdf --include-distribution-metadata=pyobs-core --include-distribution-metadata=pyobs-gui
72
+
73
+ [buildozer]
74
+
75
+ # build mode
76
+ # possible values = ["aarch64", "armv7a", "i686", "x86_64"]
77
+ # release creates a .aab, while debug creates a .apk
78
+ mode = debug
79
+
80
+ # path to pyside6 and shiboken6 recipe dir
81
+ recipe_dir =
82
+
83
+ # path to extra qt android .jar files to be loaded by the application
84
+ jars_dir =
85
+
86
+ # if empty, uses default ndk path downloaded by buildozer
87
+ ndk_path =
88
+
89
+ # if empty, uses default sdk path downloaded by buildozer
90
+ sdk_path =
91
+
92
+ # other libraries to be loaded at app startup. comma separated.
93
+ local_libs =
94
+
95
+ # architecture of deployed platform
96
+ arch =
97
+
@@ -18,3 +18,17 @@ Relevant so far:
18
18
  - `pyobs-core/specs/plans/pyobs_2_0_work_plan.md` — the `IRunning.is_running()` removal item
19
19
  required updating `mainwindow.py`'s two RPC calls to that method to read `IRunning`'s pushed
20
20
  state instead.
21
+ - `pyobs-core/specs/plans/gui-navbar-shortcuts.md` — implemented, closed. Control-group-style
22
+ keyboard shortcuts for the module sidebar.
23
+ - `pyobs-core/specs/plans/gui-acl-aware-widget-gating.md` — implemented, closed. Greying out /
24
+ hiding actions an operator isn't permitted to use, per pyobs-core 2.0 ACLs.
25
+ - `pyobs-core/specs/plans/gui-telescopewidget-layout.md` — proposed/exploratory.
26
+ `TelescopeWidget`'s minimum-width investigation and candidate fixes.
27
+ - `pyobs-core/specs/adrs/0010-pyobs-gui-stays-on-qtwidgets-not-qml.md` — accepted. Whether
28
+ `pyobs-gui` should move to Qt Quick/QML; decided to stay on QtWidgets.
29
+ - `pyobs-core/specs/plans/gui-iacquisition-widget.md` — implemented, closed. The `IAcquisition`
30
+ widget.
31
+ - `pyobs-core/specs/plans/gui-iautofocus-widget.md` — proposed, not implemented yet. The
32
+ `IAutoFocus` widget.
33
+ - `pyobs-core/specs/plans/gui-iautoguiding-widget.md` — in progress. The `IAutoGuiding` widget;
34
+ first pass shipped, follow-up refinement (physical-unit offsets) in progress.
@@ -2375,7 +2375,7 @@ wheels = [
2375
2375
 
2376
2376
  [[package]]
2377
2377
  name = "pyobs-gui"
2378
- version = "2.0.0.dev5"
2378
+ version = "2.0.0.dev6"
2379
2379
  source = { editable = "." }
2380
2380
  dependencies = [
2381
2381
  { name = "astroplan" },