Skip to content

Releases: permitio/permit-python

permit 3.0.1

Choose a tag to compare

@EliMoshkovich EliMoshkovich released this 30 Oct 20:31
3dc35cc

What's Changed

  • Log through an SDK logger that applies the log settings (#163)

Fixes

  • The client's startup log line no longer contains the API key. It carries the API and PDP URLs without credentials.
  • log.level and log.label are now applied. Every SDK log record goes through the SDK's logger, which replaces the API key of every client in the process with [REDACTED], as given and in lower case.
  • The API key is also replaced in the exceptions the SDK raises: their messages, the error bodies in them, and the aiohttp response or error they carry, including the exceptions chained under that error. If the key can't be removed from an aiohttp error, PermitConnectionError carries a plain aiohttp.ClientError in its place.
  • The README lists where the key is not replaced, such as request URLs and the values a traceback shows for each frame.

Behaviour changes

With log={"enable": True} and nothing else, the defaults now apply:

  • DEBUG records are dropped (the default level is info).
  • Each SDK message starts with [Permit] .

Set level="debug" or label="" to keep the old output.

Full Changelog: v3.0.0...v3.0.1

permit 3.0.0

Choose a tag to compare

@zeevmoney zeevmoney released this 28 Sep 15:39
99707ce

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 permit keeps 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,<2 or >=2.4.2
    pydantic 3.13 >=1.10.18,<2 or >=2.8.0
    pydantic 3.14 >=1.10.25,<2 or >=2.13
    aiohttp all >=3.14.3,<4
    typing-extensions all >=4.14.0,<5
    loguru 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_imports or # 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() returns PaginatedResultRelationRead (A1). Read .data. In 2.x every call raised ValidationError.
  • On permit.sync.Permit, authorized_users(), get_user_permissions() and filter_objects() are synchronous (A2). Drop the await.
    • In 2.x they returned a coroutine, and awaiting it raised RuntimeError: This event loop is already running.
    • The async permit.Permit is unchanged.
  • Removed (A3, A6):
    • ContextStore.register_transform(), ContextStore.transform() and ContextTransform. A registered transform was never applied.
    • ApiKeyLevel. Use ApiKeyAccessLevel.
    • LoginAsErrorMessages, OpaResult and JWT.
    • permit.PYDANTIC_VERSION, and the same name in permit.api.models and permit.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_id on AuditLogModel and DetailedAuditLogModel is Optional[UUID].
    • DetailedAuditLogModel.objects is optional.
    • New: Engine.GENERIC and GenericEngineDecisionLog.
  • Relationship-tuple and API-key models accept what the API returns (A5).
    • object_id on RelationshipTupleRead and RelationshipTupleDetailedRead is Optional[UUID].
    • The four *_details fields on RelationshipTupleDetailedRead are optional.
    • APIKeyOwnerType gains nats_pdp_config.
    • In 2.x, relationship_tuples.list() and create() raised on a tuple with no object_id, and environments.get_api_key() raised on a key owned by nats_pdp_config.

Wire behaviour (same API, different request bytes)

  • An explicitly set None is sent as null (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() and unassign_role() omit unset fields (W2), like role_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 Authorization header uses Bearer (W5), not bearer. This fixes the 401 from Cloud PDP (#122).
  • The deprecated permit.api.assign_role() and unassign_role() call /users/{user}/roles (W6), through permit.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 permit warns once.
  • The 21 flat methods on permit.api (D2). For example, permit.api.get_user() becomes permit.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 flat permit.api methods sent their request and then raised ValueError: a coroutine was expected. A write took effect while the caller got an error. They now return the result, so remove any except ValueError around them.
  • pydantic 2. authorized_users() failed under pydantic 2.
  • ABAC context. Decisions can change. bulk_check() ignored a per-check context, and filter_objects() dropped your context. So ABAC checks that depend on context were evaluated against an empty one.
  • User names in checks. UserInput silently dropped first_name and last_name from every check.
  • Request bodies. A nested datetime, UUID or Enum in 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". PermitConnectionError now 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() and tenants.bulk_delete() went to the PDP's users endpoint.
  • PDP API client. permit.pdp_api.* calls ignored pdp_timeout, and the sync pdp_api client didn't initialize correctly.
  • users.sync(). It removed key from the dict you passed, so that path always returned 422.
  • resource_instances.list(detailed_key=...). It raised TypeError.
  • pydantic pre-releases. import permit crashed on pre-releases such as 2.14.0b2.
  • Packaging. permit installed a top-level tests package 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 --strict and pyright:
    • plain str is accepted for EmailStr fields;
    • UserInput accepts both field spellings;
    • PermitConfig() without a token is a type error, as it already was at runtime.
  • 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.

Fixes #116, #122, #124.

Full changelog: https://github.com/permitio/permit-python/compare/v2...

Read more

v2.8.3

Choose a tag to compare

@shuvyA shuvyA released this 25 Sep 19:01
47e47df

What's Changed

  • Shuvy/per 12881 add list create and delete invite user to python sdk by @shuvyA in #120
  • Upgrade aiohttp version by @shuvyA in #121

Full Changelog: 2.8.2...2.8.3

v2.8.2

Choose a tag to compare

@shuvyA shuvyA released this 14 Sep 07:41
3cc9ecf

What's Changed

New Contributors

Full Changelog: 2.8.1...2.8.2

v2.8.1

Choose a tag to compare

@shuvyA shuvyA released this 11 Sep 13:52
43ec517

What's Changed

Full Changelog: 2.8.0...2.8.1

v2.8.0

Choose a tag to compare

@shuvyA shuvyA released this 11 Sep 11:54
07c3e1f

What's Changed

New Contributors

Full Changelog: 2.7.5...2.8.0

v2.7.5

Choose a tag to compare

@danyi1212 danyi1212 released this 17 Apr 16:17
fb88993

What's Changed

Full Changelog: 2.7.4...2.7.5

2.7.4

Choose a tag to compare

@danyi1212 danyi1212 released this 16 Apr 14:53
0f15077

What's Changed

Full Changelog: 2.7.3...2.7.4

v2.7.3

Choose a tag to compare

@danyi1212 danyi1212 released this 30 Mar 14:20
c0d593d

What's Changed

Full Changelog: 2.7.2...2.7.3

v2.7.2

Choose a tag to compare

@gemanor gemanor released this 11 Feb 09:37
ba80fca

What's Changed

  • Add get_user_permissions and filter_objects by @icode247 in #111

Full Changelog: 2.7.1...2.7.2