4747
4848namespace 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
5666DuckDBPyConnection::~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
129139bool DuckDBPyConnection::IsJupyter () {
130- return DuckDBPyConnection:: environment == PythonEnvironmentType::JUPYTER ;
140+ return GetModuleState (). environment == PythonEnvironmentType::JUPYTER ;
131141}
132142
133143std::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
22872297std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::DefaultConnection () {
2288- return default_connection.Get ();
2298+ return GetModuleState (). default_connection .Get ();
22892299}
22902300
22912301void 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
22952305PythonImportCache *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
23182329bool DuckDBPyConnection::IsInteractive () {
2319- return DuckDBPyConnection:: environment != PythonEnvironmentType::NORMAL ;
2330+ return GetModuleState (). environment != PythonEnvironmentType::NORMAL ;
23202331}
23212332
23222333std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::Enter () {
@@ -2334,8 +2345,8 @@ void DuckDBPyConnection::Exit(DuckDBPyConnection &self, const py::object &exc_ty
23342345}
23352346
23362347void DuckDBPyConnection::Cleanup () {
2337- default_connection.Set (nullptr );
2338- import_cache.reset ();
2348+ GetModuleState (). default_connection .Set (nullptr );
2349+ GetModuleState (). import_cache .reset ();
23392350}
23402351
23412352bool DuckDBPyConnection::IsPandasDataframe (const py::object &object) {
0 commit comments