Auxiliary Functions

Decorators

pcapkit.utilities.decorators contains several useful decorators, including seekset(), beholder() and prepare().

@pcapkit.utilities.decorators.seekset(func)[source]

Read file from start then set back to original.

Important

This decorator function is designed for decorating class methods.

The decorator will keep the current offset of self._file, then call the decorated function. Afterwards, it will rewind the offset of self._file to the original and returns the return value from the decorated function.

Note

The decorated function should have following signature:

func(self: 'pcapkit.protocols.protocol.ProtocolBase',
     *args: 'typing.Any', **kwargs: 'typing.Any') -> 'typing.Any'
Parameters:

func (Callable[[Concatenate[ProtocolBase, ParamSpec(P, bound= None)]], TypeVar(R_seekset)]) – decorated function

Return type:

Callable[[ParamSpec(P, bound= None)], TypeVar(R_seekset)]

@pcapkit.utilities.decorators.beholder(func)[source]

Behold extraction procedure.

Important

This decorator function is designed for decorating class methods.

This decorator first keep the current offset of self._file, then try to call the decorated function. Should any exception raised, it will re-parse the self._file as Raw protocol.

Note

The decorated function should have following signature:

func(self: 'pcapkit.protocols.protocol.ProtocolBase',
     proto: 'int', length: 'typing.Optional[int]',
     *args: 'typing.Any', **kwargs: 'typing.Any') -> 'pcapkit.protocols.protocol.ProtocolBase'
Parameters:

func (Callable[[Concatenate[ProtocolBase, int, int | None, ParamSpec(P, bound= None)]], TypeVar(R_beholder, bound= ProtocolBase)]) – decorated function

Return type:

Callable[[ParamSpec(P, bound= None)], TypeVar(R_beholder, bound= ProtocolBase)]

@pcapkit.utilities.decorators.prepare(func)[source]

Prepare schema packet data before unpacking.

Important

This decorate function is designed for decorating the Schema.unpack class method.

This decorator will revise the parameter list provided to the original Schema.unpack method and extract necessary information based on the given parameters, then provide the revised version of parameter list to the original method.

Note

The decorated function should have following signature:

func(cls: 'typing.Type[pcapkit.protocols.schema.schema.Schema]',
     data: 'bytes | typing.IO[bytes]',
     length: 'Optional[int]',
     packet: 'Optional[dict[str, Any]]') -> 'pcapkit.protocols.schema.schema.Schema'

No further positional or keyword arguments are read from – or forwarded to – the decorated function. prepare() is applied to exactly one function in this tree, Schema.unpack, whose real signature has never had more than these four parameters, and nothing calls it with more; an earlier revision of this note nonetheless promised implementors a trailing *args, **kwargs, which the wrapper below never populated. A caller relying on that promise got extras silently discarded instead of forwarded – see #454 – so the wrapper now raises TypeError for a fifth positional argument or an unconsumed keyword, the same as an ordinary call with too many arguments would.

Parameters:

func (Callable[[Concatenate[Type[TypeVar(R_prepare, bound= Schema)], bytes | IO[bytes], int | None, dict[str, Any] | None, ParamSpec(P, bound= None)]], TypeVar(R_prepare, bound= Schema)]) – decorated function

Return type:

Callable[[ParamSpec(P, bound= None)], TypeVar(R_prepare, bound= Schema)]

Important

All three decorators above are designed for decorating class methods. For more information, please refer to the documentation of each decorator function.

Type Variables

pcapkit.utilities.decorators.R_seekset: Any
pcapkit.utilities.decorators.R_beholder: pcapkit.protocols.protocol.ProtocolBase
pcapkit.utilities.decorators.R_prepare: pcapkit.protocols.schema.schema.Schema

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. What makes this a fix rather than a rewrite is that 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, as this function once did, grew with the outer stack, so the frame it named drifted 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(), which cannot be used here: traceback.StackSummary.extract() honours sys.tracebacklimit, and BaseError sets that to 0 for every loud error outside development mode. One such error therefore made extract_stack() return an empty list for the rest of the process, which is where the old -1 came from – so in ordinary use the first error silently broke the attribution of every warning after it. Walking frames also skips building the FrameSummary objects and the linecache lookups behind them, which is worth having on a function called once per warning.

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.warnings.warn(message, category, stacklevel=None)[source]

Wrapper function of warnings.warn().

The warning is reported once on the logger logger, then once through warnings.warn(). The logger call does not consult warnings.filters, so a log-based consumer sees the complaint even when the application has filtered the category out; the warnings.warn() call is filtered normally, so the application keeps full control of that channel – including turning the warning into an error with -W error.

Parameters:
  • message (str | Warning) – Warning message.

  • category (Type[Warning]) – Warning category.

  • stacklevel (int | None) – Warning stack level, relative to the caller of this function – 1 blames the line that called warn(), 2 its caller, and so on, exactly as the argument of the same name reads on warnings.warn() itself. Defaults to stacklevel(), i.e. the innermost frame outside pcapkit.

See also

pcapkit.utilities.warnings for the emission model in full, and for how to silence either channel.