From 3d085fa767f993a3c0ac6255eaa83f5252d56d8f Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Fri, 7 Nov 2025 14:45:00 +0530 Subject: [PATCH 01/13] Initial changes to enable python 3.14 support - adjusted requirements --- sdk/ml/azure-ai-ml/dev_requirements.txt | 4 ++-- sdk/ml/azure-ai-ml/setup.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/ml/azure-ai-ml/dev_requirements.txt b/sdk/ml/azure-ai-ml/dev_requirements.txt index 7547f90c9b86..eedda6cf8025 100644 --- a/sdk/ml/azure-ai-ml/dev_requirements.txt +++ b/sdk/ml/azure-ai-ml/dev_requirements.txt @@ -12,10 +12,10 @@ pytest-mock pytest pydash azure-mgmt-msi -pywin32==306 ; sys_platform == 'win32' +pywin32==311 ; sys_platform == 'win32' docker;platform.python_implementation!="PyPy" numpy;platform.python_implementation!="PyPy" -scikit-image;platform.python_implementation!="PyPy" +scikit-image;platform.python_implementation!="PyPy" and python_version < "3.14" mldesigner azure-mgmt-resourcegraph<9.0.0,>=2.0.0 azure-mgmt-resource<23.0.0,>=3.0.0 diff --git a/sdk/ml/azure-ai-ml/setup.py b/sdk/ml/azure-ai-ml/setup.py index 83c0d47fe922..7a0f31bc4246 100644 --- a/sdk/ml/azure-ai-ml/setup.py +++ b/sdk/ml/azure-ai-ml/setup.py @@ -53,6 +53,7 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: MIT License", ], zip_safe=False, From d3d28bdbf4a11e266eaf5b3d1a9f407a174d6dc9 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Mon, 17 Nov 2025 14:14:17 +0530 Subject: [PATCH 02/13] Fix test_equality - Python 3.14 no longer allows NotImplemented to be used in boolean contexts --- .../unittests/test_endpoint_entity.py | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/batch_online_common/unittests/test_endpoint_entity.py b/sdk/ml/azure-ai-ml/tests/batch_online_common/unittests/test_endpoint_entity.py index e816ba4f8ff0..d56438f8ca28 100644 --- a/sdk/ml/azure-ai-ml/tests/batch_online_common/unittests/test_endpoint_entity.py +++ b/sdk/ml/azure-ai-ml/tests/batch_online_common/unittests/test_endpoint_entity.py @@ -1,23 +1,23 @@ -import pytest -import yaml -import json import copy +import json import sys + +import pytest +import yaml from test_utilities.utils import verify_entity_load_and_dump -from azure.ai.ml._restclient.v2022_02_01_preview.models import ( - OnlineEndpointData, - EndpointAuthKeys as RestEndpointAuthKeys, - EndpointAuthToken as RestEndpointAuthToken, -) -from azure.ai.ml._restclient.v2023_10_01.models import BatchEndpoint as BatchEndpointData + from azure.ai.ml import load_batch_endpoint, load_online_endpoint +from azure.ai.ml._restclient.v2022_02_01_preview.models import EndpointAuthKeys as RestEndpointAuthKeys +from azure.ai.ml._restclient.v2022_02_01_preview.models import EndpointAuthToken as RestEndpointAuthToken +from azure.ai.ml._restclient.v2022_02_01_preview.models import OnlineEndpointData +from azure.ai.ml._restclient.v2023_10_01.models import BatchEndpoint as BatchEndpointData from azure.ai.ml.entities import ( BatchEndpoint, - ManagedOnlineEndpoint, - KubernetesOnlineEndpoint, - OnlineEndpoint, EndpointAuthKeys, EndpointAuthToken, + KubernetesOnlineEndpoint, + ManagedOnlineEndpoint, + OnlineEndpoint, ) from azure.ai.ml.exceptions import ValidationException @@ -332,15 +332,12 @@ def test_dump(self) -> None: assert online_endpoint_dict["identity"]["type"] == online_endpoint.identity.type assert online_endpoint_dict["traffic"] == online_endpoint.traffic - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_equality(self) -> None: online_endpoint = load_online_endpoint(TestManagedOnlineEndpoint.ONLINE_ENDPOINT) batch_online_endpoint = load_batch_endpoint(TestManagedOnlineEndpoint.BATCH_ENDPOINT_WITH_BLUE) - assert online_endpoint.__eq__(None) - assert online_endpoint.__eq__(batch_online_endpoint) + assert online_endpoint.__eq__(None) is NotImplemented + assert online_endpoint.__eq__(batch_online_endpoint) is NotImplemented other_online_endpoint = copy.deepcopy(online_endpoint) assert online_endpoint == other_online_endpoint From 9e0ed292b2bb220e947ba61154124e5126c0717f Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Mon, 17 Nov 2025 14:41:38 +0530 Subject: [PATCH 03/13] Unskip test in test_data_utils - Python 3.13+ enforces stricter context manager protocol for mocked objects, updated the mocks to fix it --- .../tests/dataset/unittests/test_data_utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/dataset/unittests/test_data_utils.py b/sdk/ml/azure-ai-ml/tests/dataset/unittests/test_data_utils.py index 9f6d20862d35..339d54cb7512 100644 --- a/sdk/ml/azure-ai-ml/tests/dataset/unittests/test_data_utils.py +++ b/sdk/ml/azure-ai-ml/tests/dataset/unittests/test_data_utils.py @@ -1,7 +1,7 @@ +import sys from collections import OrderedDict from pathlib import Path from unittest.mock import Mock, patch -import sys import pytest @@ -97,10 +97,6 @@ def test_read_mltable_metadata_contents( read_local_mltable_metadata_contents(path=mltable_folder / "should-fail") assert "No such file or directory" in str(ex) - @pytest.mark.skipif( - sys.version_info >= (3, 13), - reason="Failing in spacific use case of TemporaryDirectory in Python 3.13 in test case only, skipping the test for now.", - ) @patch("azure.ai.ml._utils._data_utils.get_datastore_info") @patch("azure.ai.ml._utils._data_utils.get_storage_client") def test_read_remote_mltable_metadata_contents( @@ -126,7 +122,9 @@ def test_read_remote_mltable_metadata_contents( tmp_metadata_file.write_text(file_contents) # remote azureml accessible - with patch("azure.ai.ml._utils._data_utils.TemporaryDirectory", return_value=mltable_folder): + with patch("azure.ai.ml._utils._data_utils.TemporaryDirectory") as mock_tmp: + mock_tmp.return_value.__enter__.return_value = str(mltable_folder) + mock_tmp.return_value.__exit__.return_value = None contents = read_remote_mltable_metadata_contents( datastore_operations=mock_datastore_operations, base_uri="azureml://datastores/mydatastore/paths/images/dogs", From 485b77ec7f596f97cb334a947a9a29137b523d5b Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Mon, 17 Nov 2025 15:49:16 +0530 Subject: [PATCH 04/13] Fix DistillationJob.__eq__ logic and unskip test in test_distillation_conversion - Python 3.13+ no longer allows NotImplemented to be used in boolean contexts --- .../entities/_job/distillation/distillation_job.py | 12 ++++++++++-- .../unittests/test_distillation_conversion.py | 6 ++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py b/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py index 469fde98a8c0..87953efb1221 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py @@ -517,9 +517,17 @@ def __eq__(self, other: object) -> bool: """ if not isinstance(other, DistillationJob): return False + parent_eq = super().__eq__(other) + + if parent_eq is NotImplemented: + # Parent doesn't implement comparison, we'll continue comparison on our end + pass + elif not parent_eq: + # Parent says objects are not equal + return False + return ( - super().__eq__(other) - and self.data_generation_type == other.data_generation_type + self.data_generation_type == other.data_generation_type and self.data_generation_task_type == other.data_generation_task_type and self.teacher_model_endpoint_connection.name == other.teacher_model_endpoint_connection.name and self.student_model == other.student_model diff --git a/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py b/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py index d52c882a13b0..ac06a7564dc8 100644 --- a/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py +++ b/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py @@ -1,6 +1,7 @@ -import pytest import sys +import pytest + from azure.ai.ml._restclient.v2024_01_01_preview.models import MLFlowModelJobInput, UriFileJobInput from azure.ai.ml.constants import DataGenerationTaskType, DataGenerationType from azure.ai.ml.constants._common import AssetTypes @@ -14,9 +15,6 @@ from azure.ai.ml.entities._workspace.connections.workspace_connection import WorkspaceConnection -@pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" -) class TestDistillationJobConversion: @pytest.mark.parametrize( "data_generation_task_type", From 4832c01bcea10b176060f9a8479b49acb3f1ee05 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 19 Nov 2025 10:54:58 +0530 Subject: [PATCH 05/13] Replace help() with inspect.signature() in test_dsl_group - making the test more robust and independent of python version --- .../tests/dsl/unittests/test_dsl_group.py | 72 +++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_dsl_group.py b/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_dsl_group.py index 9e6c3edc1eb7..6295d15caff6 100644 --- a/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_dsl_group.py +++ b/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_dsl_group.py @@ -1,3 +1,4 @@ +import inspect import sys from enum import Enum as PyEnum from io import StringIO @@ -71,9 +72,6 @@ def test_restore_flattened_inputs(self) -> None: assert isinstance(result.ab, _GroupAttrDict) assert isinstance(result.ab.c, PipelineInput) - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_auto_generated_functions(self) -> None: class EnumOps(PyEnum): Option1 = "Option1" @@ -89,15 +87,27 @@ class MixedGroup: # __init__ func test assert hasattr(MixedGroup, "__init__") is True - original_out = sys.stdout - sys.stdout = stdout_str_IO = StringIO() - help(MixedGroup.__init__) - assert ( - "__init__(self,*,int_param:int=None,str_param:str=None,enum_param:str=None," - "str_default_param:str='test',optional_int_param:int=5)->None" - in remove_extra_character(stdout_str_IO.getvalue()) - ) - sys.stdout = original_out + sig = inspect.signature(MixedGroup.__init__) + params = sig.parameters + + # Verify all expected parameters exist + assert "self" in params + assert "int_param" in params + assert "str_param" in params + assert "enum_param" in params + assert "str_default_param" in params + assert "optional_int_param" in params + + # Verify default values + assert params["int_param"].default is None + assert params["str_param"].default is None + assert params["enum_param"].default is None + assert params["str_default_param"].default == "test" + assert params["optional_int_param"].default == 5 + + # Verify keyword-only parameters (after *) + assert params["int_param"].kind == inspect.Parameter.KEYWORD_ONLY + assert params["str_param"].kind == inspect.Parameter.KEYWORD_ONLY # __repr__ func test var = MixedGroup( @@ -415,9 +425,6 @@ def my_pipeline(my_inputs: PortInputs): assert "Only primitive types can be used as input of group, got uri_file" in str(e.value) - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_group_defaults_with_outputs(self): @group class MixedGroup: @@ -428,21 +435,28 @@ class MixedGroup: optional_int_param: Input(type="integer", optional=True) = 5 output_folder: Output(type="uri_folder") + # __init__ func test assert hasattr(MixedGroup, "__init__") is True - original_out = sys.stdout - sys.stdout = stdout_str_IO = StringIO() - help(MixedGroup.__init__) - assert ( - "__init__(self,*," - "int_param:int=None," - "str_default_param:str='test'," - "str_param:str=None," - "input_folder:{'type':'uri_folder'}=None," - "optional_int_param:int=5," - "output_folder:{'type':'uri_folder'}=None)" - "->None" in remove_extra_character(stdout_str_IO.getvalue()) - ) - sys.stdout = original_out + sig = inspect.signature(MixedGroup.__init__) + params = sig.parameters + + # Verify all expected parameters exist + assert "int_param" in params + assert "str_default_param" in params + assert "str_param" in params + assert "input_folder" in params + assert "optional_int_param" in params + assert "output_folder" in params + + # Verify default values + assert params["int_param"].default is None + assert params["str_default_param"].default == "test" + assert params["str_param"].default is None + assert params["optional_int_param"].default == 5 + # input_folder and output_folder should have None as default + assert params["input_folder"].default is None + assert params["output_folder"].default is None + # __repr__ func test var = MixedGroup( int_param=1, str_param="test-str", input_folder=Input(path="input"), output_folder=Output(path="output") From eb18f39bced56226cc9f3498b48ec85445bf55ca Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 19 Nov 2025 11:17:26 +0530 Subject: [PATCH 06/13] Fix Python 3.14 test failures in test_init_finalize_job by accessing class attributes directly instead of through self in closures --- .../dsl/unittests/test_init_finalize_job.py | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_init_finalize_job.py b/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_init_finalize_job.py index 4f9601f52904..954938f24bfe 100644 --- a/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_init_finalize_job.py +++ b/sdk/ml/azure-ai-ml/tests/dsl/unittests/test_init_finalize_job.py @@ -1,8 +1,8 @@ +import sys from functools import partial from pathlib import Path import pytest -import sys from azure.ai.ml import dsl, load_component, load_job from azure.ai.ml.entities import PipelineJob @@ -33,13 +33,13 @@ def test_init_finalize_job_from_yaml(self) -> None: assert pipeline_job_dict["properties"]["settings"]["on_init"] == "a" assert pipeline_job_dict["properties"]["settings"]["on_finalize"] == "c" - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_init_finalize_job_from_sdk(self) -> None: from azure.ai.ml._internal.dsl import set_pipeline_settings from azure.ai.ml.dsl import pipeline + # Access class attributes directly to avoid capturing self in closures + component_func = TestInitFinalizeJob.component_func + def assert_pipeline_job_init_finalize_job(pipeline_job: PipelineJob): assert pipeline_job._validate_init_finalize_job().passed assert pipeline_job.settings.on_init == "init_job" @@ -51,10 +51,10 @@ def assert_pipeline_job_init_finalize_job(pipeline_job: PipelineJob): # pipeline.settings.on_init/on_finalize @pipeline() def job_settings_func(): - init_job = self.component_func() # noqa: F841 - work1 = self.component_func() # noqa: F841 - work2 = self.component_func() # noqa: F841 - finalize_job = self.component_func() # noqa: F841 + init_job = component_func() # noqa: F841 + work1 = component_func() # noqa: F841 + work2 = component_func() # noqa: F841 + finalize_job = component_func() # noqa: F841 pipeline1 = job_settings_func() pipeline1.settings.on_init = "init_job" @@ -64,10 +64,10 @@ def job_settings_func(): # dsl.settings() @pipeline() def dsl_settings_func(): - init_job = self.component_func() - work1 = self.component_func() # noqa: F841 - work2 = self.component_func() # noqa: F841 - finalize_job = self.component_func() # noqa: F841 + init_job = component_func() # noqa: F841 + work1 = component_func() # noqa: F841 + work2 = component_func() # noqa: F841 + finalize_job = component_func() # noqa: F841 # `set_pipeline_settings` can receive either `BaseNode` or str, both should work set_pipeline_settings(on_init=init_job, on_finalize="finalize_job") @@ -80,10 +80,10 @@ def dsl_settings_func(): on_finalize="finalize_job", ) def in_decorator_func(): - init_job = self.component_func() # noqa: F841 - work1 = self.component_func() # noqa: F841 - work2 = self.component_func() # noqa: F841 - finalize_job = self.component_func() # noqa: F841 + init_job = component_func() # noqa: F841 + work1 = component_func() # noqa: F841 + work2 = component_func() # noqa: F841 + finalize_job = component_func() # noqa: F841 pipeline3 = in_decorator_func() assert_pipeline_job_init_finalize_job(pipeline3) @@ -97,14 +97,15 @@ def test_invalid_init_finalize_job_from_yaml(self) -> None: == "On_finalize job should not have connection to other execution node." ) - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_invalid_init_finalize_job_from_sdk(self) -> None: + # Access class attributes directly to avoid capturing self in closures + component_func = TestInitFinalizeJob.component_func + hello_world_func = TestInitFinalizeJob.hello_world_func + # invalid case: job name not exists @dsl.pipeline() def invalid_init_finalize_job_func(): - self.component_func() + component_func() invalid_pipeline1 = invalid_init_finalize_job_func() invalid_pipeline1.settings.on_init = "init_job" @@ -120,8 +121,8 @@ def invalid_init_finalize_job_func(): # invalid case: no normal node, on_init/on_finalize job is not isolated @dsl.pipeline() def init_finalize_with_invalid_connection_func(int_param: int, str_param: str): - node1 = self.hello_world_func(component_in_number=int_param, component_in_path=str_param) - node2 = self.hello_world_func( # noqa: F841 + node1 = hello_world_func(component_in_number=int_param, component_in_path=str_param) + node2 = hello_world_func( # noqa: F841 component_in_number=int_param, component_in_path=node1.outputs.component_out_path, ) @@ -152,28 +153,28 @@ def init_finalize_with_invalid_connection_func(int_param: int, str_param: str): # invalid case: set on_init for pipeline component @dsl.pipeline def subgraph_func(): - node = self.component_func() + node = component_func() set_pipeline_settings(on_init=node) # set on_init for subgraph (pipeline component) @dsl.pipeline def subgraph_with_init_func(): subgraph_func() - self.component_func() + component_func() with pytest.raises(UserErrorException) as e: subgraph_with_init_func() assert str(e.value) == "On_init/on_finalize is not supported for pipeline component." - @pytest.mark.skipif( - condition=sys.version_info >= (3, 13), reason="historical implementation doesn't support Python 3.13+" - ) def test_init_finalize_job_with_subgraph(self) -> None: from azure.ai.ml._internal.dsl import set_pipeline_settings + # Access class attributes directly to avoid capturing self in closures + component_func = TestInitFinalizeJob.component_func + # happy path @dsl.pipeline() def subgraph_func(): - node = self.component_func() + node = component_func() node.compute = "cpu-cluster" @dsl.pipeline() From 1220e7405fdb59e4c816b47bc1902939ee601170 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 19 Nov 2025 12:35:04 +0530 Subject: [PATCH 07/13] Temp: Checking if the test runs for 3.13 --- .../tests/internal_utils/unittests/test_persistent_locals.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py index 5b3e0f2b57f3..491a0fc2faac 100644 --- a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py +++ b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py @@ -65,7 +65,7 @@ def mock_function_multi_return_expected(__self, mock_arg): @pytest.mark.unittest @pytest.mark.pipeline_test @pytest.mark.skipif( - condition=sys.version_info >= (3, 13) or platform.python_implementation() == "PyPy", + condition=sys.version_info > (3, 13) or platform.python_implementation() == "PyPy", reason="historical implementation doesn't support Python 3.13+, and relies on CPython bytecode optimization; PyPy does not support required opcodes", ) class TestPersistentLocalsProfiler: @@ -208,7 +208,8 @@ def test_multiple_return(self): @pytest.mark.unittest @pytest.mark.pipeline_test @pytest.mark.skipif( - condition=sys.version_info >= (3, 11), reason="historical implementation doesn't support Python 3.11+" + condition=sys.version_info >= (3, 11), + reason="Historical implementation doesn't support Python 3.11+. This tests legacy bytecode manipulation from azureml-components. Runtime uses profiler from 3.12+, but this test forces bytecode usage to validate the historical implementation.", ) class TestPersistentLocalsHistoricalImplementation(TestPersistentLocalsPrivatePreview): """This is to test the implementation of persistent locals function in azuerml-components.""" From 56efed475207ed5d43ae71a1313c3ea73d45d4e0 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 19 Nov 2025 14:17:55 +0530 Subject: [PATCH 08/13] Temp: Checking if the test runs for 3.14 --- .../tests/internal_utils/unittests/test_persistent_locals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py index 491a0fc2faac..4e53e03285b4 100644 --- a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py +++ b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py @@ -65,8 +65,8 @@ def mock_function_multi_return_expected(__self, mock_arg): @pytest.mark.unittest @pytest.mark.pipeline_test @pytest.mark.skipif( - condition=sys.version_info > (3, 13) or platform.python_implementation() == "PyPy", - reason="historical implementation doesn't support Python 3.13+, and relies on CPython bytecode optimization; PyPy does not support required opcodes", + platform.python_implementation() == "PyPy", + reason="Relies on CPython bytecode optimization; PyPy does not support required opcodes", ) class TestPersistentLocalsProfiler: @classmethod From c9e61b09a8433d0e96ccbf043d2787d79eb4bcc0 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 19 Nov 2025 15:13:29 +0530 Subject: [PATCH 09/13] Update skipif condition in test_persistent_locals - bytecode implementation is till 3.12, after that it uses profiler at runtime, hence need not test bytecode for 3.13 onwards --- .../tests/internal_utils/unittests/test_persistent_locals.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py index 4e53e03285b4..72a5b9aa8d48 100644 --- a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py +++ b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_persistent_locals.py @@ -136,6 +136,10 @@ def test_multiple_return(self): @pytest.mark.unittest @pytest.mark.pipeline_test @pytest.mark.usefixtures("enable_pipeline_private_preview_features") +@pytest.mark.skipif( + condition=sys.version_info >= (3, 14), + reason="Bytecode builder only supports CPython 3.7-3.11. Runtime automatically uses profiler for 3.12+ (see is_bytecode_optimization_enabled in utils.py)", +) class TestPersistentLocalsPrivatePreview(TestPersistentLocalsProfiler): _PACKAGE = "bytecode" From bc11a0dc07bb37fc2166e4b51b752ded7b8b31e9 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Thu, 20 Nov 2025 10:51:08 +0530 Subject: [PATCH 10/13] Update README --- sdk/ml/azure-ai-ml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ml/azure-ai-ml/README.md b/sdk/ml/azure-ai-ml/README.md index 44187bd1b6dd..98343f9cb28f 100644 --- a/sdk/ml/azure-ai-ml/README.md +++ b/sdk/ml/azure-ai-ml/README.md @@ -10,7 +10,7 @@ We are excited to introduce the GA of Azure Machine Learning Python SDK v2. The | [Samples][ml_samples] -This package has been tested with Python 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13. +This package has been tested with Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14. For a more complete set of Azure libraries, see https://aka.ms/azsdk/python/all From f5017c06910f3c1f72656b6b34fb18cdcc3e8604 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Thu, 20 Nov 2025 10:52:14 +0530 Subject: [PATCH 11/13] Update CHANGELOG --- sdk/ml/azure-ai-ml/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/ml/azure-ai-ml/CHANGELOG.md b/sdk/ml/azure-ai-ml/CHANGELOG.md index d34232af6a97..a0257c2b6b2f 100644 --- a/sdk/ml/azure-ai-ml/CHANGELOG.md +++ b/sdk/ml/azure-ai-ml/CHANGELOG.md @@ -9,6 +9,7 @@ ### Other Changes - Ensuring that azureml-dataprep-rslex is only installed for PyPy below 3.10 and CPython below 3.13. +- Adding support for Python 3.14. ## 1.30.0 (2025-10-29) From 64d0c6b0a1f340a9bc1ffd9eec30f47c55c13fb6 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Mon, 24 Nov 2025 11:49:20 +0530 Subject: [PATCH 12/13] Add tests for dev requirments --- .../unittests/test_dev_requirements.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_dev_requirements.py b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_dev_requirements.py index 1decd8c085bd..efc6747d6bc8 100644 --- a/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_dev_requirements.py +++ b/sdk/ml/azure-ai-ml/tests/internal_utils/unittests/test_dev_requirements.py @@ -4,7 +4,8 @@ import pytest -PACKAGE_NAME = "azureml-dataprep-rslex" +PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX = "azureml-dataprep-rslex" +PACKAGE_NAME_SCIKIT_IMAGE = "scikit-image" IS_CPYTHON = platform.python_implementation() == "CPython" IS_PYPY = platform.python_implementation() == "PyPy" @@ -28,15 +29,35 @@ class TestPackageInstallation: ) def test_package_not_installed_in_cpython_3_13(self): assert not is_package_installed( - PACKAGE_NAME - ), f"{PACKAGE_NAME} should not be installed in CPython 3.13 or above environment." + PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX + ), f"{PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX} should not be installed in CPython 3.13 or above environment." @pytest.mark.skipif( not (IS_CPYTHON and sys.version_info < (3, 13)), reason="Skipping because environment is not below cpython 3.13", ) def test_package_installed_below_cpython_3_13(self): - assert is_package_installed(PACKAGE_NAME), f"{PACKAGE_NAME} should be installed in CPython < 3.13." + assert is_package_installed( + PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX + ), f"{PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX} should be installed in CPython < 3.13." + + @pytest.mark.skipif( + not (IS_CPYTHON and sys.version_info < (3, 14)), + reason="Skipping because environment is not below cpython 3.14", + ) + def test_package_installed_below_cpython_3_14(self): + assert is_package_installed( + PACKAGE_NAME_SCIKIT_IMAGE + ), f"{PACKAGE_NAME_SCIKIT_IMAGE} should be installed in CPython < 3.14." + + @pytest.mark.skipif( + not (IS_CPYTHON and sys.version_info >= (3, 14)), + reason="Skipping because environment is not below cpython 3.14", + ) + def test_package_not_installed_above_cpython_3_13(self): + assert not is_package_installed( + PACKAGE_NAME_SCIKIT_IMAGE + ), f"{PACKAGE_NAME_SCIKIT_IMAGE} should not be installed in CPython >= 3.14." @pytest.mark.skipif( not (IS_PYPY and sys.version_info >= (3, 10)), @@ -44,12 +65,14 @@ def test_package_installed_below_cpython_3_13(self): ) def test_package_not_installed_in_pypy_3_10(self): assert not is_package_installed( - PACKAGE_NAME - ), f"{PACKAGE_NAME} should not be installed in PyPy 3.10 or above environment." + PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX + ), f"{PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX} should not be installed in PyPy 3.10 or above environment." @pytest.mark.skipif( not (IS_PYPY and sys.version_info < (3, 10)), reason="Skipping because environment is not below pypy 3.10", ) def test_package_installed_below_pypy_3_10(self): - assert is_package_installed(PACKAGE_NAME), f"{PACKAGE_NAME} should be installed in PyPy < 3.10 environment." + assert is_package_installed( + PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX + ), f"{PACKAGE_NAME_AZURE_ML_DATAPREP_RSLEX} should be installed in PyPy < 3.10 environment." From ef338ef2c1fdb12748797136aa20197699bc377f Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 26 Nov 2025 13:22:08 +0530 Subject: [PATCH 13/13] Add test to cover src code changes in distillation_job --- .../_job/distillation/distillation_job.py | 2 ++ .../unittests/test_distillation_conversion.py | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py b/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py index 87953efb1221..b6b79630e868 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/entities/_job/distillation/distillation_job.py @@ -522,6 +522,8 @@ def __eq__(self, other: object) -> bool: if parent_eq is NotImplemented: # Parent doesn't implement comparison, we'll continue comparison on our end pass + # Adding this case for future + # currently the parent doesn't implement __eq__ so we will always get NotImplemented only elif not parent_eq: # Parent says objects are not equal return False diff --git a/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py b/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py index ac06a7564dc8..8be2579c4592 100644 --- a/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py +++ b/sdk/ml/azure-ai-ml/tests/distillation_job/unittests/test_distillation_conversion.py @@ -1,4 +1,5 @@ import sys +from unittest.mock import patch import pytest @@ -10,12 +11,31 @@ from azure.ai.ml.entities._job.distillation.endpoint_request_settings import EndpointRequestSettings from azure.ai.ml.entities._job.distillation.prompt_settings import PromptSettings from azure.ai.ml.entities._job.distillation.teacher_model_settings import TeacherModelSettings +from azure.ai.ml.entities._job.job import Job from azure.ai.ml.entities._job.resource_configuration import ResourceConfiguration from azure.ai.ml.entities._workspace.connections.connection_subtypes import ServerlessConnection from azure.ai.ml.entities._workspace.connections.workspace_connection import WorkspaceConnection class TestDistillationJobConversion: + + def test_distillation_job_eq_type_check_and_parent_false(self): + """Test __eq__ edge cases for Python 3.14 compatibility.""" + distillation_job = DistillationJob( + data_generation_type=DataGenerationType.DATA_GENERATION, + data_generation_task_type=DataGenerationTaskType.NLI, + teacher_model_endpoint_connection=ServerlessConnection( + name="llama-teacher", endpoint="http://bar.com", api_key="TESTKEY" + ), + student_model=Input(type=AssetTypes.MLFLOW_MODEL, path="azureml://foo/bar"), + name="test-job", + ) + + # mock parent __eq__ returning False + # As parent doesn't currently implement __eq__ and defaults to NotImplemented which gives error in boolean context + with patch.object(Job, "__eq__", return_value=False): + assert distillation_job != distillation_job + @pytest.mark.parametrize( "data_generation_task_type", [