pyezvizapi 1.0.1.6__py3-none-any.whl → 1.0.1.8__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.

Potentially problematic release.


This version of pyezvizapi might be problematic. Click here for more details.

@@ -1,149 +1,135 @@
1
1
  """Test camera RTSP authentication."""
2
+
3
+ from __future__ import annotations
4
+
2
5
  import base64
3
6
  import hashlib
4
7
  import socket
8
+ from typing import TypedDict
5
9
 
6
10
  from .exceptions import AuthTestResultFailed, InvalidHost
7
11
 
8
12
 
9
- def genmsg_describe(url, seq, user_agent, auth_seq):
10
- """Generate RTSP describe message."""
11
- msg_ret = "DESCRIBE " + url + " RTSP/1.0\r\n"
12
- msg_ret += "CSeq: " + str(seq) + "\r\n"
13
- msg_ret += "Authorization: " + auth_seq + "\r\n"
14
- msg_ret += "User-Agent: " + user_agent + "\r\n"
15
- msg_ret += "Accept: application/sdp\r\n"
16
- msg_ret += "\r\n"
13
+ def genmsg_describe(url: str, seq: int, user_agent: str, auth_seq: str) -> str:
14
+ """Generate RTSP DESCRIBE request message."""
15
+ msg_ret = f"DESCRIBE {url} RTSP/1.0\r\n"
16
+ msg_ret += f"CSeq: {seq}\r\n"
17
+ msg_ret += f"Authorization: {auth_seq}\r\n"
18
+ msg_ret += f"User-Agent: {user_agent}\r\n"
19
+ msg_ret += "Accept: application/sdp\r\n\r\n"
17
20
  return msg_ret
18
21
 
19
22
 
23
+ class RTSPDetails(TypedDict):
24
+ """Typed structure for RTSP test parameters."""
25
+ bufLen: int
26
+ defaultServerIp: str
27
+ defaultServerPort: int
28
+ defaultTestUri: str
29
+ defaultUserAgent: str
30
+ defaultUsername: str | None
31
+ defaultPassword: str | None
32
+
33
+
20
34
  class TestRTSPAuth:
21
- """Test RTSP credentials."""
35
+ """Test RTSP credentials against an RTSP server."""
36
+
37
+ _rtsp_details: RTSPDetails
22
38
 
23
39
  def __init__(
24
- self,
25
- ip_addr,
26
- username=None,
27
- password=None,
28
- test_uri="",
40
+ self, ip_addr: str, username: str | None = None, password: str | None = None, test_uri: str = ""
29
41
  ) -> None:
30
42
  """Initialize RTSP credential test."""
31
- self._rtsp_details = {
32
- "bufLen": 1024,
33
- "defaultServerIp": ip_addr,
34
- "defaultServerPort": 554,
35
- "defaultTestUri": test_uri,
36
- "defaultUserAgent": "RTSP Client",
37
- "defaultUsername": username,
38
- "defaultPassword": password,
39
- }
40
-
41
- def generate_auth_string(self, realm, method, uri, nonce):
42
- """Generate digest auth string."""
43
- map_return_info = {}
43
+ self._rtsp_details = RTSPDetails(
44
+ bufLen=1024,
45
+ defaultServerIp=ip_addr,
46
+ defaultServerPort=554,
47
+ defaultTestUri=test_uri,
48
+ defaultUserAgent="RTSP Client",
49
+ defaultUsername=username,
50
+ defaultPassword=password,
51
+ )
52
+
53
+ def generate_auth_string(self, realm: bytes, method: str, uri: str, nonce: bytes) -> str:
54
+ """Generate the HTTP Digest Authorization header value."""
44
55
  m_1 = hashlib.md5(
45
- f"{self._rtsp_details['defaultUsername']}:"
46
- f"{realm.decode()}:"
47
- f"{self._rtsp_details['defaultPassword']}".encode()
56
+ f"{self._rtsp_details['defaultUsername']}:{realm.decode()}:{self._rtsp_details['defaultPassword']}".encode()
48
57
  ).hexdigest()
49
58
  m_2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
50
- response = hashlib.md5(f"{m_1}:{nonce}:{m_2}".encode()).hexdigest()
59
+ response = hashlib.md5(f"{m_1}:{nonce.decode()}:{m_2}".encode()).hexdigest()
51
60
 
52
- map_return_info = (
53
- f"Digest "
61
+ return (
62
+ "Digest "
54
63
  f"username=\"{self._rtsp_details['defaultUsername']}\", "
55
- f'realm="{realm.decode()}", '
56
- f'algorithm="MD5", '
57
- f'nonce="{nonce.decode()}", '
58
- f'uri="{uri}", '
59
- f'response="{response}"'
64
+ f"realm=\"{realm.decode()}\", "
65
+ 'algorithm="MD5", '
66
+ f"nonce=\"{nonce.decode()}\", "
67
+ f"uri=\"{uri}\", "
68
+ f"response=\"{response}\""
60
69
  )
61
- return map_return_info
62
70
 
63
- def main(self):
64
- """Start main method."""
65
- session = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
71
+ def main(self) -> None:
72
+ """Open RTSP socket, try Basic and then Digest auth for DESCRIBE."""
73
+ session: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
66
74
 
67
75
  try:
68
76
  session.connect(
69
- (
70
- self._rtsp_details["defaultServerIp"],
71
- self._rtsp_details["defaultServerPort"],
72
- )
77
+ (self._rtsp_details["defaultServerIp"], self._rtsp_details["defaultServerPort"])
73
78
  )
74
-
75
79
  except TimeoutError as err:
76
80
  raise AuthTestResultFailed("Invalid ip or camera hibernating") from err
77
-
78
81
  except (socket.gaierror, ConnectionRefusedError) as err:
79
82
  raise InvalidHost("Invalid IP or Hostname") from err
80
83
 
81
- seq = 1
84
+ seq: int = 1
82
85
 
83
- url = (
84
- "rtsp://"
85
- + self._rtsp_details["defaultServerIp"]
86
- + self._rtsp_details["defaultTestUri"]
87
- )
88
-
89
- auth_seq = base64.b64encode(
90
- f"{self._rtsp_details['defaultUsername']}:"
91
- f"{self._rtsp_details['defaultPassword']}".encode("ascii")
92
- )
93
- auth_seq = "Basic " + auth_seq.decode()
86
+ url: str = "rtsp://" + self._rtsp_details["defaultServerIp"] + self._rtsp_details["defaultTestUri"]
94
87
 
95
- print(
96
- genmsg_describe(url, seq, self._rtsp_details["defaultUserAgent"], auth_seq)
88
+ # Basic Authorization header
89
+ auth_b64: bytes = base64.b64encode(
90
+ f"{self._rtsp_details['defaultUsername']}:{self._rtsp_details['defaultPassword']}".encode("ascii")
97
91
  )
98
- session.send(
99
- genmsg_describe(
100
- url, seq, self._rtsp_details["defaultUserAgent"], auth_seq
101
- ).encode()
102
- )
103
- msg1 = session.recv(self._rtsp_details["bufLen"])
104
- seq = seq + 1
105
-
106
- if msg1.decode().find("200 OK") > 1:
107
- print(f"Basic auth result: {msg1.decode()}")
108
- return print("Basic Auth test passed. Credentials Valid!")
109
-
110
- if msg1.decode().find("Unauthorized") > 1:
111
- # Basic failed, doing new DESCRIBE with digest authentication.
112
- start = msg1.decode().find("realm")
113
- begin = msg1.decode().find('"', start)
114
- end = msg1.decode().find('"', begin + 1)
115
- realm = msg1[begin + 1 : end]
116
-
117
- start = msg1.decode().find("nonce")
118
- begin = msg1.decode().find('"', start)
119
- end = msg1.decode().find('"', begin + 1)
120
- nonce = msg1[begin + 1 : end]
121
-
122
- auth_seq = self.generate_auth_string(
123
- realm,
124
- "DESCRIBE",
125
- self._rtsp_details["defaultTestUri"],
126
- nonce,
127
- )
128
-
129
- print(
130
- genmsg_describe(
131
- url, seq, self._rtsp_details["defaultUserAgent"], auth_seq
132
- )
133
- )
134
-
135
- session.send(
136
- genmsg_describe(
137
- url, seq, self._rtsp_details["defaultUserAgent"], auth_seq
138
- ).encode()
139
- )
92
+ auth_seq: str = "Basic " + auth_b64.decode()
93
+
94
+ describe = genmsg_describe(url, seq, self._rtsp_details["defaultUserAgent"], auth_seq)
95
+ print(describe)
96
+ session.send(describe.encode())
97
+ msg1: bytes = session.recv(self._rtsp_details["bufLen"])
98
+ seq += 1
99
+
100
+ decoded = msg1.decode()
101
+ if "200 OK" in decoded:
102
+ print(f"Basic auth result: {decoded}")
103
+ print("Basic Auth test passed. Credentials Valid!")
104
+ return
105
+
106
+ if "Unauthorized" in decoded:
107
+ # Basic failed, do new DESCRIBE with digest authentication.
108
+ start = decoded.find("realm")
109
+ begin = decoded.find('"', start)
110
+ end = decoded.find('"', begin + 1)
111
+ realm: bytes = msg1[begin + 1 : end]
112
+
113
+ start = decoded.find("nonce")
114
+ begin = decoded.find('"', start)
115
+ end = decoded.find('"', begin + 1)
116
+ nonce: bytes = msg1[begin + 1 : end]
117
+
118
+ auth_seq = self.generate_auth_string(realm, "DESCRIBE", self._rtsp_details["defaultTestUri"], nonce)
119
+
120
+ describe = genmsg_describe(url, seq, self._rtsp_details["defaultUserAgent"], auth_seq)
121
+ print(describe)
122
+ session.send(describe.encode())
140
123
  msg1 = session.recv(self._rtsp_details["bufLen"])
141
- print(f"Digest auth result: {msg1.decode()}")
124
+ decoded = msg1.decode()
125
+ print(f"Digest auth result: {decoded}")
142
126
 
143
- if msg1.decode().find("200 OK") > 1:
144
- return print("Digest Auth test Passed. Credentials Valid!")
127
+ if "200 OK" in decoded:
128
+ print("Digest Auth test Passed. Credentials Valid!")
129
+ return
145
130
 
146
- if msg1.decode().find("401 Unauthorized") > 1:
131
+ if "401 Unauthorized" in decoded:
147
132
  raise AuthTestResultFailed("Credentials not valid!!")
148
133
 
149
- return print("Basic Auth test passed. Credentials Valid!")
134
+ print("Basic Auth test passed. Credentials Valid!")
135
+ # ruff: noqa: T201
@@ -0,0 +1,135 @@
1
+ """MQTT test module.
2
+
3
+ Run a simple MQTT listener using either a saved token file
4
+ (`--token-file ezviz_token.json`) or by prompting for username/password
5
+ with MFA similar to the main CLI.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ from getpass import getpass
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ import sys
16
+ import time
17
+ from typing import Any, cast
18
+
19
+ from .client import EzvizClient
20
+ from .exceptions import EzvizAuthVerificationCode, PyEzvizError
21
+
22
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
23
+
24
+ LOG_FILE = Path("mqtt_messages.jsonl") # JSON Lines format
25
+
26
+
27
+ def message_handler(msg: dict[str, Any]) -> None:
28
+ """Handle new MQTT messages by printing and saving them to a file."""
29
+ print("📩 New MQTT message:", msg)
30
+ with LOG_FILE.open("a", encoding="utf-8") as f:
31
+ f.write(json.dumps(msg, ensure_ascii=False) + "\n")
32
+
33
+
34
+ def _load_token_file(path: str | None) -> dict[str, Any] | None:
35
+ if not path:
36
+ return None
37
+ p = Path(path)
38
+ if not p.exists():
39
+ return None
40
+ try:
41
+ return cast(dict[str, Any], json.loads(p.read_text(encoding="utf-8")))
42
+ except (OSError, json.JSONDecodeError):
43
+ logging.getLogger(__name__).warning("Failed to read token file: %s", p)
44
+ return None
45
+
46
+
47
+ def _save_token_file(path: str | None, token: dict[str, Any]) -> None:
48
+ if not path:
49
+ return
50
+ p = Path(path)
51
+ try:
52
+ p.write_text(json.dumps(token, indent=2), encoding="utf-8")
53
+ logging.getLogger(__name__).info("Saved token to %s", p)
54
+ except OSError:
55
+ logging.getLogger(__name__).warning("Failed to save token file: %s", p)
56
+
57
+
58
+ def main(argv: list[str] | None = None) -> int:
59
+ """Entry point for testing MQTT messages."""
60
+ parser = argparse.ArgumentParser(prog="test_mqtt")
61
+ parser.add_argument("-u", "--username", required=False, help="Ezviz username")
62
+ parser.add_argument("-p", "--password", required=False, help="Ezviz password")
63
+ parser.add_argument(
64
+ "-r",
65
+ "--region",
66
+ required=False,
67
+ default="apiieu.ezvizlife.com",
68
+ help="Ezviz API region",
69
+ )
70
+ parser.add_argument(
71
+ "--token-file",
72
+ type=str,
73
+ default="ezviz_token.json",
74
+ help="Path to JSON token file (default: ezviz_token.json)",
75
+ )
76
+ parser.add_argument(
77
+ "--save-token",
78
+ action="store_true",
79
+ help="Save token to --token-file after successful login",
80
+ )
81
+ args = parser.parse_args(argv)
82
+
83
+ token = _load_token_file(args.token_file)
84
+
85
+ username = args.username
86
+ password = args.password
87
+
88
+ # If no token and missing username/password, prompt interactively
89
+ if not token and (not username or not password):
90
+ print("No token found. Please enter Ezviz credentials.")
91
+ if not username:
92
+ username = input("Username: ")
93
+ if not password:
94
+ password = getpass("Password: ")
95
+
96
+ client = EzvizClient(username, password, args.region, token=token)
97
+
98
+ # Login if we have credentials (to refresh session and populate service URLs)
99
+ if username and password:
100
+ try:
101
+ client.login()
102
+ except EzvizAuthVerificationCode:
103
+ mfa_code = input("MFA code required, please input MFA code.\n")
104
+ try:
105
+ code_int = int(mfa_code.strip())
106
+ except ValueError:
107
+ code_int = None
108
+ client.login(sms_code=code_int)
109
+ except PyEzvizError as exp:
110
+ print(f"Login failed: {exp}")
111
+ return 1
112
+
113
+ # Start MQTT client
114
+ mqtt_client = client.get_mqtt_client(on_message_callback=message_handler)
115
+ mqtt_client.connect()
116
+
117
+ try:
118
+ print("Listening for MQTT messages... (Ctrl+C to quit)")
119
+ while True:
120
+ time.sleep(1)
121
+ except KeyboardInterrupt:
122
+ print("\nStopping...")
123
+ finally:
124
+ mqtt_client.stop()
125
+ print("Stopped.")
126
+
127
+ if args.save_token and args.token_file:
128
+ _save_token_file(args.token_file, cast(dict[str, Any], client._token)) # noqa: SLF001
129
+
130
+ return 0
131
+
132
+
133
+ if __name__ == "__main__":
134
+ sys.exit(main())
135
+ # ruff: noqa: T201
pyezvizapi/utils.py CHANGED
@@ -6,6 +6,7 @@ from hashlib import md5
6
6
  import json
7
7
  import logging
8
8
  from typing import Any
9
+ import uuid
9
10
 
10
11
  from Crypto.Cipher import AES
11
12
 
@@ -61,10 +62,12 @@ def fetch_nested_value(data: Any, keys: list, default_value: Any = None) -> Any:
61
62
  try:
62
63
  for key in keys:
63
64
  data = data[key]
64
- return data
65
+
65
66
  except (KeyError, TypeError):
66
67
  return default_value
67
68
 
69
+ return data
70
+
68
71
 
69
72
  def decrypt_image(input_data: bytes, password: str) -> bytes:
70
73
  """Decrypts image data with provided password.
@@ -80,7 +83,6 @@ def decrypt_image(input_data: bytes, password: str) -> bytes:
80
83
  bytes: Decrypted image data
81
84
 
82
85
  """
83
-
84
86
  if len(input_data) < 48:
85
87
  raise PyEzvizError("Invalid image data")
86
88
 
@@ -162,3 +164,27 @@ def deep_merge(dict1: Any, dict2: Any) -> Any:
162
164
  merged[key] = dict2[key]
163
165
 
164
166
  return merged
167
+
168
+
169
+ def generate_unique_code() -> str:
170
+ """Generate a deterministic, platform-agnostic unique code for the current host.
171
+
172
+ This function retrieves the host's MAC address using Python's standard
173
+ `uuid.getnode()` (works on Windows, Linux, macOS), converts it to a
174
+ canonical string representation, and then hashes it using MD5 to produce
175
+ a fixed-length hexadecimal string.
176
+
177
+ Returns:
178
+ str: A 32-character hexadecimal string uniquely representing
179
+ the host's MAC address. For example:
180
+ 'a94e6756hghjgfghg49e0f310d9e44a'.
181
+
182
+ Notes:
183
+ - The output is deterministic: the same machine returns the same code.
184
+ - If the MAC address changes (e.g., different network adapter),
185
+ the output will change.
186
+ - MD5 is used here only for ID generation, not for security.
187
+ """
188
+ mac_int = uuid.getnode()
189
+ mac_str = ":".join(f"{(mac_int >> i) & 0xFF:02x}" for i in range(40, -1, -8))
190
+ return md5(mac_str.encode("utf-8")).hexdigest()
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.1.6
3
+ Version: 1.0.1.8
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
7
7
  Author-email: RenierM26@users.github.com
8
8
  License: Apache Software License 2.0
9
- Requires-Python: >=3.6
9
+ Requires-Python: >=3.11
10
10
  License-File: LICENSE
11
11
  License-File: LICENSE.md
12
12
  Requires-Dist: requests
@@ -0,0 +1,21 @@
1
+ pyezvizapi/__init__.py,sha256=IDnIN_nfIISVwuy0cVBh4wspgAav6MuOJCQGajjyU3g,1881
2
+ pyezvizapi/__main__.py,sha256=SeV954H-AV-U1thNxRd7rWTGtSlfWyNzdrjF8gikYus,20777
3
+ pyezvizapi/api_endpoints.py,sha256=rk6VinLVCn-B6DxnhfV79liplNpgUsipNbTEa_MRVwU,2755
4
+ pyezvizapi/camera.py,sha256=HcJPUXQnYEAVqh6alphuRiMl6al28C9JORP6LO3m_es,24927
5
+ pyezvizapi/cas.py,sha256=ISmb-eTPuacI10L87lULbQ-oDMBuygO7Tf-s9f-tvYE,5995
6
+ pyezvizapi/client.py,sha256=dcHAsdtfSTkVCrkaEZKpFggDXX4t5debpuxc76sOy-k,71267
7
+ pyezvizapi/constants.py,sha256=R0zGg8Rv59An36dSXQLl3WU7VbpTbpo2comk9VJc68k,12535
8
+ pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
9
+ pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
10
+ pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
11
+ pyezvizapi/mqtt.py,sha256=TxIjg2Xk6Qh4LWQQXKS6vpgLj2Rl2KOkDRwaz46PGUU,22367
12
+ pyezvizapi/test_cam_rtsp.py,sha256=WGSM5EiOTl_r1mWHoMb7bXHm_BCn1P9X_669YQ38r6k,4903
13
+ pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
14
+ pyezvizapi/utils.py,sha256=o342o3LI9eP8qla1vXM2rqlVbdTiLK0dAqrkyUSXpg8,5939
15
+ pyezvizapi-1.0.1.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ pyezvizapi-1.0.1.8.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ pyezvizapi-1.0.1.8.dist-info/METADATA,sha256=7t6tdY_1RMQ524_vV-Z5zcMZr5hPeK7h8PIYXsrsUoA,695
18
+ pyezvizapi-1.0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ pyezvizapi-1.0.1.8.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
20
+ pyezvizapi-1.0.1.8.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
21
+ pyezvizapi-1.0.1.8.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- pyezvizapi/__init__.py,sha256=yWITfZOi5zvKblfaeB2ft-wXYmZ2NviYVq_tsK8YkSk,1334
2
- pyezvizapi/__main__.py,sha256=OsawbYa-eoLUTOMr4xWeryq4sDzs09WL9GELKQTbda8,16396
3
- pyezvizapi/api_endpoints.py,sha256=rk6VinLVCn-B6DxnhfV79liplNpgUsipNbTEa_MRVwU,2755
4
- pyezvizapi/camera.py,sha256=BpmLySbnWWEKimSwgjx_cB60Q-6dbgdY-NA--NzUvps,11486
5
- pyezvizapi/cas.py,sha256=d31ZflYSD9P40MnsNRNZbT0HVLvlnHokKpLbdAjWQ74,5631
6
- pyezvizapi/client.py,sha256=lak8r1Src1R57dQYwzVDq7NRt1iIOdkESyuR2FWqmRQ,85563
7
- pyezvizapi/constants.py,sha256=jjLO-Ne9jq9m9_giYB4rnPXDZKkzKhesVXBqP1B3-00,12304
8
- pyezvizapi/exceptions.py,sha256=28lLyM0ILTRHgWqr9D-DqqKFXx7POuF0WAZctdC8Kbc,735
9
- pyezvizapi/light_bulb.py,sha256=ADLrPZ6NL4vANzmohU63QuD9qVGkKHkX9C0o7Evbv-A,5730
10
- pyezvizapi/mqtt.py,sha256=Y4X99Z0Avm32SE8vog7CNsv6tGUPmPYUZUgPDGS0QJA,7866
11
- pyezvizapi/test_cam_rtsp.py,sha256=w7GPcYIeK78TxL8zFDihdGSDQNWcYrurwZOr6uFzzgo,4902
12
- pyezvizapi/utils.py,sha256=5J10o3h-y8prWDvl3LSAF-9wS1jBgBMg5cpAEebcuSM,4936
13
- pyezvizapi-1.0.1.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
- pyezvizapi-1.0.1.6.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- pyezvizapi-1.0.1.6.dist-info/METADATA,sha256=GAvczQGW5LSpRKQg5jCaExw9KM4GcvqKa0L9WYWvMxc,694
16
- pyezvizapi-1.0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- pyezvizapi-1.0.1.6.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
18
- pyezvizapi-1.0.1.6.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
19
- pyezvizapi-1.0.1.6.dist-info/RECORD,,