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.
Files changed (68) hide show
  1. {edgesync-0.3.1 → edgesync-0.3.2}/CHANGELOG.md +17 -1
  2. {edgesync-0.3.1 → edgesync-0.3.2}/PKG-INFO +63 -1
  3. {edgesync-0.3.1 → edgesync-0.3.2}/README.md +62 -0
  4. edgesync-0.3.2/VERSION +1 -0
  5. {edgesync-0.3.1 → edgesync-0.3.2}/docs/getting-started.md +25 -0
  6. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_mqtt_transport.py +44 -0
  7. edgesync-0.3.1/VERSION +0 -1
  8. {edgesync-0.3.1 → edgesync-0.3.2}/.github/workflows/ci.yml +0 -0
  9. {edgesync-0.3.1 → edgesync-0.3.2}/.github/workflows/release.yml +0 -0
  10. {edgesync-0.3.1 → edgesync-0.3.2}/.gitignore +0 -0
  11. {edgesync-0.3.1 → edgesync-0.3.2}/CONTRIBUTING.md +0 -0
  12. {edgesync-0.3.1 → edgesync-0.3.2}/LICENSE +0 -0
  13. {edgesync-0.3.1 → edgesync-0.3.2}/SECURITY.md +0 -0
  14. {edgesync-0.3.1 → edgesync-0.3.2}/docs/integrations.md +0 -0
  15. {edgesync-0.3.1 → edgesync-0.3.2}/docs/reliability.md +0 -0
  16. {edgesync-0.3.1 → edgesync-0.3.2}/docs/storage.md +0 -0
  17. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/__init__.py +0 -0
  18. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/client.py +0 -0
  19. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/config.py +0 -0
  20. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/exceptions.py +0 -0
  21. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/logging.py +0 -0
  22. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/__init__.py +0 -0
  23. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/delivery.py +0 -0
  24. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/message.py +0 -0
  25. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/receipt.py +0 -0
  26. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/models/stats.py +0 -0
  27. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/py.typed +0 -0
  28. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/queue/__init__.py +0 -0
  29. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/queue/manager.py +0 -0
  30. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/__init__.py +0 -0
  31. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/backoff.py +0 -0
  32. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/retry/policy.py +0 -0
  33. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/__init__.py +0 -0
  34. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/base.py +0 -0
  35. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/migrations.py +0 -0
  36. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/storage/sqlite.py +0 -0
  37. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/__init__.py +0 -0
  38. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/base.py +0 -0
  39. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/http.py +0 -0
  40. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/mqtt.py +0 -0
  41. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/transports/registry.py +0 -0
  42. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/__init__.py +0 -0
  43. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/clock.py +0 -0
  44. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/utils/ids.py +0 -0
  45. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/__init__.py +0 -0
  46. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/lifecycle.py +0 -0
  47. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/scheduler.py +0 -0
  48. {edgesync-0.3.1 → edgesync-0.3.2}/edgesync/worker/sync_worker.py +0 -0
  49. {edgesync-0.3.1 → edgesync-0.3.2}/pyproject.toml +0 -0
  50. {edgesync-0.3.1 → edgesync-0.3.2}/tests/__init__.py +0 -0
  51. {edgesync-0.3.1 → edgesync-0.3.2}/tests/conftest.py +0 -0
  52. {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/__init__.py +0 -0
  53. {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/factories.py +0 -0
  54. {edgesync-0.3.1 → edgesync-0.3.2}/tests/fixtures/fake_transport.py +0 -0
  55. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/__init__.py +0 -0
  56. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_client.py +0 -0
  57. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_http_transport.py +0 -0
  58. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_sqlite_storage.py +0 -0
  59. {edgesync-0.3.1 → edgesync-0.3.2}/tests/integration/test_worker.py +0 -0
  60. {edgesync-0.3.1 → edgesync-0.3.2}/tests/reliability/__init__.py +0 -0
  61. {edgesync-0.3.1 → edgesync-0.3.2}/tests/reliability/test_reliability.py +0 -0
  62. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/__init__.py +0 -0
  63. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_backoff.py +0 -0
  64. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_config.py +0 -0
  65. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_models.py +0 -0
  66. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_queue_manager.py +0 -0
  67. {edgesync-0.3.1 → edgesync-0.3.2}/tests/unit/test_retry_policy.py +0 -0
  68. {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.1...HEAD
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.1
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