Skip to content

Update the Python SDK docs for permit 3.0 and 3.1 - #662

Draft
zeevmoney wants to merge 64 commits into
masterfrom
per-16341/python-3-docs
Draft

zeevmoney wants to merge 64 commits into
masterfrom
per-16341/python-3-docs

Conversation

@zeevmoney

@zeevmoney zeevmoney commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Linear issues

  • PER-16341: Update the docs for the permit-python 3.0.0 and 3.1.0 releases and align every Python SDK snippet with them

Why

permit 3.0.0 is the current Python SDK release on PyPI, and 3.1.0 is next. The docs still described 2.8.3 in several places, and Python snippets across the site used calls that 3.x deprecates or that did not work as written:

  • The SDK parity and version tables, the install steps and the prerequisites did not reflect 3.x or its Python 3.10 requirement.
  • Two pages called the deprecated flat permit.api.sync_user().
  • The FastAPI quick-start's /register returned the SDK's models directly, which current FastAPI answers with 500.
  • Step 5 of the first-user walkthrough added an existing user to a second tenant with POST /tenants/{tenant}/users, which answers 409 for a key that already exists.
  • The PydanticAI tools caught PermitApiError, which check() and filter_objects() never raise.
  • Some Python examples did not parse because of lost indentation or stray leading spaces.
  • The features new in 3.1 had no docs.

What changed

permit 3.0

  • SDK overview: Python is ✅ for Role Derivations and Create User + Assign Roles. The version table lists the Python release, with a sentence on the Python 3.10 requirement and a link to the 3.0 migration guide at the v3.0.0 tag.
  • Cloud PDP capabilities: the Edge-PDP-only list adds POST /user-tenants, POST /allowed_url and the Local Facts API. It says that with proxy_facts_via_pdp=True the Python SDK sends facts reads to the PDP as well as writes, and that tenants.create_user() always goes to the Permit API.
  • Send Consistent Updates: the synced route list includes DELETE /facts/users/{user_id}/roles and DELETE /facts/role_assignments.
  • Python install steps and prerequisites: the shared asyncio and sync install steps state the Python 3.10 requirement before pip install permit, and note that pip on Python 3.8 or 3.9 installs 2.x. The policy-script page, the FastAPI, Flask and Django quick-starts and the AWS API Gateway page require Python 3.10 or later.
  • User permissions how-to: Python tabs for the basic call and the filter example.

permit 3.1 (each marked "permit 3.1 or later")

  • Groups API page: a Python section for permit.api.groups. The group-to-group cURL example sends the other group's key alone, because the type:instance form returns 404.
  • Python SDK examples: tenants.create_user(), permit.get_user_tenants(), list_detailed(), permit.api.pdps.refresh(), connection reuse with close() and async with/with, and the log settings. A table lists the APIs that permit 4.0 removes. It says which deprecations need 3.1, and gives the PermitException warning text and filter for 3.1 and for 3.0.
  • User permissions how-to: a Python tab for ABAC permissions with the context argument.
  • Cloud PDP capabilities, list role assignments, Send Consistent Updates: how the SDK reports routes that only a container PDP serves, which facts writes wait for the PDP, and what a sync timeout of 0 does.
  • SDK overview: Python is ✅ for Groups, Get User Tenant and Get User Permission (ABAC) from 3.1.0, and the version table lists 3.1.0.

Snippets aligned with 3.x

  • v2 migration guide and Access Request MCP implementation guide: permit.api.users.sync() replaces the flat permit.api.sync_user(). The v2 guide says the flat method takes the same single argument, is deprecated, and is removed in permit 4.0.
  • Quick-starts: FastAPI returns user.dict() and role_assignment.dict(). Flask uses the blocking client (permit.sync.Permit) and links the sync quickstart. Django and Flask call .dict() directly.
  • First-user walkthrough, step 5: the cURL assigns the manager role in the marketing tenant with POST /users/{id}/roles, as the Node.js tab does. The tenant users endpoint is gone from the page's endpoint table.
  • PydanticAI: the tools catch PermitConnectionError. A paragraph says when it is raised, says a timeout raises TimeoutError, and shows how to catch both. The versions note says the example's lock file installs permit 2.7.2.
  • LangChain: the version warning says langchain-permit 0.1.4 requires permit 2.x, so the tutorial stays on 2.x. It links "Staying on 2.x for now" in the migration guide.
  • AWS API Gateway: the Lambda layer is built for the Lambda runtime (manylinux, CPython 3.12, binary wheels only), with an arm64 variant. The authorizer denies a request that has no x-user-key header. The page names the blocking client.
  • Elements API snippets (src/components/elements/codeBlock.json): the Python login routes parse, and the client snippet uses from permit import Permit.
  • Smaller fixes: authorized-users and sync-applications-data say the blocking client takes the same calls without await. local-facts-uploader mentions the policy argument of wait_for_sync(). data-filtering says what the Python sample prints. The policy script's lists are typed. The ABAC check examples indent with spaces. Two Permit(...) placeholders get real arguments. The Flask example in the auth partial has its indentation back.

Formatting

  • Prettier 2.8.8 (.prettierrc.json) runs on every changed page and on codeBlock.json. Tables are realigned, and whitespace changes inside some JSON, JS and Python code blocks.
  • Two What's next lists keep one-line links under {/* prettier-ignore */}, so MDX does not wrap the link text in <p>.
  • The MCP implementation guide's install commands are indented to match their fences.
  • The auth partial's Node.js example keeps its original text under {/* prettier-ignore */}.

Behaviour changes

Most changes are to docs content. One data file under src/ changes: src/components/elements/codeBlock.json, which holds the Elements API snippets. These changes alter what the documented code does:

  • FastAPI quick-start: /register answers 200 with the user and the role assignment as JSON, instead of 500.
  • Flask quick-start: the app uses permit.sync.Permit and calls it directly, instead of wrapping each asyncio call in asyncio.run().
  • First-user walkthrough, step 5: the cURL sends POST /v2/facts/{proj}/{env}/users/user%7C123456789/roles with {"role": "manager", "tenant": "marketing"}.
  • AWS API Gateway: the authorizer returns a Deny policy without calling the PDP when x-user-key is missing, and the layer contains Linux wheels for the Lambda runtime.
  • PydanticAI: a PDP connection or status error becomes the tool's SecurityError.
  • Elements API: the Python login route snippets parse.

How it was tested

  • npm run build on the final head: exit 0.
    • Redirect lint, relative links and private paths (656 tracked files) pass.
    • The Docusaurus build succeeds. Its one webpack warning comes from src/components/code-block/CodeBlock.js, which this PR does not change.
    • Hyperlink checked 29,626 links in 2,114 files: 0 bad links, 0 bad anchors.
    • All 526 baseline routes resolve (0 new), and sidebar coverage passes (343 docs).
  • Prettier: prettier --check passes on all 24 changed files.
  • Python snippets: 106 Python blocks on the 23 changed pages, plus the 3 Python snippets in codeBlock.json.
    • All of them parse.
    • Each of the 81 blocks that use the SDK gets a preamble that creates the client its page uses. All 81 type-check with mypy 2.3.1 against the 3.1 SDK source, including the bodies of untyped functions. The one exception is the v2 guide's v1 sample, which shows the v1 API on purpose.
    • Against permit 3.0.0 from PyPI, 9 blocks fail. Each fails on a feature its page marks "permit 3.1 or later".
    • A scan for the 21 deprecated flat permit.api methods, tenants.add_user(), detailed_key= and PermitException finds only the v1 sample.
    • 7 of 7 mutations on copies of the snippets make a check fail: the flat sync_user() on two pages, a dropped await, tenants.add_user(), the Flask routes on the asyncio client, list(detailed=True), and stray leading spaces in an Elements snippet.
  • Claims checked by running the SDK:
    • permit 3.0.0 under -W error::DeprecationWarning: import permit fails with "Use PermitError instead" and passes with ignore:Use PermitError instead:DeprecationWarning. On the 3.1 source, the page's 3.1 filter silences from permit import PermitException.
    • Against a socket that accepts and never answers (pdp_timeout=1), check() and filter_objects() raise TimeoutError. A refused connection raises PermitConnectionError on 3.0.0 and on 2.7.2.
    • FastAPI quick-start on FastAPI 0.142.2: /register answers 200 with the expected JSON. Before the change it answered 500.
    • uv cannot resolve langchain-permit==0.1.4 together with permit>=3.0.0,<4. On its own it resolves to permit 2.8.3.
    • With permit 3.0.0 and proxy_facts_via_pdp=True, the facts reads sent GET /facts/... to the PDP. With the option off, they went to /v2/facts/... on the API.
    • The new user-permissions tabs ran with permit 3.0.0 on Python 3.10.19 and 3.13.9 against a stub PDP. 7 of 7 mutations made the run fail.
  • Rendering:
    • For the first formatting commit, I built the site before and after and compared the rendered articles of 360 pages. 354 are byte-identical, and the other 6 differ only in whitespace and commas inside code blocks.
    • For each later Prettier commit, I compiled the changed pages before and after with @mdx-js/mdx 3.1.0. The prose is identical, apart from the two What's next lists, which the prettier-ignore commit fixes. The final build has 0 links that wrap a <p>.

Owner actions before merge

  • Merge only after permit 3.1.0 is on PyPI.
  • Choose the migration guide link target. This PR pins it to the v3.0.0 tag, which never moves; main would pick up later edits.
  • Delete or fix these unused files, which are wrong for 3.x:
    • src/sdks/walkthroughs/authz-queries/getAuthorizedUsers/example.py is missing an await.
    • src/sdks/walkthroughs/sync-first-user/add-user-to-tenant/example.bash sends POST /tenants/{tenant}/users, which answers 409 for an existing user.
    • The bearer key in codeBlock.json tuple-unpacks login_as().
    • docs/tutorials/_connecting_to_authentication.mdx is not imported by any page.
  • Not verified without a backend: whether POST /users/{id}/roles answers 409 for a role the user already has. This decides whether the sync quickstart's "safe to restart" sentence needs a try/except.

🤖 Generated with Claude Code

zeevmoney and others added 10 commits October 1, 2026 12:27
The SDK parity table marked Python as missing two features that the
Python SDK has:

- Role Derivations: permit.api.resource_roles has
  create_role_derivation(), delete_role_derivation() and
  update_role_derivation_conditions().
- Create User + Assign Roles: UserCreate takes role_assignments, which
  permit.api.users.create() sends with the user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parity table was compared against permit 3.0.0, the current
release on PyPI, not 2.8.3. Say that 3.0.0 requires Python 3.10 or
later, and link the migration guide from 2.x, pinned to the v3.0.0
tag so the link keeps pointing at the 2.x-to-3.0 guide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Cloud PDP serves the check, bulk check, user permissions,
authorized users and AuthZen routes. It has no /user-tenants, no
/allowed_url and no /facts forwarding, so a call to them gets a 404.
The "APIs available only on an Edge PDP" list named only the
all-tenants check and /local/*. Add the three missing APIs, and add
user tenants and the URL mapping check to the comparison table (the
table already covers the facts API as Send Consistent Updates).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PDP waits for the data update on DELETE /facts/users/{user_id}/roles
and DELETE /facts/role_assignments, the routes that
permit.api.users.unassign_role() and permit.api.role_assignments.unassign()
call when proxy_facts_via_pdp is on. The route list left them out and a
separate sentence below it named them. Move them into the list under
Role Assignments and drop the sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The signature table lists permit.get_user_permissions(), but the page
had no Python example. Add a Python tab to the basic call and to the
filter example, after the Node.js tab. Both use the async client,
run with asyncio.run(), and pass the filters as keyword arguments.
The result is a dict keyed by tenant or resource instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.0.0 requires Python 3.10 or later (python_requires>=3.10).
On Python 3.8 or 3.9, pip install permit quietly installs a 2.x
release, so a reader on an older Python gets an SDK that the pages no
longer describe. The pages said "Python 3" or named no version.

- Python SDK install steps (asyncio and sync, shared by the SDK
  quickstarts and Connect your app): state the requirement and link
  the 3.0 migration guide for applications on 2.x.
- Policy script and the FastAPI, Flask and Django quickstarts: the
  prerequisite says Python 3.10 or later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With proxy_facts_via_pdp on, the Python SDK sends list and get calls
for users, tenants, role assignments, resource instances and
relationship tuples to the PDP's /facts/ routes, not only writes. The
Edge-PDP-only list said writes only, so a reader pointing the SDK at
the Cloud PDP would expect reads to keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The requirement sat after the install command, so a reader on Python
3.8 or 3.9 had already installed a 2.x release before reading it. Put
it in the install step's text, and keep the note about 2.x and the
migration guide link after the command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example passed the optional log setting. It now shows only the
token and the PDP address; the table above still documents `log`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CONTRIBUTING.md asks for Prettier-formatted .mdx, and all 11 pages
this PR changes already failed prettier --check on master. Run the
repo's Prettier (2.8.8, .prettierrc.json) on those files only.

Tables are realigned, trailing spaces are removed, list continuation
lines lose their indent, and JS and JSON code blocks get Prettier's
spacing and trailing commas. The rendered prose of every page is
unchanged; only whitespace and commas inside those code blocks differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for permitio-docs ready!

Name Link
🔨 Latest commit deebadc
🔍 Latest deploy log https://app.netlify.com/projects/permitio-docs/deploys/6ac118304f54da00085d28d9
😎 Deploy Preview https://deploy-preview-662--permitio-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@linear-code

linear-code Bot commented Oct 1, 2026

Copy link
Copy Markdown

PER-16341

zeevmoney and others added 18 commits October 2, 2026 18:13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The following commits change this page, and it failed prettier --check
on master. Run the repo's Prettier (2.8.8, .prettierrc.json) on it
first, so their diffs show only content. Only the endpoint table is
realigned; the rendered page is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The assign_group example sent {"group_instance_key": "group:org1"}.
The API looks the body's group up by instance key or ID among the
groups of the path group's resource type, so the type:instance form
matches no group and the request returns 404. The body now sends the
key alone, "org1". The Python SDK's end-to-end test of
groups.assign_group() checks both forms against the API.

The page also said only "org1 is assigned to support", which doesn't
say who gets whose roles. State the direction the same test confirms:
the members of the group in the path become members of the group in
the body and derive its roles, and not the other way round.

The note on path identifiers said a path takes resource_type:instance
only. It also takes the group's ID, or the instance key alone, which
names a group of the default "group" resource type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.groups, which sends the Groups API requests
the page describes. The new section sets up the page's marketing team
example from Python and maps each method to its endpoint. It states:

- the identifiers group_instance_key takes: the group's ID,
  "<type>:<key>", or the key alone for the default "group" type;
- that a group's role is a resource role on one instance, reached
  through ReBAC derivation, so a type-only check doesn't use it;
- the direction of assign_group(), and that its second argument takes
  the key alone or the ID;
- that list() takes no filters, and that the early-access list
  endpoints have no SDK method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.tenants.create_user() and
permit.get_user_tenants(). In the Python SDK examples:

- create_user() creates a new user as a member of a tenant, with no
  role there. The API returns 409 for an existing key, so it can't add
  an existing user to another tenant; assign_role() does that. It
  always goes to the Permit API, delete_tenant_user() returns 404 for
  a member with no role, and add_user() is a deprecated alias.
- get_user_tenants() asks the PDP for the tenants in which the user
  has a tenant-level role. Resource roles and role-less membership
  don't count, and the Cloud PDP returns 404, which the SDK raises as
  a PermitConnectionError.

The Cloud PDP capabilities page said /user-tenants lists the tenants
a user is "associated with". It lists the tenants in which the user
has a tenant-level role; name the Python method next to Java's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds a context argument to permit.get_user_permissions(),
so Python can request ABAC permissions with
enable_abac_user_permissions. The page said the Python function takes
no context and sent Python callers to the PDP API.

- Add context=None to the Python signature, with the version it needs.
- Say Node.js has no context argument, and that the Python SDK merges
  context over the context store's base context, as check() does; a
  call without context sends none.
- Add a Python tab to the ABAC example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds list_detailed() to permit.api.role_assignments,
resource_instances and relationship_tuples. Each takes the filters of
list() as keyword arguments and returns one page with the total count.
The Python SDK examples now show the role assignment call and list
what each method's results add over list(): the role, user, tenant
and resource instance as objects; an instance's relationships; and a
tuple's subject, relation, object and tenant details.

They also say that through the PDP (proxy_facts_via_pdp) only the last
value of a list filter is forwarded, and that
resource_instances.list(detailed_key=...) is deprecated in favour of
list_detailed().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.pdps.refresh(), which makes every PDP of
the environment fetch all of its data from Permit again instead of at
its next periodic update. Add a section with the call, and say that
it returns once the refresh is triggered, not finished; that reason
is optional and at most 512 characters; what the result holds; and
the API key it needs (403 for a read-only key, 404 for an environment
with no PDP configuration).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In permit 3.1 a client keeps one connection pool for the Permit API and
one for the PDP (per event loop on the asyncio client) and reuses them.
Add a section to the Python SDK examples that says closing is
optional, and shows async with Permit(...) and close() on both
clients. It covers what close() does, that the client stays usable
after it, loops you drive yourself, requests in flight, the blocking
client's background thread, and the client wait_for_sync() yields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next commit changes this page, and it failed prettier --check on
master. Run the repo's Prettier (2.8.8, .prettierrc.json) on it first,
so that commit's diff shows only content. Tables are realigned and the
JSON example is reindented to two spaces; the rendered prose is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 changes what the Python SDK does around routes only a
container PDP serves, and around proxy facts:

- The Cloud PDP's 404 for /user-tenants, /local/* and /facts/* is
  raised as an error that names the route and says it needs a
  container PDP (PermitConnectionError from get_user_tenants(),
  PermitApiError from pdp_api and the facts calls). Say so on the
  Cloud PDP capabilities, list role assignments and Send Consistent
  Updates pages.
- A client created with proxy_facts_via_pdp=True and the Cloud PDP
  address issues a UserWarning.
- A timeout of 0 (facts_sync_timeout=0 or wait_for_sync(timeout=0))
  is sent as X-Wait-Timeout, so the PDP answers without waiting.
  Earlier releases sent no header for 0, and the PDP waited its
  default.

The Send Consistent Updates page listed the routes the PDP waits on,
but not which Python methods send them. List those methods, name
writes that return before the PDP has the change, and say that
tenants.create_user() always goes to the Permit API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client table described log as enable and level only. Add a
section that shows log={...} for permit 3.1 or later and describes
each setting: enable, level (the SDK drops its records below it; an
unknown name logs a warning and falls back to info), label (the
bracketed prefix), and json, which isn't applied. It says the SDK adds
no loguru sink, logs HTTP requests at debug, and that the settings are
process-wide, and it gives the loguru serialized-sink recipe for JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a section to the Python SDK examples with what permit 4.0 will
remove and what to use instead: pydantic 1 support, the flat
permit.api methods, tenants.add_user() (an alias of create_user()),
the detailed_key argument of resource_instances.list(), and
PermitException. It says that in permit 3.1 PermitException warns
only where code imports or reads it, including a star import, and how
to make Python show DeprecationWarning outside __main__.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1.0 adds permit.api.groups, permit.get_user_tenants() and the
context argument of permit.get_user_permissions(), which requests ABAC
permissions with enable_abac_user_permissions.

- Mark Python supported in the Groups, Get User Tenant and Get User
  Permission (ABAC) rows, and say those cells need 3.1.0 or later.
  The Groups cell is full support: the SDK covers every Groups
  endpoint but the early-access list endpoints, as the Groups API
  page says.
- List Python 3.1.0 in the package version table.
- Split the "Java only" row of the method table: give Get User
  Permission (ABAC) and Get User Tenant their own rows with the Python
  calls, and keep Check URL as Java only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page's description and intro listed only the topics it had before.
Name the log, close, list, PDP refresh and deprecation sections, and
link the Python section of the Groups API page from the intro and the
next steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.api.sync_user() is deprecated in permit 3.x and warns with its
replacement, permit.api.users.sync(), which takes the same user object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zeevmoney and others added 26 commits October 3, 2026 16:49
The /register endpoint returned the UserRead and RoleAssignmentRead
objects the SDK gives back. They are pydantic v1 models, which current
FastAPI releases refuse to serialize, so the endpoint answered 500.
Return them with .dict().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Flask routes ran each call of the asyncio client with
asyncio.run(), which starts a new event loop, and with it new HTTP
connections, for every call. permit.sync.Permit is the client for
synchronous code, so the routes call it directly. The SDK's models
always have .dict(), so the hasattr() fallbacks go too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK's models are pydantic v1 models under either pydantic major,
so .dict() is always there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.sync.Permit returns the results of the API calls directly, and
since permit 3.0 the result of authorized_users() too. Two pages said
every Python call must be awaited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The expected output showed "with id", which the Go sample prints. The
Python sample prints "with key".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In permit 3.1, tenants.create_user() always goes to the Permit API,
even with proxy_facts_via_pdp on, so the Cloud PDP capabilities page
no longer says every tenants call goes to the PDP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wait_for_sync(timeout, policy) takes a timeout policy as Node.js's
waitForSync() does; the page named only the Node.js one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Python example had lost its indentation, so it did not parse.
Indent it as in the example repository, with its except clause, and
say that its permit.check() runs on the blocking client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The login route examples had a stray space before every line after the
first, so they did not parse, and the client example said the SDK must
be "at least version 1.1.3rc1". Indent the routes, and create the
client from `from permit import Permit` as the other Python pages do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two quickstart ABAC examples mixed tabs and spaces, which renders
out of line and which PEP 8 rules out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error handling and check examples created the client with
Permit(...), which fails at runtime and which type checkers reject now
that the package is typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.0 ships type information, and mypy inferred the unannotated
lists as dicts of Collection[str], so the script's resources.get() and
roles.get() calls failed type checking. Annotate the lists as
list[dict[str, Any]].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The close section said connection reuse is new in 3.1, but not that
close() and the context manager are too: permit 3.0's client has
neither, so async with Permit() fails on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prettier split the long links of the What's next box onto three lines.
MDX then wraps the link text in a paragraph, so the links rendered as
spaced-out blocks instead of a plain list. The lists are back on one
line each, with a prettier-ignore comment so Prettier leaves them so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three install steps of the Access Request MCP implementation guide
opened each code fence at the step's indentation but put the commands
and the closing fence at column 0. Prettier reads that as unclosed
fences and corrupts them. The commands and closing fences now share the
opening fence's indentation, which MDX strips, so the page renders the
same commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prettier aligns the tables, adds blank lines in the timeline steps, and
puts the args of the Claude Desktop config on one line. The rendered
page is otherwise the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deprecated APIs table said every row works and warns in permit 3.x.
tenants.add_user() and create_user() are new in permit 3.1, and permit
3.0 doesn't warn for the detailed_key argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deprecated APIs section described the PermitException warning and
its filter as permit 3.x behaviour. permit 3.0 warns when `import permit`
runs, with the message "Use PermitError instead", so the page's filter
doesn't match it. The section now marks the warning and its filter as
permit 3.1 or later, and gives the permit 3.0 message and filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Formatting the partial with Prettier also reformatted its Node.js
(Express) example, which this Python sweep leaves alone. The example is
back to its original text, with a prettier-ignore comment before it so
the file stays Prettier-clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page said check() and filter_objects() raise PermitConnectionError
whenever the PDP doesn't answer. They raise it when the PDP can't be
reached or answers with an error status. A request that times out
raises TimeoutError, which the page's handlers don't catch. The
paragraph now says so and shows how to catch both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example repository's lock file pins permit 2.7.2. The versions note
now says so, and that the page's Permit SDK calls work the same in
permit 3.x, as the LangChain page does for its example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Flask quick-start uses the blocking client, but its install step
linked the asyncio quickstart. The step now names the client the
tutorial uses and links the sync quickstart, as the FastAPI and Django
pages do for theirs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Takes the same argument" could read as the four arguments of the v1
sample above it. The sentence now names the single dict or UserCreate
argument that permit.api.users.sync() takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file failed prettier --check on master too. Prettier indents it with
two spaces and writes the escaped slashes in the two Java snippets as
plain slashes. The parsed JSON is the same, so every snippet renders
the same text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant