Skip to content

Commit 4542c30

Browse files
Extract DuckDBPyConnection module state into DuckDBPyModuleState (#515)
Consolidate the five process-global statics (default_connection, instance_cache, import_cache, environment, formatted_python_version) into one DuckDBPyModuleState struct reached only through GetModuleState(). The accessor returns a function-local singleton for now, so behavior is unchanged; it is the single seam to retarget for PEP 489 multi-phase init / per-module state (PyModule_GetState) on the path to free-threading. Removes the // NOLINT: allow global statics from pyconnection.
2 parents 5a7cb18 + b27d05b commit 4542c30

2 files changed

Lines changed: 28 additions & 24 deletions

File tree

src/duckdb_py/include/duckdb_python/pyconnection/pyconnection.hpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,6 @@ struct DuckDBPyConnection : public std::enable_shared_from_this<DuckDBPyConnecti
361361
void EnableProfiling();
362362
void DisableProfiling();
363363

364-
//! Default connection to an in-memory database
365-
static DefaultConnectionHolder default_connection;
366-
//! Caches and provides an interface to get frequently used modules+subtypes
367-
static std::shared_ptr<PythonImportCache> import_cache;
368-
369364
static bool IsPandasDataframe(const py::object &object);
370365
static PyArrowObjectType GetArrowType(const py::handle &obj);
371366
static bool IsAcceptedArrowObject(const py::object &object);
@@ -383,8 +378,6 @@ struct DuckDBPyConnection : public std::enable_shared_from_this<DuckDBPyConnecti
383378
bool side_effects);
384379
vector<unique_ptr<SQLStatement>> GetStatements(const py::object &query);
385380

386-
static PythonEnvironmentType environment;
387-
static std::string formatted_python_version;
388381
static void DetectEnvironment();
389382
};
390383

src/duckdb_py/pyconnection.cpp

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,21 @@
4747

4848
namespace duckdb {
4949

50-
DefaultConnectionHolder DuckDBPyConnection::default_connection; // NOLINT: allow global
51-
DBInstanceCache instance_cache; // NOLINT: allow global
52-
std::shared_ptr<PythonImportCache> DuckDBPyConnection::import_cache = nullptr; // NOLINT: allow global
53-
PythonEnvironmentType DuckDBPyConnection::environment = PythonEnvironmentType::NORMAL; // NOLINT: allow global
54-
std::string DuckDBPyConnection::formatted_python_version = "";
50+
// All process-global module state lives in one struct, reached only through GetModuleState().
51+
// This is the single seam to retarget for PEP 489 multi-phase init (per-module state via
52+
// PyModule_GetState); call sites never touch the storage directly.
53+
struct DuckDBPyModuleState {
54+
DefaultConnectionHolder default_connection;
55+
DBInstanceCache instance_cache;
56+
std::shared_ptr<PythonImportCache> import_cache;
57+
PythonEnvironmentType environment = PythonEnvironmentType::NORMAL;
58+
std::string formatted_python_version;
59+
};
60+
61+
static DuckDBPyModuleState &GetModuleState() {
62+
static DuckDBPyModuleState state; // NOLINT: allow global - sole module-state seam (future: PyModule_GetState)
63+
return state;
64+
}
5565

5666
DuckDBPyConnection::~DuckDBPyConnection() {
5767
try {
@@ -91,14 +101,14 @@ void DuckDBPyConnection::DetectEnvironment() {
91101
py::object version_info = sys.attr("version_info");
92102
int major = py::cast<int>(version_info.attr("major"));
93103
int minor = py::cast<int>(version_info.attr("minor"));
94-
DuckDBPyConnection::formatted_python_version = std::to_string(major) + "." + std::to_string(minor);
104+
GetModuleState().formatted_python_version = std::to_string(major) + "." + std::to_string(minor);
95105

96106
// If __main__ does not have a __file__ attribute, we are in interactive mode
97107
auto main_module = py::module_::import("__main__");
98108
if (py::hasattr(main_module, "__file__")) {
99109
return;
100110
}
101-
DuckDBPyConnection::environment = PythonEnvironmentType::INTERACTIVE;
111+
GetModuleState().environment = PythonEnvironmentType::INTERACTIVE;
102112
if (!ModuleIsLoaded<IpythonCacheItem>()) {
103113
return;
104114
}
@@ -116,7 +126,7 @@ void DuckDBPyConnection::DetectEnvironment() {
116126
}
117127
py::dict ipython_config = ipython.attr("config");
118128
if (ipython_config.contains("IPKernelApp")) {
119-
DuckDBPyConnection::environment = PythonEnvironmentType::JUPYTER;
129+
GetModuleState().environment = PythonEnvironmentType::JUPYTER;
120130
}
121131
return;
122132
}
@@ -127,11 +137,11 @@ bool DuckDBPyConnection::DetectAndGetEnvironment() {
127137
}
128138

129139
bool DuckDBPyConnection::IsJupyter() {
130-
return DuckDBPyConnection::environment == PythonEnvironmentType::JUPYTER;
140+
return GetModuleState().environment == PythonEnvironmentType::JUPYTER;
131141
}
132142

133143
std::string DuckDBPyConnection::FormattedPythonVersion() {
134-
return DuckDBPyConnection::formatted_python_version;
144+
return GetModuleState().formatted_python_version;
135145
}
136146

137147
// NOTE: this function is generated by tools/pythonpkg/scripts/generate_connection_methods.py.
@@ -2199,8 +2209,8 @@ static std::shared_ptr<DuckDBPyConnection> FetchOrCreateInstance(const string &d
21992209
D_ASSERT(py::gil_check());
22002210
py::gil_scoped_release release;
22012211
unique_lock<std::recursive_mutex> lock(res->py_connection_lock);
2202-
auto database =
2203-
instance_cache.GetOrCreateInstance(database_path, config, cache_instance, InstantiateNewInstance);
2212+
auto database = GetModuleState().instance_cache.GetOrCreateInstance(database_path, config, cache_instance,
2213+
InstantiateNewInstance);
22042214
res->con.SetDatabase(std::move(database));
22052215
res->con.SetConnection(make_uniq<Connection>(res->con.GetDatabase()));
22062216
}
@@ -2285,14 +2295,15 @@ identifier_map_t<BoundParameterData> DuckDBPyConnection::TransformPythonParamDic
22852295
}
22862296

22872297
std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::DefaultConnection() {
2288-
return default_connection.Get();
2298+
return GetModuleState().default_connection.Get();
22892299
}
22902300

22912301
void DuckDBPyConnection::SetDefaultConnection(std::shared_ptr<DuckDBPyConnection> connection) {
2292-
return default_connection.Set(std::move(connection));
2302+
return GetModuleState().default_connection.Set(std::move(connection));
22932303
}
22942304

22952305
PythonImportCache *DuckDBPyConnection::ImportCache() {
2306+
auto &import_cache = GetModuleState().import_cache;
22962307
if (!import_cache) {
22972308
import_cache = std::make_shared<PythonImportCache>();
22982309
}
@@ -2316,7 +2327,7 @@ ModifiedMemoryFileSystem &DuckDBPyConnection::GetObjectFileSystem() {
23162327
}
23172328

23182329
bool DuckDBPyConnection::IsInteractive() {
2319-
return DuckDBPyConnection::environment != PythonEnvironmentType::NORMAL;
2330+
return GetModuleState().environment != PythonEnvironmentType::NORMAL;
23202331
}
23212332

23222333
std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::Enter() {
@@ -2334,8 +2345,8 @@ void DuckDBPyConnection::Exit(DuckDBPyConnection &self, const py::object &exc_ty
23342345
}
23352346

23362347
void DuckDBPyConnection::Cleanup() {
2337-
default_connection.Set(nullptr);
2338-
import_cache.reset();
2348+
GetModuleState().default_connection.Set(nullptr);
2349+
GetModuleState().import_cache.reset();
23392350
}
23402351

23412352
bool DuckDBPyConnection::IsPandasDataframe(const py::object &object) {

0 commit comments

Comments
 (0)