4646
4747namespace duckdb {
4848
49- DefaultConnectionHolder DuckDBPyConnection::default_connection; // NOLINT: allow global
50- DBInstanceCache instance_cache; // NOLINT: allow global
51- std::shared_ptr<PythonImportCache> DuckDBPyConnection::import_cache = nullptr ; // NOLINT: allow global
52- PythonEnvironmentType DuckDBPyConnection::environment = PythonEnvironmentType::NORMAL ; // NOLINT: allow global
53- std::string DuckDBPyConnection::formatted_python_version = " " ;
49+ // All process-global module state lives in one struct, reached only through GetModuleState().
50+ // This is the single seam to retarget for PEP 489 multi-phase init (per-module state via
51+ // PyModule_GetState); call sites never touch the storage directly.
52+ struct DuckDBPyModuleState {
53+ DefaultConnectionHolder default_connection;
54+ DBInstanceCache instance_cache;
55+ std::shared_ptr<PythonImportCache> import_cache;
56+ PythonEnvironmentType environment = PythonEnvironmentType::NORMAL ;
57+ std::string formatted_python_version;
58+ };
59+
60+ static DuckDBPyModuleState &GetModuleState () {
61+ static DuckDBPyModuleState state; // NOLINT: allow global - sole module-state seam (future: PyModule_GetState)
62+ return state;
63+ }
5464
5565DuckDBPyConnection::~DuckDBPyConnection () {
5666 try {
@@ -90,14 +100,14 @@ void DuckDBPyConnection::DetectEnvironment() {
90100 py::object version_info = sys.attr (" version_info" );
91101 int major = py::cast<int >(version_info.attr (" major" ));
92102 int minor = py::cast<int >(version_info.attr (" minor" ));
93- DuckDBPyConnection:: formatted_python_version = std::to_string (major) + " ." + std::to_string (minor);
103+ GetModuleState (). formatted_python_version = std::to_string (major) + " ." + std::to_string (minor);
94104
95105 // If __main__ does not have a __file__ attribute, we are in interactive mode
96106 auto main_module = py::module_::import (" __main__" );
97107 if (py::hasattr (main_module, " __file__" )) {
98108 return ;
99109 }
100- DuckDBPyConnection:: environment = PythonEnvironmentType::INTERACTIVE ;
110+ GetModuleState (). environment = PythonEnvironmentType::INTERACTIVE ;
101111 if (!ModuleIsLoaded<IpythonCacheItem>()) {
102112 return ;
103113 }
@@ -115,7 +125,7 @@ void DuckDBPyConnection::DetectEnvironment() {
115125 }
116126 py::dict ipython_config = ipython.attr (" config" );
117127 if (ipython_config.contains (" IPKernelApp" )) {
118- DuckDBPyConnection:: environment = PythonEnvironmentType::JUPYTER ;
128+ GetModuleState (). environment = PythonEnvironmentType::JUPYTER ;
119129 }
120130 return ;
121131}
@@ -126,11 +136,11 @@ bool DuckDBPyConnection::DetectAndGetEnvironment() {
126136}
127137
128138bool DuckDBPyConnection::IsJupyter () {
129- return DuckDBPyConnection:: environment == PythonEnvironmentType::JUPYTER ;
139+ return GetModuleState (). environment == PythonEnvironmentType::JUPYTER ;
130140}
131141
132142std::string DuckDBPyConnection::FormattedPythonVersion () {
133- return DuckDBPyConnection:: formatted_python_version;
143+ return GetModuleState (). formatted_python_version ;
134144}
135145
136146// NOTE: this function is generated by tools/pythonpkg/scripts/generate_connection_methods.py.
@@ -2198,8 +2208,8 @@ static std::shared_ptr<DuckDBPyConnection> FetchOrCreateInstance(const string &d
21982208 D_ASSERT (py::gil_check ());
21992209 py::gil_scoped_release release;
22002210 unique_lock<std::recursive_mutex> lock (res->py_connection_lock );
2201- auto database =
2202- instance_cache. GetOrCreateInstance (database_path, config, cache_instance, InstantiateNewInstance);
2211+ auto database = GetModuleState (). instance_cache . GetOrCreateInstance (database_path, config, cache_instance,
2212+ InstantiateNewInstance);
22032213 res->con .SetDatabase (std::move (database));
22042214 res->con .SetConnection (make_uniq<Connection>(res->con .GetDatabase ()));
22052215 }
@@ -2284,14 +2294,15 @@ identifier_map_t<BoundParameterData> DuckDBPyConnection::TransformPythonParamDic
22842294}
22852295
22862296std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::DefaultConnection () {
2287- return default_connection.Get ();
2297+ return GetModuleState (). default_connection .Get ();
22882298}
22892299
22902300void DuckDBPyConnection::SetDefaultConnection (std::shared_ptr<DuckDBPyConnection> connection) {
2291- return default_connection.Set (std::move (connection));
2301+ return GetModuleState (). default_connection .Set (std::move (connection));
22922302}
22932303
22942304PythonImportCache *DuckDBPyConnection::ImportCache () {
2305+ auto &import_cache = GetModuleState ().import_cache ;
22952306 if (!import_cache) {
22962307 import_cache = std::make_shared<PythonImportCache>();
22972308 }
@@ -2315,7 +2326,7 @@ ModifiedMemoryFileSystem &DuckDBPyConnection::GetObjectFileSystem() {
23152326}
23162327
23172328bool DuckDBPyConnection::IsInteractive () {
2318- return DuckDBPyConnection:: environment != PythonEnvironmentType::NORMAL ;
2329+ return GetModuleState (). environment != PythonEnvironmentType::NORMAL ;
23192330}
23202331
23212332std::shared_ptr<DuckDBPyConnection> DuckDBPyConnection::Enter () {
@@ -2333,8 +2344,8 @@ void DuckDBPyConnection::Exit(DuckDBPyConnection &self, const py::object &exc_ty
23332344}
23342345
23352346void DuckDBPyConnection::Cleanup () {
2336- default_connection.Set (nullptr );
2337- import_cache.reset ();
2347+ GetModuleState (). default_connection .Set (nullptr );
2348+ GetModuleState (). import_cache .reset ();
23382349}
23392350
23402351bool DuckDBPyConnection::IsPandasDataframe (const py::object &object) {
0 commit comments