diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 02421889d..38704c9a7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -54,12 +54,17 @@ jobs: python ${example} done + # mkdocs.yml inherits .provide/foundry/base-mkdocs.yml, which is + # gitignored because it is an extract of provide-foundry rather than + # source of ours. Without this the build dies at config load, before it + # reads a page -- which is what every run of this workflow has done. + - name: 🏗️ Extract docs scaffolding + run: uv run --group docs python scripts/extract_docs_scaffolding.py + + # Strict mode: any mkdocs warning fails the build, dangling links and + # griffe docstring complaints included. - name: 📚 Build and Validate MkDocs - run: | - source .venv/bin/activate - # Build with strict mode (fails on warnings) - mkdocs build --clean --strict - echo "✅ MkDocs build completed successfully with no warnings" + run: uv run --group docs mkdocs build --clean --strict - name: 🔗 Validate README Links run: | diff --git a/.gitignore b/.gitignore index 56d53666c..c6821897d 100644 --- a/.gitignore +++ b/.gitignore @@ -422,3 +422,9 @@ memray-output/ # Local act CI config (machine-specific) .actrc + +# Generated API reference. mkdocs-gen-files keeps these in a temp overlay +# during a build, but running .provide/foundry/gen_ref_pages.py directly +# writes them here for real -- 351 files that are not ours to commit. +docs/reference/provide/ +docs/reference/SUMMARY.md diff --git a/docs/reference/index.md b/docs/reference/index.md index 493c759f8..6b3e15160 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -65,7 +65,7 @@ from provide.foundation.eventsets.types import EventSet, EventMapping | Feature | Purpose | Documentation | |---------|---------|---------------| | `@injectable` | Mark classes for dependency injection | [hub docs](provide/foundation/hub/index.md) | -| `Container` | Dependency injection container | [container docs](provide/foundation/hub/container/index.md) | +| `Container` | Dependency injection container | [container docs](provide/foundation/hub/container.md) | | `EventSet` | Define custom event sets with emojis | [eventsets docs](provide/foundation/eventsets/index.md) | | `EventMapping` | Map events to emoji representations | [eventsets docs](provide/foundation/eventsets/index.md) | @@ -114,7 +114,7 @@ from provide.foundation.eventsets.types import EventSet, EventMapping For a complete hierarchical view of all modules, classes, and functions: -**[📑 Full Module Index](SUMMARY/)** - Complete navigation tree +**[📑 Full Module Index](SUMMARY.md)** - Complete navigation tree ## Module Count diff --git a/pyproject.toml b/pyproject.toml index 7ecc8ae2e..3579354d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,6 +118,17 @@ dev = [ ] docs = [ "provide-testkit[docs]>=0.4.0", + # mkdocs.yml inherits .provide/foundry/base-mkdocs.yml and runs + # .provide/foundry/gen_ref_pages.py, neither of which is in this repo: + # .gitignore excludes .provide/foundry/ because it is an extract of this + # package. scripts/extract_docs_scaffolding.py writes it, and needs the + # package installed to do so. + # + # Floored at 0.4.1, not 0.4.0: the gen_ref_pages.py that 0.4.0 extracts + # never invokes itself under mkdocs-gen-files, so the reference section + # comes out empty and --strict fails on the dangling links out of + # docs/reference/index.md. 0.4.1 is the first release where this builds. + "provide-foundry>=0.4.1", ] protobuf = [ "protobuf>=6.32.0", diff --git a/scripts/extract_docs_scaffolding.py b/scripts/extract_docs_scaffolding.py new file mode 100644 index 000000000..d4df3c8fc --- /dev/null +++ b/scripts/extract_docs_scaffolding.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) provide.io llc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Materialise the shared docs scaffolding that mkdocs.yml inherits from. + +mkdocs.yml opens with `INHERIT: .provide/foundry/base-mkdocs.yml`, and +.gitignore excludes `.provide/foundry/` because it is an extract of the +provide-foundry package rather than source of ours. Nothing in a fresh checkout +creates it, so `mkdocs build` there fails before reading a single page: + + Error: Inherited config file '.provide/foundry/base-mkdocs.yml' does not exist + +which is what every documentation CI run has done. Locally the directory +happens to be present, so the failure is invisible on a developer machine. + +Run this before mkdocs. It writes `.provide/foundry/` from the installed +provide-foundry: the base config, the theme referenced by `custom_dir`, the +shared partials, the docs helper scripts, and gen_ref_pages.py for the +mkdocs-gen-files plugin. + +Exit codes: + 0 - scaffolding extracted + 1 - provide-foundry is not installed +""" + +from __future__ import annotations + +from pathlib import Path +import sys + + +def main() -> int: + """Extract the docs scaffolding into the current working directory.""" + try: + from provide.foundry.config import extract_base_mkdocs + except ImportError: + print( + "provide-foundry is not installed, so the docs scaffolding cannot be\n" + "extracted and `mkdocs build` will fail on its INHERIT line.\n" + "It belongs to the `docs` dependency group: `uv sync --group docs`.", + file=sys.stderr, + ) + return 1 + + base_mkdocs = extract_base_mkdocs(Path.cwd()) + print(f"✅ Docs scaffolding extracted to {base_mkdocs.parent}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + +# 🧱🏗️🔚 diff --git a/src/provide/foundation/errors/handlers.py b/src/provide/foundation/errors/handlers.py index 9a2ed401a..4e2b25bd8 100644 --- a/src/provide/foundation/errors/handlers.py +++ b/src/provide/foundation/errors/handlers.py @@ -196,7 +196,7 @@ def handle_error( The fallback value if not re-raising. Raises: - The original error if reraise=True. + Exception: The error passed in, re-raised unchanged, when reraise=True. Examples: >>> try: @@ -277,7 +277,8 @@ def handle(self, error: Exception) -> Any: Result from the handler function. Raises: - The original error if reraise_unhandled=True and no handler matches. + Exception: The error passed in, re-raised unchanged, when + reraise_unhandled=True and no policy matches it. Examples: >>> result = handler.handle(ValidationError("Invalid")) diff --git a/src/provide/foundation/hub/decorators.py b/src/provide/foundation/hub/decorators.py index 955497272..5993dc619 100644 --- a/src/provide/foundation/hub/decorators.py +++ b/src/provide/foundation/hub/decorators.py @@ -115,10 +115,11 @@ def container_group(): category: Command category for grouping group: Whether this is a command group (not a command) replace: Whether to replace existing registration - force_options: If True, all parameters with defaults become --options - (disables Position-Based Hybrid for first parameter) registry: Custom registry (defaults to global) - **metadata: Additional metadata stored in CommandInfo.metadata + **metadata: Additional metadata stored in CommandInfo.metadata. The CLI + builder reads `force_options` from here: if True, every parameter + with a default becomes a --option, disabling the Position-Based + Hybrid rule for the first parameter. Returns: Decorator function or decorated function diff --git a/src/provide/foundation/resilience/retry.py b/src/provide/foundation/resilience/retry.py index 745eeb56f..da699af02 100644 --- a/src/provide/foundation/resilience/retry.py +++ b/src/provide/foundation/resilience/retry.py @@ -223,7 +223,8 @@ def execute_sync(self, func: Callable[..., T], *args: Any, **kwargs: Any) -> T: Result from successful execution Raises: - Last exception if all retries are exhausted + Exception: The last exception raised by func, once every attempt + allowed by the policy has been used. """ last_exception = None @@ -295,7 +296,8 @@ async def execute_async(self, func: Callable[..., Awaitable[T]], *args: Any, **k Result from successful execution Raises: - Last exception if all retries are exhausted + Exception: The last exception raised by func, once every attempt + allowed by the policy has been used. """ last_exception = None diff --git a/src/provide/foundation/utils/scoped_cache.py b/src/provide/foundation/utils/scoped_cache.py index ea5163494..f8f8cfea6 100644 --- a/src/provide/foundation/utils/scoped_cache.py +++ b/src/provide/foundation/utils/scoped_cache.py @@ -80,8 +80,6 @@ def scope(self) -> Generator[None]: Yields: None (use cache methods within the context) - Raises: - No exceptions - cleanup is guaranteed even on errors """ if self._context_var.get() is None: # No existing cache - create new scope diff --git a/uv.lock b/uv.lock index b96971fb2..84c89dab5 100644 --- a/uv.lock +++ b/uv.lock @@ -1928,6 +1928,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] +[[package]] +name = "mkdocs-autolinks-plugin" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/b7/efc75b7870a4fecbc9a05ba94ce622462a40b5a13670d00036c5b47bf82f/mkdocs-autolinks-plugin-0.7.1.tar.gz", hash = "sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151", size = 4375, upload-time = "2023-08-04T14:42:25.67Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/9c/ee3d81a799b8b0b73a89625bcbf3c58cd369c8a26a1b415413edea9bf259/mkdocs_autolinks_plugin-0.7.1-py3-none-any.whl", hash = "sha256:5c6c17f6649b68e79a9ef0b2648d59f3072e18002b90ee1586a64c505f11ab12", size = 4235, upload-time = "2023-08-04T14:42:23.955Z" }, +] + [[package]] name = "mkdocs-autorefs" version = "1.4.4" @@ -2119,6 +2131,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/cd/2e8d0d92421916e2ea4ff97f10a544a9bd5588eb747556701c983581df13/mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6", size = 6723, upload-time = "2024-01-29T16:11:31.851Z" }, ] +[[package]] +name = "mkdocs-monorepo-plugin" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "python-slugify" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/6a/a75245020e44beb9d7c806158f8a2cda37597711409d40c5a37c70078a7e/mkdocs-monorepo-plugin-1.1.2.tar.gz", hash = "sha256:09200bcf837ad35070e6da973aa0cb682e69ed6e16f254a30584550c6d2d8ebb", size = 13723, upload-time = "2025-06-05T19:09:45.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/26/4f4c19457d1d4e6d571a3b092921b7a0ce9477d18d997755ac615d72b96b/mkdocs_monorepo_plugin-1.1.2-py3-none-any.whl", hash = "sha256:4b917bc224b89e34e1736bb31ad5ae9deb0a907da879e03bb9454b41fb8b1cac", size = 14539, upload-time = "2025-06-05T19:09:43.74Z" }, +] + [[package]] name = "mkdocs-redirects" version = "1.2.3" @@ -2613,6 +2638,7 @@ dev = [ { name = "provide-testkit", extra = ["advanced-testing", "build", "standard", "typecheck", "utils"] }, ] docs = [ + { name = "provide-foundry" }, { name = "provide-testkit", extra = ["docs"] }, ] protobuf = [ @@ -2647,9 +2673,37 @@ dev = [ { name = "memray", marker = "sys_platform != 'win32'", specifier = ">=1.0" }, { name = "provide-testkit", extras = ["advanced-testing", "build", "standard", "typecheck", "utils"], specifier = ">=0.4.0" }, ] -docs = [{ name = "provide-testkit", extras = ["docs"], specifier = ">=0.4.0" }] +docs = [ + { name = "provide-foundry", specifier = ">=0.4.1" }, + { name = "provide-testkit", extras = ["docs"], specifier = ">=0.4.0" }, +] protobuf = [{ name = "protobuf", specifier = ">=6.32.0" }] +[[package]] +name = "provide-foundry" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mike" }, + { name = "mkdocs" }, + { name = "mkdocs-autolinks-plugin" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-htmlproofer-plugin" }, + { name = "mkdocs-literate-nav" }, + { name = "mkdocs-macros-plugin" }, + { name = "mkdocs-material" }, + { name = "mkdocs-monorepo-plugin" }, + { name = "mkdocs-section-index" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "provide-foundation" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/ec/e2b4d6313243939e85f35306580b5d220010f6224e43aa8482b013429f05/provide_foundry-0.4.1.tar.gz", hash = "sha256:6f0834195e2d2505d2daab339ec9f11890ea4f685407877e93201688c554cdd2", size = 75117, upload-time = "2026-08-27T08:47:57.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/e4/5e961a4b8c592d1700597b15f8fb4a1cf65a99218a0d107c39bbb1bad560/provide_foundry-0.4.1-py3-none-any.whl", hash = "sha256:e74fa8195f3513487140df4b5063b88697b8aa8eebb2b2a4f2a960f1254df011", size = 88266, upload-time = "2026-08-27T08:47:56.215Z" }, +] + [[package]] name = "provide-testkit" version = "0.4.3" @@ -3094,6 +3148,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, ] +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -3507,6 +3573,15 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/c8d6d60a597c693559dab3b3362bd01e2212530e9a163eb0164af81e1ec1/TestSlide-2.7.1.tar.gz", hash = "sha256:d25890d5c383f673fac44a5f9e2561b7118d04f29f2c2b3d4f549e6db94cb34d", size = 50255, upload-time = "2023-03-16T14:09:41.204Z" } +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + [[package]] name = "textual" version = "8.2.8"