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.
- {axis-56 → axis-57}/PKG-INFO +5 -5
- {axis-56 → axis-57}/axis/__init__.py +1 -1
- {axis-56 → axis-57}/axis/__main__.py +0 -1
- {axis-56 → axis-57}/axis/errors.py +5 -5
- {axis-56 → axis-57}/axis/models/event.py +6 -6
- {axis-56 → axis-57}/axis/vapix/interfaces/api_handler.py +3 -1
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/__init__.py +1 -1
- {axis-56 → axis-57}/axis/vapix/models/parameters/image.py +1 -1
- {axis-56 → axis-57}/axis/vapix/models/parameters/ptz.py +4 -4
- {axis-56 → axis-57}/axis/vapix/models/pwdgrp_cgi.py +3 -9
- {axis-56 → axis-57}/axis/vapix/vapix.py +1 -2
- {axis-56 → axis-57}/axis.egg-info/PKG-INFO +5 -5
- {axis-56 → axis-57}/axis.egg-info/requires.txt +3 -3
- {axis-56 → axis-57}/pyproject.toml +38 -40
- {axis-56 → axis-57}/setup.py +1 -1
- {axis-56 → axis-57}/tests/test_light_control.py +7 -7
- {axis-56 → axis-57}/tests/test_rtsp.py +102 -95
- {axis-56 → axis-57}/tests/test_vapix.py +16 -4
- {axis-56 → axis-57}/LICENSE +0 -0
- {axis-56 → axis-57}/README.md +0 -0
- {axis-56 → axis-57}/axis/configuration.py +0 -0
- {axis-56 → axis-57}/axis/device.py +0 -0
- {axis-56 → axis-57}/axis/event_stream.py +0 -0
- {axis-56 → axis-57}/axis/models/__init__.py +0 -0
- {axis-56 → axis-57}/axis/py.typed +0 -0
- {axis-56 → axis-57}/axis/rtsp.py +0 -0
- {axis-56 → axis-57}/axis/stream_manager.py +0 -0
- {axis-56 → axis-57}/axis/vapix/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/api_discovery.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/application_handler.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/applications.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/fence_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/loitering_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/motion_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/object_analytics.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/applications/vmd4.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/basic_device_info.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/event_instances.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/light_control.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/mqtt.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/brand.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/image.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/io_port.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/param_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/param_handler.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/properties.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/ptz.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/parameters/stream_profile.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/pir_sensor_configuration.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/port_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/port_management.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/ptz.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/pwdgrp_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/stream_profiles.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/user_groups.py +0 -0
- {axis-56 → axis-57}/axis/vapix/interfaces/view_areas.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/api.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/api_discovery.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/application.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/fence_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/loitering_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/motion_guard.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/object_analytics.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/applications/vmd4.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/basic_device_info.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/event_instance.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/light_control.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/mqtt.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/__init__.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/brand.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/io_port.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/param_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/properties.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/parameters/stream_profile.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/pir_sensor_configuration.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/port_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/port_management.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/ptz_cgi.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/stream_profile.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/user_group.py +0 -0
- {axis-56 → axis-57}/axis/vapix/models/view_area.py +0 -0
- {axis-56 → axis-57}/axis.egg-info/SOURCES.txt +0 -0
- {axis-56 → axis-57}/axis.egg-info/dependency_links.txt +0 -0
- {axis-56 → axis-57}/axis.egg-info/entry_points.txt +0 -0
- {axis-56 → axis-57}/axis.egg-info/top_level.txt +0 -0
- {axis-56 → axis-57}/setup.cfg +0 -0
- {axis-56 → axis-57}/tests/test_api_discovery.py +0 -0
- {axis-56 → axis-57}/tests/test_api_handler.py +0 -0
- {axis-56 → axis-57}/tests/test_basic_device_info.py +0 -0
- {axis-56 → axis-57}/tests/test_configuration.py +0 -0
- {axis-56 → axis-57}/tests/test_device.py +0 -0
- {axis-56 → axis-57}/tests/test_event.py +0 -0
- {axis-56 → axis-57}/tests/test_event_instances.py +0 -0
- {axis-56 → axis-57}/tests/test_event_stream.py +0 -0
- {axis-56 → axis-57}/tests/test_mqtt.py +0 -0
- {axis-56 → axis-57}/tests/test_pir_sensor_configuration.py +0 -0
- {axis-56 → axis-57}/tests/test_port_cgi.py +0 -0
- {axis-56 → axis-57}/tests/test_port_management.py +0 -0
- {axis-56 → axis-57}/tests/test_ptz.py +0 -0
- {axis-56 → axis-57}/tests/test_pwdgrp_cgi.py +0 -0
- {axis-56 → axis-57}/tests/test_stream_manager.py +0 -0
- {axis-56 → axis-57}/tests/test_stream_profiles.py +0 -0
- {axis-56 → axis-57}/tests/test_user_groups.py +0 -0
- {axis-56 → axis-57}/tests/test_view_areas.py +0 -0
{axis-56 → axis-57}/PKG-INFO
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: axis
|
|
3
|
-
Version:
|
|
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/
|
|
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.
|
|
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.
|
|
39
|
-
Requires-Dist: ruff==0.3.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
104
|
-
TOPIC = NOTIFICATION_MESSAGE
|
|
105
|
-
TIMESTAMP = MESSAGE
|
|
106
|
-
OPERATION = MESSAGE
|
|
107
|
-
SOURCE = MESSAGE
|
|
108
|
-
DATA = MESSAGE
|
|
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:
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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/
|
|
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.
|
|
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.
|
|
39
|
-
Requires-Dist: ruff==0.3.
|
|
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.
|
|
19
|
+
pytest-asyncio==0.23.6
|
|
20
20
|
pytest-cov==4.1.0
|
|
21
|
-
respx==0.
|
|
22
|
-
ruff==0.3.
|
|
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 = "
|
|
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.
|
|
40
|
+
"pytest-asyncio==0.23.6",
|
|
41
41
|
"pytest-cov==4.1.0",
|
|
42
|
-
"respx==0.
|
|
43
|
-
"ruff==0.3.
|
|
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
|
-
"
|
|
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",
|
|
107
|
-
"E",
|
|
108
|
-
"F",
|
|
109
|
-
"G",
|
|
110
|
-
"I",
|
|
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
|
-
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
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
|
-
"
|
|
125
|
-
|
|
126
|
-
"
|
|
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",
|
|
136
|
-
"W",
|
|
134
|
+
"UP", # pyupgrade
|
|
135
|
+
"W", # pycodestyle
|
|
137
136
|
]
|
|
138
137
|
|
|
139
138
|
lint.ignore = [
|
|
140
|
-
"D203",
|
|
141
|
-
"D213",
|
|
142
|
-
"E501",
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
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]
|
{axis-56 → axis-57}/setup.py
RENAMED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
-
(
|
|
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."""
|
{axis-56 → axis-57}/LICENSE
RENAMED
|
File without changes
|
{axis-56 → axis-57}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis-56 → axis-57}/axis/rtsp.py
RENAMED
|
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
|
|
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
|
{axis-56 → axis-57}/setup.cfg
RENAMED
|
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
|