pyqttier 0.1.0__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.
@@ -0,0 +1,27 @@
1
+ name: Mypy Type Check
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ mypy:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install paho-mqtt mypy
25
+ - name: Run mypy
26
+ run: |
27
+ PYTHONPATH=src mypy --check-untyped-defs ./src/
@@ -0,0 +1,30 @@
1
+ name: Python Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install paho-mqtt
25
+ - name: Run unit tests
26
+ run: |
27
+ PYTHONPATH=src python -m unittest discover tests
28
+ - name: Run mock example
29
+ run: |
30
+ PYTHONPATH=src python examples/mock_example.py
@@ -0,0 +1,151 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # poetry
90
+ poetry.lock
91
+
92
+ # pdm
93
+ .pdm.toml
94
+ .pdm-python
95
+ .pdm-build/
96
+
97
+ # PEP 582
98
+ __pypackages__/
99
+
100
+ # Celery stuff
101
+ celerybeat-schedule
102
+ celerybeat.pid
103
+
104
+ # SageMath parsed files
105
+ *.sage.py
106
+
107
+ # Environments
108
+ .env
109
+ .venv
110
+ env/
111
+ venv/
112
+ ENV/
113
+ env.bak/
114
+ venv.bak/
115
+
116
+ # Spyder project settings
117
+ .spyderproject
118
+ .spyproject
119
+
120
+ # Rope project settings
121
+ .ropeproject
122
+
123
+ # mkdocs documentation
124
+ /site
125
+
126
+ # mypy
127
+ .mypy_cache/
128
+ .dmypy.json
129
+ dmypy.json
130
+
131
+ # Pyre type checker
132
+ .pyre/
133
+
134
+ # pytype static type analyzer
135
+ .pytype/
136
+
137
+ # Cython debug symbols
138
+ cython_debug/
139
+
140
+ # IDEs
141
+ .vscode/
142
+ .idea/
143
+ *.swp
144
+ *.swo
145
+ *~
146
+ .DS_Store
147
+
148
+ # Project specific
149
+ uv.lock
150
+ check_py37_compat.py
151
+ PYTHON37_COMPAT.md
pyqttier-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jacob Brunson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyqttier
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author-email: Jacob Brunson <github@jacobbrunson.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.7
8
+ Requires-Dist: paho-mqtt>=2.1.0
9
+ Requires-Dist: pydantic>=2.5.3
10
+ Description-Content-Type: text/markdown
11
+
12
+ # PyQTTier
13
+
14
+ A Python MQTT client library providing a clean, type-safe wrapper around paho-mqtt with support for MQTT 5.0 features.
15
+
16
+ ## Features
17
+
18
+ - **MQTT 5.0 Support** - Full support for MQTT 5.0 protocol features including message properties, correlation data, and response topics
19
+ - **Type Safety** - Strongly typed with mypy-checked type hints for reliability
20
+ - **Multiple Transports** - Supports TCP, WebSocket, and Unix socket connections
21
+ - **Mock Implementation** - Built-in `MockConnection` for easy testing without a broker
22
+ - **Python 3.7+** - Compatible with Python 3.7 through 3.12
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install pyqttier
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Basic Usage
33
+
34
+ ```python
35
+ from pyqttier.connection import Mqtt5Connection
36
+ from pyqttier.transport import MqttTransport, MqttTransportType
37
+ from pyqttier.message import Message
38
+
39
+ # Connect to broker
40
+ transport = MqttTransport(MqttTransportType.TCP, host="localhost", port=1883)
41
+ conn = Mqtt5Connection(transport=transport, client_id="my-client")
42
+
43
+ # Subscribe to a topic
44
+ def on_message(msg: Message):
45
+ print(f"Received: {msg.payload.decode()}")
46
+
47
+ conn.subscribe("sensors/temperature", callback=on_message)
48
+
49
+ # Publish a message
50
+ msg = Message(topic="sensors/temperature", payload=b"23.5", qos=1)
51
+ conn.publish(msg)
52
+ ```
53
+
54
+ ### Testing with MockConnection
55
+
56
+ ```python
57
+ from pyqttier.mock import MockConnection
58
+ from pyqttier.message import Message
59
+
60
+ # Create mock connection for testing
61
+ conn = MockConnection()
62
+
63
+ # Publish and verify
64
+ conn.publish(Message(topic="test", payload=b"data", qos=1))
65
+ assert len(conn.published_messages) == 1
66
+ assert conn.published_messages[0].topic == "test"
67
+ ```
68
+
69
+ ## Examples
70
+
71
+ See the [examples/](examples/) directory for more detailed usage examples:
72
+
73
+ - `usage_example.py` - Real broker connections, wildcards, request-response patterns
74
+ - `mock_example.py` - Testing with MockConnection
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ # Type checking
80
+ uv run mypy --check-untyped-defs ./src/
81
+
82
+ # Unit tests
83
+ uv run pytest
84
+ ```
85
+
86
+ ## License
87
+
88
+ See [LICENSE](LICENSE) file for details.
@@ -0,0 +1,77 @@
1
+ # PyQTTier
2
+
3
+ A Python MQTT client library providing a clean, type-safe wrapper around paho-mqtt with support for MQTT 5.0 features.
4
+
5
+ ## Features
6
+
7
+ - **MQTT 5.0 Support** - Full support for MQTT 5.0 protocol features including message properties, correlation data, and response topics
8
+ - **Type Safety** - Strongly typed with mypy-checked type hints for reliability
9
+ - **Multiple Transports** - Supports TCP, WebSocket, and Unix socket connections
10
+ - **Mock Implementation** - Built-in `MockConnection` for easy testing without a broker
11
+ - **Python 3.7+** - Compatible with Python 3.7 through 3.12
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install pyqttier
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### Basic Usage
22
+
23
+ ```python
24
+ from pyqttier.connection import Mqtt5Connection
25
+ from pyqttier.transport import MqttTransport, MqttTransportType
26
+ from pyqttier.message import Message
27
+
28
+ # Connect to broker
29
+ transport = MqttTransport(MqttTransportType.TCP, host="localhost", port=1883)
30
+ conn = Mqtt5Connection(transport=transport, client_id="my-client")
31
+
32
+ # Subscribe to a topic
33
+ def on_message(msg: Message):
34
+ print(f"Received: {msg.payload.decode()}")
35
+
36
+ conn.subscribe("sensors/temperature", callback=on_message)
37
+
38
+ # Publish a message
39
+ msg = Message(topic="sensors/temperature", payload=b"23.5", qos=1)
40
+ conn.publish(msg)
41
+ ```
42
+
43
+ ### Testing with MockConnection
44
+
45
+ ```python
46
+ from pyqttier.mock import MockConnection
47
+ from pyqttier.message import Message
48
+
49
+ # Create mock connection for testing
50
+ conn = MockConnection()
51
+
52
+ # Publish and verify
53
+ conn.publish(Message(topic="test", payload=b"data", qos=1))
54
+ assert len(conn.published_messages) == 1
55
+ assert conn.published_messages[0].topic == "test"
56
+ ```
57
+
58
+ ## Examples
59
+
60
+ See the [examples/](examples/) directory for more detailed usage examples:
61
+
62
+ - `usage_example.py` - Real broker connections, wildcards, request-response patterns
63
+ - `mock_example.py` - Testing with MockConnection
64
+
65
+ ## Development
66
+
67
+ ```bash
68
+ # Type checking
69
+ uv run mypy --check-untyped-defs ./src/
70
+
71
+ # Unit tests
72
+ uv run pytest
73
+ ```
74
+
75
+ ## License
76
+
77
+ See [LICENSE](LICENSE) file for details.
@@ -0,0 +1,24 @@
1
+ # PyQTTier Examples
2
+
3
+ This directory contains concise examples for using PyQTTier.
4
+
5
+ ## Usage Example (`usage_example.py`)
6
+
7
+ Demonstrates connecting to an MQTT broker, publishing/subscribing, retained messages, wildcards, and request-response. Requires a broker on `localhost:1883`:
8
+
9
+ ```bash
10
+ docker run -p 1883:1883 eclipse-mosquitto
11
+ ```
12
+
13
+ Run the examples:
14
+ ```bash
15
+ uv run examples/usage_example.py
16
+ ```
17
+
18
+ ## Mock Connection Example (`mock_example.py`)
19
+
20
+ Shows how to use `MockConnection` for testing without a real broker. Run with:
21
+
22
+ ```bash
23
+ uv run examples/mock_example.py
24
+ ```
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example demonstrating how to use MockConnection for testing MQTT-based applications.
4
+
5
+ This example shows:
6
+ 1. Publishing messages
7
+ 2. Subscribing to topics with callbacks
8
+ 3. Using global message callbacks
9
+ 4. Topic wildcard matching
10
+ 5. Simulating received messages
11
+ 6. Verifying published messages
12
+ """
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ # Add src to path for standalone execution
18
+ sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))
19
+
20
+ from pyqttier.mock import MockConnection
21
+ from pyqttier.message import Message
22
+
23
+
24
+ def example_basic_publish_subscribe():
25
+ """Example 1: Basic publish and subscribe with callbacks."""
26
+ print("=" * 60)
27
+ print("Example 1: Basic Publish and Subscribe")
28
+ print("=" * 60)
29
+
30
+ # Create a mock connection
31
+ conn = MockConnection().set_connected(True)
32
+
33
+ # Publish a message
34
+ msg = Message(
35
+ topic="sensors/temperature",
36
+ payload=b"23.5",
37
+ qos=1,
38
+ retain=False
39
+ )
40
+ fut = conn.publish(msg)
41
+ print(f"📤 Published temperature reading: {msg.payload.decode()}°C")
42
+
43
+ assert len(conn.published_messages) == 1, "Should have one published message"
44
+
45
+ # Verify the message was received
46
+ print(f"✓ Published {len(conn.published_messages)} message.\n")
47
+
48
+ def example_basic_subscribe():
49
+ """Example 2: Basic subscribe with topic filter and callback."""
50
+ print("=" * 60)
51
+ print("Example 2: Basic Subscribe with Callback")
52
+ print("=" * 60)
53
+
54
+ # Create a mock connection
55
+ conn = MockConnection().set_connected(True)
56
+
57
+ received_count = 0
58
+
59
+ # Define a callback for received messages
60
+ def message_callback(msg: Message):
61
+ nonlocal received_count
62
+ print(f"📥 Received message on topic '{msg.topic}': {msg.payload.decode()}")
63
+ received_count += 1
64
+
65
+ # Subscribe to a topic
66
+ sub_id = conn.subscribe("sensors/+", message_callback)
67
+ print(f"🔔 Subscribed to 'sensors/+' with subscription ID {sub_id}")
68
+
69
+ # Simulate receiving a message
70
+ incoming_msg = Message(
71
+ topic="sensors/humidity",
72
+ payload=b"45%",
73
+ qos=1,
74
+ retain=False
75
+ )
76
+ conn.simulate_message(incoming_msg)
77
+
78
+ assert received_count == 1, "Should have received one message"
79
+ print(f"✓ Received {received_count} message(s) via subscription.\n")
80
+
81
+ def example_request_response():
82
+ """Example 3: Request-Response pattern using response topics."""
83
+ print("=" * 60)
84
+ print("Example 3: Request-Response Pattern")
85
+ print("=" * 60)
86
+
87
+ # Create a mock connection
88
+ conn = MockConnection().set_connected(True)
89
+
90
+ responses_received = 0
91
+
92
+ # Define a callback for responses
93
+ def response_callback(msg: Message):
94
+ nonlocal responses_received
95
+ responses_received += 1
96
+ print(f"📥 Received response on topic '{msg.topic}': {msg.payload.decode()}")
97
+
98
+ # Subscribe to the response topic
99
+ response_topic = "responses/device123"
100
+ conn.subscribe(response_topic, response_callback)
101
+ print(f"🔔 Subscribed to response topic '{response_topic}'")
102
+
103
+ # Simulate sending a request
104
+ request_msg = Message(
105
+ topic="requests/device123",
106
+ payload=b"GetStatus",
107
+ qos=1,
108
+ retain=False,
109
+ response_topic=response_topic
110
+ )
111
+ conn.publish(request_msg)
112
+ print(f"📤 Sent request: {request_msg.payload.decode()}")
113
+
114
+ assert len(conn.published_messages) == 1, "Should have one published request message"
115
+
116
+ # Simulate receiving a response
117
+ response_msg = Message(
118
+ topic=response_topic,
119
+ payload=b"Status: OK",
120
+ qos=1,
121
+ retain=False
122
+ )
123
+ conn.simulate_message(response_msg)
124
+
125
+ assert responses_received == 1, "Should have received one response"
126
+
127
+ print("✓ Completed request-response simulation.\n")
128
+
129
+ if __name__ == "__main__":
130
+ example_basic_publish_subscribe()
131
+ example_basic_subscribe()
132
+ example_request_response()