Repository navigation
permit 3.0.0
permit 3.0.0 is a major release. It requires Python 3.10 or later and raises dependency floors to clear known advisories. It also fixes SDK bugs, ships type information and supports Python 3.14.
Before you upgrade, read the migration guide. Each change below has an ID there (for example C1 or A2), with who is affected, what to do, and a before/after example.
Upgrade with an AI agent. The permit-python-3-migration skill takes an agent such as Claude Code through the guide:
- It stops if your project still runs on Python 3.8 or 3.9.
- It applies the mechanical edits and asks you about each judgement call.
- It runs your tests, type checker and linter.
To install it, download permit-python-3-migration.skill below and unzip it into .claude/skills/, or copy skills/permit-python-3-migration. Its scanner is read-only and runs on Python 3.8+:
python3 .claude/skills/permit-python-3-migration/scripts/scan.py .Breaking changes
Compatibility
-
Python 3.10 or later (C1). aiohttp 3.14.3 requires it, and it is the only release that fixes CVE-2026-69244. On Python 3.9,
pip install -U permitkeeps your current version without an error. -
httpx is no longer installed with permit (C2). The packages that came only through it are gone too: h11, httpcore, anyio, certifi, and with some versions sniffio and exceptiongroup. zipp is also gone. permit never imported any of them, so declare the ones your code uses.
-
Higher dependency floors (C3):
Package Python 3.0.0 requires pydantic 3.10–3.12 >=1.10.18,<2or>=2.4.2pydantic 3.13 >=1.10.18,<2or>=2.8.0pydantic 3.14 >=1.10.25,<2or>=2.13aiohttp all >=3.14.3,<4typing-extensions all >=4.14.0,<5loguru all >=0.7.3,<1 -
permit ships type information (T1–T3, PEP 561
py.typed).- Type checkers now check your calls into permit instead of skipping it, so real type errors in your code may show up.
- You can remove
ignore_missing_importsor# type: ignore[import-untyped]for permit. - SDK models are typed as the pydantic v1 models they are at runtime. So pydantic 2 methods such as
.model_dump()on them are now type errors; they already failed at runtime.
API
resource_relations.list()returnsPaginatedResultRelationRead(A1). Read.data. In 2.x every call raisedValidationError.- On
permit.sync.Permit,authorized_users(),get_user_permissions()andfilter_objects()are synchronous (A2). Drop theawait.- In 2.x they returned a coroutine, and awaiting it raised
RuntimeError: This event loop is already running. - The async
permit.Permitis unchanged.
- In 2.x they returned a coroutine, and awaiting it raised
- Removed (A3, A6):
ContextStore.register_transform(),ContextStore.transform()andContextTransform. A registered transform was never applied.ApiKeyLevel. UseApiKeyAccessLevel.LoginAsErrorMessages,OpaResultandJWT.permit.PYDANTIC_VERSION, and the same name inpermit.api.modelsandpermit.pdp_api.base.- A few names that modules only re-exported from their own imports. A6 lists them.
- Audit-log models accept what the API returns (A4).
pdp_config_idonAuditLogModelandDetailedAuditLogModelisOptional[UUID].DetailedAuditLogModel.objectsis optional.- New:
Engine.GENERICandGenericEngineDecisionLog.
- Relationship-tuple and API-key models accept what the API returns (A5).
object_idonRelationshipTupleReadandRelationshipTupleDetailedReadisOptional[UUID].- The four
*_detailsfields onRelationshipTupleDetailedReadare optional. APIKeyOwnerTypegainsnats_pdp_config.- In 2.x,
relationship_tuples.list()andcreate()raised on a tuple with noobject_id, andenvironments.get_api_key()raised on a key owned bynats_pdp_config.
Wire behaviour (same API, different request bytes)
- An explicitly set
Noneis sent asnull(W1), so an update can clear a field. In 2.x,users.update(key, UserUpdate(email=None))sent{}. Fields you never set are still omitted. users.assign_role()andunassign_role()omit unset fields (W2), likerole_assignments.assign().elements.login_as()sends hyphenated UUIDs (W3). The API accepts both forms.- A 3xx response raises (W4) instead of counting as success.
- The
Authorizationheader usesBearer(W5), notbearer. This fixes the 401 from Cloud PDP (#122). - The deprecated
permit.api.assign_role()andunassign_role()call/users/{user}/roles(W6), throughpermit.api.users, instead of/role_assignments. The effect is the same.
PermitConnectionError still subclasses PermitException, so except PermitException still catches connection failures.
Deprecations
permit 4.0, a future major release, will remove the following. They work in 3.x. Each raises a DeprecationWarning that names the replacement, reported at the line that made the call.
- pydantic 1 support (D1). On pydantic 1,
import permitwarns once. - The 21 flat methods on
permit.api(D2). For example,permit.api.get_user()becomespermit.api.users.get(). The guide maps all 21.
To find every deprecated call your tests reach:
python -m pytest -W error::DeprecationWarning -W "ignore:Use PermitError instead:DeprecationWarning"The second filter is needed in 2.x too: import permit warns because PermitConnectionError subclasses the deprecated PermitException.
Security
| Package | 2.8.3 allowed | 3.0.0 | Fixes |
|---|---|---|---|
| aiohttp | >=3.12.14 |
>=3.14.3 |
32 advisories, including CVE-2026-69244 (out-of-bounds read in the HTTP response parser) |
| pydantic | >=1.10.7 |
see above | CVE-2024-3772 (ReDoS in EmailStr validation) |
| httpx | >=0.24.1 |
removed | Removes h11 (CVE-2025-43859) and anyio (CVE-2026-63374) from your dependency tree |
| zipp | >=3.19.1 |
removed | Unused |
If you are staying on 2.x for now, see the guide's "Staying on 2.x for now". It lists constraints that clear these advisories on Python 3.10+, and the advisories' workarounds for 3.8 and 3.9.
Bug fixes
- Sync client flat methods. On
permit.sync.Permit, the flatpermit.apimethods sent their request and then raisedValueError: a coroutine was expected. A write took effect while the caller got an error. They now return the result, so remove anyexcept ValueErroraround them. - pydantic 2.
authorized_users()failed under pydantic 2. - ABAC context. Decisions can change.
bulk_check()ignored a per-checkcontext, andfilter_objects()dropped your context. So ABAC checks that depend on context were evaluated against an empty one. - User names in checks.
UserInputsilently droppedfirst_nameandlast_namefrom every check. - Request bodies. A nested
datetime,UUIDorEnumin a dict or list request body crashed inside aiohttp. - PDP errors. A non-200 PDP response, such as a 403 from a wrong API key, was reported as "cannot connect to the PDP container".
PermitConnectionErrornow carries the status code and response body. Code that matches the old message needs updating. - Tenant bulk operations. With
proxy_facts_via_pdp,tenants.bulk_create()andtenants.bulk_delete()went to the PDP's users endpoint. - PDP API client.
permit.pdp_api.*calls ignoredpdp_timeout, and the syncpdp_apiclient didn't initialize correctly. users.sync(). It removedkeyfrom the dict you passed, so that path always returned 422.resource_instances.list(detailed_key=...). It raisedTypeError.- pydantic pre-releases.
import permitcrashed on pre-releases such as2.14.0b2. - Packaging. permit installed a top-level
testspackage into site-packages, where it could shadow your own.
Python 3.14
Python 3.14 is supported and tested (#124). The floors above keep resolvers off releases that crash or warn on 3.14. import permit no longer emits 21 DeprecationWarnings there.
Typing
- Optional model fields show as optional in pyright and Pylance.
- Methods that accept dicts at runtime accept them in their annotations.
- The sync client is typed as synchronous.
- Your code can type-check clean under
mypy --strictand pyright:- plain
stris accepted forEmailStrfields; UserInputaccepts both field spellings;PermitConfig()without atokenis a type error, as it already was at runtime.
- plain
- On pydantic 2, mypy users who want plugin checking of SDK models can use
pydantic.v1.mypy(T3).
Build and release
- Advisory scans. Every pull request and release is scanned for dependency advisories (Trivy and pip-audit), at the lowest and the highest versions permit allows. A high or critical advisory with a fix fails the check and stops the release.
- Release order. Releases run build → scan → publish, so nothing reaches PyPI without a passing scan.
- Test matrix. CI runs the tests on Python 3.10–3.14, on pydantic 1 and 2, at the lowest and newest allowed versions.
- Schema check. A weekly check compares the SDK's models with the public API schema.
Contributors
Thanks to @SureneruS for the Bearer report and fix (#122, #123), to @Tarcio0211 for the py.typed marker (#125), and to @coactive-jeffrey (#116) and @jameswu1991 (#124) for their reports. #123 and #125 shipped as part of #126.
Full changelog: v2.8.3...v3.0.0