axis 56__tar.gz → 57__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 (108) hide show
  1. {axis-56 → axis-57}/PKG-INFO +5 -5
  2. {axis-56 → axis-57}/axis/__init__.py +1 -1
  3. {axis-56 → axis-57}/axis/__main__.py +0 -1
  4. {axis-56 → axis-57}/axis/errors.py +5 -5
  5. {axis-56 → axis-57}/axis/models/event.py +6 -6
  6. {axis-56 → axis-57}/axis/vapix/interfaces/api_handler.py +3 -1
  7. {axis-56 → axis-57}/axis/vapix/interfaces/applications/__init__.py +1 -1
  8. {axis-56 → axis-57}/axis/vapix/models/parameters/image.py +1 -1
  9. {axis-56 → axis-57}/axis/vapix/models/parameters/ptz.py +4 -4
  10. {axis-56 → axis-57}/axis/vapix/models/pwdgrp_cgi.py +3 -9
  11. {axis-56 → axis-57}/axis/vapix/vapix.py +1 -2
  12. {axis-56 → axis-57}/axis.egg-info/PKG-INFO +5 -5
  13. {axis-56 → axis-57}/axis.egg-info/requires.txt +3 -3
  14. {axis-56 → axis-57}/pyproject.toml +38 -40
  15. {axis-56 → axis-57}/setup.py +1 -1
  16. {axis-56 → axis-57}/tests/test_light_control.py +7 -7
  17. {axis-56 → axis-57}/tests/test_rtsp.py +102 -95
  18. {axis-56 → axis-57}/tests/test_vapix.py +16 -4
  19. {axis-56 → axis-57}/LICENSE +0 -0
  20. {axis-56 → axis-57}/README.md +0 -0
  21. {axis-56 → axis-57}/axis/configuration.py +0 -0
  22. {axis-56 → axis-57}/axis/device.py +0 -0
  23. {axis-56 → axis-57}/axis/event_stream.py +0 -0
  24. {axis-56 → axis-57}/axis/models/__init__.py +0 -0
  25. {axis-56 → axis-57}/axis/py.typed +0 -0
  26. {axis-56 → axis-57}/axis/rtsp.py +0 -0
  27. {axis-56 → axis-57}/axis/stream_manager.py +0 -0
  28. {axis-56 → axis-57}/axis/vapix/__init__.py +0 -0
  29. {axis-56 → axis-57}/axis/vapix/interfaces/__init__.py +0 -0
  30. {axis-56 → axis-57}/axis/vapix/interfaces/api_discovery.py +0 -0
  31. {axis-56 → axis-57}/axis/vapix/interfaces/applications/application_handler.py +0 -0
  32. {axis-56 → axis-57}/axis/vapix/interfaces/applications/applications.py +0 -0
  33. {axis-56 → axis-57}/axis/vapix/interfaces/applications/fence_guard.py +0 -0
  34. {axis-56 → axis-57}/axis/vapix/interfaces/applications/loitering_guard.py +0 -0
  35. {axis-56 → axis-57}/axis/vapix/interfaces/applications/motion_guard.py +0 -0
  36. {axis-56 → axis-57}/axis/vapix/interfaces/applications/object_analytics.py +0 -0
  37. {axis-56 → axis-57}/axis/vapix/interfaces/applications/vmd4.py +0 -0
  38. {axis-56 → axis-57}/axis/vapix/interfaces/basic_device_info.py +0 -0
  39. {axis-56 → axis-57}/axis/vapix/interfaces/event_instances.py +0 -0
  40. {axis-56 → axis-57}/axis/vapix/interfaces/light_control.py +0 -0
  41. {axis-56 → axis-57}/axis/vapix/interfaces/mqtt.py +0 -0
  42. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/__init__.py +0 -0
  43. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/brand.py +0 -0
  44. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/image.py +0 -0
  45. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/io_port.py +0 -0
  46. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/param_cgi.py +0 -0
  47. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/param_handler.py +0 -0
  48. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/properties.py +0 -0
  49. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/ptz.py +0 -0
  50. {axis-56 → axis-57}/axis/vapix/interfaces/parameters/stream_profile.py +0 -0
  51. {axis-56 → axis-57}/axis/vapix/interfaces/pir_sensor_configuration.py +0 -0
  52. {axis-56 → axis-57}/axis/vapix/interfaces/port_cgi.py +0 -0
  53. {axis-56 → axis-57}/axis/vapix/interfaces/port_management.py +0 -0
  54. {axis-56 → axis-57}/axis/vapix/interfaces/ptz.py +0 -0
  55. {axis-56 → axis-57}/axis/vapix/interfaces/pwdgrp_cgi.py +0 -0
  56. {axis-56 → axis-57}/axis/vapix/interfaces/stream_profiles.py +0 -0
  57. {axis-56 → axis-57}/axis/vapix/interfaces/user_groups.py +0 -0
  58. {axis-56 → axis-57}/axis/vapix/interfaces/view_areas.py +0 -0
  59. {axis-56 → axis-57}/axis/vapix/models/__init__.py +0 -0
  60. {axis-56 → axis-57}/axis/vapix/models/api.py +0 -0
  61. {axis-56 → axis-57}/axis/vapix/models/api_discovery.py +0 -0
  62. {axis-56 → axis-57}/axis/vapix/models/applications/__init__.py +0 -0
  63. {axis-56 → axis-57}/axis/vapix/models/applications/application.py +0 -0
  64. {axis-56 → axis-57}/axis/vapix/models/applications/fence_guard.py +0 -0
  65. {axis-56 → axis-57}/axis/vapix/models/applications/loitering_guard.py +0 -0
  66. {axis-56 → axis-57}/axis/vapix/models/applications/motion_guard.py +0 -0
  67. {axis-56 → axis-57}/axis/vapix/models/applications/object_analytics.py +0 -0
  68. {axis-56 → axis-57}/axis/vapix/models/applications/vmd4.py +0 -0
  69. {axis-56 → axis-57}/axis/vapix/models/basic_device_info.py +0 -0
  70. {axis-56 → axis-57}/axis/vapix/models/event_instance.py +0 -0
  71. {axis-56 → axis-57}/axis/vapix/models/light_control.py +0 -0
  72. {axis-56 → axis-57}/axis/vapix/models/mqtt.py +0 -0
  73. {axis-56 → axis-57}/axis/vapix/models/parameters/__init__.py +0 -0
  74. {axis-56 → axis-57}/axis/vapix/models/parameters/brand.py +0 -0
  75. {axis-56 → axis-57}/axis/vapix/models/parameters/io_port.py +0 -0
  76. {axis-56 → axis-57}/axis/vapix/models/parameters/param_cgi.py +0 -0
  77. {axis-56 → axis-57}/axis/vapix/models/parameters/properties.py +0 -0
  78. {axis-56 → axis-57}/axis/vapix/models/parameters/stream_profile.py +0 -0
  79. {axis-56 → axis-57}/axis/vapix/models/pir_sensor_configuration.py +0 -0
  80. {axis-56 → axis-57}/axis/vapix/models/port_cgi.py +0 -0
  81. {axis-56 → axis-57}/axis/vapix/models/port_management.py +0 -0
  82. {axis-56 → axis-57}/axis/vapix/models/ptz_cgi.py +0 -0
  83. {axis-56 → axis-57}/axis/vapix/models/stream_profile.py +0 -0
  84. {axis-56 → axis-57}/axis/vapix/models/user_group.py +0 -0
  85. {axis-56 → axis-57}/axis/vapix/models/view_area.py +0 -0
  86. {axis-56 → axis-57}/axis.egg-info/SOURCES.txt +0 -0
  87. {axis-56 → axis-57}/axis.egg-info/dependency_links.txt +0 -0
  88. {axis-56 → axis-57}/axis.egg-info/entry_points.txt +0 -0
  89. {axis-56 → axis-57}/axis.egg-info/top_level.txt +0 -0
  90. {axis-56 → axis-57}/setup.cfg +0 -0
  91. {axis-56 → axis-57}/tests/test_api_discovery.py +0 -0
  92. {axis-56 → axis-57}/tests/test_api_handler.py +0 -0
  93. {axis-56 → axis-57}/tests/test_basic_device_info.py +0 -0
  94. {axis-56 → axis-57}/tests/test_configuration.py +0 -0
  95. {axis-56 → axis-57}/tests/test_device.py +0 -0
  96. {axis-56 → axis-57}/tests/test_event.py +0 -0
  97. {axis-56 → axis-57}/tests/test_event_instances.py +0 -0
  98. {axis-56 → axis-57}/tests/test_event_stream.py +0 -0
  99. {axis-56 → axis-57}/tests/test_mqtt.py +0 -0
  100. {axis-56 → axis-57}/tests/test_pir_sensor_configuration.py +0 -0
  101. {axis-56 → axis-57}/tests/test_port_cgi.py +0 -0
  102. {axis-56 → axis-57}/tests/test_port_management.py +0 -0
  103. {axis-56 → axis-57}/tests/test_ptz.py +0 -0
  104. {axis-56 → axis-57}/tests/test_pwdgrp_cgi.py +0 -0
  105. {axis-56 → axis-57}/tests/test_stream_manager.py +0 -0
  106. {axis-56 → axis-57}/tests/test_stream_profiles.py +0 -0
  107. {axis-56 → axis-57}/tests/test_user_groups.py +0 -0
  108. {axis-56 → axis-57}/tests/test_view_areas.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: axis
3
- Version: 56
3
+ Version: 57
4
4
  Summary: A Python library for communicating with devices from Axis Communications
5
5
  Home-page: https://github.com/Kane610/axis
6
- Download-URL: https://github.com/Kane610/axis/archive/v56.tar.gz
6
+ Download-URL: https://github.com/Kane610/axis/archive/v57.tar.gz
7
7
  Author: Robert Svensson
8
8
  Author-email: Robert Svensson <Kane610@users.noreply.github.com>
9
9
  License: MIT
@@ -33,10 +33,10 @@ Provides-Extra: requirements-test
33
33
  Requires-Dist: mypy==1.9.0; extra == "requirements-test"
34
34
  Requires-Dist: pytest==8.1.1; extra == "requirements-test"
35
35
  Requires-Dist: pytest-aiohttp==1.0.5; extra == "requirements-test"
36
- Requires-Dist: pytest-asyncio==0.23.5.post1; extra == "requirements-test"
36
+ Requires-Dist: pytest-asyncio==0.23.6; extra == "requirements-test"
37
37
  Requires-Dist: pytest-cov==4.1.0; extra == "requirements-test"
38
- Requires-Dist: respx==0.20.2; extra == "requirements-test"
39
- Requires-Dist: ruff==0.3.2; extra == "requirements-test"
38
+ Requires-Dist: respx==0.21.0; extra == "requirements-test"
39
+ Requires-Dist: ruff==0.3.3; extra == "requirements-test"
40
40
  Requires-Dist: types-orjson==3.6.2; extra == "requirements-test"
41
41
  Requires-Dist: types-xmltodict==v0.13.0.3; extra == "requirements-test"
42
42
  Provides-Extra: requirements-dev
@@ -1,4 +1,4 @@
1
1
  """Library to communicate with a Axis device."""
2
2
 
3
3
  from .device import AxisDevice # noqa: F401
4
- from .errors import * # noqa: F401, F403
4
+ from .errors import * # noqa: F403
@@ -76,7 +76,6 @@ async def main(
76
76
 
77
77
  except asyncio.CancelledError:
78
78
  device.stream.stop()
79
- pass
80
79
 
81
80
  finally:
82
81
  await device.config.session.aclose()
@@ -20,6 +20,10 @@ class Unauthorized(AxisException):
20
20
  """Username is not authorized."""
21
21
 
22
22
 
23
+ class Forbidden(AxisException):
24
+ """Endpoint is not accessible due to low permissions."""
25
+
26
+
23
27
  class LoginRequired(AxisException):
24
28
  """User is logged out."""
25
29
 
@@ -32,11 +36,7 @@ class PathNotFound(AxisException):
32
36
  """Path not found."""
33
37
 
34
38
 
35
- class NoPermission(AxisException):
36
- """Users permissions are not high enough."""
37
-
38
-
39
- ERRORS = {401: Unauthorized, 404: PathNotFound, 405: MethodNotAllowed}
39
+ ERRORS = {401: Unauthorized, 403: Forbidden, 404: PathNotFound, 405: MethodNotAllowed}
40
40
 
41
41
 
42
42
  def raise_error(error: int) -> None:
@@ -100,12 +100,12 @@ EVENT_TYPE = "type"
100
100
  EVENT_VALUE = "value"
101
101
 
102
102
  NOTIFICATION_MESSAGE = ("MetadataStream", "Event", "NotificationMessage")
103
- MESSAGE = NOTIFICATION_MESSAGE + ("Message", "Message")
104
- TOPIC = NOTIFICATION_MESSAGE + ("Topic", "#text")
105
- TIMESTAMP = MESSAGE + ("@UtcTime",)
106
- OPERATION = MESSAGE + ("@PropertyOperation",)
107
- SOURCE = MESSAGE + ("Source",)
108
- DATA = MESSAGE + ("Data",)
103
+ MESSAGE = (*NOTIFICATION_MESSAGE, "Message", "Message")
104
+ TOPIC = (*NOTIFICATION_MESSAGE, "Topic", "#text")
105
+ TIMESTAMP = (*MESSAGE, "@UtcTime")
106
+ OPERATION = (*MESSAGE, "@PropertyOperation")
107
+ SOURCE = (*MESSAGE, "Source")
108
+ DATA = (*MESSAGE, "Data")
109
109
 
110
110
  XML_NAMESPACES = {
111
111
  "http://www.onvif.org/ver10/schema": None,
@@ -11,7 +11,7 @@ from collections.abc import (
11
11
  )
12
12
  from typing import TYPE_CHECKING, Generic, final
13
13
 
14
- from ...errors import PathNotFound, Unauthorized
14
+ from ...errors import Forbidden, PathNotFound, Unauthorized
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from ..models.api_discovery import ApiId
@@ -126,6 +126,8 @@ class ApiHandler(SubscriptionHandler, Generic[ApiItemT]):
126
126
  obj_ids = await self._update()
127
127
  except Unauthorized: # Probably a viewer account
128
128
  return False
129
+ except Forbidden: # Invalid permissions
130
+ return False
129
131
  except PathNotFound: # Device doesn't support the endpoint
130
132
  return False
131
133
  for obj_id in obj_ids:
@@ -1,3 +1,3 @@
1
1
  """Control stand alone applications of an Axis device."""
2
2
 
3
- from .applications import * # noqa: F401, F403
3
+ from .applications import * # noqa: F403
@@ -159,7 +159,7 @@ class ImageParam(ParamItem):
159
159
  """Create objects from dict."""
160
160
  image_data = [ # Rename channels I0-I19 to 0-19
161
161
  (str(id), data[0][channel_id])
162
- for id in range(0, 20)
162
+ for id in range(20)
163
163
  if (channel_id := f"I{id}") in data[0]
164
164
  ]
165
165
  return {v.id: v for v in cls.decode_to_list(image_data)}
@@ -235,13 +235,13 @@ class PtzSupport:
235
235
  auto_ir_cut_filter: bool
236
236
  auto_iris: bool
237
237
  auxiliary: bool
238
- backLight: bool
238
+ back_light: bool
239
239
  continuous_brightness: bool
240
240
  continuous_focus: bool
241
241
  continuous_iris: bool
242
242
  continuous_pan: bool
243
243
  continuous_tilt: bool
244
- continuousZoom: bool
244
+ continuous_zoom: bool
245
245
  device_preset: bool
246
246
  digital_zoom: bool
247
247
  generic_http: bool
@@ -275,13 +275,13 @@ class PtzSupport:
275
275
  auto_ir_cut_filter=data["AutoIrCutFilter"],
276
276
  auto_iris=data["AutoIris"],
277
277
  auxiliary=data["Auxiliary"],
278
- backLight=data["BackLight"],
278
+ back_light=data["BackLight"],
279
279
  continuous_brightness=data["ContinuousBrightness"],
280
280
  continuous_focus=data["ContinuousFocus"],
281
281
  continuous_iris=data["ContinuousIris"],
282
282
  continuous_pan=data["ContinuousPan"],
283
283
  continuous_tilt=data["ContinuousTilt"],
284
- continuousZoom=data["ContinuousZoom"],
284
+ continuous_zoom=data["ContinuousZoom"],
285
285
  device_preset=data["DevicePreset"],
286
286
  digital_zoom=data["DigitalZoom"],
287
287
  generic_http=data["GenericHTTP"],
@@ -52,18 +52,12 @@ class User(ApiItem):
52
52
  @property
53
53
  def privileges(self) -> SecondaryGroup:
54
54
  """Return highest privileged role supported."""
55
- if self.admin and self.ptz:
56
- return SecondaryGroup.ADMIN_PTZ
57
55
  if self.admin:
58
- return SecondaryGroup.ADMIN
59
- if self.operator and self.ptz:
60
- return SecondaryGroup.OPERATOR_PTZ
56
+ return SecondaryGroup.ADMIN_PTZ if self.ptz else SecondaryGroup.ADMIN
61
57
  if self.operator:
62
- return SecondaryGroup.OPERATOR
63
- if self.viewer and self.ptz:
64
- return SecondaryGroup.VIEWER_PTZ
58
+ return SecondaryGroup.OPERATOR_PTZ if self.ptz else SecondaryGroup.OPERATOR
65
59
  if self.viewer:
66
- return SecondaryGroup.VIEWER
60
+ return SecondaryGroup.VIEWER_PTZ if self.ptz else SecondaryGroup.VIEWER
67
61
  return SecondaryGroup.UNKNOWN
68
62
 
69
63
  @classmethod
@@ -205,9 +205,8 @@ class Vapix:
205
205
 
206
206
  async def initialize_applications(self) -> None:
207
207
  """Load data for applications on device."""
208
- if not self.applications.supported:
208
+ if not self.applications.supported or not await self.applications.update():
209
209
  return
210
- await self.applications.update()
211
210
 
212
211
  apps: tuple[ApiHandler[Any], ...] = (
213
212
  self.fence_guard,
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: axis
3
- Version: 56
3
+ Version: 57
4
4
  Summary: A Python library for communicating with devices from Axis Communications
5
5
  Home-page: https://github.com/Kane610/axis
6
- Download-URL: https://github.com/Kane610/axis/archive/v56.tar.gz
6
+ Download-URL: https://github.com/Kane610/axis/archive/v57.tar.gz
7
7
  Author: Robert Svensson
8
8
  Author-email: Robert Svensson <Kane610@users.noreply.github.com>
9
9
  License: MIT
@@ -33,10 +33,10 @@ Provides-Extra: requirements-test
33
33
  Requires-Dist: mypy==1.9.0; extra == "requirements-test"
34
34
  Requires-Dist: pytest==8.1.1; extra == "requirements-test"
35
35
  Requires-Dist: pytest-aiohttp==1.0.5; extra == "requirements-test"
36
- Requires-Dist: pytest-asyncio==0.23.5.post1; extra == "requirements-test"
36
+ Requires-Dist: pytest-asyncio==0.23.6; extra == "requirements-test"
37
37
  Requires-Dist: pytest-cov==4.1.0; extra == "requirements-test"
38
- Requires-Dist: respx==0.20.2; extra == "requirements-test"
39
- Requires-Dist: ruff==0.3.2; extra == "requirements-test"
38
+ Requires-Dist: respx==0.21.0; extra == "requirements-test"
39
+ Requires-Dist: ruff==0.3.3; extra == "requirements-test"
40
40
  Requires-Dist: types-orjson==3.6.2; extra == "requirements-test"
41
41
  Requires-Dist: types-xmltodict==v0.13.0.3; extra == "requirements-test"
42
42
  Provides-Extra: requirements-dev
@@ -16,9 +16,9 @@ pre-commit==3.6.2
16
16
  mypy==1.9.0
17
17
  pytest==8.1.1
18
18
  pytest-aiohttp==1.0.5
19
- pytest-asyncio==0.23.5.post1
19
+ pytest-asyncio==0.23.6
20
20
  pytest-cov==4.1.0
21
- respx==0.20.2
22
- ruff==0.3.2
21
+ respx==0.21.0
22
+ ruff==0.3.3
23
23
  types-orjson==3.6.2
24
24
  types-xmltodict==v0.13.0.3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "axis"
7
- version = "56"
7
+ version = "57"
8
8
  license = {text = "MIT"}
9
9
  description = "A Python library for communicating with devices from Axis Communications"
10
10
  readme = "README.md"
@@ -37,10 +37,10 @@ requirements_test = [
37
37
  "mypy==1.9.0",
38
38
  "pytest==8.1.1",
39
39
  "pytest-aiohttp==1.0.5",
40
- "pytest-asyncio==0.23.5.post1",
40
+ "pytest-asyncio==0.23.6",
41
41
  "pytest-cov==4.1.0",
42
- "respx==0.20.2",
43
- "ruff==0.3.2",
42
+ "respx==0.21.0",
43
+ "ruff==0.3.3",
44
44
  "types-orjson==3.6.2",
45
45
  "types-xmltodict==v0.13.0.3",
46
46
  ]
@@ -97,54 +97,52 @@ testpaths = ["tests"]
97
97
  target-version = "py311"
98
98
  lint.select = [
99
99
  "B002", # Python does not support the unary prefix increment
100
+ "B005", # Using .strip() with multi-character strings is misleading
100
101
  "B007", # Loop control variable {name} not used within loop body
101
102
  "B014", # Exception handler with duplicate exception
103
+ "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it.
104
+ "B018", # Found useless attribute access. Either assign it to a variable or remove it.
102
105
  "B023", # Function definition does not bind loop variable {name}
103
106
  "B026", # Star-arg unpacking after a keyword argument is strongly discouraged
104
- "C", # complexity
107
+ "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)?
108
+ "C", # complexity
105
109
  "COM818", # Trailing comma on bare tuple prohibited
106
- "D", # docstrings
107
- "E", # pycodestyle
108
- "F", # pyflakes/autoflake
109
- "G", # flake8-logging-format
110
- "I", # isort
110
+ "D", # docstrings
111
+ "E", # pycodestyle
112
+ "F", # pyflakes/autoflake
113
+ "G", # flake8-logging-format
114
+ "I", # isort
111
115
  "ICN001", # import concentions; {name} should be imported as {asname}
112
- # "ISC001", # Implicitly concatenated string literals on one line
113
- "N804", # First argument of a class method should be named cls
114
- "N805", # First argument of a method should be named self
115
- # "N815", # Variable {name} in class scope should not be mixedCase
116
- "PERF", # A Linter for performance anti-patterns
117
- "PGH004", # Use specific rule codes when using noqa
118
- "PLC0414", # Useless import alias. Import alias does not rename original package.
119
- "PLC", # pylint
120
- "PLE", # pylint
121
- "PLR", # pylint
122
- "PLW", # pylint
116
+ "ISC", # flake8-implicit-str-concat
117
+ "LOG", # flake8-logging
118
+ "N804", # First argument of a class method should be named cls
119
+ "N805", # First argument of a method should be named self
120
+ "N815", # Variable {name} in class scope should not be mixedCase
121
+ "PERF", # A Linter for performance anti-patterns
122
+ "PGH004", # Use specific rule codes when using noqa
123
+ "PIE", # flake8-pie
124
+ "PL", # pylint
125
+ "PERF", # Perflint
126
+ "RSE", # flake8-raise
127
+ "RUF005", # Consider iterable unpacking instead of concatenation
123
128
  "RUF006", # Store a reference to the return value of asyncio.create_task
124
- "S307", # No builtin eval() allowed
125
- # "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
126
- "SIM117", # Merge with-statements that use the same scope
127
- "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
128
- "SIM201", # Use {left} != {right} instead of not {left} == {right}
129
- "SIM208", # Use {expr} instead of not (not {expr})
130
- "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
131
- "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
132
- "SIM401", # Use get from dict with default instead of an if block
129
+ "RUF100", # Unused `noqa` directive
130
+ "S307", # No builtin eval() allowed
131
+ "SIM", # flake8-simplify
133
132
  "T100", # Trace found: {name} used
134
133
  "T20", # flake8-print
135
- "UP", # pyupgrade
136
- "W", # pycodestyle
134
+ "UP", # pyupgrade
135
+ "W", # pycodestyle
137
136
  ]
138
137
 
139
138
  lint.ignore = [
140
- "D203", # 1 blank line required before class docstring
141
- "D213", # Multi-line docstring summary should start at the second line
142
- "E501", # Checks for lines that exceed the specified maximum character length
143
- "PLR0911", # Too many return statements
144
- "PLR0912", # Maximum number of branches allowed for a function or method body
145
- "PLR0913", # Too many arguments to function call ({c_args} > {max_args})
146
- "PLR0915", # Too many statements ({statements} > {max_statements})
147
- "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
139
+ "D203", # 1 blank line required before class docstring
140
+ "D213", # Multi-line docstring summary should start at the second line
141
+ "E501", # Checks for lines that exceed the specified maximum character length
142
+ "PLR0912", # Maximum number of branches allowed for a function or method body
143
+ "PLR0913", # Too many arguments to function call ({c_args} > {max_args})
144
+ "PLR0915", # Too many statements ({statements} > {max_statements})
145
+ "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
148
146
  ]
149
147
 
150
148
  [tool.ruff.lint.flake8-pytest-style]
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
4
4
 
5
5
  MIN_PY_VERSION = "3.11"
6
6
  PACKAGES = find_packages(exclude=["tests", "tests.*"])
7
- VERSION = "56"
7
+ VERSION = "57"
8
8
 
9
9
  setup(
10
10
  name="axis",
@@ -115,13 +115,13 @@ async def test_get_service_capabilities(respx_mock, light_control: LightHandler)
115
115
  "context": "Axis library",
116
116
  }
117
117
 
118
- response.automatic_intensity_support is True
119
- response.manual_intensity_support is True
120
- response.get_current_intensity_support is True
121
- response.individual_intensity_support is False
122
- response.automatic_angle_of_illumination_support is False
123
- response.manual_angle_of_illumination_support is False
124
- response.day_night_synchronize_support is True
118
+ assert response.automatic_intensity_support is True
119
+ assert response.manual_intensity_support is True
120
+ assert response.get_current_intensity_support is True
121
+ assert response.individual_intensity_support is False
122
+ assert response.automatic_angle_of_illumination_support is False
123
+ assert response.manual_angle_of_illumination_support is False
124
+ assert response.day_night_synchronize_support is True
125
125
 
126
126
 
127
127
  async def test_get_light_information(respx_mock, light_control: LightHandler):
@@ -31,55 +31,63 @@ async def test_successful_connect(rtsp_server, rtsp_client):
31
31
  rtsp_server.register_responses(
32
32
  [
33
33
  # OPTIONS
34
- "RTSP/1.0 200 OK\r\n"
35
- + "CSeq: 0\r\n"
36
- + "Public: OPTIONS, DESCRIBE, ANNOUNCE, GET_PARAMETER, PAUSE, PLAY, RECORD, SETUP, SET_PARAMETER, TEARDOWN\r\n"
37
- + "Server: GStreamer RTSP server\r\n"
38
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
34
+ (
35
+ "RTSP/1.0 200 OK\r\n"
36
+ "CSeq: 0\r\n"
37
+ "Public: OPTIONS, DESCRIBE, ANNOUNCE, GET_PARAMETER, PAUSE, PLAY, RECORD, SETUP, SET_PARAMETER, TEARDOWN\r\n"
38
+ "Server: GStreamer RTSP server\r\n"
39
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n"
40
+ ),
39
41
  # DESCRIBE is denied since it requires authentication
40
- "RTSP/1.0 401 Unauthorized\r\n"
41
- + "CSeq: 1\r\n"
42
- + 'WWW-Authenticate: Digest realm="AXIS_ACCC8E012345", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", stale=FALSE\r\n'
43
- + "Server: GStreamer RTSP server\r\n "
44
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
42
+ (
43
+ "RTSP/1.0 401 Unauthorized\r\n"
44
+ "CSeq: 1\r\n"
45
+ 'WWW-Authenticate: Digest realm="AXIS_ACCC8E012345", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", stale=FALSE\r\n'
46
+ "Server: GStreamer RTSP server\r\n "
47
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n"
48
+ ),
45
49
  # DESCRIBE with digest authentication
46
- "RTSP/1.0 200 OK\r\n"
47
- + "CSeq: 1\r\n"
48
- + "Content-Type: application/sdp\r\n"
49
- + "Content-Base: rtsp://127.0.0.1/axis-media/media.amp/\r\n"
50
- + "Server: GStreamer RTSP server\r\n"
51
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n"
52
- + "Content-Length: 440\r\n\r\n"
53
- + "v=0\r\n"
54
- + "o=- 18302136002250915122 1 IN IP4 host\r\n"
55
- + "s=Session streamed with GStreamer\r\n"
56
- + "i=rtsp-server\r\n"
57
- + "t=0 0\r\n"
58
- + "a=tool:GStreamer\r\n"
59
- + "a=type:broadcast\r\n"
60
- + "a=range:npt=now-\r\n"
61
- + "a=control:rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on\r\n"
62
- + "m=application 0 RTP/AVP 98\r\n"
63
- + "c=IN IP4 0.0.0.0\r\n"
64
- + "a=rtpmap:98 vnd.onvif.metadata/90000\r\n"
65
- + "a=ts-refclk:local\r\n"
66
- + "a=mediaclk:sender\r\n"
67
- + "a=control:rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on\r\n",
50
+ (
51
+ "RTSP/1.0 200 OK\r\n"
52
+ "CSeq: 1\r\n"
53
+ "Content-Type: application/sdp\r\n"
54
+ "Content-Base: rtsp://127.0.0.1/axis-media/media.amp/\r\n"
55
+ "Server: GStreamer RTSP server\r\n"
56
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n"
57
+ "Content-Length: 440\r\n\r\n"
58
+ "v=0\r\n"
59
+ "o=- 18302136002250915122 1 IN IP4 host\r\n"
60
+ "s=Session streamed with GStreamer\r\n"
61
+ "i=rtsp-server\r\n"
62
+ "t=0 0\r\n"
63
+ "a=tool:GStreamer\r\n"
64
+ "a=type:broadcast\r\n"
65
+ "a=range:npt=now-\r\n"
66
+ "a=control:rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on\r\n"
67
+ "m=application 0 RTP/AVP 98\r\n"
68
+ "c=IN IP4 0.0.0.0\r\n"
69
+ "a=rtpmap:98 vnd.onvif.metadata/90000\r\n"
70
+ "a=ts-refclk:local\r\n"
71
+ "a=mediaclk:sender\r\n"
72
+ "a=control:rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on\r\n"
73
+ ),
68
74
  # SETUP requires to know the rtp and rtcp ports which are not yet known
69
75
  # "RTSP/1.0 200 OK\r\n"
70
- # + "CSeq: 2\r\n"
71
- # + 'Transport: RTP/AVP;unicast;client_port=45678-45679;server_port=50000-50001;ssrc=315460DA;mode="PLAY"\r\n'
72
- # + "Server: GStreamer RTSP server\r\n"
73
- # + "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
74
- # + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
76
+ # "CSeq: 2\r\n"
77
+ # 'Transport: RTP/AVP;unicast;client_port=45678-45679;server_port=50000-50001;ssrc=315460DA;mode="PLAY"\r\n'
78
+ # "Server: GStreamer RTSP server\r\n"
79
+ # "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
80
+ # "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
75
81
  # PLAY
76
- "RTSP/1.0 200 OK\r\n"
77
- + "CSeq: 3\r\n"
78
- + "RTP-Info: url=rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on;seq=13114;rtptime=3803548519\r\n"
79
- + "Range: npt=now-\r\n"
80
- + "Server: GStreamer RTSP server\r\n"
81
- + "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
82
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
82
+ (
83
+ "RTSP/1.0 200 OK\r\n"
84
+ "CSeq: 3\r\n"
85
+ "RTP-Info: url=rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on;seq=13114;rtptime=3803548519\r\n"
86
+ "Range: npt=now-\r\n"
87
+ "Server: GStreamer RTSP server\r\n"
88
+ "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
89
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n"
90
+ ),
83
91
  ]
84
92
  )
85
93
 
@@ -136,9 +144,9 @@ async def test_successful_connect(rtsp_server, rtsp_client):
136
144
  # OPTIONS ask server to show what options it supports
137
145
  assert rtsp_server.last_request == (
138
146
  "OPTIONS "
139
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
140
- + "CSeq: 0\r\n"
141
- + "User-Agent: HASS Axis\r\n\r\n"
147
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
148
+ "CSeq: 0\r\n"
149
+ "User-Agent: HASS Axis\r\n\r\n"
142
150
  )
143
151
  rtsp_server.step_response()
144
152
  await rtsp_server.next_request_received.wait()
@@ -179,10 +187,10 @@ async def test_successful_connect(rtsp_server, rtsp_client):
179
187
  # DESCRIBE ask server what it provides
180
188
  assert rtsp_server.last_request == (
181
189
  "DESCRIBE "
182
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
183
- + "CSeq: 1\r\n"
184
- + "User-Agent: HASS Axis\r\n"
185
- + "Accept: application/sdp\r\n\r\n"
190
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
191
+ "CSeq: 1\r\n"
192
+ "User-Agent: HASS Axis\r\n"
193
+ "Accept: application/sdp\r\n\r\n"
186
194
  )
187
195
  rtsp_server.step_response()
188
196
  await rtsp_server.next_request_received.wait()
@@ -212,11 +220,11 @@ async def test_successful_connect(rtsp_server, rtsp_client):
212
220
  # DESCRIBE with digest authentication
213
221
  assert rtsp_server.last_request == (
214
222
  "DESCRIBE "
215
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
216
- + "CSeq: 1\r\n"
217
- + 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="909e6c215f6b0f350147a81ae3980018"\r\n'
218
- + "User-Agent: HASS Axis\r\n"
219
- + "Accept: application/sdp\r\n\r\n"
223
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
224
+ "CSeq: 1\r\n"
225
+ 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="909e6c215f6b0f350147a81ae3980018"\r\n'
226
+ "User-Agent: HASS Axis\r\n"
227
+ "Accept: application/sdp\r\n\r\n"
220
228
  )
221
229
  rtsp_server.step_response()
222
230
  await rtsp_server.next_request_received.wait()
@@ -266,19 +274,19 @@ async def test_successful_connect(rtsp_server, rtsp_client):
266
274
  # SETUP setup rtp connection to server
267
275
  assert rtsp_server.last_request == (
268
276
  "SETUP "
269
- + "rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on RTSP/1.0\r\n"
270
- + "CSeq: 2\r\n"
271
- + 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="3c4d88afefd4ab931f6db658326d60c2"\r\n'
272
- + "User-Agent: HASS Axis\r\n"
273
- + f"Transport: RTP/AVP;unicast;client_port={rtp_port}-{rtcp_port}\r\n\r\n"
277
+ "rtsp://127.0.0.1/axis-media/media.amp/stream=0?video=0&audio=0&event=on RTSP/1.0\r\n"
278
+ "CSeq: 2\r\n"
279
+ 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="3c4d88afefd4ab931f6db658326d60c2"\r\n'
280
+ "User-Agent: HASS Axis\r\n"
281
+ f"Transport: RTP/AVP;unicast;client_port={rtp_port}-{rtcp_port}\r\n\r\n"
274
282
  )
275
283
  rtsp_server.send_response(
276
284
  "RTSP/1.0 200 OK\r\n"
277
- + "CSeq: 2\r\n"
278
- + f'Transport: RTP/AVP;unicast;client_port={rtp_port}-{rtcp_port};server_port=50000-50001;ssrc=315460DA;mode="PLAY"\r\n'
279
- + "Server: GStreamer RTSP server\r\n"
280
- + "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
281
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
285
+ "CSeq: 2\r\n"
286
+ f'Transport: RTP/AVP;unicast;client_port={rtp_port}-{rtcp_port};server_port=50000-50001;ssrc=315460DA;mode="PLAY"\r\n'
287
+ "Server: GStreamer RTSP server\r\n"
288
+ "Session: ghLlkf_I9pCBP24t;timeout=60\r\n"
289
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n",
282
290
  )
283
291
  await rtsp_server.next_request_received.wait()
284
292
  assert rtsp_client.session.sequence == 3
@@ -330,11 +338,11 @@ async def test_successful_connect(rtsp_server, rtsp_client):
330
338
  # PLAY start stream
331
339
  assert rtsp_server.last_request == (
332
340
  "PLAY "
333
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
334
- + "CSeq: 3\r\n"
335
- + 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="1c882325f7733b0d48d177ee8a0e6b2a"\r\n'
336
- + "User-Agent: HASS Axis\r\n"
337
- + "Session: ghLlkf_I9pCBP24t\r\n\r\n"
341
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
342
+ "CSeq: 3\r\n"
343
+ 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="1c882325f7733b0d48d177ee8a0e6b2a"\r\n'
344
+ "User-Agent: HASS Axis\r\n"
345
+ "Session: ghLlkf_I9pCBP24t\r\n\r\n"
338
346
  )
339
347
  with patch.object(rtsp_client, "callback") as mock_callback:
340
348
  rtsp_server.step_response()
@@ -425,19 +433,19 @@ async def test_successful_connect(rtsp_server, rtsp_client):
425
433
  await rtsp_server.next_request_received.wait()
426
434
  assert rtsp_server.last_request == (
427
435
  "OPTIONS "
428
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
429
- + "CSeq: 4\r\n"
430
- + "User-Agent: HASS Axis\r\n"
431
- + "Session: ghLlkf_I9pCBP24t\r\n\r\n"
436
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
437
+ "CSeq: 4\r\n"
438
+ "User-Agent: HASS Axis\r\n"
439
+ "Session: ghLlkf_I9pCBP24t\r\n\r\n"
432
440
  )
433
441
  with patch.object(rtsp_client, "callback") as mock_callback:
434
442
  rtsp_server.send_response(
435
443
  "RTSP/1.0 200 OK\r\n"
436
- + "CSeq: 4\r\n"
437
- + "Public: OPTIONS, DESCRIBE, ANNOUNCE, GET_PARAMETER, PAUSE, PLAY, RECORD, SETUP, SET_PARAMETER, TEARDOWN\r\n"
438
- + "Server: GStreamer RTSP server\r\n"
439
- + "Session: ghLlkf_I9pCBP24t;timeout=30\r\n"
440
- + "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n"
444
+ "CSeq: 4\r\n"
445
+ "Public: OPTIONS, DESCRIBE, ANNOUNCE, GET_PARAMETER, PAUSE, PLAY, RECORD, SETUP, SET_PARAMETER, TEARDOWN\r\n"
446
+ "Server: GStreamer RTSP server\r\n"
447
+ "Session: ghLlkf_I9pCBP24t;timeout=30\r\n"
448
+ "Date: Sat, 12 Dec 2020 10:44:25 GMT\r\n\r\n"
441
449
  )
442
450
  # We don't expect additional requests as RTSP handshake is complete
443
451
  # but we still need to wait for rtsp client to process response before continuing
@@ -456,11 +464,11 @@ async def test_successful_connect(rtsp_server, rtsp_client):
456
464
  await rtsp_server.next_request_received.wait()
457
465
  assert rtsp_server.last_request == (
458
466
  "TEARDOWN "
459
- + "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
460
- + "CSeq: 5\r\n"
461
- + 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="2417a81b93d5ae64926d226e18a5ab99"\r\n'
462
- + "User-Agent: HASS Axis\r\n"
463
- + "Session: ghLlkf_I9pCBP24t\r\n\r\n"
467
+ "rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on RTSP/1.0\r\n"
468
+ "CSeq: 5\r\n"
469
+ 'Authorization: Digest username="root", realm="AXIS_ACCC8E012345", algorithm="MD5", nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", response="2417a81b93d5ae64926d226e18a5ab99"\r\n'
470
+ "User-Agent: HASS Axis\r\n"
471
+ "Session: ghLlkf_I9pCBP24t\r\n\r\n"
464
472
  )
465
473
 
466
474
 
@@ -576,8 +584,8 @@ def test_session_state_method(rtsp_client):
576
584
  assert session.state == State.STOPPED
577
585
 
578
586
  with patch.object(session, "sequence", 6), pytest.raises(IndexError):
579
- session.method
580
- session.state
587
+ _ = session.method # B018 lint
588
+ _ = session.state # B018 lint
581
589
 
582
590
 
583
591
  def test_session_update_status_codes(rtsp_client):
@@ -630,14 +638,13 @@ def test_session_generate_digest_auth(rtsp_client):
630
638
 
631
639
  digest_auth = session.generate_digest()
632
640
  assert (
633
- digest_auth
634
- == "Digest "
635
- + 'username="root", '
636
- + 'realm="AXIS_ACCC8E012345", '
637
- + 'algorithm="MD5", '
638
- + 'nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", '
639
- + 'uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", '
640
- + 'response="359495f9e42106627750e3cd65145f6b"'
641
+ digest_auth == "Digest "
642
+ 'username="root", '
643
+ 'realm="AXIS_ACCC8E012345", '
644
+ 'algorithm="MD5", '
645
+ 'nonce="0000eb57Y1462133062b37999f0cd530f02755fa37b8df1", '
646
+ 'uri="rtsp://127.0.0.1/axis-media/media.amp?video=0&audio=0&event=on", '
647
+ 'response="359495f9e42106627750e3cd65145f6b"'
641
648
  )
642
649
 
643
650
 
@@ -7,7 +7,13 @@ import httpx
7
7
  import pytest
8
8
 
9
9
  from axis.device import AxisDevice
10
- from axis.errors import MethodNotAllowed, PathNotFound, RequestError, Unauthorized
10
+ from axis.errors import (
11
+ Forbidden,
12
+ MethodNotAllowed,
13
+ PathNotFound,
14
+ RequestError,
15
+ Unauthorized,
16
+ )
11
17
  from axis.vapix.models.applications.application import ApplicationStatus
12
18
  from axis.vapix.models.pwdgrp_cgi import SecondaryGroup
13
19
  from axis.vapix.models.stream_profile import StreamProfile
@@ -286,7 +292,8 @@ async def test_initialize_applications(respx_mock, vapix: Vapix):
286
292
  assert len(vapix.vmd4.values()) == 1
287
293
 
288
294
 
289
- async def test_initialize_applications_unauthorized(respx_mock, vapix: Vapix):
295
+ @pytest.mark.parametrize("code", [401, 403])
296
+ async def test_initialize_applications_unauthorized(respx_mock, vapix: Vapix, code):
290
297
  """Verify initialize applications doesnt break on too low credentials."""
291
298
  respx_mock.post("/axis-cgi/param.cgi").respond(
292
299
  text=PARAM_CGI_RESPONSE,
@@ -295,7 +302,7 @@ async def test_initialize_applications_unauthorized(respx_mock, vapix: Vapix):
295
302
  respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
296
303
  json=LIGHT_CONTROL_RESPONSE,
297
304
  )
298
- respx_mock.post("/axis-cgi/applications/list.cgi").respond(status_code=401)
305
+ respx_mock.post("/axis-cgi/applications/list.cgi").respond(status_code=code)
299
306
 
300
307
  await vapix.initialize_param_cgi()
301
308
  await vapix.initialize_applications()
@@ -431,7 +438,12 @@ async def test_not_loading_user_groups_makes_access_rights_unknown(vapix: Vapix)
431
438
 
432
439
  @pytest.mark.parametrize(
433
440
  ("code", "error"),
434
- ((401, Unauthorized), (404, PathNotFound), (405, MethodNotAllowed)),
441
+ (
442
+ (401, Unauthorized),
443
+ (403, Forbidden),
444
+ (404, PathNotFound),
445
+ (405, MethodNotAllowed),
446
+ ),
435
447
  )
436
448
  async def test_request_raises(respx_mock, vapix: Vapix, code, error):
437
449
  """Verify that a HTTP error raises the appropriate exception."""
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
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
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
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
File without changes