@@ -1074,21 +1074,35 @@ def initialize(cls, locales, initial_lang):
1074
1074
encodings [lang ] = encoding
1075
1075
1076
1076
cls .encodings = encodings
1077
- cls .__shared_state [ 'current_lang' ] = initial_lang
1077
+ cls .__initial_lang = initial_lang
1078
1078
cls .initialized = True
1079
1079
1080
+ def __get_shared_state (self ):
1081
+ if not self .initialized :
1082
+ raise LocaleBorgUninitializedException ()
1083
+ shared_state = getattr (self .__thread_local , 'shared_state' , None )
1084
+ if shared_state is None :
1085
+ shared_state = {'current_lang' : self .__initial_lang }
1086
+ self .__thread_local .shared_state = shared_state
1087
+ return shared_state
1088
+
1080
1089
@classmethod
1081
1090
def reset (cls ):
1082
1091
"""Reset LocaleBorg.
1083
1092
1084
1093
Used in testing to prevent leaking state between tests.
1085
1094
"""
1095
+ import threading
1096
+ cls .__thread_local = threading .local ()
1097
+ cls .__thread_lock = threading .Lock ()
1098
+
1086
1099
cls .locales = {}
1087
1100
cls .encodings = {}
1088
- cls .__shared_state = {'current_lang' : None }
1089
1101
cls .initialized = False
1090
1102
cls .month_name_handlers = []
1091
1103
cls .formatted_date_handlers = []
1104
+ cls .thread_local = None
1105
+ cls .thread_lock = None
1092
1106
1093
1107
@classmethod
1094
1108
def add_handler (cls , month_name_handler = None , formatted_date_handler = None ):
@@ -1115,7 +1129,16 @@ def __init__(self):
1115
1129
"""Initialize."""
1116
1130
if not self .initialized :
1117
1131
raise LocaleBorgUninitializedException ()
1118
- self .__dict__ = self .__shared_state
1132
+
1133
+ @property
1134
+ def current_lang (self ):
1135
+ """Return the current language."""
1136
+ return self .__get_shared_state ()['current_lang' ]
1137
+
1138
+ def __set_locale (self , lang ):
1139
+ """Set the locale for language lang without updating current_lang."""
1140
+ locale_n = self .locales [lang ]
1141
+ locale .setlocale (locale .LC_ALL , locale_n )
1119
1142
1120
1143
def set_locale (self , lang ):
1121
1144
"""Set the locale for language lang, returns an empty string.
@@ -1124,58 +1147,64 @@ def set_locale(self, lang):
1124
1147
in windows that cannot be guaranted.
1125
1148
In either case, the locale encoding is available in cls.encodings[lang]
1126
1149
"""
1127
- # intentional non try-except: templates must ask locales with a lang,
1128
- # let the code explode here and not hide the point of failure
1129
- # Also, not guarded with an if lang==current_lang because calendar may
1130
- # put that out of sync
1131
- locale_n = self . locales [ lang ]
1132
- self . __shared_state [ 'current_lang' ] = lang
1133
- locale . setlocale ( locale . LC_ALL , locale_n )
1134
- return ''
1150
+ with self . __thread_lock :
1151
+ # intentional non try-except: templates must ask locales with a lang,
1152
+ # let the code explode here and not hide the point of failure
1153
+ # Also, not guarded with an if lang==current_lang because calendar may
1154
+ # put that out of sync
1155
+ self . __set_locale ( lang )
1156
+ self . __get_shared_state ()[ 'current_lang' ] = lang
1157
+ return ''
1135
1158
1136
1159
def get_month_name (self , month_no , lang ):
1137
1160
"""Return localized month name in an unicode string."""
1138
- for handler in self .month_name_handlers :
1139
- res = handler (month_no , lang )
1140
- if res is not None :
1141
- return res
1142
- if sys .version_info [0 ] == 3 : # Python 3
1143
- with calendar .different_locale (self .locales [lang ]):
1144
- s = calendar .month_name [month_no ]
1145
- # for py3 s is unicode
1146
- else : # Python 2
1147
- with calendar .TimeEncoding (self .locales [lang ]):
1148
- s = calendar .month_name [month_no ]
1149
- enc = self .encodings [lang ]
1150
- if not enc :
1151
- enc = 'UTF-8'
1152
-
1153
- s = s .decode (enc )
1154
- # paranoid about calendar ending in the wrong locale (windows)
1155
- self .set_locale (self .current_lang )
1156
- return s
1161
+ # For thread-safety
1162
+ with self .__thread_lock :
1163
+ for handler in self .month_name_handlers :
1164
+ res = handler (month_no , lang )
1165
+ if res is not None :
1166
+ return res
1167
+ if sys .version_info [0 ] == 3 : # Python 3
1168
+ with calendar .different_locale (self .locales [lang ]):
1169
+ s = calendar .month_name [month_no ]
1170
+ # for py3 s is unicode
1171
+ else : # Python 2
1172
+ with calendar .TimeEncoding (self .locales [lang ]):
1173
+ s = calendar .month_name [month_no ]
1174
+ enc = self .encodings [lang ]
1175
+ if not enc :
1176
+ enc = 'UTF-8'
1177
+
1178
+ s = s .decode (enc )
1179
+ # paranoid about calendar ending in the wrong locale (windows)
1180
+ self .__set_locale (self .current_lang )
1181
+ return s
1157
1182
1158
1183
def formatted_date (self , date_format , date ):
1159
1184
"""Return the formatted date as unicode."""
1160
- fmt_date = None
1161
- # First check handlers
1162
- for handler in self .formatted_date_handlers :
1163
- fmt_date = handler (date_format , date , self .__shared_state ['current_lang' ])
1164
- if fmt_date is not None :
1165
- break
1166
- # If no handler was able to format the date, ask Python
1167
- if fmt_date is None :
1168
- if date_format == 'webiso' :
1169
- # Formatted after RFC 3339 (web ISO 8501 profile) with Zulu
1170
- # zone desgignator for times in UTC and no microsecond precision.
1171
- fmt_date = date .replace (microsecond = 0 ).isoformat ().replace ('+00:00' , 'Z' )
1172
- else :
1173
- fmt_date = date .strftime (date_format )
1185
+ with self .__thread_lock :
1186
+ current_lang = self .current_lang
1187
+ # For thread-safety
1188
+ self .__set_locale (current_lang )
1189
+ fmt_date = None
1190
+ # First check handlers
1191
+ for handler in self .formatted_date_handlers :
1192
+ fmt_date = handler (date_format , date , current_lang )
1193
+ if fmt_date is not None :
1194
+ break
1195
+ # If no handler was able to format the date, ask Python
1196
+ if fmt_date is None :
1197
+ if date_format == 'webiso' :
1198
+ # Formatted after RFC 3339 (web ISO 8501 profile) with Zulu
1199
+ # zone desgignator for times in UTC and no microsecond precision.
1200
+ fmt_date = date .replace (microsecond = 0 ).isoformat ().replace ('+00:00' , 'Z' )
1201
+ else :
1202
+ fmt_date = date .strftime (date_format )
1174
1203
1175
- # Issue #383, this changes from py2 to py3
1176
- if isinstance (fmt_date , bytes_str ):
1177
- fmt_date = fmt_date .decode ('utf8' )
1178
- return fmt_date
1204
+ # Issue #383, this changes from py2 to py3
1205
+ if isinstance (fmt_date , bytes_str ):
1206
+ fmt_date = fmt_date .decode ('utf8' )
1207
+ return fmt_date
1179
1208
1180
1209
1181
1210
class ExtendedRSS2 (rss .RSS2 ):
0 commit comments