aioads 0.1.0.dev6__tar.gz → 0.1.0.dev7__tar.gz
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.
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/PKG-INFO +1 -1
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/transport.py +52 -2
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/pyproject.toml +1 -1
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_transport.py +69 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/.github/dependabot.yml +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/.github/workflows/ci.yml +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/.github/workflows/publish.yml +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/.gitignore +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/.vscode/settings.json +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/LICENSE +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/README.md +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ads_client.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ads_error_codes.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ads_notifications.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ads_symbol_cache.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ads_symbol_parser.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ams_address.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ams_header.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ams_service_port.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/ams_tcp_header.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_add_notification.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_command.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_delete_notification.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_read.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_read_device_info.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_read_state.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_read_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/ads_write_state.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/commands/errors.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/errors.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_enable_route.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_function.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_sum_read.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_sum_read_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_datatype_by_name.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_datatype_upload.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_info_by_name_ex.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_table_version.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_upload.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/functions/ads_symbol_upload_info.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/py.typed +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/stream.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/aioads/utils/local_ip.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/docs/transmission_mode.md +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/docs/unittest_style_guide.html +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_cmd_reuse_mqtt.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_cycles.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_cycles_mqtt.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_multiple.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_multiple_mqtt.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/examples/read_single.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/pdm.lock +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/builders.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/README.md +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/base.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/config.example.toml +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/config.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/test_connection.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/test_performance.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/integration/test_read_symbols.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_add_notification.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_command.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_delete_notification.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_read.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_read_device_info.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_read_state.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_read_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_ads_write_state.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/commands/test_errors.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_enable_route.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_function.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_sum_read.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_sum_read_write.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_datatype_by_name.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_datatype_upload.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_info_by_name_ex.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_table_version.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_upload.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_upload_info.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ads_client.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ads_error_codes.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ads_notifications.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ads_symbol_cache.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ads_symbol_parser.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ams_address.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ams_header.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_ams_tcp_header.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_errors.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/test_stream.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/utils/__init__.py +0 -0
- {aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/utils/test_local_ip.py +0 -0
|
@@ -7,6 +7,7 @@ import enum
|
|
|
7
7
|
import logging
|
|
8
8
|
import random
|
|
9
9
|
import re
|
|
10
|
+
import socket
|
|
10
11
|
from abc import ABC
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from dataclasses import field
|
|
@@ -211,6 +212,22 @@ class AdsTcpTransport(BaseTransport, ITransport):
|
|
|
211
212
|
RECONNECT_INITIAL_BACKOFF = 1.0
|
|
212
213
|
RECONNECT_MAX_BACKOFF = 30.0
|
|
213
214
|
|
|
215
|
+
# TCP keepalive / liveness tuning.
|
|
216
|
+
# Without these, a half-open connection (peer power-cut, cable pull, NAT
|
|
217
|
+
# idle-eviction) is never detected: no FIN/RST arrives, so the read loop
|
|
218
|
+
# blocks forever and the supervisor never reconnects. These force the kernel
|
|
219
|
+
# to probe an idle peer and to bound how long unacked writes wait, so a dead
|
|
220
|
+
# link surfaces as a socket error within ~KEEPALIVE_IDLE + KEEPALIVE_INTERVAL
|
|
221
|
+
# * KEEPALIVE_COUNT seconds.
|
|
222
|
+
KEEPALIVE_IDLE = 10
|
|
223
|
+
"""Seconds of idle before the kernel starts sending keepalive probes (Linux: TCP_KEEPIDLE)."""
|
|
224
|
+
KEEPALIVE_INTERVAL = 3
|
|
225
|
+
"""Seconds between keepalive probes (Linux: TCP_KEEPINTVL)."""
|
|
226
|
+
KEEPALIVE_COUNT = 3
|
|
227
|
+
"""Unanswered probes before the connection is considered dead (Linux: TCP_KEEPCNT)."""
|
|
228
|
+
USER_TIMEOUT_MS = 15_000
|
|
229
|
+
"""Milliseconds an unacked write may wait before the socket errors (Linux: TCP_USER_TIMEOUT)."""
|
|
230
|
+
|
|
214
231
|
def __init__(
|
|
215
232
|
self,
|
|
216
233
|
src_address: AmsAddress,
|
|
@@ -279,10 +296,43 @@ class AdsTcpTransport(BaseTransport, ITransport):
|
|
|
279
296
|
reader, writer = await asyncio.wait_for(
|
|
280
297
|
asyncio.open_connection(self.ip, self.port), self.CONNECT_TIMEOUT
|
|
281
298
|
)
|
|
299
|
+
|
|
300
|
+
self._enable_keepalive(writer)
|
|
282
301
|
async with self._stream_lock:
|
|
283
302
|
self._stream = (reader, writer)
|
|
284
303
|
self._state = ConnectionState.CONNECTED
|
|
285
304
|
|
|
305
|
+
def _enable_keepalive(self, writer: asyncio.StreamWriter) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Enable TCP keepalive so a half-open connection is detected
|
|
308
|
+
even when no FIN/RST is ever sent.
|
|
309
|
+
|
|
310
|
+
The per-option settings beyond SO_KEEPALIVE are platform specific; each
|
|
311
|
+
is applied only when the running platform exposes it, so this stays a
|
|
312
|
+
no-op on systems that lack the knob rather than raising.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
sock: socket.socket | None = writer.get_extra_info("socket")
|
|
316
|
+
if sock is None:
|
|
317
|
+
self.logger.warning(
|
|
318
|
+
"Failed to enable TCP keepalive: StreamWriter has no socket"
|
|
319
|
+
)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
324
|
+
for opt_name, value in (
|
|
325
|
+
("TCP_KEEPIDLE", self.KEEPALIVE_IDLE),
|
|
326
|
+
("TCP_KEEPINTVL", self.KEEPALIVE_INTERVAL),
|
|
327
|
+
("TCP_KEEPCNT", self.KEEPALIVE_COUNT),
|
|
328
|
+
("TCP_USER_TIMEOUT", self.USER_TIMEOUT_MS),
|
|
329
|
+
):
|
|
330
|
+
opt = getattr(socket, opt_name, None)
|
|
331
|
+
if opt is not None:
|
|
332
|
+
sock.setsockopt(socket.IPPROTO_TCP, opt, value)
|
|
333
|
+
except OSError as e:
|
|
334
|
+
self.logger.warning("Failed to enable TCP keepalive: %s", e)
|
|
335
|
+
|
|
286
336
|
async def _teardown_stream(self, ex: BaseException):
|
|
287
337
|
"""
|
|
288
338
|
Fail all in-flight requests and close the current stream (if any).
|
|
@@ -364,7 +414,7 @@ class AdsTcpTransport(BaseTransport, ITransport):
|
|
|
364
414
|
while self._state is not ConnectionState.CLOSED:
|
|
365
415
|
try:
|
|
366
416
|
await self._read_loop()
|
|
367
|
-
except asyncio.CancelledError:
|
|
417
|
+
except asyncio.CancelledError: # pylint: disable=try-except-raise
|
|
368
418
|
# Except everything... except a cancellation event
|
|
369
419
|
raise
|
|
370
420
|
except Exception as e:
|
|
@@ -382,7 +432,7 @@ class AdsTcpTransport(BaseTransport, ITransport):
|
|
|
382
432
|
self.logger.info("Reconnected to the remote")
|
|
383
433
|
backoff = self.RECONNECT_INITIAL_BACKOFF
|
|
384
434
|
break
|
|
385
|
-
except asyncio.CancelledError:
|
|
435
|
+
except asyncio.CancelledError: # pylint: disable=try-except-raise
|
|
386
436
|
raise
|
|
387
437
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
388
438
|
self.logger.info(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import socket
|
|
5
6
|
import unittest
|
|
6
7
|
from unittest.mock import Mock
|
|
7
8
|
|
|
@@ -216,5 +217,73 @@ class TestAdsTcpTransport(unittest.IsolatedAsyncioTestCase):
|
|
|
216
217
|
self.assertIsInstance(ctx.exception.__cause__, asyncio.IncompleteReadError)
|
|
217
218
|
|
|
218
219
|
|
|
220
|
+
class TestAdsTcpTransportKeepalive(unittest.TestCase):
|
|
221
|
+
"""TCP keepalive / liveness configuration applied to the active socket."""
|
|
222
|
+
|
|
223
|
+
def setUp(self) -> None:
|
|
224
|
+
self.transport = AdsTcpTransport(
|
|
225
|
+
src_address=make_ams_address(), ip="127.0.0.1"
|
|
226
|
+
)
|
|
227
|
+
# A real AF_INET TCP socket whose options we can read back. It does not
|
|
228
|
+
# need to be connected to accept these socket options.
|
|
229
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
230
|
+
self.addCleanup(self.sock.close)
|
|
231
|
+
# A StreamWriter exposes its underlying socket via get_extra_info("socket").
|
|
232
|
+
self.writer = Mock()
|
|
233
|
+
self.writer.get_extra_info.return_value = self.sock
|
|
234
|
+
|
|
235
|
+
def test_enable_keepalive_turns_on_so_keepalive(self) -> None:
|
|
236
|
+
# Act
|
|
237
|
+
self.transport._enable_keepalive(self.writer)
|
|
238
|
+
|
|
239
|
+
# Assert
|
|
240
|
+
self.assertEqual(
|
|
241
|
+
self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE), 1
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@unittest.skipUnless(
|
|
245
|
+
hasattr(socket, "TCP_KEEPIDLE"), "platform lacks TCP keepalive tuning"
|
|
246
|
+
)
|
|
247
|
+
def test_enable_keepalive_applies_configured_tuning_values(self) -> None:
|
|
248
|
+
# Act
|
|
249
|
+
self.transport._enable_keepalive(self.writer)
|
|
250
|
+
|
|
251
|
+
# Assert — each kernel knob reflects the transport's configured constant
|
|
252
|
+
expected = [
|
|
253
|
+
("TCP_KEEPIDLE", AdsTcpTransport.KEEPALIVE_IDLE),
|
|
254
|
+
("TCP_KEEPINTVL", AdsTcpTransport.KEEPALIVE_INTERVAL),
|
|
255
|
+
("TCP_KEEPCNT", AdsTcpTransport.KEEPALIVE_COUNT),
|
|
256
|
+
("TCP_USER_TIMEOUT", AdsTcpTransport.USER_TIMEOUT_MS),
|
|
257
|
+
]
|
|
258
|
+
for opt_name, value in expected:
|
|
259
|
+
opt = getattr(socket, opt_name, None)
|
|
260
|
+
if opt is None:
|
|
261
|
+
continue
|
|
262
|
+
with self.subTest(option=opt_name):
|
|
263
|
+
self.assertEqual(
|
|
264
|
+
self.sock.getsockopt(socket.IPPROTO_TCP, opt), value
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def test_enable_keepalive_without_socket_does_not_raise(self) -> None:
|
|
268
|
+
# Arrange — a StreamWriter without an underlying socket
|
|
269
|
+
self.writer.get_extra_info.return_value = None
|
|
270
|
+
|
|
271
|
+
# Act / Assert — keepalive is best-effort, so it is skipped quietly
|
|
272
|
+
try:
|
|
273
|
+
self.transport._enable_keepalive(self.writer)
|
|
274
|
+
except Exception as e: # pragma: no cover - failure path
|
|
275
|
+
self.fail(f"_enable_keepalive must not raise when no socket: {e}")
|
|
276
|
+
|
|
277
|
+
def test_enable_keepalive_on_closed_socket_does_not_raise(self) -> None:
|
|
278
|
+
# Arrange — a closed socket makes setsockopt fail with OSError
|
|
279
|
+
self.sock.close()
|
|
280
|
+
|
|
281
|
+
# Act / Assert — keepalive is best-effort, so the error is swallowed
|
|
282
|
+
try:
|
|
283
|
+
self.transport._enable_keepalive(self.writer)
|
|
284
|
+
except OSError:
|
|
285
|
+
self.fail("_enable_keepalive must not propagate socket errors")
|
|
286
|
+
|
|
287
|
+
|
|
219
288
|
if __name__ == "__main__":
|
|
220
289
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_datatype_by_name.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_datatype_upload.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_info_by_name_ex.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev6 → aioads-0.1.0.dev7}/tests/unit/functions/test_ads_symbol_table_version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|