Skip to content

Commit 90ed9d3

Browse files
iscai-msftiscai-msft
andauthored
[python] add sphinx check to pipeline (#8810)
Co-authored-by: iscai-msft <[email protected]>
1 parent d159842 commit 90ed9d3

File tree

10 files changed

+226
-18
lines changed

10 files changed

+226
-18
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: internal
3+
packages:
4+
- "@typespec/http-client-python"
5+
---
6+
7+
add sphinx check to pipelines

cspell.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ words:
117117
- jdwp
118118
- jobject
119119
- Johan
120+
- jquery
120121
- jsyaml
121122
- keyer
122123
- killpg
@@ -196,6 +197,7 @@ words:
196197
- pycache
197198
- pyexpat
198199
- pygen
200+
- pygments
199201
- pyimport
200202
- pylint
201203
- pylintrc
@@ -261,6 +263,7 @@ words:
261263
- Uncapitalize
262264
- uncollapsed
263265
- undifferentiable
266+
- undoc
264267
- Ungroup
265268
- uninstantiated
266269
- unioned

packages/http-client-python/emitter/src/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ function parseToken(token: Token): string {
323323
if (codeBlockStyle === undefined) {
324324
codeBlockStyle = token.raw.split("\n")[0].replace("```", "").trim();
325325
}
326+
// Convert invalid Pygments lexer names to valid ones
327+
if (codeBlockStyle === "txt") {
328+
codeBlockStyle = "text";
329+
}
326330
parsed += `\n\n.. code-block:: ${codeBlockStyle ?? ""}\n\n ${token.text.split("\n").join("\n ")}`;
327331
break;
328332
case "link":

packages/http-client-python/eng/pipeline/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ extends:
3030
LanguageShortName: "python"
3131
CadlRanchName: "@typespec/http-client-python"
3232
EnableCadlRanchReport: false
33-
PythonVersion: "3.9"
33+
PythonVersion: "3.11"

packages/http-client-python/eng/pipeline/templates/ci-stages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ stages:
2626
Condition: ${{ parameters.Condition }}
2727
DependsOn: ${{ parameters.DependsOn }}
2828
LanguageShortName: "python"
29-
PythonVersion: "3.9"
29+
PythonVersion: "3.11"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python
2+
3+
# --------------------------------------------------------------------------------------------
4+
# Copyright (c) Microsoft Corporation. All rights reserved.
5+
# Licensed under the MIT License. See License.txt in the project root for license information.
6+
# --------------------------------------------------------------------------------------------
7+
8+
# Central Sphinx configuration for all generated packages
9+
10+
import sys
11+
from pathlib import Path
12+
13+
# Basic project information
14+
project = "Generated Python SDK"
15+
copyright = "Microsoft Corporation"
16+
author = "Microsoft Corporation"
17+
18+
# Sphinx extensions
19+
extensions = [
20+
"sphinx.ext.autodoc",
21+
"sphinx.ext.napoleon",
22+
"sphinx.ext.viewcode",
23+
]
24+
25+
# Napoleon settings for Google and NumPy style docstrings
26+
napoleon_google_docstring = True
27+
napoleon_numpy_docstring = True
28+
napoleon_include_init_with_doc = True
29+
napoleon_include_private_with_doc = False
30+
napoleon_include_special_with_doc = True
31+
napoleon_use_admonition_for_examples = False
32+
napoleon_use_admonition_for_notes = False
33+
napoleon_use_admonition_for_references = False
34+
napoleon_use_ivar = True
35+
napoleon_use_param = True
36+
napoleon_use_rtype = True
37+
napoleon_use_keyword = True
38+
39+
# Autodoc settings
40+
autodoc_default_options = {
41+
"members": True,
42+
"undoc-members": True,
43+
"show-inheritance": True,
44+
"special-members": "__init__",
45+
}
46+
47+
# HTML theme and output
48+
html_theme = "basic"
49+
html_static_path = []
50+
html_show_sourcelink = False
51+
html_show_sphinx = False
52+
53+
# Suppress warnings for missing references
54+
suppress_warnings = []
55+
56+
# Master document
57+
master_doc = "index"
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python
2+
3+
# --------------------------------------------------------------------------------------------
4+
# Copyright (c) Microsoft Corporation. All rights reserved.
5+
# Licensed under the MIT License. See License.txt in the project root for license information.
6+
# --------------------------------------------------------------------------------------------
7+
8+
# This script is used to execute sphinx documentation build within a tox environment.
9+
# It uses a central sphinx configuration and validates docstrings by running sphinx-build.
10+
11+
from subprocess import check_call, CalledProcessError
12+
import os
13+
import logging
14+
import sys
15+
from pathlib import Path
16+
from util import run_check
17+
18+
logging.getLogger().setLevel(logging.INFO)
19+
20+
# Get the central Sphinx config directory
21+
SPHINX_CONF_DIR = os.path.abspath(os.path.dirname(__file__))
22+
23+
24+
def _create_minimal_index_rst(docs_dir, package_name, module_names):
25+
"""Create a minimal index.rst file for sphinx to process."""
26+
index_rst_content = f"""{package_name}
27+
{"=" * len(package_name)}
28+
29+
"""
30+
31+
for module_name in module_names:
32+
index_rst_content += f"""
33+
{module_name}
34+
{"-" * len(module_name)}
35+
36+
.. automodule:: {module_name}
37+
:members:
38+
:undoc-members:
39+
:show-inheritance:
40+
41+
"""
42+
43+
index_rst_path = docs_dir / "index.rst"
44+
with open(index_rst_path, "w") as f:
45+
f.write(index_rst_content)
46+
47+
48+
def _single_dir_sphinx(mod):
49+
"""Run sphinx-build on a single package directory."""
50+
51+
# Find the actual Python package directories
52+
package_dirs = [
53+
d for d in mod.iterdir() if d.is_dir() and not d.name.startswith("_") and (d / "__init__.py").exists()
54+
]
55+
56+
if not package_dirs:
57+
logging.info(f"No Python package found in {mod}, skipping sphinx build")
58+
return True
59+
60+
# Get the main package directory
61+
main_package = package_dirs[0]
62+
63+
# Find submodules
64+
module_names = []
65+
for item in main_package.iterdir():
66+
if item.is_dir() and (item / "__init__.py").exists():
67+
module_names.append(f"{main_package.name}.{item.name}")
68+
69+
# If no submodules, just use the main package
70+
if not module_names:
71+
module_names = [main_package.name]
72+
73+
# Create docs directory structure
74+
docs_dir = mod / "docs"
75+
docs_dir.mkdir(exist_ok=True)
76+
77+
# Create index.rst with the correct module names
78+
_create_minimal_index_rst(docs_dir, mod.stem, module_names)
79+
80+
# Set up output directory
81+
output_dir = mod / "_build" / "html"
82+
output_dir.mkdir(parents=True, exist_ok=True)
83+
84+
# Add the package to sys.path so sphinx can import it
85+
sys.path.insert(0, str(mod.absolute()))
86+
87+
try:
88+
result = check_call(
89+
[
90+
sys.executable,
91+
"-m",
92+
"sphinx",
93+
"-b",
94+
"html", # Build HTML output
95+
"-c",
96+
SPHINX_CONF_DIR, # Use central config
97+
"-W", # Treat warnings as errors
98+
"--keep-going", # Continue to get all errors
99+
"-E", # Don't use cached environment
100+
"-q", # Quiet mode (only show warnings/errors)
101+
str(docs_dir.absolute()), # Source directory
102+
str(output_dir.absolute()), # Output directory
103+
]
104+
)
105+
logging.info(f"Sphinx build completed successfully for {mod.stem}")
106+
return True
107+
except CalledProcessError as e:
108+
logging.error(f"{mod.stem} exited with sphinx build error {e.returncode}")
109+
return False
110+
finally:
111+
# Remove from sys.path
112+
if str(mod.absolute()) in sys.path:
113+
sys.path.remove(str(mod.absolute()))
114+
115+
116+
if __name__ == "__main__":
117+
run_check("sphinx", _single_dir_sphinx, "Sphinx documentation build")

packages/http-client-python/generator/test/azure/tox.ini

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
[tox]
2-
envlist=base, lint, mypy, pyright, apiview
2+
envlist=base, lint, mypy, pyright, apiview, sphinx
33
skipsdist=True
44

55
[testenv:ci]
66
deps=
77
-r requirements.txt
88
commands =
99
# pytest
10-
pytest mock_api_tests ../generic_mock_api_tests
10+
{[testenv:test]commands}
1111

1212
# pylint
13-
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/"
14-
python ../../../eng/scripts/ci/run_pylint.py -t azure -s "generated" {posargs}
13+
{[testenv:lint]commands}
1514

1615
# mypy
17-
python ../../../eng/scripts/ci/run_mypy.py -t azure -s "generated" {posargs}
16+
{[testenv:mypy]commands}
1817

1918
# pyright
20-
python ../../../eng/scripts/ci/run_pyright.py -t azure -s "generated" {posargs}
19+
{[testenv:pyright]commands}
2120

2221
# apiview
23-
pip install apiview-stub-generator==0.3.19 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
24-
python ../../../eng/scripts/ci/run_apiview.py -t azure -s "generated" {posargs}
22+
{[testenv:apiview]commands}
23+
24+
# sphinx docstring validation
25+
{[testenv:sphinx]commands}
2526

2627
[testenv:test]
2728
deps=
@@ -54,3 +55,10 @@ deps=
5455
commands =
5556
pip install apiview-stub-generator==0.3.19 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
5657
python ../../../eng/scripts/ci/run_apiview.py -t azure -s "generated" {posargs}
58+
59+
[testenv:sphinx]
60+
basepython = python3.10
61+
deps=
62+
-r requirements.txt
63+
commands =
64+
python ../../../eng/scripts/ci/run_sphinx_build.py -t azure -s "generated" {posargs}

packages/http-client-python/generator/test/dev_requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
aiohttp
33
pytest-asyncio==0.14.0
44
requests==2.32.2
5+
sphinx==8.2.0
6+
sphinx_rtd_theme==3.0.2
7+
myst_parser==4.0.1
8+
sphinxcontrib-jquery==4.1

packages/http-client-python/generator/test/unbranded/tox.ini

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
[tox]
2-
envlist=base, lint, mypy, pyright, apiview
2+
envlist=base, lint, mypy, pyright, apiview, sphinx
33
skipsdist=True
44

55
[testenv:ci]
66
deps=
77
-r requirements.txt
88
commands =
99
# pytest
10-
pytest mock_api_tests ../generic_mock_api_tests
10+
{[testenv:test]commands}
1111

1212
# pylint
13-
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/"
14-
python ../../../eng/scripts/ci/run_pylint.py -t unbranded -s "generated" {posargs}
13+
{[testenv:lint]commands}
1514

1615
# mypy
17-
python ../../../eng/scripts/ci/run_mypy.py -t unbranded -s "generated" {posargs}
16+
{[testenv:mypy]commands}
1817

1918
# pyright
20-
python ../../../eng/scripts/ci/run_pyright.py -t unbranded -s "generated" {posargs}
19+
{[testenv:pyright]commands}
2120

2221
# apiview
23-
pip install apiview-stub-generator==0.3.19 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
24-
python ../../../eng/scripts/ci/run_apiview.py -t unbranded -s "generated" {posargs}
22+
{[testenv:apiview]commands}
23+
24+
# sphinx docstring validation
25+
{[testenv:sphinx]commands}
2526

2627
[testenv:test]
2728
deps=
@@ -54,3 +55,10 @@ deps=
5455
commands =
5556
pip install apiview-stub-generator==0.3.19 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/"
5657
python ../../../eng/scripts/ci/run_apiview.py -t unbranded -s "generated" {posargs}
58+
59+
[testenv:sphinx]
60+
basepython = python3.10
61+
deps=
62+
-r requirements.txt
63+
commands =
64+
python ../../../eng/scripts/ci/run_sphinx_build.py -t unbranded -s "generated" {posargs}

0 commit comments

Comments
 (0)