Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/wand/resource.py : 42%

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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4There is the global resource to manage in MagickWand API. This module
5implements automatic global resource management through reference counting.
7"""
8import atexit
9import contextlib
10import ctypes
11import warnings
13from .api import library
14from .compat import abc, string_type, text
15from .exceptions import TYPE_MAP, WandException
16from .version import MAGICK_VERSION_NUMBER
18__all__ = ('genesis', 'limits', 'safe_copy', 'shutdown', 'terminus',
19 'DestroyedResourceError', 'Resource', 'ResourceLimits')
22def genesis():
23 """Instantiates the MagickWand API.
25 .. warning::
27 Don't call this function directly. Use :func:`increment_refcount()` and
28 :func:`decrement_refcount()` functions instead.
30 """
31 library.MagickWandGenesis()
34def terminus():
35 """Cleans up the MagickWand API.
37 .. warning::
39 Don't call this function directly. Use :func:`increment_refcount()` and
40 :func:`decrement_refcount()` functions instead.
42 """
43 if library.IsMagickWandInstantiated is None: # pragma no cover
44 library.MagickWandTerminus()
45 elif library.IsMagickWandInstantiated():
46 library.MagickWandTerminus()
49allocation_map = {}
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
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)
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()
78def safe_copy(ptr):
79 """Safely cast memory address to char pointer, convert to python string,
80 and immediately free resources.
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`)
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
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::
100 with Resource() as resource:
101 # use the resource...
102 pass
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::
109 class Pizza(Resource):
110 '''My pizza yummy.'''
112 def __init__(self):
113 with self.allocate():
114 self.resource = library.NewPizza()
116 .. versionadded:: 0.1.2
118 """
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
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
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
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
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.
162 """
163 if getattr(self, 'c_resource', None) is None:
164 raise DestroyedResourceError(repr(self) + ' is destroyed already')
165 return self.c_resource
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()
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')
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
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::
191 with resource.allocate():
192 resource.resource = library.NewResource()
194 """
195 yield self
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.
202 """
203 del self.resource
205 def get_exception(self):
206 """Gets a current exception instance.
208 :returns: a current exception. it can be ``None`` as well if any
209 errors aren't occurred
210 :rtype: :class:`wand.exceptions.WandException`
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)
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
232 def make_blob(self, format=None):
233 raise NotImplementedError
235 def __enter__(self):
236 return self
238 def __exit__(self, type, value, traceback):
239 self.destroy()
241 def __del__(self):
242 try:
243 self.destroy()
244 except DestroyedResourceError:
245 pass
248class DestroyedResourceError(WandException, ReferenceError, AttributeError):
249 """An error that rises when some code tries access to an already
250 destroyed resource.
252 .. versionchanged:: 0.3.0
253 It becomes a subtype of :exc:`wand.exceptions.WandException`.
255 """
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.
263 For example::
265 from wand.image import Image
266 from wand.resource import limits
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
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']))
279 # Dump list of all limits.
280 for label in limits:
281 print('{0} => {1}'.format(label, limits[label]))
283 Available resource keys:
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.
303 .. versionadded:: 0.5.1
304 """
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')
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')
314 def __init__(self):
315 if MAGICK_VERSION_NUMBER < 0x700:
316 self.limits = self._limits6
317 else:
318 self.limits = self._limits7
320 def __getitem__(self, r):
321 return self.get_resource_limit(r)
323 def __setitem__(self, r, v):
324 self.set_resource_limit(r, v)
326 def __delitem__(self, r):
327 self[r] = 0
329 def __iter__(self):
330 return iter(self.limits)
332 def __len__(self):
333 return len(self.limits)
335 def _to_idx(self, resource):
336 """Helper method to map resource string to enum value."""
337 return self.limits.index(resource)
339 def resource(self, resource):
340 """Get the current value for the resource type.
342 :param resource: Resource type.
343 :type resource: :class:`basestring`
344 :rtype: :class:`numeric.Integral`
346 .. versionadded:: 0.5.1
347 """
348 return library.MagickGetResource(self._to_idx(resource))
350 def get_resource_limit(self, resource):
351 """Get the current limit for the resource type.
353 :param resource: Resource type.
354 :type resource: :class:`basestring`
355 :rtype: :class:`numeric.Integral`
357 .. versionadded:: 0.5.1
358 """
359 return library.MagickGetResourceLimit(self._to_idx(resource))
361 def set_resource_limit(self, resource, limit):
362 """Sets a new limit for resource type.
364 .. note::
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.
370 :param resource: Resource type.
371 :type resource: :class:`basestring`
372 :param limit: New limit value.
373 :type limit: :class:`numeric.Integral`
375 .. versionadded:: 0.5.1
376 """
377 genesis()
378 library.MagickSetResourceLimit(self._to_idx(resource), int(limit))
381#: (:class:`ResourceLimits`) Helper to get & set Magick Resource Limits.
382#:
383#: .. versionadded:: 0.5.1
384limits = ResourceLimits()