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 - uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.10
- name: Install dependencies - name: Install dependencies
run: | run: |

View File

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

View File

@@ -21,7 +21,7 @@ repos:
rev: 21.5b1 rev: 21.5b1
hooks: hooks:
- id: black - id: black
language_version: python3.9 language_version: python3.10
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: 3.9.2 rev: 3.9.2
hooks: 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/), 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). 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 ## [0.11.0] - 2024-06-08
### Added ### Added
- A new experimental downloader that uses the same mechanism the web ui does. Enable with - 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 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 your new functionality into a function with a docstring. If adding a CLI
option, you should update the "usage" in README.md. 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 https://github.com/ep1cman/unifi-protect-backup/actions
and make sure that the tests pass for all supported Python versions. and make sure that the tests pass for all supported Python versions.

View File

@@ -10,7 +10,7 @@ WORKDIR /app
COPY dist/unifi_protect_backup-0.11.0.tar.gz sdist.tar.gz COPY dist/unifi_protect_backup-0.11.0.tar.gz sdist.tar.gz
# https://github.com/rust-lang/cargo/issues/2808 # https://github.com/rust-lang/cargo/issues/2808
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
RUN \ RUN \
echo "**** install build packages ****" && \ echo "**** install build packages ****" && \
@@ -51,7 +51,7 @@ ENV IGNORE_CAMERAS=""
ENV SQLITE_PATH=/config/database/events.sqlite ENV SQLITE_PATH=/config/database/events.sqlite
# Fixes issue where `platformdirs` is unable to properly detect the user directory # 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/ / COPY docker_root/ /

View File

@@ -28,8 +28,8 @@ retention period.
- Automatic pruning of old clips - Automatic pruning of old clips
## Requirements ## Requirements
- Python 3.9+ - Python 3.10+
- Unifi Protect version 1.20 or higher (as per [`pyunifiprotect`](https://github.com/briis/pyunifiprotect)) - Unifi Protect version 1.20 or higher (as per [`uiprotect`](https://github.com/uilibs/uiprotect))
- `rclone` installed with at least one remote configured. - `rclone` installed with at least one remote configured.
# Setup # 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). 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: 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 - *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) - *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 - *camera_name:* The name of the camera that generated this event
@@ -355,7 +355,7 @@ docker run \
</a> </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/) - 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. - 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 RCLONE_CONFIG=/config/rclone/rclone.conf
export XDG_CACHE_HOME=/config
echo $VERBOSITY echo $VERBOSITY
[[ -n "$VERBOSITY" ]] && export VERBOSITY_ARG=-$VERBOSITY || export VERBOSITY_ARG="" [[ -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', 'License :: OSI Approved :: MIT License',
'Natural Language :: English', 'Natural Language :: English',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10',
] ]
packages = [ packages = [
{ include = "unifi_protect_backup" }, { include = "unifi_protect_backup" },
@@ -21,7 +21,7 @@ packages = [
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9.0,<4.0" python = ">=3.10.0,<4.0"
click = "8.0.1" click = "8.0.1"
aiorun = "^2023.7.2" aiorun = "^2023.7.2"
aiosqlite = "^0.17.0" aiosqlite = "^0.17.0"
@@ -30,7 +30,7 @@ apprise = "^1.5.0"
expiring-dict = "^1.1.0" expiring-dict = "^1.1.0"
async-lru = "^2.0.4" async-lru = "^2.0.4"
aiolimiter = "^1.1.0" aiolimiter = "^1.1.0"
pyunifiprotect = {git = "https://github.com/ep1cman/pyunifiprotect.git", rev = "experimental"} uiprotect = "^5.4.0"
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true
@@ -64,7 +64,7 @@ unifi-protect-backup = 'unifi_protect_backup.cli:main'
[tool.black] [tool.black]
line-length = 120 line-length = 120
skip-string-normalization = true skip-string-normalization = true
target-version = ['py39'] target-version = ['py310']
include = '\.pyi?$' include = '\.pyi?$'
exclude = ''' exclude = '''
/( /(

View File

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

View File

@@ -10,11 +10,11 @@ from typing import Optional
import aiosqlite import aiosqlite
import pytz import pytz
from aiohttp.client_exceptions import ClientPayloadError from aiohttp.client_exceptions import ClientPayloadError
from expiring_dict import ExpiringDict # type: ignore
from aiolimiter import AsyncLimiter from aiolimiter import AsyncLimiter
from pyunifiprotect import ProtectApiClient from expiring_dict import ExpiringDict # type: ignore
from pyunifiprotect.data.nvr import Event from uiprotect import ProtectApiClient
from pyunifiprotect.data.types import EventType from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from unifi_protect_backup.utils import ( from unifi_protect_backup.utils import (
SubprocessException, SubprocessException,
@@ -102,7 +102,7 @@ class VideoDownloader:
self.current_event = event self.current_event = event
self.logger = logging.LoggerAdapter(self.base_logger, {'event': f' [{event.id}]'}) 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. # the timezone of the unifi protect NVR.
event.start = event.start.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone) 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) 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 aiosqlite
import pytz import pytz
from aiohttp.client_exceptions import ClientPayloadError from aiohttp.client_exceptions import ClientPayloadError
from expiring_dict import ExpiringDict # type: ignore
from aiolimiter import AsyncLimiter from aiolimiter import AsyncLimiter
from pyunifiprotect import ProtectApiClient from expiring_dict import ExpiringDict # type: ignore
from pyunifiprotect.data.nvr import Event from uiprotect import ProtectApiClient
from pyunifiprotect.data.types import EventType from uiprotect.data.nvr import Event
from uiprotect.data.types import EventType
from unifi_protect_backup.utils import ( from unifi_protect_backup.utils import (
SubprocessException, SubprocessException,
@@ -102,7 +102,7 @@ class VideoDownloaderExperimental:
self.current_event = event self.current_event = event
self.logger = logging.LoggerAdapter(self.base_logger, {'event': f' [{event.id}]'}) 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. # the timezone of the unifi protect NVR.
event.start = event.start.replace(tzinfo=pytz.utc).astimezone(self._protect.bootstrap.nvr.timezone) 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) 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 time import sleep
from typing import List from typing import List
from pyunifiprotect.api import ProtectApiClient from uiprotect.api import ProtectApiClient
from pyunifiprotect.data.nvr import Event from uiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType from uiprotect.data.types import EventType
from pyunifiprotect.data.websocket import WSAction, WSSubscriptionMessage from uiprotect.data.websocket import WSAction, WSSubscriptionMessage
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -111,7 +111,7 @@ class EventListener:
logger.warning("Attempting reconnect...") logger.warning("Attempting reconnect...")
try: try:
# Start the pyunifiprotect connection by calling `update` # Start the uiprotect connection by calling `update`
await self._protect.close_session() await self._protect.close_session()
self._protect._bootstrap = None self._protect._bootstrap = None
await self._protect.update(force=True) await self._protect.update(force=True)

View File

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

View File

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

View File

@@ -6,16 +6,16 @@ import re
from datetime import datetime from datetime import datetime
import aiosqlite import aiosqlite
from pyunifiprotect import ProtectApiClient from uiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event from uiprotect.data.nvr import Event
from unifi_protect_backup.utils import ( from unifi_protect_backup.utils import (
SubprocessException,
VideoQueue, VideoQueue,
get_camera_name, get_camera_name,
human_readable_size, human_readable_size,
run_command, run_command,
setup_event_logger, setup_event_logger,
SubprocessException,
) )
@@ -138,7 +138,7 @@ class VideoUploader:
Provides the following fields to the format string: Provides the following fields to the format string:
event: The `Event` object as per 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 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) 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 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 apprise import NotifyType
from async_lru import alru_cache from async_lru import alru_cache
from pyunifiprotect import ProtectApiClient from uiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event from uiprotect.data.nvr import Event
from unifi_protect_backup import notifications from unifi_protect_backup import notifications