Skip to content

Commit 1ebb480

Browse files
committed
Replace print statements with log, add PyYAML to requirements, fix test and add more type hints
1 parent 40e359c commit 1ebb480

25 files changed

+409
-189
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
*.bak
12
secrets.cfg
23
# Byte-compiled / optimized / DLL files
34
__pycache__/

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ _Note: 'Unreleased' section below is used for untagged changes that will be issu
1515
#### Security
1616
__BEGIN-CHANGELOG__
1717

18+
### [1.0.1] - 2022-04-24
19+
#### Added
20+
- More type hints (albeit they'll need to be made more precise later)
21+
- `PyYAML` was left out of requirements upon scanning the older `setup.py` file, so that was added & pinned
22+
#### Changed
23+
- `print` statements are now log outputs
24+
- Some dictionaries were simplified and optimized
25+
#### Fixed
26+
- Minor variable misspellings
27+
- `test_camera.py` now doesn't test a real camera connection
28+
1829
### [1.0.0] - 2022-04-23
1930
#### Added
2031
- `CHANGELOG.md`

poetry.lock

Lines changed: 79 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "reolink_api"
7-
version = '1.0.0'
7+
version = '1.0.1'
88
description = "Unofficial Reolink API fork for personal use in Python 3.10"
99
authors = ["bobrock <[email protected]>"]
1010
license = "GPL-3.0"
@@ -19,6 +19,8 @@ include = ["CHANGELOG.md"]
1919
python = "^3.10"
2020
Pillow = "9.1.0"
2121
PySocks = "1.7.1"
22+
PyYAML = "6.0"
23+
loguru = "0.6.0"
2224
numpy = "^1.22.3"
2325
opencv-python = "4.5.5.64"
2426
requests = "^2.27.1"

reolink_api/APIHandler.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
from typing import (
2+
Dict,
3+
List,
4+
Union
5+
)
16
import requests
7+
from loguru import logger
8+
from reolink_api.alarm import AlarmAPIMixin
9+
from reolink_api.device import DeviceAPIMixin
10+
from reolink_api.display import DisplayAPIMixin
11+
from reolink_api.download import DownloadAPIMixin
12+
from reolink_api.image import ImageAPIMixin
13+
from reolink_api.motion import MotionAPIMixin
14+
from reolink_api.network import NetworkAPIMixin
15+
from reolink_api.ptz import PtzAPIMixin
16+
from reolink_api.recording import RecordingAPIMixin
217
from reolink_api.resthandle import Request
3-
from .alarm import AlarmAPIMixin
4-
from .device import DeviceAPIMixin
5-
from .display import DisplayAPIMixin
6-
from .download import DownloadAPIMixin
7-
from .image import ImageAPIMixin
8-
from .motion import MotionAPIMixin
9-
from .network import NetworkAPIMixin
10-
from .ptz import PtzAPIMixin
11-
from .recording import RecordingAPIMixin
12-
from .system import SystemAPIMixin
13-
from .user import UserAPIMixin
14-
from .zoom import ZoomAPIMixin
18+
from reolink_api.system import SystemAPIMixin
19+
from reolink_api.user import UserAPIMixin
20+
from reolink_api.zoom import ZoomAPIMixin
1521

1622

1723
class APIHandler(AlarmAPIMixin,
@@ -35,7 +41,7 @@ class APIHandler(AlarmAPIMixin,
3541
All Code will try to follow the PEP 8 standard as described here: https://www.python.org/dev/peps/pep-0008/
3642
"""
3743

38-
def __init__(self, ip: str, username: str, password: str, https=False, **kwargs):
44+
def __init__(self, ip: str, username: str, password: str, https: bool = False, **kwargs):
3945
"""
4046
Initialise the Camera API Handler (maps api calls into python)
4147
:param ip:
@@ -63,21 +69,22 @@ def login(self) -> bool:
6369
body = [{"cmd": "Login", "action": 0,
6470
"param": {"User": {"userName": self.username, "password": self.password}}}]
6571
param = {"cmd": "Login", "token": "null"}
66-
response = Request.post(self.url, data=body, params=param)
72+
response = Request.post(self.url, data=body, params=param) # type: requests.Response
6773
if response is not None:
6874
data = response.json()[0]
6975
code = data["code"]
7076
if int(code) == 0:
7177
self.token = data["value"]["Token"]["name"]
72-
print("Login success")
78+
logger.debug("Login success")
7379
return True
74-
print(self.token)
80+
logger.debug(self.token)
7581
return False
7682
else:
77-
print("Failed to login\nStatus Code:", response.status_code)
83+
# Response object was NoneType (empty)
84+
logger.warning("Failed to login\nError in request.")
7885
return False
7986
except Exception as e:
80-
print("Error Login\n", e)
87+
logger.error("Error Login\n", e)
8188
raise
8289

8390
def logout(self) -> bool:
@@ -88,13 +95,12 @@ def logout(self) -> bool:
8895
try:
8996
data = [{"cmd": "Logout", "action": 0}]
9097
self._execute_command('Logout', data)
91-
# print(ret)
9298
return True
9399
except Exception as e:
94-
print("Error Logout\n", e)
100+
logger.error("Error Logout\n", e)
95101
return False
96102

97-
def _execute_command(self, command, data, multi=False):
103+
def _execute_command(self, command: str, data: List[Dict], multi: bool = False) -> Union[bool, Dict]:
98104
"""
99105
Send a POST request to the IP camera with given data.
100106
:param command: name of the command to send
@@ -122,12 +128,12 @@ def _execute_command(self, command, data, multi=False):
122128
f.write(req.content)
123129
return True
124130
else:
125-
print(f'Error received: {req.status_code}')
131+
logger.error(f'Error received: {req.status_code}')
126132
return False
127133

128134
else:
129135
response = Request.post(self.url, data=data, params=params)
130-
return response.json()
136+
return response.json()
131137
except Exception as e:
132-
print(f"Command {command} failed: {e}")
138+
logger.error(f"Command {command} failed: {e}")
133139
raise

reolink_api/ConfigHandler.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import io
2+
from typing import (
3+
Dict,
4+
Optional
5+
)
6+
from loguru import logger
27
import yaml
38

49

510
class ConfigHandler:
611
camera_settings = {}
712

813
@staticmethod
9-
def load() -> yaml or None:
14+
def load() -> Optional[Dict]:
1015
try:
1116
stream = io.open("config.yml", 'r', encoding='utf8')
1217
data = yaml.safe_load(stream)
1318
return data
1419
except Exception as e:
15-
print("Config Property Error\n", e)
20+
logger.error("Config Property Error\n", e)
1621
return None

reolink_api/RtspClient.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from threading import ThreadError
3+
from loguru import logger
34
import cv2
45
from reolink_api.util import threaded
56

@@ -12,7 +13,8 @@ class RtspClient:
1213
- https://stackoverflow.com/questions/55828451/video-streaming-from-ip-camera-in-python-using-opencv-cv2-videocapture
1314
"""
1415

15-
def __init__(self, ip, username, password, port=554, profile="main", use_udp=True, callback=None, **kwargs):
16+
def __init__(self, ip: str, username: str, password: str, port: int = 554, profile: str = "main",
17+
use_udp: bool = True, callback=None, **kwargs):
1618
"""
1719
RTSP client is used to retrieve frames from the camera in a stream
1820
@@ -34,12 +36,8 @@ def __init__(self, ip, username, password, port=554, profile="main", use_udp=Tru
3436
self.password = password
3537
self.port = port
3638
self.proxy = kwargs.get("proxies")
37-
self.url = "rtsp://" + self.username + ":" + self.password + "@" + \
38-
self.ip + ":" + str(self.port) + "//h264Preview_01_" + profile
39-
if use_udp:
40-
capture_options = capture_options + 'udp'
41-
else:
42-
capture_options = capture_options + 'tcp'
39+
self.url = f'rtsp://{self.username}:{self.password}@{self.ip}:{self.port}//h264Preview_01_{profile}'
40+
capture_options += 'udp' if use_udp else 'tcp'
4341

4442
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = capture_options
4543

@@ -50,19 +48,19 @@ def _open_video_capture(self):
5048
# To CAP_FFMPEG or not To ?
5149
self.capture = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG)
5250

53-
def _stream_blocking(self):
51+
def _stream_blocking(self) -> object:
5452
while True:
5553
try:
5654
if self.capture.isOpened():
5755
ret, frame = self.capture.read()
5856
if ret:
5957
yield frame
6058
else:
61-
print("stream closed")
59+
logger.info("stream closed")
6260
self.capture.release()
6361
return
6462
except Exception as e:
65-
print(e)
63+
logger.error(e)
6664
self.capture.release()
6765
return
6866

@@ -75,17 +73,17 @@ def _stream_non_blocking(self):
7573
if ret:
7674
self.callback(frame)
7775
else:
78-
print("stream is closed")
76+
logger.info("stream is closed")
7977
self.stop_stream()
8078
except ThreadError as e:
81-
print(e)
79+
logger.error(e)
8280
self.stop_stream()
8381

8482
def stop_stream(self):
8583
self.capture.release()
8684
self.thread_cancelled = True
8785

88-
def open_stream(self):
86+
def open_stream(self) -> object:
8987
"""
9088
Opens OpenCV Video stream and returns the result according to the OpenCV documentation
9189
https://docs.opencv.org/3.4/d8/dfe/classcv_1_1VideoCapture.html#a473055e77dd7faa4d26d686226b292c1
@@ -95,7 +93,7 @@ def open_stream(self):
9593
if self.capture is None or not self.capture.isOpened():
9694
self._open_video_capture()
9795

98-
print("opening stream")
96+
logger.info("opening stream")
9997

10098
if self.callback is None:
10199
return self._stream_blocking()

reolink_api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .APIHandler import APIHandler
22
from .Camera import Camera
33

4-
__version__ = "1.0.0"
5-
__update_date__ = '2022-04-23_10:56:59'
4+
__version__ = '1.0.1'
5+
__update_date__ = '2022-04-24_10:52:20'

0 commit comments

Comments
 (0)