Revize 76bdccc4
Přidáno uživatelem Jakub Šilhavý před asi 3 roky(ů)
client/src/api_client.py | ||
---|---|---|
1 |
import json |
|
2 |
import requests |
|
3 |
import logging |
|
4 |
from time import sleep |
|
5 |
from diskcache import Deque |
|
6 |
from requests import HTTPError, ConnectionError |
|
7 |
from requests.exceptions import InvalidSchema |
|
8 |
|
|
9 |
_uri = None |
|
10 |
_cache = None |
|
11 |
_config = None |
|
12 |
|
|
13 |
|
|
14 |
def api_client_set_config(config): |
|
15 |
global _config, _cache, _uri |
|
16 |
_config = config |
|
17 |
_cache = _init_cache() |
|
18 |
_uri = config.server_url + ":" + config.server_port + config.server_endpoint |
|
19 |
|
|
20 |
|
|
21 |
def _init_cache(): |
|
22 |
return Deque(directory=_config.cache_dir) |
|
23 |
|
|
24 |
|
|
25 |
def send_data(payload: dict): |
|
26 |
if _uri is None: |
|
27 |
logging.warning(f"sending payload = {payload} failed because uri is set to None") |
|
28 |
_cache_failed_payload(payload) |
|
29 |
return |
|
30 |
try: |
|
31 |
logging.info(f"sending payload = {payload} to {_uri}") |
|
32 |
response = requests.post(url=_uri, data=json.dumps(payload)) |
|
33 |
logging.info(f"response text: {response.text}") |
|
34 |
except (ConnectionError, InvalidSchema): |
|
35 |
logging.warning(f"sending payload = {payload} to {_uri} failed") |
|
36 |
_cache_failed_payload(payload) |
|
37 |
except HTTPError as error: |
|
38 |
logging.error(f"HTTP Error ({_uri}) payload = {payload}, {error}") |
|
39 |
_cache_failed_payload(payload) |
|
40 |
|
|
41 |
|
|
42 |
def _cache_failed_payload(payload: dict): |
|
43 |
if len(_cache) >= _config.cache_max_entries: |
|
44 |
oldest_payload = _cache.pop() |
|
45 |
logging.warning(f"cache is full - discarding payload = {oldest_payload}") |
|
46 |
|
|
47 |
logging.info(f"adding payload = {payload} into cache") |
|
48 |
_cache.append(payload) |
|
49 |
|
|
50 |
|
|
51 |
def _resend_cached_payloads(): |
|
52 |
retries = min(_config.max_retries, len(_cache)) |
|
53 |
logging.info(f"emptying the cache ({retries} records)") |
|
54 |
for _ in range(0, retries): |
|
55 |
payload = _cache.pop() |
|
56 |
send_data(payload) |
|
57 |
|
|
58 |
|
|
59 |
def api_client_run(): |
|
60 |
while True: |
|
61 |
_resend_cached_payloads() |
|
62 |
sleep(_config.cache_retry_period_seconds) |
client/src/main.py | ||
---|---|---|
9 | 9 |
from config_manager import Config |
10 | 10 |
from usb_detector.detector import register_listener, usb_detector_run, usb_detector_set_config |
11 | 11 |
from usb_detector.event_listener import usb_connected_callback, usb_disconnected_callback |
12 |
from client.src.api_client import api_client_run, api_client_set_config
|
|
12 |
from usb_detector.api_client import api_client_run, api_client_set_config
|
|
13 | 13 |
|
14 | 14 |
|
15 | 15 |
def init_logging(app_config: Config): |
client/src/usb_detector/api_client.py | ||
---|---|---|
1 |
import json |
|
2 |
import requests |
|
3 |
import logging |
|
4 |
from time import sleep |
|
5 |
from diskcache import Deque |
|
6 |
from requests import HTTPError, ConnectionError |
|
7 |
from requests.exceptions import InvalidSchema |
|
8 |
|
|
9 |
_uri = None |
|
10 |
_cache = None |
|
11 |
_config = None |
|
12 |
|
|
13 |
|
|
14 |
def api_client_set_config(config): |
|
15 |
global _config, _cache, _uri |
|
16 |
_config = config |
|
17 |
_cache = _init_cache() |
|
18 |
_uri = config.server_url + ":" + config.server_port + config.server_endpoint |
|
19 |
|
|
20 |
|
|
21 |
def _init_cache(): |
|
22 |
return Deque(directory=_config.cache_dir) |
|
23 |
|
|
24 |
|
|
25 |
def send_data(payload: dict): |
|
26 |
if _uri is None: |
|
27 |
logging.warning(f"sending payload = {payload} failed because uri is set to None") |
|
28 |
_cache_failed_payload(payload) |
|
29 |
return |
|
30 |
try: |
|
31 |
logging.info(f"sending payload = {payload} to {_uri}") |
|
32 |
response = requests.post(url=_uri, data=json.dumps(payload)) |
|
33 |
logging.info(f"response text: {response.text}") |
|
34 |
except (ConnectionError, InvalidSchema): |
|
35 |
logging.warning(f"sending payload = {payload} to {_uri} failed") |
|
36 |
_cache_failed_payload(payload) |
|
37 |
except HTTPError as error: |
|
38 |
logging.error(f"HTTP Error ({_uri}) payload = {payload}, {error}") |
|
39 |
_cache_failed_payload(payload) |
|
40 |
|
|
41 |
|
|
42 |
def _cache_failed_payload(payload: dict): |
|
43 |
if len(_cache) >= _config.cache_max_entries: |
|
44 |
oldest_payload = _cache.pop() |
|
45 |
logging.warning(f"cache is full - discarding payload = {oldest_payload}") |
|
46 |
|
|
47 |
logging.info(f"adding payload = {payload} into cache") |
|
48 |
_cache.append(payload) |
|
49 |
|
|
50 |
|
|
51 |
def _resend_cached_payloads(): |
|
52 |
retries = min(_config.max_retries, len(_cache)) |
|
53 |
logging.info(f"emptying the cache ({retries} records)") |
|
54 |
for _ in range(0, retries): |
|
55 |
payload = _cache.pop() |
|
56 |
send_data(payload) |
|
57 |
|
|
58 |
|
|
59 |
def api_client_run(): |
|
60 |
while True: |
|
61 |
_resend_cached_payloads() |
|
62 |
sleep(_config.cache_retry_period_seconds) |
client/src/usb_detector/event_listener.py | ||
---|---|---|
3 | 3 |
import getpass |
4 | 4 |
from datetime import datetime |
5 | 5 |
|
6 |
from ..api_client import send_data
|
|
6 |
from .api_client import send_data |
|
7 | 7 |
|
8 | 8 |
|
9 | 9 |
def _get_metadata() -> dict: |
client/tests/api_client/test_api_client_set_config.py | ||
---|---|---|
1 |
import client.src.api_client
|
|
1 |
from client.src.usb_detector import api_client
|
|
2 | 2 |
|
3 | 3 |
from unittest import mock |
4 | 4 |
|
5 | 5 |
|
6 |
@mock.patch('client.src.api_client._init_cache') |
|
6 |
@mock.patch('client.src.usb_detector.api_client._init_cache')
|
|
7 | 7 |
def test_api_client_set_config_1(_init_cache_mock): |
8 | 8 |
class Config: |
9 | 9 |
def __init__(self): |
... | ... | |
12 | 12 |
self.server_endpoint = "/api/v1/usb-logs" |
13 | 13 |
|
14 | 14 |
config = Config() |
15 |
client.src.api_client.api_client_set_config(config)
|
|
15 |
api_client.api_client_set_config(config) |
|
16 | 16 |
|
17 |
assert client.src.api_client._config is config |
|
18 |
assert client.src.api_client._uri == "127.0.0.1:54444/api/v1/usb-logs" |
|
17 |
assert api_client._config is config |
|
18 |
assert api_client._uri == "127.0.0.1:54444/api/v1/usb-logs" |
client/tests/api_client/test_cache_failed_payload.py | ||
---|---|---|
1 |
import client.src.api_client as api_client
|
|
1 |
from client.src.usb_detector import api_client
|
|
2 | 2 |
|
3 | 3 |
|
4 | 4 |
class CacheMock: |
client/tests/api_client/test_resend_cached_payloads.py | ||
---|---|---|
1 |
from client.src.usb_detector import api_client |
|
2 |
|
|
3 |
from unittest import mock |
|
4 |
|
|
5 |
|
|
6 |
class CacheMock: |
|
7 |
|
|
8 |
def __init__(self): |
|
9 |
self._data = [] |
|
10 |
|
|
11 |
def pop(self): |
|
12 |
return self._data.pop() |
|
13 |
|
|
14 |
def append(self, payload): |
|
15 |
self._data.append(payload) |
|
16 |
|
|
17 |
def __len__(self): |
|
18 |
return len(self._data) |
|
19 |
|
|
20 |
|
|
21 |
class ConfigMock: |
|
22 |
|
|
23 |
def __init__(self): |
|
24 |
self.cache_max_entries = 5 |
|
25 |
self.max_retries = 3 |
|
26 |
|
|
27 |
|
|
28 |
payload_mock = { |
|
29 |
"device": { |
|
30 |
"vendor_id": 1, |
|
31 |
"product_id": 2 |
|
32 |
} |
|
33 |
} |
|
34 |
|
|
35 |
|
|
36 |
@mock.patch('client.src.usb_detector.api_client.send_data') |
|
37 |
def test_resend_cached_payloads_1(send_data_mock): |
|
38 |
cache = CacheMock() |
|
39 |
config = ConfigMock() |
|
40 |
|
|
41 |
api_client._cache = cache |
|
42 |
api_client._config = config |
|
43 |
|
|
44 |
for _ in range(0, config.cache_max_entries + 1): |
|
45 |
api_client._cache_failed_payload(payload_mock) |
|
46 |
|
|
47 |
api_client._resend_cached_payloads() |
|
48 |
send_data_mock.assert_called() |
|
49 |
assert len(api_client._cache) == 2 |
|
50 |
|
|
51 |
api_client._resend_cached_payloads() |
|
52 |
assert len(api_client._cache) == 0 |
|
53 |
|
|
54 |
api_client._resend_cached_payloads() |
|
55 |
assert len(api_client._cache) == 0 |
|
56 |
|
|
57 |
|
|
58 |
@mock.patch('client.src.usb_detector.api_client.send_data') |
|
59 |
def test_resend_cached_payloads_2(send_data_mock): |
|
60 |
cache = CacheMock() |
|
61 |
config = ConfigMock() |
|
62 |
|
|
63 |
api_client._cache = cache |
|
64 |
api_client._config = config |
|
65 |
|
|
66 |
api_client._resend_cached_payloads() |
|
67 |
send_data_mock.assert_not_called() |
|
68 |
assert len(api_client._cache) == 0 |
|
69 |
|
|
70 |
|
|
71 |
@mock.patch('client.src.usb_detector.api_client.send_data') |
|
72 |
def test_resend_cached_payloads_3(send_data_mock): |
|
73 |
cache = CacheMock() |
|
74 |
config = ConfigMock() |
|
75 |
|
|
76 |
api_client._cache = cache |
|
77 |
api_client._config = config |
|
78 |
|
|
79 |
for _ in range(0, 2): |
|
80 |
api_client._cache_failed_payload(payload_mock) |
|
81 |
|
|
82 |
assert len(api_client._cache) == 2 |
|
83 |
api_client._resend_cached_payloads() |
|
84 |
send_data_mock.assert_called() |
|
85 |
assert len(api_client._cache) == 0 |
|
86 |
|
client/tests/api_client/test_send_data.py | ||
---|---|---|
1 |
from client.src.usb_detector import api_client |
|
2 |
|
|
3 |
import requests |
|
4 |
from unittest import mock |
|
5 |
|
|
6 |
|
|
7 |
@mock.patch('client.src.usb_detector.api_client._cache_failed_payload') |
|
8 |
def test_send_data_1(_cache_failed_payload_mock): |
|
9 |
payload_mock = { |
|
10 |
"device": { |
|
11 |
"vendor_id": 1, |
|
12 |
"product_id": 2 |
|
13 |
} |
|
14 |
} |
|
15 |
|
|
16 |
api_client._uri = None |
|
17 |
api_client.send_data(payload_mock) |
|
18 |
|
|
19 |
args = _cache_failed_payload_mock.call_args.args |
|
20 |
_cache_failed_payload_mock.assert_called() |
|
21 |
assert args[0] == payload_mock |
|
22 |
|
|
23 |
|
|
24 |
@mock.patch('client.src.usb_detector.api_client._cache_failed_payload') |
|
25 |
def test_send_data_2(_cache_failed_payload_mock): |
|
26 |
payload_mock = { |
|
27 |
"device": { |
|
28 |
"vendor_id": 1, |
|
29 |
"product_id": 2 |
|
30 |
} |
|
31 |
} |
|
32 |
|
|
33 |
api_client._uri = "127.0.0.1:54444/api/v1/usb-logs" |
|
34 |
api_client.send_data(payload_mock) |
|
35 |
|
|
36 |
args = _cache_failed_payload_mock.call_args.args |
|
37 |
_cache_failed_payload_mock.assert_called() |
|
38 |
assert args[0] == payload_mock |
|
39 |
|
|
40 |
|
|
41 |
@mock.patch('client.src.usb_detector.api_client.requests.post') |
|
42 |
@mock.patch('client.src.usb_detector.api_client._cache_failed_payload') |
|
43 |
def test_send_data_3(_cache_failed_payload_mock, post_mock): |
|
44 |
payload_mock = { |
|
45 |
"device": { |
|
46 |
"vendor_id": 1, |
|
47 |
"product_id": 2 |
|
48 |
} |
|
49 |
} |
|
50 |
|
|
51 |
api_client._uri = "127.0.0.1:54444/api/v1/usb-logs" |
|
52 |
api_client.send_data(payload_mock) |
|
53 |
|
|
54 |
_cache_failed_payload_mock.assert_not_called() |
|
55 |
|
|
56 |
|
|
57 |
@mock.patch('client.src.usb_detector.api_client.requests.post') |
|
58 |
@mock.patch('client.src.usb_detector.api_client._cache_failed_payload') |
|
59 |
def test_send_data_4(_cache_failed_payload_mock, post_mock): |
|
60 |
payload_mock = { |
|
61 |
"device": { |
|
62 |
"vendor_id": 1, |
|
63 |
"product_id": 2 |
|
64 |
} |
|
65 |
} |
|
66 |
post_mock.side_effect = requests.exceptions.HTTPError() |
|
67 |
|
|
68 |
api_client._uri = "127.0.0.1:54444/api/v1/usb-logs" |
|
69 |
api_client.send_data(payload_mock) |
|
70 |
|
|
71 |
args = _cache_failed_payload_mock.call_args.args |
|
72 |
_cache_failed_payload_mock.assert_called() |
|
73 |
assert args[0] == payload_mock |
Také k dispozici: Unified diff
re #9421 Added unit tests for resend_cached_payload() and send_data(); moved api_client.py into usb_detector package