stinger-python-utils 0.1.8rc5__tar.gz → 0.1.9__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 (21) hide show
  1. stinger_python_utils-0.1.9/.github/workflows/python-tests.yml +51 -0
  2. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/.github/workflows/python37.yml +1 -1
  3. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/PKG-INFO +4 -4
  4. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/pyproject.toml +5 -4
  5. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/mcp/server.py +2 -2
  6. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/message_creator.py +17 -0
  7. stinger_python_utils-0.1.9/uv.lock +2052 -0
  8. stinger_python_utils-0.1.8rc5/.github/workflows/python-tests.yml +0 -29
  9. stinger_python_utils-0.1.8rc5/.python-version +0 -4
  10. stinger_python_utils-0.1.8rc5/uv.lock +0 -868
  11. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/.gitignore +0 -0
  12. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/.vscode/settings.json +0 -0
  13. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/LICENSE +0 -0
  14. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/README.md +0 -0
  15. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/__init__.py +0 -0
  16. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/mcp/__init__.py +0 -0
  17. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/mcp/__main__.py +0 -0
  18. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/mcp/plugin.py +0 -0
  19. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/src/stinger_python_utils/return_codes.py +0 -0
  20. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/test/__init__.py +0 -0
  21. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.1.9}/test/test_message_creator.py +0 -0
@@ -0,0 +1,51 @@
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 3.7 requires a completely different setup because it is very EOL.
15
+ include:
16
+ - python-version: "3.10"
17
+ uv-sync-flags: "--all-extras"
18
+ mypy-flags: "--exclude '/mcp/'"
19
+ - python-version: "3.11"
20
+ uv-sync-flags: "--all-extras"
21
+ mypy-flags: ""
22
+ - python-version: "3.12"
23
+ uv-sync-flags: "--all-extras"
24
+ mypy-flags: ""
25
+ - python-version: "3.13"
26
+ uv-sync-flags: "--all-extras"
27
+ mypy-flags: ""
28
+ - python-version: "3.14"
29
+ uv-sync-flags: "--all-extras"
30
+ mypy-flags: ""
31
+ - python-version: "3.8"
32
+ uv-sync-flags: ""
33
+ mypy-flags: "--exclude '/mcp/'"
34
+ - python-version: "3.9"
35
+ uv-sync-flags: ""
36
+ mypy-flags: "--exclude '/mcp/'"
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - name: Install UV
40
+ uses: astral-sh/setup-uv@v7
41
+ with:
42
+ python-version: ${{ matrix.python-version }}
43
+ - name: Install project
44
+ run: |
45
+ uv sync --locked ${{ matrix.uv-sync-flags }} --dev
46
+ - name: Run unit tests
47
+ run: |
48
+ uv run pytest
49
+ - name: Mypy Type Check
50
+ run: |
51
+ uv run mypy --check-untyped-defs ${{ matrix.mypy-flags }} ./src/
@@ -7,7 +7,7 @@ on:
7
7
  branches: [ main ]
8
8
 
9
9
  jobs:
10
- test:
10
+ test_py37:
11
11
  runs-on: ubuntu-22.04
12
12
  steps:
13
13
  - uses: actions/checkout@v4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stinger-python-utils
3
- Version: 0.1.8rc5
3
+ Version: 0.1.9
4
4
  Summary: Common utilities for Stinger Python services.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -8,9 +8,9 @@ Requires-Python: >=3.7
8
8
  Requires-Dist: pydantic>=2.5.3
9
9
  Requires-Dist: pyqttier>=0.2.0
10
10
  Provides-Extra: mcp
11
- Requires-Dist: mcp>=1.0.0; extra == 'mcp'
12
- Requires-Dist: stevedore>=5.0; extra == 'mcp'
13
- Requires-Dist: uvicorn>=0.20.0; extra == 'mcp'
11
+ Requires-Dist: mcp>=1.27.0; (python_version >= '3.10') and extra == 'mcp'
12
+ Requires-Dist: stevedore>=5.7; (python_version >= '3.10') and extra == 'mcp'
13
+ Requires-Dist: uvicorn>=0.44.0; (python_version >= '3.10') and extra == 'mcp'
14
14
  Description-Content-Type: text/markdown
15
15
 
16
16
  # stinger-python-utils
@@ -4,11 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stinger-python-utils"
7
- version = "0.1.8rc5"
7
+ version = "0.1.9"
8
8
  description = "Common utilities for Stinger Python services."
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.7"
12
+
12
13
  dependencies = [
13
14
  "pydantic>=2.5.3",
14
15
  "pyqttier>=0.2.0",
@@ -16,9 +17,9 @@ dependencies = [
16
17
 
17
18
  [project.optional-dependencies]
18
19
  mcp = [
19
- "mcp>=1.0.0",
20
- "stevedore>=5.0",
21
- "uvicorn>=0.20.0",
20
+ "mcp>=1.27.0; python_version >= '3.10'",
21
+ "stevedore>=5.7; python_version >= '3.10'",
22
+ "uvicorn>=0.44.0; python_version >= '3.10'",
22
23
  ]
23
24
 
24
25
  [project.scripts]
@@ -377,11 +377,11 @@ class StingerMCPServer:
377
377
 
378
378
  def _load_plugins(self) -> None:
379
379
  def _on_failure(
380
- _mgr: ExtensionManager, entrypoint: Any, err: Exception
380
+ _mgr: Any, entrypoint: Any, err: BaseException
381
381
  ) -> None:
382
382
  logger.error("Failed to load plugin %s: %s", entrypoint, err)
383
383
 
384
- mgr = ExtensionManager(
384
+ mgr: ExtensionManager[Any] = ExtensionManager(
385
385
  namespace=self.STEVEDORE_NAMESPACE,
386
386
  invoke_on_load=True,
387
387
  on_load_failure_callback=_on_failure,
@@ -7,8 +7,16 @@ from .return_codes import MethodReturnCode
7
7
 
8
8
  class MessageCreator:
9
9
 
10
+ @staticmethod
11
+ def _validate_topic(topic: str, param_name: str = "topic") -> None:
12
+ if "+" in topic:
13
+ raise ValueError(
14
+ f"{param_name} must not contain '+', got: {topic!r}"
15
+ )
16
+
10
17
  @classmethod
11
18
  def signal_message(cls, topic: str, payload: BaseModel) -> Message:
19
+ cls._validate_topic(topic)
12
20
  return Message(
13
21
  topic=topic,
14
22
  payload=payload.model_dump_json(by_alias=True).encode("utf-8"),
@@ -21,6 +29,7 @@ class MessageCreator:
21
29
  def status_message(
22
30
  cls, topic, status_message: BaseModel, expiry_seconds: int
23
31
  ) -> Message:
32
+ cls._validate_topic(topic)
24
33
  return Message(
25
34
  topic=topic,
26
35
  payload=status_message.model_dump_json(by_alias=True).encode("utf-8"),
@@ -41,6 +50,7 @@ class MessageCreator:
41
50
  """
42
51
  This could be used for a response to a request, but where there was an error fulfilling the request.
43
52
  """
53
+ cls._validate_topic(topic)
44
54
  rc = (
45
55
  return_code.value
46
56
  if isinstance(return_code, MethodReturnCode)
@@ -76,6 +86,7 @@ class MessageCreator:
76
86
  """
77
87
  This could be used for a successful response to a request.
78
88
  """
89
+ cls._validate_topic(response_topic, "response_topic")
79
90
  if isinstance(response_obj, BaseModel):
80
91
  payload = response_obj.model_dump_json(by_alias=True).encode("utf-8")
81
92
  elif isinstance(response_obj, str):
@@ -109,6 +120,7 @@ class MessageCreator:
109
120
  """
110
121
  Creates a retained message representing the state/value of a property.
111
122
  """
123
+ cls._validate_topic(topic)
112
124
  msg_obj = Message(
113
125
  topic=topic,
114
126
  payload=state_obj.model_dump_json(by_alias=True).encode("utf-8"),
@@ -132,6 +144,8 @@ class MessageCreator:
132
144
  """
133
145
  Creates a message representing a request to update a property.
134
146
  """
147
+ cls._validate_topic(topic)
148
+ cls._validate_topic(response_topic, "response_topic")
135
149
  msg_obj = Message(
136
150
  topic=topic,
137
151
  payload=property_obj.model_dump_json(by_alias=True).encode("utf-8"),
@@ -161,6 +175,7 @@ class MessageCreator:
161
175
  """
162
176
  Creates a message representing a response to a property update request.
163
177
  """
178
+ cls._validate_topic(response_topic, "response_topic")
164
179
  rc = (
165
180
  return_code.value
166
181
  if isinstance(return_code, MethodReturnCode)
@@ -196,6 +211,8 @@ class MessageCreator:
196
211
  response_topic: str,
197
212
  correlation_id: Union[str, bytes, None] = None,
198
213
  ) -> Message:
214
+ cls._validate_topic(topic)
215
+ cls._validate_topic(response_topic, "response_topic")
199
216
  if correlation_id is None:
200
217
  correlation_id = str(uuid.uuid4())
201
218
  msg_obj = Message(