Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion conda/conda-recipes/azure-ai-evaluation/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ source:
build:
noarch: python
number: 0
script: "{{ PYTHON }} -m pip install . -vv"
script: "{{ PYTHON }} -m pip install . -vv --no-build-isolation"
Copy link
Copy Markdown
Member

@JennyPng JennyPng May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is necessary because of a pipeline failure due to pkg_resources usage? Can we leave a comment here to explain that this change, and the addition of setuptools and wheel to host reqs, should be reverted whenever this fix is no longer necessary

Copy link
Copy Markdown
Member

@JennyPng JennyPng May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, I'm seeing that the Conda pipeline is using packages from version 2025.09.01 when the most recent is this past March , is this branch up to date with main ? the change to azure-ai-ml's meta.yaml is already in main actually https://github.com/Azure/azure-sdk-for-python/blob/main/conda/conda-recipes/azure-ai-ml/meta.yaml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No that's the reason for the changes outside of cosmos we are hotfixing an old release but the pipeline has had changes since then.


requirements:
host:
- setuptools <70
- wheel
- azure-core >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
- azure-identity >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
- msrest >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
Expand Down
4 changes: 3 additions & 1 deletion conda/conda-recipes/azure-ai-ml/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ source:
build:
noarch: python
number: 0
script: "{{ PYTHON }} -m pip install . -vv"
script: "{{ PYTHON }} -m pip install . -vv --no-build-isolation"

requirements:
host:
- setuptools <70
- wheel
- azure-core >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
- azure-identity >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
- msrest >={{ environ.get('AZURESDK_CONDA_VERSION', '0.0.0') }}
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/templates/stages/conda-sdk-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ extends:
service: ml
in_batch: ${{ parameters.release_azure_ai_ml }}
channels:
- conda-forge
- https://prefix.dev/conda-forge
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this change?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 1ES build agents block conda.anaconda.org (DNS sinkhole 192.0.2.11) due to network isolation, so the per-package channel override -c conda-forge was failing to resolve when conda-build invoked it.
https://prefix.dev/conda-forge is the allow-listed conda-forge mirror that 1ES allows through.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tvaron3 did you see this failure though? I would expect any of the existing pipelines to be grandfathered in to the correct network isolation.

checkout:
- package: azure-ai-ml
version: 1.28.1
Expand Down
4 changes: 2 additions & 2 deletions eng/tools/azure-sdk-tools/ci_tools/conda/conda_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

CONDA_ENV_FILE = """name: azure-build-env
channels:
- conda-forge
- https://prefix.dev/conda-forge
- defaults
dependencies:
- python=3.10
Expand Down Expand Up @@ -294,7 +294,7 @@ def create_combined_sdist(
[
os.path.join(config_assembled_folder, a)
for a in os.listdir(config_assembled_folder)
if os.path.isfile(os.path.join(config_assembled_folder, a)) and conda_build.name in a
if os.path.isfile(os.path.join(config_assembled_folder, a)) and conda_build.name.replace("-", "_") in a
]
)
)
Expand Down
8 changes: 6 additions & 2 deletions eng/tools/azure-sdk-tools/ci_tools/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,12 @@ def discover_targeted_packages(
for pkg in collected_packages:
try:
parsed_packages.append(ParsedSetup.from_path(pkg))
except RuntimeError as e:
logging.error(f"Unable to parse metadata for package {pkg}, omitting from build.")
except Exception as e:
# Some packages have setup.py files that import modules unavailable in the
# current environment (e.g. pkg_resources removed by setuptools>=80). Such
# packages should be omitted from the build/regression set rather than
# aborting discovery for the entire repo.
logging.error(f"Unable to parse metadata for package {pkg}, omitting from build. Reason: {e}")
continue

# filter for compatibility, this means excluding a package that doesn't support py36 when we are running a py36 executable
Expand Down
7 changes: 7 additions & 0 deletions sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Release History

### 4.14.7 (Unreleased)

Comment thread
dibahlfi marked this conversation as resolved.
#### Bugs Fixed
* Fixed `SELECT VALUE` aggregation classification across partitions: booleans are no longer treated as numeric aggregates, non-aggregate numeric projections are no longer merged, and `MIN`/`MAX` detection is now correct. See [PR 46692](https://github.com/Azure/azure-sdk-for-python/pull/46692)
* Fixed a bug in `query_items(feed_range=...)` where pagination could return incorrect results after a partition split caused the supplied feed range to overlap multiple physical partitions. See [PR 46692](https://github.com/Azure/azure-sdk-for-python/pull/46692)
* Fixed bug where unavailable regional endpoints were dropped from the routing list instead of being kept as fallback options. See [PR 45200](https://github.com/Azure/azure-sdk-for-python/pull/45200)

### 4.14.6 (2026-02-02)

#### Bugs Fixed
Expand Down
99 changes: 56 additions & 43 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The MIT License (MIT)
# The MIT License (MIT)
# Copyright (c) 2014 Microsoft Corporation

# Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down Expand Up @@ -39,6 +39,11 @@
from . import documents
from . import http_constants
from . import _runtime_constants
from ._query_aggregate_utils import (
_AggregatePartialClassification,
_classify_aggregate_partial,
_get_select_value_aggregate_function,
)
from ._constants import _Constants as Constants
from .auth import _get_authorization_header
from .offer import ThroughputProperties
Expand Down Expand Up @@ -124,6 +129,7 @@ def build_options(kwargs: dict[str, Any]) -> dict[str, Any]:
options['accessCondition'] = {'type': 'IfNoneMatch', 'condition': if_none_match}
return options


def _merge_query_results(
results: dict[str, Any],
partial_result: dict[str, Any],
Expand Down Expand Up @@ -163,22 +169,13 @@ def _merge_query_results(

results_docs = results.get("Documents")

# Check if both results are aggregate queries
is_partial_agg = (
isinstance(partial_docs, list)
and len(partial_docs) == 1
and isinstance(partial_docs[0], dict)
and partial_docs[0].get("_aggregate") is not None
)
is_results_agg = (
results_docs
and isinstance(results_docs, list)
and len(results_docs) == 1
and isinstance(results_docs[0], dict)
and results_docs[0].get("_aggregate") is not None
)
partial_aggregate_class = _classify_aggregate_partial(partial_docs, query)
results_aggregate_class = _classify_aggregate_partial(results_docs, query)

if is_partial_agg and is_results_agg:
if (
partial_aggregate_class == _AggregatePartialClassification.OBJECT
and results_aggregate_class == _AggregatePartialClassification.OBJECT
):
agg_results = results_docs[0]["_aggregate"] # type: ignore[index]
agg_partial = partial_docs[0]["_aggregate"]
for key in agg_partial:
Expand All @@ -196,33 +193,26 @@ def _merge_query_results(
agg_results[key] += agg_partial[key]
return results

# Check if both are VALUE aggregate queries
is_partial_value_agg = (
isinstance(partial_docs, list)
and len(partial_docs) == 1
and isinstance(partial_docs[0], (int, float))
)
is_results_value_agg = (
results_docs
and isinstance(results_docs, list)
and len(results_docs) == 1
and isinstance(results_docs[0], (int, float))
)

if is_partial_value_agg and is_results_value_agg:
query_text = query.get("query") if isinstance(query, dict) else query
if query_text:
query_upper = query_text.upper()
# For MIN/MAX, we find the min/max of the partial results.
# For COUNT/SUM, we sum the partial results.
# Without robust query parsing, we can't distinguish them reliably.
# Defaulting to sum for COUNT/SUM. MIN/MAX VALUE queries are not fully supported client-side.
if " SELECT VALUE MIN" in query_upper:
results_docs[0] = min(results_docs[0], partial_docs[0]) # type: ignore[index]
elif " SELECT VALUE MAX" in query_upper:
results_docs[0] = max(results_docs[0], partial_docs[0]) # type: ignore[index]
else: # For COUNT/SUM, we sum the partial results
results_docs[0] += partial_docs[0] # type: ignore[index]
if (
partial_aggregate_class == _AggregatePartialClassification.VALUE
and results_aggregate_class == _AggregatePartialClassification.VALUE
):
aggregate_fn = _get_select_value_aggregate_function(query)
if aggregate_fn is None:
raise ValueError(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommendation — Maintainability: Internal invariant violation should use RuntimeError, not ValueError

This raise guards an internal code invariant — it should only fire if _classify_aggregate_partial returned VALUE but _get_select_value_aggregate_function returns None, which is a contradiction in the classification logic itself, not bad user input.

Using ValueError means it's caught by the except ValueError as merge_error handler six lines below in the pagination loop, which routes it through _raise_query_merge_value_error. That function doesn't match this message (it looks for the AVG string), so the raw "Invariant violation" message leaks to the user — confusing for something that's never their fault.

Suggested fix: Use RuntimeError (or AssertionError) so it bypasses the ValueError handler and surfaces as an unhandled internal error, which is its true nature.

⚠️ AI-generated review — may be incorrect. Agree? → resolve the conversation. Disagree? → reply with your reasoning.

"Invariant violation: VALUE aggregate classification requires a recognized aggregate function."
)
if aggregate_fn == "MIN":
results_docs[0] = min(results_docs[0], partial_docs[0]) # type: ignore[index]
elif aggregate_fn == "MAX":
results_docs[0] = max(results_docs[0], partial_docs[0]) # type: ignore[index]
elif aggregate_fn == "AVG":
raise ValueError(
"VALUE AVG aggregate merge across partitions is not supported client-side."
)
else:
# COUNT/SUM are additive.
results_docs[0] += partial_docs[0] # type: ignore[index]
return results

# Standard query, append documents
Expand All @@ -234,6 +224,29 @@ def _merge_query_results(
return results


def _raise_query_merge_value_error(merge_error: ValueError) -> None:
"""Raise a clearer user-facing error for unsupported VALUE aggregate merges.

``SELECT VALUE AVG(...)`` partials cannot be merged correctly client-side
across multiple partition/range responses. We fail loudly instead of
falling back to list concatenation (which would silently produce
mathematically incorrect results).

:param merge_error: ValueError raised while merging partial query results.
:type merge_error: ValueError
:raises ValueError: Always re-raises, potentially with a clearer message.
"""
merge_message = str(merge_error)
if "VALUE AVG aggregate merge across partitions is not supported client-side." in merge_message:
raise ValueError(
"Unsupported query shape for range-scoped pagination: "
"SELECT VALUE AVG(...) cannot be merged client-side when the query "
"scope spans multiple physical partitions."
) from merge_error
raise merge_error



def GetHeaders( # pylint: disable=too-many-statements,too-many-branches
cosmos_client_connection: Union["CosmosClientConnection", "AsyncClientConnection"],
default_headers: Mapping[str, Any],
Expand Down
Loading
Loading