diff --git a/.github/actions/publish-api-docs/action.yml b/.github/actions/publish-api-docs/action.yml new file mode 100644 index 0000000..f27de82 --- /dev/null +++ b/.github/actions/publish-api-docs/action.yml @@ -0,0 +1,123 @@ +name: Create API Docs PR +description: Generates API docs for all packages and creates a pull request in SAP/ai-sdk. + +inputs: + token: + description: 'GitHub App token with write access to SAP/ai-sdk.' + required: true + bot-email: + description: 'Email for the bot git config.' + required: true + bot-name: + description: 'Name for the bot git config.' + required: true + +runs: + using: composite + steps: + - name: Get versions from pyproject.toml + id: versions + shell: bash + run: | + get_major() { + grep -m1 '^version' "packages/$1/pyproject.toml" \ + | sed 's/version = "\(.*\)"/\1/' \ + | cut -d. -f1 + } + echo "base-major=$(get_major base)" >> "$GITHUB_OUTPUT" + echo "core-major=$(get_major core)" >> "$GITHUB_OUTPUT" + echo "gen-major=$(get_major gen)" >> "$GITHUB_OUTPUT" + + - name: Generate pydoc docs for base and core + shell: bash + run: | + generate_docs() { + ( + cd "packages/$1/docs" + uv run make + ) + } + generate_docs base + generate_docs core + + - name: Build Sphinx docs for gen + shell: bash + working-directory: packages/gen/docs + run: uv run make + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: SAP/ai-sdk + path: ai-sdk + token: ${{ inputs.token }} + persist-credentials: true + + - name: Copy API docs + shell: bash + env: + BASE_MAJOR: ${{ steps.versions.outputs.base-major }} + CORE_MAJOR: ${{ steps.versions.outputs.core-major }} + GEN_MAJOR: ${{ steps.versions.outputs.gen-major }} + run: | + rsync -avz --delete packages/base/docs/build/html/ "ai-sdk/static/api/python/base/v${BASE_MAJOR}/" + rsync -avz --delete packages/core/docs/build/html/ "ai-sdk/static/api/python/core/v${CORE_MAJOR}/" + rsync -avz --delete packages/gen/docs/build/html/ "ai-sdk/static/api/python/gen/v${GEN_MAJOR}/" + + - name: Check for changes + id: check-changes + shell: bash + working-directory: ai-sdk + run: | + if test -z "$(git status --porcelain)"; then + echo "docs=false" >> "$GITHUB_OUTPUT" + else + echo "docs=true" >> "$GITHUB_OUTPUT" + fi + + - name: Commit and push changes + if: steps.check-changes.outputs.docs == 'true' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + BOT_EMAIL: ${{ inputs.bot-email }} + BOT_NAME: ${{ inputs.bot-name }} + BASE_MAJOR: ${{ steps.versions.outputs.base-major }} + CORE_MAJOR: ${{ steps.versions.outputs.core-major }} + GEN_MAJOR: ${{ steps.versions.outputs.gen-major }} + BRANCH_NAME: docs/api-update-python-${{ github.run_id }} + run: | + gh auth setup-git + + cd ai-sdk + git config user.email "$BOT_EMAIL" + git config user.name "$BOT_NAME" + git checkout -b "$BRANCH_NAME" + git add -A + git commit -m "Update SAP AI SDK for Python API documentation (base v${BASE_MAJOR}, core v${CORE_MAJOR}, gen v${GEN_MAJOR})" + git push -u origin "$BRANCH_NAME" + + - name: Create pull request + if: steps.check-changes.outputs.docs == 'true' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + BASE_MAJOR: ${{ steps.versions.outputs.base-major }} + CORE_MAJOR: ${{ steps.versions.outputs.core-major }} + GEN_MAJOR: ${{ steps.versions.outputs.gen-major }} + BRANCH_NAME: docs/api-update-python-${{ github.run_id }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + gh pr create \ + --repo SAP/ai-sdk \ + --base main \ + --head "$BRANCH_NAME" \ + --title "chore: Update API documentation (Python)" \ + --body "Update the API documentation for SAP Cloud SDK for AI, Python. + + | Package | Version | + |---------|---------| + | base | v${BASE_MAJOR} | + | core | v${CORE_MAJOR} | + | gen | v${GEN_MAJOR} | + + Automatically generated by the [api-docs workflow run]($WORKFLOW_URL)." diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index f0d9b49..4a551b1 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -41,6 +41,8 @@ runs: - name: Install uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #v8.2.0 + with: + uv-version: "0.11.16" - name: Install dependencies shell: bash env: @@ -51,10 +53,10 @@ runs: if [[ "$IS_FORK" == "true" ]]; then echo "Fork detected — syncing from PyPI without lockfile" UV_INDEX_URL="https://pypi.org/simple/" \ - uv sync --all-packages --all-extras + uv sync --all-packages --all-extras --no-group docs else echo "Syncing from Artifactory with lockfile" UV_INDEX_ARTIFACTORY_USERNAME="${INDEX_USERNAME}" \ UV_INDEX_ARTIFACTORY_PASSWORD="${INDEX_TOKEN}" \ - uv sync --all-packages --all-extras --locked + uv sync --all-packages --all-extras --locked --no-group docs fi diff --git a/.github/workflows/auto-doc.yml b/.github/workflows/auto-doc.yml index a6b7601..ac6d9e2 100644 --- a/.github/workflows/auto-doc.yml +++ b/.github/workflows/auto-doc.yml @@ -42,8 +42,7 @@ jobs: generate_docs() { ( cd packages/$1/docs - rm -f *.html - uv run python -m pydoc -w ../ || true + uv run make ) } @@ -59,6 +58,6 @@ jobs: if git diff --cached --quiet; then echo "No documentation changes to commit" else - git commit -m "docs: update pydoc3 documentation [skip ci]" + git commit -m "docs: update sphinx documentation [skip ci]" git push origin HEAD:"$HEAD_REF" fi diff --git a/packages/base/docs/Makefile b/packages/base/docs/Makefile new file mode 100644 index 0000000..b2fd32f --- /dev/null +++ b/packages/base/docs/Makefile @@ -0,0 +1,18 @@ +SHELL := /bin/bash + +.PHONY: all api_doc build comment_out_lines + +all: api_doc build comment_out_lines + +api_doc: + python -m sphinx.ext.apidoc -o source/_api_doc ../ai_api_client_sdk -d 1 -T -f -M + +build: + python -m sphinx -b html ./source ./build/html + +comment_out_lines: + @if [ "$$(uname)" = "Darwin" ]; then \ + sed -i '' '/window\.location/s/^/\/\/ /' ./build/html/_static/doctools.js; \ + else \ + sed -i '/window\.location/s/^/\/\/ /' ./build/html/_static/doctools.js; \ + fi diff --git a/packages/base/docs/source/_static/custom.css b/packages/base/docs/source/_static/custom.css new file mode 100644 index 0000000..b83ef55 --- /dev/null +++ b/packages/base/docs/source/_static/custom.css @@ -0,0 +1,115 @@ +/* Light theme */ +:root { + --mystnb-source-bg-color: #f8fafc; + --mystnb-source-color: #334155; + --mystnb-stdout-bg-color: #f0f7ff; + --mystnb-output-border-color: #bfdbfe; + --mystnb-border-color: #e2e8f0; +} + +/* Dark theme */ +.dark { + --mystnb-source-bg-color: #1e1e2e; + --mystnb-source-color: #cdd6f4; + --mystnb-stdout-bg-color: #171731; + --mystnb-output-border-color: #2e2e72; + --mystnb-border-color: #313244; +} + +.cell_input { + border: 1px solid var(--mystnb-border-color) !important; + border-radius: 8px; + margin: 1.5rem 0; + padding: 1px; +} + +.cell_output { + border: 1px solid var(--mystnb-output-border-color); + border-radius: 8px; + margin: 1.5rem 0; + padding: 1px; + overflow: hidden; + background: var(--mystnb-stdout-bg-color); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.cell_output .output.stream { + border: none; + margin: 0; + border-radius: 8px; +} + +.cell_input:focus-within { + outline: 2px solid var(--mystnb-source-color); + outline-offset: 2px; +} + +.highlight { + background-color: var(--mystnb-source-bg-color); + color: var(--mystnb-source-color); + scrollbar-width: thin; + scrollbar-color: var(--mystnb-source-color) transparent; + border-radius: 6px; + margin: 0; +} + +.cell_output .highlight { + background-color: var(--mystnb-stdout-bg-color); + border: none; + border-radius: 8px; + margin: -1px; +} + +.highlight::-webkit-scrollbar { + height: 6px; +} + +.highlight::-webkit-scrollbar-thumb { + background: var(--mystnb-source-color); + border-radius: 3px; +} + +/* Dark theme syntax */ +.dark .highlight .c1 { color: #7f849c; } +.dark .highlight .s1, .dark .highlight .s2 { color: #89dceb; } +.dark .highlight .k, .dark .highlight .kn { color: #f5c2e7; } +.dark .highlight .n { color: #cdd6f4; } +.dark .highlight .o { color: #89b4fa; } +.dark .highlight .p { color: #9399b2; } + +/* Light theme syntax */ +.highlight .c1 { color: #64748b; } +.highlight .s1, .highlight .s2 { color: #0369a1; } +.highlight .k, .highlight .kn { color: #be185d; } +.highlight .n { color: #334155; } +.highlight .o { color: #0284c7; } +.highlight .p { color: #475569; } + +button.copy { + background: transparent; + border: none; + opacity: 0.25; + transition: opacity 0.2s ease; +} + +button.copy:hover { + opacity: 1; +} + +/* autodoc related adjustments*/ + +/* autodoc generation for docstring causes no wordwrap */ +.sig-name, .viewcode-link, .py-attribute, .py-class, .py-function, .descclassname { + word-break: break-all; + white-space: normal !important; +} + +/* break word and avoid horizontal scrollbar */ +#left-sidebar { + word-break: break-word; +} + +main{ + word-wrap: break-word; +} + diff --git a/packages/base/docs/source/conf.py b/packages/base/docs/source/conf.py new file mode 100644 index 0000000..5d978ce --- /dev/null +++ b/packages/base/docs/source/conf.py @@ -0,0 +1,39 @@ +import pathlib +import sys +sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) + +import tomllib + +project = 'SAP Cloud SDK for AI (Python) - base' +copyright = '2026, SAP SE' +author = 'SAP SE' + +def get_version(): + pyproject = pathlib.Path(__file__).parents[2] / 'pyproject.toml' + with open(pyproject, 'rb') as f: + return tomllib.load(f)['project']['version'] + +release = get_version() + +extensions = [ + 'sphinx.ext.duration', + 'sphinx.ext.autodoc', +] + +templates_path = ['_templates'] +exclude_patterns = [] + +html_theme = 'sphinxawesome_theme' +html_static_path = ['_static'] +html_css_files = ['custom.css'] + +smartquotes = False +html_title = "SAP Cloud SDK for AI (Python) - base v" + release +html_permalinks = False + +autodoc_typehints = "description" +autodoc_class_signature = "separated" +add_module_names = False +autodoc_typehints_format = 'short' +autodoc_member_order = 'groupwise' +modindex_common_prefix = ['ai_api_client_sdk'] diff --git a/packages/base/docs/source/index.rst b/packages/base/docs/source/index.rst new file mode 100644 index 0000000..01d9552 --- /dev/null +++ b/packages/base/docs/source/index.rst @@ -0,0 +1,5 @@ +SAP Cloud SDK for AI (Python) - base +===================================== + +.. toctree:: + API Reference <./_api_doc/ai_api_client_sdk.rst> diff --git a/packages/base/pyproject.toml b/packages/base/pyproject.toml index e708884..1700c6e 100644 --- a/packages/base/pyproject.toml +++ b/packages/base/pyproject.toml @@ -36,6 +36,10 @@ dev = [ "pylint==4.0.5", "pytest-dotenv>=0.5.2", ] +docs = [ + "sphinx<9.0.0", + "sphinxawesome-theme", +] [tool.pytest.ini_options] env_files = [".env"] diff --git a/packages/core/docs/Makefile b/packages/core/docs/Makefile new file mode 100644 index 0000000..9c62d42 --- /dev/null +++ b/packages/core/docs/Makefile @@ -0,0 +1,18 @@ +SHELL := /bin/bash + +.PHONY: all api_doc build comment_out_lines + +all: api_doc build comment_out_lines + +api_doc: + python -m sphinx.ext.apidoc -o source/_api_doc ../ai_core_sdk -d 1 -T -f -M + +build: + python -m sphinx -b html ./source ./build/html + +comment_out_lines: + @if [ "$$(uname)" = "Darwin" ]; then \ + sed -i '' '/window\.location/s/^/\/\/ /' ./build/html/_static/doctools.js; \ + else \ + sed -i '/window\.location/s/^/\/\/ /' ./build/html/_static/doctools.js; \ + fi diff --git a/packages/core/docs/source/_static/custom.css b/packages/core/docs/source/_static/custom.css new file mode 100644 index 0000000..b83ef55 --- /dev/null +++ b/packages/core/docs/source/_static/custom.css @@ -0,0 +1,115 @@ +/* Light theme */ +:root { + --mystnb-source-bg-color: #f8fafc; + --mystnb-source-color: #334155; + --mystnb-stdout-bg-color: #f0f7ff; + --mystnb-output-border-color: #bfdbfe; + --mystnb-border-color: #e2e8f0; +} + +/* Dark theme */ +.dark { + --mystnb-source-bg-color: #1e1e2e; + --mystnb-source-color: #cdd6f4; + --mystnb-stdout-bg-color: #171731; + --mystnb-output-border-color: #2e2e72; + --mystnb-border-color: #313244; +} + +.cell_input { + border: 1px solid var(--mystnb-border-color) !important; + border-radius: 8px; + margin: 1.5rem 0; + padding: 1px; +} + +.cell_output { + border: 1px solid var(--mystnb-output-border-color); + border-radius: 8px; + margin: 1.5rem 0; + padding: 1px; + overflow: hidden; + background: var(--mystnb-stdout-bg-color); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.cell_output .output.stream { + border: none; + margin: 0; + border-radius: 8px; +} + +.cell_input:focus-within { + outline: 2px solid var(--mystnb-source-color); + outline-offset: 2px; +} + +.highlight { + background-color: var(--mystnb-source-bg-color); + color: var(--mystnb-source-color); + scrollbar-width: thin; + scrollbar-color: var(--mystnb-source-color) transparent; + border-radius: 6px; + margin: 0; +} + +.cell_output .highlight { + background-color: var(--mystnb-stdout-bg-color); + border: none; + border-radius: 8px; + margin: -1px; +} + +.highlight::-webkit-scrollbar { + height: 6px; +} + +.highlight::-webkit-scrollbar-thumb { + background: var(--mystnb-source-color); + border-radius: 3px; +} + +/* Dark theme syntax */ +.dark .highlight .c1 { color: #7f849c; } +.dark .highlight .s1, .dark .highlight .s2 { color: #89dceb; } +.dark .highlight .k, .dark .highlight .kn { color: #f5c2e7; } +.dark .highlight .n { color: #cdd6f4; } +.dark .highlight .o { color: #89b4fa; } +.dark .highlight .p { color: #9399b2; } + +/* Light theme syntax */ +.highlight .c1 { color: #64748b; } +.highlight .s1, .highlight .s2 { color: #0369a1; } +.highlight .k, .highlight .kn { color: #be185d; } +.highlight .n { color: #334155; } +.highlight .o { color: #0284c7; } +.highlight .p { color: #475569; } + +button.copy { + background: transparent; + border: none; + opacity: 0.25; + transition: opacity 0.2s ease; +} + +button.copy:hover { + opacity: 1; +} + +/* autodoc related adjustments*/ + +/* autodoc generation for docstring causes no wordwrap */ +.sig-name, .viewcode-link, .py-attribute, .py-class, .py-function, .descclassname { + word-break: break-all; + white-space: normal !important; +} + +/* break word and avoid horizontal scrollbar */ +#left-sidebar { + word-break: break-word; +} + +main{ + word-wrap: break-word; +} + diff --git a/packages/core/docs/source/conf.py b/packages/core/docs/source/conf.py new file mode 100644 index 0000000..704585a --- /dev/null +++ b/packages/core/docs/source/conf.py @@ -0,0 +1,39 @@ +import pathlib +import sys +sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) + +import tomllib + +project = 'SAP Cloud SDK for AI (Python) - core' +copyright = '2026, SAP SE' +author = 'SAP SE' + +def get_version(): + pyproject = pathlib.Path(__file__).parents[2] / 'pyproject.toml' + with open(pyproject, 'rb') as f: + return tomllib.load(f)['project']['version'] + +release = get_version() + +extensions = [ + 'sphinx.ext.duration', + 'sphinx.ext.autodoc', +] + +templates_path = ['_templates'] +exclude_patterns = [] + +html_theme = 'sphinxawesome_theme' +html_static_path = ['_static'] +html_css_files = ['custom.css'] + +smartquotes = False +html_title = "SAP Cloud SDK for AI (Python) - core v" + release +html_permalinks = False + +autodoc_typehints = "description" +autodoc_class_signature = "separated" +add_module_names = False +autodoc_typehints_format = 'short' +autodoc_member_order = 'groupwise' +modindex_common_prefix = ['ai_core_sdk'] diff --git a/packages/core/docs/source/index.rst b/packages/core/docs/source/index.rst new file mode 100644 index 0000000..f5af382 --- /dev/null +++ b/packages/core/docs/source/index.rst @@ -0,0 +1,5 @@ +SAP Cloud SDK for AI (Python) - core +===================================== + +.. toctree:: + API Reference <./_api_doc/ai_core_sdk.rst> diff --git a/packages/core/pyproject.toml b/packages/core/pyproject.toml index bc7784a..1d8ed45 100644 --- a/packages/core/pyproject.toml +++ b/packages/core/pyproject.toml @@ -37,6 +37,10 @@ dev = [ "pyhamcrest==2.1.0", "pytest-dotenv>=0.5.2", ] +docs = [ + "sphinx<9.0.0", + "sphinxawesome-theme", +] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/packages/gen/docs/Makefile b/packages/gen/docs/Makefile index b6df959..297e875 100644 --- a/packages/gen/docs/Makefile +++ b/packages/gen/docs/Makefile @@ -1,4 +1,5 @@ # Minimal makefile for Sphinx documentation +SHELL := /bin/bash # Define the source files and destination directory FILES := ../README_sphynx.md ../RELEASE_NOTES.md ./gen_ai_hub/examples/gen_ai_hub.ipynb \ @@ -29,11 +30,11 @@ copy_files: check_files # Generate API documentation with depth 1, force overwrite and no module index api_doc: - sphinx-apidoc -o source/_api_doc ../gen_ai_hub -d 1 -T -f -M + python -m sphinx.ext.apidoc -o source/_api_doc ../gen_ai_hub -d 1 -T -f -M # Build the documentation with Sphinx build: copy_files - sphinx-build -b html ./source ./build/html + python -m sphinx -b html ./source ./build/html # Open generated documentation in browser preview_html: all diff --git a/packages/gen/pyproject.toml b/packages/gen/pyproject.toml index 4dc9d9a..757a204 100644 --- a/packages/gen/pyproject.toml +++ b/packages/gen/pyproject.toml @@ -64,10 +64,12 @@ dev = [ "respx==0.23.1", "parameterized==0.9.0", "pillow==12.2.0", + "pytest-dotenv>=0.5.2", +] +docs = [ "sphinx<9.0.0", "myst_nb", "sphinxawesome-theme", - "pytest-dotenv>=0.5.2", ] [tool.pytest.ini_options] diff --git a/packages/gen/version.txt b/packages/gen/version.txt new file mode 100644 index 0000000..0ee843c --- /dev/null +++ b/packages/gen/version.txt @@ -0,0 +1 @@ +7.2.0 diff --git a/uv.lock b/uv.lock index 11654d7..adb94a0 100644 --- a/uv.lock +++ b/uv.lock @@ -4040,6 +4040,11 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-dotenv" }, ] +docs = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxawesome-theme" }, +] [package.metadata] requires-dist = [ @@ -4055,6 +4060,10 @@ dev = [ { name = "pytest-cov", specifier = "==7.1.0" }, { name = "pytest-dotenv", specifier = ">=0.5.2" }, ] +docs = [ + { name = "sphinx", specifier = "<9.0.0" }, + { name = "sphinxawesome-theme" }, +] [[package]] name = "sap-ai-sdk-core" @@ -4073,6 +4082,11 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-dotenv" }, ] +docs = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxawesome-theme" }, +] [package.metadata] requires-dist = [ @@ -4088,6 +4102,10 @@ dev = [ { name = "pytest-cov", specifier = "==7.1.0" }, { name = "pytest-dotenv", specifier = ">=0.5.2" }, ] +docs = [ + { name = "sphinx", specifier = "<9.0.0" }, + { name = "sphinxawesome-theme" }, +] [[package]] name = "sap-ai-sdk-gen" @@ -4132,7 +4150,6 @@ google = [ [package.dev-dependencies] dev = [ - { name = "myst-nb" }, { name = "parameterized" }, { name = "pillow" }, { name = "pylint" }, @@ -4142,6 +4159,9 @@ dev = [ { name = "pytest-dotenv" }, { name = "requests-mock" }, { name = "respx" }, +] +docs = [ + { name = "myst-nb" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://common.repositories.cloud.sap/artifactory/api/pypi/pypi-proxy/simple/" }, marker = "python_full_version >= '3.11'" }, { name = "sphinxawesome-theme" }, @@ -4175,7 +4195,6 @@ provides-extras = ["google", "amazon", "all"] [package.metadata.requires-dev] dev = [ - { name = "myst-nb" }, { name = "parameterized", specifier = "==0.9.0" }, { name = "pillow", specifier = "==12.2.0" }, { name = "pylint", specifier = "==4.0.5" }, @@ -4185,6 +4204,9 @@ dev = [ { name = "pytest-dotenv", specifier = ">=0.5.2" }, { name = "requests-mock", specifier = "==1.12.1" }, { name = "respx", specifier = "==0.23.1" }, +] +docs = [ + { name = "myst-nb" }, { name = "sphinx", specifier = "<9.0.0" }, { name = "sphinxawesome-theme" }, ]