edgesync 0.3.1__tar.gz → 0.3.2__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.
- {edgesync-0.3.1 → edgesync-0.3.2}/CHANGELOG.md +17 -1
- {edgesync-0.3.1 → edgesync-0.3.2}/PKG-INFO +63 -1
- {edgesync-0.3.1 → edgesync-0.3.2}/README.md +62 -0
- edgesync-0.3.2/VERSION +1 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/docs/getting-started.md +25 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_mqtt_transport.py +44 -0
- edgesync-0.3.1/VERSION +0 -1
- {edgesync-0.3.1 → edgesync-0.3.2}/.github/workflows/ci.yml +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/.github/workflows/release.yml +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/.gitignore +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/CONTRIBUTING.md +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/LICENSE +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/SECURITY.md +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/docs/integrations.md +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/docs/reliability.md +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/docs/storage.md +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/client.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/config.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/exceptions.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/logging.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/delivery.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/message.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/receipt.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/stats.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/py.typed +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/queue/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/queue/manager.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/backoff.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/policy.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/base.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/migrations.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/sqlite.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/base.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/http.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/mqtt.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/registry.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/clock.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/ids.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/lifecycle.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/scheduler.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/sync_worker.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/pyproject.toml +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/conftest.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/factories.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/fake_transport.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_client.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_http_transport.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_sqlite_storage.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_worker.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/reliability/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/reliability/test_reliability.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/__init__.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_backoff.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_config.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_models.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_queue_manager.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_retry_policy.py +0 -0
- {edgesync-0.3.1 → edgesync-0.3.2}/uv.lock +0 -0
|
@@ -7,6 +7,21 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.2] - 2026-08-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- MQTT TLS documentation in `README.md` and `docs/getting-started.md`, covering
|
|
15
|
+
`tls_params`/`port` usage for TLS connections and client-certificate (mutual TLS) auth, plus
|
|
16
|
+
test coverage for the `aiomqtt.Client` construction path (both plaintext and TLS), which
|
|
17
|
+
wasn't previously exercised since existing tests injected a fake client directly.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `README.md`'s Quickstart now includes a full MQTT usage section (previously it only linked
|
|
22
|
+
out to the getting-started guide), so the MQTT example renders directly on PyPI's project
|
|
23
|
+
page instead of requiring a click-through to GitHub.
|
|
24
|
+
|
|
10
25
|
## [0.3.1] - 2026-08-24
|
|
11
26
|
|
|
12
27
|
### Fixed
|
|
@@ -82,7 +97,8 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
82
97
|
- Crash and restart recovery via expired-lease sweeps, safe across concurrent workers and
|
|
83
98
|
processes sharing the same database file.
|
|
84
99
|
|
|
85
|
-
[Unreleased]: https://github.com/adhuldas/EdgeSync/compare/v0.3.
|
|
100
|
+
[Unreleased]: https://github.com/adhuldas/EdgeSync/compare/v0.3.2...HEAD
|
|
101
|
+
[0.3.2]: https://github.com/adhuldas/EdgeSync/compare/v0.3.1...v0.3.2
|
|
86
102
|
[0.3.1]: https://github.com/adhuldas/EdgeSync/compare/v0.3.0...v0.3.1
|
|
87
103
|
[0.3.0]: https://github.com/adhuldas/EdgeSync/compare/v0.2.1...v0.3.0
|
|
88
104
|
[0.2.1]: https://github.com/adhuldas/EdgeSync/compare/v0.2.0...v0.2.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: edgesync
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Reliable data delivery for unreliable networks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/adhuldas/EdgeSync
|
|
6
6
|
Project-URL: Repository, https://github.com/adhuldas/EdgeSync
|
|
@@ -94,6 +94,68 @@ That's it: `publish()` durably persists the message and returns as soon as it's
|
|
|
94
94
|
background worker delivers it, retrying with exponential backoff on failure, without the
|
|
95
95
|
caller needing to stay connected or wait for the network.
|
|
96
96
|
|
|
97
|
+
## MQTT
|
|
98
|
+
|
|
99
|
+
Install the optional extra first:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install edgesync[mqtt]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Publishing to MQTT instead of HTTP is the same pattern with a different transport:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from edgesync import EdgeSync
|
|
109
|
+
from edgesync.transports.mqtt import MQTTTransport
|
|
110
|
+
|
|
111
|
+
sync = EdgeSync(
|
|
112
|
+
database="edgesync.db",
|
|
113
|
+
destinations={
|
|
114
|
+
"telemetry": MQTTTransport(
|
|
115
|
+
"broker.example.com",
|
|
116
|
+
"devices/device-001/telemetry",
|
|
117
|
+
qos=1,
|
|
118
|
+
username="device-001",
|
|
119
|
+
password="...",
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
default_destination="telemetry",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
await sync.publish({"temperature": 28.5})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`MQTTTransport` only accepts QoS 1 or 2 — QoS 0 publishes are never acknowledged by the
|
|
129
|
+
broker, so there'd be no way to tell a successful delivery from a lost one, which would break
|
|
130
|
+
EdgeSync's at-least-once guarantee. Unlike `HTTPTransport`, MQTT holds a persistent broker
|
|
131
|
+
connection: `EdgeSync.start()` still won't raise if the broker is unreachable (data keeps
|
|
132
|
+
queuing locally either way), and the transport automatically reconnects on the next delivery
|
|
133
|
+
attempt after a dropped connection or failed publish.
|
|
134
|
+
|
|
135
|
+
**TLS**: by default `MQTTTransport` connects in plaintext on port `1883`. To connect over
|
|
136
|
+
TLS, pass `tls_params` (an `aiomqtt.TLSParameters`) and switch to the broker's TLS port,
|
|
137
|
+
conventionally `8883`:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import aiomqtt
|
|
141
|
+
from edgesync.transports.mqtt import MQTTTransport
|
|
142
|
+
|
|
143
|
+
MQTTTransport(
|
|
144
|
+
"broker.example.com",
|
|
145
|
+
"devices/device-001/telemetry",
|
|
146
|
+
port=8883,
|
|
147
|
+
tls_params=aiomqtt.TLSParameters(
|
|
148
|
+
ca_certs="/etc/ssl/certs/ca-certificates.crt",
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`TLSParameters` also accepts `certfile` / `keyfile` for client-certificate (mutual TLS)
|
|
154
|
+
authentication. Omit `tls_params` entirely for an unencrypted connection, e.g. a broker on a
|
|
155
|
+
trusted local network or a local development/test setup. See the
|
|
156
|
+
[MQTT section of the getting-started guide](https://github.com/adhuldas/EdgeSync/blob/main/docs/getting-started.md#mqtt)
|
|
157
|
+
for further detail.
|
|
158
|
+
|
|
97
159
|
## Usage with FastAPI, Flask, and plain asyncio
|
|
98
160
|
|
|
99
161
|
EdgeSync is async-native, so it's most at home in an `asyncio` app or an async framework like
|
|
@@ -61,6 +61,68 @@ That's it: `publish()` durably persists the message and returns as soon as it's
|
|
|
61
61
|
background worker delivers it, retrying with exponential backoff on failure, without the
|
|
62
62
|
caller needing to stay connected or wait for the network.
|
|
63
63
|
|
|
64
|
+
## MQTT
|
|
65
|
+
|
|
66
|
+
Install the optional extra first:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install edgesync[mqtt]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Publishing to MQTT instead of HTTP is the same pattern with a different transport:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from edgesync import EdgeSync
|
|
76
|
+
from edgesync.transports.mqtt import MQTTTransport
|
|
77
|
+
|
|
78
|
+
sync = EdgeSync(
|
|
79
|
+
database="edgesync.db",
|
|
80
|
+
destinations={
|
|
81
|
+
"telemetry": MQTTTransport(
|
|
82
|
+
"broker.example.com",
|
|
83
|
+
"devices/device-001/telemetry",
|
|
84
|
+
qos=1,
|
|
85
|
+
username="device-001",
|
|
86
|
+
password="...",
|
|
87
|
+
),
|
|
88
|
+
},
|
|
89
|
+
default_destination="telemetry",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
await sync.publish({"temperature": 28.5})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`MQTTTransport` only accepts QoS 1 or 2 — QoS 0 publishes are never acknowledged by the
|
|
96
|
+
broker, so there'd be no way to tell a successful delivery from a lost one, which would break
|
|
97
|
+
EdgeSync's at-least-once guarantee. Unlike `HTTPTransport`, MQTT holds a persistent broker
|
|
98
|
+
connection: `EdgeSync.start()` still won't raise if the broker is unreachable (data keeps
|
|
99
|
+
queuing locally either way), and the transport automatically reconnects on the next delivery
|
|
100
|
+
attempt after a dropped connection or failed publish.
|
|
101
|
+
|
|
102
|
+
**TLS**: by default `MQTTTransport` connects in plaintext on port `1883`. To connect over
|
|
103
|
+
TLS, pass `tls_params` (an `aiomqtt.TLSParameters`) and switch to the broker's TLS port,
|
|
104
|
+
conventionally `8883`:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import aiomqtt
|
|
108
|
+
from edgesync.transports.mqtt import MQTTTransport
|
|
109
|
+
|
|
110
|
+
MQTTTransport(
|
|
111
|
+
"broker.example.com",
|
|
112
|
+
"devices/device-001/telemetry",
|
|
113
|
+
port=8883,
|
|
114
|
+
tls_params=aiomqtt.TLSParameters(
|
|
115
|
+
ca_certs="/etc/ssl/certs/ca-certificates.crt",
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`TLSParameters` also accepts `certfile` / `keyfile` for client-certificate (mutual TLS)
|
|
121
|
+
authentication. Omit `tls_params` entirely for an unencrypted connection, e.g. a broker on a
|
|
122
|
+
trusted local network or a local development/test setup. See the
|
|
123
|
+
[MQTT section of the getting-started guide](https://github.com/adhuldas/EdgeSync/blob/main/docs/getting-started.md#mqtt)
|
|
124
|
+
for further detail.
|
|
125
|
+
|
|
64
126
|
## Usage with FastAPI, Flask, and plain asyncio
|
|
65
127
|
|
|
66
128
|
EdgeSync is async-native, so it's most at home in an `asyncio` app or an async framework like
|
edgesync-0.3.2/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.2
|
|
@@ -160,6 +160,31 @@ connection: `EdgeSync.start()` still won't raise if the broker is unreachable (d
|
|
|
160
160
|
queuing locally either way), and the transport automatically reconnects on the next delivery
|
|
161
161
|
attempt after a dropped connection or failed publish.
|
|
162
162
|
|
|
163
|
+
### TLS
|
|
164
|
+
|
|
165
|
+
By default `MQTTTransport` connects in plaintext on port `1883`. To connect over TLS, pass
|
|
166
|
+
`tls_params` (an `aiomqtt.TLSParameters`, re-exported from the underlying `paho-mqtt` client)
|
|
167
|
+
and switch to the broker's TLS port, conventionally `8883`:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import aiomqtt
|
|
171
|
+
from edgesync.transports.mqtt import MQTTTransport
|
|
172
|
+
|
|
173
|
+
MQTTTransport(
|
|
174
|
+
"broker.example.com",
|
|
175
|
+
"devices/device-001/telemetry",
|
|
176
|
+
port=8883,
|
|
177
|
+
tls_params=aiomqtt.TLSParameters(
|
|
178
|
+
ca_certs="/etc/ssl/certs/ca-certificates.crt",
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`TLSParameters` also accepts `certfile` / `keyfile` for client-certificate (mutual TLS)
|
|
184
|
+
authentication — see [aiomqtt's documentation](https://aiomqtt.bo3hm.com) for the full set of
|
|
185
|
+
options. Omit `tls_params` entirely for an unencrypted connection, e.g. when talking to a
|
|
186
|
+
broker on a trusted local network or in a local development/test setup.
|
|
187
|
+
|
|
163
188
|
## Inspecting the queue
|
|
164
189
|
|
|
165
190
|
```python
|
|
@@ -149,3 +149,47 @@ async def test_close_after_failed_start_does_not_disconnect() -> None:
|
|
|
149
149
|
await transport.start()
|
|
150
150
|
await transport.close()
|
|
151
151
|
assert fake.disconnect_calls == 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def test_start_without_tls_params_constructs_plaintext_client(
|
|
155
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
156
|
+
) -> None:
|
|
157
|
+
captured: dict[str, object] = {}
|
|
158
|
+
|
|
159
|
+
class RecordingClient(FakeMQTTClient):
|
|
160
|
+
def __init__(self, hostname: str, port: int, **kwargs: object) -> None:
|
|
161
|
+
super().__init__()
|
|
162
|
+
captured["hostname"] = hostname
|
|
163
|
+
captured["port"] = port
|
|
164
|
+
captured.update(kwargs)
|
|
165
|
+
|
|
166
|
+
monkeypatch.setattr(aiomqtt, "Client", RecordingClient)
|
|
167
|
+
|
|
168
|
+
transport = MQTTTransport("broker.invalid", "telemetry")
|
|
169
|
+
await transport.start()
|
|
170
|
+
|
|
171
|
+
assert captured["hostname"] == "broker.invalid"
|
|
172
|
+
assert captured["port"] == 1883
|
|
173
|
+
assert captured["tls_params"] is None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def test_start_with_tls_params_constructs_tls_client(
|
|
177
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
178
|
+
) -> None:
|
|
179
|
+
captured: dict[str, object] = {}
|
|
180
|
+
|
|
181
|
+
class RecordingClient(FakeMQTTClient):
|
|
182
|
+
def __init__(self, hostname: str, port: int, **kwargs: object) -> None:
|
|
183
|
+
super().__init__()
|
|
184
|
+
captured["hostname"] = hostname
|
|
185
|
+
captured["port"] = port
|
|
186
|
+
captured.update(kwargs)
|
|
187
|
+
|
|
188
|
+
monkeypatch.setattr(aiomqtt, "Client", RecordingClient)
|
|
189
|
+
|
|
190
|
+
tls_params = aiomqtt.TLSParameters(ca_certs="/etc/ssl/certs/ca-certificates.crt")
|
|
191
|
+
transport = MQTTTransport("broker.invalid", "telemetry", port=8883, tls_params=tls_params)
|
|
192
|
+
await transport.start()
|
|
193
|
+
|
|
194
|
+
assert captured["port"] == 8883
|
|
195
|
+
assert captured["tls_params"] is tls_params
|
edgesync-0.3.1/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.1
|
|
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
|