mypy fixes

This commit is contained in:
Sebastian Goscik
2024-08-10 00:17:55 +01:00
parent 0a6a259120
commit 78be4808d9
8 changed files with 95 additions and 57 deletions

View File

@@ -19,9 +19,11 @@ repos:
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.901 rev: v1.11.1
hooks: hooks:
- id: mypy - id: mypy
exclude: tests/ exclude: tests/
additional_dependencies: additional_dependencies:
- types-click - types-pytz
- types-cryptography
- types-python-dateutil

75
poetry.lock generated
View File

@@ -869,53 +869,60 @@ files = [
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "0.900" version = "1.11.1"
description = "Optional static typing for Python" description = "Optional static typing for Python"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.8"
files = [ files = [
{file = "mypy-0.900-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:07efc88486877d595cca7f7d237e6d04d1ba6f01dc8f74a81b716270f6770968"}, {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"},
{file = "mypy-0.900-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:80c96f97de241ee7383cfe646bfc51113a48089d50c33275af0033b98dee3b1c"}, {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"},
{file = "mypy-0.900-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:0e703c0afe36511746513d168e1d2a52f88e2a324169b87a6b6a58901c3afcf3"}, {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"},
{file = "mypy-0.900-cp35-cp35m-win_amd64.whl", hash = "sha256:23100137579d718cd6f05d572574ca00701fa2bfc7b645ebc5130d93e2af3bee"}, {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"},
{file = "mypy-0.900-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:468b3918b26f81d003e8e9b788c62160acb885487cf4d83a3f22ba9061cb49e2"}, {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"},
{file = "mypy-0.900-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d90c296cd5cdef86e720a0994d41d72c06d6ff8ab8fc6aaaf0ee6c675835d596"}, {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"},
{file = "mypy-0.900-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:42d66b3d716fe5e22b32915d1fa59e7183a0e02f00b337b834a596c1f5e37f01"}, {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"},
{file = "mypy-0.900-cp36-cp36m-win_amd64.whl", hash = "sha256:a354613b4cc6e0e9f1ba7811083cd8f63ccee97333d1df7594c438399c83249a"}, {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"},
{file = "mypy-0.900-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:22f97de97373dd6180c4abee90b20c60780820284d2cdc5579927c0e37854cf6"}, {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"},
{file = "mypy-0.900-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e75f0c97cfe8d86da89b22ad7039f5af44b8f6b0af12bd2877791a92b4b9e987"}, {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"},
{file = "mypy-0.900-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:41f082275a20e3eea48364915f7bc6ec5338be89db1ed8b2e570b9e3d12d4dc6"}, {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"},
{file = "mypy-0.900-cp37-cp37m-win_amd64.whl", hash = "sha256:83adbf3f8c5023f4276557fbcb3b6306f9dce01783e8ac5f8c11fcb29f62e899"}, {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"},
{file = "mypy-0.900-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2220f97804890f3e6da3f849f81f3e56e367a2027a51dde5ce3b7ebb2ad3342b"}, {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"},
{file = "mypy-0.900-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3f1d0601842c6b4248923963fc59a6fdd05dee0fddc8b07e30c508b6a269e68f"}, {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"},
{file = "mypy-0.900-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c2f87505840c0f3557ea4aa5893f2459daf6516adac30b15d1d5cf567e0d7939"}, {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"},
{file = "mypy-0.900-cp38-cp38-win_amd64.whl", hash = "sha256:a0461da00ed23d17fcb04940db2b72f920435cf79be943564b717e378ffeeddf"}, {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"},
{file = "mypy-0.900-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9560b1f572cdaab43fdcdad5ef45138e89dc191729329db1b8ce5636f4cdeacf"}, {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"},
{file = "mypy-0.900-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d794f10b9f28d21af7a93054e217872aaf9b9ad1bd354ae5e1a3a923d734b73f"}, {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"},
{file = "mypy-0.900-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:68fd1c1c1fc9b405f0ed6cfcd00541de7e83f41007419a125c20fa5db3881cb1"}, {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"},
{file = "mypy-0.900-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7eb1e5820deb71e313aa2b5a5220803a9b2e3efa43475537a71d0ffed7495e1e"}, {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"},
{file = "mypy-0.900-cp39-cp39-win_amd64.whl", hash = "sha256:6598e39cd5aa1a09d454ad39687b89cf3f3fd7cf1f9c3f81a1a2775f6f6b16f8"}, {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"},
{file = "mypy-0.900-py3-none-any.whl", hash = "sha256:3be7c68fab8b318a2d5bcfac8e028dc77b9096ea1ec5594e9866c8fb57ae0296"}, {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"},
{file = "mypy-0.900.tar.gz", hash = "sha256:65c78570329c54fb40f956f7645e2359af5da9d8c54baa44f461cdc7f4984108"}, {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"},
{file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"},
{file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"},
{file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"},
{file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"},
] ]
[package.dependencies] [package.dependencies]
mypy-extensions = ">=0.4.3,<0.5.0" mypy-extensions = ">=1.0.0"
toml = "*" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=3.7.4" typing-extensions = ">=4.6.0"
[package.extras] [package.extras]
dmypy = ["psutil (>=4.0)"] dmypy = ["psutil (>=4.0)"]
python2 = ["typed-ast (>=1.4.0,<1.5.0)"] install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "0.4.4" version = "1.0.0"
description = "Experimental type system extensions for programs checked with the mypy typechecker." description = "Type system extensions for programs checked with the mypy type checker."
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=3.5"
files = [ files = [
{file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
] ]
[[package]] [[package]]
@@ -1992,4 +1999,4 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10.0,<4.0" python-versions = ">=3.10.0,<4.0"
content-hash = "81ee0cdb5af9e498001e3e8239c0ba259d114e19a2099f133b2de9645f596788" content-hash = "dea68a0cc11ccb56faf879210d124f44b2ee73a7a36205f1214dd8ab35451d1c"

View File

@@ -36,7 +36,7 @@ uiprotect = "^5.4.0"
optional = true optional = true
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
mypy = "^0.900" mypy = "^1.11.1"
types-pytz = "^2021.3.5" types-pytz = "^2021.3.5"
types-cryptography = "^3.3.18" types-cryptography = "^3.3.18"
types-python-dateutil = "^2.8.19.10" types-python-dateutil = "^2.8.19.10"

View File

@@ -12,10 +12,10 @@ from .uploader import VideoUploader
from .missing_event_checker import MissingEventChecker from .missing_event_checker import MissingEventChecker
__all__ = [ __all__ = [
VideoDownloader, "VideoDownloader",
VideoDownloaderExperimental, "VideoDownloaderExperimental",
EventListener, "EventListener",
Purge, "Purge",
VideoUploader, "VideoUploader",
MissingEventChecker, "MissingEventChecker",
] ]

View File

@@ -85,6 +85,8 @@ class VideoDownloaderExperimental:
else: else:
self._has_ffprobe = False self._has_ffprobe = False
raise RuntimeError("The `uiprotect` library is currently missing the features for this to work.")
async def start(self): async def start(self):
"""Main loop.""" """Main loop."""
self.logger.info("Starting Downloader") self.logger.info("Starting Downloader")
@@ -149,7 +151,10 @@ class VideoDownloaderExperimental:
self._failures[event.id] = 1 self._failures[event.id] = 1
else: else:
self._failures[event.id] += 1 self._failures[event.id] += 1
self.logger.warning(f"Event failed download attempt {self._failures[event.id]}", exc_info=e) self.logger.warning(
f"Event failed download attempt {self._failures[event.id]}",
exc_info=e,
)
if self._failures[event.id] >= 10: if self._failures[event.id] >= 10:
self.logger.error( self.logger.error(
@@ -171,7 +176,10 @@ class VideoDownloaderExperimental:
self.current_event = None self.current_event = None
except Exception as e: except Exception as e:
self.logger.error(f"Unexpected exception occurred, abandoning event {event.id}:", exc_info=e) self.logger.error(
f"Unexpected exception occurred, abandoning event {event.id}:",
exc_info=e,
)
async def _download(self, event: Event) -> Optional[bytes]: async def _download(self, event: Event) -> Optional[bytes]:
"""Downloads the video clip for the given event.""" """Downloads the video clip for the given event."""
@@ -181,8 +189,12 @@ class VideoDownloaderExperimental:
assert isinstance(event.start, datetime) assert isinstance(event.start, datetime)
assert isinstance(event.end, datetime) assert isinstance(event.end, datetime)
try: try:
prepared_video_file = await self._protect.prepare_camera_video(event.camera_id, event.start, event.end) prepared_video_file = await self._protect.prepare_camera_video( # type: ignore
video = await self._protect.download_camera_video(event.camera_id, prepared_video_file["fileName"]) event.camera_id, event.start, event.end
)
video = await self._protect.download_camera_video( # type: ignore
event.camera_id, prepared_video_file["fileName"]
)
assert isinstance(video, bytes) assert isinstance(video, bytes)
break break
except (AssertionError, ClientPayloadError, TimeoutError) as e: except (AssertionError, ClientPayloadError, TimeoutError) as e:

View File

@@ -3,7 +3,7 @@
import asyncio import asyncio
import logging import logging
from datetime import datetime from datetime import datetime
from typing import List from typing import AsyncIterator, List
import aiosqlite import aiosqlite
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@@ -54,14 +54,14 @@ class MissingEventChecker:
self.ignore_cameras: List[str] = ignore_cameras self.ignore_cameras: List[str] = ignore_cameras
self.interval: int = interval self.interval: int = interval
async def _get_missing_events(self) -> List[Event]: async def _get_missing_events(self) -> AsyncIterator[Event]:
start_time = datetime.now() - self.retention start_time = datetime.now() - self.retention
end_time = datetime.now() end_time = datetime.now()
chunk_size = 500 chunk_size = 500
while True: while True:
# Get list of events that need to be backed up from unifi protect # Get list of events that need to be backed up from unifi protect
logger.extra_debug(f"Fetching events for interval: {start_time} - {end_time}") logger.extra_debug(f"Fetching events for interval: {start_time} - {end_time}") # type: ignore
events_chunk = await self._protect.get_events( events_chunk = await self._protect.get_events(
start=start_time, start=start_time,
end=end_time, end=end_time,
@@ -72,6 +72,7 @@ class MissingEventChecker:
if not events_chunk: if not events_chunk:
break # There were no events to backup break # There were no events to backup
assert events_chunk[-1].end is not None
start_time = events_chunk[-1].end start_time = events_chunk[-1].end
unifi_events = {event.id: event for event in events_chunk} unifi_events = {event.id: event for event in events_chunk}
@@ -166,6 +167,9 @@ class MissingEventChecker:
await self._download_queue.put(event) await self._download_queue.put(event)
except Exception as e: except Exception as e:
logger.error("Unexpected exception occurred during missing event check:", exc_info=e) logger.error(
"Unexpected exception occurred during missing event check:",
exc_info=e,
)
await asyncio.sleep(self.interval) await asyncio.sleep(self.interval)

View File

@@ -21,7 +21,13 @@ from unifi_protect_backup import (
VideoUploader, VideoUploader,
notifications, notifications,
) )
from unifi_protect_backup.utils import SubprocessException, VideoQueue, human_readable_size, run_command, setup_logging from unifi_protect_backup.utils import (
SubprocessException,
VideoQueue,
human_readable_size,
run_command,
setup_logging,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -67,7 +73,7 @@ class UnifiProtectBackup:
max_event_length: int, max_event_length: int,
sqlite_path: str = "events.sqlite", sqlite_path: str = "events.sqlite",
color_logging: bool = False, color_logging: bool = False,
download_rate_limit: float = None, download_rate_limit: float | None = None,
port: int = 443, port: int = 443,
use_experimental_downloader: bool = False, use_experimental_downloader: bool = False,
): ):
@@ -196,7 +202,10 @@ class UnifiProtectBackup:
await self._protect.update() await self._protect.update()
break break
except Exception as e: except Exception as e:
logger.warning(f"Failed to connect to UniFi Protect, retrying in {attempts}s...", exc_info=e) logger.warning(
f"Failed to connect to UniFi Protect, retrying in {attempts}s...",
exc_info=e,
)
await asyncio.sleep(attempts) await asyncio.sleep(attempts)
else: else:
raise ConnectionError("Failed to connect to UniFi Protect after 10 attempts") raise ConnectionError("Failed to connect to UniFi Protect after 10 attempts")
@@ -269,7 +278,11 @@ class UnifiProtectBackup:
# Create purge task # Create purge task
# This will, every midnight, purge old backups from the rclone remotes and database # This will, every midnight, purge old backups from the rclone remotes and database
purge = Purge( purge = Purge(
self._db, self.retention, self.rclone_destination, self._purge_interval, self.rclone_purge_args self._db,
self.retention,
self.rclone_destination,
self._purge_interval,
self.rclone_purge_args,
) )
tasks.append(purge.start()) tasks.append(purge.start())

View File

@@ -294,7 +294,7 @@ async def get_camera_name(protect: ProtectApiClient, id: str):
# Refresh cameras # Refresh cameras
logger.debug(f"Unknown camera id: '{id}', checking API") logger.debug(f"Unknown camera id: '{id}', checking API")
await protect.update(force=True) await protect.update()
try: try:
name = protect.bootstrap.cameras[id].name name = protect.bootstrap.cameras[id].name
@@ -377,7 +377,7 @@ class VideoQueue(asyncio.Queue):
self._queue.append(item) # type: ignore self._queue.append(item) # type: ignore
self._bytes_sum += len(item[1]) self._bytes_sum += len(item[1])
def full(self, item: tuple[Event, bytes] = None): def full(self, item: tuple[Event, bytes] | None = None):
"""Return True if there are maxsize bytes in the queue. """Return True if there are maxsize bytes in the queue.
optionally if `item` is provided, it will return False if there is enough space to optionally if `item` is provided, it will return False if there is enough space to