Conversation
permit 4.0 removes PermitException (PER-16331). It carried typing_extensions' @deprecated("Use PermitError instead"), which did not mention 4.0 and warns at runtime on every subclass: the SDK silenced it for PermitConnectionError, and code that subclassed PermitConnectionError still got it. The class now carries the PEP 702 marker with category=None, so type checkers flag every use, with the new message, and the class issues no warning itself. permit.exceptions binds it under the private name _PermitException, which the SDK uses. A module __getattr__ in permit and in permit.exceptions serves the public name, and each read warns once, at the line that reads it: PermitException is deprecated and will be removed in permit 4.0; catch PermitConnectionError instead (in 4.0 it becomes a PermitError). `from package import name` reads the name twice; the first read, importlib's _handle_fromlist checking that the package has it, does not warn. Type checkers do not see the __getattr__ functions, which would make them accept any name on the two modules. Unchanged: PermitConnectionError still subclasses the class, so `except PermitException` and isinstance checks hold, and the MRO, repr and pickles are the same. dir() still lists the name. `import permit` and star imports do not warn, so a star import no longer binds PermitException. The new tests replace the ones written for the runtime decorator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The consumer type check now imports PermitException, a line that must stay a deprecation error, and catches it, which still type-checks. mypy reports PEP 702 deprecations only under the deprecated error code, which strict does not enable, so the consumer's mypy.ini enables it: it is the strictest setting a user can run. Two more lines must stay errors: a name that permit, or permit.exceptions, does not have. Type checkers that saw the modules' __getattr__ would accept any name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It is the next removal in permit 4.0, which makes PermitConnectionError a direct subclass of PermitError. The entry says what to catch instead, where the warning appears, which type checkers flag it, and that a star import no longer binds the name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`import permit` no longer warns, so the guide's and the migration skill's `pytest -W error::DeprecationWarning` run needs no `ignore:Use PermitError instead` filter. Both now say that the run also fails on each line that names PermitException, and keep the filter only for projects on permit 3.0.0 or a 2.x release that deprecated PermitException, where `import permit` itself warns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The consumer type check read PermitException only from permit.exceptions. permit re-exports it for type checkers under TYPE_CHECKING, since its __getattr__ is hidden from them; dropping that re-export would turn `from permit import PermitException` into an attr-defined error, and the check still passed. Read it through the package too, as a line that must stay a deprecation error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MIGRATION.md said "permit 3.0.0 and the later 2.x releases" warn on `import permit`; the subclass that warns has been there since 2.7.0, so name the range. The README's PermitException entry said importing permit does not warn, right below the entry that says it does on pydantic 1: say that `import permit` does not issue this warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
help(), inspect.getmembers() and mock.create_autospec() read every name dir() lists. With PermitException listed, introspecting permit or permit.exceptions issued its deprecation warning in code that never names it, and raised under -W error. Neither module overrides __dir__ now, and a test checks that introspecting them does not warn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fresh-interpreter tests reach _warn_deprecated_name's importlib check only as importlib._bootstrap, because importing permit imports importlib, which renames _frozen_importlib. A direct test now runs the helper under each bootstrap module name, and checks that a function named _handle_fromlist elsewhere, or other importlib code, still warns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs said every line that names PermitException warns. Only an import of it, or a read of permit.PermitException or permit.exceptions.PermitException, warns, and only when that line runs: later uses of an imported name do not. The README and MIGRATION.md also give the filter for the new message, since filters for "Use PermitError instead" do not match it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Binding the name in a star import would read it, and the read warns, so from permit import * no longer binds it. Code that star-imports permit and catches PermitException raises NameError when an exception reaches that handler. The README and MIGRATION.md now say so, and what to catch instead. 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. |
permit and permit.exceptions serve PermitException through a module __getattr__ (PER-16331), so the name is not in their globals, and a star import of either module stopped binding it. Code that catches PermitException after `from permit import *` raised NameError when an exception reached the handler. Both modules now have an __all__ that lists the names a star import bound before, plus PermitException, so the star import binds it through __getattr__ and warns once, at the star-import line. It leaves out what the modules import for their own use: standard library, typing, pydantic and aiohttp names, permit.exceptions' type variables, PYDANTIC_VERSION and sdk_logger, and the submodules the import system binds in the package. The tests check that each module lists every public name it binds and nothing it cannot serve, and that a star import binds the name, warns once at its line and catches a PermitConnectionError, in a fresh interpreter, with or without either client imported first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With __all__ in permit/__init__.py, mypy and pyright take the package's public names from it, so the redundant `X as X` aliases no longer mark anything. mypy strict and pyright strict report the same diagnostics on the consumer probes with and without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A star import of permit or permit.exceptions binds PermitException again (PER-16331), so a handler for it after a star import no longer raises NameError. The README, the migration guide and the migration skill now say that the star import warns once, at its line, even where the code never uses the name, and that importing the names the code uses avoids it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After regenerating permit/api/models.py, a new model reaches a star import of permit, and type checkers, only once __all__ lists it. The guide's regeneration steps now say so, and where a name the models import for their own use goes instead. 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zeevmoney
added this pull request to stack #145
October 2, 2026 18:24
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
PermitExceptionfor removal in permit 4.0, warning only when user code reads it.Why
Base: #143 (stacked on #142 down to #128). This is the last 3.x PR of the stack.
permit 4.0 removes
PermitExceptionand makesPermitConnectionErrora direct subclass ofPermitError(PER-16236), and 3.x has to say so. On the base,PermitExceptioncarried a runtime@deprecated("Use PermitError instead")whose message does not mention 4.0. Reading the name did not warn. Instantiating or subclassing the class did, and so did subclassingPermitConnectionError, which is not deprecated.import permitwas already silent, because the SDK created its own subclass undercatch_warnings.What changed
permit/exceptions.py:PermitExceptionkeeps its public name and carries the PEP 702 marker (typing_extensions.deprecated) withcategory=Noneand this message: "PermitException is deprecated and will be removed in permit 4.0; catch PermitConnectionError instead (in 4.0 it becomes a PermitError)." Type checkers flag it, and the class issues no warning of its own at runtime. The SDK refers to it as_PermitException. At runtime the public name is removed from the module dict and served by a PEP 562__getattr__, which warns. The warning text is read from the marker's__deprecated__, so type checkers and the runtime show the same string.permit/__init__.py: the same__getattr__servespermit.PermitExceptionandfrom permit import PermitException, and aTYPE_CHECKINGre-export lets type checkers see the deprecated name on the package.__getattr__is visible to type checkers, so a misspelled name onpermitorpermit.exceptionsis still an error.__dir__.help(),inspect.getmembers()andmock.create_autospec()read every namedir()lists, so they never readPermitException.permit/utils/deprecation.py:_warn_deprecated_name()attributes the warning to the reading line (stacklevel 3). It skips importlib's_handle_fromlistcheck, sofrom permit import PermitExceptionwarns once, not twice.__all__:permitandpermit.exceptionsnow define__all__, listing every SDK name a star import bound before, plusPermitException. Sofrom permit import *andfrom permit.exceptions import *still bindPermitException, and a handler for it after a star import still catchesPermitConnectionError. The star import reads the name through the module__getattr__, so it warns once, at the star-import line, even when the code never uses the name; importing the names the code uses avoids the warning. A test fails when a public name is missing from__all__.PermitExceptionunderTYPE_CHECKING. Here the one runtime class carries the marker instead. mypy recognises the marker only when its message is a string literal, so this keeps one class and one message.tests/type_check/mypy.inienables mypy'sdeprecatederror code;strictalone does not report PEP 702 deprecations.tests/type_check/consumer.pyimportsPermitExceptionfrompermit.exceptionsand readspermit.PermitException, each with a# type: ignore[deprecated]that must stay used. It also checks that missing names on both modules stayattr-definederrors.PermitExceptionas the next 4.0 removal. The entry says which reads warn, what a star import does, and the filter for the new message.ignore:Use PermitError insteadfrom their-W errorcommands. They keep it as a note for the releases whereimport permititself warns: permit 2.7.0 to 3.0.0 in MIGRATION.md, permit 3.0.0 in the skill.tests/test_fix_permit_exception_deprecation.py(26 tests) replaces 5 tests intests/test_offline_regressions.pythat were written for the runtime decorator.Behaviour changes
PermitExceptionwarns once, at the reading line. That coversfrom permit import PermitException,from permit.exceptions import PermitException,permit.PermitException,permit.exceptions.PermitException,getattrandhasattr.except permit.PermitException:clause only when an exception reaches it, and an annotation never on Python 3.14, which does not evaluate it.PermitConnectionErrorno longer warns.permitorpermit.exceptionswarns once (thePermitExceptiondeprecation) and binds the same SDK names as before. It no longer binds names the modules import for their own use:typing,datetime,enum,uuid,http,functools, pydantic and aiohttp names,permit.exceptions' type variablesPandR,PYDANTIC_VERSION,sdk_logger, and the package's submodules.dir(permit)anddir(permit.exceptions)no longer listPermitException.ignore:PermitException is deprecated:DeprecationWarning.PermitException.__deprecated__, whichPermitConnectionErrorinherits, holds the new message.PermitExceptioninstance warns, because pickle looks the class up by its public name; the pickle bytes are unchanged.PermitConnectionErrorpickles without a warning.except PermitExceptionstill catchesPermitConnectionError, andisinstancestill holds.PermitConnectionError.__mro__, reprs and pickle bytes are the same.import permitemits no warning. On pydantic 1 it still issues only "Support for pydantic 1 is deprecated".Release notes
How it was tested
pytest -m "not e2e"): 1912 passed, 3 skipped, 0 warnings on each pydantic lane. The base had 1891 passed and 3 skipped.tests/test_typing_surface.py: 4 passed on each lane.mypy: no issues in 122 source files on each lane.test_fix_permit_exception_deprecation.py,test_typing_surface.py,test_offline_regressions.py): 125 passed on Python 3.10, 3.12 and 3.14, on both lanes.python -W error::DeprecationWarning -c 'import permit'on Python 3.10, 3.12 and 3.14: silent on pydantic 2. On pydantic 1 the only warning recorded is "Support for pydantic 1 is deprecated".permit.PermitException,permit.exceptions.PermitExceptionand anexcept PermitExceptionclause;PermitConnectionError;permit.NoSuchNameas an error.pyright is not a repo check.
__getattr__serving nothing, not warning, or visible to type checkers;__dir__added back;PermitConnectionErrormoved underPermitError;deprecatederror code removed.uv lock --check, actionlint and zizmor (no findings) pass.API Coveragejob runs it with the offline record: exit 0.Owner actions before merge
ignore:Use PermitError instead:DeprecationWarningfilter (separate PR there).🤖 Generated with Claude Code