pyezvizapi 1.0.2.1__py3-none-any.whl → 1.0.2.3__py3-none-any.whl
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.
- pyezvizapi/camera.py +18 -90
- pyezvizapi/utils.py +156 -0
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/METADATA +1 -1
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/RECORD +9 -9
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/WHEEL +0 -0
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/entry_points.txt +0 -0
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/licenses/LICENSE +0 -0
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/licenses/LICENSE.md +0 -0
- {pyezvizapi-1.0.2.1.dist-info → pyezvizapi-1.0.2.3.dist-info}/top_level.txt +0 -0
pyezvizapi/camera.py
CHANGED
|
@@ -4,14 +4,17 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
6
6
|
import logging
|
|
7
|
-
import re
|
|
8
7
|
from typing import TYPE_CHECKING, Any, Literal, TypedDict, cast
|
|
9
|
-
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
10
8
|
|
|
11
9
|
from .constants import BatteryCameraWorkMode, DeviceSwitchType, SoundMode
|
|
12
10
|
from .exceptions import PyEzvizError
|
|
13
11
|
from .models import EzvizDeviceRecord
|
|
14
|
-
from .utils import
|
|
12
|
+
from .utils import (
|
|
13
|
+
compute_motion_from_alarm,
|
|
14
|
+
fetch_nested_value,
|
|
15
|
+
parse_timezone_value,
|
|
16
|
+
string_to_list,
|
|
17
|
+
)
|
|
15
18
|
|
|
16
19
|
if TYPE_CHECKING:
|
|
17
20
|
from .client import EzvizClient
|
|
@@ -99,6 +102,7 @@ class EzvizCamera:
|
|
|
99
102
|
self._alarmmotiontrigger: dict[str, Any] = {
|
|
100
103
|
"alarm_trigger_active": False,
|
|
101
104
|
"timepassed": None,
|
|
105
|
+
"last_alarm_time_str": None,
|
|
102
106
|
}
|
|
103
107
|
self._record: EzvizDeviceRecord | None = None
|
|
104
108
|
|
|
@@ -171,100 +175,21 @@ class EzvizCamera:
|
|
|
171
175
|
|
|
172
176
|
Prefer numeric epoch fields if available to avoid parsing localized strings.
|
|
173
177
|
"""
|
|
174
|
-
# Use timezone-aware datetimes based on camera or local timezone.
|
|
175
178
|
tzinfo = self._get_tzinfo()
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
# Prefer epoch fields if available
|
|
179
|
-
epoch = self._last_alarm.get("alarmStartTime") or self._last_alarm.get(
|
|
180
|
-
"alarmTime"
|
|
179
|
+
active, seconds_out, last_alarm_str = compute_motion_from_alarm(
|
|
180
|
+
self._last_alarm, tzinfo
|
|
181
181
|
)
|
|
182
|
-
last_alarm_dt: datetime.datetime | None = None
|
|
183
|
-
if epoch is not None:
|
|
184
|
-
try:
|
|
185
|
-
# Accept int/float/str; auto-detect ms vs s
|
|
186
|
-
if isinstance(epoch, str):
|
|
187
|
-
epoch = float(epoch)
|
|
188
|
-
ts = float(epoch)
|
|
189
|
-
if ts > 1e11: # very likely milliseconds
|
|
190
|
-
ts = ts / 1000.0
|
|
191
|
-
last_alarm_dt = datetime.datetime.fromtimestamp(ts, tz=tzinfo)
|
|
192
|
-
except (
|
|
193
|
-
TypeError,
|
|
194
|
-
ValueError,
|
|
195
|
-
OSError,
|
|
196
|
-
): # fall back to string parsing below
|
|
197
|
-
last_alarm_dt = None
|
|
198
|
-
|
|
199
|
-
if last_alarm_dt is None:
|
|
200
|
-
# Fall back to string parsing
|
|
201
|
-
raw = str(
|
|
202
|
-
self._last_alarm.get("alarmStartTimeStr")
|
|
203
|
-
or self._last_alarm.get("alarmTimeStr")
|
|
204
|
-
or ""
|
|
205
|
-
)
|
|
206
|
-
if not raw:
|
|
207
|
-
return
|
|
208
|
-
if "Today" in raw:
|
|
209
|
-
raw = raw.replace("Today", str(now.date()))
|
|
210
|
-
try:
|
|
211
|
-
last_alarm_dt = datetime.datetime.strptime(
|
|
212
|
-
raw, "%Y-%m-%d %H:%M:%S"
|
|
213
|
-
).replace(tzinfo=tzinfo)
|
|
214
|
-
except ValueError: # Unrecognized format; give up gracefully
|
|
215
|
-
_LOGGER.debug(
|
|
216
|
-
"Unrecognized alarm time format for %s: %s", self._serial, raw
|
|
217
|
-
)
|
|
218
|
-
self._alarmmotiontrigger = {
|
|
219
|
-
"alarm_trigger_active": False,
|
|
220
|
-
"timepassed": None,
|
|
221
|
-
}
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
timepassed = now - last_alarm_dt
|
|
225
|
-
seconds = max(0.0, timepassed.total_seconds()) if timepassed else None
|
|
226
182
|
|
|
227
183
|
self._alarmmotiontrigger = {
|
|
228
|
-
"alarm_trigger_active":
|
|
229
|
-
"timepassed":
|
|
184
|
+
"alarm_trigger_active": active,
|
|
185
|
+
"timepassed": seconds_out,
|
|
186
|
+
"last_alarm_time_str": last_alarm_str,
|
|
230
187
|
}
|
|
231
188
|
|
|
232
189
|
def _get_tzinfo(self) -> datetime.tzinfo:
|
|
233
|
-
"""Return tzinfo from camera setting if recognizable, else local tzinfo.
|
|
234
|
-
|
|
235
|
-
Attempts to parse common formats like 'UTC+02:00', 'GMT+8', '+0530', or IANA names.
|
|
236
|
-
Falls back to local timezone.
|
|
237
|
-
"""
|
|
190
|
+
"""Return tzinfo from camera setting if recognizable, else local tzinfo."""
|
|
238
191
|
tz_val = self.fetch_key(["STATUS", "optionals", "timeZone"])
|
|
239
|
-
|
|
240
|
-
if isinstance(tz_val, str) and "/" in tz_val:
|
|
241
|
-
try:
|
|
242
|
-
return ZoneInfo(tz_val)
|
|
243
|
-
except ZoneInfoNotFoundError:
|
|
244
|
-
pass
|
|
245
|
-
# Offset formats
|
|
246
|
-
offset_minutes: int | None = None
|
|
247
|
-
if isinstance(tz_val, int):
|
|
248
|
-
# Heuristic: treat small absolute values as hours, large as minutes/seconds
|
|
249
|
-
if -14 <= tz_val <= 14:
|
|
250
|
-
offset_minutes = tz_val * 60
|
|
251
|
-
elif -24 * 60 <= tz_val <= 24 * 60:
|
|
252
|
-
offset_minutes = tz_val
|
|
253
|
-
elif -24 * 3600 <= tz_val <= 24 * 3600:
|
|
254
|
-
offset_minutes = int(tz_val / 60)
|
|
255
|
-
elif isinstance(tz_val, str):
|
|
256
|
-
s = tz_val.strip().upper().replace("UTC", "").replace("GMT", "")
|
|
257
|
-
# Normalize formats like '+02:00', '+0200', '+2'
|
|
258
|
-
m = re.match(r"^([+-]?)(\d{1,2})(?::?(\d{2}))?$", s)
|
|
259
|
-
if m:
|
|
260
|
-
sign = -1 if m.group(1) == "-" else 1
|
|
261
|
-
hours = int(m.group(2))
|
|
262
|
-
minutes = int(m.group(3)) if m.group(3) else 0
|
|
263
|
-
offset_minutes = sign * (hours * 60 + minutes)
|
|
264
|
-
if offset_minutes is not None:
|
|
265
|
-
return datetime.timezone(datetime.timedelta(minutes=offset_minutes))
|
|
266
|
-
# Fallback to local timezone
|
|
267
|
-
return datetime.datetime.now().astimezone().tzinfo or datetime.UTC
|
|
192
|
+
return parse_timezone_value(tz_val)
|
|
268
193
|
|
|
269
194
|
def _is_alarm_schedules_enabled(self) -> bool:
|
|
270
195
|
"""Check if alarm schedules enabled."""
|
|
@@ -370,7 +295,10 @@ class EzvizCamera:
|
|
|
370
295
|
"PIR_Status": self.fetch_key(["STATUS", "pirStatus"]),
|
|
371
296
|
"Motion_Trigger": self._alarmmotiontrigger["alarm_trigger_active"],
|
|
372
297
|
"Seconds_Last_Trigger": self._alarmmotiontrigger["timepassed"],
|
|
373
|
-
|
|
298
|
+
# Keep last_alarm_time in sync with the time actually used to
|
|
299
|
+
# compute Motion_Trigger/Seconds_Last_Trigger.
|
|
300
|
+
"last_alarm_time": self._alarmmotiontrigger.get("last_alarm_time_str")
|
|
301
|
+
or self._last_alarm.get("alarmStartTimeStr"),
|
|
374
302
|
"last_alarm_pic": self._last_alarm.get(
|
|
375
303
|
"picUrl",
|
|
376
304
|
"https://eustatics.ezvizlife.com/ovs_mall/web/img/index/EZVIZ_logo.png?ver=3007907502",
|
pyezvizapi/utils.py
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import datetime
|
|
5
6
|
from hashlib import md5
|
|
6
7
|
import json
|
|
7
8
|
import logging
|
|
9
|
+
import re as _re
|
|
8
10
|
from typing import Any
|
|
9
11
|
import uuid
|
|
12
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
10
13
|
|
|
11
14
|
from Crypto.Cipher import AES
|
|
12
15
|
|
|
@@ -188,3 +191,156 @@ def generate_unique_code() -> str:
|
|
|
188
191
|
mac_int = uuid.getnode()
|
|
189
192
|
mac_str = ":".join(f"{(mac_int >> i) & 0xFF:02x}" for i in range(40, -1, -8))
|
|
190
193
|
return md5(mac_str.encode("utf-8")).hexdigest()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# Time helpers for alarm/motion handling
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
def normalize_alarm_time(
|
|
201
|
+
last_alarm: dict[str, Any], tzinfo: datetime.tzinfo
|
|
202
|
+
) -> tuple[datetime.datetime | None, datetime.datetime | None, str | None]:
|
|
203
|
+
"""Normalize EZVIZ alarm timestamps.
|
|
204
|
+
|
|
205
|
+
Returns a tuple of:
|
|
206
|
+
- alarm_dt_local: datetime in the camera's timezone (for display)
|
|
207
|
+
- alarm_dt_utc: datetime in UTC (for robust delta calculation)
|
|
208
|
+
- alarm_time_str: formatted 'YYYY-MM-DD HH:MM:SS' string in camera tz
|
|
209
|
+
|
|
210
|
+
Behavior:
|
|
211
|
+
- Prefer epoch fields (alarmStartTime/alarmTime). Interpret as UTC by default.
|
|
212
|
+
- If a string time exists and differs from the epoch by >120 seconds,
|
|
213
|
+
reinterpret the epoch as if reported in camera local time.
|
|
214
|
+
- If no epoch, fall back to parsing the string time in the camera tz.
|
|
215
|
+
"""
|
|
216
|
+
# Prefer epoch
|
|
217
|
+
epoch = last_alarm.get("alarmStartTime") or last_alarm.get("alarmTime")
|
|
218
|
+
raw_time_str = str(
|
|
219
|
+
last_alarm.get("alarmStartTimeStr") or last_alarm.get("alarmTimeStr") or ""
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
alarm_dt_local: datetime.datetime | None = None
|
|
223
|
+
alarm_dt_utc: datetime.datetime | None = None
|
|
224
|
+
alarm_str: str | None = None
|
|
225
|
+
|
|
226
|
+
now_local = datetime.datetime.now(tz=tzinfo)
|
|
227
|
+
|
|
228
|
+
if epoch is not None:
|
|
229
|
+
try:
|
|
230
|
+
ts = float(epoch if not isinstance(epoch, str) else float(epoch))
|
|
231
|
+
if ts > 1e11: # milliseconds
|
|
232
|
+
ts /= 1000.0
|
|
233
|
+
event_utc = datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)
|
|
234
|
+
alarm_dt_local = event_utc.astimezone(tzinfo)
|
|
235
|
+
alarm_dt_utc = event_utc
|
|
236
|
+
|
|
237
|
+
if raw_time_str:
|
|
238
|
+
raw_norm = raw_time_str.replace("Today", str(now_local.date()))
|
|
239
|
+
try:
|
|
240
|
+
dt_str_local = datetime.datetime.strptime(
|
|
241
|
+
raw_norm, "%Y-%m-%d %H:%M:%S"
|
|
242
|
+
).replace(tzinfo=tzinfo)
|
|
243
|
+
diff = abs(
|
|
244
|
+
(event_utc - dt_str_local.astimezone(datetime.UTC)).total_seconds()
|
|
245
|
+
)
|
|
246
|
+
if diff > 120:
|
|
247
|
+
# Reinterpret epoch as local clock time in camera tz
|
|
248
|
+
naive_utc = (
|
|
249
|
+
datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)
|
|
250
|
+
.replace(tzinfo=None)
|
|
251
|
+
)
|
|
252
|
+
event_local_reint = naive_utc.replace(tzinfo=tzinfo)
|
|
253
|
+
alarm_dt_local = event_local_reint
|
|
254
|
+
alarm_dt_utc = event_local_reint.astimezone(datetime.UTC)
|
|
255
|
+
except ValueError:
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
if alarm_dt_local is not None:
|
|
259
|
+
alarm_str = alarm_dt_local.strftime("%Y-%m-%d %H:%M:%S")
|
|
260
|
+
return alarm_dt_local, alarm_dt_utc, alarm_str
|
|
261
|
+
# If conversion failed unexpectedly, fall through to string parsing
|
|
262
|
+
except (TypeError, ValueError, OSError):
|
|
263
|
+
alarm_dt_local = None
|
|
264
|
+
|
|
265
|
+
# Fallback to string parsing
|
|
266
|
+
if raw_time_str:
|
|
267
|
+
raw = raw_time_str.replace("Today", str(now_local.date()))
|
|
268
|
+
try:
|
|
269
|
+
alarm_dt_local = datetime.datetime.strptime(raw, "%Y-%m-%d %H:%M:%S").replace(
|
|
270
|
+
tzinfo=tzinfo
|
|
271
|
+
)
|
|
272
|
+
alarm_dt_utc = alarm_dt_local.astimezone(datetime.UTC)
|
|
273
|
+
alarm_str = alarm_dt_local.strftime("%Y-%m-%d %H:%M:%S")
|
|
274
|
+
except ValueError:
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
return alarm_dt_local, alarm_dt_utc, alarm_str
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def compute_motion_from_alarm(
|
|
281
|
+
last_alarm: dict[str, Any], tzinfo: datetime.tzinfo, window_seconds: float = 60.0
|
|
282
|
+
) -> tuple[bool, float, str | None]:
|
|
283
|
+
"""Compute motion state and seconds-since from an alarm payload.
|
|
284
|
+
|
|
285
|
+
Returns (active, seconds_since, last_alarm_time_str).
|
|
286
|
+
- Uses UTC for delta when epoch-derived UTC is available.
|
|
287
|
+
- Falls back to camera local tz deltas when only string times are present.
|
|
288
|
+
- Clamps negative deltas to 0.0 and deactivates motion.
|
|
289
|
+
"""
|
|
290
|
+
alarm_dt_local, alarm_dt_utc, alarm_str = normalize_alarm_time(last_alarm, tzinfo)
|
|
291
|
+
if alarm_dt_local is None:
|
|
292
|
+
return False, 0.0, None
|
|
293
|
+
|
|
294
|
+
now_local = datetime.datetime.now(tz=tzinfo).replace(microsecond=0)
|
|
295
|
+
now_utc = datetime.datetime.now(tz=datetime.UTC).replace(microsecond=0)
|
|
296
|
+
|
|
297
|
+
if alarm_dt_utc is not None:
|
|
298
|
+
delta = now_utc - alarm_dt_utc
|
|
299
|
+
else:
|
|
300
|
+
delta = now_local - alarm_dt_local
|
|
301
|
+
|
|
302
|
+
seconds = float(delta.total_seconds())
|
|
303
|
+
if seconds < 0:
|
|
304
|
+
return False, 0.0, alarm_str
|
|
305
|
+
|
|
306
|
+
return seconds < window_seconds, seconds, alarm_str
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def parse_timezone_value(tz_val: Any) -> datetime.tzinfo:
|
|
310
|
+
"""Parse EZVIZ timeZone value into a tzinfo.
|
|
311
|
+
|
|
312
|
+
Supports:
|
|
313
|
+
- IANA names like 'Europe/Paris'
|
|
314
|
+
- Offsets like 'UTC+02:00', 'GMT-5', '+0530', or integers (hours/minutes/seconds)
|
|
315
|
+
Falls back to the local system timezone, or UTC if unavailable.
|
|
316
|
+
"""
|
|
317
|
+
# IANA zone name
|
|
318
|
+
if isinstance(tz_val, str) and "/" in tz_val:
|
|
319
|
+
try:
|
|
320
|
+
return ZoneInfo(tz_val)
|
|
321
|
+
except ZoneInfoNotFoundError:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
# Numeric offsets
|
|
325
|
+
offset_minutes: int | None = None
|
|
326
|
+
if isinstance(tz_val, int):
|
|
327
|
+
if -14 <= tz_val <= 14:
|
|
328
|
+
offset_minutes = tz_val * 60
|
|
329
|
+
elif -24 * 60 <= tz_val <= 24 * 60:
|
|
330
|
+
offset_minutes = tz_val
|
|
331
|
+
elif -24 * 3600 <= tz_val <= 24 * 3600:
|
|
332
|
+
offset_minutes = int(tz_val / 60)
|
|
333
|
+
elif isinstance(tz_val, str):
|
|
334
|
+
s = tz_val.strip().upper().replace("UTC", "").replace("GMT", "")
|
|
335
|
+
m = _re.match(r"^([+-]?)(\d{1,2})(?::?(\d{2}))?$", s)
|
|
336
|
+
if m:
|
|
337
|
+
sign = -1 if m.group(1) == "-" else 1
|
|
338
|
+
hours = int(m.group(2))
|
|
339
|
+
minutes = int(m.group(3)) if m.group(3) else 0
|
|
340
|
+
offset_minutes = sign * (hours * 60 + minutes)
|
|
341
|
+
|
|
342
|
+
if offset_minutes is not None:
|
|
343
|
+
return datetime.timezone(datetime.timedelta(minutes=offset_minutes))
|
|
344
|
+
|
|
345
|
+
# Fallbacks
|
|
346
|
+
return datetime.datetime.now().astimezone().tzinfo or datetime.UTC
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
pyezvizapi/__init__.py,sha256=IDnIN_nfIISVwuy0cVBh4wspgAav6MuOJCQGajjyU3g,1881
|
|
2
2
|
pyezvizapi/__main__.py,sha256=SeV954H-AV-U1thNxRd7rWTGtSlfWyNzdrjF8gikYus,20777
|
|
3
3
|
pyezvizapi/api_endpoints.py,sha256=rk6VinLVCn-B6DxnhfV79liplNpgUsipNbTEa_MRVwU,2755
|
|
4
|
-
pyezvizapi/camera.py,sha256=
|
|
4
|
+
pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
|
|
5
5
|
pyezvizapi/cas.py,sha256=ISmb-eTPuacI10L87lULbQ-oDMBuygO7Tf-s9f-tvYE,5995
|
|
6
6
|
pyezvizapi/client.py,sha256=Bp5eQbn4-pjsZicfpWy6jD5bDjQeYFw-SN1p0uzKJRY,71782
|
|
7
7
|
pyezvizapi/constants.py,sha256=SqdJRQSRdVYQxMgJa__AgorzdWglgA4MM4H2fq3QLAE,12633
|
|
@@ -11,11 +11,11 @@ pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
|
|
|
11
11
|
pyezvizapi/mqtt.py,sha256=aOL-gexZgYvCCaNQ03M4vZan91d5p2Fl_qsFykn9NW4,22365
|
|
12
12
|
pyezvizapi/test_cam_rtsp.py,sha256=WGSM5EiOTl_r1mWHoMb7bXHm_BCn1P9X_669YQ38r6k,4903
|
|
13
13
|
pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
|
|
14
|
-
pyezvizapi/utils.py,sha256=
|
|
15
|
-
pyezvizapi-1.0.2.
|
|
16
|
-
pyezvizapi-1.0.2.
|
|
17
|
-
pyezvizapi-1.0.2.
|
|
18
|
-
pyezvizapi-1.0.2.
|
|
19
|
-
pyezvizapi-1.0.2.
|
|
20
|
-
pyezvizapi-1.0.2.
|
|
21
|
-
pyezvizapi-1.0.2.
|
|
14
|
+
pyezvizapi/utils.py,sha256=bOWLIytbELQfwBpA2LnhP1m8OthmOk9Vhh_uUXTFpoc,12124
|
|
15
|
+
pyezvizapi-1.0.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
pyezvizapi-1.0.2.3.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
17
|
+
pyezvizapi-1.0.2.3.dist-info/METADATA,sha256=xvvts4etMltvpLLaIr0lcKhm1quwBYCJbFqltigk55U,695
|
|
18
|
+
pyezvizapi-1.0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
pyezvizapi-1.0.2.3.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
|
|
20
|
+
pyezvizapi-1.0.2.3.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
|
|
21
|
+
pyezvizapi-1.0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|