Skip to content

Commit e8a72cf

Browse files
committed
fix: Python 3.13 changes to typing
1 parent 3aaf800 commit e8a72cf

File tree

10 files changed

+99
-40
lines changed

10 files changed

+99
-40
lines changed

.devcontainer/devcontainer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/python
3+
{
4+
"name": "Python 3",
5+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6+
// "image": "mcr.microsoft.com/devcontainers/python:3.9-bookworm",
7+
// "image": "mcr.microsoft.com/devcontainers/python:3.10-bookworm",
8+
// "image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
9+
// "image": "mcr.microsoft.com/devcontainers/python:3.12-bookworm",
10+
// "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
11+
// "image": "mcr.microsoft.com/devcontainers/python:3.13-bookworm",
12+
"image": "mcr.microsoft.com/devcontainers/python:3.13-bullseye",
13+
14+
"features": {
15+
"ghcr.io/hspaans/devcontainer-features/pytest:1": {},
16+
"ghcr.io/devcontainers-extra/features/pylint:2": {},
17+
"ghcr.io/devcontainers-extra/features/poetry:2": {}
18+
},
19+
20+
// Features to add to the dev container. More info: https://containers.dev/features.
21+
// "features": {},
22+
23+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
24+
// "forwardPorts": [],
25+
26+
// Use 'postCreateCommand' to run commands after the container is created.
27+
"postCreateCommand": "git config --global core.autocrlf true && pip3 install --user -r requirements-dev.txt",
28+
29+
// Configure tool-specific properties.
30+
"customizations": {
31+
"vscode": {
32+
"extensions": ["ms-python.python"]
33+
}
34+
}
35+
36+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
37+
// "remoteUser": "root"
38+
}

.vscode/settings.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
{
2-
"editor.formatOnSave": true
2+
"editor.formatOnSave": true,
3+
"python.testing.pytestArgs": [
4+
"tests"
5+
],
6+
"python.testing.unittestEnabled": false,
7+
"python.testing.pytestEnabled": true
38
}

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pycparser==2.22
7474

7575
pyjwt[crypto]==2.9.0 ; python_version >= '3.7'
7676

77-
pylint==3.2.7
77+
pylint==3.3.3
7878

7979
pyproject-hooks==1.2.0 ; python_version >= '3.7'
8080

src/msgraph_core/models/large_file_upload_session.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from __future__ import annotations
22

33
import datetime
4-
from collections.abc import Callable
54
from dataclasses import dataclass, field
6-
from typing import Any, Optional
5+
from typing import Any, Callable, Optional
76

87
from kiota_abstractions.serialization import (
98
AdditionalDataHolder,

src/msgraph_core/models/page_result.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
"""
1212
from __future__ import annotations
1313

14-
from collections.abc import Callable
1514
from dataclasses import dataclass
16-
from typing import Optional, TypeVar
15+
from typing import Callable, Optional, TypeVar
1716

1817
from kiota_abstractions.serialization.parsable import Parsable
1918
from kiota_abstractions.serialization.parse_node import ParseNode

src/msgraph_core/models/upload_result.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from collections.abc import Callable
21
from dataclasses import dataclass
32
from datetime import datetime
4-
from typing import Any, Generic, Optional, TypeVar
3+
from typing import Any, Callable, Generic, Optional, TypeVar
54

65
from kiota_abstractions.serialization import (
76
AdditionalDataHolder,

src/msgraph_core/requests/batch_response_content.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import base64
2-
from collections.abc import Callable
32
from io import BytesIO
4-
from typing import Optional, Type, TypeVar, Union
3+
from typing import Callable, Optional, Type, TypeVar, Union
54

65
from kiota_abstractions.serialization import (
76
Parsable,
@@ -108,17 +107,20 @@ def response_body(self, request_id: str, type: Type[T]) -> Optional[T]:
108107
raise ValueError(f"No response found for id: {request_id}")
109108

110109
if not issubclass(type, Parsable):
111-
raise ValueError("Type passed must implement the Parsable interface")
110+
raise ValueError(
111+
"Type passed must implement the Parsable interface")
112112

113113
response = self.get_response_by_id(request_id)
114114
if response is not None:
115115
content_type = response.content_type
116116
else:
117117
raise ValueError(
118-
f"Unable to get content-type header in response item for request Id: {request_id}"
118+
f"Unable to get content-type header in response item for request Id: {
119+
request_id}"
119120
)
120121
if not content_type:
121-
raise RuntimeError("Unable to get content-type header in response item")
122+
raise RuntimeError(
123+
"Unable to get content-type header in response item")
122124

123125
response_body = response.body or BytesIO()
124126
try:
@@ -128,15 +130,17 @@ def response_body(self, request_id: str, type: Type[T]) -> Optional[T]:
128130
)
129131
except Exception:
130132
response_body.seek(0)
131-
base64_decoded_body = BytesIO(base64.b64decode(response_body.read()))
133+
base64_decoded_body = BytesIO(
134+
base64.b64decode(response_body.read()))
132135
parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
133136
content_type, base64_decoded_body
134137
)
135138
response.body = base64_decoded_body
136139
return parse_node.get_object_value(type)
137140
except Exception:
138141
raise ValueError(
139-
f"Unable to deserialize batch response for request Id: {request_id} to {type}"
142+
f"Unable to deserialize batch response for request Id: {
143+
request_id} to {type}"
140144
)
141145

142146
def get_field_deserializers(self) -> dict[str, Callable[[ParseNode], None]]:
@@ -161,7 +165,8 @@ def serialize(self, writer: SerializationWriter) -> None:
161165
:param writer: The writer to write to
162166
"""
163167
if self._responses is not None:
164-
writer.write_collection_of_object_values('responses', list(self._responses.values()))
168+
writer.write_collection_of_object_values(
169+
'responses', list(self._responses.values()))
165170
else:
166171
writer.write_collection_of_object_values('responses', [])
167172

src/msgraph_core/requests/batch_response_content_collection.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from collections.abc import Callable
2-
1+
from typing import Callable
32
from kiota_abstractions.serialization import Parsable, ParseNode, SerializationWriter
43

54
from .batch_response_content import BatchResponseContent
@@ -52,7 +51,8 @@ async def responses_status_codes(self) -> dict[str, int]:
5251
else:
5352
raise ValueError("Response ID cannot be None")
5453
else:
55-
raise TypeError("Invalid type: Collection must be of type BatchResponseContent")
54+
raise TypeError(
55+
"Invalid type: Collection must be of type BatchResponseContent")
5656
return status_codes
5757

5858
def get_field_deserializers(self) -> dict[str, Callable[[ParseNode], None]]:
@@ -65,7 +65,8 @@ def get_field_deserializers(self) -> dict[str, Callable[[ParseNode], None]]:
6565
return {
6666
'responses':
6767
lambda n:
68-
setattr(self, "_responses", n.get_collection_of_object_values(BatchResponseItem))
68+
setattr(self, "_responses",
69+
n.get_collection_of_object_values(BatchResponseItem))
6970
}
7071

7172
def serialize(self, writer: SerializationWriter) -> None:

src/msgraph_core/tasks/large_file_upload.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import logging
22
import os
33
from asyncio import Future
4-
from collections.abc import Callable
54
from datetime import datetime, timedelta, timezone
65
from io import BytesIO
7-
from typing import Any, Optional, Tuple, TypeVar, Union
6+
from typing import Any, Callable, Optional, Tuple, TypeVar, Union
87

98
from kiota_abstractions.headers_collection import HeadersCollection
109
from kiota_abstractions.method import Method
@@ -38,11 +37,13 @@ def __init__(
3837
self.max_chunk_size = max_chunk_size
3938
self.factory = parsable_factory
4039
cleaned_value = self.check_value_exists(
41-
upload_session, 'get_next_expected_range', ['next_expected_range', 'NextExpectedRange']
40+
upload_session, 'get_next_expected_range', [
41+
'next_expected_range', 'NextExpectedRange']
4242
)
4343
self.next_range = cleaned_value[0]
4444
self._chunks = int((self.file_size / max_chunk_size) + 0.5)
45-
self.on_chunk_upload_complete: Optional[Callable[[list[int]], None]] = None
45+
self.on_chunk_upload_complete: Optional[Callable[[
46+
list[int]], None]] = None
4647

4748
@property
4849
def upload_session(self):
@@ -68,7 +69,8 @@ def upload_session_expired(self, upload_session: Optional[Parsable] = None) -> b
6869
now = datetime.now(timezone.utc)
6970
upload_session = upload_session or self.upload_session
7071
if not hasattr(upload_session, "expiration_date_time"):
71-
raise ValueError("Upload session does not have an expiration date time")
72+
raise ValueError(
73+
"Upload session does not have an expiration date time")
7274
expiry = getattr(upload_session, 'expiration_date_time')
7375
if expiry is None:
7476
raise ValueError("Expiry is None")
@@ -93,13 +95,16 @@ async def upload(self, after_chunk_upload: Optional[Callable] = None):
9395

9496
self.on_chunk_upload_complete = after_chunk_upload or self.on_chunk_upload_complete
9597
session: LargeFileUploadSession = await self.next_chunk(
96-
self.stream, 0, max(0, min(self.max_chunk_size - 1, self.file_size - 1))
98+
self.stream, 0, max(
99+
0, min(self.max_chunk_size - 1, self.file_size - 1))
97100
)
98101
process_next = session
99102
# determine the range to be uploaded
100103
# even when resuming existing upload sessions.
101-
range_parts = self.next_range[0].split("-") if self.next_range else ['0', '0']
102-
end = min(int(range_parts[0]) + self.max_chunk_size - 1, self.file_size)
104+
range_parts = self.next_range[0].split(
105+
"-") if self.next_range else ['0', '0']
106+
end = min(int(range_parts[0]) +
107+
self.max_chunk_size - 1, self.file_size)
103108
uploaded_range = [range_parts[0], end]
104109
response = None
105110

@@ -124,12 +129,13 @@ async def upload(self, after_chunk_upload: Optional[Callable] = None):
124129
if not next_range:
125130
continue
126131
range_parts = str(next_range[0]).split("-")
127-
end = min(int(range_parts[0]) + self.max_chunk_size, self.file_size)
132+
end = min(int(range_parts[0]) +
133+
self.max_chunk_size, self.file_size)
128134
uploaded_range = [range_parts[0], end]
129135
self.next_range = next_range[0] + "-"
130136
process_next = await self.next_chunk(self.stream)
131137

132-
except Exception as error: #pylint: disable=broad-except
138+
except Exception as error: # pylint: disable=broad-except
133139
logging.error("Error uploading chunk %s", error)
134140
finally:
135141
self.chunks -= 1
@@ -176,7 +182,8 @@ async def next_chunk(
176182
chunk_data = file.read(end - start + 1)
177183
info.headers = HeadersCollection()
178184

179-
info.headers.try_add('Content-Range', f'bytes {start}-{end}/{self.file_size}')
185+
info.headers.try_add(
186+
'Content-Range', f'bytes {start}-{end}/{self.file_size}')
180187
info.headers.try_add('Content-Length', str(len(chunk_data)))
181188
info.headers.try_add("Content-Type", "application/octet-stream")
182189
info.set_stream_content(bytes(chunk_data))
@@ -216,7 +223,8 @@ async def last_chunk(
216223
chunk_data = file.read(end - start + 1)
217224
info.headers = HeadersCollection()
218225

219-
info.headers.try_add('Content-Range', f'bytes {start}-{end}/{self.file_size}')
226+
info.headers.try_add(
227+
'Content-Range', f'bytes {start}-{end}/{self.file_size}')
220228
info.headers.try_add('Content-Length', str(len(chunk_data)))
221229
info.headers.try_add("Content-Type", "application/octet-stream")
222230
info.set_stream_content(bytes(chunk_data))
@@ -231,7 +239,8 @@ def get_file(self) -> BytesIO:
231239

232240
async def cancel(self) -> Parsable:
233241
upload_url = self.get_validated_upload_url(self.upload_session)
234-
request_information = RequestInformation(method=Method.DELETE, url_template=upload_url)
242+
request_information = RequestInformation(
243+
method=Method.DELETE, url_template=upload_url)
235244

236245
await self.request_adapter.send_no_response_content_async(request_information)
237246

@@ -254,7 +263,8 @@ def additional_data_contains(self, parsable: Parsable,
254263
'AdditionalDataHolder'
255264
)
256265
if not hasattr(parsable, 'additional_data'):
257-
raise ValueError('The object passed does not contain an additional_data property')
266+
raise ValueError(
267+
'The object passed does not contain an additional_data property')
258268
additional_data = parsable.additional_data
259269
for property_candidate in property_candidates:
260270
if property_candidate in additional_data:
@@ -298,7 +308,8 @@ async def resume(self) -> Future:
298308

299309
def get_validated_upload_url(self, upload_session: Parsable) -> str:
300310
if not hasattr(upload_session, 'upload_url'):
301-
raise RuntimeError('The upload session does not contain a valid upload url')
311+
raise RuntimeError(
312+
'The upload session does not contain a valid upload url')
302313
result = upload_session.upload_url
303314

304315
if result is None or result.strip() == '':

src/msgraph_core/tasks/page_iterator.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
and models modules.
1818
"""
1919

20-
from collections.abc import Callable
21-
from typing import Optional, Type, TypeVar, Union
20+
from typing import Callable, Optional, Type, TypeVar, Union
2221

2322
from kiota_abstractions.headers_collection import HeadersCollection
2423
from kiota_abstractions.method import Method
@@ -152,7 +151,8 @@ async def next(self) -> Optional[PageResult]:
152151
next_link = response.odata_next_link if response and hasattr(
153152
response, 'odata_next_link'
154153
) else None
155-
value = response.value if response and hasattr(response, 'value') else None
154+
value = response.value if response and hasattr(
155+
response, 'value') else None
156156
return PageResult(next_link, value)
157157

158158
@staticmethod
@@ -180,7 +180,8 @@ def convert_to_page(response: Union[T, list, object]) -> PageResult:
180180
value = getattr(response, 'value', [])
181181
if value is None:
182182
raise ValueError('The response does not contain a value.')
183-
parsable_page = response if isinstance(response, dict) else vars(response)
183+
parsable_page = response if isinstance(
184+
response, dict) else vars(response)
184185
next_link = parsable_page.get('odata_next_link', '') if isinstance(
185186
parsable_page, dict
186187
) else getattr(parsable_page, 'odata_next_link', '')
@@ -230,7 +231,8 @@ def enumerate(self, callback: Optional[Callable] = None) -> bool:
230231
if not page_items:
231232
return False
232233
for i in range(self.pause_index, len(page_items)):
233-
keep_iterating = callback(page_items[i]) if callback is not None else True
234+
keep_iterating = callback(
235+
page_items[i]) if callback is not None else True
234236
if not keep_iterating:
235237
self.pause_index = i + 1
236238
break

0 commit comments

Comments
 (0)