Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .chronus/changes/preserve-pyproject-fields-2026-06-15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-python"
---

Preserve manually customized `description`, `classifiers`, and `[project.urls]` fields in an existing `pyproject.toml` instead of overwriting them on regeneration.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ def external_lib_version_map(self, file_content: str, additional_version_map: di

# Process dependencies
if "project" in loaded_pyproject_toml:
project = loaded_pyproject_toml["project"]

# Keep manually customized project fields the emitter would otherwise overwrite.
for field in ("description", "classifiers", "urls"):
if field in project:
result["KEEP_FIELDS"][f"project.{field}"] = project[field]

# Handle main dependencies
if "dependencies" in loaded_pyproject_toml["project"]:
kept_deps = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ name = "{{ options.get('package-name')|lower }}"
authors = [
{ name = "{{ code_model.company_name }}"{% if code_model.is_azure_flavor %}, email = "azpysdkhelp@microsoft.com"{% endif %} },
]
{% if options.get("azure-arm") %}
{% if KEEP_FIELDS and KEEP_FIELDS.get('project.description') %}
description = {{ KEEP_FIELDS.get('project.description')|tojson }}
{% elif options.get("azure-arm") %}
description = "Microsoft Azure {{ options.get('package-pprint-name') }} Client Library for Python"
{% else %}
description = "{{ code_model.company_name }} {% if code_model.is_azure_flavor and not options.get('package-pprint-name').startswith('Azure ') %}Azure {% endif %}{{ options.get('package-pprint-name') }} Client Library for Python"
{% endif %}
license = "MIT"
{% if KEEP_FIELDS and KEEP_FIELDS.get('project.classifiers') %}
classifiers = [
{% for classifier in KEEP_FIELDS.get('project.classifiers') %}
{{ classifier|tojson }},
{% endfor %}
]
{% else %}
classifiers = [
"Development Status :: {{ dev_status }}",
"Programming Language :: Python",
Expand All @@ -29,10 +38,15 @@ classifiers = [
"Programming Language :: Python :: 3.{{ version }}",
{% endfor %}
]
{% endif %}
requires-python = ">={{ MIN_PYTHON_VERSION }}"
{% else %}
{% if KEEP_FIELDS and KEEP_FIELDS.get('project.description') %}
description = {{ KEEP_FIELDS.get('project.description')|tojson }}
{% else %}
description = "{{ options.get('package-name') }}"
{% endif %}
{% endif %}
{% if code_model.is_azure_flavor %}
keywords = ["azure", "azure sdk"]
{% endif %}
Expand Down Expand Up @@ -77,7 +91,13 @@ version = "{{ options.get("package-version", "unknown") }}"
]
{% endfor %}
{% endif %}
{% if code_model.is_azure_flavor %}
{% if KEEP_FIELDS and KEEP_FIELDS.get('project.urls') %}

[project.urls]
{% for key, val in KEEP_FIELDS.get('project.urls').items() %}
{{ key }} = {{ val|tojson }}
{% endfor %}
{% elif code_model.is_azure_flavor %}

[project.urls]
repository = "https://github.com/Azure/azure-sdk-for-python"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
"""Unit tests for preserving manually customized pyproject.toml fields.

The emitter regenerates pyproject.toml on every emit. Manual edits to fields
the emitter doesn't own (description, classifiers, project URLs) must be
preserved so they are not clobbered (see GitHub issue #10311).
"""
from pygen.codegen.serializers.general_serializer import GeneralSerializer


def _keep_fields(file_content: str) -> dict:
# external_lib_version_map only relies on module-level helpers, not on
# instance state, so we can bypass __init__ for a focused unit test.
serializer = GeneralSerializer.__new__(GeneralSerializer)
return serializer.external_lib_version_map(file_content, {})["KEEP_FIELDS"]


def test_preserve_description():
content = """
[project]
name = "azure-ai-sample"
description = "Microsoft Azure AI Sample Client Library for Python"
"""
keep_fields = _keep_fields(content)
assert keep_fields["project.description"] == "Microsoft Azure AI Sample Client Library for Python"


def test_preserve_classifiers():
content = """
[project]
name = "azure-ai-sample"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
"""
keep_fields = _keep_fields(content)
assert "Programming Language :: Python :: 3.14" in keep_fields["project.classifiers"]


def test_preserve_project_urls():
content = """
[project]
name = "azure-ai-sample"

[project.urls]
repository = "https://github.com/Azure/azure-sdk-for-python-custom"
documentation = "https://aka.ms/custom-docs"
"""
keep_fields = _keep_fields(content)
assert keep_fields["project.urls"]["repository"] == "https://github.com/Azure/azure-sdk-for-python-custom"
assert keep_fields["project.urls"]["documentation"] == "https://aka.ms/custom-docs"


def test_missing_fields_not_kept():
content = """
[project]
name = "azure-ai-sample"
"""
keep_fields = _keep_fields(content)
assert "project.description" not in keep_fields
assert "project.classifiers" not in keep_fields
assert "project.urls" not in keep_fields


def test_invalid_toml_returns_empty():
assert _keep_fields("this is : not valid = toml [[[") == {}
Loading