Tracing function calls

The core data type in MonkeyType is the CallTrace. A CallTrace instance represents a single traced call of a single function or method, including the concrete type of each argument and the return or yield type.

A CallTrace is recorded by monkeytype run or the trace() context manager (or direct use of a CallTracer), logged via a CallTraceLogger, probably stored in a CallTraceStore, and later queried from that store by monkeytype stub or monkeytype apply and combined with all other traces of the same function in order to generate a stub or type annotation for that function.

monkeytype run

The simplest way to trace some function calls with MonkeyType is to run a Python script under MonkeyType tracing using monkeytype run or monkeytype run -m at the command line:

$ monkeytype run myscript.py
$ monkeytype run -m mymodule

monkeytype run accepts the same monkeytype -c option as monkeytype stub and monkeytype apply, to point MonkeyType to the config it should use.

Because of the way Python treats scripts and imported modules differently, MonkeyType will not record traces for the entry point itself (that is, the script passed to monkeytype run or the module passed to run -m); traces are recorded only for imported modules. If you want to annotate the entry point script/module, write another short script that imports and calls its function(s), and run that script with monkeytype run.

trace context manager

You can also trace calls by wrapping a section of code inside the trace() context manager:

import monkeytype

with monkeytype.trace():
    # argument and yield/return types for all function calls will be traced
    # and stored to `monkeytype.sqlite3`

You can pass a Config object to trace() to customize its behavior:

import monkeytype
from my_mt_config import my_config

with monkeytype.trace(my_config):
    # arg and yield/return types for function calls here will be traced and
    # logged as specified by your config.
monkeytype.trace([config: Config]) → ContextManager

Trace all enclosed function calls and log them per the given config. If no config is given, use the DefaultConfig.

CallTracer

class monkeytype.tracing.CallTracer(logger: CallTraceLogger, code_filter: CodeFilter, sample_rate: int)

For more complex tracing cases where you can’t easily wrap the code to trace in a context manager, you can also use a CallTracer directly. CallTracer doesn’t accept a Config object; instead you pass it a logger, filter, and sample rate.

If you have a config, you can easily pull those from it:

from monkeytype.tracing import CallTracer
from my_mt_config import my_config

logger = my_config.trace_logger()
tracer = CallTracer(
    logger=logger,
    code_filter=my_config.code_filter(),
    sample_rate=my_config.sample_rate(),
)

The CallTracer has no public API apart from its constructor, but it is suitable for passing to sys.setprofile as a profiler:

sys.setprofile(tracer)

# run some code to be traced

sys.setprofile(None)  # remove the tracer

If your CallTraceLogger requires flushing, you should also do this after completing tracing:

logger.flush()

Deciding which calls to trace

You probably don’t want to store traces for every single function called by your program; that will likely include a lot of calls to Python standard-library or third-party library functions that aren’t your target for type annotation.

To filter the calls that will be traced, you can return a predicate function from the code_filter() method of your Config(). This function should take a Python code object and return a boolean: True means the function will be traced, and False means it will not.

The DefaultConfig includes a default code filter. If the environment variable MONKEYTYPE_TRACE_MODULES is set to a list of package and/or module names, the default filter traces only code from within those modules. Otherwise, the default filter simply excludes code from the Python standard library and site-packages.

Logging traces

A call-trace logger is responsible for accepting CallTrace instances one by one as they are generated by the tracing code and doing something with them. It could print them directly to stdout, in the simplest case, or (more likely) hand them off to a CallTraceStore for storage and later retrieval.

class monkeytype.tracing.CallTraceLogger

Defines the interface that call-trace loggers should implement.

log(trace: CallTrace) → None

Accept a single CallTrace and do something with it. This method is called every time a new CallTrace is generated.

flush() → None

Flush logged call traces. This method is called once on exiting from the trace() context manager.

This method doesn’t have to be implemented; by default it is a no-op. For very simple trace loggers (e.g. logging to stdout), each trace can be fully handled in log() directly as it is received, and no batching or flushing is needed.

CallTraceStoreLogger

class monkeytype.db.base.CallTraceStoreLogger(store: CallTraceStore)

The typical function of a call-trace logger is just to batch collected traces and then store them in a CallTraceStore. This is implemented by CallTraceStoreLogger. Its log() method just appends the trace to an in-memory list, and its flush() method saves all collected traces to the given store.

CallTrace

class monkeytype.tracing.CallTrace(func: Callable, arg_types: Dict[str, type], return_type: Optional[type] = None, yield_type: Optional[type] = None)

Type information for one traced call of one function.

func: Callable

The function that was called.

funcname: str

Fully-qualified name of the function, including module name (e.g. some.module.some_func or some.module.SomeClass.some_method).

arg_types: Dict[str, type]

Dictionary mapping argument names to types, for this particular traced call.

return_type: Optional[type]

Type returned by this call, or None if this call did not return.

yield_type: Optional[type]

Type yielded by this call, or None if this call did not yield.