PyAPI: support logging for Context.temp_override

Adds comprehensive logging system for temp_override context manager to
help developers debug "context is incorrect" operator poll failures.
The logging tracks all context member access during temp_override
sessions and provides detailed summaries to identify context
availability issues.

Features:
- Command-line logging: `./blender --log-level trace --log "context" `
- Python programmatic control: `temp_override_handle.logging_set(True)`
- C-level API: CTX_temp_override_logging_set/get() functions
- Tracks individual member access
- Uses CLOG logging infrastructure

The logging helps identify which context members are accessed during
temp_override sessions and whether they return valid data, making it
easier to debug operator poll functions that fail with context errors.

Ref !144810
This commit is contained in:
Nick Alberelli
2025-09-19 06:48:07 +00:00
committed by Campbell Barton
parent 20bea06f4a
commit 439fe8a1a0
5 changed files with 275 additions and 14 deletions

View File

@@ -8,12 +8,13 @@
#pragma once
#include <optional>
#include <string>
#include "BLI_sys_types.h"
#ifdef WITH_INTERNATIONAL
# include <optional>
# include "BLI_string_ref.hh"
#endif
@@ -137,6 +138,12 @@ void BPY_free_srna_pytype(StructRNA *srna);
*/
[[nodiscard]] bool BPY_string_is_keyword(const char *str);
/**
* Get current Python stack location.
* Returns a string like `filename.py:123` if available, #std::nullopt otherwise.
*/
[[nodiscard]] std::optional<std::string> BPY_python_current_file_and_line(void);
/* `bpy_rna_callback.cc` */
void BPY_callback_screen_free(ARegionType *art);

View File

@@ -12,6 +12,7 @@
#include <Python.h>
#include <frameobject.h>
#include <optional>
#ifdef WITH_PYTHON_MODULE
# include "pylifecycle.h" /* For `Py_Version`. */
@@ -21,6 +22,7 @@
#include "CLG_log.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
@@ -850,6 +852,37 @@ bool BPY_context_member_get(bContext *C, const char *member, bContextDataResult
return done;
}
std::optional<std::string> BPY_python_current_file_and_line()
{
/* Early return if Python is not initialized, usually during startup.
* This function shouldn't operate if Python isn't initialized yet.
*
* In most cases this shouldn't be done, make an exception as it's needed for logging. */
if (!Py_IsInitialized()) {
return std::nullopt;
}
PyGILState_STATE gilstate;
const bool use_gil = !PyC_IsInterpreterActive();
std::optional<std::string> result = std::nullopt;
if (use_gil) {
gilstate = PyGILState_Ensure();
}
const char *filename = nullptr;
int lineno = -1;
PyC_FileAndNum_Safe(&filename, &lineno);
if (filename) {
result = std::string(filename) + ":" + std::to_string(lineno);
}
if (use_gil) {
PyGILState_Release(gilstate);
}
return result;
}
#ifdef WITH_PYTHON_MODULE
/* TODO: reloading the module isn't functional at the moment. */

View File

@@ -23,6 +23,7 @@
#include "bpy_rna_context.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "RNA_access.hh"
@@ -106,6 +107,14 @@ static bool wm_check_region_exists(const bScreen *screen,
return false;
}
/**
* Helper function to configure context logging with extensible options.
*/
static void bpy_rna_context_logging_set(bContext *C, bool enable)
{
CTX_member_logging_set(C, enable);
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -121,6 +130,9 @@ struct ContextStore {
bool area_is_set;
ARegion *region;
bool region_is_set;
/** User's desired logging state for this temp_override instance (can be changed at runtime). */
bool use_logging;
};
struct BPyContextTempOverride {
@@ -289,6 +301,11 @@ static PyObject *bpy_rna_context_temp_override_enter(BPyContextTempOverride *sel
bContext *C = self->context;
Main *bmain = CTX_data_main(C);
/* Enable logging for this temporary override context if the user has requested it. */
if (self->ctx_temp.use_logging) {
bpy_rna_context_logging_set(C, true);
}
/* It's crucial to call #CTX_py_state_pop if this function fails with an error. */
CTX_py_state_push(C, &self->py_state, self->py_state_context_dict);
@@ -349,7 +366,7 @@ static PyObject *bpy_rna_context_temp_override_enter(BPyContextTempOverride *sel
CTX_wm_region_set(C, self->ctx_temp.region);
}
Py_RETURN_NONE;
return Py_NewRef(self);
}
static PyObject *bpy_rna_context_temp_override_exit(BPyContextTempOverride *self,
@@ -502,11 +519,33 @@ static PyObject *bpy_rna_context_temp_override_exit(BPyContextTempOverride *self
if (context_dict_test && (context_dict_test != self->py_state_context_dict)) {
Py_DECREF(context_dict_test);
}
/* Restore logging state based on the user's preference stored in ctx_init.use_logging. */
bpy_rna_context_logging_set(C, self->ctx_init.use_logging);
CTX_py_state_pop(C, &self->py_state);
Py_RETURN_NONE;
}
static PyObject *bpy_rna_context_temp_override_logging_set(BPyContextTempOverride *self,
PyObject *args,
PyObject *kwds)
{
bool enable = true;
static const char *kwlist[] = {"", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", (char **)kwlist, PyC_ParseBool, &enable)) {
return nullptr;
}
self->ctx_temp.use_logging = enable;
bpy_rna_context_logging_set(self->context, enable);
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
@@ -520,6 +559,9 @@ static PyObject *bpy_rna_context_temp_override_exit(BPyContextTempOverride *self
static PyMethodDef bpy_rna_context_temp_override_methods[] = {
{"__enter__", (PyCFunction)bpy_rna_context_temp_override_enter, METH_NOARGS},
{"__exit__", (PyCFunction)bpy_rna_context_temp_override_exit, METH_VARARGS},
{"logging_set",
(PyCFunction)bpy_rna_context_temp_override_logging_set,
METH_VARARGS | METH_KEYWORDS},
{nullptr},
};