Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""":mod:`wand.resource` --- Global resource management 

2~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

3 

4There is the global resource to manage in MagickWand API. This module 

5implements automatic global resource management through reference counting. 

6 

7""" 

8import atexit 

9import contextlib 

10import ctypes 

11import warnings 

12 

13from .api import library 

14from .compat import abc, string_type, text 

15from .exceptions import TYPE_MAP, WandException 

16from .version import MAGICK_VERSION_NUMBER 

17 

18__all__ = ('genesis', 'limits', 'safe_copy', 'shutdown', 'terminus', 

19 'DestroyedResourceError', 'Resource', 'ResourceLimits') 

20 

21 

22def genesis(): 

23 """Instantiates the MagickWand API. 

24 

25 .. warning:: 

26 

27 Don't call this function directly. Use :func:`increment_refcount()` and 

28 :func:`decrement_refcount()` functions instead. 

29 

30 """ 

31 library.MagickWandGenesis() 

32 

33 

34def terminus(): 

35 """Cleans up the MagickWand API. 

36 

37 .. warning:: 

38 

39 Don't call this function directly. Use :func:`increment_refcount()` and 

40 :func:`decrement_refcount()` functions instead. 

41 

42 """ 

43 if library.IsMagickWandInstantiated is None: # pragma no cover 

44 library.MagickWandTerminus() 

45 elif library.IsMagickWandInstantiated(): 

46 library.MagickWandTerminus() 

47 

48 

49allocation_map = {} 

50 

51 

52def allocate_ref(addr, deallocator): 

53 global allocation_map 

54 if len(allocation_map) == 0: 

55 genesis() 

56 if addr: 

57 allocation_map[addr] = deallocator 

58 

59 

60def deallocate_ref(addr): 

61 global allocation_map 

62 if addr in list(allocation_map): 

63 deallocator = allocation_map.pop(addr) 

64 if callable(deallocator): 

65 deallocator(addr) 

66 

67 

68@atexit.register 

69def shutdown(): 

70 global allocation_map 

71 for addr in list(allocation_map): 

72 deallocator = allocation_map.pop(addr) 

73 if callable(deallocator): 

74 deallocator(addr) 

75 terminus() 

76 

77 

78def safe_copy(ptr): 

79 """Safely cast memory address to char pointer, convert to python string, 

80 and immediately free resources. 

81 

82 :param ptr: The memory address to convert to text string. 

83 :type ptr: :class:`ctypes.c_void_p` 

84 :returns: :class:`tuple` (:class:`ctypes.c_void_p`, :class:`str`) 

85 

86 .. versionadded:: 0.5.3 

87 """ 

88 string = None 

89 if bool(ptr): 

90 string = text(ctypes.cast(ptr, ctypes.c_char_p).value) 

91 ptr = library.MagickRelinquishMemory(ptr) # Force pointer to zero 

92 return ptr, string 

93 

94 

95class Resource(object): 

96 """Abstract base class for MagickWand object that requires resource 

97 management. Its all subclasses manage the resource semiautomatically 

98 and support :keyword:`with` statement as well:: 

99 

100 with Resource() as resource: 

101 # use the resource... 

102 pass 

103 

104 It doesn't implement constructor by itself, so subclasses should 

105 implement it. Every constructor should assign the pointer of its 

106 resource data into :attr:`resource` attribute inside of :keyword:`with` 

107 :meth:`allocate()` context. For example:: 

108 

109 class Pizza(Resource): 

110 '''My pizza yummy.''' 

111 

112 def __init__(self): 

113 with self.allocate(): 

114 self.resource = library.NewPizza() 

115 

116 .. versionadded:: 0.1.2 

117 

118 """ 

119 

120 #: (:class:`ctypes.CFUNCTYPE`) The :mod:`ctypes` predicate function 

121 #: that returns whether the given pointer (that contains a resource data 

122 #: usually) is a valid resource. 

123 #: 

124 #: .. note:: 

125 #: 

126 #: It is an abstract attribute that has to be implemented 

127 #: in the subclass. 

128 c_is_resource = NotImplemented 

129 

130 #: (:class:`ctypes.CFUNCTYPE`) The :mod:`ctypes` function that destroys 

131 #: the :attr:`resource`. 

132 #: 

133 #: .. note:: 

134 #: 

135 #: It is an abstract attribute that has to be implemented 

136 #: in the subclass. 

137 c_destroy_resource = NotImplemented 

138 

139 #: (:class:`ctypes.CFUNCTYPE`) The :mod:`ctypes` function that gets 

140 #: an exception from the :attr:`resource`. 

141 #: 

142 #: .. note:: 

143 #: 

144 #: It is an abstract attribute that has to be implemented 

145 #: in the subclass. 

146 c_get_exception = NotImplemented 

147 

148 #: (:class:`ctypes.CFUNCTYPE`) The :mod:`ctypes` function that clears 

149 #: an exception of the :attr:`resource`. 

150 #: 

151 #: .. note:: 

152 #: 

153 #: It is an abstract attribute that has to be implemented 

154 #: in the subclass. 

155 c_clear_exception = NotImplemented 

156 

157 @property 

158 def resource(self): 

159 """Internal pointer to the resource instance. It may raise 

160 :exc:`DestroyedResourceError` when the resource has destroyed already. 

161 

162 """ 

163 if getattr(self, 'c_resource', None) is None: 

164 raise DestroyedResourceError(repr(self) + ' is destroyed already') 

165 return self.c_resource 

166 

167 @resource.setter 

168 def resource(self, resource): 

169 # Delete the existing resource if there is one 

170 if getattr(self, 'c_resource', None): 

171 self.destroy() 

172 

173 if self.c_is_resource(resource): 

174 self.c_resource = resource 

175 allocate_ref(self.c_resource, self.c_destroy_resource) 

176 else: 

177 raise TypeError(repr(resource) + ' is an invalid resource') 

178 

179 @resource.deleter 

180 def resource(self): 

181 if getattr(self, 'c_resource', None): 

182 deallocate_ref(self.c_resource) 

183 self.c_resource = None 

184 

185 @contextlib.contextmanager 

186 def allocate(self): 

187 """Allocates the memory for the resource explicitly. Its subclasses 

188 should assign the created resource into :attr:`resource` attribute 

189 inside of this context. For example:: 

190 

191 with resource.allocate(): 

192 resource.resource = library.NewResource() 

193 

194 """ 

195 yield self 

196 

197 def destroy(self): 

198 """Cleans up the resource explicitly. If you use the resource in 

199 :keyword:`with` statement, it was called implicitly so have not to 

200 call it. 

201 

202 """ 

203 del self.resource 

204 

205 def get_exception(self): 

206 """Gets a current exception instance. 

207 

208 :returns: a current exception. it can be ``None`` as well if any 

209 errors aren't occurred 

210 :rtype: :class:`wand.exceptions.WandException` 

211 

212 """ 

213 severity = ctypes.c_int() 

214 desc = self.c_get_exception(self.resource, ctypes.byref(severity)) 

215 if severity.value == 0: 

216 return 

217 self.c_clear_exception(self.resource) 

218 exc_cls = TYPE_MAP[severity.value] 

219 message = desc.value 

220 if not isinstance(message, string_type): 

221 message = message.decode(errors='replace') 

222 return exc_cls(message) 

223 

224 def raise_exception(self, stacklevel=1): 

225 """Raises an exception or warning if it has occurred.""" 

226 e = self.get_exception() 

227 if isinstance(e, Warning): 

228 warnings.warn(e, stacklevel=stacklevel + 1) 

229 elif isinstance(e, Exception): 

230 raise e 

231 

232 def make_blob(self, format=None): 

233 raise NotImplementedError 

234 

235 def __enter__(self): 

236 return self 

237 

238 def __exit__(self, type, value, traceback): 

239 self.destroy() 

240 

241 def __del__(self): 

242 try: 

243 self.destroy() 

244 except DestroyedResourceError: 

245 pass 

246 

247 

248class DestroyedResourceError(WandException, ReferenceError, AttributeError): 

249 """An error that rises when some code tries access to an already 

250 destroyed resource. 

251 

252 .. versionchanged:: 0.3.0 

253 It becomes a subtype of :exc:`wand.exceptions.WandException`. 

254 

255 """ 

256 

257 

258class ResourceLimits(abc.MutableMapping): 

259 """Wrapper for MagickCore resource limits. 

260 Useful for dynamically reducing system resources before attempting risky, 

261 or slow running, :class:`~wand.image.Image` operations. 

262 

263 For example:: 

264 

265 from wand.image import Image 

266 from wand.resource import limits 

267 

268 # Use 100MB of ram before writing temp data to disk. 

269 limits['memory'] = 1024 * 1024 * 100 

270 # Reject images larger than 1000x1000. 

271 limits['width'] = 1000 

272 limits['height'] = 1000 

273 

274 # Debug resources used. 

275 with Image(filename='user.jpg') as img: 

276 print('Using {0} of {1} memory'.format(limits.resource('memory'), 

277 limits['memory'])) 

278 

279 # Dump list of all limits. 

280 for label in limits: 

281 print('{0} => {1}'.format(label, limits[label])) 

282 

283 Available resource keys: 

284 

285 - ``'area'`` - Maximum `width * height` of a pixel cache before writing to 

286 disk. 

287 - ``'disk'`` - Maximum bytes used by pixel cache on disk before exception 

288 is thrown. 

289 - ``'file'`` - Maximum cache files opened at any given time. 

290 - ``'height'`` - Maximum height of image before exception is thrown. 

291 - ``'list_length'`` - Maximum images in sequence. Only available with 

292 recent version of ImageMagick. 

293 - ``'map'`` - Maximum memory map in bytes to allocated for pixel cache 

294 before using disk. 

295 - ``'memory'`` - Maximum bytes to allocated for pixel cache before using 

296 disk. 

297 - ``'thread'`` - Maximum parallel task sub-routines can spawn - if using 

298 OpenMP. 

299 - ``'throttle'`` - Total milliseconds to yield to CPU - if possible. 

300 - ``'time'`` - Maximum seconds before exception is thrown. 

301 - ``'width'`` - Maximum width of image before exception is thrown. 

302 

303 .. versionadded:: 0.5.1 

304 """ 

305 

306 #: (:class:`tuple`) List of available resource types for ImageMagick-6. 

307 _limits6 = ('undefined', 'area', 'disk', 'file', 'map', 'memory', 'thread', 

308 'time', 'throttle', 'width', 'height') 

309 

310 #: (:class:`tuple`) List of available resource types for ImageMagick-7. 

311 _limits7 = ('undefined', 'area', 'disk', 'file', 'height', 'map', 'memory', 

312 'thread', 'throttle', 'time', 'width', 'list_length') 

313 

314 def __init__(self): 

315 if MAGICK_VERSION_NUMBER < 0x700: 

316 self.limits = self._limits6 

317 else: 

318 self.limits = self._limits7 

319 

320 def __getitem__(self, r): 

321 return self.get_resource_limit(r) 

322 

323 def __setitem__(self, r, v): 

324 self.set_resource_limit(r, v) 

325 

326 def __delitem__(self, r): 

327 self[r] = 0 

328 

329 def __iter__(self): 

330 return iter(self.limits) 

331 

332 def __len__(self): 

333 return len(self.limits) 

334 

335 def _to_idx(self, resource): 

336 """Helper method to map resource string to enum value.""" 

337 return self.limits.index(resource) 

338 

339 def resource(self, resource): 

340 """Get the current value for the resource type. 

341 

342 :param resource: Resource type. 

343 :type resource: :class:`basestring` 

344 :rtype: :class:`numeric.Integral` 

345 

346 .. versionadded:: 0.5.1 

347 """ 

348 return library.MagickGetResource(self._to_idx(resource)) 

349 

350 def get_resource_limit(self, resource): 

351 """Get the current limit for the resource type. 

352 

353 :param resource: Resource type. 

354 :type resource: :class:`basestring` 

355 :rtype: :class:`numeric.Integral` 

356 

357 .. versionadded:: 0.5.1 

358 """ 

359 return library.MagickGetResourceLimit(self._to_idx(resource)) 

360 

361 def set_resource_limit(self, resource, limit): 

362 """Sets a new limit for resource type. 

363 

364 .. note:: 

365 

366 The new limit value must be equal to, or less then, the maximum 

367 limit defined by the :file:`policy.xml`. Any values set outside 

368 normal bounds will be ignored silently. 

369 

370 :param resource: Resource type. 

371 :type resource: :class:`basestring` 

372 :param limit: New limit value. 

373 :type limit: :class:`numeric.Integral` 

374 

375 .. versionadded:: 0.5.1 

376 """ 

377 genesis() 

378 library.MagickSetResourceLimit(self._to_idx(resource), int(limit)) 

379 

380 

381#: (:class:`ResourceLimits`) Helper to get & set Magick Resource Limits. 

382#: 

383#: .. versionadded:: 0.5.1 

384limits = ResourceLimits()