arcsecond 3.16.1__tar.gz → 3.16.2__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.

Potentially problematic release.


This version of arcsecond might be problematic. Click here for more details.

Files changed (104) hide show
  1. {arcsecond-3.16.1 → arcsecond-3.16.2}/PKG-INFO +2 -2
  2. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/backups.py +12 -2
  3. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/local.py +6 -4
  4. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/opencv.py +30 -3
  5. {arcsecond-3.16.1 → arcsecond-3.16.2}/pyproject.toml +1 -1
  6. arcsecond-3.16.2/tests/test_imagesources_webcam.py +140 -0
  7. {arcsecond-3.16.1 → arcsecond-3.16.2}/uv.lock +1 -1
  8. {arcsecond-3.16.1 → arcsecond-3.16.2}/.docker/Dockerfile_postgres +0 -0
  9. {arcsecond-3.16.1 → arcsecond-3.16.2}/.docker/Dockerfile_redis +0 -0
  10. {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/dependabot.yml +0 -0
  11. {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/workflows/pythonpublish.yml +0 -0
  12. {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/workflows/tests.yml +0 -0
  13. {arcsecond-3.16.1 → arcsecond-3.16.2}/.gitignore +0 -0
  14. {arcsecond-3.16.1 → arcsecond-3.16.2}/LICENSE +0 -0
  15. {arcsecond-3.16.1 → arcsecond-3.16.2}/Makefile +0 -0
  16. {arcsecond-3.16.1 → arcsecond-3.16.2}/README.md +0 -0
  17. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/__init__.py +0 -0
  18. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/__version__.py +0 -0
  19. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/__init__.py +0 -0
  20. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/commands.py +0 -0
  21. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/dome_probe.py +0 -0
  22. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/__init__.py +0 -0
  23. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/config.py +0 -0
  24. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/constants.py +0 -0
  25. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/endpoint.py +0 -0
  26. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/main.py +0 -0
  27. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/resources.py +0 -0
  28. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cli.py +0 -0
  29. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/__init__.py +0 -0
  30. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/auth.py +0 -0
  31. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/resources.py +0 -0
  32. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/__init__.py +0 -0
  33. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/constants.py +0 -0
  34. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/context.py +0 -0
  35. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  36. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  37. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  38. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  39. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  40. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/errors.py +0 -0
  41. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/logger.py +0 -0
  42. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/uploader.py +0 -0
  43. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/utils.py +0 -0
  44. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/walker.py +0 -0
  45. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploads.py +0 -0
  46. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/errors.py +0 -0
  47. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/__init__.py +0 -0
  48. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/checks.py +0 -0
  49. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/constants.py +0 -0
  50. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/database.py +0 -0
  51. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/__init__.py +0 -0
  52. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/constants.py +0 -0
  53. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/containers.py +0 -0
  54. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/docker-compose.yml +0 -0
  55. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/images.py +0 -0
  56. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/utils.py +0 -0
  57. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/__init__.py +0 -0
  58. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/client.py +0 -0
  59. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/utils.py +0 -0
  60. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/main.py +0 -0
  61. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/postgres/init-db.sh +0 -0
  62. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/setup.py +0 -0
  63. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/utils.py +0 -0
  64. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/validation.py +0 -0
  65. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/__init__.py +0 -0
  66. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/commands.py +0 -0
  67. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/proxy.py +0 -0
  68. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/registry.py +0 -0
  69. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/__init__.py +0 -0
  70. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/base.py +0 -0
  71. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/filewatch.py +0 -0
  72. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/network.py +0 -0
  73. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/store.py +0 -0
  74. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/options.py +0 -0
  75. {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/targets.py +0 -0
  76. {arcsecond-3.16.1 → arcsecond-3.16.2}/examples/example_upload_files.py +0 -0
  77. {arcsecond-3.16.1 → arcsecond-3.16.2}/requirements.txt +0 -0
  78. {arcsecond-3.16.1 → arcsecond-3.16.2}/setup.cfg +0 -0
  79. {arcsecond-3.16.1 → arcsecond-3.16.2}/setup.py +0 -0
  80. {arcsecond-3.16.1 → arcsecond-3.16.2}/sonar-project.properties +0 -0
  81. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/__init__.py +0 -0
  82. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/__init__.py +0 -0
  83. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_api.py +0 -0
  84. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_api_endpoint.py +0 -0
  85. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_config.py +0 -0
  86. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_targets.py +0 -0
  87. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/__init__.py +0 -0
  88. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/__init__.py +0 -0
  89. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/__init__.py +0 -0
  90. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  91. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  92. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  93. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  94. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  95. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/conftest.py +0 -0
  96. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/fixtures/file1.fits +0 -0
  97. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_alpaca_probe.py +0 -0
  98. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_cli.py +0 -0
  99. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_hosting_database.py +0 -0
  100. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_hosting_local.py +0 -0
  101. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_imagesources_netcam.py +0 -0
  102. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_imagesources_store.py +0 -0
  103. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_targets_planning.py +0 -0
  104. {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: arcsecond
3
- Version: 3.16.1
3
+ Version: 3.16.2
4
4
  Summary: CLI for arcsecond.io
5
5
  Project-URL: Homepage, https://github.com/arcsecond-io/cli
6
6
  Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
@@ -331,7 +331,15 @@ def _run_destination_backups_command(*args, timeout=60):
331
331
  return None
332
332
  try:
333
333
  out = subprocess.run(
334
- ["docker", "exec", API_CONTAINER, "python", "manage.py", "destination_backups", *args],
334
+ [
335
+ "docker",
336
+ "exec",
337
+ API_CONTAINER,
338
+ "python",
339
+ "manage.py",
340
+ "destination_backups",
341
+ *args,
342
+ ],
335
343
  capture_output=True,
336
344
  text=True,
337
345
  check=False,
@@ -367,7 +375,9 @@ def _remote_only_items(items, remote_names):
367
375
  if not m or name in local_names:
368
376
  continue
369
377
  try:
370
- ts = datetime.strptime(m.group(1), "%Y%m%d-%H%M%S").replace(tzinfo=timezone.utc)
378
+ ts = datetime.strptime(m.group(1), "%Y%m%d-%H%M%S").replace(
379
+ tzinfo=timezone.utc
380
+ )
371
381
  except ValueError:
372
382
  continue
373
383
  result.append((name, ts))
@@ -228,7 +228,7 @@ def _extract_optional_service_block(packaged_text, name):
228
228
  end_index = next((i for i, line in enumerate(lines) if line.strip() == end), None)
229
229
  if begin_index is None or end_index is None or end_index < begin_index:
230
230
  return None
231
- return lines[begin_index:end_index + 1]
231
+ return lines[begin_index : end_index + 1]
232
232
 
233
233
 
234
234
  def _with_trailing_newline_like(lines, original_text):
@@ -244,7 +244,7 @@ def _strip_optional_service_block(text, name):
244
244
  end_index = next((i for i, line in enumerate(lines) if line.strip() == end), None)
245
245
  if begin_index is None or end_index is None or end_index < begin_index:
246
246
  return text
247
- del lines[begin_index:end_index + 1]
247
+ del lines[begin_index : end_index + 1]
248
248
  # Drop the blank separator the block carried, so strip(splice(x)) == x.
249
249
  if begin_index < len(lines) and not lines[begin_index].strip():
250
250
  del lines[begin_index]
@@ -297,12 +297,14 @@ def _reconcile_version_header(current_text, expected_text):
297
297
  updated = (
298
298
  current_text[: current_match.start()]
299
299
  + expected_match.group(0)
300
- + current_text[current_match.end():]
300
+ + current_text[current_match.end() :]
301
301
  )
302
302
  return updated if updated == expected_text else None
303
303
 
304
304
 
305
- def write_docker_compose_file(enabled_services=frozenset(), removed_services=frozenset()) -> Path:
305
+ def write_docker_compose_file(
306
+ enabled_services=frozenset(), removed_services=frozenset()
307
+ ) -> Path:
306
308
  """
307
309
  Materialise the packaged docker-compose.yml in the current directory.
308
310
 
@@ -7,6 +7,7 @@ Designed for USB webcams attached to the host running the proxy.
7
7
 
8
8
  import asyncio
9
9
  import logging
10
+ import sys
10
11
  from typing import Optional
11
12
 
12
13
  from .base import FrameSource, SourceInfo
@@ -18,6 +19,28 @@ _JPEG_QUALITY = 60 # 0-100
18
19
  _MAX_PROBE = 10 # device indices to probe during detection
19
20
 
20
21
 
22
+ def _capture_backend():
23
+ """The OpenCV backend to open device *indices* with, for this platform.
24
+
25
+ Never ``CAP_ANY``. Left to choose for itself, OpenCV walks its backend list
26
+ and — on a machine with no camera at that index — ends up at FFMPEG, which
27
+ interprets an integer index by asking libavdevice to enumerate DirectShow
28
+ devices. That path is slow, cannot work, and prints a wall of
29
+ ``Could not enumerate audio only devices`` to stderr for every index probed.
30
+
31
+ Pinning the platform's native backend also keeps index numbering stable:
32
+ detection and ``open()`` must agree on what device index 1 means, and each
33
+ backend enumerates in its own order.
34
+ """
35
+ import cv2
36
+
37
+ if sys.platform == "win32":
38
+ return cv2.CAP_DSHOW
39
+ if sys.platform == "darwin":
40
+ return cv2.CAP_AVFOUNDATION
41
+ return cv2.CAP_V4L2
42
+
43
+
21
44
  class OpenCVWebcamSource(FrameSource):
22
45
  kind = "webcam"
23
46
  poll_interval = _FRAME_INTERVAL
@@ -29,10 +52,13 @@ class OpenCVWebcamSource(FrameSource):
29
52
  self._cap = None
30
53
 
31
54
  async def open(self) -> None:
32
- import cv2
55
+ import cv2 # noqa: F401
33
56
 
34
57
  loop = asyncio.get_running_loop()
35
- self._cap = await loop.run_in_executor(None, cv2.VideoCapture, self.index)
58
+ backend = _capture_backend()
59
+ self._cap = await loop.run_in_executor(
60
+ None, cv2.VideoCapture, self.index, backend
61
+ )
36
62
  if not await loop.run_in_executor(None, self._cap.isOpened):
37
63
  raise RuntimeError(f"Cannot open webcam at device index {self.index}.")
38
64
 
@@ -75,9 +101,10 @@ def detect_webcams(max_index: int = _MAX_PROBE) -> list[SourceInfo]:
75
101
  """Blocking probe of device indices 0..max_index-1."""
76
102
  import cv2
77
103
 
104
+ backend = _capture_backend()
78
105
  found: list[SourceInfo] = []
79
106
  for i in range(max_index):
80
- cap = cv2.VideoCapture(i)
107
+ cap = cv2.VideoCapture(i, backend)
81
108
  if not cap.isOpened():
82
109
  cap.release()
83
110
  continue
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arcsecond"
7
- version = "3.16.1"
7
+ version = "3.16.2"
8
8
  authors = [
9
9
  { name = "Cedric Foellmi", email = "cedric@arcsecond.io" },
10
10
  ]
@@ -0,0 +1,140 @@
1
+ """Tests for the OpenCV webcam source (``webcam:``).
2
+
3
+ The point of most of these is the capture *backend*. Device indices must never
4
+ be opened with ``CAP_ANY``: OpenCV then falls through its backend list to
5
+ FFMPEG, which tries to enumerate DirectShow devices, floods stderr and cannot
6
+ succeed. Detection and ``open()`` must also agree on the backend, or they would
7
+ not agree on what device index 1 refers to.
8
+ """
9
+
10
+ import asyncio
11
+ import sys
12
+ from unittest.mock import MagicMock, patch
13
+
14
+ from arcsecond.imagesources.sources.opencv import (
15
+ OpenCVWebcamSource,
16
+ _capture_backend,
17
+ detect_webcams,
18
+ )
19
+
20
+
21
+ def _fake_cv2():
22
+ cv2 = MagicMock()
23
+ cv2.CAP_ANY = 0
24
+ cv2.CAP_DSHOW = 700
25
+ cv2.CAP_AVFOUNDATION = 1200
26
+ cv2.CAP_V4L2 = 200
27
+ cv2.CAP_PROP_FRAME_WIDTH = 3
28
+ cv2.CAP_PROP_FRAME_HEIGHT = 4
29
+ cv2.CAP_PROP_FPS = 5
30
+ cv2.IMWRITE_JPEG_QUALITY = 1
31
+ cv2.VideoCapture.return_value.isOpened.return_value = True
32
+ cv2.VideoCapture.return_value.get.return_value = 0
33
+ return cv2
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Backend selection
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ def test_capture_backend_is_dshow_on_windows():
42
+ cv2 = _fake_cv2()
43
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
44
+ assert _capture_backend() == cv2.CAP_DSHOW
45
+
46
+
47
+ def test_capture_backend_is_avfoundation_on_macos():
48
+ cv2 = _fake_cv2()
49
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "darwin"):
50
+ assert _capture_backend() == cv2.CAP_AVFOUNDATION
51
+
52
+
53
+ def test_capture_backend_is_v4l2_on_linux():
54
+ cv2 = _fake_cv2()
55
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
56
+ assert _capture_backend() == cv2.CAP_V4L2
57
+
58
+
59
+ def test_capture_backend_is_never_cap_any():
60
+ cv2 = _fake_cv2()
61
+ for platform in ("win32", "darwin", "linux", "freebsd14"):
62
+ with (
63
+ patch.dict(sys.modules, {"cv2": cv2}),
64
+ patch.object(sys, "platform", platform),
65
+ ):
66
+ assert _capture_backend() != cv2.CAP_ANY
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Detection
71
+ # ---------------------------------------------------------------------------
72
+
73
+
74
+ def test_detect_webcams_pins_the_backend_on_every_probe():
75
+ cv2 = _fake_cv2()
76
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
77
+ detect_webcams(max_index=3)
78
+
79
+ assert cv2.VideoCapture.call_count == 3
80
+ for i, call in enumerate(cv2.VideoCapture.call_args_list):
81
+ assert call.args == (i, cv2.CAP_DSHOW)
82
+
83
+
84
+ def test_detect_webcams_reports_every_open_index():
85
+ cv2 = _fake_cv2()
86
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
87
+ found = detect_webcams(max_index=2)
88
+
89
+ assert [i.id for i in found] == ["webcam:0", "webcam:1"]
90
+ assert all(i.kind == "webcam" for i in found)
91
+ assert [i.extra["index"] for i in found] == [0, 1]
92
+
93
+
94
+ def test_detect_webcams_skips_indices_that_do_not_open():
95
+ cv2 = _fake_cv2()
96
+ cv2.VideoCapture.return_value.isOpened.side_effect = [False, True, False]
97
+
98
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
99
+ found = detect_webcams(max_index=3)
100
+
101
+ assert [i.id for i in found] == ["webcam:1"]
102
+ # Every probe is released, including the ones that never opened.
103
+ assert cv2.VideoCapture.return_value.release.call_count == 3
104
+
105
+
106
+ def test_detect_webcams_finds_nothing_when_no_device_opens():
107
+ cv2 = _fake_cv2()
108
+ cv2.VideoCapture.return_value.isOpened.return_value = False
109
+
110
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
111
+ assert detect_webcams(max_index=4) == []
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Opening a source
116
+ # ---------------------------------------------------------------------------
117
+
118
+
119
+ def test_open_uses_the_same_backend_as_detection():
120
+ cv2 = _fake_cv2()
121
+ source = OpenCVWebcamSource(2)
122
+
123
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
124
+ asyncio.run(source.open())
125
+
126
+ cv2.VideoCapture.assert_called_once_with(2, cv2.CAP_DSHOW)
127
+
128
+
129
+ def test_open_raises_when_the_device_will_not_open():
130
+ cv2 = _fake_cv2()
131
+ cv2.VideoCapture.return_value.isOpened.return_value = False
132
+ source = OpenCVWebcamSource(0)
133
+
134
+ with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
135
+ try:
136
+ asyncio.run(source.open())
137
+ except RuntimeError as e:
138
+ assert "device index 0" in str(e)
139
+ else:
140
+ raise AssertionError("expected open() to raise")
@@ -155,7 +155,7 @@ wheels = [
155
155
 
156
156
  [[package]]
157
157
  name = "arcsecond"
158
- version = "3.16.1"
158
+ version = "3.16.2"
159
159
  source = { editable = "." }
160
160
  dependencies = [
161
161
  { name = "alpyca" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes