Repository navigation
Conversation
The examples were indented code, so the API reference site would render their comment lines as headings and their code as prose. Each one is now an Examples section with its code in a fenced python block, which ruff formats (docstring-code-format). The `Usage example:` headings, which are not a Google docstring section, become `Examples:`. Fixes in the example code: - `elements.loginAs()` is `elements.login_as()`. - The blocking client's `pdp_api` example called `role_assignments(...)`, which is not callable; it now calls `role_assignments.list()`, as the async client's example does. - The blocking client's `bulk_check()` example no longer awaits. - The `bulk_check()` examples' dicts keyed by the names `type` and `key` now use the strings "type" and "key". Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Enforcer's check(), bulk_check() and authorized_users() examples get the same fenced Examples sections as the clients' (bulk_check()'s dict keys become strings there too). - decimal_encoder()'s doctest was plain text, which Markdown renders as a blockquote. It moves to an Examples section, fenced as pycon so the prompts and outputs stay as they are. - ApiContext's code block was glued to the paragraph above it and had no language; it gets a blank line and `python`. Regenerate permit/_sync_types.pyi, which copies the async docstrings into the blocking stubs. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Griffe's Google parser reads each line at an item's own indentation as a new item, so these Returns and Yields sections rendered as several rows of sentence fragments. Their continuation lines are now indented under the item: - Permit.wait_for_sync() (Yields) - GroupsApi.list() and GroupsApi.get() - ResourceRelationsApi.list() - PdpsApi.refresh() - Enforcer.get_user_tenants() - permit.utils.sync.async_to_sync() Regenerate permit/_sync_types.pyi, which copies the async docstrings into the blocking stubs. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResourceInstancesApi.bulk_delete() documented `resource_instances` over three lines at the same indentation, so Griffe read the second line as a parameter named "Each identity can be either `resource_type" and failed to parse the third: three warnings, each repeated for the generated stub. The entry is rewrapped as one parameter with its continuation lines indented, with the same words. Regenerate permit/_sync_types.pyi, which copies the async docstrings into the blocking stubs. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- RolesApi.assign_permissions() and remove_permissions(), and ConditionSetRulesApi.list(): the placeholders <resourceKey:actionKey> and <resource>:<action> were read as HTML tags and dropped from the page. They are inline code now. - SYNC_WRAPPER_MARKER: the reST roles :func: and :class: were printed as is. The names are plain inline code now. - ApiContext: the list of context levels had no blank line above it, so Python-Markdown folded it into the paragraph. Regenerate permit/_sync_types.pyi, which copies the async docstrings into the blocking stubs. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Griffe's default Google options, a Returns or Yields item written as `type: description` is read as a value named after the type when the type is a bare name (`bool:`, `dict:`, `Permit:`, `AuthorizedUsersResult:`), and keeps the type at the start of its description otherwise (`list[bool]:` and the like). The reference site would show a return value named "bool", or repeat the type in the description. The items now hold the description alone; the type comes from the return annotation, as for the SDK's other Returns sections. The items are the Returns of check(), bulk_check(), authorized_users(), get_user_permissions(), get_user_tenants() and filter_objects() on permit.Permit and permit.sync.Permit, the Yields of Permit.wait_for_sync(), and the Returns of the Enforcer's check(), bulk_check(), authorized_users() and filter_objects(). Regenerate permit/_sync_types.pyi, which copies the async docstrings into the blocking stubs. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_LoopThread._track and _LoopThread.submit had Returns items whose second line was not indented, so Griffe split each into two return values. They are private, but async_to_sync in the same module had the same defect fixed, and an unindented continuation line breaks the item for any Google-style renderer. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The closing ``` sat on the line after the last expected output, so doctest read it as part of that output and the example failed under `python -m doctest` or `pytest --doctest-modules`. A blank line now ends the output first; the rendered pycon block is unchanged. Refs PER-16775. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependency Security AuditScanned: pyproject.toml dependencies + dev group, resolved at Python 3.10 (the current resolution, and the lowest versions the published specs permit under each pydantic major) ✅ No known vulnerabilities found. Both the resolved dependency set and the lowest versions the published specs permit are clean at HIGH and CRITICAL. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear issues
awaitexamples.Stacked on #150.
Why
The API reference site will be generated with Zensical and mkdocstrings-python. mkdocstrings parses docstrings with Griffe's Google parser and renders them as Markdown. At the base, many docstrings rendered wrongly there:
#comment lines rendered as headings and their code as prose. 7 of them were headedUsage example:, which is not a Google section, so Griffe folded them into the description.ResourceInstancesApi.bulk_delete()Args entry, in the source and in the generated stub.type: descriptionshowed a return value named after the type.<...>placeholders were dropped as HTML tags.::literal block printed as written.This PR changes docstrings only, so the site PR can build with no docstring warnings.
What changed
Examples. Every example section is now a Google
Examples:section with its code in a fencedpythonblock: 8 inpermit/permit.py, 9 inpermit/sync.pyand 3 inpermit/enforcement/enforcer.py. The 7Usage example:headings are nowExamples:. Three more snippets are fenced:::block in thePermitclass docstring;ApiContextsnippet, which had no language and no blank line above it;decimal_encoder()doctest, which is now an Examples section fenced aspyconand still passes as a doctest.Ruff's
docstring-code-formatformats the fenced code (double quotes, thebulk_check()list layout).Example code that did not match the API:
permit.elements.loginAs(...)is nowpermit.elements.login_as(...), on both clients.permit.pdp_api.role_assignments(...), which is not callable, is nowpermit.pdp_api.role_assignments.list().bulk_check()example no longer usesawait.bulk_check()examples' dicts keyed by the bare namestypeandkeynow use the strings"type"and"key"(Permit, syncPermitandEnforcer).bulk_delete()Args. The continuation lines of theresource_instancesentry are indented under it. The words are the same.Returns and Yields.
Permit.wait_for_sync()(Yields),GroupsApi.list()andget(),ResourceRelationsApi.list(),PdpsApi.refresh(),Enforcer.get_user_tenants(),async_to_sync(), and the private_LoopThread._track()andsubmit().type: descriptionnow hold only the description. The type comes from the return annotation, as in the SDK's other Returns sections.Markup.
<resourceKey:actionKey>(2 inRolesApi) and<resource>:<action>(ConditionSetRulesApi.list()) are inline code.:func:and:class:roles in theSYNC_WRAPPER_MARKERdocstring are inline code.ApiContextlist has the blank line it needs to render as a list.Stub.
permit/_sync_types.pyiis regenerated withuv run python scripts/generate_sync_stubs.pyin each commit that changes a docstring it copies, so every commit passes the stub drift test.Behaviour changes
None. Only docstrings and the regenerated stub change. For each of the 13 changed files,
ast.dumpof the module with every docstring removed is identical to the base.How it was tested
Griffe 2.3.0,
uvx --exclude-newer 2026-09-26 --from griffe griffe dump permit -s . -d google -f -o /dev/null: 0 warnings, against 6 at the base.A scratch script walked every docstring Griffe parses: all of
permit/except the generatedpermit/api/models.py.Usage example:headingspython, 1pycon)Rendering every section with Python-Markdown and
pymdownx.superfencesgives 38<h1>-<h6>elements at the base and 0 here.Every fenced block parses: the
pythonblocks withPyCF_ALLOW_TOP_LEVEL_AWAIT, and thepyconblock's doctests pass. The blocks make 41permit.calls, which were resolved on realpermit.Permitandpermit.sync.Permitinstances. All of them exist, and each is awaited exactly when it is a coroutine, except 7 calls in the stub'sSyncEnforcercopies (see below).uv run pre-commit run --all-files: all hooks pass.uv run mypyanduv run --group pydantic-v1 mypy: no issues in 123 source files.uv run pytest -q -m 'not e2e': 1918 passed, 3 skipped (the pydantic 1 tests), 45 deselected. This includes the sync-stub drift test andtests/test_typing_surface.py.Each of the 9 commits passes ruff and
tests/test_typing_surface.pyon its own.The checks catch regressions. Each of these made its check fail: an unfenced example, a misspelled method, a stray
await, an unindented Args line, a closing fence right after doctest output, and a code change outside docstrings. Editing anEnforcerdocstring without regenerating the stub fails the drift test.Not changed: the
SyncEnforcerexamples. Type checkers and IDEs resolvepermit.enforcement.enforcer.SyncEnforcerto the stub class, and the stub generator copies its docstrings word for word fromEnforcer. So its examples inpermit/_sync_types.pyistillawaitits blocking methods, as at the base. Griffe treats that name as non-public, so the site does not show it. Fixing the examples means changingscripts/generate_sync_stubs.py, which is outside this PR; PER-16803 tracks it.Owner actions before merge
None for this PR. For the site PR:
returns_named_value: true, or rewrap the first Returns line ofpermit.utils.sync.async_to_sync(), which contains a colon.pymdownx.superfences, because the fences rely on it.See Also:section ofPermit.wait_for_sync()renders. Griffe turns it into an admonition, and its bare URL becomes a link only with an autolink or magiclink extension.🤖 Generated with Claude Code