pyobs-fli 2.0.0.dev9__tar.gz → 2.0.0.dev10__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 (80) hide show
  1. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/.github/dependabot.yml +5 -0
  2. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/.github/workflows/pypi.yml +1 -1
  3. pyobs_fli-2.0.0.dev10/.github/workflows/pyrefly.yml +26 -0
  4. pyobs_fli-2.0.0.dev10/.github/workflows/pytest.yml +28 -0
  5. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/PKG-INFO +1 -1
  6. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/flicamera.py +1 -0
  7. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyproject.toml +6 -1
  8. pyobs_fli-2.0.0.dev10/tests/test_flicamera.py +91 -0
  9. pyobs_fli-2.0.0.dev10/tests/test_flifilterwheel.py +79 -0
  10. pyobs_fli-2.0.0.dev10/tests/test_import.py +29 -0
  11. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/uv.lock +61 -1
  12. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/.github/workflows/ruff.yml +0 -0
  13. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/.pre-commit-config.yaml +0 -0
  14. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/.readthedocs.yml +0 -0
  15. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/CHANGELOG.rst +0 -0
  16. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/CMakeLists.txt +0 -0
  17. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/LICENSE +0 -0
  18. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/README.md +0 -0
  19. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/Makefile +0 -0
  20. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/make.bat +0 -0
  21. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/requirements.txt +0 -0
  22. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/source/_static/pyobs.gif +0 -0
  23. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/source/conf.py +0 -0
  24. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/docs/source/index.rst +0 -0
  25. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/Makefile +0 -0
  26. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/docxx.sty +0 -0
  27. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera-parport.c +0 -0
  28. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera-parport.h +0 -0
  29. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera-usb.c +0 -0
  30. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera-usb.h +0 -0
  31. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera.c +0 -0
  32. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-camera.h +0 -0
  33. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-debug.h +0 -0
  34. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-filter-focuser.c +0 -0
  35. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-filter-focuser.h +0 -0
  36. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-libfli.h +0 -0
  37. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-mem.c +0 -0
  38. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-mem.h +0 -0
  39. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-raw.c +0 -0
  40. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli-raw.h +0 -0
  41. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli.c +0 -0
  42. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli.dxx +0 -0
  43. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/libfli.h +0 -0
  44. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/bsd/libfli-usb-sys.c +0 -0
  45. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-debug.c +0 -0
  46. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-parport.h +0 -0
  47. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-serial.c +0 -0
  48. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-serial.h +0 -0
  49. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-sys.c +0 -0
  50. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-sys.h +0 -0
  51. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-usb.c +0 -0
  52. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/libfli-usb.h +0 -0
  53. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/linux/fli_ioctl.h +0 -0
  54. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/linux/fliusb_ioctl.h +0 -0
  55. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/linux/libfli-parport.c +0 -0
  56. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/linux/libfli-usb-sys.c +0 -0
  57. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/osx/libfli-usb-sys.c +0 -0
  58. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/unix/osx/libfli-usb-sys.h +0 -0
  59. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-debug.c +0 -0
  60. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-parport.h +0 -0
  61. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-serial.c +0 -0
  62. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-serial.h +0 -0
  63. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-sys.h +0 -0
  64. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-usb.c +0 -0
  65. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-usb.h +0 -0
  66. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-windows-parport.c +0 -0
  67. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli-windows.c +0 -0
  68. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli.def +0 -0
  69. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli.rc +0 -0
  70. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli.sln +0 -0
  71. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli.vcproj +0 -0
  72. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/libfli64.def +0 -0
  73. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/lib/windows/resource.h +0 -0
  74. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/poetry.lock +0 -0
  75. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/__init__.py +0 -0
  76. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/flibase.py +0 -0
  77. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/flidriver.pyx +0 -0
  78. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/flifilterwheel.py +0 -0
  79. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/pyobs_fli/libfli.pxd +0 -0
  80. {pyobs_fli-2.0.0.dev9 → pyobs_fli-2.0.0.dev10}/specs/plans/2026-08-16-nogil-fli-driver-calls.md +0 -0
@@ -6,5 +6,10 @@ updates:
6
6
  interval: "weekly"
7
7
  day: "tuesday"
8
8
  target-branch: "develop"
9
+ groups:
10
+ patch-and-minor:
11
+ update-types:
12
+ - "minor"
13
+ - "patch"
9
14
  labels:
10
15
  - "dependencies"
@@ -56,7 +56,7 @@ jobs:
56
56
  - name: Set up Python
57
57
  uses: astral-sh/setup-uv@v5
58
58
  with:
59
- enable-cache: true
59
+ enable-cache: false
60
60
  python-version: "3.13"
61
61
 
62
62
  - name: Publish
@@ -0,0 +1,26 @@
1
+ name: pyrefly
2
+ on: push
3
+
4
+ jobs:
5
+ pyrefly:
6
+ runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+
9
+ steps:
10
+ - name: Check out repository code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+ python-version: "3.13"
18
+
19
+ - name: Install system dependencies
20
+ run: sudo apt-get install -y libcfitsio-dev
21
+
22
+ - name: Install packages
23
+ run: uv sync --all-extras --dev
24
+
25
+ - name: Run pyrefly
26
+ run: uv run pyrefly check
@@ -0,0 +1,28 @@
1
+ name: PyTest
2
+ on: push
3
+
4
+ jobs:
5
+ test:
6
+ runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+
9
+ steps:
10
+ - name: Check out repository code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+ python-version: "3.13"
18
+
19
+ - name: Install system dependencies
20
+ run: sudo apt-get install -y libcfitsio-dev
21
+
22
+ - name: Install packages
23
+ run: uv sync --all-extras --dev
24
+
25
+ - name: Run tests
26
+ run: uv run pytest tests
27
+ env:
28
+ CI: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-fli
3
- Version: 2.0.0.dev9
3
+ Version: 2.0.0.dev10
4
4
  Summary: pyobs module for FLI cameras
5
5
  Author-Email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -92,6 +92,7 @@ class FliCamera(FliBaseMixin, BaseCamera, ICamera, IWindow, IBinning, ICooling,
92
92
  ),
93
93
  )
94
94
  await self.comm.set_state(IBinning, BinningState(x=self._binning[0], y=self._binning[1]))
95
+ await self.comm.set_state(ITemperatures, TemperaturesState())
95
96
 
96
97
  async def close(self) -> None:
97
98
  """Close the module."""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-fli"
3
- version = "2.0.0.dev9"
3
+ version = "2.0.0.dev10"
4
4
  description = "pyobs module for FLI cameras"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
@@ -17,6 +17,8 @@ dev = [
17
17
  "pre-commit>=4.2.0,<5",
18
18
  "ruff>=0.9.0",
19
19
  "pyrefly>=1.1.1",
20
+ "pytest>=8.0.0",
21
+ "pytest-asyncio>=0.24.0",
20
22
  ]
21
23
 
22
24
  [tool.scikit-build]
@@ -49,3 +51,6 @@ select = ["E", "F", "W", "I", "UP", "G"]
49
51
  [tool.pyrefly]
50
52
  python-version = "3.11"
51
53
  ignore-missing-imports = []
54
+
55
+ [tool.pytest.ini_options]
56
+ asyncio_mode = "strict"
@@ -0,0 +1,91 @@
1
+ """Unit tests for the non-hardware logic in FliCamera: constructor defaults, the
2
+ window/binning setters, and the _run_blocking/_run_blocking_or_raise thread wrappers.
3
+
4
+ Hardware I/O (opening, exposing, reading out) is out of scope here.
5
+ """
6
+
7
+ import asyncio
8
+ import threading
9
+ from unittest.mock import AsyncMock
10
+
11
+ import pytest
12
+ from pyobs.interfaces import IBinning, IWindow
13
+
14
+ from pyobs_fli import FliCamera
15
+
16
+
17
+ def test_constructor_defaults() -> None:
18
+ camera = FliCamera()
19
+ assert camera._temp_setpoint == -20.0
20
+ assert camera._cooling_enabled is False
21
+ assert camera._window == (0, 0, 0, 0)
22
+ assert camera._binning == (1, 1)
23
+ assert camera._driver is None
24
+
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_set_window() -> None:
28
+ camera = FliCamera()
29
+ camera.comm.set_state = AsyncMock() # type: ignore[method-assign]
30
+
31
+ await camera.set_window(10, 20, 100, 200)
32
+
33
+ assert camera._window == (10, 20, 100, 200)
34
+ assert camera.comm.set_state.await_args is not None
35
+ interface, state = camera.comm.set_state.await_args.args
36
+ assert interface is IWindow
37
+ assert (state.x, state.y, state.width, state.height) == (10, 20, 100, 200)
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_set_binning() -> None:
42
+ camera = FliCamera()
43
+ camera.comm.set_state = AsyncMock() # type: ignore[method-assign]
44
+
45
+ await camera.set_binning(2, 3)
46
+
47
+ assert camera._binning == (2, 3)
48
+ assert camera.comm.set_state.await_args is not None
49
+ interface, state = camera.comm.set_state.await_args.args
50
+ assert interface is IBinning
51
+ assert (state.x, state.y) == (2, 3)
52
+
53
+
54
+ @pytest.mark.asyncio
55
+ async def test_run_blocking_runs_func_and_returns_true() -> None:
56
+ ran: list[bool] = []
57
+
58
+ def fast() -> None:
59
+ ran.append(True)
60
+
61
+ assert await FliCamera._run_blocking(fast) is True
62
+ assert ran == [True]
63
+
64
+
65
+ @pytest.mark.asyncio
66
+ async def test_run_blocking_times_out() -> None:
67
+ done = threading.Event()
68
+
69
+ def slow() -> None:
70
+ done.wait()
71
+
72
+ assert await FliCamera._run_blocking(slow, timeout=0.01) is False
73
+ done.set()
74
+ await asyncio.sleep(0.05)
75
+
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_run_blocking_or_raise_returns_value() -> None:
79
+ camera = FliCamera()
80
+ assert await camera._run_blocking_or_raise(lambda: 42) == 42
81
+
82
+
83
+ @pytest.mark.asyncio
84
+ async def test_run_blocking_or_raise_reraises() -> None:
85
+ camera = FliCamera()
86
+
87
+ def boom() -> int:
88
+ raise ValueError("boom")
89
+
90
+ with pytest.raises(ValueError):
91
+ await camera._run_blocking_or_raise(boom)
@@ -0,0 +1,79 @@
1
+ """Unit tests for the non-hardware logic in FliFilterWheel: filter-name normalization,
2
+ position <-> name mapping, and set_filter position computation.
3
+
4
+ Hardware I/O (moving the wheel) is mocked out.
5
+ """
6
+
7
+ from unittest.mock import MagicMock
8
+
9
+ import pytest
10
+ from pyobs.utils import exceptions as exc
11
+
12
+ from pyobs_fli import FliFilterWheel
13
+
14
+ _TWO_WHEELS = [["A", "B", "C", "D", "E", "F", "G"], ["H", "I", "J"]]
15
+
16
+
17
+ def test_constructor_single_wheel() -> None:
18
+ wheel = FliFilterWheel(filter_names=["A", "B", "C"])
19
+ assert wheel._filter_names == [["A", "B", "C"]]
20
+
21
+
22
+ def test_constructor_two_wheels() -> None:
23
+ wheel = FliFilterWheel(filter_names=_TWO_WHEELS)
24
+ assert wheel._filter_names == _TWO_WHEELS
25
+
26
+
27
+ @pytest.mark.parametrize(
28
+ ("pos", "expected"),
29
+ [
30
+ (0, "A"),
31
+ (1, "G"),
32
+ (6, "B"),
33
+ (7, "H"),
34
+ (8, "G"),
35
+ (14, "I"),
36
+ ],
37
+ )
38
+ def test_resolve_filter_name(pos: int, expected: str) -> None:
39
+ wheel = FliFilterWheel(filter_names=_TWO_WHEELS)
40
+ assert wheel._resolve_filter_name(pos) == expected
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_set_filter_first_wheel() -> None:
45
+ wheel = FliFilterWheel(filter_names=_TWO_WHEELS)
46
+ wheel._driver = MagicMock()
47
+ wheel._driver.set_filter_pos = MagicMock()
48
+
49
+ async def fake_run(func, timeout: float = 5.0) -> None:
50
+ func()
51
+
52
+ wheel._run_blocking_or_raise = fake_run # type: ignore[method-assign]
53
+
54
+ await wheel.set_filter("A")
55
+ wheel._driver.set_filter_pos.assert_called_once_with(0)
56
+ assert wheel._current_filter == "A"
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_set_filter_second_wheel() -> None:
61
+ wheel = FliFilterWheel(filter_names=_TWO_WHEELS)
62
+ wheel._driver = MagicMock()
63
+ wheel._driver.set_filter_pos = MagicMock()
64
+
65
+ async def fake_run(func, timeout: float = 5.0) -> None:
66
+ func()
67
+
68
+ wheel._run_blocking_or_raise = fake_run # type: ignore[method-assign]
69
+
70
+ await wheel.set_filter("H")
71
+ wheel._driver.set_filter_pos.assert_called_once_with(7)
72
+ assert wheel._current_filter == "H"
73
+
74
+
75
+ @pytest.mark.asyncio
76
+ async def test_set_filter_unknown_filter() -> None:
77
+ wheel = FliFilterWheel(filter_names=_TWO_WHEELS)
78
+ with pytest.raises(exc.ModuleError):
79
+ await wheel.set_filter("nope")
@@ -0,0 +1,29 @@
1
+ """Smoke tests: import the drivers and instantiate them without hardware, asserting
2
+ the interfaces they claim.
3
+
4
+ The Cython extension (flidriver) is compiled from the vendored libfli C source during
5
+ install, but device enumeration/opening only happens inside open(), so instantiation
6
+ is safe with no FLI hardware attached.
7
+ """
8
+
9
+ from pyobs.interfaces import IAbortable, IBinning, ICamera, ICooling, IFilters, ITemperatures, IWindow
10
+ from pyobs.modules import Module
11
+
12
+ from pyobs_fli import FliCamera, FliFilterWheel
13
+
14
+
15
+ def test_instantiate_camera() -> None:
16
+ camera = FliCamera()
17
+ assert isinstance(camera, Module)
18
+ assert isinstance(camera, ICamera)
19
+ assert isinstance(camera, IWindow)
20
+ assert isinstance(camera, IBinning)
21
+ assert isinstance(camera, ICooling)
22
+ assert isinstance(camera, ITemperatures)
23
+ assert isinstance(camera, IAbortable)
24
+
25
+
26
+ def test_instantiate_filter_wheel() -> None:
27
+ wheel = FliFilterWheel(filter_names=["A", "B", "C"])
28
+ assert isinstance(wheel, Module)
29
+ assert isinstance(wheel, IFilters)
@@ -865,6 +865,15 @@ wheels = [
865
865
  { url = "https://files.pythonhosted.org/packages/7f/ec/6b120b4a86f6fadc7ddb1d7c7cdcb15bcfb332deb022ab60df51bcd4494c/influxdb_client-1.50.0-py3-none-any.whl", hash = "sha256:f172975cf7f0c95bfe74f288b31273393b164d2c58a948de55497d9956ab49be", size = 746289, upload-time = "2026-01-23T09:39:37.377Z" },
866
866
  ]
867
867
 
868
+ [[package]]
869
+ name = "iniconfig"
870
+ version = "2.3.0"
871
+ source = { registry = "https://pypi.org/simple" }
872
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
873
+ wheels = [
874
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
875
+ ]
876
+
868
877
  [[package]]
869
878
  name = "invoke"
870
879
  version = "3.0.3"
@@ -1437,6 +1446,15 @@ wheels = [
1437
1446
  { url = "https://files.pythonhosted.org/packages/49/e2/4e6eee633809c376c024821b91ade709cbfd040ec53939ffbcc292aa7eee/platformdirs-4.11.2-py3-none-any.whl", hash = "sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4", size = 23361, upload-time = "2026-08-10T15:48:04.855Z" },
1438
1447
  ]
1439
1448
 
1449
+ [[package]]
1450
+ name = "pluggy"
1451
+ version = "1.6.0"
1452
+ source = { registry = "https://pypi.org/simple" }
1453
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
1454
+ wheels = [
1455
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
1456
+ ]
1457
+
1440
1458
  [[package]]
1441
1459
  name = "pre-commit"
1442
1460
  version = "4.6.2"
@@ -1838,6 +1856,15 @@ wheels = [
1838
1856
  { url = "https://files.pythonhosted.org/packages/b4/11/97233cf23ad5411ac6f13b1d6ee3888f90ace4f974d9bf9db887aa428912/pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0", size = 349410, upload-time = "2024-11-11T15:22:24.817Z" },
1839
1857
  ]
1840
1858
 
1859
+ [[package]]
1860
+ name = "pygments"
1861
+ version = "2.20.0"
1862
+ source = { registry = "https://pypi.org/simple" }
1863
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
1864
+ wheels = [
1865
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
1866
+ ]
1867
+
1841
1868
  [[package]]
1842
1869
  name = "pynacl"
1843
1870
  version = "1.6.2"
@@ -1914,7 +1941,7 @@ wheels = [
1914
1941
 
1915
1942
  [[package]]
1916
1943
  name = "pyobs-fli"
1917
- version = "2.0.0.dev9"
1944
+ version = "2.0.0.dev10"
1918
1945
  source = { editable = "." }
1919
1946
  dependencies = [
1920
1947
  { name = "astropy" },
@@ -1928,6 +1955,8 @@ dev = [
1928
1955
  { name = "black" },
1929
1956
  { name = "pre-commit" },
1930
1957
  { name = "pyrefly" },
1958
+ { name = "pytest" },
1959
+ { name = "pytest-asyncio" },
1931
1960
  { name = "ruff" },
1932
1961
  ]
1933
1962
 
@@ -1943,6 +1972,8 @@ dev = [
1943
1972
  { name = "black", specifier = ">=25.1.0,<27" },
1944
1973
  { name = "pre-commit", specifier = ">=4.2.0,<5" },
1945
1974
  { name = "pyrefly", specifier = ">=1.1.1" },
1975
+ { name = "pytest", specifier = ">=8.0.0" },
1976
+ { name = "pytest-asyncio", specifier = ">=0.24.0" },
1946
1977
  { name = "ruff", specifier = ">=0.9.0" },
1947
1978
  ]
1948
1979
 
@@ -1965,6 +1996,35 @@ wheels = [
1965
1996
  { url = "https://files.pythonhosted.org/packages/1b/1c/df3cb0a2e5591660ded7a1836cd2f29dc48c91adb1c0a3a700a96f6d09e1/pyrefly-1.2.0-py3-none-win_arm64.whl", hash = "sha256:3a90bb8df39dfbac74b1f3b2e9d7c526b8f80568884c3944d955023a73ebf61e", size = 13430873, upload-time = "2026-08-01T02:56:25.425Z" },
1966
1997
  ]
1967
1998
 
1999
+ [[package]]
2000
+ name = "pytest"
2001
+ version = "9.1.1"
2002
+ source = { registry = "https://pypi.org/simple" }
2003
+ dependencies = [
2004
+ { name = "colorama", marker = "sys_platform == 'win32'" },
2005
+ { name = "iniconfig" },
2006
+ { name = "packaging" },
2007
+ { name = "pluggy" },
2008
+ { name = "pygments" },
2009
+ ]
2010
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
2011
+ wheels = [
2012
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
2013
+ ]
2014
+
2015
+ [[package]]
2016
+ name = "pytest-asyncio"
2017
+ version = "1.4.0"
2018
+ source = { registry = "https://pypi.org/simple" }
2019
+ dependencies = [
2020
+ { name = "pytest" },
2021
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
2022
+ ]
2023
+ sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
2024
+ wheels = [
2025
+ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
2026
+ ]
2027
+
1968
2028
  [[package]]
1969
2029
  name = "python-dateutil"
1970
2030
  version = "2.9.0.post0"
File without changes