diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 35f8f67a..086b6949 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.3.4" + ".": "1.3.5" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 96588c85..4fbd2bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [1.3.5](https://github.com/microsoftgraph/msgraph-sdk-python-core/compare/v1.3.4...v1.3.5) (2025-06-27) + + +### Bug Fixes + +* BatchRequestItem now properly serializes the header and body fields ([7b5ad11](https://github.com/microsoftgraph/msgraph-sdk-python-core/commit/7b5ad1130f16438579e160aaea1d3aae000b96b6)) +* GraphClientFactory baseUrl setting logic ([5377bba](https://github.com/microsoftgraph/msgraph-sdk-python-core/commit/5377bbafdfbd68d9467bf12c1ccdb40e13c4e374)) + ## [1.3.4](https://github.com/microsoftgraph/msgraph-sdk-python-core/compare/v1.3.3...v1.3.4) (2025-06-02) diff --git a/pyproject.toml b/pyproject.toml index a2a1b4c1..ac1ece82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "msgraph-core" # The SDK version # x-release-please-start-version -version = "1.3.4" +version = "1.3.5" # x-release-please-end authors = [{name = "Microsoft", email = "graphtooling+python@microsoft.com"}] description = "Core component of the Microsoft Graph Python SDK" diff --git a/requirements-dev.txt b/requirements-dev.txt index 7aec90ac..985fc00d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,7 +14,7 @@ build==1.2.2.post1 bumpver==2024.1130 -certifi==2025.4.26 ; python_version >= '3.6' +certifi==2025.6.15 ; python_version >= '3.6' cffi==1.17.1 ; os_name == 'nt' and implementation_name != 'pypy' @@ -24,9 +24,9 @@ click==8.1.8 ; python_version >= '3.6' colorama==0.4.6 ; os_name == 'nt' -coverage[toml]==7.8.2 ; python_version >= '3.7' +coverage[toml]==7.9.1 ; python_version >= '3.7' -cryptography==45.0.3 ; python_version >= '3.7' +cryptography==45.0.4 ; python_version >= '3.7' dill==0.4.0 ; python_version < '3.11' @@ -54,7 +54,7 @@ msal==1.32.3 msal-extensions==1.3.1 -mypy==1.16.0 +mypy==1.16.1 mypy-extensions==1.1.0 ; python_version >= '3.5' @@ -78,21 +78,21 @@ pylint==3.3.7 pyproject-hooks==1.2.0 ; python_version >= '3.7' -pytest==8.3.5 +pytest==8.4.1 pytest-cov==5.0.0 pytest-mock==3.14.1 -python-dotenv==1.1.0 +python-dotenv==1.1.1 pytest-trio==0.8.0 -pytest-asyncio==0.26.0 +pytest-asyncio==1.0.0 pywin32==310 ; platform_system == 'Windows' -requests==2.32.3 ; python_version >= '3.7' +requests==2.32.4 ; python_version >= '3.7' setuptools==80.9.0 @@ -106,24 +106,24 @@ toml==0.10.2 tomli==2.2.1 ; python_version < '3.11' -tomlkit==0.13.2 ; python_version >= '3.7' +tomlkit==0.13.3 ; python_version >= '3.7' trio==0.30.0 types-python-dateutil==2.9.0.20250516 -types-requests==2.32.0.20250602; python_version >= '3.7' -urllib3==2.4.0 ; python_version >= '3.7' -typing-extensions==4.13.2 ; python_version >= '3.7' +types-requests==2.32.4.20250611; python_version >= '3.7' +urllib3==2.5.0 ; python_version >= '3.7' +typing-extensions==4.14.0 ; python_version >= '3.7' wrapt==1.17.2 ; python_version < '3.11' yapf==0.43.0 -zipp==3.22.0 ; python_version >= '3.7' +zipp==3.23.0 ; python_version >= '3.7' -aiohttp==3.12.6 ; python_version >= '3.6' +aiohttp==3.12.13 ; python_version >= '3.6' aiosignal==1.3.1 ; python_version >= '3.7' @@ -131,7 +131,7 @@ anyio==4.9.0 ; python_version >= '3.7' async-timeout==5.0.1 ; python_version >= '3.6' -frozenlist==1.6.0 ; python_version >= '3.7' +frozenlist==1.7.0 ; python_version >= '3.7' h11==0.16.0 ; python_version >= '3.7' @@ -151,11 +151,13 @@ microsoft-kiota-authentication-azure==1.9.3 microsoft-kiota-http==1.9.3 -multidict==6.4.4 ; python_version >= '3.7' +microsoft-kiota-serialization-json==1.9.3 -uritemplate==4.1.1 ; python_version >= '3.6' +multidict==6.5.1 ; python_version >= '3.7' -yarl==1.20.0 ; python_version >= '3.7' +uritemplate==4.2.0 ; python_version >= '3.6' + +yarl==1.20.1 ; python_version >= '3.7' deprecated==1.2.18 diff --git a/src/msgraph_core/_constants.py b/src/msgraph_core/_constants.py index fb7ad47d..177ea952 100644 --- a/src/msgraph_core/_constants.py +++ b/src/msgraph_core/_constants.py @@ -10,6 +10,6 @@ DEFAULT_CONNECTION_TIMEOUT = 30 # The SDK version # x-release-please-start-version -SDK_VERSION = '1.3.4' +SDK_VERSION = '1.3.5' # x-release-please-end MS_DEFAULT_SCOPE = 'https://graph.microsoft.com/.default' diff --git a/src/msgraph_core/graph_client_factory.py b/src/msgraph_core/graph_client_factory.py index 968b53fa..f4079b63 100644 --- a/src/msgraph_core/graph_client_factory.py +++ b/src/msgraph_core/graph_client_factory.py @@ -16,14 +16,13 @@ from .middleware.options import GraphTelemetryHandlerOption -class GraphClientFactory(KiotaClientFactory): +class GraphClientFactory(): """Constructs httpx.AsyncClient instances configured with either custom or default pipeline of graph specific middleware. """ @staticmethod - def create_with_default_middleware( # type: ignore - # Breaking change to remove KiotaClientFactory as base class + def create_with_default_middleware( api_version: APIVersion = APIVersion.v1, client: Optional[httpx.AsyncClient] = None, host: NationalClouds = NationalClouds.Global, @@ -35,10 +34,13 @@ def create_with_default_middleware( # type: ignore Args: api_version (APIVersion): The Graph API version to be used. Defaults to APIVersion.v1. - client (httpx.AsyncClient): The httpx.AsyncClient instance to be used. - Defaults to KiotaClientFactory.get_default_client(). + This is only used if the client parameter is None. + client (Optional[httpx.AsyncClient]]): The httpx.AsyncClient instance to be used. + Defaults to None. + When None, a client will be created with base url set to https://{host}/{api_version}. host (NationalClouds): The national clound endpoint to be used. Defaults to NationalClouds.Global. + This is only used if the client parameter is None. options (Optional[dict[str, RequestOption]]): The request options to use when instantiating default middleware. Defaults to dict[str, RequestOption]=None. @@ -47,15 +49,15 @@ def create_with_default_middleware( # type: ignore """ if client is None: client = KiotaClientFactory.get_default_client() - client.base_url = GraphClientFactory._get_base_url(host, api_version) # type: ignore + client.base_url = GraphClientFactory._get_base_url(host, api_version) + middleware = KiotaClientFactory.get_default_middleware(options) telemetry_handler = GraphClientFactory._get_telemetry_handler(options) middleware.append(telemetry_handler) return GraphClientFactory._load_middleware_to_client(client, middleware) @staticmethod - def create_with_custom_middleware( # type: ignore - # Breaking change to remove Kiota client factory as base class + def create_with_custom_middleware( middleware: Optional[list[BaseMiddleware]], api_version: APIVersion = APIVersion.v1, client: Optional[httpx.AsyncClient] = None, @@ -64,19 +66,24 @@ def create_with_custom_middleware( # type: ignore """Applies a custom middleware chain to the HTTP Client Args: - middleware(list[BaseMiddleware]): Custom middleware list that will be used to create - a middleware pipeline. The middleware should be arranged in the order in which they will - modify the request. - api_version (APIVersion): The Graph API version to be used. + middleware(Optional[list[BaseMiddleware]]): Custom middleware list that will be used to + create a middleware pipeline. The middleware should be arranged in the order in which + they will modify the request. + Defaults to None, + api_version (APIVersion): The Graph API version to be used. Defaults to APIVersion.v1. - client (httpx.AsyncClient): The httpx.AsyncClient instance to be used. - Defaults to KiotaClientFactory.get_default_client(). - host (NationalClouds): The national clound endpoint to be used. + This is only used if the client parameter is None. + client (Optional[httpx.AsyncClient]): The httpx.AsyncClient instance to be used. + Defaults to None. + When None, a client will be created with base url set to https://{host}/{api_version}. + host (NationalClouds): The national cloud endpoint to be used. Defaults to NationalClouds.Global. + This is only used if the client parameter is None. """ if client is None: client = KiotaClientFactory.get_default_client() - client.base_url = GraphClientFactory._get_base_url(host, api_version) # type: ignore + client.base_url = GraphClientFactory._get_base_url(host, api_version) + return GraphClientFactory._load_middleware_to_client(client, middleware) @staticmethod diff --git a/src/msgraph_core/requests/batch_request_item.py b/src/msgraph_core/requests/batch_request_item.py index d7818736..957d684b 100644 --- a/src/msgraph_core/requests/batch_request_item.py +++ b/src/msgraph_core/requests/batch_request_item.py @@ -3,11 +3,11 @@ import json import re import urllib.request -from deprecated import deprecated from io import BytesIO from typing import Any, Optional, Union from urllib.parse import urlparse from uuid import uuid4 +from deprecated import deprecated from kiota_abstractions.headers_collection import HeadersCollection as RequestHeaders from kiota_abstractions.method import Method @@ -258,19 +258,16 @@ def serialize(self, writer: SerializationWriter) -> None: writer.write_str_value('method', self.method) writer.write_str_value('url', self.url) writer.write_collection_of_primitive_values('depends_on', self._depends_on) - writer.write_collection_of_object_values( - 'headers', - self._headers # type: ignore # need method to serialize dicts + writer.write_additional_data_value( + {'headers': self._headers} # need proper method to serialize dicts ) if self._body: json_object = json.loads(self._body) is_json_string = json_object and isinstance(json_object, dict) # /$batch API expects JSON object or base 64 encoded value for the body if is_json_string: - writer.write_collection_of_object_values( # type: ignore - # need method to serialize dicts - 'body', - json_object + writer.write_additional_data_value( + {'body': json_object} # need proper method to serialize dicts ) else: writer.write_str_value('body', base64.b64encode(self._body).decode('utf-8')) diff --git a/tests/requests/test_batch_request_item.py b/tests/requests/test_batch_request_item.py index 1f3dd971..433cce09 100644 --- a/tests/requests/test_batch_request_item.py +++ b/tests/requests/test_batch_request_item.py @@ -1,9 +1,13 @@ import pytest +import json from io import BytesIO +from unittest.mock import Mock from urllib.request import Request from kiota_abstractions.request_information import RequestInformation +from kiota_abstractions.serialization.serialization_writer import SerializationWriter from kiota_abstractions.method import Method from kiota_abstractions.headers_collection import HeadersCollection as RequestHeaders +from kiota_serialization_json.json_serialization_writer_factory import JsonSerializationWriterFactory from msgraph_core.requests.batch_request_item import BatchRequestItem base_url = "https://graph.microsoft.com/v1.0/me" @@ -11,23 +15,26 @@ @pytest.fixture def request_info(): - request_info = RequestInformation() + request_info = RequestInformation() request_info.http_method = "GET" - request_info.url = "f{base_url}/me" + request_info.url = base_url request_info.headers = RequestHeaders() - request_info.content = BytesIO(b'{"key": "value"}') + request_info.headers.add("Content-Type", "application/json") + request_info.content = b'{"key": "value"}' return request_info @pytest.fixture def batch_request_item(request_info): - return BatchRequestItem(request_information=request_info) + return BatchRequestItem(request_information=request_info, id="123") def test_initialization(batch_request_item, request_info): + assert batch_request_item.id == "123" assert batch_request_item.method == "GET" - assert batch_request_item.url == "f{base_url}/me" - assert batch_request_item.body.read() == b'{"key": "value"}' + assert batch_request_item.url == base_url + assert batch_request_item.headers == {"content-type": "application/json"} + assert batch_request_item.body == b'{"key": "value"}' def test_create_with_urllib_request(): @@ -123,3 +130,15 @@ def test_batch_request_item_method_enum(): def test_depends_on_property(batch_request_item): batch_request_item.set_depends_on(["request1", "request2"]) assert batch_request_item.depends_on == ["request1", "request2"] + + +def test_serialize_json(batch_request_item): + writer = JsonSerializationWriterFactory().get_serialization_writer('application/json') + batch_request_item.serialize(writer) + content = json.loads(writer.get_serialized_content()) + assert content["id"] == "123" + assert content["method"] == "GET" + assert content["url"] == base_url + assert content["headers"] == {"content-type": "application/json"} + assert content["body"] == {"key": "value"} +