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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/http-client-python"
---

[Python] Generate model/client/config docstrings and targeted pylint suppressions that satisfy the updated `azure-pylint-guidelines-checker` docstring checks (`docstring-keyword-should-match-keyword-only`, `docstring-missing-param`)
8 changes: 8 additions & 0 deletions .chronus/changes/vnext-bump-2026-6-7-22-13-32.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: dependencies
packages:
- "@typespec/http-client-python"
---

[Python] Bump tool version targets: pylint 4.0.6, mypy 2.1.0, pyright 1.1.411, azure-pylint-guidelines-checker 0.5.9
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ enable=useless-suppression
# cyclic-import: because of https://github.com/PyCQA/pylint/issues/850
# too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods.
# too-many-lines: Due to code generation many files end up with too many lines.
# no-cross-package-private-import: The guideline checker does not resolve relative
# imports (it ignores the import level), so legitimate same-package relative imports
# like `from ...operations._operations import ...` in generated async code are
# misreported as cross-package private imports.
# Let's black deal with bad-continuation
disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,disallowed-name,consider-using-max-builtin,unknown-option-value,file-needs-copyright-header,too-many-positional-arguments
disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,disallowed-name,consider-using-max-builtin,unknown-option-value,file-needs-copyright-header,too-many-positional-arguments,no-cross-package-private-import

[FORMAT]
max-line-length=120
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pyright==1.1.407
pylint==4.0.4
pyright==1.1.411
pylint==4.0.6
tox==4.16.0
tox-uv
mypy==1.19.1
mypy==2.1.0
colorama==0.4.6
debugpy==1.8.2
pytest==9.0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ def description(self) -> str:
def name(self) -> str:
return self.yaml_data["name"]

@property
def has_docstring_only_keyword(self) -> bool:
"""Whether the constructor docstring documents a ``:keyword:`` that is not a real
keyword-only argument (it is popped from ``kwargs`` instead).

This happens for params routed to ``**kwargs`` (e.g. ``api_version``): they are
still rendered as ``:keyword:`` in the docstring, so we must silence the
``docstring-keyword-should-match-keyword-only`` guideline check.
"""
return any(p for p in self.parameters.method if p.method_location == ParameterMethodLocation.KWARG)


class Client(_ClientConfigBase[ClientGlobalParameterList]):
"""Model representing our service client"""
Expand Down Expand Up @@ -187,6 +198,8 @@ def pylint_disable(self) -> str:
retval = add_to_pylint_disable(retval, "too-many-instance-attributes")
if len(self.name) > NAME_LENGTH_LIMIT:
retval = add_to_pylint_disable(retval, "name-too-long")
if self.has_docstring_only_keyword or self.has_public_lro_operations:
retval = add_to_pylint_disable(retval, "docstring-keyword-should-match-keyword-only")
return retval

@property
Expand Down Expand Up @@ -375,6 +388,8 @@ def pylint_disable(self) -> str:
retval = add_to_pylint_disable("", "too-many-instance-attributes") if self.code_model.is_azure_flavor else ""
if len(self.name) > NAME_LENGTH_LIMIT:
retval = add_to_pylint_disable(retval, "name-too-long")
if self.has_docstring_only_keyword:
retval = add_to_pylint_disable(retval, "docstring-keyword-should-match-keyword-only")
return retval

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def pylint_disable(self) -> str:
retval: str = ""
if self.has_abstract_operations:
retval = add_to_pylint_disable(retval, "abstract-class-instantiated")
if not self.is_mixin:
retval = add_to_pylint_disable(retval, "docstring-missing-param")
if len(self.operations) > 20:
retval = add_to_pylint_disable(retval, "too-many-public-methods")
if len(self.class_name) > NAME_LENGTH_LIMIT and self.class_name[0] != "_":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
)
from .import_serializer import FileImportSerializer
from .base_serializer import BaseSerializer
from ..models.utils import NamespaceType
from ..models.utils import NamespaceType, add_to_pylint_disable


def _get_xml_deserializer_name(prop: Property) -> Optional[str]: # pylint: disable=too-many-return-statements
Expand Down Expand Up @@ -122,6 +122,15 @@ def input_documentation_string(prop: Property) -> list[str]:
# building the param line of the property doc
return _documentation_string(prop, "keyword", "paramtype")

def keyword_documentation_string(self, model: ModelType) -> list[str]:
# building the ``:keyword:``/``:paramtype:`` lines for the keyword-only
# arguments of the generated ``__init__`` overload so that the docstring
# keeps a 1:1 correlation with the keyword-only arguments in the signature.
retval: list[str] = []
for prop in sorted(self._init_line_parameters(model), key=lambda x: x.optional):
retval.extend(self.input_documentation_string(prop))
return retval

@staticmethod
def variable_documentation_string(prop: Property) -> list[str]:
return _documentation_string(prop, "ivar", "vartype")
Expand Down Expand Up @@ -199,6 +208,16 @@ def pylint_disable_items(self, model: ModelType) -> list[str]:
def pylint_disable(self, model: ModelType) -> str:
return " # pylint: disable=" + ", ".join(self.pylint_disable_items(model))

def class_pylint_disable(self, model: ModelType) -> str:
"""Class-level pylint disables for the model declaration line."""
retval = model.pylint_disable()
# When the model's only constructor is ``def __init__(self, *args, **kwargs)`` (i.e. no
# typed overload is generated), the guideline checker reports the ``*args`` vararg as an
# undocumented param. There is no meaningful param to document, so silence the check.
if not self.need_init(model) and self.initialize_properties(model):
retval = add_to_pylint_disable(retval, "docstring-missing-param")
return retval

def global_pylint_disables(self) -> str:
return ""

Expand Down Expand Up @@ -244,7 +263,7 @@ def declare_model(self, model: ModelType) -> str:
)
if model.parents:
basename = ", ".join([m.name for m in model.parents])
return f"class {model.name}({basename}):{model.pylint_disable()}"
return f"class {model.name}({basename}):{self.class_pylint_disable(model)}"

@staticmethod
def get_properties_to_initialize(model: ModelType) -> list[Property]:
Expand Down Expand Up @@ -407,7 +426,7 @@ def declare_model(self, model: ModelType) -> str:
basename = ", ".join([m.name for m in model.parents])
if model.discriminator_value:
basename += f", discriminator='{model.discriminator_value}'"
return f"class {model.name}({basename}):{model.pylint_disable()}"
return f"class {model.name}({basename}):{self.class_pylint_disable(model)}"

@staticmethod
def get_properties_to_declare(model: ModelType) -> list[Property]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,15 @@ def _is_readonly(p):


class SdkJSONEncoder(JSONEncoder):
"""A JSON encoder that's capable of serializing datetime objects and bytes."""
"""A JSON encoder that's capable of serializing datetime objects and bytes.

:param args: Additional positional arguments passed to the base ``JSONEncoder``.
:type args: typing.Any
:keyword exclude_readonly: Whether to exclude readonly properties. Defaults to False.
:paramtype exclude_readonly: bool
:keyword format: The format to use for serialization. Defaults to None.
:paramtype format: typing.Optional[str]
"""

def __init__(self, *args, exclude_readonly: bool = False, format: typing.Optional[str] = None, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
{% endfor %}
{% endfor %}
{% endif %}
{% if serializer.need_init(model) %}
{% for line in serializer.keyword_documentation_string(model) %}
{{ macros.wrap_model_string(line, '\n ') -}}
{% endfor %}
{% endif %}
"""

{% if model.is_polymorphic %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,11 @@ def _decode_attribute_map_key(key):


class Serializer: # pylint: disable=too-many-public-methods
"""Request object model serializer."""
"""Request object model serializer.

:param classes: Mapping of model names to model types, used to resolve models during serialization.
:type classes: typing.Optional[typing.Mapping[str, type]]
"""

basic_types = {str: "str", int: "int", bool: "bool", float: "float"}

Expand Down
2 changes: 1 addition & 1 deletion packages/http-client-python/tests/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ deps =
-r {tox_root}/requirements/azure.txt
-e {tox_root}/../generator
commands =
uv pip install azure-pylint-guidelines-checker==0.5.2 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
uv pip install azure-pylint-guidelines-checker==0.5.9 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
python {tox_root}/install_packages.py azure {tox_root}
python {tox_root}/../eng/scripts/ci/run_pylint.py -t azure -s generated {posargs}

Expand Down
Loading