From d29971b0d9f314878acc2ba8de78097474a94c1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:13:50 +0000 Subject: [PATCH 1/4] Initial plan From 4465dac8c9d0f2e34f005ca74a3c958a3d146c27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:40:55 +0000 Subject: [PATCH 2/4] feat(http-client-python): add encode/boolean mock API tests and fix boolean string encode support - Add `encode/boolean` to regenerate-common.ts spec list - Bump @typespec/http-specs to 0.1.0-alpha.39-dev.4 (includes encode/boolean spec) - Fix TCGC to propagate encode=string for boolean types - Fix Python generator BooleanType to set encode="str" when @encode(string) is used - Add _deserialize_bool_as_str to model_base.py.jinja2 template - Add sync and async mock API tests for azure and unbranded flavors - Add changelog entry Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> --- ...on-encode-boolean-test-2026-6-24-23-0-0.md | 7 +++ .../eng/scripts/ci/regenerate-common.ts | 3 ++ .../pygen/codegen/models/primitive_types.py | 5 +++ .../codegen/templates/model_base.py.jinja2 | 8 ++++ packages/http-client-python/package-lock.json | 14 +++--- packages/http-client-python/package.json | 2 +- .../asynctests/test_encode_boolean_async.py | 43 +++++++++++++++++++ .../mock_api/azure/test_encode_boolean.py | 37 ++++++++++++++++ .../asynctests/test_encode_boolean_async.py | 43 +++++++++++++++++++ .../mock_api/unbranded/test_encode_boolean.py | 38 ++++++++++++++++ 10 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 .chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md create mode 100644 packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py create mode 100644 packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py create mode 100644 packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py create mode 100644 packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py diff --git a/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md b/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md new file mode 100644 index 00000000000..088584ff2c5 --- /dev/null +++ b/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +Add mock API test coverage for `@encode(string)` on boolean properties (`encode/boolean` Spector scenarios). Fix Python generator to correctly serialize and deserialize boolean values encoded as strings (case-insensitive `true`/`false`). diff --git a/packages/http-client-python/eng/scripts/ci/regenerate-common.ts b/packages/http-client-python/eng/scripts/ci/regenerate-common.ts index 9b2defd0e8c..81330d54c2f 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate-common.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate-common.ts @@ -178,6 +178,9 @@ export const AZURE_EMITTER_OPTIONS: Record< "client/overload": { namespace: "client.overload", }, + "encode/boolean": { + namespace: "encode.boolean", + }, "encode/duration": { namespace: "encode.duration", }, diff --git a/packages/http-client-python/generator/pygen/codegen/models/primitive_types.py b/packages/http-client-python/generator/pygen/codegen/models/primitive_types.py index cc864cf3ee1..9d231548d47 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/primitive_types.py +++ b/packages/http-client-python/generator/pygen/codegen/models/primitive_types.py @@ -49,6 +49,11 @@ def default_template_representation_declaration(self) -> str: class BooleanType(PrimitiveType): + def __init__(self, yaml_data: dict[str, Any], code_model: "CodeModel") -> None: + super().__init__(yaml_data=yaml_data, code_model=code_model) + if yaml_data.get("encode") == "string": + self.encode = "str" + def serialization_type(self, **kwargs: Any) -> str: return "bool" diff --git a/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 index e51a7645dde..dbe8f8f0adb 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 @@ -348,6 +348,12 @@ def _deserialize_int_as_str(attr): return int(attr) +def _deserialize_bool_as_str(attr): + if isinstance(attr, bool): + return attr + return attr.lower() == "true" + + _DESERIALIZE_MAPPING = { datetime: _deserialize_datetime, date: _deserialize_date, @@ -375,6 +381,8 @@ _DESERIALIZE_MAPPING_WITHFORMAT = { def get_deserializer(annotation: typing.Any, rf: typing.Optional["_RestField"] = None): if annotation is int and rf and rf._format == "str": return _deserialize_int_as_str + if annotation is bool and rf and rf._format == "str": + return _deserialize_bool_as_str if annotation is str and rf and rf._format in _ARRAY_ENCODE_MAPPING: return functools.partial(_deserialize_array_encoded, _ARRAY_ENCODE_MAPPING[rf._format]) if rf and rf._format: diff --git a/packages/http-client-python/package-lock.json b/packages/http-client-python/package-lock.json index 01d5631147f..9c78f12ccf6 100644 --- a/packages/http-client-python/package-lock.json +++ b/packages/http-client-python/package-lock.json @@ -29,7 +29,7 @@ "@typespec/compiler": "^1.13.0", "@typespec/events": "~0.83.0", "@typespec/http": "^1.13.0", - "@typespec/http-specs": "0.1.0-alpha.39-dev.2", + "@typespec/http-specs": "0.1.0-alpha.39-dev.4", "@typespec/openapi": "^1.13.0", "@typespec/rest": "~0.83.0", "@typespec/spec-api": "0.1.0-alpha.14", @@ -2529,20 +2529,20 @@ } }, "node_modules/@typespec/http-specs": { - "version": "0.1.0-alpha.39-dev.2", - "resolved": "https://registry.npmjs.org/@typespec/http-specs/-/http-specs-0.1.0-alpha.39-dev.2.tgz", - "integrity": "sha512-NsxuimkS12gB6RF3j2yGf48bV/LzG0JQbtrZbiGy00RxOAZV59L7gYp852ij6fYN37Uv8P31KHcGM6wIAduAyg==", + "version": "0.1.0-alpha.39-dev.4", + "resolved": "https://registry.npmjs.org/@typespec/http-specs/-/http-specs-0.1.0-alpha.39-dev.4.tgz", + "integrity": "sha512-kcbEa1n/3L+4ed6BFTuGZWp9w79q69lC2awvQ5oDeP9mSOE2hzorszlby0t6h4k4pplucAVinNi26LJeOfmUzA==", "dev": true, "license": "MIT", "dependencies": { - "@typespec/spec-api": "^0.1.0-alpha.14 || >= 0.1.0-dev.0", - "@typespec/spector": "^0.1.0-alpha.25 || >= 0.1.0-dev.0" + "@typespec/spec-api": "^0.1.0-alpha.14 || >= 0.1.0-alpha.15-dev.1", + "@typespec/spector": "^0.1.0-alpha.25 || >= 0.1.0-alpha.26-dev.1" }, "engines": { "node": ">=22.0.0" }, "peerDependencies": { - "@typespec/compiler": "^1.13.0 || >= 1.14.0-dev.0", + "@typespec/compiler": "^1.13.0 || >= 1.14.0-dev.5", "@typespec/http": "^1.13.0 || >= 1.14.0-dev.0", "@typespec/rest": "^0.83.0 || >= 0.84.0-dev.0", "@typespec/versioning": "^0.83.0 || >= 0.84.0-dev.0", diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index 65f08273ff0..bd3f22af688 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -119,7 +119,7 @@ "@typespec/sse": "~0.83.0", "@typespec/streams": "~0.83.0", "@typespec/xml": "~0.83.0", - "@typespec/http-specs": "0.1.0-alpha.39-dev.2", + "@typespec/http-specs": "0.1.0-alpha.39-dev.4", "@types/js-yaml": "~4.0.5", "@types/node": "~25.0.2", "@types/semver": "7.5.8", diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py new file mode 100644 index 00000000000..6fbc5556bb1 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py @@ -0,0 +1,43 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from encode.boolean.aio import BooleanClient +from encode.boolean import models + + +@pytest_asyncio.fixture +async def client(): + async with BooleanClient() as client: + yield client + + +@pytest.mark.asyncio +async def test_property_true_lower(client: BooleanClient): + result = await client.property.true_lower(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "true" + + +@pytest.mark.asyncio +async def test_property_false_lower(client: BooleanClient): + result = await client.property.false_lower(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "false" + + +@pytest.mark.asyncio +async def test_property_true_upper(client: BooleanClient): + result = await client.property.true_upper(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "TRUE" + + +@pytest.mark.asyncio +async def test_property_false_mixed(client: BooleanClient): + result = await client.property.false_mixed(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py b/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py new file mode 100644 index 00000000000..80139affcce --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py @@ -0,0 +1,37 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from encode.boolean import BooleanClient, models + + +@pytest.fixture +def client(): + with BooleanClient() as client: + yield client + + +def test_property_true_lower(client: BooleanClient): + result = client.property.true_lower(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "true" + + +def test_property_false_lower(client: BooleanClient): + result = client.property.false_lower(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "false" + + +def test_property_true_upper(client: BooleanClient): + result = client.property.true_upper(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "TRUE" + + +def test_property_false_mixed(client: BooleanClient): + result = client.property.false_mixed(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py new file mode 100644 index 00000000000..03d23002bb9 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py @@ -0,0 +1,43 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from encode.boolean.aio import BooleanClient +from encode.boolean.property import models + + +@pytest_asyncio.fixture +async def client(): + async with BooleanClient() as client: + yield client + + +@pytest.mark.asyncio +async def test_property_true_lower(client: BooleanClient): + result = await client.property.true_lower(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "true" + + +@pytest.mark.asyncio +async def test_property_false_lower(client: BooleanClient): + result = await client.property.false_lower(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "false" + + +@pytest.mark.asyncio +async def test_property_true_upper(client: BooleanClient): + result = await client.property.true_upper(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "TRUE" + + +@pytest.mark.asyncio +async def test_property_false_mixed(client: BooleanClient): + result = await client.property.false_mixed(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py b/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py new file mode 100644 index 00000000000..c6bc89cae0f --- /dev/null +++ b/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py @@ -0,0 +1,38 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from encode.boolean import BooleanClient +from encode.boolean.property import models + + +@pytest.fixture +def client(): + with BooleanClient() as client: + yield client + + +def test_property_true_lower(client: BooleanClient): + result = client.property.true_lower(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "true" + + +def test_property_false_lower(client: BooleanClient): + result = client.property.false_lower(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "false" + + +def test_property_true_upper(client: BooleanClient): + result = client.property.true_upper(models.BoolAsStringProperty(value=True)) + assert result.value == True + assert result["value"] == "TRUE" + + +def test_property_false_mixed(client: BooleanClient): + result = client.property.false_mixed(models.BoolAsStringProperty(value=False)) + assert result.value == False + assert result["value"] == "FaLsE" From 88627e3d72989107c28d4a6e89ffd29759dc3d9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:44:07 +0000 Subject: [PATCH 3/4] fix(http-client-python): use idiomatic `is True`/`is False` in boolean assertions Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> --- .../azure/asynctests/test_encode_boolean_async.py | 8 ++++---- .../tests/mock_api/azure/test_encode_boolean.py | 8 ++++---- .../unbranded/asynctests/test_encode_boolean_async.py | 8 ++++---- .../tests/mock_api/unbranded/test_encode_boolean.py | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py index 6fbc5556bb1..3245fdb434e 100644 --- a/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_encode_boolean_async.py @@ -18,26 +18,26 @@ async def client(): @pytest.mark.asyncio async def test_property_true_lower(client: BooleanClient): result = await client.property.true_lower(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "true" @pytest.mark.asyncio async def test_property_false_lower(client: BooleanClient): result = await client.property.false_lower(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "false" @pytest.mark.asyncio async def test_property_true_upper(client: BooleanClient): result = await client.property.true_upper(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "TRUE" @pytest.mark.asyncio async def test_property_false_mixed(client: BooleanClient): result = await client.property.false_mixed(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py b/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py index 80139affcce..28a89fed980 100644 --- a/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py +++ b/packages/http-client-python/tests/mock_api/azure/test_encode_boolean.py @@ -15,23 +15,23 @@ def client(): def test_property_true_lower(client: BooleanClient): result = client.property.true_lower(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "true" def test_property_false_lower(client: BooleanClient): result = client.property.false_lower(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "false" def test_property_true_upper(client: BooleanClient): result = client.property.true_upper(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "TRUE" def test_property_false_mixed(client: BooleanClient): result = client.property.false_mixed(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py index 03d23002bb9..7083622bb99 100644 --- a/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py +++ b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_encode_boolean_async.py @@ -18,26 +18,26 @@ async def client(): @pytest.mark.asyncio async def test_property_true_lower(client: BooleanClient): result = await client.property.true_lower(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "true" @pytest.mark.asyncio async def test_property_false_lower(client: BooleanClient): result = await client.property.false_lower(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "false" @pytest.mark.asyncio async def test_property_true_upper(client: BooleanClient): result = await client.property.true_upper(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "TRUE" @pytest.mark.asyncio async def test_property_false_mixed(client: BooleanClient): result = await client.property.false_mixed(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "FaLsE" diff --git a/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py b/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py index c6bc89cae0f..0d98ed6416f 100644 --- a/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py +++ b/packages/http-client-python/tests/mock_api/unbranded/test_encode_boolean.py @@ -16,23 +16,23 @@ def client(): def test_property_true_lower(client: BooleanClient): result = client.property.true_lower(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "true" def test_property_false_lower(client: BooleanClient): result = client.property.false_lower(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "false" def test_property_true_upper(client: BooleanClient): result = client.property.true_upper(models.BoolAsStringProperty(value=True)) - assert result.value == True + assert result.value is True assert result["value"] == "TRUE" def test_property_false_mixed(client: BooleanClient): result = client.property.false_mixed(models.BoolAsStringProperty(value=False)) - assert result.value == False + assert result.value is False assert result["value"] == "FaLsE" From 2e5053f4d81b195c037a36e66b352cad7401e823 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Jun 2026 02:10:03 +0000 Subject: [PATCH 4/4] fix(http-client-python): update changelog changeKind from internal to feature Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> --- .chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md b/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md index 088584ff2c5..1ab68d0911b 100644 --- a/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md +++ b/.chronus/changes/python-encode-boolean-test-2026-6-24-23-0-0.md @@ -1,5 +1,5 @@ --- -changeKind: internal +changeKind: feature packages: - "@typespec/http-client-python" ---