Repository: duckdb/duckdb
File: tools/pythonpkg/src/pyconnection.cpp (C++)

Code before:
	return optional_idx(limit);
}

static optional_idx GetGlobalLimit(ClientContext &context) {
	Value result;
	if (!context.TryGetCurrentSetting("global_scan_frames", result)) {
		return optional_idx();
	}
	auto limit = result.GetValue<uint64_t>();
	return optional_idx(limit);
}

static unique_ptr<TableRef> ScanReplacement(ClientContext &context, const string &table_name,
                                            ReplacementScanData *data) {
	py::gil_scoped_acquire acquire;
	auto py_table_name = py::str(table_name);
	// Here we do an exhaustive search on the frame lineage
	auto current_frame = py::module::import("inspect").attr("currentframe")();
	auto client_properties = context.GetClientProperties();

	auto global_limit = GetGlobalLimit(context);
	auto local_limit = GetLocalLimit(context);

	idx_t depth = 0;
	while (hasattr(current_frame, "f_locals")) {
		auto local_dict = py::reinterpret_borrow<py::dict>(current_frame.attr("f_locals"));
		// search local dictionary

		auto can_search_local = !local_limit.IsValid() || depth < local_limit.GetIndex();
		auto can_search_global = !global_limit.IsValid() || depth < global_limit.GetIndex();

		if (local_dict && can_search_local) {
			auto result = TryReplacement(local_dict, py_table_name, client_properties, current_frame);
			if (result) {
				return result;
			}
		}
		// search global dictionary
		auto global_dict = py::reinterpret_borrow<py::dict>(current_frame.attr("f_globals"));
		if (global_dict && can_search_global) {
			auto result = TryReplacement(global_dict, py_table_name, client_properties, current_frame);
			if (result) {
				return result;
			}
		}
		if (!can_search_local && !can_search_global) {
			break;
		}
		current_frame = current_frame.attr("f_back");
		depth++;
	}

Review comment (suggestion):
I wonder if we should search the entire stack by default. It seems this behavior was introduced in https://github.com/duckdb/duckdb/pull/3000 - but that might be have been too heavy-handed of a fix for the linked issue. Perhaps we should respect Pythons' variable resolution by default instead?

Since that's a breaking change we would need to be careful with making it, though.

Code after:
	vector<Value> args;
	args.reserve(py::len(params));

	for (auto param : params) {
		args.emplace_back(TransformPythonValue(param, LogicalType::UNKNOWN, false));
	}
	return args;
}

case_insensitive_map_t<Value> DuckDBPyConnection::TransformPythonParamDict(const py::dict &params) {
	case_insensitive_map_t<Value> args;

	for (auto pair : params) {
		auto &key = pair.first;
		auto &value = pair.second;
		args[std::string(py::str(key))] = TransformPythonValue(value, LogicalType::UNKNOWN, false);
	}
	return args;
}

shared_ptr<DuckDBPyConnection> DuckDBPyConnection::DefaultConnection() {
	if (!default_connection) {
		py::dict config_dict;
		default_connection = DuckDBPyConnection::Connect(":memory:", false, config_dict);
	}
	return default_connection;
}

PythonImportCache *DuckDBPyConnection::ImportCache() {
	if (!import_cache) {
		import_cache = make_shared_ptr<PythonImportCache>();
	}
	return import_cache.get();
}

ModifiedMemoryFileSystem &DuckDBPyConnection::GetObjectFileSystem() {
	if (!internal_object_filesystem) {
		D_ASSERT(!FileSystemIsRegistered("DUCKDB_INTERNAL_OBJECTSTORE"));
		auto &import_cache_py = *ImportCache();
		auto modified_memory_fs = import_cache_py.duckdb.filesystem.ModifiedMemoryFileSystem();
		if (modified_memory_fs.ptr() == nullptr) {
			throw InvalidInputException(
			    "This operation could not be completed because required module 'fsspec' is not installed");
		}
		internal_object_filesystem = make_shared_ptr<ModifiedMemoryFileSystem>(modified_memory_fs());
		auto &abstract_fs = reinterpret_cast<AbstractFileSystem &>(*internal_object_filesystem);
		RegisterFilesystem(abstract_fs);
	}
	return *internal_object_filesystem;
}