User Defined Exceptions

pcapkit.utilities.exceptions holds pcapkit’s exceptions, each refining a built-in one, grouped below by that built-in.

Loud and Quiet Errors

A BaseError is loud by default: it is logged once at logging.CRITICAL on the pcapkit logger (logger), and outside development mode an exception hook is installed on sys.excepthook() and threading.excepthook(), the first time a loud error needs it, so the user sees the exception line rather than a walk through pcapkit’s internals [*]. The hook shortens only a BaseError; it hands every other exception, unchanged, to the hook installed before it.

quiet=True marks an error pcapkit raises as internal control flow and catches itself – the MissingKeyError behind MultiDict.get is the archetype. It emits nothing on any channel and touches no process-global state, but is otherwise an ordinary exception carrying its message.

An error constructed inside a CaughtByCaller block naming one of its classes is quiet too. That is how pcapkit marks as control flow an error raised by code it calls but does not own, such as a generated _validate_value override, which cannot pass quiet=True itself (#1845). beholder() runs the whole call to a next layer in such a block for ProtocolError, so a refusal it keeps as Raw is recorded on that layer’s error alone (#2588).

What decides whether a constructed error is loud:

        flowchart TD
    NEW["a BaseError<br/>is constructed"] --> QUIET{"quiet=True?"}
    QUIET -->|yes| SILENT["quiet: nothing logged,<br/>no hook touched"]
    QUIET -->|no| CAUGHT{"inside a CaughtByCaller<br/>block naming one<br/>of its classes?"}
    CAUGHT -->|yes| SILENT
    CAUGHT -->|no| DEV{"PCAPKIT_DEVMODE?"}
    DEV -->|yes| DEVLOG["logger.critical, with<br/>traceback and stack<br/>under PCAPKIT_VERBOSE"]
    DEV -->|no| LOG["logger.critical"]
    LOG --> HOOK["\_install\_excepthook<br/>sys.excepthook and<br/>threading.excepthook"]
    SILENT --> RAISE["an ordinary exception<br/>for the caller to catch"]
    DEVLOG --> RAISE
    HOOK --> RAISE

    click CAUGHT "exceptions.html#pcapkit.utilities.exceptions.CaughtByCaller"
    

Changed in version 1.5.0: quiet=True logs nothing, where 1.4.x logged at ERROR, and neither path sets sys.tracebacklimit. In 1.4.x a single MultiDict.get() miss gave an ERROR record – one per frame when parsing unfragmented IPv6 with reassembly on – and truncated unrelated tracebacks for the rest of the process, though no such record was a fault. To observe internal lookup misses, catch the exception rather than read the log (#362).

exception pcapkit.utilities.exceptions.BaseError(*args, quiet=False, **kwargs)[source]

Bases: Exception

Base error class of all kinds.

A loud error – the default – is reported once, at logging.CRITICAL level, on the pcapkit logger (logger). Outside development mode it also installs _excepthook() as sys.excepthook() and _threading_excepthook() as threading.excepthook(), the first time a loud error needs them, so that error – and every loud error reaching the top level after it, on the main thread or any other – prints its one-line exception message rather than a walk through pcapkit’s internals.

A quiet error (quiet=True) is one pcapkit raises as internal control flow and expects to catch itself, such as the MissingKeyError behind MultiDict.get. It is therefore silent and free of side effects: nothing is logged, and neither sys.excepthook() nor threading.excepthook() is touched. It is still a perfectly ordinary exception, carrying its message for whoever catches it.

An error is also quiet when it is constructed inside a CaughtByCaller block naming one of its classes, which is how a pcapkit caller marks as control flow an error raised by code that does not pass quiet=True itself. The effect is exactly that of quiet=True.

Important

  • The terseness is deliberately not done by setting sys.tracebacklimit to 0: that is process-global, so one loud error would truncate the tracebacks of every other, unrelated exception for the rest of the process. Each hook here shortens the printing of a BaseError only, and hands every other exception, unchanged, to whatever hook pcapkit found installed before it first needed its own.

  • Outside development mode, importing pcapkit replaces both hooks itself, through tbtrim.set_trim_rule: its hooks cut the traceback of every exception except a BaseError at the first pcapkit frame, and a hook the program installed before the import is replaced. The hooks installed here go over those, the first time a loud error needs them, and hand every other exception to what they replaced. Under PCAPKIT_DEVMODE neither pair is installed.

  • The hooks still do not cover every path an exception can take. A traceback a caller formats itself with traceback – rather than letting it reach the top level uncaught – passes through neither (#719).

  • The stacklevel of the log record is the relative level stacklevel() computes, so the record is attributed to the caller whose operation failed rather than to this module. It must not be negated: logging.Logger.findCaller() reads that as “do not walk out at all” and would blame logging itself for every error.

Changed in version 1.5.0: A quiet error logs nothing, where 1.4.x logged it at ERROR, and no error sets sys.tracebacklimit (#362).

__init__(*args, quiet=False, **kwargs)[source]

Log the error, unless it is quiet, and initialise the exception.

A loud error logs at CRITICAL and, unless PCAPKIT_DEVMODE turns development mode on, installs the terse exception hooks; the class docstring says when an error is quiet.

Parameters:
  • *args (Any) – Arbitrary positional arguments.

  • quiet (bool) – If True, the error is neither logged nor allowed to install the exception hook; it is raised silently, as internal control flow.

  • **kwargs (Any) – Arbitrary keyword arguments.

class pcapkit.utilities.exceptions.CaughtByCaller(*types)[source]

Bases: object

Construct errors of the given classes quietly for the duration of a block.

A BaseError constructed inside the block that is an instance of one of types behaves exactly as if it had been given quiet=True: nothing is logged and no exception hook is installed. Every other error stays loud.

Use it around a call into code the caller does not own – a generated _validate_value() override, say – whose named exceptions the caller then catches itself. An error the callee raises is then the quiet control flow it is at that call site, without the callee having to pass quiet=True, which it cannot know to do (#1845). Only the named classes are affected: anything else the callee raises escapes the caller and so stays loud.

Given no classes, it declares that nothing is caught, which makes errors loud again inside an enclosing block – how register() keeps its rejection loud whoever calls it.

Re-entrant: on exit a block restores whatever the enclosing block declared. An instance holds the token of the one with it is entered by, so it is built afresh for each. The declaration is per thread and per contextvars context, so a block never quiets an error raised on another thread.

A class rather than contextlib.contextmanager(), because EnumLookup.get enters one on every call with a non-str key, and the generator machinery measured at several times the cost of the lookup it would wrap.

Parameters:

*types (Type[BaseException]) – Exception classes the caller catches.

Added in version 1.5.0.

TypeError Category

exception pcapkit.utilities.exceptions.DigitError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be (a) number(s).

exception pcapkit.utilities.exceptions.IntError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be integral.

exception pcapkit.utilities.exceptions.RealError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The function is not defined for real numbers.

exception pcapkit.utilities.exceptions.ComplexError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The function is not defined for complex numbers.

exception pcapkit.utilities.exceptions.BoolError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be bool type.

exception pcapkit.utilities.exceptions.BytesError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be bytes type.

exception pcapkit.utilities.exceptions.StringError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be str type.

exception pcapkit.utilities.exceptions.BytearrayError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be bytearray type.

exception pcapkit.utilities.exceptions.DictError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be dict type.

exception pcapkit.utilities.exceptions.ListError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be list type.

exception pcapkit.utilities.exceptions.TupleError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be tuple type.

exception pcapkit.utilities.exceptions.IterableError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be iterable.

exception pcapkit.utilities.exceptions.IOObjError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be file-like object.

exception pcapkit.utilities.exceptions.ProtocolUnbound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

Protocol slice unbound.

exception pcapkit.utilities.exceptions.CallableError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be callable.

exception pcapkit.utilities.exceptions.InfoError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be Info instance.

exception pcapkit.utilities.exceptions.IPError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be IP address.

exception pcapkit.utilities.exceptions.EnumError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be enumeration protocol type.

exception pcapkit.utilities.exceptions.ComparisonError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

Rich comparison not supported between instances.

exception pcapkit.utilities.exceptions.RegistryError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be registry type.

exception pcapkit.utilities.exceptions.FieldError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, TypeError

The argument(s) must be field type.

AttributeError Category

exception pcapkit.utilities.exceptions.FormatError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, AttributeError

Unknown format(s).

exception pcapkit.utilities.exceptions.UnsupportedCall(*args, quiet=False, **kwargs)[source]

Bases: BaseError, AttributeError

Unsupported function or property call.

IOError Category

exception pcapkit.utilities.exceptions.FileError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, OSError

[Errno 5] Wrong file format.

FileExistsError Category

exception pcapkit.utilities.exceptions.FileExists(*args, quiet=False, **kwargs)[source]

Bases: BaseError, FileExistsError

[Errno 17] File already exists.

FileNotFoundError Category

exception pcapkit.utilities.exceptions.FileNotFound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, FileNotFoundError

[Errno 2] File not found.

IndexError Category

exception pcapkit.utilities.exceptions.ProtocolNotFound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, IndexError

Protocol not found in ProtoChain.

ValueError Category

exception pcapkit.utilities.exceptions.VersionError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Unknown IP version.

exception pcapkit.utilities.exceptions.IndexNotFound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Protocol not in ProtoChain.

exception pcapkit.utilities.exceptions.ProtocolError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid protocol format.

exception pcapkit.utilities.exceptions.EndianError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid endian (byte order).

exception pcapkit.utilities.exceptions.KeyExists(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Key already exists.

exception pcapkit.utilities.exceptions.NoDefaultValue(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

No default value.

exception pcapkit.utilities.exceptions.EnumValueError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

No member of an enumeration carries this value.

The value-miss half of the pair whose name-miss half is EnumKeyError; see that one for the stdlib Enum shape both follow, and for why the two halves derive from different builtins.

exception pcapkit.utilities.exceptions.FieldValueError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid field value.

exception pcapkit.utilities.exceptions.SchemaError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid schema.

exception pcapkit.utilities.exceptions.SeekError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid seek position.

exception pcapkit.utilities.exceptions.TruncateError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Invalid truncate size.

exception pcapkit.utilities.exceptions.VendorPathNotFound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ValueError

Crawler module is not inside the vendor package root.

NotImplementedError Category

exception pcapkit.utilities.exceptions.ProtocolNotImplemented(*args, quiet=False, **kwargs)[source]

Bases: BaseError, NotImplementedError

Protocol not implemented.

exception pcapkit.utilities.exceptions.VendorNotImplemented(*args, quiet=False, **kwargs)[source]

Bases: BaseError, NotImplementedError

Vendor not implemented.

struct.error Category

exception pcapkit.utilities.exceptions.StructError(*args, eof=False, **kwargs)[source]

Bases: BaseError, error

Unpack failed.

EOFError Category

exception pcapkit.utilities.exceptions.StreamEOFError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, EOFError

Underlying stream exhausted; no data left to read.

Raised by prepare() when the length of a schema’s read was derived by measuring what is actually left in the stream – rather than declared by the caller – and that measurement came back zero. This is the frame reader’s “no more packets” signal, so it subclasses EOFError rather than replacing it: an except (EOFError, StopIteration) handler catches it, and a caller that wants to be more specific can catch this instead.

A declared zero length – a nested schema legitimately sized to have nothing to read – is a different situation and does not raise this.

Frame and PCAPNG raise it too, quietly, when fewer octets are left than a record header or a block needs.

Note

prepare() always raises this with quiet=True: reaching end of stream is the frame reader’s ordinary way of finding out there is nothing left to parse, not a fault to log – the same convention StructError follows for the same situation via its own eof=True.

KeyError Category

exception pcapkit.utilities.exceptions.MissingKeyError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, KeyError

Key not found.

exception pcapkit.utilities.exceptions.FragmentError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, KeyError

Invalid fragment dict.

exception pcapkit.utilities.exceptions.PacketError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, KeyError

Invalid packet dict.

exception pcapkit.utilities.exceptions.EnumKeyError(*args, quiet=False, **kwargs)[source]

Bases: BaseError, KeyError

No member of an enumeration carries this name.

The name-miss half of the pair whose value-miss half is EnumValueError, and the split between them follows stdlib Enum rather than this package’s own taste: E['nosuch'] raises KeyError and E(999) raises ValueError, so a lookup that misses by name is KeyError-derived and one that misses by value is ValueError-derived. That is a ruling given in review of #877’s phase-2 re-parenting, carried out by #923: raise whichever of the two stdlib Enum would raise in the same circumstances, and raise it from this module rather than as a builtin.

Deriving from KeyError is what makes that ruling cheap to carry out: the in-library call sites catch get()’s name miss as KeyError – Method’s get catches it in order to build an unregistered member, so for that one a failed name lookup is part of a successful call – so every one of them catches this as well.

Note

Distinct from MissingKeyError, which is deliberately not reused here: that one reports an absent mapping key, as MultiDict and the pcapkit.toolkit extractors raise it, and conflating the two would leave a caller unable to tell a registry that has no such member from a packet dict that has no such field.

Added in version 1.5.0.

ModuleNotFoundError Category

exception pcapkit.utilities.exceptions.ModuleNotFound(*args, quiet=False, **kwargs)[source]

Bases: BaseError, ModuleNotFoundError

Module not found.

io.UnsupportedOperation Category

exception pcapkit.utilities.exceptions.UnsupportedOperation(*args, quiet=False, **kwargs)[source]

Bases: BaseError, UnsupportedOperation

Unsupported operation.

Error Handling Utilities

pcapkit.utilities.exceptions.stacklevel()[source]

Stack level of the innermost frame outside pcapkit.

The value is a relative level, in the sense both warnings.warn() and the logging module use: level 1 is the frame that called stacklevel(), level 2 its caller, and so on outwards. Handing it to either of them attributes the complaint to the caller who reached into pcapkit, rather than to whichever pcapkit internal happened to notice the problem – which is the whole point of the function, since the internal frames are noise to the user reading the report.

The arithmetic, since it is easy to get backwards. Number the frames outwards from this one, so that a number is the relative level a consumer wants:

level 0             stacklevel() itself
level 1             whoever called stacklevel()
...
level boundary      the outermost frame inside pcapkit
...
level outermost     the interpreter entry point

The frame to name is the first one past the boundary, hence boundary + 1. boundary is measured from the inside out: it depends only on how deep the pcapkit frames run, never on how deep the caller’s own stack is. Numbering from the outside in would grow with the outer stack, so the frame named would drift one further out for every extra frame above the boundary – under pytest, dozens of them.

Both bounds are enforced, as neither consumer copes with a level outside them:

  • Never below 1. 0 and negative values mean “do not walk out at all” to logging.Logger.findCaller(), which then attributes the record to logging itself; warnings.warn() treats them as 1.

  • Never above outermost. That bound is reached when the whole stack is inside pcapkit, e.g. running the package as a script, and walking past the outermost frame makes warnings.warn() fall back to blaming the sys module.

The walk goes through inspect.currentframe() and f_back rather than traceback.extract_stack(): it skips building the FrameSummary objects and the linecache lookups traceback.extract_stack() does for every frame, which is worth avoiding on a function called once per warning. It is also immune to sys.tracebacklimit, which traceback.StackSummary.extract() honours: a limit of 0 would make extract_stack() return an empty list and silently break the attribution of every warning. BaseError does not set that limit (it prints its terse line through an exception hook instead), but user code may.

Important

The level is relative to the caller of stacklevel(). A function that forwards it to warnings.warn() on its caller’s behalf has to add one for its own frame – see pcapkit.utilities.warnings.warn(), which does exactly that.

Return type:

int

Returns:

Number of frames from the caller of stacklevel() outwards to the innermost frame whose path does not contain /pcapkit/.

pcapkit.utilities.exceptions._install_excepthook()[source]

Install _excepthook() and _threading_excepthook(), once each.

Called from BaseError’s constructor, outside development mode, the first time a loud error is raised – not at import. What the import leaves in both slots, outside development mode, is the pair tbtrim.set_trim_rule installs from pcapkit’s __init__; this replaces that pair, and the matching _previous_* records it.

Idempotent for this module instance, independently for each hook: if a given hook slot already holds this instance’s own function – checked by identity, is, not by the installed_by_pcapkit marker – installing it again does nothing, so a loud error later in the same run never wraps either hook in another copy of itself. That distinction matters because the identical check is wrong one level up: a hook slot already holding some function of the same name from an earlier loaded copy of this module – a genuine importlib.reload(), or a test harness re-executing the file fresh – is not this instance’s own hook, and must not be skipped over. A fresh instance installs over it exactly as it would over a host application’s own hook, capturing it as the matching _previous_* and delegating to it for whatever that older instance’s own hook does not claim as one of its own BaseError instances – which, correctly, includes a BaseError raised by that older instance, since isinstance does not hold across the reload and delegating down the chain is what lets the older instance’s own hook recognise it instead.

pcapkit.utilities.exceptions._excepthook(etype, value, tb)[source]

Print a loud BaseError tersely; chain through everything else.

Installed by _install_excepthook(), which is also what records _previous_excepthook. A BaseError reaching here is printed with limit=0 against its real traceback – not a bare one-liner built by discarding the traceback outright, which is close but not the same thing. traceback.print_exception() is also what formats a chained exception (raise ... from err, or a bare raise inside an except block), and it does so by walking from the outermost cause or context inward, applying limit to each link’s own frames as it goes. Passing tb=None only ever hands it the top exception’s own traceback – the chain’s inner links keep their real __cause__/__context__ traceback attributes regardless, untouched by this call, and get printed in full. limit=0 against the real tb is what reaches every link, giving the one-line answer for a plain BaseError and a chain without frames for a chained one.

Anything else is handed to _previous_excepthook exactly as received: the hook sys.excepthook() held when _install_excepthook() ran – outside development mode, tbtrim’s from the import, unless the program replaced it after the import – or sys.__excepthook__ when none was recorded.

pcapkit.utilities.exceptions._threading_excepthook(args)[source]

Thread analogue of _excepthook(); same contract, one argument.

threading.excepthook() is called with a single threading.ExceptHookArgs, carrying exc_type, exc_value, exc_traceback and thread – not the three positional parameters sys.excepthook() takes. Everything else here mirrors _excepthook() as closely as the default thread hook’s own output allows: a loud BaseError prints the "Exception in thread ...:" header the default hook always prints first – the one piece of that output sys.tracebacklimit does not touch, since it only bounds the traceback – then its own message tersely, with limit=0 against the real traceback so a chained exception still comes out right. Anything else is handed to _previous_threading_excepthook – or default_threading_excepthook, the interpreter’s own default, if nothing was previously installed – unchanged, header included, since that path does not touch the default hook’s own printing at all.

This hook is needed because the interpreter’s default threading.excepthook() consults sys.tracebacklimit, which BaseError does not set; without it a loud BaseError raised on a worker thread would print a full default traceback.

Footnotes