1
1
import logging
2
2
import os
3
+ import threading
3
4
import warnings
4
5
6
+ from cpython.pythread cimport PyThread_tss_create, PyThread_tss_get, PyThread_tss_set
5
7
from libc.stdlib cimport free, malloc
6
8
7
9
from pyproj._compat cimport cstrencode
@@ -12,17 +14,22 @@ from pyproj.utils import strtobool
12
14
# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
13
15
_LOGGER = logging.getLogger(" pyproj" )
14
16
_LOGGER.addHandler(logging.NullHandler())
15
- # default to False is the safest mode
16
- # as it supports multithreading
17
- _USE_GLOBAL_CONTEXT = strtobool(os.environ.get(" PYPROJ_GLOBAL_CONTEXT" , " OFF" ))
18
17
# static user data directory to prevent core dumping
19
18
# see: https://github.com/pyproj4/pyproj/issues/678
20
19
cdef const char * _USER_DATA_DIR = proj_context_get_user_writable_directory(NULL , False )
21
20
# Store the message from any internal PROJ errors
22
21
cdef str _INTERNAL_PROJ_ERROR = None
22
+ # global variables
23
+ cdef bint _NETWORK_ENABLED = strtobool(os.environ.get(" PROJ_NETWORK" , " OFF" ))
24
+ cdef char * _CA_BUNDLE_PATH = " "
25
+ # The key to get the context in each thread
26
+ cdef Py_tss_t CONTEXT_THREAD_KEY
27
+
23
28
24
29
def set_use_global_context (active = None ):
25
30
"""
31
+ .. deprecated:: 3.7.0 No longer necessary as there is only one context per thread now.
32
+
26
33
.. versionadded:: 3.0.0
27
34
28
35
Activates the usage of the global context. Using this
@@ -44,10 +51,17 @@ def set_use_global_context(active=None):
44
51
the environment variable PYPROJ_GLOBAL_CONTEXT and defaults
45
52
to False if it is not found.
46
53
"""
47
- global _USE_GLOBAL_CONTEXT
48
54
if active is None :
49
55
active = strtobool(os.environ.get(" PYPROJ_GLOBAL_CONTEXT" , " OFF" ))
50
- _USE_GLOBAL_CONTEXT = bool (active)
56
+ if active:
57
+ warnings.warn(
58
+ (
59
+ " PYPROJ_GLOBAL_CONTEXT is no longer necessary in pyproj 3.7+ "
60
+ " and does not do anything."
61
+ ),
62
+ FutureWarning ,
63
+ stacklevel = 2 ,
64
+ )
51
65
52
66
53
67
def get_user_data_dir (create = False ):
@@ -74,7 +88,7 @@ def get_user_data_dir(create=False):
74
88
The user writable data directory.
75
89
"""
76
90
return proj_context_get_user_writable_directory(
77
- PYPROJ_GLOBAL_CONTEXT , bool (create)
91
+ pyproj_context_create() , bool (create)
78
92
)
79
93
80
94
@@ -124,7 +138,7 @@ cdef void set_context_data_dir(PJ_CONTEXT* context) except *:
124
138
cdef bytes b_database_path = cstrencode(os.path.join(data_dir_list[0 ], " proj.db" ))
125
139
cdef const char * c_database_path = b_database_path
126
140
if not proj_context_set_database_path(context, c_database_path, NULL , NULL ):
127
- warnings.warn(" pyproj unable to set database path." )
141
+ warnings.warn(" pyproj unable to set PROJ database path." )
128
142
cdef int dir_list_len = len (data_dir_list)
129
143
cdef const char ** c_data_dir = < const char ** > malloc(
130
144
(dir_list_len + 1 ) * sizeof(const char * )
@@ -147,6 +161,8 @@ cdef void pyproj_context_initialize(PJ_CONTEXT* context) except *:
147
161
proj_log_func(context, NULL , pyproj_log_function)
148
162
proj_context_use_proj4_init_rules(context, 1 )
149
163
set_context_data_dir(context)
164
+ proj_context_set_ca_bundle_path(context, _CA_BUNDLE_PATH)
165
+ proj_context_set_enable_network(context, _NETWORK_ENABLED)
150
166
151
167
152
168
cdef class ContextManager:
@@ -170,35 +186,75 @@ cdef class ContextManager:
170
186
return context_manager
171
187
172
188
173
- # Different libraries that modify the PROJ global context will influence
174
- # each other without realizing it. Due to this, pyproj is creating it's own
175
- # global context so that it doesn't bother other libraries and is insulated
176
- # against possible external changes made to the PROJ global context.
177
- # See: https://github.com/pyproj4/pyproj/issues/722
178
- cdef PJ_CONTEXT* PYPROJ_GLOBAL_CONTEXT = proj_context_create()
179
- cdef ContextManager CONTEXT_MANAGER = ContextManager.create(PYPROJ_GLOBAL_CONTEXT)
189
+ class ContextManagerLocal (threading.local ):
190
+ """
191
+ Threading local instance for cython ContextManager class.
192
+ """
193
+
194
+ def __init__ (self ):
195
+ self .context_manager = None # Initialises in each thread
196
+ super ().__init__()
197
+
180
198
199
+ _CONTEXT_MANAGER_LOCAL = ContextManagerLocal()
181
200
182
201
cdef PJ_CONTEXT* pyproj_context_create() except * :
183
202
"""
184
203
Create and initialize the context(s) for pyproj.
185
204
This also manages whether the global context is used.
186
205
"""
187
- if _USE_GLOBAL_CONTEXT:
188
- return PYPROJ_GLOBAL_CONTEXT
189
- return proj_context_clone(PYPROJ_GLOBAL_CONTEXT)
206
+ global _CONTEXT_MANAGER_LOCAL
207
+
208
+ if PyThread_tss_create(& CONTEXT_THREAD_KEY) != 0 :
209
+ raise MemoryError (" Unable to create key for PROJ context in thread." )
210
+ cdef const void * thread_pyproj_context = PyThread_tss_get(& CONTEXT_THREAD_KEY)
211
+ cdef PJ_CONTEXT* pyproj_context = NULL
212
+ if thread_pyproj_context == NULL :
213
+ pyproj_context = proj_context_create()
214
+ pyproj_context_initialize(pyproj_context)
215
+ PyThread_tss_set(& CONTEXT_THREAD_KEY, pyproj_context)
216
+ _CONTEXT_MANAGER_LOCAL.context_manager = ContextManager.create(pyproj_context)
217
+ else :
218
+ pyproj_context = < PJ_CONTEXT* > thread_pyproj_context
219
+ return pyproj_context
220
+
221
+
222
+ def get_context_manager ():
223
+ """
224
+ This returns the manager for the context
225
+ responsible for cleanup
226
+ """
227
+ return _CONTEXT_MANAGER_LOCAL.context_manager
190
228
191
- cdef void pyproj_context_destroy(PJ_CONTEXT* context) except * :
229
+
230
+ cpdef _set_context_data_dir():
192
231
"""
193
- Destroy context only if not the global context
232
+ Python compatible function to set the
233
+ data directory on the current context
194
234
"""
195
- if context ! = PYPROJ_GLOBAL_CONTEXT:
196
- proj_context_destroy(context)
235
+ set_context_data_dir(pyproj_context_create())
236
+
197
237
238
+ cpdef _set_context_ca_bundle_path(str ca_bundle_path):
239
+ """
240
+ Python compatible function to set the
241
+ CA Bundle path on the current context
242
+ and cache for future generated contexts
243
+ """
244
+ global _CA_BUNDLE_PATH
198
245
199
- cpdef _pyproj_global_context_initialize():
200
- pyproj_context_initialize(PYPROJ_GLOBAL_CONTEXT)
246
+ b_ca_bundle_path = cstrencode(ca_bundle_path)
247
+ _CA_BUNDLE_PATH = b_ca_bundle_path
248
+ proj_context_set_ca_bundle_path(pyproj_context_create(), _CA_BUNDLE_PATH)
201
249
202
250
203
- cpdef _global_context_set_data_dir():
204
- set_context_data_dir(PYPROJ_GLOBAL_CONTEXT)
251
+ cpdef _set_context_network_enabled(bint enabled):
252
+ """
253
+ Python compatible function to set the
254
+ network enables on the current context
255
+ and cache for future generated contexts
256
+ """
257
+ global _NETWORK_ENABLED
258
+
259
+ _NETWORK_ENABLED = enabled
260
+ proj_context_set_enable_network(pyproj_context_create(), _NETWORK_ENABLED)
0 commit comments