Switch to using UIProtect library (#160)

* Updated poetry dependencies to remove optional flags on dev/test

* file fixups from running poetry run tox

* Updated to Python 3.10

* Switched to UI Protect library

* Updated changelog

* Fix docker permissions

- Make scripts executable by everyone
- Correct XDG variable name to fix incorrect config path being used

* Revert "Updated poetry dependencies to remove optional flags on dev/test" and regenerated lock file
This reverts commit 432d0d3df7.

---------

Co-authored-by: Sebastian Goscik <sebastian.goscik@live.co.uk>
This commit is contained in:
Lloyd Pickering
2024-08-09 23:16:19 +01:00
committed by GitHub
parent a8328fd09e
commit ccf2cde272
19 changed files with 826 additions and 842 deletions

View File

@@ -59,7 +59,7 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.10
- name: Install dependencies
run: |

View File

@@ -39,7 +39,7 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.10
- name: Install dependencies
run: |

View File

@@ -21,7 +21,7 @@ repos:
rev: 21.5b1
hooks:
- id: black
language_version: python3.9
language_version: python3.10
- repo: https://github.com/pycqa/flake8
rev: 3.9.2
hooks:

View File

@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.12.0] - 2024-08-06
### Fixed
- Tool now targets UIProtect instead of pyunifiprotect which should help any lingering auth issues with Unifi OS 4.X
- Python Version bumped to 3.10 (based on UIPortect need)
- (had to make the dev and test dependencies required instead of extras to get poetry to work)
## [0.11.0] - 2024-06-08
### Added
- A new experimental downloader that uses the same mechanism the web ui does. Enable with

View File

@@ -103,7 +103,7 @@ Before you submit a pull request, check that it meets these guidelines:
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring. If adding a CLI
option, you should update the "usage" in README.md.
3. The pull request should work for Python 3.9. Check
3. The pull request should work for Python 3.10. Check
https://github.com/ep1cman/unifi-protect-backup/actions
and make sure that the tests pass for all supported Python versions.

View File

@@ -51,7 +51,7 @@ ENV IGNORE_CAMERAS=""
ENV SQLITE_PATH=/config/database/events.sqlite
# Fixes issue where `platformdirs` is unable to properly detect the user directory
ENV XDG_CACHE_HOME=/config
ENV XDG_CONFIG_HOME=/config
COPY docker_root/ /

View File

@@ -28,8 +28,8 @@ retention period.
- Automatic pruning of old clips
## Requirements
- Python 3.9+
- Unifi Protect version 1.20 or higher (as per [`pyunifiprotect`](https://github.com/briis/pyunifiprotect))
- Python 3.10+
- Unifi Protect version 1.20 or higher (as per [`uiprotect`](https://github.com/uilibs/uiprotect))
- `rclone` installed with at least one remote configured.
# Setup
@@ -235,7 +235,7 @@ If you wish for the clips to be structured differently you can do this using the
option. It uses standard [python format string syntax](https://docs.python.org/3/library/string.html#formatstrings).
The following fields are provided to the format string:
- *event:* The `Event` object as per https://github.com/briis/pyunifiprotect/blob/master/pyunifiprotect/data/nvr.py
- *event:* The `Event` object as per https://github.com/uilibs/uiprotect/blob/main/src/uiprotect/data/nvr.py
- *duration_seconds:* The duration of the event in seconds
- *detection_type:* A nicely formatted list of the event detection type and the smart detection types (if any)
- *camera_name:* The name of the camera that generated this event
@@ -355,7 +355,7 @@ docker run \
</a>
- Heavily utilises [`pyunifiprotect`](https://github.com/briis/pyunifiprotect) by [@briis](https://github.com/briis/)
- Heavily utilises [`uiprotect`](https://github.com/uilibs/uiprotect)
- All the cloud functionality is provided by [`rclone`](https://rclone.org/)
- This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [waynerv/cookiecutter-pypackage](https://github.com/waynerv/cookiecutter-pypackage) project template.

0
docker_root/etc/cont-init.d/30-config Normal file → Executable file
View File

2
docker_root/etc/services.d/unifi-protect-backup/run Normal file → Executable file
View File

@@ -2,6 +2,8 @@
export RCLONE_CONFIG=/config/rclone/rclone.conf
export XDG_CACHE_HOME=/config
echo $VERBOSITY
[[ -n "$VERBOSITY" ]] && export VERBOSITY_ARG=-$VERBOSITY || export VERBOSITY_ARG=""

1572
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ classifiers=[
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
]
packages = [
{ include = "unifi_protect_backup" },
@@ -21,7 +21,7 @@ packages = [
]
[tool.poetry.dependencies]
python = ">=3.9.0,<4.0"
python = ">=3.10.0,<4.0"
click = "8.0.1"
aiorun = "^2023.7.2"
aiosqlite = "^0.17.0"
@@ -30,7 +30,7 @@ apprise = "^1.5.0"
expiring-dict = "^1.1.0"
async-lru = "^2.0.4"
aiolimiter = "^1.1.0"
pyunifiprotect = {git = "https://github.com/ep1cman/pyunifiprotect.git", rev = "experimental"}
uiprotect = "^5.4.0"
[tool.poetry.group.dev]
optional = true
@@ -64,7 +64,7 @@ unifi-protect-backup = 'unifi_protect_backup.cli:main'
[tool.black]
line-length = 120
skip-string-normalization = true
target-version = ['py39']
target-version = ['py310']
include = '\.pyi?$'
exclude = '''
/(

View File

@@ -40,11 +40,11 @@ exclude_lines =
[tox:tox]
isolated_build = true
envlist = py39, format, lint, build
envlist = py310, format, lint, build
[gh-actions]
python =
3.9: py39, format, lint, build
3.10: py310, format, lint, build
[testenv]
allowlist_externals = pytest

View File

@@ -10,11 +10,11 @@ from typing import Optional
import aiosqlite
import pytz
from aiohttp.client_exceptions import ClientPayloadError
from expiring_dict import ExpiringDict # type: ignore
from aiolimiter import AsyncLimiter
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType
from expiring_dict import ExpiringDict # type: ignore
from uiprotect import ProtectApiClient
from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from unifi_protect_backup.utils import (
SubprocessException,
@@ -102,7 +102,7 @@ class VideoDownloader:
self.current_event = event
self.logger = logging.LoggerAdapter(self.base_logger, {'event': f' [{event.id}]'})
# Fix timezones since pyunifiprotect sets all timestamps to UTC. Instead localize them to
# Fix timezones since uiprotect sets all timestamps to UTC. Instead localize them to
# the timezone of the unifi protect NVR.
event.start = event.start.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone)
event.end = event.end.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone)

View File

@@ -10,11 +10,11 @@ from typing import Optional
import aiosqlite
import pytz
from aiohttp.client_exceptions import ClientPayloadError
from expiring_dict import ExpiringDict # type: ignore
from aiolimiter import AsyncLimiter
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType
from expiring_dict import ExpiringDict # type: ignore
from uiprotect import ProtectApiClient
from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from unifi_protect_backup.utils import (
SubprocessException,
@@ -102,7 +102,7 @@ class VideoDownloaderExperimental:
self.current_event = event
self.logger = logging.LoggerAdapter(self.base_logger, {'event': f' [{event.id}]'})
# Fix timezones since pyunifiprotect sets all timestamps to UTC. Instead localize them to
# Fix timezones since uiprotect sets all timestamps to UTC. Instead localize them to
# the timezone of the unifi protect NVR.
event.start = event.start.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone)
event.end = event.end.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone)

View File

@@ -5,10 +5,10 @@ import logging
from time import sleep
from typing import List
from pyunifiprotect.api import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType
from pyunifiprotect.data.websocket import WSAction, WSSubscriptionMessage
from uiprotect.api import ProtectApiClient
from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from uiprotect.data.websocket import WSAction, WSSubscriptionMessage
logger = logging.getLogger(__name__)
@@ -111,7 +111,7 @@ class EventListener:
logger.warning("Attempting reconnect...")
try:
# Start the pyunifiprotect connection by calling `update`
# Start the uiprotect connection by calling `update`
await self._protect.close_session()
self._protect._bootstrap = None
await self._protect.update(force=True)

View File

@@ -7,9 +7,9 @@ from typing import List
import aiosqlite
from dateutil.relativedelta import relativedelta
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType
from uiprotect import ProtectApiClient
from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from unifi_protect_backup import VideoDownloader, VideoUploader

View File

@@ -4,13 +4,13 @@ import asyncio
import logging
import os
import shutil
from datetime import datetime, timezone, timedelta
from datetime import datetime, timedelta, timezone
from typing import Callable, List
import aiosqlite
from dateutil.relativedelta import relativedelta
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.types import ModelType
from uiprotect import ProtectApiClient
from uiprotect.data.types import ModelType
from unifi_protect_backup import (
EventListener,
@@ -188,7 +188,7 @@ class UnifiProtectBackup:
logger.info("Checking rclone configuration...")
await self._check_rclone()
# Start the pyunifiprotect connection by calling `update`
# Start the uiprotect connection by calling `update`
logger.info("Connecting to Unifi Protect...")
for attempts in range(10):

View File

@@ -6,16 +6,16 @@ import re
from datetime import datetime
import aiosqlite
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from uiprotect import ProtectApiClient
from uiprotect.data.nvr import Event
from unifi_protect_backup.utils import (
SubprocessException,
VideoQueue,
get_camera_name,
human_readable_size,
run_command,
setup_event_logger,
SubprocessException,
)
@@ -138,7 +138,7 @@ class VideoUploader:
Provides the following fields to the format string:
event: The `Event` object as per
https://github.com/briis/pyunifiprotect/blob/master/pyunifiprotect/data/nvr.py
https://github.com/briis/uiprotect/blob/master/uiprotect/data/nvr.py
duration_seconds: The duration of the event in seconds
detection_type: A nicely formatted list of the event detection type and the smart detection types (if any)
camera_name: The name of the camera that generated this event

View File

@@ -8,8 +8,8 @@ from typing import List, Optional
from apprise import NotifyType
from async_lru import alru_cache
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from uiprotect import ProtectApiClient
from uiprotect.data.nvr import Event
from unifi_protect_backup import notifications