Skip to content

Commit 23e1730

Browse files
Merge pull request #131 from NHSDigital/mesh-2771-dependabot
mesh-2771: drop support for python 3.9 + add support for python 3.14
2 parents a031c93 + c8a10ff commit 23e1730

11 files changed

Lines changed: 731 additions & 713 deletions

File tree

.github/workflows/pull-request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
tox:
1010
strategy:
1111
matrix:
12-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
12+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1313

1414
runs-on: ubuntu-latest
1515
if: github.repository == 'NHSDigital/mesh-client'

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
poetry 2.1.3
2-
python 3.13.5 3.12.11 3.11.13 3.10.18 3.9.23
2+
python 3.14.1 3.13.5 3.12.11 3.11.13 3.10.18

CHANGE-LOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ These are not all encompassing, but we will try and capture noteable differences
55

66
----
77

8+
# 5.0
9+
* drop support for Python 3.9 which is now [EOL](https://devguide.python.org/versions/#status-of-python-versions)
10+
* add support for Python 3.14
11+
812
# 4.0
913
* drop support for Python 3.7 and 3.8 which are now [EOL](https://devguide.python.org/versions/#status-of-python-versions)
1014
* add support for Python 3.12 and 3.13

mesh_client/__init__.py

Lines changed: 50 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from io import BytesIO
1717
from itertools import chain
1818
from types import TracebackType
19-
from typing import Any, NoReturn, Optional, TypeVar, Union, cast
19+
from typing import Any, NoReturn, TypeVar, cast
2020
from urllib.parse import quote as q
2121
from urllib.parse import urlparse
2222

@@ -46,9 +46,9 @@
4646
TrackingResponse_v2,
4747
)
4848

49-
if sys.version_info[:2] < (3, 9): # noqa: UP036
49+
if sys.version_info[:2] < (3, 10): # noqa: UP036
5050
warnings.warn(
51-
"python versions < 3.9 are end of life and no longer supported as of mesh-client v4",
51+
"python versions < 3.10 are end of life and no longer supported.",
5252
category=DeprecationWarning,
5353
stacklevel=2,
5454
)
@@ -136,9 +136,9 @@ def optional_header_map() -> dict[str, str]:
136136

137137
# urllib3 'futures' ( not part of 1.26 .. but available in 2.x )
138138
def reraise(
139-
tp: Optional[type[BaseException]],
139+
tp: type[BaseException] | None,
140140
value: BaseException,
141-
tb: Optional[TracebackType] = None,
141+
tb: TracebackType | None = None,
142142
) -> NoReturn:
143143
try:
144144
if value.__traceback__ is not tb:
@@ -149,7 +149,7 @@ def reraise(
149149
tb = None
150150

151151

152-
def try_get_endpoint_from_url(url: str) -> Optional[Endpoint]:
152+
def try_get_endpoint_from_url(url: str) -> Endpoint | None:
153153
url_parsed = urlparse(url)
154154
if not url_parsed.hostname:
155155
return None
@@ -195,7 +195,7 @@ def _looks_like_send_error(status_code: int, response_dict: dict) -> bool:
195195

196196
def _get_send_error_message(
197197
response_dict: dict,
198-
) -> tuple[str, Union[SendMessageErrorResponse_v1, SendMessageErrorResponse_v2, dict]]:
198+
) -> tuple[str, SendMessageErrorResponse_v1 | SendMessageErrorResponse_v2 | dict]:
199199
if "errorDescription" in response_dict:
200200
return response_dict["errorDescription"], cast(SendMessageErrorResponse_v1, response_dict)
201201

@@ -214,12 +214,12 @@ class MeshRetry(Retry):
214214

215215
def increment(
216216
self,
217-
method: Optional[str] = None,
218-
url: Optional[str] = None,
217+
method: str | None = None,
218+
url: str | None = None,
219219
response=None,
220-
error: Optional[Exception] = None,
221-
_pool: Optional[ConnectionPool] = None,
222-
_stacktrace: Optional[TracebackType] = None,
220+
error: Exception | None = None,
221+
_pool: ConnectionPool | None = None,
222+
_stacktrace: TracebackType | None = None,
223223
) -> "MeshRetry":
224224
if method != "POST" or not url or not url.endswith("/outbox"):
225225
return cast(MeshRetry, super().increment(method, url, response, error, _pool, _stacktrace))
@@ -243,11 +243,11 @@ def increment(
243243
class SSLContextAdapter(HTTPAdapter):
244244
def __init__(
245245
self,
246-
cert: Optional[Union[tuple[str], tuple[str, str], tuple[str, str, str]]] = None,
247-
verify: Optional[Union[str, bool]] = None,
248-
check_hostname: Optional[bool] = None,
249-
hostname_checks_common_name: Optional[bool] = None,
250-
max_retries: Union[int, Retry] = 0,
246+
cert: tuple[str] | tuple[str, str] | tuple[str, str, str] | None = None,
247+
verify: str | bool | None = None,
248+
check_hostname: bool | None = None,
249+
hostname_checks_common_name: bool | None = None,
250+
max_retries: int | Retry = 0,
251251
):
252252
self.cert = cert
253253
self.verify = verify
@@ -306,23 +306,23 @@ class MeshClient:
306306

307307
def __init__( # NOSONAR(S3776) # noqa: C901
308308
self, # NOSONAR(S107)
309-
url: Union[str, Endpoint],
309+
url: str | Endpoint,
310310
mailbox: str,
311311
password: str,
312-
shared_key: Optional[bytes] = None,
313-
cert: Optional[Union[tuple[str], tuple[str, str], tuple[str, str, str]]] = None,
314-
verify: Optional[Union[str, bool]] = None,
315-
check_hostname: Optional[bool] = None,
316-
hostname_checks_common_name: Optional[bool] = None,
312+
shared_key: bytes | None = None,
313+
cert: tuple[str] | tuple[str, str] | tuple[str, str, str] | None = None,
314+
verify: str | bool | None = None,
315+
check_hostname: bool | None = None,
316+
hostname_checks_common_name: bool | None = None,
317317
max_chunk_size=75 * 1024 * 1024,
318-
proxies: Optional[dict[str, str]] = None,
318+
proxies: dict[str, str] | None = None,
319319
transparent_compress: bool = False,
320-
max_retries: Union[int, Retry] = 3,
321-
retry_backoff_factor: Union[int, float] = 0.5,
320+
max_retries: int | Retry = 3,
321+
retry_backoff_factor: int | float = 0.5,
322322
retry_status_force_list: tuple[int, ...] = (425, 429, 502, 503, 504),
323323
retry_methods: tuple[str, ...] = ("HEAD", "GET", "PUT", "POST", "DELETE", "OPTIONS", "TRACE"),
324-
timeout: Union[int, float] = 10 * 60,
325-
application_name: Optional[str] = None,
324+
timeout: int | float = 10 * 60,
325+
application_name: str | None = None,
326326
):
327327
"""
328328
Create a new MeshClient.
@@ -388,7 +388,7 @@ def __init__( # NOSONAR(S3776) # noqa: C901
388388

389389
url_lower = self._url.lower()
390390

391-
self._retries: Union[int, Retry] = 0
391+
self._retries: int | Retry = 0
392392
if isinstance(max_retries, Retry):
393393
self._retries = max_retries
394394
elif max_retries:
@@ -498,16 +498,14 @@ def lookup_endpoint(self, ods_code: str, workflow_id: str) -> EndpointLookupResp
498498
response.raise_for_status()
499499
return cast(EndpointLookupResponse_v2, response.json())
500500

501-
def _inbox_v2_page(
502-
self, url: Optional[str] = None, params: Optional[dict[str, Any]] = None
503-
) -> ListMessageResponse_v2:
501+
def _inbox_v2_page(self, url: str | None = None, params: dict[str, Any] | None = None) -> ListMessageResponse_v2:
504502
url = url or f"{self.mailbox_url}/inbox"
505503
response = self._session.get(url, timeout=self._timeout, params=params)
506504
response.raise_for_status()
507505

508506
return cast(ListMessageResponse_v2, response.json())
509507

510-
def list_messages(self, max_results: Optional[int] = None, workflow_filter: Optional[str] = None) -> list[str]:
508+
def list_messages(self, max_results: int | None = None, workflow_filter: str | None = None) -> list[str]:
511509
"""
512510
lists messages ids in the inbox; note if workflow_filter is set it's possible to receive an empty page
513511
when more results exist outside the first max_results
@@ -521,7 +519,7 @@ def list_messages(self, max_results: Optional[int] = None, workflow_filter: Opti
521519
list[str]: message ids
522520
"""
523521

524-
params: dict[str, Union[str, int]] = {}
522+
params: dict[str, str | int] = {}
525523
if max_results:
526524
if max_results < 10:
527525
raise ValueError("if set max_results should be >= 10")
@@ -617,8 +615,8 @@ def send_chunk(
617615
chunk,
618616
chunk_num: int,
619617
total_chunks: int,
620-
compress: Optional[bool] = None,
621-
message_id: Optional[str] = None,
618+
compress: bool | None = None,
619+
message_id: str | None = None,
622620
**kwargs,
623621
) -> Response:
624622
"""
@@ -690,8 +688,8 @@ def send_message(
690688
self,
691689
recipient: str,
692690
data,
693-
max_chunk_size: Optional[int] = None,
694-
compress: Optional[bool] = None,
691+
max_chunk_size: int | None = None,
692+
compress: bool | None = None,
695693
**kwargs,
696694
) -> str:
697695
"""
@@ -779,7 +777,7 @@ def acknowledge_message(self, message_id: str):
779777
response.raise_for_status()
780778

781779
def iterate_message_ids(
782-
self, workflow_filter: Optional[str] = None, batch_size: Optional[int] = None
780+
self, workflow_filter: str | None = None, batch_size: int | None = None
783781
) -> Generator[str, None, None]:
784782
"""
785783
generator lists messages ids in the inbox;
@@ -793,7 +791,7 @@ def iterate_message_ids(
793791
Generator[str]: message ids
794792
"""
795793

796-
params: dict[str, Union[int, str]] = {}
794+
params: dict[str, int | str] = {}
797795
if batch_size:
798796
if batch_size < 10:
799797
raise ValueError("if set batch_size should be >= 10")
@@ -802,7 +800,7 @@ def iterate_message_ids(
802800
if workflow_filter:
803801
params["workflow_filter"] = workflow_filter
804802

805-
def _next_messages(page_result: ListMessageResponse_v2) -> tuple[Optional[str], list[str]]:
803+
def _next_messages(page_result: ListMessageResponse_v2) -> tuple[str | None, list[str]]:
806804
return cast(dict[str, str], page_result.get("links", {})).get("next"), cast(
807805
list[str], page_result.get("messages", [])
808806
)
@@ -815,7 +813,7 @@ def _next_messages(page_result: ListMessageResponse_v2) -> tuple[Optional[str],
815813
next_page, messages = _next_messages(result)
816814
yield from messages
817815

818-
def iterate_messages(self, workflow_filter: Optional[str] = None, batch_size: Optional[int] = None):
816+
def iterate_messages(self, workflow_filter: str | None = None, batch_size: int | None = None):
819817
"""
820818
generator lists messages ids in the inbox;
821819
Iterate over a list of Message objects for all messages in the user's
@@ -886,17 +884,17 @@ class _MessageAttrs:
886884
message_type: str
887885
recipient: str
888886
content_type: str
889-
sender: Optional[str] = None
887+
sender: str | None = None
890888

891-
workflow_id: Optional[str] = None
892-
filename: Optional[str] = None
893-
local_id: Optional[str] = None
894-
partner_id: Optional[str] = None
895-
chunk_range: Optional[str] = None
889+
workflow_id: str | None = None
890+
filename: str | None = None
891+
local_id: str | None = None
892+
partner_id: str | None = None
893+
chunk_range: str | None = None
896894

897-
subject: Optional[str] = None
898-
encrypted: Optional[Union[str, bool]] = None
899-
compressed: Optional[Union[str, bool]] = None
895+
subject: str | None = None
896+
encrypted: str | bool | None = None
897+
compressed: str | bool | None = None
900898

901899

902900
TDefault = TypeVar("TDefault")
@@ -1014,7 +1012,7 @@ def acknowledge(self):
10141012
"""
10151013
self._client.acknowledge_message(self._msg_id)
10161014

1017-
def mex_header(self, key: str, default: Optional[TDefault] = None) -> Union[str, Optional[TDefault]]:
1015+
def mex_header(self, key: str, default: TDefault | None = None) -> str | TDefault | None:
10181016
"""get a mex header if present
10191017
10201018
Args:

0 commit comments

Comments
 (0)