summaryrefslogtreecommitdiffstats
path: root/venv/lib/python3.9/site-packages/pympler
diff options
context:
space:
mode:
authornoptuno <repollo.marrero@gmail.com>2023-04-28 02:29:30 +0200
committernoptuno <repollo.marrero@gmail.com>2023-04-28 02:29:30 +0200
commit355dee533bb34a571b9367820a63cccb668cf866 (patch)
tree838af886b4fec07320aeb10f0d1e74ba79e79b5c /venv/lib/python3.9/site-packages/pympler
parentadded pyproject.toml file (diff)
downloadgpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar.gz
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar.bz2
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar.lz
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar.xz
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.tar.zst
gpt4free-355dee533bb34a571b9367820a63cccb668cf866.zip
Diffstat (limited to 'venv/lib/python3.9/site-packages/pympler')
-rw-r--r--venv/lib/python3.9/site-packages/pympler/__init__.py1
-rw-r--r--venv/lib/python3.9/site-packages/pympler/asizeof.py2840
-rw-r--r--venv/lib/python3.9/site-packages/pympler/charts.py62
-rw-r--r--venv/lib/python3.9/site-packages/pympler/classtracker.py590
-rw-r--r--venv/lib/python3.9/site-packages/pympler/classtracker_stats.py780
-rw-r--r--venv/lib/python3.9/site-packages/pympler/garbagegraph.py80
-rw-r--r--venv/lib/python3.9/site-packages/pympler/mprofile.py97
-rw-r--r--venv/lib/python3.9/site-packages/pympler/muppy.py275
-rw-r--r--venv/lib/python3.9/site-packages/pympler/panels.py115
-rw-r--r--venv/lib/python3.9/site-packages/pympler/process.py238
-rw-r--r--venv/lib/python3.9/site-packages/pympler/refbrowser.py451
-rw-r--r--venv/lib/python3.9/site-packages/pympler/refgraph.py350
-rw-r--r--venv/lib/python3.9/site-packages/pympler/static/jquery.sparkline.min.js5
-rw-r--r--venv/lib/python3.9/site-packages/pympler/summary.py321
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/asized_referents.tpl9
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/footer.tpl6
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/garbage.tpl33
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/garbage_index.tpl41
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/header.tpl36
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/index.tpl26
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.min.js8
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.stack.min.js7
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.tooltip.min.js12
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/memory_panel.html58
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/process.tpl89
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/referents.tpl13
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/stacktrace.tpl30
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/style.css992
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/tracker.tpl127
-rw-r--r--venv/lib/python3.9/site-packages/pympler/templates/tracker_class.tpl79
-rw-r--r--venv/lib/python3.9/site-packages/pympler/tracker.py267
-rw-r--r--venv/lib/python3.9/site-packages/pympler/util/__init__.py0
-rw-r--r--venv/lib/python3.9/site-packages/pympler/util/bottle.py3771
-rw-r--r--venv/lib/python3.9/site-packages/pympler/util/compat.py23
-rw-r--r--venv/lib/python3.9/site-packages/pympler/util/stringutils.py77
-rw-r--r--venv/lib/python3.9/site-packages/pympler/web.py346
36 files changed, 12255 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/pympler/__init__.py b/venv/lib/python3.9/site-packages/pympler/__init__.py
new file mode 100644
index 00000000..cd7ca498
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.0.1'
diff --git a/venv/lib/python3.9/site-packages/pympler/asizeof.py b/venv/lib/python3.9/site-packages/pympler/asizeof.py
new file mode 100644
index 00000000..f9e87c96
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/asizeof.py
@@ -0,0 +1,2840 @@
+#!/usr/bin/env python
+
+# Copyright, license and disclaimer are at the very end of this file.
+
+# This is the latest, enhanced version of the asizeof.py recipes at
+# <http://ASPN.ActiveState.com/ASPN/Cookbook/Python/Recipe/546530>
+# <http://ASPN.ActiveState.com/ASPN/Cookbook/Python/Recipe/544288>
+
+# Note, objects like ``namedtuples``, ``closure``, and NumPy data
+# ``arange``, ``array``, ``matrix``, etc. are only handled by recent
+# versions of this module. Sizing of ``__slots__`` has been incorrect
+# in versions before this one. Also, property ``Asizer.duplicate`` gave
+# incorrect values before this release. Several other properties
+# have been added to the ``Asizer`` class and the ``print_summary``
+# method has been updated.
+
+'''
+This module exposes 9 functions and 2 classes to obtain lengths and
+sizes of Python objects (for Python 3.5 or later).
+
+Earlier versions of this module supported Python versions down to
+Python 2.2. If you are using Python 3.5 or older, please consider
+downgrading Pympler.
+
+**Public Functions** [#unsafe]_
+
+ Function **asizeof** calculates the combined (approximate) size
+ in bytes of one or several Python objects.
+
+ Function **asizesof** returns a tuple containing the (approximate)
+ size in bytes for each given Python object separately.
+
+ Function **asized** returns for each object an instance of class
+ **Asized** containing all the size information of the object and
+ a tuple with the referents [#refs]_.
+
+ Functions **basicsize** and **itemsize** return the *basic-*
+ respectively *itemsize* of the given object, both in bytes. For
+ objects as ``array.array``, ``numpy.array``, ``numpy.matrix``,
+ etc. where the item size varies depending on the instance-specific
+ data type, function **itemsize** returns that item size.
+
+ Function **flatsize** returns the *flat size* of a Python object
+ in bytes defined as the *basic size* plus the *item size* times
+ the *length* of the given object.
+
+ Function **leng** returns the *length* of an object, like standard
+ function ``len`` but extended for several types. E.g. the **leng**
+ of a multi-precision int (or long) is the number of ``digits``
+ [#digit]_. The length of most *mutable* sequence objects includes
+ an estimate of the over-allocation and therefore, the **leng** value
+ may differ from the standard ``len`` result. For objects like
+ ``array.array``, ``numpy.array``, ``numpy.matrix``, etc. function
+ **leng** returns the proper number of items.
+
+ Function **refs** returns (a generator for) the referents [#refs]_
+ of the given object.
+
+ Certain classes are known to be sub-classes of or to behave as
+ ``dict`` objects. Function **adict** can be used to register
+ other class objects to be treated like ``dict``.
+
+**Public Classes** [#unsafe]_
+
+ Class **Asizer** may be used to accumulate the results of several
+ **asizeof** or **asizesof** calls. After creating an **Asizer**
+ instance, use methods **asizeof** and **asizesof** as needed to
+ size any number of additional objects.
+
+ Call methods **exclude_refs** and/or **exclude_types** to exclude
+ references to respectively instances or types of certain objects.
+
+ Use one of the **print\\_...** methods to report the statistics.
+
+ An instance of class **Asized** is returned for each object sized
+ by the **asized** function or method.
+
+**Duplicate Objects**
+
+ Any duplicate, given objects are sized only once and the size
+ is included in the accumulated total only once. But functions
+ **asizesof** and **asized** will return a size value respectively
+ an **Asized** instance for each given object, including duplicates.
+
+**Definitions** [#arb]_
+
+ The *length* of an objects like ``dict``, ``list``, ``set``,
+ ``str``, ``tuple``, etc. is defined as the number of items held
+ in or allocated by the object. Held items are *references* to
+ other objects, called the *referents*.
+
+ The *size* of an object is defined as the sum of the *flat size*
+ of the object plus the sizes of any referents [#refs]_. Referents
+ are visited recursively up to the specified detail level. However,
+ the size of objects referenced multiple times is included only once
+ in the total *size*.
+
+ The *flat size* of an object is defined as the *basic size* of the
+ object plus the *item size* times the number of allocated *items*,
+ *references* to referents. The *flat size* does include the size
+ for the *references* to the referents, but not the size of the
+ referents themselves.
+
+ The *flat size* returned by function *flatsize* equals the result
+ of function *asizeof* with options *code=True*, *ignored=False*,
+ *limit=0* and option *align* set to the same value.
+
+ The accurate *flat size* for an object is obtained from function
+ ``sys.getsizeof()`` where available. Otherwise, the *length* and
+ *size* of sequence objects as ``dicts``, ``lists``, ``sets``, etc.
+ is based on an estimate for the number of allocated items. As a
+ result, the reported *length* and *size* may differ substantially
+ from the actual *length* and *size*.
+
+ The *basic* and *item size* are obtained from the ``__basicsize__``
+ respectively ``__itemsize__`` attributes of the (type of the)
+ object. Where necessary (e.g. sequence objects), a zero
+ ``__itemsize__`` is replaced by the size of a corresponding C type.
+
+ The overhead for Python's garbage collector (GC) is included in
+ the *basic size* of (GC managed) objects as well as the space
+ needed for ``refcounts`` (used only in certain Python builds).
+
+ Optionally, size values can be aligned to any power-of-2 multiple.
+
+**Size of (byte)code**
+
+ The *(byte)code size* of objects like classes, functions, methods,
+ modules, etc. can be included by setting option *code=True*.
+
+ Iterators are handled like sequences: iterated object(s) are sized
+ like *referents* [#refs]_, but only up to the specified level or
+ recursion *limit* (and only if function ``gc.get_referents()``
+ returns the referent object of iterators).
+
+ Generators are sized as *(byte)code* only, but the generated
+ objects are never sized.
+
+**Old- and New-style Classes**
+
+ All old- and new-style ``class``, instance and ``type`` objects are
+ handled uniformly such that (a) instance objects are distinguished
+ from class objects and (b) instances of different old-style classes
+ can be dealt with separately.
+
+ Class and type objects are represented as ``<class ....* def>``
+ respectively ``<type ... def>`` where the ``*`` indicates an old-style
+ class and the ``... def`` suffix marks the *definition object*.
+ Instances of classes are shown as ``<class module.name*>`` without
+ the ``... def`` suffix. The ``*`` after the name indicates an
+ instance of an old-style class.
+
+**Ignored Objects**
+
+ To avoid excessive sizes, several object types are ignored [#arb]_
+ by default, e.g. built-in functions, built-in types and classes
+ [#bi]_, function globals and module referents. However, any
+ instances thereof and module objects will be sized when passed as
+ given objects. Ignored object types are included unless option
+ *ignored* is set accordingly.
+
+ In addition, many ``__...__`` attributes of callable objects are
+ ignored [#arb]_, except crucial ones, e.g. class attributes ``__dict__``,
+ ``__doc__``, ``__name__`` and ``__slots__``. For more details, see
+ the type-specific ``_..._refs()`` and ``_len_...()`` functions below.
+
+.. rubric:: Footnotes
+.. [#unsafe] The functions and classes in this module are not thread-safe.
+
+.. [#refs] The *referents* of an object are the objects referenced *by*
+ that object. For example, the *referents* of a ``list`` are the
+ objects held in the ``list``, the *referents* of a ``dict`` are
+ the key and value objects in the ``dict``, etc.
+
+.. [#arb] These definitions and other assumptions are rather arbitrary
+ and may need corrections or adjustments.
+
+.. [#digit] See Python source file ``.../Include/longinterp.h`` for the
+ C ``typedef`` of ``digit`` used in multi-precision int (or long)
+ objects. The C ``sizeof(digit)`` in bytes can be obtained in
+ Python from the int (or long) ``__itemsize__`` attribute.
+ Function **leng** determines the number of ``digits`` of an int
+ (or long) object.
+
+.. [#bi] ``Type``s and ``class``es are considered built-in if the
+ ``__module__`` of the type or class is listed in the private
+ ``_builtin_modules``.
+''' # PYCHOK escape
+import sys
+if sys.version_info < (3, 6, 0):
+ raise NotImplementedError('%s requires Python 3.6 or newer' % ('asizeof',))
+
+from typing import Callable, Dict, List, Optional, Tuple, Union
+
+# all imports listed explicitly to help PyChecker
+from inspect import (isbuiltin, isclass, iscode, isframe, isfunction,
+ ismethod, ismodule, stack)
+from math import log
+from os import curdir, linesep
+from struct import calcsize # type/class Struct only in Python 2.5+
+import types as Types
+import warnings
+import weakref as Weakref
+
+__all__ = ['adict', 'asized', 'asizeof', 'asizesof',
+ 'Asized', 'Asizer', # classes
+ 'basicsize', 'flatsize', 'itemsize', 'leng', 'refs']
+__version__ = '21.08.09'
+
+# Any classes or types in modules listed in _builtin_modules are
+# considered built-in and ignored by default, as built-in functions
+_builtin_mods = [int.__module__, 'types', Exception.__module__] # 'weakref'
+if __name__ != '__main__': # treat this very module as built-in
+ _builtin_mods.append(__name__)
+_builtin_modules = tuple(_builtin_mods)
+
+# Sizes of some primitive C types
+# XXX len(pack(T, 0)) == Struct(T).size == calcsize(T)
+_sizeof_Cbyte = calcsize('c') # sizeof(unsigned char)
+_sizeof_Clong = calcsize('l') # sizeof(long)
+_sizeof_Cvoidp = calcsize('P') # sizeof(void*)
+
+# sizeof(long) != sizeof(ssize_t) on LLP64
+if _sizeof_Clong < _sizeof_Cvoidp: # pragma: no coverage
+ _z_P_L = 'P'
+else:
+ _z_P_L = 'L'
+
+
+def _calcsize(fmt):
+ '''Like struct.calcsize() but handling 'z' for Py_ssize_t.
+ '''
+ return calcsize(fmt.replace('z', _z_P_L))
+
+
+# Defaults for some basic sizes with 'z' for C Py_ssize_t
+_sizeof_CPyCodeObject = _calcsize('Pz10P5i0P') # sizeof(PyCodeObject)
+_sizeof_CPyFrameObject = _calcsize('Pzz13P63i0P') # sizeof(PyFrameObject)
+_sizeof_CPyModuleObject = _calcsize('PzP0P') # sizeof(PyModuleObject)
+
+# Defaults for some item sizes with 'z' for C Py_ssize_t
+_sizeof_CPyDictEntry = _calcsize('z2P') # sizeof(PyDictEntry)
+_sizeof_Csetentry = _calcsize('lP') # sizeof(setentry)
+
+_sizeof_Cdigit = int.__itemsize__
+if _sizeof_Cdigit < 2: # pragma: no coverage
+ raise AssertionError('sizeof(%s) bad: %d' % ('digit', _sizeof_Cdigit))
+
+_builtins2 = (range,)
+
+# Get character size for internal unicode representation in Python < 3.3
+u = '\0'.encode('utf-8')
+_sizeof_Cunicode = len(u)
+del u
+
+try: # Size of GC header, sizeof(PyGC_Head)
+ import _testcapi as t
+ _sizeof_CPyGC_Head = t.SIZEOF_PYGC_HEAD # new in Python 2.6
+except (ImportError, AttributeError): # sizeof(PyGC_Head)
+ # alignment should be to sizeof(long double) but there
+ # is no way to obtain that value, assume twice double
+ t = calcsize('2d') - 1
+ _sizeof_CPyGC_Head = (_calcsize('2Pz') + t) & ~t
+del t
+
+# Size of refcounts (Python debug build only)
+if hasattr(sys, 'gettotalrefcount'): # pragma: no coverage
+ _sizeof_Crefcounts = _calcsize('2z')
+else:
+ _sizeof_Crefcounts = 0
+
+from abc import ABCMeta
+
+# Some flags from .../Include/object.h
+_Py_TPFLAGS_HEAPTYPE = 1 << 9 # Py_TPFLAGS_HEAPTYPE
+_Py_TPFLAGS_HAVE_GC = 1 << 14 # Py_TPFLAGS_HAVE_GC
+
+_Type_type = type(type) # == type and new-style class type
+
+
+# Compatibility functions for more uniform
+# behavior across Python version 2.2 thu 3+
+
+def _items(obj): # dict only
+ '''Return iter-/generator, preferably.
+ '''
+ o = getattr(obj, 'iteritems', obj.items)
+ if _callable(o):
+ return o()
+ else:
+ return o or ()
+
+
+def _keys(obj): # dict only
+ '''Return iter-/generator, preferably.
+ '''
+ o = getattr(obj, 'iterkeys', obj.keys)
+ if _callable(o):
+ return o()
+ else:
+ return o or ()
+
+
+def _values(obj): # dict only
+ '''Return iter-/generator, preferably.
+ '''
+ o = getattr(obj, 'itervalues', obj.values)
+ if _callable(o):
+ return o()
+ else:
+ return o or ()
+
+
+try: # callable() builtin
+ _callable = callable
+except NameError: # callable() removed in Python 3+
+ def _callable(obj):
+ '''Substitute for callable().'''
+ return hasattr(obj, '__call__')
+
+# 'cell' is holding data used in closures
+c = (lambda unused: (lambda: unused))(None)
+_cell_type = type(c.__closure__[0]) # type: ignore
+del c
+
+from gc import get_objects as _getobjects # containers only?
+
+if sys.platform == 'ios': # Apple iOS
+ _gc_getobjects = _getobjects
+
+ def _getobjects(): # PYCHOK expected
+ # avoid Pythonista3/Python 3+ crash
+ return tuple(o for o in _gc_getobjects() if not _isNULL(o))
+
+from gc import get_referents as _getreferents
+
+# sys.getsizeof() new in Python 2.6
+_getsizeof = sys.getsizeof # overridden below
+_getsizeof_excls = () # types not sys.getsizeof'd
+
+from sys import intern as _intern
+
+
+# Private functions
+
+def _basicsize(t, base=0, heap=False, obj=None):
+ '''Get non-zero basicsize of type,
+ including the header sizes.
+ '''
+ s = max(getattr(t, '__basicsize__', 0), base)
+ # include gc header size
+ if t != _Type_type:
+ h = getattr(t, '__flags__', 0) & _Py_TPFLAGS_HAVE_GC
+ elif heap: # type, allocated on heap
+ h = True
+ else: # None has no __flags__ attr
+ h = getattr(obj, '__flags__', 0) & _Py_TPFLAGS_HEAPTYPE
+ if h:
+ s += _sizeof_CPyGC_Head
+ # include reference counters
+ return s + _sizeof_Crefcounts
+
+
+def _classof(obj, dflt=None):
+ '''Return the object's class object.
+ '''
+ return getattr(obj, '__class__', dflt)
+
+
+def _derive_typedef(typ):
+ '''Return single, existing super type typedef or None.
+ '''
+ v = [v for v in _values(_typedefs) if _issubclass(typ, v.type)]
+ if len(v) == 1:
+ return v[0]
+ return None
+
+
+def _dir2(obj, pref='', excl=(), slots=None, itor=''):
+ '''Return an attribute name, object 2-tuple for certain
+ attributes or for the ``__slots__`` attributes of the
+ given object, but not both. Any iterator referent
+ objects are returned with the given name if the
+ latter is non-empty.
+ '''
+ if slots: # __slots__ attrs
+ if hasattr(obj, slots):
+ # collect all inherited __slots__ attrs
+ # from list, tuple, or dict __slots__,
+ # while removing any duplicate attrs
+ s = {}
+ for c in type(obj).mro():
+ for a in getattr(c, slots, ()):
+ if a.startswith('__'):
+ a = '_' + c.__name__ + a
+ if hasattr(obj, a):
+ s.setdefault(a, getattr(obj, a))
+ # assume __slots__ tuple-like is holding the values
+ # yield slots, _Slots(s) # _keys(s) ... REMOVED,
+ # see _Slots.__doc__ further below
+ for t in _items(s):
+ yield t # attr name, value
+ elif itor: # iterator referents
+ for o in obj: # iter(obj)
+ yield itor, o
+ else: # regular attrs
+ for a in dir(obj):
+ if a.startswith(pref) and hasattr(obj, a) and a not in excl:
+ yield a, getattr(obj, a)
+
+
+def _getsizeof_excls_add(typ):
+ '''Add another type to the tuple of types to be
+ excluded from sys.getsizeof due to errors.
+ '''
+ global _getsizeof_excls
+ if typ and typ not in _getsizeof_excls:
+ _getsizeof_excls += (typ,)
+
+
+def _infer_dict(obj):
+ '''Return True for likely dict object via duck typing.
+ '''
+ for attrs in (('items', 'keys', 'values'), # 'update',
+ ('iteritems', 'iterkeys', 'itervalues')):
+ attrs += '__len__', 'get', 'has_key'
+ if all(_callable(getattr(obj, a, None)) for a in attrs):
+ return True
+ return False
+
+
+def _isbuiltin2(obj):
+ '''Return True for builtins like Python 2.
+ '''
+ # range is no longer a built-in in Python 3+
+ return isbuiltin(obj) or obj in _builtins2
+
+
+def _iscell(obj):
+ '''Return True if obj is a cell as used in a closure.
+ '''
+ return isinstance(obj, _cell_type)
+
+
+def _isdictclass(obj):
+ '''Return True for known dict objects.
+ '''
+ c = _classof(obj)
+ return c and c.__name__ in _dict_classes.get(c.__module__, ())
+
+
+def _isframe(obj):
+ '''Return True for a stack frame object.
+ '''
+ try: # safe isframe(), see pympler.muppy
+ return isframe(obj)
+ except ReferenceError:
+ return False
+
+
+def _isnamedtuple(obj):
+ '''Named tuples are identified via duck typing:
+ <http://www.gossamer-threads.com/lists/python/dev/1142178>
+ '''
+ return isinstance(obj, tuple) and hasattr(obj, '_fields')
+
+
+def _isNULL(obj):
+ '''Prevent asizeof(all=True, ...) crash.
+
+ Sizing gc.get_objects() crashes in Pythonista3 with
+ Python 3.5.1 on iOS due to 1-tuple (<Null>,) object,
+ see <http://forum.omz-software.com/user/mrjean1>.
+ '''
+ return isinstance(obj, tuple) and len(obj) == 1 \
+ and repr(obj) == '(<NULL>,)'
+
+
+def _issubclass(sub, sup):
+ '''Safe issubclass().
+ '''
+ if sup is not object:
+ try:
+ return issubclass(sub, sup)
+ except TypeError:
+ pass
+ return False
+
+
+def _itemsize(t, item=0):
+ '''Get non-zero itemsize of type.
+ '''
+ # replace zero value with default
+ return getattr(t, '__itemsize__', 0) or item
+
+
+def _kwdstr(**kwds):
+ '''Keyword arguments as a string.
+ '''
+ return ', '.join(sorted('%s=%r' % kv for kv in _items(kwds)))
+
+
+def _lengstr(obj):
+ '''Object length as a string.
+ '''
+ n = leng(obj)
+ if n is None: # no len
+ r = ''
+ elif n > _len(obj): # extended
+ r = ' leng %d!' % n
+ else:
+ r = ' leng %d' % n
+ return r
+
+
+def _moduleof(obj, dflt=''):
+ '''Return the object's module name.
+ '''
+ return getattr(obj, '__module__', dflt)
+
+
+def _nameof(obj, dflt=''):
+ '''Return the name of an object.
+ '''
+ return getattr(obj, '__name__', dflt)
+
+
+def _objs_opts_x(objs, all=None, **opts):
+ '''Return given or 'all' objects
+ and the remaining options.
+ '''
+ if objs: # given objects
+ t = objs
+ x = False
+ elif all in (False, None):
+ t = ()
+ x = True
+ elif all is True: # 'all' objects
+ t = _getobjects()
+ x = True
+ else:
+ raise ValueError('invalid option: %s=%r' % ('all', all))
+ return t, opts, x
+
+
+def _p100(part, total, prec=1):
+ '''Return percentage as string.
+ '''
+ r = float(total)
+ if r:
+ r = part * 100.0 / r
+ return '%.*f%%' % (prec, r)
+ return 'n/a'
+
+
+def _plural(num):
+ '''Return 's' if plural.
+ '''
+ if num == 1:
+ s = ''
+ else:
+ s = 's'
+ return s
+
+
+def _power2(n):
+ '''Find the next power of 2.
+ '''
+ p2 = 16
+ while n > p2:
+ p2 += p2
+ return p2
+
+
+def _prepr(obj, clip=0):
+ '''Prettify and clip long repr() string.
+ '''
+ return _repr(obj, clip=clip).strip('<>').replace("'", '') # remove <''>
+
+
+def _printf(fmt, *args, **print3options):
+ '''Formatted print to sys.stdout or given stream.
+
+ *print3options* -- some keyword arguments, like Python 3+ print.
+ '''
+ if print3options: # like Python 3+
+ f = print3options.get('file', None) or sys.stdout
+ if args:
+ f.write(fmt % args)
+ else:
+ f.write(fmt)
+ f.write(print3options.get('end', linesep))
+ if print3options.get('flush', False):
+ f.flush()
+ elif args:
+ print(fmt % args)
+ else:
+ print(fmt)
+
+
+def _refs(obj, named, *attrs, **kwds):
+ '''Return specific attribute objects of an object.
+ '''
+ if named:
+ _N = _NamedRef
+ else:
+ def _N(_, o):
+ return o
+
+ for a in attrs: # cf. inspect.getmembers()
+ if hasattr(obj, a):
+ yield _N(a, getattr(obj, a))
+ if kwds: # kwds are _dir2() args
+ for a, o in _dir2(obj, **kwds):
+ yield _N(a, o)
+
+
+def _repr(obj, clip=80):
+ '''Clip long repr() string.
+ '''
+ try: # safe repr()
+ r = repr(obj).replace(linesep, '\\n')
+ except Exception:
+ r = 'N/A'
+ if len(r) > clip > 0:
+ h = (clip // 2) - 2
+ if h > 0:
+ r = r[:h] + '....' + r[-h:]
+ return r
+
+
+def _SI(size, K=1024, i='i'):
+ '''Return size as SI string.
+ '''
+ if 1 < K <= size:
+ f = float(size)
+ for si in iter('KMGPTE'):
+ f /= K
+ if f < K:
+ return ' or %.1f %s%sB' % (f, si, i)
+ return ''
+
+
+def _SI2(size, **kwds):
+ '''Return size as regular plus SI string.
+ '''
+ return str(size) + _SI(size, **kwds)
+
+
+# Type-specific referents functions
+
+def _cell_refs(obj, named):
+ try: # handle 'empty' cells
+ o = obj.cell_contents
+ if named:
+ o = _NamedRef('cell_contents', o)
+ yield o
+ except (AttributeError, ValueError):
+ pass
+
+
+def _class_refs(obj, named):
+ '''Return specific referents of a class object.
+ '''
+ return _refs(obj, named, '__class__', '__doc__', '__mro__',
+ '__name__', '__slots__', '__weakref__',
+ '__dict__') # __dict__ last
+
+
+def _co_refs(obj, named):
+ '''Return specific referents of a code object.
+ '''
+ return _refs(obj, named, pref='co_')
+
+
+def _dict_refs(obj, named):
+ '''Return key and value objects of a dict/proxy.
+ '''
+ try:
+ if named:
+ for k, v in _items(obj):
+ s = str(k)
+ yield _NamedRef('[K] ' + s, k)
+ yield _NamedRef('[V] ' + s + ': ' + _repr(v), v)
+ else:
+ for k, v in _items(obj):
+ yield k
+ yield v
+ except (KeyError, ReferenceError, TypeError) as x:
+ warnings.warn("Iterating '%s': %r" % (_classof(obj), x))
+
+
+def _enum_refs(obj, named):
+ '''Return specific referents of an enumerate object.
+ '''
+ return _refs(obj, named, '__doc__')
+
+
+def _exc_refs(obj, named):
+ '''Return specific referents of an Exception object.
+ '''
+ # .message raises DeprecationWarning in Python 2.6
+ return _refs(obj, named, 'args', 'filename', 'lineno', 'msg', 'text') # , 'message', 'mixed'
+
+
+def _file_refs(obj, named):
+ '''Return specific referents of a file object.
+ '''
+ return _refs(obj, named, 'mode', 'name')
+
+
+def _frame_refs(obj, named):
+ '''Return specific referents of a frame object.
+ '''
+ return _refs(obj, named, pref='f_')
+
+
+def _func_refs(obj, named):
+ '''Return specific referents of a function or lambda object.
+ '''
+ return _refs(obj, named, '__doc__', '__name__', '__code__', '__closure__',
+ pref='func_', excl=('func_globals',))
+
+
+def _gen_refs(obj, named):
+ '''Return the referent(s) of a generator (expression) object.
+ '''
+ # only some gi_frame attrs
+ f = getattr(obj, 'gi_frame', None)
+ return _refs(f, named, 'f_locals', 'f_code')
+# do not yield any items to keep generator intact
+# for r in _refs(f, named, 'f_locals', 'f_code'):
+# yield r
+# for r in obj:
+# yield r
+
+
+def _im_refs(obj, named):
+ '''Return specific referents of a method object.
+ '''
+ return _refs(obj, named, '__doc__', '__name__', '__code__', pref='im_')
+
+
+def _inst_refs(obj, named):
+ '''Return specific referents of a class instance.
+ '''
+ return _refs(obj, named, '__dict__', '__class__', slots='__slots__')
+
+
+def _iter_refs(obj, named):
+ '''Return the referent(s) of an iterator object.
+ '''
+ r = _getreferents(obj) # special case
+ return _refs(r, named, itor=_nameof(obj) or 'iteref')
+
+
+def _module_refs(obj, named):
+ '''Return specific referents of a module object.
+ '''
+ # ignore this very module
+ if obj.__name__ == __name__:
+ return ()
+ # module is essentially a dict
+ return _dict_refs(obj.__dict__, named)
+
+
+def _namedtuple_refs(obj, named):
+ '''Return specific referents of obj-as-sequence and slots but exclude dict.
+ '''
+ for r in _refs(obj, named, '__class__', slots='__slots__'):
+ yield r
+ for r in obj:
+ yield r
+
+
+def _prop_refs(obj, named):
+ '''Return specific referents of a property object.
+ '''
+ return _refs(obj, named, '__doc__', pref='f')
+
+
+def _seq_refs(obj, unused): # named unused for PyChecker
+ '''Return specific referents of a frozen/set, list, tuple and xrange object.
+ '''
+ return obj # XXX for r in obj: yield r
+
+
+def _stat_refs(obj, named):
+ '''Return referents of a os.stat object.
+ '''
+ return _refs(obj, named, pref='st_')
+
+
+def _statvfs_refs(obj, named):
+ '''Return referents of a os.statvfs object.
+ '''
+ return _refs(obj, named, pref='f_')
+
+
+def _tb_refs(obj, named):
+ '''Return specific referents of a traceback object.
+ '''
+ return _refs(obj, named, pref='tb_')
+
+
+def _type_refs(obj, named):
+ '''Return specific referents of a type object.
+ '''
+ return _refs(obj, named, '__doc__', '__mro__', '__name__',
+ '__slots__', '__weakref__', '__dict__')
+
+
+def _weak_refs(obj, unused): # named unused for PyChecker
+ '''Return weakly referent object.
+ '''
+ try: # ignore 'key' of KeyedRef
+ return (obj(),)
+ except Exception: # XXX ReferenceError
+ return ()
+
+
+_all_refs = (None, _cell_refs, _class_refs, _co_refs, _dict_refs, _enum_refs,
+ _exc_refs, _file_refs, _frame_refs, _func_refs, _gen_refs,
+ _im_refs, _inst_refs, _iter_refs, _module_refs, _namedtuple_refs,
+ _prop_refs, _seq_refs, _stat_refs, _statvfs_refs, _tb_refs,
+ _type_refs, _weak_refs)
+
+
+# Type-specific length functions
+
+def _len(obj):
+ '''Safe len().
+ '''
+ try:
+ return len(obj)
+ except TypeError: # no len()
+ return 0
+
+
+def _len_bytearray(obj):
+ '''Bytearray size.
+ '''
+ return obj.__alloc__()
+
+
+def _len_code(obj): # see .../Lib/test/test_sys.py
+ '''Length of code object (stack and variables only).
+ '''
+ return (obj.co_stacksize + obj.co_nlocals +
+ _len(obj.co_freevars) + _len(obj.co_cellvars) - 1)
+
+
+def _len_dict(obj):
+ '''Dict length in items (estimate).
+ '''
+ n = len(obj) # active items
+ if n < 6: # ma_smalltable ...
+ n = 0 # ... in basicsize
+ else: # at least one unused
+ n = _power2(n + 1)
+ return n
+
+
+def _len_frame(obj):
+ '''Length of a frame object.
+ '''
+ c = getattr(obj, 'f_code', None)
+ if c:
+ n = _len_code(c)
+ else:
+ n = 0
+ return n
+
+
+_digit2p2 = 1 << (_sizeof_Cdigit << 3)
+_digitmax = _digit2p2 - 1 # == (2 * PyLong_MASK + 1)
+_digitlog = 1.0 / log(_digit2p2)
+
+
+def _len_int(obj):
+ '''Length of multi-precision int (aka long) in digits.
+ '''
+ if obj:
+ n, i = 1, abs(obj)
+ if i > _digitmax:
+ # no log(x[, base]) in Python 2.2
+ n += int(log(i) * _digitlog)
+ else: # zero
+ n = 0
+ return n
+
+
+def _len_iter(obj):
+ '''Length (hint) of an iterator.
+ '''
+ n = getattr(obj, '__length_hint__', None)
+ if n:
+ n = n()
+ else: # try len()
+ n = _len(obj)
+ return n
+
+
+def _len_list(obj):
+ '''Length of list (estimate).
+ '''
+ n = len(obj)
+ # estimate over-allocation
+ if n > 8:
+ n += 6 + (n >> 3)
+ elif n:
+ n += 4
+ return n
+
+
+def _len_module(obj):
+ '''Module length.
+ '''
+ return _len(obj.__dict__) # _len(dir(obj))
+
+
+def _len_set(obj):
+ '''Length of frozen/set (estimate).
+ '''
+ n = len(obj)
+ if n > 8: # assume half filled
+ n = _power2(n + n - 2)
+ elif n: # at least 8
+ n = 8
+ return n
+
+
+def _len_slice(obj):
+ '''Slice length.
+ '''
+ try:
+ return ((obj.stop - obj.start + 1) // obj.step)
+ except (AttributeError, TypeError):
+ return 0
+
+
+# REMOVED, see _Slots.__doc__
+# def _len_slots(obj):
+# '''Slots length.
+# '''
+# return len(obj) - 1
+
+
+def _len_struct(obj):
+ '''Struct length in bytes.
+ '''
+ try:
+ return obj.size
+ except AttributeError:
+ return 0
+
+
+def _len_unicode(obj):
+ '''Unicode size.
+ '''
+ return len(obj) + 1
+
+
+_all_lens = (None, _len, _len_bytearray, _len_code, _len_dict,
+ _len_frame, _len_int, _len_iter, _len_list,
+ _len_module, _len_set, _len_slice, _len_struct,
+ _len_unicode) # type: Tuple[Union[None, Callable], ...] # _len_array, _len_numpy, _len_slots
+
+
+# More private functions and classes
+
+_old_style = '*' # marker
+_new_style = '' # no marker
+
+
+class _Claskey(object):
+ '''Wrapper for class objects.
+ '''
+ __slots__ = ('_obj', '_sty')
+
+ def __init__(self, obj, style):
+ self._obj = obj # XXX Weakref.ref(obj)
+ self._sty = style
+
+ def __str__(self):
+ r = str(self._obj)
+ if r.endswith('>'):
+ r = '%s%s def>' % (r[:-1], self._sty)
+ elif self._sty is _old_style and not r.startswith('class '):
+ r = 'class %s%s def' % (r, self._sty)
+ else:
+ r = '%s%s def' % (r, self._sty)
+ return r
+ __repr__ = __str__
+
+
+# For most objects, the object type is used as the key in the
+# _typedefs dict further below, except class and type objects
+# and old-style instances. Those are wrapped with separate
+# _Claskey or _Instkey instances to be able (1) to distinguish
+# instances of different old-style classes by class, (2) to
+# distinguish class (and type) instances from class (and type)
+# definitions for new-style classes and (3) provide similar
+# results for repr() and str() of new- and old-style classes
+# and instances.
+
+_claskeys = {} # type: Dict[int, _Claskey]
+
+
+def _claskey(obj, style):
+ '''Wrap an old- or new-style class object.
+ '''
+ i = id(obj)
+ k = _claskeys.get(i, None)
+ if not k:
+ _claskeys[i] = k = _Claskey(obj, style)
+ return k
+
+
+def _keytuple(obj): # PYCHOK expected
+ '''Return class and instance keys for a class.
+ '''
+ if type(obj) is _Type_type: # isclass(obj):
+ return _claskey(obj, _new_style), obj
+ return None, None # not a class
+
+
+def _objkey(obj): # PYCHOK expected
+ '''Return the key for any object.
+ '''
+ k = type(obj)
+ if k is _Type_type: # isclass(obj):
+ k = _claskey(obj, _new_style)
+ return k
+
+
+class _NamedRef(object):
+ '''Store referred object along
+ with the name of the referent.
+ '''
+ __slots__ = ('name', 'ref')
+
+ def __init__(self, name, ref):
+ self.name = name
+ self.ref = ref
+
+
+# class _Slots(tuple):
+# '''Wrapper class for __slots__ attribute at class definition.
+# The instance-specific __slots__ attributes are stored in
+# a "tuple-like" space inside the instance, see Luciano
+# Ramalho, "Fluent Python", page 274+, O'Reilly, 2016 or
+# at <http://Books.Google.com/books>, then search for
+# "Fluent Python" "Space Savings with the __slots__".
+# '''
+# pass
+
+
+# Kinds of _Typedefs
+_i = _intern
+_all_kinds = (_kind_static, _kind_dynamic, _kind_derived, _kind_ignored, _kind_inferred) = (
+ _i('static'), _i('dynamic'), _i('derived'), _i('ignored'), _i('inferred'))
+del _i
+
+_Not_vari = '' # non-variable item size
+
+
+class _Typedef(object):
+ '''Type definition class.
+ '''
+ __slots__ = {
+ 'base': 0, # basic size in bytes
+ 'item': 0, # item size in bytes
+ 'leng': None, # or _len_...() function
+ 'refs': None, # or _..._refs() function
+ 'both': None, # both data and code if True, code only if False
+ 'kind': None, # _kind_... value
+ 'type': None, # original type
+ 'vari': None} # item size attr name or _Not_vari
+
+ def __init__(self, **kwds):
+ self.reset(**kwds)
+
+ def __lt__(self, unused): # for Python 3+
+ return True
+
+ def __repr__(self):
+ return repr(self.args())
+
+ def __str__(self):
+ t = [str(self.base), str(self.item)]
+ for f in (self.leng, self.refs):
+ if f:
+ t.append(f.__name__)
+ else:
+ t.append('n/a')
+ if not self.both:
+ t.append('(code only)')
+ return ', '.join(t)
+
+ def args(self): # as args tuple
+ '''Return all attributes as arguments tuple.
+ '''
+ return (self.base, self.item, self.leng, self.refs,
+ self.both, self.kind, self.type)
+
+ def dup(self, other=None, **kwds):
+ '''Duplicate attributes of dict or other typedef.
+ '''
+ if other is None:
+ d = _dict_typedef.kwds()
+ else:
+ d = other.kwds()
+ d.update(kwds)
+ self.reset(**d)
+
+ def flat(self, obj, mask=0):
+ '''Return the aligned flat size.
+ '''
+ s = self.base
+ if self.leng and self.item > 0: # include items
+ s += self.leng(obj) * self.item
+ # workaround sys.getsizeof (and numpy?) bug ... some
+ # types are incorrectly sized in some Python versions
+ # (note, isinstance(obj, ()) == False)
+ if not isinstance(obj, _getsizeof_excls):
+ s = _getsizeof(obj, s)
+ if mask: # align
+ s = (s + mask) & ~mask
+ return s
+
+ def format(self):
+ '''Return format dict.
+ '''
+ i = self.item
+ if self.vari:
+ i = 'var'
+ c = n = ''
+ if not self.both:
+ c = ' (code only)'
+ if self.leng:
+ n = ' (%s)' % _nameof(self.leng)
+ return dict(base=self.base, item=i, leng=n, code=c,
+ kind=self.kind)
+
+ def kwds(self):
+ '''Return all attributes as keywords dict.
+ '''
+ return dict(base=self.base, both=self.both,
+ item=self.item, kind=self.kind,
+ leng=self.leng, refs=self.refs,
+ type=self.type, vari=self.vari)
+
+ def save(self, t, base=0, heap=False):
+ '''Save this typedef plus its class typedef.
+ '''
+ c, k = _keytuple(t)
+ if k and k not in _typedefs: # instance key
+ _typedefs[k] = self
+ if c and c not in _typedefs: # class key
+ if t.__module__ in _builtin_modules:
+ k = _kind_ignored # default
+ else:
+ k = self.kind
+ _typedefs[c] = _Typedef(base=_basicsize(type(t), base=base, heap=heap),
+ refs=_type_refs,
+ both=False, kind=k, type=t)
+ elif t not in _typedefs:
+ if not _isbuiltin2(t): # array, range, xrange in Python 2.x
+ s = ' '.join((self.vari, _moduleof(t), _nameof(t)))
+ s = '%r %s %s' % ((c, k), self.both, s.strip())
+ raise KeyError('asizeof typedef %r bad: %s' % (self, s))
+
+ _typedefs[t] = _Typedef(base=_basicsize(t, base=base),
+ both=False, kind=_kind_ignored, type=t)
+
+ def set(self, safe_len=False, **kwds):
+ '''Set one or more attributes.
+ '''
+ if kwds: # double check
+ d = self.kwds()
+ d.update(kwds)
+ self.reset(**d)
+ if safe_len and self.item:
+ self.leng = _len
+
+ def reset(self, base=0, item=0, leng=None, refs=None,
+ both=True, kind=None, type=None, vari=_Not_vari):
+ '''Reset all specified attributes.
+ '''
+ if base < 0:
+ raise ValueError('invalid option: %s=%r' % ('base', base))
+ else:
+ self.base = base
+ if item < 0:
+ raise ValueError('invalid option: %s=%r' % ('item', item))
+ else:
+ self.item = item
+ if leng in _all_lens: # XXX or _callable(leng)
+ self.leng = leng
+ else:
+ raise ValueError('invalid option: %s=%r' % ('leng', leng))
+ if refs in _all_refs: # XXX or _callable(refs)
+ self.refs = refs
+ else:
+ raise ValueError('invalid option: %s=%r' % ('refs', refs))
+ if both in (False, True):
+ self.both = both
+ else:
+ raise ValueError('invalid option: %s=%r' % ('both', both))
+ if kind in _all_kinds:
+ self.kind = kind
+ else:
+ raise ValueError('invalid option: %s=%r' % ('kind', kind))
+ self.type = type
+ self.vari = vari or _Not_vari
+ if str(self.vari) != self.vari:
+ raise ValueError('invalid option: %s=%r' % ('vari', vari))
+
+
+_typedefs = {} # type: Dict[type, _Typedef]
+
+
+def _typedef_both(t, base=0, item=0, leng=None, refs=None,
+ kind=_kind_static, heap=False, vari=_Not_vari):
+ '''Add new typedef for both data and code.
+ '''
+ v = _Typedef(base=_basicsize(t, base=base), item=_itemsize(t, item),
+ refs=refs, leng=leng,
+ both=True, kind=kind, type=t, vari=vari)
+ v.save(t, base=base, heap=heap)
+ return v # for _dict_typedef
+
+
+def _typedef_code(t, base=0, refs=None, kind=_kind_static, heap=False):
+ '''Add new typedef for code only.
+ '''
+ v = _Typedef(base=_basicsize(t, base=base),
+ refs=refs,
+ both=False, kind=kind, type=t)
+ v.save(t, base=base, heap=heap)
+ return v # for _dict_typedef
+
+
+# Static typedefs for data and code types
+_typedef_both(complex)
+_typedef_both(float)
+_typedef_both(list, refs=_seq_refs, leng=_len_list, item=_sizeof_Cvoidp) # sizeof(PyObject*)
+_typedef_both(tuple, refs=_seq_refs, leng=_len, item=_sizeof_Cvoidp) # sizeof(PyObject*)
+_typedef_both(property, refs=_prop_refs)
+_typedef_both(type(Ellipsis))
+_typedef_both(type(None))
+
+# _Slots are "tuple-like", REMOVED see _Slots.__doc__
+# _typedef_both(_Slots, item=_sizeof_Cvoidp,
+# leng=_len_slots, # length less one
+# refs=None, # but no referents
+# heap=True) # plus head
+
+# dict, dictproxy, dict_proxy and other dict-like types
+_dict_typedef = _typedef_both(dict, item=_sizeof_CPyDictEntry, leng=_len_dict, refs=_dict_refs)
+# XXX any class __dict__ is <type dict_proxy> in Python 3+?
+_typedef_both(type(_Typedef.__dict__), item=_sizeof_CPyDictEntry, leng=_len_dict, refs=_dict_refs)
+# other dict-like classes and types may be derived or inferred,
+# provided the module and class name is listed here (see functions
+# adict, _isdictclass and _infer_dict for further details)
+_dict_classes = {'UserDict': ('IterableUserDict', 'UserDict'),
+ 'weakref': ('WeakKeyDictionary', 'WeakValueDictionary')}
+try: # <type module> is essentially a dict
+ _typedef_both(Types.ModuleType, base=_dict_typedef.base,
+ item=_dict_typedef.item + _sizeof_CPyModuleObject,
+ leng=_len_module, refs=_module_refs)
+except AttributeError: # missing
+ pass
+
+# Newer or obsolete types
+from array import array # array type
+
+def _array_kwds(obj):
+ if hasattr(obj, 'itemsize'):
+ v = 'itemsize'
+ else:
+ v = _Not_vari
+ # since item size varies by the array data type, set
+ # itemsize to 1 byte and use _len_array in bytes; note,
+ # function itemsize returns the actual size in bytes
+ # and function leng returns the length in number of items
+ return dict(leng=_len_array, item=_sizeof_Cbyte, vari=v)
+
+def _len_array(obj):
+ '''Array length (in bytes!).
+ '''
+ return len(obj) * obj.itemsize
+
+_all_lens += (_len_array,) # type: ignore
+
+_typedef_both(array, **_array_kwds(array('d', [])))
+
+v = sys.version_info
+_array_excl = (v[0] == 2 and v < (2, 7, 4)) or \
+ (v[0] == 3 and v < (3, 2, 4))
+if _array_excl: # see function _typedef below
+ _getsizeof_excls_add(array)
+
+del v
+
+try: # bool has non-zero __itemsize__ in 3.0
+ _typedef_both(bool)
+except NameError: # missing
+ pass
+
+try:
+ _typedef_both(bytearray, item=_sizeof_Cbyte, leng=_len_bytearray)
+except NameError: # bytearray new in 2.6, 3.0
+ pass
+try:
+ if type(bytes) is not type(str): # bytes is str in 2.6, bytes new in 2.6, 3.0
+ _typedef_both(bytes, item=_sizeof_Cbyte, leng=_len) # bytes new in 2.6, 3.0
+except NameError: # missing
+ pass
+# try: # XXX like bytes
+# _typedef_both(str8, item=_sizeof_Cbyte, leng=_len) # str8 new in 2.6, 3.0
+# except NameError: # missing
+# pass
+
+try:
+ _typedef_both(enumerate, refs=_enum_refs)
+except NameError: # missing
+ pass
+
+try: # Exception is type in Python 3+
+ _typedef_both(Exception, refs=_exc_refs)
+except Exception: # missing
+ pass
+
+try:
+ _typedef_both(frozenset, item=_sizeof_Csetentry, leng=_len_set, refs=_seq_refs)
+except NameError: # missing
+ pass
+try:
+ _typedef_both(set, item=_sizeof_Csetentry, leng=_len_set, refs=_seq_refs)
+except NameError: # missing
+ pass
+
+try: # not callable()
+ _typedef_both(Types.GetSetDescriptorType)
+except AttributeError: # missing
+ pass
+
+_typedef_both(int, item=_sizeof_Cdigit, leng=_len_int)
+
+try: # not callable()
+ _typedef_both(Types.MemberDescriptorType)
+except AttributeError: # missing
+ pass
+
+try:
+ _typedef_both(type(NotImplemented)) # == Types.NotImplementedType
+except NameError: # missing
+ pass
+
+try: # MCCABE 14
+ import numpy # NumPy array, matrix, etc.
+
+ def _isnumpy(obj):
+ '''Return True for a NumPy arange, array, matrix, etc. instance.
+ '''
+ try:
+ return isinstance(obj, _numpy_types) or (hasattr(obj, 'nbytes') and
+ _moduleof(_classof(obj)).startswith('numpy'))
+ except (AttributeError, OSError, ValueError): # on iOS/Pythonista
+ return False
+
+ def _len_numpy(obj):
+ '''NumPy array, matrix, etc. length (in bytes!).
+ '''
+ return obj.nbytes # == obj.size * obj.itemsize
+
+ def _numpy_kwds(obj):
+ b = _getsizeof(obj, 96) - obj.nbytes # XXX 96..144 typical?
+ # since item size depends on the numpy data type, set
+ # itemsize to 1 byte and use _len_numpy in bytes; note,
+ # function itemsize returns the actual size in bytes,
+ # function alen returns the length in number of items
+ return dict(base=b, item=_sizeof_Cbyte, # not obj.itemsize
+ leng=_len_numpy,
+ refs=_numpy_refs,
+ vari='itemsize')
+
+ def _numpy_refs(obj, named):
+ '''Return the .base object for NumPy slices, views, etc.
+ '''
+ return _refs(obj, named, 'base')
+
+ _all_lens += (_len_numpy,)
+ _all_refs += (_numpy_refs,)
+
+ v = tuple(map(int, numpy.__version__.split('.')[:2]))
+
+ if v < (1, 19):
+ t = (numpy.matrix(range(0)),)
+ else: # numpy.matrix deprecated in 1.19.3
+ t = ()
+ _numpy_types = () # type: Tuple[type, ...]
+ for d in (t + (numpy.array(range(0)), numpy.arange(0),
+ numpy.ma.masked_array([]), numpy.ndarray(0))):
+ t = type(d)
+ if t not in _numpy_types:
+ _numpy_types += (t,)
+ if _isnumpy(d): # double check
+ _typedef_both(t, **_numpy_kwds(d))
+ else:
+ raise AssertionError('not %s: %r' % ('numpy', d))
+
+ # sizing numpy 1.13 arrays works fine, but 1.8 and older
+ # appears to suffer from sys.getsizeof() bug like array
+ _numpy_excl = v < (1, 9)
+ if _numpy_excl: # see function _typedef below
+ for t in _numpy_types:
+ _getsizeof_excls_add(t)
+
+ del d, t, v
+except ImportError: # no NumPy
+ _numpy_excl = numpy = None # type: ignore # see function _typedef below
+
+ def _isnumpy(obj): # PYCHOK expected
+ '''Not applicable, no NumPy.
+ '''
+ return False
+
+try:
+ _typedef_both(range)
+except NameError: # missing
+ pass
+
+try:
+ _typedef_both(reversed, refs=_enum_refs)
+except NameError: # missing
+ pass
+
+try:
+ _typedef_both(slice, item=_sizeof_Cvoidp, leng=_len_slice) # XXX worst-case itemsize?
+except NameError: # missing
+ pass
+
+try:
+ from os import stat
+ _typedef_both(type(stat(curdir)), refs=_stat_refs) # stat_result
+except ImportError: # missing
+ pass
+
+try:
+ from os import statvfs
+ _typedef_both(type(statvfs(curdir)), refs=_statvfs_refs, # statvfs_result
+ item=_sizeof_Cvoidp, leng=_len)
+except ImportError: # missing
+ pass
+
+try:
+ from struct import Struct # only in Python 2.5 and 3.0
+ _typedef_both(Struct, item=_sizeof_Cbyte, leng=_len_struct) # len in bytes
+except ImportError: # missing
+ pass
+
+try:
+ _typedef_both(Types.TracebackType, refs=_tb_refs)
+except AttributeError: # missing
+ pass
+
+_typedef_both(str, leng=_len_unicode, item=_sizeof_Cunicode)
+
+try: # <type 'KeyedRef'>
+ _typedef_both(Weakref.KeyedRef, refs=_weak_refs, heap=True) # plus head
+except AttributeError: # missing
+ pass
+
+try: # <type 'weakproxy'>
+ _typedef_both(Weakref.ProxyType)
+except AttributeError: # missing
+ pass
+
+try: # <type 'weakref'>
+ _typedef_both(Weakref.ReferenceType, refs=_weak_refs)
+except AttributeError: # missing
+ pass
+
+# some other, callable types
+_typedef_code(object, kind=_kind_ignored)
+_typedef_code(super, kind=_kind_ignored)
+_typedef_code(_Type_type, kind=_kind_ignored)
+
+try:
+ _typedef_code(classmethod, refs=_im_refs)
+except NameError:
+ pass
+try:
+ _typedef_code(staticmethod, refs=_im_refs)
+except NameError:
+ pass
+try:
+ _typedef_code(Types.MethodType, refs=_im_refs)
+except NameError:
+ pass
+
+try: # generator (expression), no itemsize, no len(), not callable()
+ _typedef_both(Types.GeneratorType, refs=_gen_refs)
+except AttributeError: # missing
+ pass
+
+try: # <type 'weakcallableproxy'>
+ _typedef_code(Weakref.CallableProxyType, refs=_weak_refs)
+except AttributeError: # missing
+ pass
+
+# any type-specific iterators
+s = [_items({}), _keys({}), _values({})]
+try: # reversed list and tuples iterators
+ s.extend([reversed([]), reversed(())])
+except NameError: # missing
+ pass
+
+try: # callable-iterator
+ from re import finditer
+ s.append(finditer('', ''))
+except ImportError: # missing
+ pass
+
+for t in _values(_typedefs):
+ if t.type and t.leng:
+ try: # create an (empty) instance
+ s.append(t.type())
+ except TypeError:
+ pass
+for t in s:
+ try:
+ i = iter(t)
+ _typedef_both(type(i), leng=_len_iter, refs=_iter_refs, item=0) # no itemsize!
+ except (KeyError, TypeError): # ignore non-iterables, duplicates, etc.
+ pass
+del i, s, t
+
+
+def _typedef(obj, derive=False, frames=False, infer=False): # MCCABE 25
+ '''Create a new typedef for an object.
+ '''
+ t = type(obj)
+ v = _Typedef(base=_basicsize(t, obj=obj),
+ kind=_kind_dynamic, type=t)
+# _printf('new %r %r/%r %s', t, _basicsize(t), _itemsize(t), _repr(dir(obj)))
+ if ismodule(obj): # handle module like dict
+ v.dup(item=_dict_typedef.item + _sizeof_CPyModuleObject,
+ leng=_len_module,
+ refs=_module_refs)
+ elif _isframe(obj):
+ v.set(base=_basicsize(t, base=_sizeof_CPyFrameObject, obj=obj),
+ item=_itemsize(t),
+ leng=_len_frame,
+ refs=_frame_refs)
+ if not frames: # ignore frames
+ v.set(kind=_kind_ignored)
+ elif iscode(obj):
+ v.set(base=_basicsize(t, base=_sizeof_CPyCodeObject, obj=obj),
+ item=_sizeof_Cvoidp,
+ leng=_len_code,
+ refs=_co_refs,
+ both=False) # code only
+ elif _callable(obj):
+ if isclass(obj): # class or type
+ v.set(refs=_class_refs,
+ both=False) # code only
+ if _moduleof(obj) in _builtin_modules:
+ v.set(kind=_kind_ignored)
+ elif isbuiltin(obj): # function or method
+ v.set(both=False, # code only
+ kind=_kind_ignored)
+ elif isfunction(obj):
+ v.set(refs=_func_refs,
+ both=False) # code only
+ elif ismethod(obj):
+ v.set(refs=_im_refs,
+ both=False) # code only
+ elif isclass(t): # callable instance, e.g. SCons,
+ # handle like any other instance further below
+ v.set(item=_itemsize(t), safe_len=True,
+ refs=_inst_refs) # not code only!
+ else:
+ v.set(both=False) # code only
+ elif _issubclass(t, dict):
+ v.dup(kind=_kind_derived)
+ elif _isdictclass(obj) or (infer and _infer_dict(obj)):
+ v.dup(kind=_kind_inferred)
+ elif _iscell(obj):
+ v.set(item=_itemsize(t), refs=_cell_refs)
+ elif _isnamedtuple(obj):
+ v.set(refs=_namedtuple_refs)
+ elif numpy and _isnumpy(obj): # NumPy data
+ v.set(**_numpy_kwds(obj))
+ if _numpy_excl:
+ _getsizeof_excls_add(t)
+ elif array and isinstance(obj, array):
+ v.set(**_array_kwds(obj))
+ if _array_excl:
+ _getsizeof_excls_add(t)
+ elif _moduleof(obj) in _builtin_modules:
+ v.set(kind=_kind_ignored)
+ else: # assume an instance of some class
+ if derive:
+ p = _derive_typedef(t)
+ if p: # duplicate parent
+ v.dup(other=p, kind=_kind_derived)
+ return v
+ if _issubclass(t, Exception):
+ v.set(item=_itemsize(t), safe_len=True,
+ refs=_exc_refs,
+ kind=_kind_derived)
+ elif isinstance(obj, Exception):
+ v.set(item=_itemsize(t), safe_len=True,
+ refs=_exc_refs)
+ else:
+ v.set(item=_itemsize(t), safe_len=True,
+ refs=_inst_refs)
+ return v
+
+
+class _Prof(object):
+ '''Internal type profile class.
+ '''
+ high = 0 # largest size
+ number = 0 # number of (unique) objects
+ objref = None # largest obj (weakref)
+ total = 0 # total size
+ weak = False # objref is weakref(obj)
+
+ def __cmp__(self, other):
+ if self.total < other.total:
+ return -1
+ elif self.total > other.total:
+ return +1
+ elif self.number < other.number:
+ return -1
+ elif self.number > other.number:
+ return +1
+ return 0
+
+ def __lt__(self, other): # for Python 3+
+ return self.__cmp__(other) < 0
+
+ def format(self, clip=0, grand=None):
+ '''Return format dict.
+ '''
+ if self.number > 1: # avg., plural
+ a, p = int(self.total / self.number), 's'
+ else:
+ a, p = self.total, ''
+ o = self.objref
+ if self.weak:
+ o = o()
+ t = _SI2(self.total)
+ if grand:
+ t += ' (%s)' % _p100(self.total, grand, prec=0)
+ return dict(avg=_SI2(a), high=_SI2(self.high),
+ lengstr=_lengstr(o), obj=_repr(o, clip=clip),
+ plural=p, total=t)
+
+ def update(self, obj, size):
+ '''Update this profile.
+ '''
+ self.number += 1
+ self.total += size
+ if self.high < size: # largest
+ self.high = size
+ try: # prefer using weak ref
+ self.objref, self.weak = Weakref.ref(obj), True
+ except TypeError:
+ self.objref, self.weak = obj, False
+
+
+class _Rank(object):
+ '''Internal largest object class.
+ '''
+ __slots__ = {
+ 'deep': 0, # recursion depth
+ 'id': 0, # obj id
+ 'key': None, # Typedef
+ 'objref': None, # obj or Weakref.ref(obj)
+ 'pid': 0, # parent obj id
+ 'size': 0, # size in bytes
+ 'weak': False} # objref is Weakref.ref
+
+ def __init__(self, key, obj, size, deep, pid):
+ self.deep = deep
+ self.id = id(obj)
+ self.key = key
+ try: # prefer using weak ref
+ self.objref, self.weak = Weakref.ref(obj), True
+ except TypeError:
+ self.objref, self.weak = obj, False
+ self.pid = pid
+ self.size = size
+
+ def format(self, clip=0, id2x={}):
+ '''Return string.
+ '''
+ o = self.objref
+ if self.weak:
+ o = o()
+ if self.deep > 0:
+ d = ' (at %s)' % (self.deep,)
+ else:
+ d = ''
+ if self.pid:
+ p = ', pix %s' % (id2x.get(self.pid, '?'),)
+ else:
+ p = ''
+ return '%s: %s%s, ix %d%s%s' % (_prepr(self.key, clip=clip),
+ _repr(o, clip=clip), _lengstr(o), id2x[self.id], d, p)
+
+
+class _Seen(dict):
+ '''Internal obj visits counter.
+ '''
+ def again(self, key):
+ try:
+ s = self[key] + 1
+ except KeyError:
+ s = 1
+ if s > 0:
+ self[key] = s
+
+
+# Public classes
+
+class Asized(object):
+ '''Stores the results of an **asized** object in the following
+ 4 attributes:
+
+ *size* -- total size of the object (including referents)
+
+ *flat* -- flat size of the object (in bytes)
+
+ *name* -- name or ``repr`` of the object
+
+ *refs* -- tuple containing an **Asized** instance for each referent
+ '''
+ __slots__ = ('flat', 'name', 'refs', 'size')
+
+ def __init__(self, size, flat, refs=(), name=None):
+ self.size = size # total size
+ self.flat = flat # flat size
+ self.name = name # name, repr or None
+ self.refs = tuple(refs)
+
+ def __str__(self):
+ return 'size %r, flat %r, refs[%d], name %r' % (
+ self.size, self.flat, len(self.refs), self.name)
+
+ def format(self, format='%(name)s size=%(size)d flat=%(flat)d',
+ depth=-1, order_by='size', indent=''):
+ '''Format the size information of the object and of all
+ sized referents as a string.
+
+ *format* -- Specifies the format per instance (with 'name',
+ 'size' and 'flat' as interpolation parameters)
+
+ *depth* -- Recursion level up to which the referents are
+ printed (use -1 for unlimited)
+
+ *order_by* -- Control sort order of referents, valid choices
+ are 'name', 'size' and 'flat'
+
+ *indent* -- Optional indentation (default '')
+ '''
+ t = indent + (format % dict(size=self.size, flat=self.flat,
+ name=self.name))
+ if depth and self.refs:
+ rs = sorted(self.refs, key=lambda x: getattr(x, order_by),
+ reverse=order_by in ('size', 'flat'))
+ rs = [r.format(format=format, depth=depth-1, order_by=order_by,
+ indent=indent+' ') for r in rs]
+ t = '\n'.join([t] + rs)
+ return t
+
+ def get(self, name, dflt=None):
+ '''Return the named referent (or *dflt* if not found).
+ '''
+ for ref in self.refs:
+ if name == ref.name:
+ return ref
+ return dflt
+
+
+class Asizer(object):
+ '''Sizer state and options to accumulate sizes.
+ '''
+ _above_ = 1024 # rank only objs of size 1K+
+ _align_ = 8
+ _clip_ = 80
+ _code_ = False
+ _cutoff_ = 0 # in percent
+ _derive_ = False
+ _detail_ = 0 # for Asized only
+ _frames_ = False
+ _infer_ = False
+ _limit_ = 100
+ _stats_ = 0
+
+ _depth = 0 # deepest recursion
+ _excl_d = None # {}
+ _ign_d = _kind_ignored
+ _incl = '' # or ' (incl. code)'
+ _mask = 7 # see _align_
+ _missed = 0 # due to errors
+ _profile = False # no profiling
+ _profs = None # {}
+ _ranked = 0
+ _ranks = [] # type: List[_Rank] # sorted by decreasing size
+ _seen = None # {}
+ _stream = None # I/O stream for printing
+ _total = 0 # total size
+
+ def __init__(self, **opts):
+ '''New **Asizer** accumulator.
+
+ See this module documentation for more details.
+ See method **reset** for all available options and defaults.
+ '''
+ self._excl_d = {}
+ self.reset(**opts)
+
+ def _c100(self, stats):
+ '''Cutoff as percentage (for backward compatibility)
+ '''
+ s = int(stats)
+ c = int((stats - s) * 100.0 + 0.5) or self.cutoff
+ return s, c
+
+ def _clear(self):
+ '''Clear state.
+ '''
+ self._depth = 0 # recursion depth reached
+ self._incl = '' # or ' (incl. code)'
+ self._missed = 0 # due to errors
+ self._profile = False
+ self._profs = {}
+ self._ranked = 0
+ self._ranks = []
+ self._seen = _Seen()
+ self._total = 0 # total size
+ for k in _keys(self._excl_d):
+ self._excl_d[k] = 0
+ # don't size, profile or rank private, possibly large objs
+ m = sys.modules[__name__]
+ self.exclude_objs(self, self._excl_d, self._profs, self._ranks,
+ self._seen, m, m.__dict__, m.__doc__,
+ _typedefs)
+
+ def _nameof(self, obj):
+ '''Return the object's name.
+ '''
+ return _nameof(obj, '') or self._repr(obj)
+
+ def _prepr(self, obj):
+ '''Like **prepr()**.
+ '''
+ return _prepr(obj, clip=self._clip_)
+
+ def _printf(self, fmt, *args, **print3options):
+ '''Print to sys.stdout or the configured stream if any is
+ specified and if the file keyword argument is not already
+ set in the **print3options** for this specific call.
+ '''
+ if self._stream and not print3options.get('file', None):
+ if args:
+ fmt = fmt % args
+ _printf(fmt, file=self._stream, **print3options)
+ else:
+ _printf(fmt, *args, **print3options)
+
+ def _prof(self, key):
+ '''Get _Prof object.
+ '''
+ p = self._profs.get(key, None)
+ if not p:
+ self._profs[key] = p = _Prof()
+ self.exclude_objs(p) # XXX superfluous?
+ return p
+
+ def _rank(self, key, obj, size, deep, pid):
+ '''Rank 100 largest objects by size.
+ '''
+ rs = self._ranks
+ # bisect, see <http://GitHub.com/python/cpython/blob/master/Lib/bisect.py>
+ i, j = 0, len(rs)
+ while i < j:
+ m = (i + j) // 2
+ if size < rs[m].size:
+ i = m + 1
+ else:
+ j = m
+ if i < 100:
+ r = _Rank(key, obj, size, deep, pid)
+ rs.insert(i, r)
+ self.exclude_objs(r) # XXX superfluous?
+ while len(rs) > 100:
+ rs.pop()
+ # self._ranks[:] = rs[:100]
+ self._ranked += 1
+
+ def _repr(self, obj):
+ '''Like ``repr()``.
+ '''
+ return _repr(obj, clip=self._clip_)
+
+ def _sizer(self, obj, pid, deep, sized): # MCCABE 19
+ '''Size an object, recursively.
+ '''
+ s, f, i = 0, 0, id(obj)
+ if i not in self._seen:
+ self._seen[i] = 1
+ elif deep or self._seen[i]:
+ # skip obj if seen before
+ # or if ref of a given obj
+ self._seen.again(i)
+ if sized:
+ s = sized(s, f, name=self._nameof(obj))
+ self.exclude_objs(s)
+ return s # zero
+ else: # deep == seen[i] == 0
+ self._seen.again(i)
+ try:
+ k, rs = _objkey(obj), []
+ if k in self._excl_d:
+ self._excl_d[k] += 1
+ else:
+ v = _typedefs.get(k, None)
+ if not v: # new typedef
+ _typedefs[k] = v = _typedef(obj, derive=self._derive_,
+ frames=self._frames_,
+ infer=self._infer_)
+ if (v.both or self._code_) and v.kind is not self._ign_d:
+ s = f = v.flat(obj, self._mask) # flat size
+ if self._profile:
+ # profile based on *flat* size
+ self._prof(k).update(obj, s)
+ # recurse, but not for nested modules
+ if v.refs and deep < self._limit_ \
+ and not (deep and ismodule(obj)):
+ # add sizes of referents
+ z, d = self._sizer, deep + 1
+ if sized and deep < self._detail_:
+ # use named referents
+ self.exclude_objs(rs)
+ for o in v.refs(obj, True):
+ if isinstance(o, _NamedRef):
+ r = z(o.ref, i, d, sized)
+ r.name = o.name
+ else:
+ r = z(o, i, d, sized)
+ r.name = self._nameof(o)
+ rs.append(r)
+ s += r.size
+ else: # just size and accumulate
+ for o in v.refs(obj, False):
+ s += z(o, i, d, None)
+ # deepest recursion reached
+ if self._depth < d:
+ self._depth = d
+ if self._stats_ and s > self._above_ > 0:
+ # rank based on *total* size
+ self._rank(k, obj, s, deep, pid)
+ except RuntimeError: # XXX RecursionLimitExceeded:
+ self._missed += 1
+ if not deep:
+ self._total += s # accumulate
+ if sized:
+ s = sized(s, f, name=self._nameof(obj), refs=rs)
+ self.exclude_objs(s)
+ return s
+
+ def _sizes(self, objs, sized=None):
+ '''Return the size or an **Asized** instance for each
+ given object plus the total size. The total includes
+ the size of duplicates only once.
+ '''
+ self.exclude_refs(*objs) # skip refs to objs
+ s, t = {}, []
+ self.exclude_objs(s, t)
+ for o in objs:
+ i = id(o)
+ if i in s: # duplicate
+ self._seen.again(i)
+ else:
+ s[i] = self._sizer(o, 0, 0, sized)
+ t.append(s[i])
+ return tuple(t)
+
+ @property
+ def above(self):
+ '''Get the large object size threshold (int).
+ '''
+ return self._above_
+
+ @property
+ def align(self):
+ '''Get the size alignment (int).
+ '''
+ return self._align_
+
+ def asized(self, *objs, **opts):
+ '''Size each object and return an **Asized** instance with
+ size information and referents up to the given detail
+ level (and with modified options, see method **set**).
+
+ If only one object is given, the return value is the
+ **Asized** instance for that object. The **Asized** size
+ of duplicate and ignored objects will be zero.
+ '''
+ if opts:
+ self.set(**opts)
+ t = self._sizes(objs, Asized)
+ if len(t) == 1:
+ t = t[0]
+ return t
+
+ def asizeof(self, *objs, **opts):
+ '''Return the combined size of the given objects
+ (with modified options, see method **set**).
+ '''
+ if opts:
+ self.set(**opts)
+ self.exclude_refs(*objs) # skip refs to objs
+ return sum(self._sizer(o, 0, 0, None) for o in objs)
+
+ def asizesof(self, *objs, **opts):
+ '''Return the individual sizes of the given objects
+ (with modified options, see method **set**).
+
+ The size of duplicate and ignored objects will be zero.
+ '''
+ if opts:
+ self.set(**opts)
+ return self._sizes(objs, None)
+
+ @property
+ def clip(self):
+ '''Get the clipped string length (int).
+ '''
+ return self._clip_
+
+ @property
+ def code(self):
+ '''Size (byte) code (bool).
+ '''
+ return self._code_
+
+ @property
+ def cutoff(self):
+ '''Stats cutoff (int).
+ '''
+ return self._cutoff_
+
+ @property
+ def derive(self):
+ '''Derive types (bool).
+ '''
+ return self._derive_
+
+ @property
+ def detail(self):
+ '''Get the detail level for **Asized** refs (int).
+ '''
+ return self._detail_
+
+ @property
+ def duplicate(self):
+ '''Get the number of duplicate objects seen so far (int).
+ '''
+ return sum(1 for v in _values(self._seen) if v > 1) # == len
+
+ def exclude_objs(self, *objs):
+ '''Exclude the specified objects from sizing, profiling and ranking.
+ '''
+ for o in objs:
+ self._seen.setdefault(id(o), -1)
+
+ def exclude_refs(self, *objs):
+ '''Exclude any references to the specified objects from sizing.
+
+ While any references to the given objects are excluded, the
+ objects will be sized if specified as positional arguments
+ in subsequent calls to methods **asizeof** and **asizesof**.
+ '''
+ for o in objs:
+ self._seen.setdefault(id(o), 0)
+
+ def exclude_types(self, *objs):
+ '''Exclude the specified object instances and types from sizing.
+
+ All instances and types of the given objects are excluded,
+ even objects specified as positional arguments in subsequent
+ calls to methods **asizeof** and **asizesof**.
+ '''
+ for o in objs:
+ for t in _keytuple(o):
+ if t and t not in self._excl_d:
+ self._excl_d[t] = 0
+
+ @property
+ def excluded(self):
+ '''Get the types being excluded (tuple).
+ '''
+ return tuple(_keys(self._excl_d))
+
+ @property
+ def frames(self):
+ '''Ignore stack frames (bool).
+ '''
+ return self._frames_
+
+ @property
+ def ignored(self):
+ '''Ignore certain types (bool).
+ '''
+ return True if self._ign_d else False
+
+ @property
+ def infer(self):
+ '''Infer types (bool).
+ '''
+ return self._infer_
+
+ @property
+ def limit(self):
+ '''Get the recursion limit (int).
+ '''
+ return self._limit_
+
+ @property
+ def missed(self):
+ '''Get the number of objects missed due to errors (int).
+ '''
+ return self._missed
+
+ def print_largest(self, w=0, cutoff=0, **print3options):
+ '''Print the largest objects.
+
+ The available options and defaults are:
+
+ *w=0* -- indentation for each line
+
+ *cutoff=100* -- number of largest objects to print
+
+ *print3options* -- some keyword arguments, like Python 3+ print
+ '''
+ c = int(cutoff) if cutoff else self._cutoff_
+ n = min(len(self._ranks), max(c, 0))
+ s = self._above_
+ if n > 0 and s > 0:
+ self._printf('%s%*d largest object%s (of %d over %d bytes%s)', linesep,
+ w, n, _plural(n), self._ranked, s, _SI(s), **print3options)
+ id2x = dict((r.id, i) for i, r in enumerate(self._ranks))
+ for r in self._ranks[:n]:
+ s, t = r.size, r.format(self._clip_, id2x)
+ self._printf('%*d bytes%s: %s', w, s, _SI(s), t, **print3options)
+
+ def print_profiles(self, w=0, cutoff=0, **print3options):
+ '''Print the profiles above *cutoff* percentage.
+
+ The available options and defaults are:
+
+ *w=0* -- indentation for each line
+
+ *cutoff=0* -- minimum percentage printed
+
+ *print3options* -- some keyword arguments, like Python 3+ print
+ '''
+ # get the profiles with non-zero size or count
+ t = [(v, k) for k, v in _items(self._profs) if v.total > 0 or v.number > 1]
+ if (len(self._profs) - len(t)) < 9: # just show all
+ t = [(v, k) for k, v in _items(self._profs)]
+ if t:
+ s = ''
+ if self._total:
+ s = ' (% of grand total)'
+ c = int(cutoff) if cutoff else self._cutoff_
+ C = int(c * 0.01 * self._total)
+ else:
+ C = c = 0
+ self._printf('%s%*d profile%s: total%s, average, and largest flat size%s: largest object',
+ linesep, w, len(t), _plural(len(t)), s, self._incl, **print3options)
+ r = len(t)
+ for v, k in sorted(t, reverse=True):
+ s = 'object%(plural)s: %(total)s, %(avg)s, %(high)s: %(obj)s%(lengstr)s' % v.format(self._clip_, self._total)
+ self._printf('%*d %s %s', w, v.number, self._prepr(k), s, **print3options)
+ r -= 1
+ if r > 1 and v.total < C:
+ self._printf('%+*d profiles below cutoff (%.0f%%)', w, r, c)
+ break
+ z = len(self._profs) - len(t)
+ if z > 0:
+ self._printf('%+*d %r object%s', w, z, 'zero', _plural(z), **print3options)
+
+ def print_stats(self, objs=(), opts={}, sized=(), sizes=(), stats=3, **print3options):
+ '''Prints the statistics.
+
+ The available options and defaults are:
+
+ *w=0* -- indentation for each line
+
+ *objs=()* -- optional, list of objects
+
+ *opts={}* -- optional, dict of options used
+
+ *sized=()* -- optional, tuple of **Asized** instances returned
+
+ *sizes=()* -- optional, tuple of sizes returned
+
+ *stats=3* -- print stats, see function **asizeof**
+
+ *print3options* -- some keyword arguments, like Python 3+ print
+ '''
+ s = min(opts.get('stats', stats) or 0, self.stats)
+ if s > 0: # print stats
+ w = len(str(self.missed + self.seen + self.total)) + 1
+ t = c = ''
+ o = _kwdstr(**opts)
+ if o and objs:
+ c = ', '
+ # print header line(s)
+ if sized and objs:
+ n = len(objs)
+ if n > 1:
+ self._printf('%sasized(...%s%s) ...', linesep, c, o, **print3options)
+ for i in range(n): # no enumerate in Python 2.2.3
+ self._printf('%*d: %s', w - 1, i, sized[i], **print3options)
+ else:
+ self._printf('%sasized(%s): %s', linesep, o, sized, **print3options)
+ elif sizes and objs:
+ self._printf('%sasizesof(...%s%s) ...', linesep, c, o, **print3options)
+ for z, o in zip(sizes, objs):
+ self._printf('%*d bytes%s%s: %s', w, z, _SI(z), self._incl, self._repr(o), **print3options)
+ else:
+ if objs:
+ t = self._repr(objs)
+ self._printf('%sasizeof(%s%s%s) ...', linesep, t, c, o, **print3options)
+ # print summary
+ self.print_summary(w=w, objs=objs, **print3options)
+ # for backward compatibility, cutoff from fractional stats
+ s, c = self._c100(s)
+ self.print_largest(w=w, cutoff=c if s < 2 else 10, **print3options)
+ if s > 1: # print profile
+ self.print_profiles(w=w, cutoff=c, **print3options)
+ if s > 2: # print typedefs
+ self.print_typedefs(w=w, **print3options) # PYCHOK .print_largest?
+
+ def print_summary(self, w=0, objs=(), **print3options):
+ '''Print the summary statistics.
+
+ The available options and defaults are:
+
+ *w=0* -- indentation for each line
+
+ *objs=()* -- optional, list of objects
+
+ *print3options* -- some keyword arguments, like Python 3+ print
+ '''
+ self._printf('%*d bytes%s%s', w, self._total, _SI(self._total), self._incl, **print3options)
+ if self._mask:
+ self._printf('%*d byte aligned', w, self._mask + 1, **print3options)
+ self._printf('%*d byte sizeof(void*)', w, _sizeof_Cvoidp, **print3options)
+ n = len(objs or ())
+ self._printf('%*d object%s %s', w, n, _plural(n), 'given', **print3options)
+ n = self.sized
+ self._printf('%*d object%s %s', w, n, _plural(n), 'sized', **print3options)
+ if self._excl_d:
+ n = sum(_values(self._excl_d))
+ self._printf('%*d object%s %s', w, n, _plural(n), 'excluded', **print3options)
+ n = self.seen
+ self._printf('%*d object%s %s', w, n, _plural(n), 'seen', **print3options)
+ n = self.ranked
+ if n > 0:
+ self._printf('%*d object%s %s', w, n, _plural(n), 'ranked', **print3options)
+ n = self.missed
+ self._printf('%*d object%s %s', w, n, _plural(n), 'missed', **print3options)
+ n = self.duplicate
+ self._printf('%*d duplicate%s', w, n, _plural(n), **print3options)
+ if self._depth > 0:
+ self._printf('%*d deepest recursion', w, self._depth, **print3options)
+
+ def print_typedefs(self, w=0, **print3options):
+ '''Print the types and dict tables.
+
+ The available options and defaults are:
+
+ *w=0* -- indentation for each line
+
+ *print3options* -- some keyword arguments, like Python 3+ print
+ '''
+ for k in _all_kinds:
+ # XXX Python 3+ doesn't sort type objects
+ t = [(self._prepr(a), v) for a, v in _items(_typedefs)
+ if v.kind == k and (v.both or self._code_)]
+ if t:
+ self._printf('%s%*d %s type%s: basicsize, itemsize, _len_(), _refs()',
+ linesep, w, len(t), k, _plural(len(t)), **print3options)
+ for a, v in sorted(t):
+ self._printf('%*s %s: %s', w, '', a, v, **print3options)
+ # dict and dict-like classes
+ t = sum(len(v) for v in _values(_dict_classes))
+ if t:
+ self._printf('%s%*d dict/-like classes:', linesep, w, t, **print3options)
+ for m, v in _items(_dict_classes):
+ self._printf('%*s %s: %s', w, '', m, self._prepr(v), **print3options)
+
+ @property
+ def ranked(self):
+ '''Get the number objects ranked by size so far (int).
+ '''
+ return self._ranked
+
+ def reset(self, above=1024, align=8, clip=80, code=False, # PYCHOK too many args
+ cutoff=10, derive=False, detail=0, frames=False, ignored=True,
+ infer=False, limit=100, stats=0, stream=None, **extra):
+ '''Reset sizing options, state, etc. to defaults.
+
+ The available options and default values are:
+
+ *above=0* -- threshold for largest objects stats
+
+ *align=8* -- size alignment
+
+ *code=False* -- incl. (byte)code size
+
+ *cutoff=10* -- limit large objects or profiles stats
+
+ *derive=False* -- derive from super type
+
+ *detail=0* -- **Asized** refs level
+
+ *frames=False* -- ignore frame objects
+
+ *ignored=True* -- ignore certain types
+
+ *infer=False* -- try to infer types
+
+ *limit=100* -- recursion limit
+
+ *stats=0* -- print statistics, see function **asizeof**
+
+ *stream=None* -- output stream for printing
+
+ See function **asizeof** for a description of the options.
+ '''
+ if extra:
+ t = _plural(len(extra)), _kwdstr(**extra)
+ raise KeyError('invalid option%s: %s' % t)
+ # options
+ self._above_ = above
+ self._align_ = align
+ self._clip_ = clip
+ self._code_ = code
+ self._cutoff_ = cutoff
+ self._derive_ = derive
+ self._detail_ = detail # for Asized only
+ self._frames_ = frames
+ self._infer_ = infer
+ self._limit_ = limit
+ self._stats_ = stats
+ self._stream = stream
+ if ignored:
+ self._ign_d = _kind_ignored
+ else:
+ self._ign_d = None
+ # clear state
+ self._clear()
+ self.set(align=align, code=code, cutoff=cutoff, stats=stats)
+
+ @property
+ def seen(self):
+ '''Get the number objects seen so far (int).
+ '''
+ return sum(v for v in _values(self._seen) if v > 0)
+
+ def set(self, above=None, align=None, code=None, cutoff=None,
+ frames=None, detail=None, limit=None, stats=None):
+ '''Set some sizing options. See also **reset**.
+
+ The available options are:
+
+ *above* -- threshold for largest objects stats
+
+ *align* -- size alignment
+
+ *code* -- incl. (byte)code size
+
+ *cutoff* -- limit large objects or profiles stats
+
+ *detail* -- **Asized** refs level
+
+ *frames* -- size or ignore frame objects
+
+ *limit* -- recursion limit
+
+ *stats* -- print statistics, see function **asizeof**
+
+ Any options not set remain unchanged from the previous setting.
+ '''
+ # adjust
+ if above is not None:
+ self._above_ = int(above)
+ if align is not None:
+ self._align_ = align
+ if align > 1:
+ self._mask = align - 1
+ if (self._mask & align) != 0:
+ raise ValueError('invalid option: %s=%r' % ('align', align))
+ else:
+ self._mask = 0
+ if code is not None:
+ self._code_ = code
+ if code: # incl. (byte)code
+ self._incl = ' (incl. code)'
+ if detail is not None:
+ self._detail_ = detail
+ if frames is not None:
+ self._frames_ = frames
+ if limit is not None:
+ self._limit_ = limit
+ if stats is not None:
+ if stats < 0:
+ raise ValueError('invalid option: %s=%r' % ('stats', stats))
+ # for backward compatibility, cutoff from fractional stats
+ s, c = self._c100(stats)
+ self._cutoff_ = int(cutoff) if cutoff else c
+ self._stats_ = s
+ self._profile = s > 1 # profile types
+
+ @property
+ def sized(self):
+ '''Get the number objects sized so far (int).
+ '''
+ return sum(1 for v in _values(self._seen) if v > 0)
+
+ @property
+ def stats(self):
+ '''Get the stats and cutoff setting (float).
+ '''
+ return self._stats_ # + (self._cutoff_ * 0.01)
+
+ @property
+ def total(self):
+ '''Get the total size (in bytes) accumulated so far.
+ '''
+ return self._total
+
+
+# Public functions
+
+def adict(*classes):
+ '''Install one or more classes to be handled as dict.
+ '''
+ a = True
+ for c in classes:
+ # if class is dict-like, add class
+ # name to _dict_classes[module]
+ if isclass(c) and _infer_dict(c):
+ t = _dict_classes.get(c.__module__, ())
+ if c.__name__ not in t: # extend tuple
+ _dict_classes[c.__module__] = t + (c.__name__,)
+ else: # not a dict-like class
+ a = False
+ return a # all installed if True
+
+
+_asizer = Asizer()
+
+
+def asized(*objs, **opts):
+ '''Return a tuple containing an **Asized** instance for each
+ object passed as positional argument.
+
+ The available options and defaults are:
+
+ *above=0* -- threshold for largest objects stats
+
+ *align=8* -- size alignment
+
+ *code=False* -- incl. (byte)code size
+
+ *cutoff=10* -- limit large objects or profiles stats
+
+ *derive=False* -- derive from super type
+
+ *detail=0* -- Asized refs level
+
+ *frames=False* -- ignore stack frame objects
+
+ *ignored=True* -- ignore certain types
+
+ *infer=False* -- try to infer types
+
+ *limit=100* -- recursion limit
+
+ *stats=0* -- print statistics
+
+ If only one object is given, the return value is the **Asized**
+ instance for that object. Otherwise, the length of the returned
+ tuple matches the number of given objects.
+
+ The **Asized** size of duplicate and ignored objects will be zero.
+
+ Set *detail* to the desired referents level and *limit* to the
+ maximum recursion depth.
+
+ See function **asizeof** for descriptions of the other options.
+ '''
+ _asizer.reset(**opts)
+ if objs:
+ t = _asizer.asized(*objs)
+ _asizer.print_stats(objs, opts=opts, sized=t) # show opts as _kwdstr
+ _asizer._clear()
+ else:
+ t = ()
+ return t
+
+
+def asizeof(*objs, **opts):
+ '''Return the combined size (in bytes) of all objects passed
+ as positional arguments.
+
+ The available options and defaults are:
+
+ *above=0* -- threshold for largest objects stats
+
+ *align=8* -- size alignment
+
+ *clip=80* -- clip ``repr()`` strings
+
+ *code=False* -- incl. (byte)code size
+
+ *cutoff=10* -- limit large objects or profiles stats
+
+ *derive=False* -- derive from super type
+
+ *frames=False* -- ignore stack frame objects
+
+ *ignored=True* -- ignore certain types
+
+ *infer=False* -- try to infer types
+
+ *limit=100* -- recursion limit
+
+ *stats=0* -- print statistics
+
+ Set *align* to a power of 2 to align sizes. Any value less
+ than 2 avoids size alignment.
+
+ If *all* is True and if no positional arguments are supplied.
+ size all current gc objects, including module, global and stack
+ frame objects.
+
+ A positive *clip* value truncates all repr() strings to at
+ most *clip* characters.
+
+ The (byte)code size of callable objects like functions,
+ methods, classes, etc. is included only if *code* is True.
+
+ If *derive* is True, new types are handled like an existing
+ (super) type provided there is one and only of those.
+
+ By default certain base types like object, super, etc. are
+ ignored. Set *ignored* to False to include those.
+
+ If *infer* is True, new types are inferred from attributes
+ (only implemented for dict types on callable attributes
+ as get, has_key, items, keys and values).
+
+ Set *limit* to a positive value to accumulate the sizes of
+ the referents of each object, recursively up to the limit.
+ Using *limit=0* returns the sum of the flat sizes of the
+ given objects. High *limit* values may cause runtime errors
+ and miss objects for sizing.
+
+ A positive value for *stats* prints up to 9 statistics, (1)
+ a summary of the number of objects sized and seen and a list
+ of the largests objects with size over *above* bytes, (2) a
+ simple profile of the sized objects by type and (3+) up to 6
+ tables showing the static, dynamic, derived, ignored, inferred
+ and dict types used, found respectively installed.
+ The fractional part of the *stats* value (x 100) is the number
+ of largest objects shown for (*stats*1.+) or the cutoff
+ percentage for simple profiles for (*stats*=2.+). For example,
+ *stats=1.10* shows the summary and the 10 largest objects,
+ also the default.
+
+ See this module documentation for the definition of flat size.
+ '''
+ t, p, x = _objs_opts_x(objs, **opts)
+ _asizer.reset(**p)
+ if t:
+ if x: # don't size, profile or rank _getobjects tuple
+ _asizer.exclude_objs(t)
+ s = _asizer.asizeof(*t)
+ _asizer.print_stats(objs=t, opts=opts) # show opts as _kwdstr
+ _asizer._clear()
+ else:
+ s = 0
+ return s
+
+
+def asizesof(*objs, **opts):
+ '''Return a tuple containing the size (in bytes) of all objects
+ passed as positional arguments.
+
+ The available options and defaults are:
+
+ *above=1024* -- threshold for largest objects stats
+
+ *align=8* -- size alignment
+
+ *clip=80* -- clip ``repr()`` strings
+
+ *code=False* -- incl. (byte)code size
+
+ *cutoff=10* -- limit large objects or profiles stats
+
+ *derive=False* -- derive from super type
+
+ *frames=False* -- ignore stack frame objects
+
+ *ignored=True* -- ignore certain types
+
+ *infer=False* -- try to infer types
+
+ *limit=100* -- recursion limit
+
+ *stats=0* -- print statistics
+
+ See function **asizeof** for a description of the options.
+
+ The length of the returned tuple equals the number of given
+ objects.
+
+ The size of duplicate and ignored objects will be zero.
+ '''
+ _asizer.reset(**opts)
+ if objs:
+ t = _asizer.asizesof(*objs)
+ _asizer.print_stats(objs, opts=opts, sizes=t) # show opts as _kwdstr
+ _asizer._clear()
+ else:
+ t = ()
+ return t
+
+
+def _typedefof(obj, save=False, **opts):
+ '''Get the typedef for an object.
+ '''
+ k = _objkey(obj)
+ v = _typedefs.get(k, None)
+ if not v: # new typedef
+ v = _typedef(obj, **opts)
+ if save:
+ _typedefs[k] = v
+ return v
+
+
+def basicsize(obj, **opts):
+ '''Return the basic size of an object (in bytes).
+
+ The available options and defaults are:
+
+ *derive=False* -- derive type from super type
+
+ *infer=False* -- try to infer types
+
+ *save=False* -- save the object's type definition if new
+
+ See this module documentation for the definition of *basic size*.
+ '''
+ b = t = _typedefof(obj, **opts)
+ if t:
+ b = t.base
+ return b
+
+
+def flatsize(obj, align=0, **opts):
+ '''Return the flat size of an object (in bytes), optionally aligned
+ to the given power of 2.
+
+ See function **basicsize** for a description of other available options.
+
+ See this module documentation for the definition of *flat size*.
+ '''
+ f = t = _typedefof(obj, **opts)
+ if t:
+ if align > 1:
+ m = align - 1
+ if (align & m) != 0:
+ raise ValueError('invalid option: %s=%r' % ('align', align))
+ else:
+ m = 0
+ f = t.flat(obj, mask=m)
+ return f
+
+
+def itemsize(obj, **opts):
+ '''Return the item size of an object (in bytes).
+
+ See function **basicsize** for a description of the available options.
+
+ See this module documentation for the definition of *item size*.
+ '''
+ i = t = _typedefof(obj, **opts)
+ if t:
+ i, v = t.item, t.vari
+ if v and i == _sizeof_Cbyte:
+ i = getattr(obj, v, i)
+ return i
+
+
+def leng(obj, **opts):
+ '''Return the length of an object (in items).
+
+ See function **basicsize** for a description of the available options.
+ '''
+ n = t = _typedefof(obj, **opts)
+ if t:
+ n = t.leng
+ if n and _callable(n):
+ i, v, n = t.item, t.vari, n(obj)
+ if v and i == _sizeof_Cbyte:
+ i = getattr(obj, v, i)
+ if i > _sizeof_Cbyte:
+ n = n // i
+ return n
+
+
+def named_refs(obj, **opts):
+ '''Return all named **referents** of an object (re-using
+ functionality from **asizeof**).
+
+ Does not return un-named *referents*, e.g. objects in a list.
+
+ See function **basicsize** for a description of the available options.
+ '''
+ rs = []
+ v = _typedefof(obj, **opts)
+ if v:
+ v = v.refs
+ if v and _callable(v):
+ for r in v(obj, True):
+ try:
+ rs.append((r.name, r.ref))
+ except AttributeError:
+ pass
+ return rs
+
+
+def refs(obj, **opts):
+ '''Return (a generator for) specific *referents* of an object.
+
+ See function **basicsize** for a description of the available options.
+ '''
+ v = _typedefof(obj, **opts)
+ if v:
+ v = v.refs
+ if v and _callable(v):
+ v = v(obj, False)
+ return v
+
+
+if __name__ == '__main__':
+
+ if '-v' in sys.argv:
+ import platform
+ print('%s %s (Python %s %s)' % (__file__, __version__,
+ sys.version.split()[0],
+ platform.architecture()[0]))
+
+ elif '-types' in sys.argv: # print static _typedefs
+ n = len(_typedefs)
+ w = len(str(n)) * ' '
+ _printf('%s%d type definitions: %s and %s, kind ... %s', linesep,
+ n, 'basic-', 'itemsize (leng)', '-type[def]s')
+ for k, td in sorted((_prepr(k), td) for k, td in _items(_typedefs)):
+ desc = '%(base)s and %(item)s%(leng)s, %(kind)s%(code)s' % td.format()
+ _printf('%s %s: %s', w, k, desc)
+
+ else:
+ import gc
+ collect = False
+ if '-gc' in sys.argv:
+ collect = True
+ gc.collect()
+
+ frames = '-frames' in sys.argv
+
+ # just an example
+ asizeof(all=True, frames=frames, stats=1, above=1024) # print summary + 10 largest
+
+ if collect:
+ print('gc.collect() %d' % (gc.collect(),))
+
+# License from the initial version of this source file follows:
+
+# --------------------------------------------------------------------
+# Copyright (c) 2002-2019 -- ProphICy Semiconductor, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# - Neither the name of ProphICy Semiconductor, Inc. nor the names
+# of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+# --------------------------------------------------------------------
diff --git a/venv/lib/python3.9/site-packages/pympler/charts.py b/venv/lib/python3.9/site-packages/pympler/charts.py
new file mode 100644
index 00000000..050cc027
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/charts.py
@@ -0,0 +1,62 @@
+"""
+Generate charts from gathered data.
+
+Requires **matplotlib**.
+"""
+
+from pympler.classtracker_stats import Stats
+
+try:
+ import matplotlib
+ matplotlib.use('Agg')
+ import matplotlib.pyplot as plt
+
+ def tracker_timespace(filename: str, stats: Stats) -> None:
+ """
+ Create a time-space chart from a ``Stats`` instance.
+ """
+ classlist = list(stats.index.keys())
+ classlist.sort()
+
+ for snapshot in stats.snapshots:
+ stats.annotate_snapshot(snapshot)
+
+ timestamps = [fp.timestamp for fp in stats.snapshots]
+ offsets = [0] * len(stats.snapshots)
+ poly_labels = []
+ polys = []
+ for clsname in classlist:
+ pct = [fp.classes[clsname]['pct'] for fp in stats.snapshots
+ if fp.classes and clsname in fp.classes]
+ if max(pct) > 3.0:
+ sizes = [fp.classes[clsname]['sum'] for fp in stats.snapshots
+ if fp.classes and clsname in fp.classes]
+ sizes = [float(x) / (1024 * 1024) for x in sizes]
+ sizes = [offset + size for offset, size in zip(offsets, sizes)]
+ poly = matplotlib.mlab.poly_between(timestamps, offsets, sizes)
+ polys.append((poly, {'label': clsname}))
+ poly_labels.append(clsname)
+ offsets = sizes
+
+ fig = plt.figure(figsize=(10, 4))
+ axis = fig.add_subplot(111)
+
+ axis.set_title("Snapshot Memory")
+ axis.set_xlabel("Execution Time [s]")
+ axis.set_ylabel("Virtual Memory [MiB]")
+
+ totals = [float(x.asizeof_total) / (1024 * 1024)
+ for x in stats.snapshots]
+ axis.plot(timestamps, totals, 'r--', label='Total')
+ tracked = [float(x.tracked_total) / (1024 * 1024)
+ for x in stats.snapshots]
+ axis.plot(timestamps, tracked, 'b--', label='Tracked total')
+
+ for (args, kwds) in polys:
+ axis.fill(*args, **kwds)
+ axis.legend(loc=2) # TODO fill legend
+ fig.savefig(filename)
+
+except ImportError:
+ def tracker_timespace(filename: str, stats: Stats) -> None:
+ pass
diff --git a/venv/lib/python3.9/site-packages/pympler/classtracker.py b/venv/lib/python3.9/site-packages/pympler/classtracker.py
new file mode 100644
index 00000000..b187e4b3
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/classtracker.py
@@ -0,0 +1,590 @@
+"""
+The `ClassTracker` is a facility delivering insight into the memory
+distribution of a Python program. It can introspect memory consumption of
+certain classes and objects. Facilities are provided to track and size
+individual objects or all instances of certain classes. Tracked objects are
+sized recursively to provide an overview of memory distribution between the
+different tracked objects.
+"""
+
+from typing import Any, Callable, Dict, IO, List, Optional, Tuple
+
+from collections import defaultdict
+from functools import partial
+from inspect import stack, isclass
+from threading import Thread, Lock
+from time import sleep, time
+from weakref import ref as weakref_ref
+
+from pympler.classtracker_stats import ConsoleStats
+from pympler.util.stringutils import safe_repr
+
+import pympler.asizeof as asizeof
+import pympler.process
+
+
+__all__ = ["ClassTracker"]
+
+# Fixpoint for program start relative time stamp.
+_local_start = time()
+
+
+class _ClassObserver(object):
+ """
+ Stores options for tracked classes.
+ The observer also keeps the original constructor of the observed class.
+ """
+ __slots__ = ('init', 'name', 'detail', 'keep', 'trace')
+
+ def __init__(self, init: Callable, name: str, detail: int, keep: bool,
+ trace: bool):
+ self.init = init
+ self.name = name
+ self.detail = detail
+ self.keep = keep
+ self.trace = trace
+
+ def modify(self, name: str, detail: int, keep: bool, trace: bool) -> None:
+ self.name = name
+ self.detail = detail
+ self.keep = keep
+ self.trace = trace
+
+
+def _get_time() -> float:
+ """
+ Get a timestamp relative to the program start time.
+ """
+ return time() - _local_start
+
+
+class TrackedObject(object):
+ """
+ Stores size and lifetime information of a tracked object. A weak reference
+ is attached to monitor the object without preventing its deletion.
+ """
+ __slots__ = ("ref", "id", "repr", "name", "birth", "death", "trace",
+ "snapshots", "_resolution_level", "__dict__")
+
+ def __init__(self, instance: Any, name: str, resolution_level: int = 0,
+ trace: bool = False, on_delete: Optional[Callable] = None):
+ """
+ Create a weak reference for 'instance' to observe an object but which
+ won't prevent its deletion (which is monitored by the finalize
+ callback). The size of the object is recorded in 'snapshots' as
+ (timestamp, size) tuples.
+ """
+ self.ref = weakref_ref(instance, self.finalize)
+ self.id = id(instance)
+ self.repr = ''
+ self.name = name
+ self.birth = _get_time()
+ self.death = None # type: Optional[float]
+ self._resolution_level = resolution_level
+ self.trace = None # type: Optional[List[Tuple]]
+
+ if trace:
+ self._save_trace()
+
+ initial_size = asizeof.basicsize(instance) or 0
+ size = asizeof.Asized(initial_size, initial_size)
+ self.snapshots = [(self.birth, size)]
+ self.on_delete = on_delete
+
+ def __getstate__(self) -> Dict:
+ """
+ Make the object serializable for dump_stats. Read the available slots
+ and store the values in a dictionary. Derived values (stored in the
+ dict) are not pickled as those can be reconstructed based on the other
+ data. References cannot be serialized, ignore 'ref' as well.
+ """
+ state = {}
+ for name in getattr(TrackedObject, '__slots__', ()):
+ if hasattr(self, name) and name not in ['ref', '__dict__']:
+ state[name] = getattr(self, name)
+ return state
+
+ def __setstate__(self, state: Dict) -> None:
+ """
+ Restore the state from pickled data. Needed because a slotted class is
+ used.
+ """
+ for key, value in list(state.items()):
+ setattr(self, key, value)
+
+ def _save_trace(self) -> None:
+ """
+ Save current stack trace as formatted string.
+ """
+ stack_trace = stack()
+ try:
+ self.trace = []
+ for frm in stack_trace[5:]: # eliminate our own overhead
+ self.trace.insert(0, frm[1:])
+ finally:
+ del stack_trace
+
+ def track_size(self, ts: float, sizer: asizeof.Asizer) -> None:
+ """
+ Store timestamp and current size for later evaluation.
+ The 'sizer' is a stateful sizing facility that excludes other tracked
+ objects.
+ """
+ obj = self.ref()
+ self.snapshots.append(
+ (ts, sizer.asized(obj, detail=self._resolution_level))
+ )
+ if obj is not None:
+ self.repr = safe_repr(obj, clip=128)
+
+ def get_max_size(self) -> int:
+ """
+ Get the maximum of all sampled sizes.
+ """
+ return max([s.size for (_, s) in self.snapshots])
+
+ def get_size_at_time(self, timestamp: float) -> int:
+ """
+ Get the size of the object at a specific time (snapshot).
+ If the object was not alive/sized at that instant, return 0.
+ """
+ size = 0
+ for (t, s) in self.snapshots:
+ if t == timestamp:
+ size = s.size
+ return size
+
+ def set_resolution_level(self, resolution_level: int) -> None:
+ """
+ Set resolution level to a new value. The next size estimation will
+ respect the new value. This is useful to set different levels for
+ different instances of tracked classes.
+ """
+ self._resolution_level = resolution_level
+
+ def finalize(self, ref: weakref_ref) -> None:
+ """
+ Mark the reference as dead and remember the timestamp. It would be
+ great if we could measure the pre-destruction size. Unfortunately, the
+ object is gone by the time the weakref callback is called. However,
+ weakref callbacks are useful to be informed when tracked objects died
+ without the need of destructors.
+
+ If the object is destroyed at the end of the program execution, it's
+ not possible to import modules anymore. Hence, the finalize callback
+ just does nothing (self.death stays None).
+ """
+ try:
+ self.death = _get_time()
+ if self.on_delete:
+ self.on_delete()
+ except Exception: # pragma: no cover
+ pass
+
+
+def track_object_creation(time_series: List[Tuple[float, int]]) -> None:
+ num_instances = time_series[-1][1] if time_series else 0
+ time_series.append((_get_time(), num_instances+1))
+
+
+def track_object_deletion(time_series: List[Tuple[float, int]]) -> None:
+ num_instances = time_series[-1][1]
+ time_series.append((_get_time(), num_instances-1))
+
+
+class PeriodicThread(Thread):
+ """
+ Thread object to take snapshots periodically.
+ """
+
+ def __init__(self, tracker: 'ClassTracker', interval: float, *args: Any,
+ **kwargs: Any):
+ """
+ Create thread with given interval and associated with the given
+ tracker.
+ """
+ self.interval = interval
+ self.tracker = tracker
+ self.stop = False
+ super(PeriodicThread, self).__init__(*args, **kwargs)
+
+ def run(self) -> None:
+ """
+ Loop until a stop signal is set.
+ """
+ self.stop = False
+ while not self.stop:
+ self.tracker.create_snapshot()
+ sleep(self.interval)
+
+
+class Snapshot(object):
+ """Sample sizes of objects and the process at an instant."""
+
+ def __init__(self, timestamp: float, description: str = '') -> None:
+ """Initialize process-wide size information."""
+ self.tracked_total = 0
+ self.asizeof_total = 0
+ self.overhead = 0
+ self.timestamp = timestamp
+ self.system_total = pympler.process.ProcessMemoryInfo()
+ self.desc = description
+ self.classes = None # type: Optional[Dict[str, Dict[str, Any]]]
+
+ @property
+ def total(self) -> int:
+ """
+ Return the total (virtual) size of the process in bytes. If process
+ information is not available, get the best number available, even if it
+ is a poor approximation of reality.
+ """
+ if self.system_total.available:
+ return self.system_total.vsz
+ elif self.asizeof_total: # pragma: no cover
+ return self.asizeof_total
+ else: # pragma: no cover
+ return self.tracked_total
+
+ @property
+ def label(self) -> str:
+ """Return timestamped label for this snapshot, or a raw timestamp."""
+ if not self.desc:
+ return "%.3fs" % self.timestamp
+ return "%s (%.3fs)" % (self.desc, self.timestamp)
+
+
+class ClassTracker(object):
+
+ def __init__(self, stream: Optional[IO] = None):
+ """
+ Creates a new `ClassTracker` object.
+
+ :param stream: Output stream to use when printing statistics via
+ ``stats``.
+ """
+ # Dictionaries of TrackedObject objects associated with the actual
+ # objects that are tracked. 'index' uses the class name as the key and
+ # associates a list of tracked objects. It contains all TrackedObject
+ # instances, including those of dead objects.
+ self.index = defaultdict(list) # type: Dict[str, List[TrackedObject]]
+
+ # 'objects' uses the id (address) as the key and associates the tracked
+ # object with it. TrackedObject's referring to dead objects are
+ # replaced lazily, i.e. when the id is recycled by another tracked
+ # object.
+ self.objects = {} # type: Dict[int, Any]
+
+ # List of `Snapshot` objects.
+ self.snapshots = [] # type: List[Snapshot]
+
+ # Time series of instance count for each tracked class.
+ self.history = defaultdict(list) \
+ # type: Dict[str, List[Tuple[float, int]]]
+
+ # Keep objects alive by holding a strong reference.
+ self._keepalive = [] # type: List[Any]
+
+ # Dictionary of class observers identified by classname.
+ self._observers = {} # type: Dict[type, _ClassObserver]
+
+ # Thread object responsible for background monitoring
+ self._periodic_thread = None # type: Optional[PeriodicThread]
+
+ self._stream = stream
+
+ @property
+ def stats(self) -> ConsoleStats:
+ """
+ Return a ``ConsoleStats`` instance initialized with the current state
+ of the class tracker.
+ """
+ return ConsoleStats(tracker=self, stream=self._stream)
+
+ def _tracker(self, _observer_: _ClassObserver, _self_: Any, *args: Any,
+ **kwds: Any) -> None:
+ """
+ Injected constructor for tracked classes.
+ Call the actual constructor of the object and track the object. Attach
+ to the object before calling the constructor to track the object with
+ the parameters of the most specialized class.
+ """
+ self.track_object(_self_,
+ name=_observer_.name,
+ resolution_level=_observer_.detail,
+ keep=_observer_.keep,
+ trace=_observer_.trace)
+ _observer_.init(_self_, *args, **kwds)
+
+ def _inject_constructor(self, cls: type, func: Callable, name: str,
+ resolution_level: int, keep: bool, trace: bool,
+ ) -> None:
+ """
+ Modifying Methods in Place - after the recipe 15.7 in the Python
+ Cookbook by Ken Seehof. The original constructors may be restored
+ later.
+ """
+ try:
+ constructor = cls.__init__ # type: ignore
+ except AttributeError:
+ def constructor(self: Any, *_args: Any, **_kwargs: Any) -> None:
+ pass
+
+ # Possible name clash between keyword arguments of the tracked class'
+ # constructor and the curried arguments of the injected constructor.
+ # Therefore, the additional argument has a 'magic' name to make it less
+ # likely that an argument name clash occurs.
+ observer = _ClassObserver(constructor,
+ name,
+ resolution_level,
+ keep,
+ trace)
+ self._observers[cls] = observer
+
+ def new_constructor(*args: Any, **kwargs: Any) -> None:
+ return func(observer, *args, **kwargs)
+
+ cls.__init__ = new_constructor # type: ignore
+
+ def _is_tracked(self, cls: type) -> bool:
+ """
+ Determine if the class is tracked.
+ """
+ return cls in self._observers
+
+ def _track_modify(self, cls: type, name: str, detail: int, keep: bool,
+ trace: bool) -> None:
+ """
+ Modify settings of a tracked class
+ """
+ self._observers[cls].modify(name, detail, keep, trace)
+
+ def _restore_constructor(self, cls: type) -> None:
+ """
+ Restore the original constructor, lose track of class.
+ """
+ cls.__init__ = self._observers[cls].init # type: ignore
+ del self._observers[cls]
+
+ def track_change(self, instance: Any, resolution_level: int = 0) -> None:
+ """
+ Change tracking options for the already tracked object 'instance'.
+ If instance is not tracked, a KeyError will be raised.
+ """
+ tobj = self.objects[id(instance)]
+ tobj.set_resolution_level(resolution_level)
+
+ def track_object(self, instance: Any, name: Optional[str] = None,
+ resolution_level: int = 0, keep: bool = False,
+ trace: bool = False) -> None:
+ """
+ Track object 'instance' and sample size and lifetime information. Not
+ all objects can be tracked; trackable objects are class instances and
+ other objects that can be weakly referenced. When an object cannot be
+ tracked, a `TypeError` is raised.
+
+ :param resolution_level: The recursion depth up to which referents are
+ sized individually. Resolution level 0 (default) treats the object
+ as an opaque entity, 1 sizes all direct referents individually, 2
+ also sizes the referents of the referents and so forth.
+ :param keep: Prevent the object's deletion by keeping a (strong)
+ reference to the object.
+ """
+
+ # Check if object is already tracked. This happens if track_object is
+ # called multiple times for the same object or if an object inherits
+ # from multiple tracked classes. In the latter case, the most
+ # specialized class wins. To detect id recycling, the weak reference
+ # is checked. If it is 'None' a tracked object is dead and another one
+ # takes the same 'id'.
+ if id(instance) in self.objects and \
+ self.objects[id(instance)].ref() is not None:
+ return
+
+ name = name if name else instance.__class__.__name__
+
+ track_object_creation(self.history[name])
+ on_delete = partial(track_object_deletion, self.history[name])
+
+ tobj = TrackedObject(instance,
+ name,
+ resolution_level=resolution_level,
+ trace=trace,
+ on_delete=on_delete)
+
+ self.index[name].append(tobj)
+ self.objects[id(instance)] = tobj
+
+ if keep:
+ self._keepalive.append(instance)
+
+ def track_class(self, cls: type, name: Optional[str] = None,
+ resolution_level: int = 0, keep: bool = False,
+ trace: bool = False) -> None:
+ """
+ Track all objects of the class `cls`. Objects of that type that already
+ exist are *not* tracked. If `track_class` is called for a class already
+ tracked, the tracking parameters are modified. Instantiation traces can
+ be generated by setting `trace` to True.
+ A constructor is injected to begin instance tracking on creation
+ of the object. The constructor calls `track_object` internally.
+
+ :param cls: class to be tracked, may be an old-style or a new-style
+ class
+ :param name: reference the class by a name, default is the
+ concatenation of module and class name
+ :param resolution_level: The recursion depth up to which referents are
+ sized individually. Resolution level 0 (default) treats the object
+ as an opaque entity, 1 sizes all direct referents individually, 2
+ also sizes the referents of the referents and so forth.
+ :param keep: Prevent the object's deletion by keeping a (strong)
+ reference to the object.
+ :param trace: Save instantiation stack trace for each instance
+ """
+ if not isclass(cls):
+ raise TypeError("only class objects can be tracked")
+ if name is None:
+ name = cls.__module__ + '.' + cls.__name__
+ if self._is_tracked(cls):
+ self._track_modify(cls, name, resolution_level, keep, trace)
+ else:
+ self._inject_constructor(cls, self._tracker, name,
+ resolution_level, keep, trace)
+
+ def detach_class(self, cls: type) -> None:
+ """
+ Stop tracking class 'cls'. Any new objects of that type are not
+ tracked anymore. Existing objects are still tracked.
+ """
+ self._restore_constructor(cls)
+
+ def detach_all_classes(self) -> None:
+ """
+ Detach from all tracked classes.
+ """
+ classes = list(self._observers.keys())
+ for cls in classes:
+ self.detach_class(cls)
+
+ def detach_all(self) -> None:
+ """
+ Detach from all tracked classes and objects.
+ Restore the original constructors and cleanse the tracking lists.
+ """
+ self.detach_all_classes()
+ self.objects.clear()
+ self.index.clear()
+ self._keepalive[:] = []
+
+ def clear(self) -> None:
+ """
+ Clear all gathered data and detach from all tracked objects/classes.
+ """
+ self.detach_all()
+ self.snapshots[:] = []
+
+ def close(self) -> None:
+ """
+ Detach from tracked classes by removing injected constructors. Makes it
+ possible to use ClassTracker in `contextlib.closing` to safely remove
+ profiling hooks when the tracker goes out of scope::
+
+ import contextlib
+ with contextlib.closing(ClassTracker()) as tracker:
+ tracker.track_class(Foo)
+
+ """
+ self.detach_all_classes()
+
+#
+# Background Monitoring
+#
+
+ def start_periodic_snapshots(self, interval: float = 1.0) -> None:
+ """
+ Start a thread which takes snapshots periodically. The `interval`
+ specifies the time in seconds the thread waits between taking
+ snapshots. The thread is started as a daemon allowing the program to
+ exit. If periodic snapshots are already active, the interval is
+ updated.
+ """
+ if not self._periodic_thread:
+ self._periodic_thread = PeriodicThread(self, interval,
+ name='BackgroundMonitor')
+ self._periodic_thread.setDaemon(True)
+ self._periodic_thread.start()
+ else:
+ self._periodic_thread.interval = interval
+
+ def stop_periodic_snapshots(self) -> None:
+ """
+ Post a stop signal to the thread that takes the periodic snapshots. The
+ function waits for the thread to terminate which can take some time
+ depending on the configured interval.
+ """
+ if self._periodic_thread and self._periodic_thread.is_alive():
+ self._periodic_thread.stop = True
+ self._periodic_thread.join()
+ self._periodic_thread = None
+
+#
+# Snapshots
+#
+
+ snapshot_lock = Lock()
+
+ def create_snapshot(self, description: str = '',
+ compute_total: bool = False) -> None:
+ """
+ Collect current per instance statistics and saves total amount of
+ memory associated with the Python process.
+
+ If `compute_total` is `True`, the total consumption of all objects
+ known to *asizeof* is computed. The latter might be very slow if many
+ objects are mapped into memory at the time the snapshot is taken.
+ Therefore, `compute_total` is set to `False` by default.
+
+ The overhead of the `ClassTracker` structure is also computed.
+
+ Snapshots can be taken asynchronously. The function is protected with a
+ lock to prevent race conditions.
+ """
+
+ try:
+ # TODO: It is not clear what happens when memory is allocated or
+ # released while this function is executed but it will likely lead
+ # to inconsistencies. Either pause all other threads or don't size
+ # individual objects in asynchronous mode.
+ self.snapshot_lock.acquire()
+
+ timestamp = _get_time()
+
+ sizer = asizeof.Asizer()
+ objs = [tobj.ref() for tobj in list(self.objects.values())]
+ sizer.exclude_refs(*objs)
+
+ # The objects need to be sized in a deterministic order. Sort the
+ # objects by its creation date which should at least work for
+ # non-parallel execution. The "proper" fix would be to handle
+ # shared data separately.
+ tracked_objects = list(self.objects.values())
+ tracked_objects.sort(key=lambda x: x.birth)
+ for tobj in tracked_objects:
+ tobj.track_size(timestamp, sizer)
+
+ snapshot = Snapshot(timestamp, str(description))
+ snapshot.tracked_total = sizer.total
+ if compute_total:
+ snapshot.asizeof_total = asizeof.asizeof(all=True, code=True)
+
+ # Compute overhead of all structures, use sizer to exclude tracked
+ # objects(!)
+ snapshot.overhead = 0
+ if snapshot.tracked_total:
+ snapshot.overhead = sizer.asizeof(self)
+ if snapshot.asizeof_total:
+ snapshot.asizeof_total -= snapshot.overhead
+
+ self.snapshots.append(snapshot)
+
+ finally:
+ self.snapshot_lock.release()
diff --git a/venv/lib/python3.9/site-packages/pympler/classtracker_stats.py b/venv/lib/python3.9/site-packages/pympler/classtracker_stats.py
new file mode 100644
index 00000000..191f1fba
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/classtracker_stats.py
@@ -0,0 +1,780 @@
+"""
+Provide saving, loading and presenting gathered `ClassTracker` statistics.
+"""
+
+from typing import (
+ Any, Dict, IO, Iterable, List, Optional, Tuple, TYPE_CHECKING, Union
+)
+
+import os
+import pickle
+import sys
+from copy import deepcopy
+from pympler.util.stringutils import trunc, pp, pp_timestamp
+
+from pympler.asizeof import Asized
+
+if TYPE_CHECKING:
+ from .classtracker import TrackedObject, ClassTracker, Snapshot
+
+
+__all__ = ["Stats", "ConsoleStats", "HtmlStats"]
+
+
+def _ref2key(ref: Asized) -> str:
+ return ref.name.split(':')[0]
+
+
+def _merge_asized(base: Asized, other: Asized, level: int = 0) -> None:
+ """
+ Merge **Asized** instances `base` and `other` into `base`.
+ """
+ base.size += other.size
+ base.flat += other.flat
+ if level > 0:
+ base.name = _ref2key(base)
+ # Add refs from other to base. Any new refs are appended.
+ base.refs = list(base.refs) # we may need to append items
+ refs = {}
+ for ref in base.refs:
+ refs[_ref2key(ref)] = ref
+ for ref in other.refs:
+ key = _ref2key(ref)
+ if key in refs:
+ _merge_asized(refs[key], ref, level=level + 1)
+ else:
+ # Don't modify existing Asized instances => deepcopy
+ base.refs.append(deepcopy(ref))
+ base.refs[-1].name = key
+
+
+def _merge_objects(tref: float, merged: Asized, obj: 'TrackedObject') -> None:
+ """
+ Merge the snapshot size information of multiple tracked objects. The
+ tracked object `obj` is scanned for size information at time `tref`.
+ The sizes are merged into **Asized** instance `merged`.
+ """
+ size = None
+ for (timestamp, tsize) in obj.snapshots:
+ if timestamp == tref:
+ size = tsize
+ if size:
+ _merge_asized(merged, size)
+
+
+def _format_trace(trace: List[Tuple]) -> str:
+ """
+ Convert the (stripped) stack-trace to a nice readable format. The stack
+ trace `trace` is a list of frame records as returned by
+ **inspect.stack** but without the frame objects.
+ Returns a string.
+ """
+ lines = []
+ for fname, lineno, func, src, _ in trace:
+ if src:
+ for line in src:
+ lines.append(' ' + line.strip() + '\n')
+ lines.append(' %s:%4d in %s\n' % (fname, lineno, func))
+ return ''.join(lines)
+
+
+class Stats(object):
+ """
+ Presents the memory statistics gathered by a `ClassTracker` based on user
+ preferences.
+ """
+
+ def __init__(self, tracker: 'Optional[ClassTracker]' = None,
+ filename: Optional[str] = None,
+ stream: Optional[IO] = None):
+ """
+ Initialize the data log structures either from a `ClassTracker`
+ instance (argument `tracker`) or a previously dumped file (argument
+ `filename`).
+
+ :param tracker: ClassTracker instance
+ :param filename: filename of previously dumped statistics
+ :param stream: where to print statistics, defaults to ``sys.stdout``
+ """
+ if stream:
+ self.stream = stream
+ else:
+ self.stream = sys.stdout
+ self.tracker = tracker
+ self.index = {} # type: Dict[str, List[TrackedObject]]
+ self.snapshots = [] # type: List[Snapshot]
+ if tracker:
+ self.index = tracker.index
+ self.snapshots = tracker.snapshots
+ self.history = tracker.history
+ self.sorted = [] # type: List[TrackedObject]
+ if filename:
+ self.load_stats(filename)
+
+ def load_stats(self, fdump: Union[str, IO[bytes]]) -> None:
+ """
+ Load the data from a dump file.
+ The argument `fdump` can be either a filename or an open file object
+ that requires read access.
+ """
+ if isinstance(fdump, str):
+ fdump = open(fdump, 'rb')
+ self.index = pickle.load(fdump)
+ self.snapshots = pickle.load(fdump)
+ self.sorted = []
+
+ def dump_stats(self, fdump: Union[str, IO[bytes]], close: bool = True
+ ) -> None:
+ """
+ Dump the logged data to a file.
+ The argument `file` can be either a filename or an open file object
+ that requires write access. `close` controls if the file is closed
+ before leaving this method (the default behaviour).
+ """
+ if self.tracker:
+ self.tracker.stop_periodic_snapshots()
+
+ if isinstance(fdump, str):
+ fdump = open(fdump, 'wb')
+ pickle.dump(self.index, fdump, protocol=pickle.HIGHEST_PROTOCOL)
+ pickle.dump(self.snapshots, fdump, protocol=pickle.HIGHEST_PROTOCOL)
+ if close:
+ fdump.close()
+
+ def _init_sort(self) -> None:
+ """
+ Prepare the data to be sorted.
+ If not yet sorted, import all tracked objects from the tracked index.
+ Extend the tracking information by implicit information to make
+ sorting easier (DSU pattern).
+ """
+ if not self.sorted:
+ # Identify the snapshot that tracked the largest amount of memory.
+ tmax = None
+ maxsize = 0
+ for snapshot in self.snapshots:
+ if snapshot.tracked_total > maxsize:
+ tmax = snapshot.timestamp
+ for key in list(self.index.keys()):
+ for tobj in self.index[key]:
+ tobj.classname = key # type: ignore
+ tobj.size = tobj.get_max_size() # type: ignore
+ tobj.tsize = tobj.get_size_at_time(tmax) # type: ignore
+ self.sorted.extend(self.index[key])
+
+ def sort_stats(self, *args: str) -> 'Stats':
+ """
+ Sort the tracked objects according to the supplied criteria. The
+ argument is a string identifying the basis of a sort (example: 'size'
+ or 'classname'). When more than one key is provided, then additional
+ keys are used as secondary criteria when there is equality in all keys
+ selected before them. For example, ``sort_stats('name', 'size')`` will
+ sort all the entries according to their class name, and resolve all
+ ties (identical class names) by sorting by size. The criteria are
+ fields in the tracked object instances. Results are stored in the
+ ``self.sorted`` list which is used by ``Stats.print_stats()`` and other
+ methods. The fields available for sorting are:
+
+ 'classname'
+ the name with which the class was registered
+ 'name'
+ the classname
+ 'birth'
+ creation timestamp
+ 'death'
+ destruction timestamp
+ 'size'
+ the maximum measured size of the object
+ 'tsize'
+ the measured size during the largest snapshot
+ 'repr'
+ string representation of the object
+
+ Note that sorts on size are in descending order (placing most memory
+ consuming items first), whereas name, repr, and creation time searches
+ are in ascending order (alphabetical).
+
+ The function returns self to allow calling functions on the result::
+
+ stats.sort_stats('size').reverse_order().print_stats()
+ """
+
+ criteria = ('classname', 'tsize', 'birth', 'death',
+ 'name', 'repr', 'size')
+
+ if not set(criteria).issuperset(set(args)):
+ raise ValueError("Invalid sort criteria")
+
+ if not args:
+ args = criteria
+
+ def args_to_tuple(obj: 'TrackedObject') -> Tuple[str, ...]:
+ keys: List[str] = []
+ for attr in args:
+ attribute = getattr(obj, attr, '')
+ if attr in ('tsize', 'size'):
+ attribute = -int(attribute)
+ keys.append(attribute)
+ return tuple(keys)
+
+ self._init_sort()
+ self.sorted.sort(key=args_to_tuple)
+
+ return self
+
+ def reverse_order(self) -> 'Stats':
+ """
+ Reverse the order of the tracked instance index `self.sorted`.
+ """
+ self._init_sort()
+ self.sorted.reverse()
+ return self
+
+ def annotate(self) -> None:
+ """
+ Annotate all snapshots with class-based summaries.
+ """
+ for snapshot in self.snapshots:
+ self.annotate_snapshot(snapshot)
+
+ def annotate_snapshot(self, snapshot: 'Snapshot'
+ ) -> Dict[str, Dict[str, Any]]:
+ """
+ Store additional statistical data in snapshot.
+ """
+ if snapshot.classes is not None:
+ return snapshot.classes
+
+ snapshot.classes = {}
+
+ for classname in list(self.index.keys()):
+ total = 0
+ active = 0
+ merged = Asized(0, 0)
+ for tobj in self.index[classname]:
+ _merge_objects(snapshot.timestamp, merged, tobj)
+ total += tobj.get_size_at_time(snapshot.timestamp)
+ if (tobj.birth < snapshot.timestamp and
+ (tobj.death is None or
+ tobj.death > snapshot.timestamp)):
+ active += 1
+
+ try:
+ pct = total * 100.0 / snapshot.total
+ except ZeroDivisionError: # pragma: no cover
+ pct = 0
+ try:
+ avg = total / active
+ except ZeroDivisionError:
+ avg = 0
+
+ snapshot.classes[classname] = dict(sum=total,
+ avg=avg,
+ pct=pct,
+ active=active)
+ snapshot.classes[classname]['merged'] = merged
+
+ return snapshot.classes
+
+ @property
+ def tracked_classes(self) -> List[str]:
+ """Return a list of all tracked classes occurring in any snapshot."""
+ return sorted(list(self.index.keys()))
+
+
+class ConsoleStats(Stats):
+ """
+ Presentation layer for `Stats` to be used in text-based consoles.
+ """
+
+ def _print_refs(self, refs: Iterable[Asized], total: int,
+ prefix: str = ' ', level: int = 1, minsize: int = 0,
+ minpct: float = 0.1) -> None:
+ """
+ Print individual referents recursively.
+ """
+ lrefs = list(refs)
+ lrefs.sort(key=lambda x: x.size)
+ lrefs.reverse()
+ for ref in lrefs:
+ if ref.size > minsize and (ref.size * 100.0 / total) > minpct:
+ self.stream.write('%-50s %-14s %3d%% [%d]\n' % (
+ trunc(prefix + str(ref.name), 50),
+ pp(ref.size),
+ int(ref.size * 100.0 / total),
+ level
+ ))
+ self._print_refs(ref.refs, total, prefix=prefix + ' ',
+ level=level + 1)
+
+ def print_object(self, tobj: 'TrackedObject') -> None:
+ """
+ Print the gathered information of object `tobj` in human-readable
+ format.
+ """
+ if tobj.death:
+ self.stream.write('%-32s ( free ) %-35s\n' % (
+ trunc(tobj.name, 32, left=True), trunc(tobj.repr, 35)))
+ else:
+ self.stream.write('%-32s 0x%08x %-35s\n' % (
+ trunc(tobj.name, 32, left=True),
+
+ tobj.id,
+ trunc(tobj.repr, 35)
+ ))
+ if tobj.trace:
+ self.stream.write(_format_trace(tobj.trace))
+ for (timestamp, size) in tobj.snapshots:
+ self.stream.write(' %-30s %s\n' % (
+ pp_timestamp(timestamp), pp(size.size)
+ ))
+ self._print_refs(size.refs, size.size)
+ if tobj.death is not None:
+ self.stream.write(' %-30s finalize\n' % (
+ pp_timestamp(tobj.death),
+ ))
+
+ def print_stats(self, clsname: Optional[str] = None, limit: float = 1.0
+ ) -> None:
+ """
+ Write tracked objects to stdout. The output can be filtered and
+ pruned. Only objects are printed whose classname contain the substring
+ supplied by the `clsname` argument. The output can be pruned by
+ passing a `limit` value.
+
+ :param clsname: Only print objects whose classname contain the given
+ substring.
+ :param limit: If `limit` is a float smaller than one, only the supplied
+ percentage of the total tracked data is printed. If `limit` is
+ bigger than one, this number of tracked objects are printed.
+ Tracked objects are first filtered, and then pruned (if specified).
+ """
+ if self.tracker:
+ self.tracker.stop_periodic_snapshots()
+
+ if not self.sorted:
+ self.sort_stats()
+
+ _sorted = self.sorted
+
+ if clsname:
+ _sorted = [
+ to for to in _sorted
+ if clsname in to.classname # type: ignore
+ ]
+
+ if limit < 1.0:
+ limit = max(1, int(len(self.sorted) * limit))
+ _sorted = _sorted[:int(limit)]
+
+ # Emit per-instance data
+ for tobj in _sorted:
+ self.print_object(tobj)
+
+ def print_summary(self) -> None:
+ """
+ Print per-class summary for each snapshot.
+ """
+ # Emit class summaries for each snapshot
+ classlist = self.tracked_classes
+
+ fobj = self.stream
+
+ fobj.write('---- SUMMARY ' + '-' * 66 + '\n')
+ for snapshot in self.snapshots:
+ classes = self.annotate_snapshot(snapshot)
+ fobj.write('%-35s %11s %12s %12s %5s\n' % (
+ trunc(snapshot.desc, 35),
+ 'active',
+ pp(snapshot.asizeof_total),
+ 'average',
+ 'pct'
+ ))
+ for classname in classlist:
+ info = classes[classname]
+ fobj.write(' %-33s %11d %12s %12s %4d%%\n' % (
+ trunc(classname, 33),
+ info['active'],
+ pp(info['sum']),
+ pp(info['avg']),
+ info['pct']
+ ))
+ fobj.write('-' * 79 + '\n')
+
+
+class HtmlStats(Stats):
+ """
+ Output the `ClassTracker` statistics as HTML pages and graphs.
+ """
+
+ style = """<style type="text/css">
+ table { width:100%; border:1px solid #000; border-spacing:0px; }
+ td, th { border:0px; }
+ div { width:200px; padding:10px; background-color:#FFEECC; }
+ #nb { border:0px; }
+ #tl { margin-top:5mm; margin-bottom:5mm; }
+ #p1 { padding-left: 5px; }
+ #p2 { padding-left: 50px; }
+ #p3 { padding-left: 100px; }
+ #p4 { padding-left: 150px; }
+ #p5 { padding-left: 200px; }
+ #p6 { padding-left: 210px; }
+ #p7 { padding-left: 220px; }
+ #hl { background-color:#FFFFCC; }
+ #r1 { background-color:#BBBBBB; }
+ #r2 { background-color:#CCCCCC; }
+ #r3 { background-color:#DDDDDD; }
+ #r4 { background-color:#EEEEEE; }
+ #r5,#r6,#r7 { background-color:#FFFFFF; }
+ #num { text-align:right; }
+ </style>
+ """
+
+ nopylab_msg = """<div color="#FFCCCC">Could not generate %s chart!
+ Install <a href="http://matplotlib.sourceforge.net/">Matplotlib</a>
+ to generate charts.</div>\n"""
+
+ chart_tag = '<img src="%s">\n'
+ header = "<html><head><title>%s</title>%s</head><body>\n"
+ tableheader = '<table border="1">\n'
+ tablefooter = '</table>\n'
+ footer = '</body></html>\n'
+
+ refrow = """<tr id="r%(level)d">
+ <td id="p%(level)d">%(name)s</td>
+ <td id="num">%(size)s</td>
+ <td id="num">%(pct)3.1f%%</td></tr>"""
+
+ def _print_refs(self, fobj: IO, refs: Iterable[Asized], total: int,
+ level: int = 1, minsize: int = 0, minpct: float = 0.1
+ ) -> None:
+ """
+ Print individual referents recursively.
+ """
+ lrefs = list(refs)
+ lrefs.sort(key=lambda x: x.size)
+ lrefs.reverse()
+ if level == 1:
+ fobj.write('<table>\n')
+ for ref in lrefs:
+ if ref.size > minsize and (ref.size * 100.0 / total) > minpct:
+ data = dict(level=level,
+ name=trunc(str(ref.name), 128),
+ size=pp(ref.size),
+ pct=ref.size * 100.0 / total)
+ fobj.write(self.refrow % data)
+ self._print_refs(fobj, ref.refs, total, level=level + 1)
+ if level == 1:
+ fobj.write("</table>\n")
+
+ class_summary = """<p>%(cnt)d instances of %(cls)s were registered. The
+ average size is %(avg)s, the minimal size is %(min)s, the maximum size
+ is %(max)s.</p>\n"""
+ class_snapshot = '''<h3>Snapshot: %(name)s, %(total)s occupied by instances
+ of class %(cls)s</h3>\n'''
+
+ def print_class_details(self, fname: str, classname: str) -> None:
+ """
+ Print detailed statistics and instances for the class `classname`. All
+ data will be written to the file `fname`.
+ """
+ fobj = open(fname, "w")
+ fobj.write(self.header % (classname, self.style))
+
+ fobj.write("<h1>%s</h1>\n" % (classname))
+
+ sizes = [tobj.get_max_size() for tobj in self.index[classname]]
+ total = 0
+ for s in sizes:
+ total += s
+ data = {'cnt': len(self.index[classname]), 'cls': classname}
+ data['avg'] = pp(total / len(sizes))
+ data['max'] = pp(max(sizes))
+ data['min'] = pp(min(sizes))
+ fobj.write(self.class_summary % data)
+
+ fobj.write(self.charts[classname])
+
+ fobj.write("<h2>Coalesced Referents per Snapshot</h2>\n")
+ for snapshot in self.snapshots:
+ if snapshot.classes and classname in snapshot.classes:
+ merged = snapshot.classes[classname]['merged']
+ fobj.write(self.class_snapshot % {
+ 'name': snapshot.desc,
+ 'cls': classname,
+ 'total': pp(merged.size),
+ })
+ if merged.refs:
+ self._print_refs(fobj, merged.refs, merged.size)
+ else:
+ fobj.write('<p>No per-referent sizes recorded.</p>\n')
+
+ fobj.write("<h2>Instances</h2>\n")
+ for tobj in self.index[classname]:
+ fobj.write('<table id="tl" width="100%" rules="rows">\n')
+ fobj.write('<tr><td id="hl" width="140px">Instance</td>' +
+ '<td id="hl">%s at 0x%08x</td></tr>\n' %
+ (tobj.name, tobj.id))
+ if tobj.repr:
+ fobj.write("<tr><td>Representation</td>" +
+ "<td>%s&nbsp;</td></tr>\n" % tobj.repr)
+ fobj.write("<tr><td>Lifetime</td><td>%s - %s</td></tr>\n" %
+ (pp_timestamp(tobj.birth), pp_timestamp(tobj.death)))
+ if tobj.trace:
+ trace = "<pre>%s</pre>" % (_format_trace(tobj.trace))
+ fobj.write("<tr><td>Instantiation</td><td>%s</td></tr>\n" %
+ trace)
+ for (timestamp, size) in tobj.snapshots:
+ fobj.write("<tr><td>%s</td>" % pp_timestamp(timestamp))
+ if not size.refs:
+ fobj.write("<td>%s</td></tr>\n" % pp(size.size))
+ else:
+ fobj.write("<td>%s" % pp(size.size))
+ self._print_refs(fobj, size.refs, size.size)
+ fobj.write("</td></tr>\n")
+ fobj.write("</table>\n")
+
+ fobj.write(self.footer)
+ fobj.close()
+
+ snapshot_cls_header = """<tr>
+ <th id="hl">Class</th>
+ <th id="hl" align="right">Instance #</th>
+ <th id="hl" align="right">Total</th>
+ <th id="hl" align="right">Average size</th>
+ <th id="hl" align="right">Share</th></tr>\n"""
+
+ snapshot_cls = """<tr>
+ <td>%(cls)s</td>
+ <td align="right">%(active)d</td>
+ <td align="right">%(sum)s</td>
+ <td align="right">%(avg)s</td>
+ <td align="right">%(pct)3.2f%%</td></tr>\n"""
+
+ snapshot_summary = """<p>Total virtual memory assigned to the program
+ at that time was %(sys)s, which includes %(overhead)s profiling
+ overhead. The ClassTracker tracked %(tracked)s in total. The measurable
+ objects including code objects but excluding overhead have a total size
+ of %(asizeof)s.</p>\n"""
+
+ def relative_path(self, filepath: str, basepath: Optional[str] = None
+ ) -> str:
+ """
+ Convert the filepath path to a relative path against basepath. By
+ default basepath is self.basedir.
+ """
+ if basepath is None:
+ basepath = self.basedir
+ if not basepath:
+ return filepath
+ if filepath.startswith(basepath):
+ filepath = filepath[len(basepath):]
+ if filepath and filepath[0] == os.sep:
+ filepath = filepath[1:]
+ return filepath
+
+ def create_title_page(self, filename: str, title: str = '') -> None:
+ """
+ Output the title page.
+ """
+ fobj = open(filename, "w")
+ fobj.write(self.header % (title, self.style))
+
+ fobj.write("<h1>%s</h1>\n" % title)
+ fobj.write("<h2>Memory distribution over time</h2>\n")
+ fobj.write(self.charts['snapshots'])
+
+ fobj.write("<h2>Snapshots statistics</h2>\n")
+ fobj.write('<table id="nb">\n')
+
+ classlist = list(self.index.keys())
+ classlist.sort()
+
+ for snapshot in self.snapshots:
+ fobj.write('<tr><td>\n')
+ fobj.write('<table id="tl" rules="rows">\n')
+ fobj.write("<h3>%s snapshot at %s</h3>\n" % (
+ snapshot.desc or 'Untitled',
+ pp_timestamp(snapshot.timestamp)
+ ))
+
+ data = {}
+ data['sys'] = pp(snapshot.system_total.vsz)
+ data['tracked'] = pp(snapshot.tracked_total)
+ data['asizeof'] = pp(snapshot.asizeof_total)
+ data['overhead'] = pp(getattr(snapshot, 'overhead', 0))
+
+ fobj.write(self.snapshot_summary % data)
+
+ if snapshot.tracked_total:
+ fobj.write(self.snapshot_cls_header)
+ for classname in classlist:
+ if snapshot.classes:
+ info = snapshot.classes[classname].copy()
+ path = self.relative_path(self.links[classname])
+ info['cls'] = '<a href="%s">%s</a>' % (path, classname)
+ info['sum'] = pp(info['sum'])
+ info['avg'] = pp(info['avg'])
+ fobj.write(self.snapshot_cls % info)
+ fobj.write('</table>')
+ fobj.write('</td><td>\n')
+ if snapshot.tracked_total:
+ fobj.write(self.charts[snapshot])
+ fobj.write('</td></tr>\n')
+
+ fobj.write("</table>\n")
+ fobj.write(self.footer)
+ fobj.close()
+
+ def create_lifetime_chart(self, classname: str, filename: str = '') -> str:
+ """
+ Create chart that depicts the lifetime of the instance registered with
+ `classname`. The output is written to `filename`.
+ """
+ try:
+ from pylab import figure, title, xlabel, ylabel, plot, savefig
+ except ImportError:
+ return HtmlStats.nopylab_msg % (classname + " lifetime")
+
+ cnt = []
+ for tobj in self.index[classname]:
+ cnt.append([tobj.birth, 1])
+ if tobj.death:
+ cnt.append([tobj.death, -1])
+ cnt.sort()
+ for i in range(1, len(cnt)):
+ cnt[i][1] += cnt[i - 1][1]
+
+ x = [t for [t, c] in cnt]
+ y = [c for [t, c] in cnt]
+
+ figure()
+ xlabel("Execution time [s]")
+ ylabel("Instance #")
+ title("%s instances" % classname)
+ plot(x, y, 'o')
+ savefig(filename)
+
+ return self.chart_tag % (os.path.basename(filename))
+
+ def create_snapshot_chart(self, filename: str = '') -> str:
+ """
+ Create chart that depicts the memory allocation over time apportioned
+ to the tracked classes.
+ """
+ try:
+ from pylab import (figure, title, xlabel, ylabel, plot, fill,
+ legend, savefig)
+ import matplotlib.mlab as mlab
+ except ImportError:
+ return self.nopylab_msg % ("memory allocation")
+
+ classlist = self.tracked_classes
+
+ times = [snapshot.timestamp for snapshot in self.snapshots]
+ base = [0.0] * len(self.snapshots)
+ poly_labels = []
+ polys = []
+ for cn in classlist:
+ pct = [snapshot.classes[cn]['pct'] for snapshot in self.snapshots
+ if snapshot.classes is not None]
+ if pct and max(pct) > 3.0:
+ sz = [float(fp.classes[cn]['sum']) / (1024 * 1024)
+ for fp in self.snapshots
+ if fp.classes is not None]
+ sz = [sx + sy for sx, sy in zip(base, sz)]
+ xp, yp = mlab.poly_between(times, base, sz)
+ polys.append(((xp, yp), {'label': cn}))
+ poly_labels.append(cn)
+ base = sz
+
+ figure()
+ title("Snapshot Memory")
+ xlabel("Execution Time [s]")
+ ylabel("Virtual Memory [MiB]")
+
+ sizes = [float(fp.asizeof_total) / (1024 * 1024)
+ for fp in self.snapshots]
+ plot(times, sizes, 'r--', label='Total')
+ sizes = [float(fp.tracked_total) / (1024 * 1024)
+ for fp in self.snapshots]
+ plot(times, sizes, 'b--', label='Tracked total')
+
+ for (args, kwds) in polys:
+ fill(*args, **kwds)
+ legend(loc=2)
+ savefig(filename)
+
+ return self.chart_tag % (self.relative_path(filename))
+
+ def create_pie_chart(self, snapshot: 'Snapshot', filename: str = '') -> str:
+ """
+ Create a pie chart that depicts the distribution of the allocated
+ memory for a given `snapshot`. The chart is saved to `filename`.
+ """
+ try:
+ from pylab import figure, title, pie, axes, savefig
+ from pylab import sum as pylab_sum
+ except ImportError:
+ return self.nopylab_msg % ("pie_chart")
+
+ # Don't bother illustrating a pie without pieces.
+ if not snapshot.tracked_total or snapshot.classes is None:
+ return ''
+
+ classlist = []
+ sizelist = []
+ for k, v in list(snapshot.classes.items()):
+ if v['pct'] > 3.0:
+ classlist.append(k)
+ sizelist.append(v['sum'])
+ sizelist.insert(0, snapshot.asizeof_total - pylab_sum(sizelist))
+ classlist.insert(0, 'Other')
+
+ title("Snapshot (%s) Memory Distribution" % (snapshot.desc))
+ figure(figsize=(8, 8))
+ axes([0.1, 0.1, 0.8, 0.8])
+ pie(sizelist, labels=classlist)
+ savefig(filename, dpi=50)
+
+ return self.chart_tag % (self.relative_path(filename))
+
+ def create_html(self, fname: str, title: str = "ClassTracker Statistics"
+ ) -> None:
+ """
+ Create HTML page `fname` and additional files in a directory derived
+ from `fname`.
+ """
+ # Create a folder to store the charts and additional HTML files.
+ self.basedir = os.path.dirname(os.path.abspath(fname))
+ self.filesdir = os.path.splitext(fname)[0] + '_files'
+ if not os.path.isdir(self.filesdir):
+ os.mkdir(self.filesdir)
+ self.filesdir = os.path.abspath(self.filesdir)
+ self.links = {} # type: Dict[str, str]
+
+ # Annotate all snapshots in advance
+ self.annotate()
+
+ # Create charts. The tags to show the images are returned and stored in
+ # the self.charts dictionary. This allows to return alternative text if
+ # the chart creation framework is not available.
+ self.charts = {} # type: Dict[Union[str, Snapshot], str]
+ fn = os.path.join(self.filesdir, 'timespace.png')
+ self.charts['snapshots'] = self.create_snapshot_chart(fn)
+
+ for fp, idx in zip(self.snapshots, list(range(len(self.snapshots)))):
+ fn = os.path.join(self.filesdir, 'fp%d.png' % (idx))
+ self.charts[fp] = self.create_pie_chart(fp, fn)
+
+ for cn in list(self.index.keys()):
+ fn = os.path.join(self.filesdir, cn.replace('.', '_') + '-lt.png')
+ self.charts[cn] = self.create_lifetime_chart(cn, fn)
+
+ # Create HTML pages first for each class and then the index page.
+ for cn in list(self.index.keys()):
+ fn = os.path.join(self.filesdir, cn.replace('.', '_') + '.html')
+ self.links[cn] = fn
+ self.print_class_details(fn, cn)
+
+ self.create_title_page(fname, title=title)
diff --git a/venv/lib/python3.9/site-packages/pympler/garbagegraph.py b/venv/lib/python3.9/site-packages/pympler/garbagegraph.py
new file mode 100644
index 00000000..fdd6cb74
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/garbagegraph.py
@@ -0,0 +1,80 @@
+
+from pympler.refgraph import ReferenceGraph
+from pympler.util.stringutils import trunc, pp
+
+import sys
+import gc
+
+__all__ = ['GarbageGraph', 'start_debug_garbage', 'end_debug_garbage']
+
+
+class GarbageGraph(ReferenceGraph):
+ """
+ The ``GarbageGraph`` is a ``ReferenceGraph`` that illustrates the objects
+ building reference cycles. The garbage collector is switched to debug mode
+ (all identified garbage is stored in `gc.garbage`) and the garbage
+ collector is invoked. The collected objects are then illustrated in a
+ directed graph.
+
+ Large graphs can be reduced to the actual cycles by passing ``reduce=True``
+ to the constructor.
+
+ It is recommended to disable the garbage collector when using the
+ ``GarbageGraph``.
+
+ >>> from pympler.garbagegraph import GarbageGraph, start_debug_garbage
+ >>> start_debug_garbage()
+ >>> l = []
+ >>> l.append(l)
+ >>> del l
+ >>> gb = GarbageGraph()
+ >>> gb.render('garbage.eps')
+ True
+ """
+ def __init__(self, reduce=False, collectable=True):
+ """
+ Initialize the GarbageGraph with the objects identified by the garbage
+ collector. If `collectable` is true, every reference cycle is recorded.
+ Otherwise only uncollectable objects are reported.
+ """
+ if collectable:
+ gc.set_debug(gc.DEBUG_SAVEALL)
+ else:
+ gc.set_debug(0)
+ gc.collect()
+
+ ReferenceGraph.__init__(self, gc.garbage, reduce)
+
+ def print_stats(self, stream=None):
+ """
+ Log annotated garbage objects to console or file.
+
+ :param stream: open file, uses sys.stdout if not given
+ """
+ if not stream: # pragma: no cover
+ stream = sys.stdout
+ self.metadata.sort(key=lambda x: -x.size)
+ stream.write('%-10s %8s %-12s %-46s\n' % ('id', 'size', 'type',
+ 'representation'))
+ for g in self.metadata:
+ stream.write('0x%08x %8d %-12s %-46s\n' % (g.id, g.size,
+ trunc(g.type, 12),
+ trunc(g.str, 46)))
+ stream.write('Garbage: %8d collected objects (%s in cycles): %12s\n' %
+ (self.count, self.num_in_cycles, pp(self.total_size)))
+
+
+def start_debug_garbage():
+ """
+ Turn off garbage collector to analyze *collectable* reference cycles.
+ """
+ gc.collect()
+ gc.disable()
+
+
+def end_debug_garbage():
+ """
+ Turn garbage collection on and disable debug output.
+ """
+ gc.set_debug(0)
+ gc.enable()
diff --git a/venv/lib/python3.9/site-packages/pympler/mprofile.py b/venv/lib/python3.9/site-packages/pympler/mprofile.py
new file mode 100644
index 00000000..936d6f76
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/mprofile.py
@@ -0,0 +1,97 @@
+"""
+Memory usage profiler for Python.
+
+"""
+import inspect
+import sys
+
+from pympler import muppy
+
+
+class MProfiler(object):
+ """A memory usage profiler class.
+
+ Memory data for each function is stored as a 3-element list in the
+ dictionary self.memories. The index is always a codepoint (see below).
+ The following are the definitions of the members:
+
+ [0] = The number of times this function was called
+ [1] = Minimum memory consumption when this function was measured.
+ [2] = Maximum memory consumption when this function was measured.
+
+ A codepoint is a list of 3-tuple of the type
+ (filename, functionname, linenumber). You can omit either element, which
+ will cause the profiling to be triggered if any of the other criteria
+ match. E.g.
+ - (None, foo, None), will profile any foo function,
+ - (bar, foo, None) will profile only the foo function from the bar file,
+ - (bar, foo, 17) will profile only line 17 of the foo function defined
+ in the file bar.
+
+ Additionally, you can define on what events you want the profiling be
+ triggered. Possible events are defined in
+ http://docs.python.org/lib/debugger-hooks.html.
+
+ If you do not define either codepoints or events, the profiler will
+ record the memory usage in at every codepoint and event.
+
+ """
+
+ def __init__(self, codepoints=None, events=None):
+ """
+ keyword arguments:
+ codepoints -- a list of points in code to monitor (defaults to all
+ codepoints)
+ events -- a list of events to monitor (defaults to all events)
+ """
+ self.memories = {}
+ self.codepoints = codepoints
+ self.events = events
+
+ def codepoint_included(self, codepoint):
+ """Check if codepoint matches any of the defined codepoints."""
+ if self.codepoints is None:
+ return True
+ for cp in self.codepoints:
+ mismatch = False
+ for i in range(len(cp)):
+ if (cp[i] is not None) and (cp[i] != codepoint[i]):
+ mismatch = True
+ break
+ if not mismatch:
+ return True
+ return False
+
+ def profile(self, frame, event, arg): # arg req to match signature
+ """Profiling method used to profile matching codepoints and events."""
+ if (self.events is None) or (event in self.events):
+ frame_info = inspect.getframeinfo(frame)
+ cp = (frame_info[0], frame_info[2], frame_info[1])
+ if self.codepoint_included(cp):
+ objects = muppy.get_objects()
+ size = muppy.get_size(objects)
+ if cp not in self.memories:
+ self.memories[cp] = [0, 0, 0, 0]
+ self.memories[cp][0] = 1
+ self.memories[cp][1] = size
+ self.memories[cp][2] = size
+ else:
+ self.memories[cp][0] += 1
+ if self.memories[cp][1] > size:
+ self.memories[cp][1] = size
+ if self.memories[cp][2] < size:
+ self.memories[cp][2] = size
+
+ def run(self, cmd):
+ sys.setprofile(self.profile)
+ try:
+ exec(cmd)
+ finally:
+ sys.setprofile(None)
+ return self
+
+
+if __name__ == "__main__":
+ p = MProfiler()
+ p.run("print('hello')")
+ print(p.memories)
diff --git a/venv/lib/python3.9/site-packages/pympler/muppy.py b/venv/lib/python3.9/site-packages/pympler/muppy.py
new file mode 100644
index 00000000..57000ff8
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/muppy.py
@@ -0,0 +1,275 @@
+from typing import Any, Callable, Dict, List, Optional, Set, Tuple
+
+import gc
+
+from pympler import summary
+from pympler.util import compat
+
+from inspect import isframe, stack
+
+from sys import getsizeof
+
+from pympler.asizeof import _Py_TPFLAGS_HAVE_GC
+
+
+def ignore_object(obj: Any) -> bool:
+ try:
+ return isframe(obj)
+ except ReferenceError:
+ return True
+
+
+def get_objects(remove_dups: bool = True, include_frames: bool = False
+ ) -> List[Any]:
+ """Return a list of all known objects excluding frame objects.
+
+ If (outer) frame objects shall be included, pass `include_frames=True`. In
+ order to prevent building reference cycles, the current frame object (of
+ the caller of get_objects) is ignored. This will not prevent creating
+ reference cycles if the object list is passed up the call-stack. Therefore,
+ frame objects are not included by default.
+
+ Keyword arguments:
+ remove_dups -- if True, all duplicate objects will be removed.
+ include_frames -- if True, includes frame objects.
+ """
+ gc.collect()
+
+ # Do not initialize local variables before calling gc.get_objects or those
+ # will be included in the list. Furthermore, ignore frame objects to
+ # prevent reference cycles.
+ tmp = gc.get_objects()
+ tmp = [o for o in tmp if not ignore_object(o)]
+
+ res = []
+ for o in tmp:
+ # gc.get_objects returns only container objects, but we also want
+ # the objects referenced by them
+ refs = get_referents(o)
+ for ref in refs:
+ if not gc.is_tracked(ref):
+ # we already got the container objects, now we only add
+ # non-container objects
+ res.append(ref)
+ res.extend(tmp)
+ if remove_dups:
+ res = _remove_duplicates(res)
+
+ if include_frames:
+ for sf in stack()[2:]:
+ res.append(sf[0])
+ return res
+
+
+def get_size(objects: List[Any]) -> int:
+ """Compute the total size of all elements in objects."""
+ res = 0
+ for o in objects:
+ try:
+ res += getsizeof(o)
+ except AttributeError:
+ print("IGNORING: type=%s; o=%s" % (str(type(o)), str(o)))
+ return res
+
+
+def get_diff(left: List[Any], right: List[Any]) -> Dict[str, List[Any]]:
+ """Get the difference of both lists.
+
+ The result will be a dict with this form {'+': [], '-': []}.
+ Items listed in '+' exist only in the right list,
+ items listed in '-' exist only in the left list.
+
+ """
+ res = {'+': [], '-': []} # type: Dict[str, List[Any]]
+
+ def partition(objects: List[Any]) -> Dict[type, List[Any]]:
+ """Partition the passed object list."""
+ res = {} # type: Dict[type, List[Any]]
+ for o in objects:
+ t = type(o)
+ if type(o) not in res:
+ res[t] = []
+ res[t].append(o)
+ return res
+
+ def get_not_included(foo: List[Any], bar: Dict[type, List[Any]]
+ ) -> List[Any]:
+ """Compare objects from foo with objects defined in the values of
+ bar (set of partitions).
+ Returns a list of all objects included in list, but not dict values.
+ """
+ res = [] # type: List[Any]
+ for o in foo:
+ if not compat.object_in_list(type(o), bar):
+ res.append(o)
+ elif not compat.object_in_list(o, bar[type(o)]):
+ res.append(o)
+ return res
+
+ # Create partitions of both lists. This will reduce the time required for
+ # the comparison
+ left_objects = partition(left)
+ right_objects = partition(right)
+ # and then do the diff
+ res['+'] = get_not_included(right, left_objects)
+ res['-'] = get_not_included(left, right_objects)
+ return res
+
+
+def sort(objects: List[Any]) -> List[Any]:
+ """Sort objects by size in bytes."""
+ objects = sorted(objects, key=getsizeof)
+ return objects
+
+
+def filter(objects: List[Any], Type: Optional[type] = None, min: int = -1,
+ max: int = -1) -> List[Any]:
+ """Filter objects.
+
+ The filter can be by type, minimum size, and/or maximum size.
+
+ Keyword arguments:
+ Type -- object type to filter by
+ min -- minimum object size
+ max -- maximum object size
+
+ """
+ res = [] # type: List[Any]
+ if min > max and max > -1:
+ raise ValueError("minimum must be smaller than maximum")
+
+ if Type is not None:
+ objects = [o for o in objects if isinstance(o, Type)]
+ if min > -1:
+ objects = [o for o in objects if getsizeof(o) > min]
+ if max > -1:
+ objects = [o for o in objects if getsizeof(o) < max]
+ return objects
+
+
+def get_referents(object: Any, level: int = 1) -> List[Any]:
+ """Get all referents of an object up to a certain level.
+
+ The referents will not be returned in a specific order and
+ will not contain duplicate objects. Duplicate objects will be removed.
+
+ Keyword arguments:
+ level -- level of indirection to which referents considered.
+
+ This function is recursive.
+
+ """
+ res = gc.get_referents(object)
+ level -= 1
+ if level > 0:
+ for o in res:
+ res.extend(get_referents(o, level))
+ res = _remove_duplicates(res)
+ return res
+
+
+def _get_usage(function: Callable, *args: Any) -> Optional[List]:
+ """Test if more memory is used after the function has been called.
+
+ The function will be invoked twice and only the second measurement will be
+ considered. Thus, memory used in initialisation (e.g. loading modules)
+ will not be included in the result. The goal is to identify memory leaks
+ caused by functions which use more and more memory.
+
+ Any arguments next to the function will be passed on to the function
+ on invocation.
+
+ Note that this function is currently experimental, because it is not
+ tested thoroughly and performs poorly.
+
+ """
+ # The usage of a function is calculated by creating one summary of all
+ # objects before the function is invoked and afterwards. These summaries
+ # are compared and the diff is returned.
+ # This function works in a 2-steps process. Before the actual function is
+ # invoked an empty dummy function is measurement to identify the overhead
+ # involved in the measuring process. This overhead then is subtracted from
+ # the measurement performed on the passed function. The result reflects the
+ # actual usage of a function call.
+ # Also, a measurement is performed twice, allowing the adjustment to
+ # initializing things, e.g. modules
+
+ res = None
+
+ def _get_summaries(function: Callable, *args: Any) -> Tuple:
+ """Get a 2-tuple containing one summary from before, and one summary
+ from after the function has been invoked.
+
+ """
+ s_before = summary.summarize(get_objects())
+ function(*args)
+ s_after = summary.summarize(get_objects())
+ return (s_before, s_after)
+
+ def _get_usage(function: Callable, *args: Any) -> List:
+ """Get the usage of a function call.
+ This function is to be used only internally. The 'real' get_usage
+ function is a wrapper around _get_usage, but the workload is done
+ here.
+
+ """
+ # init before calling
+ (s_before, s_after) = _get_summaries(function, *args)
+ # ignore all objects used for the measurement
+ ignore = []
+ if s_before != s_after:
+ ignore.append(s_before)
+ for row in s_before:
+ # ignore refs from summary and frame (loop)
+ if len(gc.get_referrers(row)) == 2:
+ ignore.append(row)
+ for item in row:
+ # ignore refs from summary and frame (loop)
+ if len(gc.get_referrers(item)) == 2:
+ ignore.append(item)
+ for o in ignore:
+ s_after = summary._subtract(s_after, o)
+ res = summary.get_diff(s_before, s_after)
+ return summary._sweep(res)
+
+ # calibrate; twice for initialization
+ def noop() -> None:
+ pass
+ offset = _get_usage(noop)
+ offset = _get_usage(noop)
+ # perform operation twice to handle objects possibly used in
+ # initialisation
+ tmp = _get_usage(function, *args)
+ tmp = _get_usage(function, *args)
+ tmp = summary.get_diff(offset, tmp)
+ tmp = summary._sweep(tmp)
+ if len(tmp) != 0:
+ res = tmp
+ return res
+
+
+def _is_containerobject(o: Any) -> bool:
+ """Is the passed object a container object."""
+ return bool(getattr(type(o), '__flags__', 0) & _Py_TPFLAGS_HAVE_GC)
+
+
+def _remove_duplicates(objects: List[Any]) -> List[Any]:
+ """Remove duplicate objects.
+
+ Inspired by http://www.peterbe.com/plog/uniqifiers-benchmark
+
+ """
+ seen = set() # type: Set[int]
+ result = []
+ for item in objects:
+ marker = id(item)
+ if marker in seen:
+ continue
+ seen.add(marker)
+ result.append(item)
+ return result
+
+
+def print_summary() -> None:
+ """Print a summary of all known objects."""
+ summary.print_(summary.summarize(get_objects()))
diff --git a/venv/lib/python3.9/site-packages/pympler/panels.py b/venv/lib/python3.9/site-packages/pympler/panels.py
new file mode 100644
index 00000000..3bb6217a
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/panels.py
@@ -0,0 +1,115 @@
+"""
+Expose a memory-profiling panel to the Django Debug toolbar.
+
+Shows process memory information (virtual size, resident set size) and model
+instances for the current request.
+
+Requires Django and Django Debug toolbar:
+
+https://github.com/jazzband/django-debug-toolbar
+
+Pympler adds a memory panel as a third party addon (not included in the
+django-debug-toolbar). It can be added by overriding the `DEBUG_TOOLBAR_PANELS`
+setting in the Django project settings::
+
+ DEBUG_TOOLBAR_PANELS = (
+ 'debug_toolbar.panels.timer.TimerDebugPanel',
+ 'pympler.panels.MemoryPanel',
+ )
+
+Pympler also needs to be added to the `INSTALLED_APPS` in the Django settings::
+
+ INSTALLED_APPS = INSTALLED_APPS + ('debug_toolbar', 'pympler')
+"""
+
+from pympler.classtracker import ClassTracker
+from pympler.process import ProcessMemoryInfo
+from pympler.util.stringutils import pp
+
+try:
+ from debug_toolbar.panels import Panel
+ from django.apps import apps
+ from django.template import Context, Template
+ from django.template.loader import render_to_string
+ from django.http.request import HttpRequest
+ from django.http.response import HttpResponse
+except ImportError:
+ class Panel(object): # type: ignore
+ pass
+
+ class Template(object): # type: ignore
+ pass
+
+ class Context(object): # type: ignore
+ pass
+
+ class HttpRequest(object): # type: ignore
+ pass
+
+ class HttpResponse(object): # type: ignore
+ pass
+
+
+class MemoryPanel(Panel):
+
+ name = 'pympler'
+
+ title = 'Memory'
+
+ template = 'memory_panel.html'
+
+ classes = [Context, Template]
+
+ def process_request(self, request: HttpRequest) -> HttpResponse:
+ self._tracker = ClassTracker()
+ for cls in apps.get_models() + self.classes:
+ self._tracker.track_class(cls)
+ self._tracker.create_snapshot('before')
+ self.record_stats({'before': ProcessMemoryInfo()})
+ response = super(MemoryPanel, self).process_request(request)
+ self.record_stats({'after': ProcessMemoryInfo()})
+ self._tracker.create_snapshot('after')
+ stats = self._tracker.stats
+ stats.annotate()
+ self.record_stats({'stats': stats})
+ return response
+
+ def enable_instrumentation(self) -> None:
+ self._tracker = ClassTracker()
+ for cls in apps.get_models() + self.classes:
+ self._tracker.track_class(cls)
+
+ def disable_instrumentation(self) -> None:
+ self._tracker.detach_all_classes()
+
+ def nav_subtitle(self) -> str:
+ context = self.get_stats()
+ before = context['before']
+ after = context['after']
+ rss = after.rss
+ delta = rss - before.rss
+ delta = ('(+%s)' % pp(delta)) if delta > 0 else ''
+ return "%s %s" % (pp(rss), delta)
+
+ @property
+ def content(self) -> str:
+ context = self.get_stats()
+ before = context['before']
+ after = context['after']
+ stats = context['stats']
+ rows = [('Resident set size', after.rss),
+ ('Virtual size', after.vsz),
+ ]
+ rows.extend(after - before)
+ rows = [(key, pp(value)) for key, value in rows]
+ rows.extend(after.os_specific)
+
+ classes = []
+ snapshot = stats.snapshots[-1]
+ for model in stats.tracked_classes:
+ history = [cnt for _, cnt in stats.history[model]]
+ size = snapshot.classes.get(model, {}).get('sum', 0)
+ if history and history[-1] > 0:
+ classes.append((model, history, pp(size)))
+ context.update({'rows': rows, 'classes': classes})
+ return render_to_string(self.template, context)
diff --git a/venv/lib/python3.9/site-packages/pympler/process.py b/venv/lib/python3.9/site-packages/pympler/process.py
new file mode 100644
index 00000000..b3a31528
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/process.py
@@ -0,0 +1,238 @@
+"""
+This module queries process memory allocation metrics from the operating
+system. It provides a platform independent layer to get the amount of virtual
+and physical memory allocated to the Python process.
+
+Different mechanisms are implemented: Either the process stat file is read
+(Linux), the `ps` command is executed (BSD/OSX/Solaris) or the resource module
+is queried (Unix fallback). On Windows try to use the win32 module if
+available. If all fails, return 0 for each attribute.
+
+Windows without the win32 module is not supported.
+
+ >>> from pympler.process import ProcessMemoryInfo
+ >>> pmi = ProcessMemoryInfo()
+ >>> print ("Virtual size [Byte]: " + str(pmi.vsz)) # doctest: +ELLIPSIS
+ Virtual size [Byte]: ...
+"""
+
+from typing import Iterable, List, Tuple
+
+import logging
+import threading
+
+from mmap import PAGESIZE # type: ignore
+from os import getpid
+from subprocess import Popen, PIPE
+
+from pympler.util.stringutils import pp
+
+
+class _ProcessMemoryInfo(object):
+ """Stores information about various process-level memory metrics. The
+ virtual size is stored in attribute `vsz`, the physical memory allocated to
+ the process in `rss`, and the number of (major) pagefaults in `pagefaults`.
+ On Linux, `data_segment`, `code_segment`, `shared_segment` and
+ `stack_segment` contain the number of Bytes allocated for the respective
+ segments. This is an abstract base class which needs to be overridden by
+ operating system specific implementations. This is done when importing the
+ module.
+ """
+
+ pagesize = PAGESIZE
+
+ def __init__(self) -> None:
+ self.pid = getpid()
+ self.rss = 0
+ self.vsz = 0
+ self.pagefaults = 0
+ self.os_specific = [] # type: List[Tuple[str, str]]
+
+ self.data_segment = 0
+ self.code_segment = 0
+ self.shared_segment = 0
+ self.stack_segment = 0
+
+ self.available = self.update()
+
+ def __repr__(self) -> str:
+ return "<%s vsz=%d rss=%d>" % (self.__class__.__name__,
+ self.vsz, self.rss)
+
+ def update(self) -> bool:
+ """
+ Refresh the information using platform instruments. Returns true if
+ this operation yields useful values on the current platform.
+ """
+ return False # pragma: no cover
+
+ def __sub__(self, other: '_ProcessMemoryInfo') -> Iterable[Tuple[str, int]]:
+ diff = [('Resident set size (delta)', self.rss - other.rss),
+ ('Virtual size (delta)', self.vsz - other.vsz),
+ ]
+ return diff
+
+
+ProcessMemoryInfo = _ProcessMemoryInfo # type: type
+
+
+def is_available() -> bool:
+ """
+ Convenience function to check if the current platform is supported by this
+ module.
+ """
+ return ProcessMemoryInfo().update()
+
+
+class _ProcessMemoryInfoPS(_ProcessMemoryInfo):
+
+ def update(self) -> bool:
+ """
+ Get virtual and resident size of current process via 'ps'.
+ This should work for MacOS X, Solaris, Linux. Returns true if it was
+ successful.
+ """
+ try:
+ p = Popen(['/bin/ps', '-p%s' % self.pid, '-o', 'rss,vsz'],
+ stdout=PIPE, stderr=PIPE)
+ except OSError: # pragma: no cover
+ pass
+ else:
+ s = p.communicate()[0].split()
+ if p.returncode == 0 and len(s) >= 2: # pragma: no branch
+ self.vsz = int(s[-1]) * 1024
+ self.rss = int(s[-2]) * 1024
+ return True
+ return False # pragma: no cover
+
+
+class _ProcessMemoryInfoProc(_ProcessMemoryInfo):
+
+ key_map = {
+ 'VmPeak': 'Peak virtual memory size',
+ 'VmSize': 'Virtual memory size',
+ 'VmLck': 'Locked memory size',
+ 'VmHWM': 'Peak resident set size',
+ 'VmRSS': 'Resident set size',
+ 'VmStk': 'Size of stack segment',
+ 'VmData': 'Size of data segment',
+ 'VmExe': 'Size of code segment',
+ 'VmLib': 'Shared library code size',
+ 'VmPTE': 'Page table entries size',
+ }
+
+ def update(self) -> bool:
+ """
+ Get virtual size of current process by reading the process' stat file.
+ This should work for Linux.
+ """
+ try:
+ stat = open('/proc/self/stat')
+ status = open('/proc/self/status')
+ except IOError: # pragma: no cover
+ return False
+ else:
+ stats = stat.read().split()
+ self.vsz = int(stats[22])
+ self.rss = int(stats[23]) * self.pagesize
+ self.pagefaults = int(stats[11])
+
+ for entry in status.readlines():
+ try:
+ key, value = entry.split(':', 1)
+ except ValueError:
+ continue
+ value = value.strip()
+
+ def size_in_bytes(x: str) -> int:
+ return int(x.split()[0]) * 1024
+
+ if key == 'VmData':
+ self.data_segment = size_in_bytes(value)
+ elif key == 'VmExe':
+ self.code_segment = size_in_bytes(value)
+ elif key == 'VmLib':
+ self.shared_segment = size_in_bytes(value)
+ elif key == 'VmStk':
+ self.stack_segment = size_in_bytes(value)
+ key = self.key_map.get(key, '')
+ if key:
+ self.os_specific.append((key, pp(size_in_bytes(value))))
+
+ stat.close()
+ status.close()
+ return True
+
+
+try:
+ from resource import getrusage, RUSAGE_SELF
+
+ class _ProcessMemoryInfoResource(_ProcessMemoryInfo):
+ def update(self) -> bool:
+ """
+ Get memory metrics of current process through `getrusage`. Only
+ available on Unix, on Linux most of the fields are not set,
+ and on BSD units are used that are not very helpful, see:
+
+ http://www.perlmonks.org/?node_id=626693
+
+ Furthermore, getrusage only provides accumulated statistics (e.g.
+ max rss vs current rss).
+ """
+ usage = getrusage(RUSAGE_SELF)
+ self.rss = usage.ru_maxrss * 1024
+ self.data_segment = usage.ru_idrss * 1024 # TODO: ticks?
+ self.shared_segment = usage.ru_ixrss * 1024 # TODO: ticks?
+ self.stack_segment = usage.ru_isrss * 1024 # TODO: ticks?
+ self.vsz = (self.data_segment + self.shared_segment +
+ self.stack_segment)
+
+ self.pagefaults = usage.ru_majflt
+ return self.rss != 0
+
+ if _ProcessMemoryInfoProc().update(): # pragma: no branch
+ ProcessMemoryInfo = _ProcessMemoryInfoProc
+ elif _ProcessMemoryInfoPS().update(): # pragma: no cover
+ ProcessMemoryInfo = _ProcessMemoryInfoPS
+ elif _ProcessMemoryInfoResource().update(): # pragma: no cover
+ ProcessMemoryInfo = _ProcessMemoryInfoResource
+
+except ImportError:
+ try:
+ # Requires pywin32
+ from win32process import GetProcessMemoryInfo
+ from win32api import GetCurrentProcess, GlobalMemoryStatusEx
+ except ImportError:
+ logging.warn("Please install pywin32 when using pympler on Windows.")
+ else:
+ class _ProcessMemoryInfoWin32(_ProcessMemoryInfo):
+ def update(self) -> bool:
+ process_handle = GetCurrentProcess()
+ meminfo = GetProcessMemoryInfo(process_handle)
+ memstatus = GlobalMemoryStatusEx()
+ self.vsz = (memstatus['TotalVirtual'] -
+ memstatus['AvailVirtual'])
+ self.rss = meminfo['WorkingSetSize']
+ self.pagefaults = meminfo['PageFaultCount']
+ return True
+
+ ProcessMemoryInfo = _ProcessMemoryInfoWin32
+
+
+class ThreadInfo(object):
+ """Collect information about an active thread."""
+
+ def __init__(self, thread: threading.Thread):
+ self.ident = thread.ident
+ self.name = thread.name
+ self.daemon = thread.daemon
+
+
+def get_current_threads() -> Iterable[ThreadInfo]:
+ """Get a list of `ThreadInfo` objects."""
+ return [ThreadInfo(thread) for thread in threading.enumerate()]
+
+
+def get_current_thread_id() -> int:
+ """Get the ID of the current thread."""
+ return threading.get_ident()
diff --git a/venv/lib/python3.9/site-packages/pympler/refbrowser.py b/venv/lib/python3.9/site-packages/pympler/refbrowser.py
new file mode 100644
index 00000000..ae8966fd
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/refbrowser.py
@@ -0,0 +1,451 @@
+"""Tree-like exploration of object referrers.
+
+This module provides a base implementation for tree-like referrers browsing.
+The two non-interactive classes ConsoleBrowser and FileBrowser output a tree
+to the console or a file. One graphical user interface for referrers browsing
+is provided as well. Further types can be subclassed.
+
+All types share a similar initialisation. That is, you provide a root object
+and may specify further settings such as the initial depth of the tree or an
+output function.
+Afterwards you can print the tree which will be arranged based on your previous
+settings.
+
+The interactive browser is based on a TreeWidget implemented in IDLE. It is
+available only if you have Tcl/Tk installed. If you try to instantiate the
+interactive browser without having Tkinter installed, an ImportError will be
+raised.
+
+"""
+import gc
+import inspect
+import sys
+
+from pympler import muppy
+from pympler import summary
+
+from pympler.util.compat import tkinter
+
+
+class _Node(object):
+ """A node as it is used in the tree structure.
+
+ Each node contains the object it represents and a list of children.
+ Children can be other nodes or arbitrary other objects. Any object
+ in a tree which is not of the type _Node is considered a leaf.
+
+ """
+ def __init__(self, o, str_func=None):
+ """You have to define the object this node represents. Also you can
+ define an output function which will be used to represent this node.
+ If no function is defined, the default str representation is used.
+
+ keyword arguments
+ str_func -- output function
+
+ """
+ self.o = o
+ self.children = []
+ self.str_func = str_func
+
+ def __str__(self):
+ """Override str(self.o) if str_func is defined."""
+ if self.str_func is not None:
+ return self.str_func(self.o)
+ else:
+ return str(self.o)
+
+
+class RefBrowser(object):
+ """Base class to other RefBrowser implementations.
+
+ This base class provides means to extract a tree from a given root object
+ and holds information on already known objects (to avoid repetition
+ if requested).
+
+ """
+
+ def __init__(self, rootobject, maxdepth=3, str_func=summary._repr,
+ repeat=True, stream=None):
+ """You have to provide the root object used in the refbrowser.
+
+ keyword arguments
+ maxdepth -- maximum depth of the initial tree
+ str_func -- function used when calling str(node)
+ repeat -- should nodes appear repeatedly in the tree, or should be
+ referred to existing nodes
+ stream -- output stream (used in derived classes)
+
+ """
+ self.root = rootobject
+ self.maxdepth = maxdepth
+ self.str_func = str_func
+ self.repeat = repeat
+ self.stream = stream
+ # objects which should be ignored while building the tree
+ # e.g. the current frame
+ self.ignore = []
+ # set of object ids which are already included
+ self.already_included = set()
+ self.ignore.append(self.already_included)
+
+ def get_tree(self):
+ """Get a tree of referrers of the root object."""
+ self.ignore.append(inspect.currentframe())
+ return self._get_tree(self.root, self.maxdepth)
+
+ def _get_tree(self, root, maxdepth):
+ """Workhorse of the get_tree implementation.
+
+ This is a recursive method which is why we have a wrapper method.
+ root is the current root object of the tree which should be returned.
+ Note that root is not of the type _Node.
+ maxdepth defines how much further down the from the root the tree
+ should be build.
+
+ """
+ objects = gc.get_referrers(root)
+ res = _Node(root, self.str_func)
+ self.already_included.add(id(root))
+ if maxdepth == 0:
+ return res
+ self.ignore.append(inspect.currentframe())
+ self.ignore.append(objects)
+ for o in objects:
+ # Ignore dict of _Node and RefBrowser objects
+ if isinstance(o, dict):
+ if any(isinstance(ref, (_Node, RefBrowser))
+ for ref in gc.get_referrers(o)):
+ continue
+ _id = id(o)
+ if not self.repeat and (_id in self.already_included):
+ s = self.str_func(o)
+ res.children.append("%s (already included, id %s)" %
+ (s, _id))
+ continue
+ if (not isinstance(o, _Node)) and (o not in self.ignore):
+ res.children.append(self._get_tree(o, maxdepth - 1))
+ return res
+
+
+class StreamBrowser(RefBrowser):
+ """RefBrowser implementation which prints the tree to the console.
+
+ If you don't like the looks, you can change it a little bit.
+ The class attributes 'hline', 'vline', 'cross', and 'space' can be
+ modified to your needs.
+
+ """
+ hline = '-'
+ vline = '|'
+ cross = '+'
+ space = ' '
+
+ def print_tree(self, tree=None):
+ """ Print referrers tree to console.
+
+ keyword arguments
+ tree -- if not None, the passed tree will be printed. Otherwise it is
+ based on the rootobject.
+
+ """
+ if tree is None:
+ tree = self.get_tree()
+ self._print(tree, '', '')
+
+ def _print(self, tree, prefix, carryon):
+ """Compute and print a new line of the tree.
+
+ This is a recursive function.
+
+ arguments
+ tree -- tree to print
+ prefix -- prefix to the current line to print
+ carryon -- prefix which is used to carry on the vertical lines
+
+ """
+ level = prefix.count(self.cross) + prefix.count(self.vline)
+ len_children = 0
+ if isinstance(tree, _Node):
+ len_children = len(tree.children)
+
+ # add vertex
+ prefix += str(tree)
+ # and as many spaces as the vertex is long
+ carryon += self.space * len(str(tree))
+ if (level == self.maxdepth) or (not isinstance(tree, _Node)) or\
+ (len_children == 0):
+ self.stream.write(prefix + '\n')
+ return
+ else:
+ # add in between connections
+ prefix += self.hline
+ carryon += self.space
+ # if there is more than one branch, add a cross
+ if len(tree.children) > 1:
+ prefix += self.cross
+ carryon += self.vline
+ prefix += self.hline
+ carryon += self.space
+
+ if len_children > 0:
+ # print the first branch (on the same line)
+ self._print(tree.children[0], prefix, carryon)
+ for b in range(1, len_children):
+ # the carryon becomes the prefix for all following children
+ prefix = carryon[:-2] + self.cross + self.hline
+ # remove the vlines for any children of last branch
+ if b == (len_children - 1):
+ carryon = carryon[:-2] + 2 * self.space
+ self._print(tree.children[b], prefix, carryon)
+ # leave a free line before the next branch
+ if b == (len_children - 1):
+ if len(carryon.strip(' ')) == 0:
+ return
+ self.stream.write(carryon[:-2].rstrip() + '\n')
+
+
+class ConsoleBrowser(StreamBrowser):
+ """RefBrowser that prints to the console (stdout)."""
+
+ def __init__(self, *args, **kwargs):
+ super(ConsoleBrowser, self).__init__(*args, **kwargs)
+ if not self.stream:
+ self.stream = sys.stdout
+
+
+class FileBrowser(StreamBrowser):
+ """RefBrowser implementation which prints the tree to a file."""
+
+ def print_tree(self, filename, tree=None):
+ """ Print referrers tree to file (in text format).
+
+ keyword arguments
+ tree -- if not None, the passed tree will be printed.
+
+ """
+ old_stream = self.stream
+ self.stream = open(filename, 'w')
+ try:
+ super(FileBrowser, self).print_tree(tree=tree)
+ finally:
+ self.stream.close()
+ self.stream = old_stream
+
+
+# Code for interactive browser (GUI)
+# ==================================
+
+# The interactive browser requires Tkinter which is not always available. To
+# avoid an import error when loading the module, we encapsulate most of the
+# code in the following try-except-block. The InteractiveBrowser itself
+# remains outside this block. If you try to instantiate it without having
+# Tkinter installed, the import error will be raised.
+try:
+ if sys.version_info < (3, 5, 2):
+ from idlelib import TreeWidget as _TreeWidget
+ else:
+ from idlelib import tree as _TreeWidget
+
+ class _TreeNode(_TreeWidget.TreeNode):
+ """TreeNode used by the InteractiveBrowser.
+
+ Not to be confused with _Node. This one is used in the GUI
+ context.
+
+ """
+ def reload_referrers(self):
+ """Reload all referrers for this _TreeNode."""
+ self.item.node = self.item.reftree._get_tree(self.item.node.o, 1)
+ self.item._clear_children()
+ self.expand()
+ self.update()
+
+ def print_object(self):
+ """Print object which this _TreeNode represents to console."""
+ print(self.item.node.o)
+
+ def drawtext(self):
+ """Override drawtext from _TreeWidget.TreeNode.
+
+ This seems to be a good place to add the popup menu.
+
+ """
+ _TreeWidget.TreeNode.drawtext(self)
+ # create a menu
+ menu = tkinter.Menu(self.canvas, tearoff=0)
+ menu.add_command(label="reload referrers",
+ command=self.reload_referrers)
+ menu.add_command(label="print", command=self.print_object)
+ menu.add_separator()
+ menu.add_command(label="expand", command=self.expand)
+ menu.add_separator()
+ # the popup only disappears when to click on it
+ menu.add_command(label="Close Popup Menu")
+
+ def do_popup(event):
+ menu.post(event.x_root, event.y_root)
+
+ self.label.bind("<Button-3>", do_popup)
+ # override, i.e. disable the editing of items
+
+ # disable editing of TreeNodes
+ def edit(self, event=None):
+ pass # see comment above
+
+ def edit_finish(self, event=None):
+ pass # see comment above
+
+ def edit_cancel(self, event=None):
+ pass # see comment above
+
+ class _ReferrerTreeItem(_TreeWidget.TreeItem, tkinter.Label):
+ """Tree item wrapper around _Node object."""
+
+ def __init__(self, parentwindow, node, reftree): # constr calls
+ """You need to provide the parent window, the node this TreeItem
+ represents, as well as the tree (_Node) which the node
+ belongs to.
+
+ """
+ _TreeWidget.TreeItem.__init__(self)
+ tkinter.Label.__init__(self, parentwindow)
+ self.node = node
+ self.parentwindow = parentwindow
+ self.reftree = reftree
+
+ def _clear_children(self):
+ """Clear children list from any TreeNode instances.
+
+ Normally these objects are not required for memory profiling, as
+ they are part of the profiler.
+
+ """
+ new_children = []
+ for child in self.node.children:
+ if not isinstance(child, _TreeNode):
+ new_children.append(child)
+ self.node.children = new_children
+
+ def GetText(self):
+ return str(self.node)
+
+ def GetIconName(self):
+ """Different icon when object cannot be expanded, i.e. has no
+ referrers.
+
+ """
+ if not self.IsExpandable():
+ return "python"
+
+ def IsExpandable(self):
+ """An object is expandable when it is a node which has children and
+ is a container object.
+
+ """
+ if not isinstance(self.node, _Node):
+ return False
+ else:
+ if len(self.node.children) > 0:
+ return True
+ else:
+ return muppy._is_containerobject(self.node.o)
+
+ def GetSubList(self):
+ """This method is the point where further referrers are computed.
+
+ Thus, the computation is done on-demand and only when needed.
+
+ """
+ sublist = []
+
+ children = self.node.children
+ if (len(children) == 0) and\
+ (muppy._is_containerobject(self.node.o)):
+ self.node = self.reftree._get_tree(self.node.o, 1)
+ self._clear_children()
+ children = self.node.children
+
+ for child in children:
+ item = _ReferrerTreeItem(self.parentwindow, child,
+ self.reftree)
+ sublist.append(item)
+ return sublist
+
+except ImportError:
+ _TreeWidget = None
+
+
+def gui_default_str_function(o):
+ """Default str function for InteractiveBrowser."""
+ return summary._repr(o) + '(id=%s)' % id(o)
+
+
+class InteractiveBrowser(RefBrowser):
+ """Interactive referrers browser.
+
+ The interactive browser is based on a TreeWidget implemented in IDLE. It is
+ available only if you have Tcl/Tk installed. If you try to instantiate the
+ interactive browser without having Tkinter installed, an ImportError will
+ be raised.
+
+ """
+ def __init__(self, rootobject, maxdepth=3,
+ str_func=gui_default_str_function, repeat=True):
+ """You have to provide the root object used in the refbrowser.
+
+ keyword arguments
+ maxdepth -- maximum depth of the initial tree
+ str_func -- function used when calling str(node)
+ repeat -- should nodes appear repeatedly in the tree, or should be
+ referred to existing nodes
+
+ """
+ if tkinter is None:
+ raise ImportError(
+ "InteractiveBrowser requires Tkinter to be installed.")
+ RefBrowser.__init__(self, rootobject, maxdepth, str_func, repeat)
+
+ def main(self, standalone=False):
+ """Create interactive browser window.
+
+ keyword arguments
+ standalone -- Set to true, if the browser is not attached to other
+ windows
+
+ """
+ window = tkinter.Tk()
+ sc = _TreeWidget.ScrolledCanvas(window, bg="white",
+ highlightthickness=0, takefocus=1)
+ sc.frame.pack(expand=1, fill="both")
+ item = _ReferrerTreeItem(window, self.get_tree(), self)
+ node = _TreeNode(sc.canvas, None, item)
+ node.expand()
+ if standalone:
+ window.mainloop()
+
+
+# list to hold to referrers
+superlist = []
+root = "root"
+for i in range(3):
+ tmp = [root]
+ superlist.append(tmp)
+
+
+def foo(o):
+ return str(type(o))
+
+
+def print_sample():
+ cb = ConsoleBrowser(root, str_func=foo)
+ cb.print_tree()
+
+
+def write_sample():
+ fb = FileBrowser(root, str_func=foo)
+ fb.print_tree('sample.txt')
+
+
+if __name__ == "__main__":
+ write_sample()
diff --git a/venv/lib/python3.9/site-packages/pympler/refgraph.py b/venv/lib/python3.9/site-packages/pympler/refgraph.py
new file mode 100644
index 00000000..7f1f28bd
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/refgraph.py
@@ -0,0 +1,350 @@
+"""
+This module exposes utilities to illustrate objects and their references as
+(directed) graphs. The current implementation requires 'graphviz' to be
+installed.
+"""
+
+from pympler.asizeof import Asizer, named_refs
+from pympler.util.stringutils import safe_repr, trunc
+from gc import get_referents
+from subprocess import Popen, PIPE
+from copy import copy
+from sys import platform
+
+__all__ = ['ReferenceGraph']
+
+
+# Popen might lead to deadlocks when file descriptors are leaked to
+# sub-processes on Linux. On Windows, however, close_fds=True leads to
+# ValueError if stdin/stdout/stderr is piped:
+# http://code.google.com/p/pympler/issues/detail?id=28#c1
+popen_flags = {}
+if platform not in ['win32']: # pragma: no branch
+ popen_flags['close_fds'] = True
+
+
+class _MetaObject(object):
+ """
+ The _MetaObject stores meta-information, like a string representation,
+ corresponding to each object passed to a ReferenceGraph.
+ """
+ __slots__ = ('size', 'id', 'type', 'str', 'group', 'cycle')
+
+ def __init__(self):
+ self.cycle = False
+
+
+class _Edge(object):
+ """
+ Describes a reference from one object `src` to another object `dst`.
+ """
+ __slots__ = ('src', 'dst', 'label', 'group')
+
+ def __init__(self, src, dst, label):
+ self.src = src
+ self.dst = dst
+ self.label = label
+ self.group = None
+
+ def __repr__(self):
+ return "<%08x => %08x, '%s', %s>" % (self.src, self.dst, self.label,
+ self.group)
+
+ def __hash__(self):
+ return (self.src, self.dst, self.label).__hash__()
+
+ def __eq__(self, other):
+ return self.__hash__() == other.__hash__()
+
+
+class ReferenceGraph(object):
+ """
+ The ReferenceGraph illustrates the references between a collection of
+ objects by rendering a directed graph. That requires that 'graphviz' is
+ installed.
+
+ >>> from pympler.refgraph import ReferenceGraph
+ >>> a = 42
+ >>> b = 'spam'
+ >>> c = {a: b}
+ >>> gb = ReferenceGraph([a,b,c])
+ >>> gb.render('spam.eps')
+ True
+ """
+ def __init__(self, objects, reduce=False):
+ """
+ Initialize the ReferenceGraph with a collection of `objects`.
+ """
+ self.objects = list(objects)
+ self.count = len(self.objects)
+ self.num_in_cycles = 'N/A'
+ self.edges = None
+
+ if reduce:
+ self.num_in_cycles = self._reduce_to_cycles()
+ self._reduced = self # TODO: weakref?
+ else:
+ self._reduced = None
+
+ self._get_edges()
+ self._annotate_objects()
+
+ def _eliminate_leafs(self, graph):
+ """
+ Eliminate leaf objects - that are objects not referencing any other
+ objects in the list `graph`. Returns the list of objects without the
+ objects identified as leafs.
+ """
+ result = []
+ idset = set([id(x) for x in graph])
+ for n in graph:
+ refset = set([id(x) for x in get_referents(n)])
+ if refset.intersection(idset):
+ result.append(n)
+ return result
+
+ def _reduce_to_cycles(self):
+ """
+ Iteratively eliminate leafs to reduce the set of objects to only those
+ that build cycles. Return the number of objects involved in reference
+ cycles. If there are no cycles, `self.objects` will be an empty list
+ and this method returns 0.
+ """
+ cycles = self.objects[:]
+ cnt = 0
+ while cnt != len(cycles):
+ cnt = len(cycles)
+ cycles = self._eliminate_leafs(cycles)
+ self.objects = cycles
+ return len(self.objects)
+
+ def reduce_to_cycles(self):
+ """
+ Iteratively eliminate leafs to reduce the set of objects to only those
+ that build cycles. Return the reduced graph. If there are no cycles,
+ None is returned.
+ """
+ if not self._reduced:
+ reduced = copy(self)
+ reduced.objects = self.objects[:]
+ reduced.metadata = []
+ reduced.edges = []
+ self.num_in_cycles = reduced._reduce_to_cycles()
+ reduced.num_in_cycles = self.num_in_cycles
+ if self.num_in_cycles:
+ reduced._get_edges()
+ reduced._annotate_objects()
+ for meta in reduced.metadata:
+ meta.cycle = True
+ else:
+ reduced = None
+ self._reduced = reduced
+ return self._reduced
+
+ def _get_edges(self):
+ """
+ Compute the edges for the reference graph.
+ The function returns a set of tuples (id(a), id(b), ref) if a
+ references b with the referent 'ref'.
+ """
+ idset = set([id(x) for x in self.objects])
+ self.edges = set([])
+ for n in self.objects:
+ refset = set([id(x) for x in get_referents(n)])
+ for ref in refset.intersection(idset):
+ label = ''
+ members = None
+ if isinstance(n, dict):
+ members = n.items()
+ if not members:
+ members = named_refs(n)
+ for (k, v) in members:
+ if id(v) == ref:
+ label = k
+ break
+ self.edges.add(_Edge(id(n), ref, label))
+
+ def _annotate_groups(self):
+ """
+ Annotate the objects belonging to separate (non-connected) graphs with
+ individual indices.
+ """
+ g = {}
+ for x in self.metadata:
+ g[x.id] = x
+
+ idx = 0
+ for x in self.metadata:
+ if not hasattr(x, 'group'):
+ x.group = idx
+ idx += 1
+ neighbors = set()
+ for e in self.edges:
+ if e.src == x.id:
+ neighbors.add(e.dst)
+ if e.dst == x.id:
+ neighbors.add(e.src)
+ for nb in neighbors:
+ g[nb].group = min(x.group, getattr(g[nb], 'group', idx))
+
+ # Assign the edges to the respective groups. Both "ends" of the edge
+ # should share the same group so just use the first object's group.
+ for e in self.edges:
+ e.group = g[e.src].group
+
+ self._max_group = idx
+
+ def _filter_group(self, group):
+ """
+ Eliminate all objects but those which belong to `group`.
+ ``self.objects``, ``self.metadata`` and ``self.edges`` are modified.
+ Returns `True` if the group is non-empty. Otherwise returns `False`.
+ """
+ self.metadata = [x for x in self.metadata if x.group == group]
+ group_set = set([x.id for x in self.metadata])
+ self.objects = [obj for obj in self.objects if id(obj) in group_set]
+ self.count = len(self.metadata)
+ if self.metadata == []:
+ return False
+
+ self.edges = [e for e in self.edges if e.group == group]
+
+ del self._max_group
+
+ return True
+
+ def split(self):
+ """
+ Split the graph into sub-graphs. Only connected objects belong to the
+ same graph. `split` yields copies of the Graph object. Shallow copies
+ are used that only replicate the meta-information, but share the same
+ object list ``self.objects``.
+
+ >>> from pympler.refgraph import ReferenceGraph
+ >>> a = 42
+ >>> b = 'spam'
+ >>> c = {a: b}
+ >>> t = (1,2,3)
+ >>> rg = ReferenceGraph([a,b,c,t])
+ >>> for subgraph in rg.split():
+ ... print (subgraph.index)
+ 0
+ 1
+ """
+ self._annotate_groups()
+ index = 0
+
+ for group in range(self._max_group):
+ subgraph = copy(self)
+ subgraph.metadata = self.metadata[:]
+ subgraph.edges = self.edges.copy()
+
+ if subgraph._filter_group(group):
+ subgraph.total_size = sum([x.size for x in subgraph.metadata])
+ subgraph.index = index
+ index += 1
+ yield subgraph
+
+ def split_and_sort(self):
+ """
+ Split the graphs into sub graphs and return a list of all graphs sorted
+ by the number of nodes. The graph with most nodes is returned first.
+ """
+ graphs = list(self.split())
+ graphs.sort(key=lambda x: -len(x.metadata))
+ for index, graph in enumerate(graphs):
+ graph.index = index
+ return graphs
+
+ def _annotate_objects(self):
+ """
+ Extract meta-data describing the stored objects.
+ """
+ self.metadata = []
+ sizer = Asizer()
+ sizes = sizer.asizesof(*self.objects)
+ self.total_size = sizer.total
+ for obj, sz in zip(self.objects, sizes):
+ md = _MetaObject()
+ md.size = sz
+ md.id = id(obj)
+ try:
+ md.type = obj.__class__.__name__
+ except (AttributeError, ReferenceError): # pragma: no cover
+ md.type = type(obj).__name__
+ md.str = safe_repr(obj, clip=128)
+ self.metadata.append(md)
+
+ def _get_graphviz_data(self):
+ """
+ Emit a graph representing the connections between the objects described
+ within the metadata list. The text representation can be transformed to
+ a graph with graphviz. Returns a string.
+ """
+ s = []
+ header = '// Process this file with graphviz\n'
+ s.append(header)
+ s.append('digraph G {\n')
+ s.append(' node [shape=box];\n')
+ for md in self.metadata:
+ label = trunc(md.str, 48).replace('"', "'")
+ extra = ''
+ if md.type == 'instancemethod':
+ extra = ', color=red'
+ elif md.type == 'frame':
+ extra = ', color=orange'
+ s.append(' "X%s" [ label = "%s\\n%s" %s ];\n' %
+ (hex(md.id)[1:], label, md.type, extra))
+ for e in self.edges:
+ extra = ''
+ if e.label == '__dict__':
+ extra = ',weight=100'
+ s.append(' X%s -> X%s [label="%s"%s];\n' %
+ (hex(e.src)[1:], hex(e.dst)[1:], e.label, extra))
+
+ s.append('}\n')
+ return "".join(s)
+
+ def render(self, filename, cmd='dot', format='ps', unflatten=False):
+ """
+ Render the graph to `filename` using graphviz. The graphviz invocation
+ command may be overridden by specifying `cmd`. The `format` may be any
+ specifier recognized by the graph renderer ('-Txxx' command). The
+ graph can be preprocessed by the *unflatten* tool if the `unflatten`
+ parameter is True. If there are no objects to illustrate, the method
+ does not invoke graphviz and returns False. If the renderer returns
+ successfully (return code 0), True is returned.
+
+ An `OSError` is raised if the graphviz tool cannot be found.
+ """
+ if self.objects == []:
+ return False
+
+ data = self._get_graphviz_data()
+
+ options = ('-Nfontsize=10',
+ '-Efontsize=10',
+ '-Nstyle=filled',
+ '-Nfillcolor=#E5EDB8',
+ '-Ncolor=#CCCCCC')
+ cmdline = (cmd, '-T%s' % format, '-o', filename) + options
+
+ if unflatten:
+ p1 = Popen(('unflatten', '-l7'), stdin=PIPE, stdout=PIPE,
+ **popen_flags)
+ p2 = Popen(cmdline, stdin=p1.stdout, **popen_flags)
+ p1.communicate(data.encode())
+ p2.communicate()
+ return p2.returncode == 0
+ else:
+ p = Popen(cmdline, stdin=PIPE, **popen_flags)
+ p.communicate(data.encode())
+ return p.returncode == 0
+
+ def write_graph(self, filename):
+ """
+ Write raw graph data which can be post-processed using graphviz.
+ """
+ f = open(filename, 'w')
+ f.write(self._get_graphviz_data())
+ f.close()
diff --git a/venv/lib/python3.9/site-packages/pympler/static/jquery.sparkline.min.js b/venv/lib/python3.9/site-packages/pympler/static/jquery.sparkline.min.js
new file mode 100644
index 00000000..74187063
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/static/jquery.sparkline.min.js
@@ -0,0 +1,5 @@
+/* jquery.sparkline 2.1.1 - http://omnipotent.net/jquery.sparkline/
+** Licensed under the New BSD License - see above site for details */
+
+(function(a){typeof define=="function"&&define.amd?define(["jquery"],a):a(jQuery)})(function(a){"use strict";var b={},c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I=0;c=function(){return{common:{type:"line",lineColor:"#00f",fillColor:"#cdf",defaultPixelsPerValue:3,width:"auto",height:"auto",composite:!1,tagValuesAttribute:"values",tagOptionsPrefix:"spark",enableTagOptions:!1,enableHighlight:!0,highlightLighten:1.4,tooltipSkipNull:!0,tooltipPrefix:"",tooltipSuffix:"",disableHiddenCheck:!1,numberFormatter:!1,numberDigitGroupCount:3,numberDigitGroupSep:",",numberDecimalMark:".",disableTooltips:!1,disableInteraction:!1},line:{spotColor:"#f80",highlightSpotColor:"#5f5",highlightLineColor:"#f22",spotRadius:1.5,minSpotColor:"#f80",maxSpotColor:"#f80",lineWidth:1,normalRangeMin:undefined,normalRangeMax:undefined,normalRangeColor:"#ccc",drawNormalOnTop:!1,chartRangeMin:undefined,chartRangeMax:undefined,chartRangeMinX:undefined,chartRangeMaxX:undefined,tooltipFormat:new e('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')},bar:{barColor:"#3366cc",negBarColor:"#f44",stackedBarColor:["#3366cc","#dc3912","#ff9900","#109618","#66aa00","#dd4477","#0099c6","#990099"],zeroColor:undefined,nullColor:undefined,zeroAxis:!0,barWidth:4,barSpacing:1,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:!1,colorMap:undefined,tooltipFormat:new e('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')},tristate:{barWidth:4,barSpacing:1,posBarColor:"#6f6",negBarColor:"#f44",zeroBarColor:"#999",colorMap:{},tooltipFormat:new e('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),tooltipValueLookups:{map:{"-1":"Loss",0:"Draw",1:"Win"}}},discrete:{lineHeight:"auto",thresholdColor:undefined,thresholdValue:0,chartRangeMax:undefined,chartRangeMin:undefined,chartRangeClip:!1,tooltipFormat:new e("{{prefix}}{{value}}{{suffix}}")},bullet:{targetColor:"#f33",targetWidth:3,performanceColor:"#33f",rangeColors:["#d3dafe","#a8b6ff","#7f94ff"],base:undefined,tooltipFormat:new e("{{fieldkey:fields}} - {{value}}"),tooltipValueLookups:{fields:{r:"Range",p:"Performance",t:"Target"}}},pie:{offset:0,sliceColors:["#3366cc","#dc3912","#ff9900","#109618","#66aa00","#dd4477","#0099c6","#990099"],borderWidth:0,borderColor:"#000",tooltipFormat:new e('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')},box:{raw:!1,boxLineColor:"#000",boxFillColor:"#cdf",whiskerColor:"#000",outlierLineColor:"#333",outlierFillColor:"#fff",medianColor:"#f00",showOutliers:!0,outlierIQR:1.5,spotRadius:1.5,target:undefined,targetColor:"#4a2",chartRangeMax:undefined,chartRangeMin:undefined,tooltipFormat:new e("{{field:fields}}: {{value}}"),tooltipFormatFieldlistKey:"field",tooltipValueLookups:{fields:{lq:"Lower Quartile",med:"Median",uq:"Upper Quartile",lo:"Left Outlier",ro:"Right Outlier",lw:"Left Whisker",rw:"Right Whisker"}}}}},B='.jqstooltip { position: absolute;left: 0px;top: 0px;visibility: hidden;background: rgb(0, 0, 0) transparent;background-color: rgba(0,0,0,0.6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";color: white;font: 10px arial, san serif;text-align: left;white-space: nowrap;padding: 5px;border: 1px solid white;z-index: 10000;}.jqsfield { color: white;font: 10px arial, san serif;text-align: left;}',d=function(){var b,c;return b=function(){this.init.apply(this,arguments)},arguments.length>1?(arguments[0]?(b.prototype=a.extend(new arguments[0],arguments[arguments.length-1]),b._super=arguments[0].prototype):b.prototype=arguments[arguments.length-1],arguments.length>2&&(c=Array.prototype.slice.call(arguments,1,-1),c.unshift(b.prototype),a.extend.apply(a,c))):b.prototype=arguments[0],b.prototype.cls=b,b},a.SPFormatClass=e=d({fre:/\{\{([\w.]+?)(:(.+?))?\}\}/g,precre:/(\w+)\.(\d+)/,init:function(a,b){this.format=a,this.fclass=b},render:function(a,b,c){var d=this,e=a,f,g,h,i,j;return this.format.replace(this.fre,function(){var a;return g=arguments[1],h=arguments[3],f=d.precre.exec(g),f?(j=f[2],g=f[1]):j=!1,i=e[g],i===undefined?"":h&&b&&b[h]?(a=b[h],a.get?b[h].get(i)||i:b[h][i]||i):(k(i)&&(c.get("numberFormatter")?i=c.get("numberFormatter")(i):i=p(i,j,c.get("numberDigitGroupCount"),c.get("numberDigitGroupSep"),c.get("numberDecimalMark"))),i)})}}),a.spformat=function(a,b){return new e(a,b)},f=function(a,b,c){return a<b?b:a>c?c:a},g=function(a,b){var c;return b===2?(c=Math.floor(a.length/2),a.length%2?a[c]:(a[c-1]+a[c])/2):a.length%2?(c=(a.length*b+b)/4,c%1?(a[Math.floor(c)]+a[Math.floor(c)-1])/2:a[c-1]):(c=(a.length*b+2)/4,c%1?(a[Math.floor(c)]+a[Math.floor(c)-1])/2:a[c-1])},h=function(a){var b;switch(a){case"undefined":a=undefined;break;case"null":a=null;break;case"true":a=!0;break;case"false":a=!1;break;default:b=parseFloat(a),a==b&&(a=b)}return a},i=function(a){var b,c=[];for(b=a.length;b--;)c[b]=h(a[b]);return c},j=function(a,b){var c,d,e=[];for(c=0,d=a.length;c<d;c++)a[c]!==b&&e.push(a[c]);return e},k=function(a){return!isNaN(parseFloat(a))&&isFinite(a)},p=function(b,c,d,e,f){var g,h;b=(c===!1?parseFloat(b).toString():b.toFixed(c)).split(""),g=(g=a.inArray(".",b))<0?b.length:g,g<b.length&&(b[g]=f);for(h=g-d;h>0;h-=d)b.splice(h,0,e);return b.join("")},l=function(a,b,c){var d;for(d=b.length;d--;){if(c&&b[d]===null)continue;if(b[d]!==a)return!1}return!0},m=function(a){var b=0,c;for(c=a.length;c--;)b+=typeof a[c]=="number"?a[c]:0;return b},o=function(b){return a.isArray(b)?b:[b]},n=function(a){var b;document.createStyleSheet?document.createStyleSheet().cssText=a:(b=document.createElement("style"),b.type="text/css",document.getElementsByTagName("head")[0].appendChild(b),b[typeof document.body.style.WebkitAppearance=="string"?"innerText":"innerHTML"]=a)},a.fn.simpledraw=function(b,c,d,e){var f,g;if(d&&(f=this.data("_jqs_vcanvas")))return f;b===undefined&&(b=a(this).innerWidth()),c===undefined&&(c=a(this).innerHeight());if(a.fn.sparkline.hasCanvas)f=new F(b,c,this,e);else{if(!a.fn.sparkline.hasVML)return!1;f=new G(b,c,this)}return g=a(this).data("_jqs_mhandler"),g&&g.registerCanvas(f),f},a.fn.cleardraw=function(){var a=this.data("_jqs_vcanvas");a&&a.reset()},a.RangeMapClass=q=d({init:function(a){var b,c,d=[];for(b in a)a.hasOwnProperty(b)&&typeof b=="string"&&b.indexOf(":")>-1&&(c=b.split(":"),c[0]=c[0].length===0?-Infinity:parseFloat(c[0]),c[1]=c[1].length===0?Infinity:parseFloat(c[1]),c[2]=a[b],d.push(c));this.map=a,this.rangelist=d||!1},get:function(a){var b=this.rangelist,c,d,e;if((e=this.map[a])!==undefined)return e;if(b)for(c=b.length;c--;){d=b[c];if(d[0]<=a&&d[1]>=a)return d[2]}return undefined}}),a.range_map=function(a){return new q(a)},r=d({init:function(b,c){var d=a(b);this.$el=d,this.options=c,this.currentPageX=0,this.currentPageY=0,this.el=b,this.splist=[],this.tooltip=null,this.over=!1,this.displayTooltips=!c.get("disableTooltips"),this.highlightEnabled=!c.get("disableHighlight")},registerSparkline:function(a){this.splist.push(a),this.over&&this.updateDisplay()},registerCanvas:function(b){var c=a(b.canvas);this.canvas=b,this.$canvas=c,c.mouseenter(a.proxy(this.mouseenter,this)),c.mouseleave(a.proxy(this.mouseleave,this)),c.click(a.proxy(this.mouseclick,this))},reset:function(a){this.splist=[],this.tooltip&&a&&(this.tooltip.remove(),this.tooltip=undefined)},mouseclick:function(b){var c=a.Event("sparklineClick");c.originalEvent=b,c.sparklines=this.splist,this.$el.trigger(c)},mouseenter:function(b){a(document.body).unbind("mousemove.jqs"),a(document.body).bind("mousemove.jqs",a.proxy(this.mousemove,this)),this.over=!0,this.currentPageX=b.pageX,this.currentPageY=b.pageY,this.currentEl=b.target,!this.tooltip&&this.displayTooltips&&(this.tooltip=new s(this.options),this.tooltip.updatePosition(b.pageX,b.pageY)),this.updateDisplay()},mouseleave:function(){a(document.body).unbind("mousemove.jqs");var b=this.splist,c=b.length,d=!1,e,f;this.over=!1,this.currentEl=null,this.tooltip&&(this.tooltip.remove(),this.tooltip=null);for(f=0;f<c;f++)e=b[f],e.clearRegionHighlight()&&(d=!0);d&&this.canvas.render()},mousemove:function(a){this.currentPageX=a.pageX,this.currentPageY=a.pageY,this.currentEl=a.target,this.tooltip&&this.tooltip.updatePosition(a.pageX,a.pageY),this.updateDisplay()},updateDisplay:function(){var b=this.splist,c=b.length,d=!1,e=this.$canvas.offset(),f=this.currentPageX-e.left,g=this.currentPageY-e.top,h,i,j,k,l;if(!this.over)return;for(j=0;j<c;j++)i=b[j],k=i.setRegionHighlight(this.currentEl,f,g),k&&(d=!0);if(d){l=a.Event("sparklineRegionChange"),l.sparklines=this.splist,this.$el.trigger(l);if(this.tooltip){h="";for(j=0;j<c;j++)i=b[j],h+=i.getCurrentRegionTooltip();this.tooltip.setContent(h)}this.disableHighlight||this.canvas.render()}k===null&&this.mouseleave()}}),s=d({sizeStyle:"position: static !important;display: block !important;visibility: hidden !important;float: left !important;",init:function(b){var c=b.get("tooltipClassname","jqstooltip"),d=this.sizeStyle,e;this.container=b.get("tooltipContainer")||document.body,this.tooltipOffsetX=b.get("tooltipOffsetX",10),this.tooltipOffsetY=b.get("tooltipOffsetY",12),a("#jqssizetip").remove(),a("#jqstooltip").remove(),this.sizetip=a("<div/>",{id:"jqssizetip",style:d,"class":c}),this.tooltip=a("<div/>",{id:"jqstooltip","class":c}).appendTo(this.container),e=this.tooltip.offset(),this.offsetLeft=e.left,this.offsetTop=e.top,this.hidden=!0,a(window).unbind("resize.jqs scroll.jqs"),a(window).bind("resize.jqs scroll.jqs",a.proxy(this.updateWindowDims,this)),this.updateWindowDims()},updateWindowDims:function(){this.scrollTop=a(window).scrollTop(),this.scrollLeft=a(window).scrollLeft(),this.scrollRight=this.scrollLeft+a(window).width(),this.updatePosition()},getSize:function(a){this.sizetip.html(a).appendTo(this.container),this.width=this.sizetip.width()+1,this.height=this.sizetip.height(),this.sizetip.remove()},setContent:function(a){if(!a){this.tooltip.css("visibility","hidden"),this.hidden=!0;return}this.getSize(a),this.tooltip.html(a).css({width:this.width,height:this.height,visibility:"visible"}),this.hidden&&(this.hidden=!1,this.updatePosition())},updatePosition:function(a,b){if(a===undefined){if(this.mousex===undefined)return;a=this.mousex-this.offsetLeft,b=this.mousey-this.offsetTop}else this.mousex=a-=this.offsetLeft,this.mousey=b-=this.offsetTop;if(!this.height||!this.width||this.hidden)return;b-=this.height+this.tooltipOffsetY,a+=this.tooltipOffsetX,b<this.scrollTop&&(b=this.scrollTop),a<this.scrollLeft?a=this.scrollLeft:a+this.width>this.scrollRight&&(a=this.scrollRight-this.width),this.tooltip.css({left:a,top:b})},remove:function(){this.tooltip.remove(),this.sizetip.remove(),this.sizetip=this.tooltip=undefined,a(window).unbind("resize.jqs scroll.jqs")}}),C=function(){n(B)},a(C),H=[],a.fn.sparkline=function(b,c){return this.each(function(){var d=new a.fn.sparkline.options(this,c),e=a(this),f,g;f=function(){var c,f,g,h,i,j,k;if(b==="html"||b===undefined){k=this.getAttribute(d.get("tagValuesAttribute"));if(k===undefined||k===null)k=e.html();c=k.replace(/(^\s*<!--)|(-->\s*$)|\s+/g,"").split(",")}else c=b;f=d.get("width")==="auto"?c.length*d.get("defaultPixelsPerValue"):d.get("width");if(d.get("height")==="auto"){if(!d.get("composite")||!a.data(this,"_jqs_vcanvas"))h=document.createElement("span"),h.innerHTML="a",e.html(h),g=a(h).innerHeight()||a(h).height(),a(h).remove(),h=null}else g=d.get("height");d.get("disableInteraction")?i=!1:(i=a.data(this,"_jqs_mhandler"),i?d.get("composite")||i.reset():(i=new r(this,d),a.data(this,"_jqs_mhandler",i)));if(d.get("composite")&&!a.data(this,"_jqs_vcanvas")){a.data(this,"_jqs_errnotify")||(alert("Attempted to attach a composite sparkline to an element with no existing sparkline"),a.data(this,"_jqs_errnotify",!0));return}j=new(a.fn.sparkline[d.get("type")])(this,c,d,f,g),j.render(),i&&i.registerSparkline(j)};if(a(this).html()&&!d.get("disableHiddenCheck")&&a(this).is(":hidden")||a.fn.jquery<"1.3.0"&&a(this).parents().is(":hidden")||!a(this).parents("body").length){if(!d.get("composite")&&a.data(this,"_jqs_pending"))for(g=H.length;g;g--)H[g-1][0]==this&&H.splice(g-1,1);H.push([this,f]),a.data(this,"_jqs_pending",!0)}else f.call(this)})},a.fn.sparkline.defaults=c(),a.sparkline_display_visible=function(){var b,c,d,e=[];for(c=0,d=H.length;c<d;c++)b=H[c][0],a(b).is(":visible")&&!a(b).parents().is(":hidden")?(H[c][1].call(b),a.data(H[c][0],"_jqs_pending",!1),e.push(c)):!a(b).closest("html").length&&!a.data(b,"_jqs_pending")&&(a.data(H[c][0],"_jqs_pending",!1),e.push(c));for(c=e.length;c;c--)H.splice(e[c-1],1)},a.fn.sparkline.options=d({init:function(c,d){var e,f,g,h;this.userOptions=d=d||{},this.tag=c,this.tagValCache={},f=a.fn.sparkline.defaults,g=f.common,this.tagOptionsPrefix=d.enableTagOptions&&(d.tagOptionsPrefix||g.tagOptionsPrefix),h=this.getTagSetting("type"),h===b?e=f[d.type||g.type]:e=f[h],this.mergedOptions=a.extend({},g,e,d)},getTagSetting:function(a){var c=this.tagOptionsPrefix,d,e,f,g;if(c===!1||c===undefined)return b;if(this.tagValCache.hasOwnProperty(a))d=this.tagValCache.key;else{d=this.tag.getAttribute(c+a);if(d===undefined||d===null)d=b;else if(d.substr(0,1)==="["){d=d.substr(1,d.length-2).split(",");for(e=d.length;e--;)d[e]=h(d[e].replace(/(^\s*)|(\s*$)/g,""))}else if(d.substr(0,1)==="{"){f=d.substr(1,d.length-2).split(","),d={};for(e=f.length;e--;)g=f[e].split(":",2),d[g[0].replace(/(^\s*)|(\s*$)/g,"")]=h(g[1].replace(/(^\s*)|(\s*$)/g,""))}else d=h(d);this.tagValCache.key=d}return d},get:function(a,c){var d=this.getTagSetting(a),e;return d!==b?d:(e=this.mergedOptions[a])===undefined?c:e}}),a.fn.sparkline._base=d({disabled:!1,init:function(b,c,d,e,f){this.el=b,this.$el=a(b),this.values=c,this.options=d,this.width=e,this.height=f,this.currentRegion=undefined},initTarget:function(){var a=!this.options.get("disableInteraction");(this.target=this.$el.simpledraw(this.width,this.height,this.options.get("composite"),a))?(this.canvasWidth=this.target.pixelWidth,this.canvasHeight=this.target.pixelHeight):this.disabled=!0},render:function(){return this.disabled?(this.el.innerHTML="",!1):!0},getRegion:function(a,b){},setRegionHighlight:function(a,b,c){var d=this.currentRegion,e=!this.options.get("disableHighlight"),f;return b>this.canvasWidth||c>this.canvasHeight||b<0||c<0?null:(f=this.getRegion(a,b,c),d!==f?(d!==undefined&&e&&this.removeHighlight(),this.currentRegion=f,f!==undefined&&e&&this.renderHighlight(),!0):!1)},clearRegionHighlight:function(){return this.currentRegion!==undefined?(this.removeHighlight(),this.currentRegion=undefined,!0):!1},renderHighlight:function(){this.changeHighlight(!0)},removeHighlight:function(){this.changeHighlight(!1)},changeHighlight:function(a){},getCurrentRegionTooltip:function(){var b=this.options,c="",d=[],f,g,h,i,j,k,l,m,n,o,p,q,r,s;if(this.currentRegion===undefined)return"";f=this.getCurrentRegionFields(),p=b.get("tooltipFormatter");if(p)return p(this,b,f);b.get("tooltipChartTitle")&&(c+='<div class="jqs jqstitle">'+b.get("tooltipChartTitle")+"</div>\n"),g=this.options.get("tooltipFormat");if(!g)return"";a.isArray(g)||(g=[g]),a.isArray(f)||(f=[f]),l=this.options.get("tooltipFormatFieldlist"),m=this.options.get("tooltipFormatFieldlistKey");if(l&&m){n=[];for(k=f.length;k--;)o=f[k][m],(s=a.inArray(o,l))!=-1&&(n[s]=f[k]);f=n}h=g.length,r=f.length;for(k=0;k<h;k++){q=g[k],typeof q=="string"&&(q=new e(q)),i=q.fclass||"jqsfield";for(s=0;s<r;s++)if(!f[s].isNull||!b.get("tooltipSkipNull"))a.extend(f[s],{prefix:b.get("tooltipPrefix"),suffix:b.get("tooltipSuffix")}),j=q.render(f[s],b.get("tooltipValueLookups"),b),d.push('<div class="'+i+'">'+j+"</div>")}return d.length?c+d.join("\n"):""},getCurrentRegionFields:function(){},calcHighlightColor:function(a,b){var c=b.get("highlightColor"),d=b.get("highlightLighten"),e,g,h,i;if(c)return c;if(d){e=/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a)||/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(a);if(e){h=[],g=a.length===4?16:1;for(i=0;i<3;i++)h[i]=f(Math.round(parseInt(e[i+1],16)*g*d),0,255);return"rgb("+h.join(",")+")"}}return a}}),t={changeHighlight:function(b){var c=this.currentRegion,d=this.target,e=this.regionShapes[c],f;e&&(f=this.renderRegion(c,b),a.isArray(f)||a.isArray(e)?(d.replaceWithShapes(e,f),this.regionShapes[c]=a.map(f,function(a){return a.id})):(d.replaceWithShape(e,f),this.regionShapes[c]=f.id))},render:function(){var b=this.values,c=this.target,d=this.regionShapes,e,f,g,h;if(!this.cls._super.render.call(this))return;for(g=b.length;g--;){e=this.renderRegion(g);if(e)if(a.isArray(e)){f=[];for(h=e.length;h--;)e[h].append(),f.push(e[h].id);d[g]=f}else e.append(),d[g]=e.id;else d[g]=null}c.render()}},a.fn.sparkline.line=u=d(a.fn.sparkline._base,{type:"line",init:function(a,b,c,d,e){u._super.init.call(this,a,b,c,d,e),this.vertices=[],this.regionMap=[],this.xvalues=[],this.yvalues=[],this.yminmax=[],this.hightlightSpotId=null,this.lastShapeId=null,this.initTarget()},getRegion:function(a,b,c){var d,e=this.regionMap;for(d=e.length;d--;)if(e[d]!==null&&b>=e[d][0]&&b<=e[d][1])return e[d][2];return undefined},getCurrentRegionFields:function(){var a=this.currentRegion;return{isNull:this.yvalues[a]===null,x:this.xvalues[a],y:this.yvalues[a],color:this.options.get("lineColor"),fillColor:this.options.get("fillColor"),offset:a}},renderHighlight:function(){var a=this.currentRegion,b=this.target,c=this.vertices[a],d=this.options,e=d.get("spotRadius"),f=d.get("highlightSpotColor"),g=d.get("highlightLineColor"),h,i;if(!c)return;e&&f&&(h=b.drawCircle(c[0],c[1],e,undefined,f),this.highlightSpotId=h.id,b.insertAfterShape(this.lastShapeId,h)),g&&(i=b.drawLine(c[0],this.canvasTop,c[0],this.canvasTop+this.canvasHeight,g),this.highlightLineId=i.id,b.insertAfterShape(this.lastShapeId,i))},removeHighlight:function(){var a=this.target;this.highlightSpotId&&(a.removeShapeId(this.highlightSpotId),this.highlightSpotId=null),this.highlightLineId&&(a.removeShapeId(this.highlightLineId),this.highlightLineId=null)},scanValues:function(){var a=this.values,b=a.length,c=this.xvalues,d=this.yvalues,e=this.yminmax,f,g,h,i,j;for(f=0;f<b;f++)g=a[f],h=typeof a[f]=="string",i=typeof a[f]=="object"&&a[f]instanceof Array,j=h&&a[f].split(":"),h&&j.length===2?(c.push(Number(j[0])),d.push(Number(j[1])),e.push(Number(j[1]))):i?(c.push(g[0]),d.push(g[1]),e.push(g[1])):(c.push(f),a[f]===null||a[f]==="null"?d.push(null):(d.push(Number(g)),e.push(Number(g))));this.options.get("xvalues")&&(c=this.options.get("xvalues")),this.maxy=this.maxyorg=Math.max.apply(Math,e),this.miny=this.minyorg=Math.min.apply(Math,e),this.maxx=Math.max.apply(Math,c),this.minx=Math.min.apply(Math,c),this.xvalues=c,this.yvalues=d,this.yminmax=e},processRangeOptions:function(){var a=this.options,b=a.get("normalRangeMin"),c=a.get("normalRangeMax");b!==undefined&&(b<this.miny&&(this.miny=b),c>this.maxy&&(this.maxy=c)),a.get("chartRangeMin")!==undefined&&(a.get("chartRangeClip")||a.get("chartRangeMin")<this.miny)&&(this.miny=a.get("chartRangeMin")),a.get("chartRangeMax")!==undefined&&(a.get("chartRangeClip")||a.get("chartRangeMax")>this.maxy)&&(this.maxy=a.get("chartRangeMax")),a.get("chartRangeMinX")!==undefined&&(a.get("chartRangeClipX")||a.get("chartRangeMinX")<this.minx)&&(this.minx=a.get("chartRangeMinX")),a.get("chartRangeMaxX")!==undefined&&(a.get("chartRangeClipX")||a.get("chartRangeMaxX")>this.maxx)&&(this.maxx=a.get("chartRangeMaxX"))},drawNormalRange:function(a,b,c,d,e){var f=this.options.get("normalRangeMin"),g=this.options.get("normalRangeMax"),h=b+Math.round(c-c*((g-this.miny)/e)),i=Math.round(c*(g-f)/e);this.target.drawRect(a,h,d,i,undefined,this.options.get("normalRangeColor")).append()},render:function(){var b=this.options,c=this.target,d=this.canvasWidth,e=this.canvasHeight,f=this.vertices,g=b.get("spotRadius"),h=this.regionMap,i,j,k,l,m,n,o,p,r,s,t,v,w,x,y,z,A,B,C,D,E,F,G,H,I;if(!u._super.render.call(this))return;this.scanValues(),this.processRangeOptions(),G=this.xvalues,H=this.yvalues;if(!this.yminmax.length||this.yvalues.length<2)return;l=m=0,i=this.maxx-this.minx===0?1:this.maxx-this.minx,j=this.maxy-this.miny===0?1:this.maxy-this.miny,k=this.yvalues.length-1,g&&(d<g*4||e<g*4)&&(g=0);if(g){E=b.get("highlightSpotColor")&&!b.get("disableInteraction");if(E||b.get("minSpotColor")||b.get("spotColor")&&H[k]===this.miny)e-=Math.ceil(g);if(E||b.get("maxSpotColor")||b.get("spotColor")&&H[k]===this.maxy)e-=Math.ceil(g),l+=Math.ceil(g);if(E||(b.get("minSpotColor")||b.get("maxSpotColor"))&&(H[0]===this.miny||H[0]===this.maxy))m+=Math.ceil(g),d-=Math.ceil(g);if(E||b.get("spotColor")||b.get("minSpotColor")||b.get("maxSpotColor")&&(H[k]===this.miny||H[k]===this.maxy))d-=Math.ceil(g)}e--,b.get("normalRangeMin")!==undefined&&!b.get("drawNormalOnTop")&&this.drawNormalRange(m,l,e,d,j),o=[],p=[o],x=y=null,z=H.length;for(I=0;I<z;I++)r=G[I],t=G[I+1],s=H[I],v=m+Math.round((r-this.minx)*(d/i)),w=I<z-1?m+Math.round((t-this.minx)*(d/i)):d,y=v+(w-v)/2,h[I]=[x||0,y,I],x=y,s===null?I&&(H[I-1]!==null&&(o=[],p.push(o)),f.push(null)):(s<this.miny&&(s=this.miny),s>this.maxy&&(s=this.maxy),o.length||o.push([v,l+e]),n=[v,l+Math.round(e-e*((s-this.miny)/j))],o.push(n),f.push(n));A=[],B=[],C=p.length;for(I=0;I<C;I++)o=p[I],o.length&&(b.get("fillColor")&&(o.push([o[o.length-1][0],l+e]),B.push(o.slice(0)),o.pop()),o.length>2&&(o[0]=[o[0][0],o[1][1]]),A.push(o));C=B.length;for(I=0;I<C;I++)c.drawShape(B[I],b.get("fillColor"),b.get("fillColor")).append();b.get("normalRangeMin")!==undefined&&b.get("drawNormalOnTop")&&this.drawNormalRange(m,l,e,d,j),C=A.length;for(I=0;I<C;I++)c.drawShape(A[I],b.get("lineColor"),undefined,b.get("lineWidth")).append();if(g&&b.get("valueSpots")){D=b.get("valueSpots"),D.get===undefined&&(D=new q(D));for(I=0;I<z;I++)F=D.get(H[I]),F&&c.drawCircle(m+Math.round((G[I]-this.minx)*(d/i)),l+Math.round(e-e*((H[I]-this.miny)/j)),g,undefined,F).append()}g&&b.get("spotColor")&&H[k]!==null&&c.drawCircle(m+Math.round((G[G.length-1]-this.minx)*(d/i)),l+Math.round(e-e*((H[k]-this.miny)/j)),g,undefined,b.get("spotColor")).append(),this.maxy!==this.minyorg&&(g&&b.get("minSpotColor")&&(r=G[a.inArray(this.minyorg,H)],c.drawCircle(m+Math.round((r-this.minx)*(d/i)),l+Math.round(e-e*((this.minyorg-this.miny)/j)),g,undefined,b.get("minSpotColor")).append()),g&&b.get("maxSpotColor")&&(r=G[a.inArray(this.maxyorg,H)],c.drawCircle(m+Math.round((r-this.minx)*(d/i)),l+Math.round(e-e*((this.maxyorg-this.miny)/j)),g,undefined,b.get("maxSpotColor")).append())),this.lastShapeId=c.getLastShapeId(),this.canvasTop=l,c.render()}}),a.fn.sparkline.bar=v=d(a.fn.sparkline._base,t,{type:"bar",init:function(b,c,d,e,g){var k=parseInt(d.get("barWidth"),10),l=parseInt(d.get("barSpacing"),10),m=d.get("chartRangeMin"),n=d.get("chartRangeMax"),o=d.get("chartRangeClip"),p=Infinity,r=-Infinity,s,t,u,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P;v._super.init.call(this,b,c,d,e,g);for(y=0,z=c.length;y<z;y++){M=c[y],s=typeof M=="string"&&M.indexOf(":")>-1;if(s||a.isArray(M))H=!0,s&&(M=c[y]=i(M.split(":"))),M=j(M,null),t=Math.min.apply(Math,M),u=Math.max.apply(Math,M),t<p&&(p=t),u>r&&(r=u)}this.stacked=H,this.regionShapes={},this.barWidth=k,this.barSpacing=l,this.totalBarWidth=k+l,this.width=e=c.length*k+(c.length-1)*l,this.initTarget(),o&&(F=m===undefined?-Infinity:m,G=n===undefined?Infinity:n),x=[],w=H?[]:x;var Q=[],R=[];for(y=0,z=c.length;y<z;y++)if(H){I=c[y],c[y]=L=[],Q[y]=0,w[y]=R[y]=0;for(J=0,K=I.length;J<K;J++)M=L[J]=o?f(I[J],F,G):I[J],M!==null&&(M>0&&(Q[y]+=M),p<0&&r>0?M<0?R[y]+=Math.abs(M):w[y]+=M:w[y]+=Math.abs(M-(M<0?r:p)),x.push(M))}else M=o?f(c[y],F,G):c[y],M=c[y]=h(M),M!==null&&x.push(M);this.max=E=Math.max.apply(Math,x),this.min=D=Math.min.apply(Math,x),this.stackMax=r=H?Math.max.apply(Math,Q):E,this.stackMin=p=H?Math.min.apply(Math,x):D,d.get("chartRangeMin")!==undefined&&(d.get("chartRangeClip")||d.get("chartRangeMin")<D)&&(D=d.get("chartRangeMin")),d.get("chartRangeMax")!==undefined&&(d.get("chartRangeClip")||d.get("chartRangeMax")>E)&&(E=d.get("chartRangeMax")),this.zeroAxis=B=d.get("zeroAxis",!0),D<=0&&E>=0&&B?C=0:B==0?C=D:D>0?C=D:C=E,this.xaxisOffset=C,A=H?Math.max.apply(Math,w)+Math.max.apply(Math,R):E-D,this.canvasHeightEf=B&&D<0?this.canvasHeight-2:this.canvasHeight-1,D<C?(O=H&&E>=0?r:E,N=(O-C)/A*this.canvasHeight,N!==Math.ceil(N)&&(this.canvasHeightEf-=2,N=Math.ceil(N))):N=this.canvasHeight,this.yoffset=N,a.isArray(d.get("colorMap"))?(this.colorMapByIndex=d.get("colorMap"),this.colorMapByValue=null):(this.colorMapByIndex=null,this.colorMapByValue=d.get("colorMap"),this.colorMapByValue&&this.colorMapByValue.get===undefined&&(this.colorMapByValue=new q(this.colorMapByValue))),this.range=A},getRegion:function(a,b,c){var d=Math.floor(b/this.totalBarWidth);return d<0||d>=this.values.length?undefined:d},getCurrentRegionFields:function(){var a=this.currentRegion,b=o(this.values[a]),c=[],d,e;for(e=b.length;e--;)d=b[e],c.push({isNull:d===null,value:d,color:this.calcColor(e,d,a),offset:a});return c},calcColor:function(b,c,d){var e=this.colorMapByIndex,f=this.colorMapByValue,g=this.options,h,i;return this.stacked?h=g.get("stackedBarColor"):h=c<0?g.get("negBarColor"):g.get("barColor"),c===0&&g.get("zeroColor")!==undefined&&(h=g.get("zeroColor")),f&&(i=f.get(c))?h=i:e&&e.length>d&&(h=e[d]),a.isArray(h)?h[b%h.length]:h},renderRegion:function(b,c){var d=this.values[b],e=this.options,f=this.xaxisOffset,g=[],h=this.range,i=this.stacked,j=this.target,k=b*this.totalBarWidth,m=this.canvasHeightEf,n=this.yoffset,o,p,q,r,s,t,u,v,w,x;d=a.isArray(d)?d:[d],u=d.length,v=d[0],r=l(null,d),x=l(f,d,!0);if(r)return e.get("nullColor")?(q=c?e.get("nullColor"):this.calcHighlightColor(e.get("nullColor"),e),o=n>0?n-1:n,j.drawRect(k,o,this.barWidth-1,0,q,q)):undefined;s=n;for(t=0;t<u;t++){v=d[t];if(i&&v===f){if(!x||w)continue;w=!0}h>0?p=Math.floor(m*(Math.abs(v-f)/h))+1:p=1,v<f||v===f&&n===0?(o=s,s+=p):(o=n-p,n-=p),q=this.calcColor(t,v,b),c&&(q=this.calcHighlightColor(q,e)),g.push(j.drawRect(k,o,this.barWidth-1,p-1,q,q))}return g.length===1?g[0]:g}}),a.fn.sparkline.tristate=w=d(a.fn.sparkline._base,t,{type:"tristate",init:function(b,c,d,e,f){var g=parseInt(d.get("barWidth"),10),h=parseInt(d.get("barSpacing"),10);w._super.init.call(this,b,c,d,e,f),this.regionShapes={},this.barWidth=g,this.barSpacing=h,this.totalBarWidth=g+h,this.values=a.map(c,Number),this.width=e=c.length*g+(c.length-1)*h,a.isArray(d.get("colorMap"))?(this.colorMapByIndex=d.get("colorMap"),this.colorMapByValue=null):(this.colorMapByIndex=null,this.colorMapByValue=d.get("colorMap"),this.colorMapByValue&&this.colorMapByValue.get===undefined&&(this.colorMapByValue=new q(this.colorMapByValue))),this.initTarget()},getRegion:function(a,b,c){return Math.floor(b/this.totalBarWidth)},getCurrentRegionFields:function(){var a=this.currentRegion;return{isNull:this.values[a]===undefined,value:this.values[a],color:this.calcColor(this.values[a],a),offset:a}},calcColor:function(a,b){var c=this.values,d=this.options,e=this.colorMapByIndex,f=this.colorMapByValue,g,h;return f&&(h=f.get(a))?g=h:e&&e.length>b?g=e[b]:c[b]<0?g=d.get("negBarColor"):c[b]>0?g=d.get("posBarColor"):g=d.get("zeroBarColor"),g},renderRegion:function(a,b){var c=this.values,d=this.options,e=this.target,f,g,h,i,j,k;f=e.pixelHeight,h=Math.round(f/2),i=a*this.totalBarWidth,c[a]<0?(j=h,g=h-1):c[a]>0?(j=0,g=h-1):(j=h-1,g=2),k=this.calcColor(c[a],a);if(k===null)return;return b&&(k=this.calcHighlightColor(k,d)),e.drawRect(i,j,this.barWidth-1,g-1,k,k)}}),a.fn.sparkline.discrete=x=d(a.fn.sparkline._base,t,{type:"discrete",init:function(b,c,d,e,f){x._super.init.call(this,b,c,d,e,f),this.regionShapes={},this.values=c=a.map(c,Number),this.min=Math.min.apply(Math,c),this.max=Math.max.apply(Math,c),this.range=this.max-this.min,this.width=e=d.get("width")==="auto"?c.length*2:this.width,this.interval=Math.floor(e/c.length),this.itemWidth=e/c.length,d.get("chartRangeMin")!==undefined&&(d.get("chartRangeClip")||d.get("chartRangeMin")<this.min)&&(this.min=d.get("chartRangeMin")),d.get("chartRangeMax")!==undefined&&(d.get("chartRangeClip")||d.get("chartRangeMax")>this.max)&&(this.max=d.get("chartRangeMax")),this.initTarget(),this.target&&(this.lineHeight=d.get("lineHeight")==="auto"?Math.round(this.canvasHeight*.3):d.get("lineHeight"))},getRegion:function(a,b,c){return Math.floor(b/this.itemWidth)},getCurrentRegionFields:function(){var a=this.currentRegion;return{isNull:this.values[a]===undefined,value:this.values[a],offset:a}},renderRegion:function(a,b){var c=this.values,d=this.options,e=this.min,g=this.max,h=this.range,i=this.interval,j=this.target,k=this.canvasHeight,l=this.lineHeight,m=k-l,n,o,p,q;return o=f(c[a],e,g),q=a*i,n=Math.round(m-m*((o-e)/h)),p=d.get("thresholdColor")&&o<d.get("thresholdValue")?d.get("thresholdColor"):d.get("lineColor"),b&&(p=this.calcHighlightColor(p,d)),j.drawLine(q,n,q,n+l,p)}}),a.fn.sparkline.bullet=y=d(a.fn.sparkline._base,{type:"bullet",init:function(a,b,c,d,e){var f,g,h;y._super.init.call(this,a,b,c,d,e),this.values=b=i(b),h=b.slice(),h[0]=h[0]===null?h[2]:h[0],h[1]=b[1]===null?h[2]:h[1],f=Math.min.apply(Math,b),g=Math.max.apply(Math,b),c.get("base")===undefined?f=f<0?f:0:f=c.get("base"),this.min=f,this.max=g,this.range=g-f,this.shapes={},this.valueShapes={},this.regiondata={},this.width=d=c.get("width")==="auto"?"4.0em":d,this.target=this.$el.simpledraw(d,e,c.get("composite")),b.length||(this.disabled=!0),this.initTarget()},getRegion:function(a,b,c){var d=this.target.getShapeAt(a,b,c);return d!==undefined&&this.shapes[d]!==undefined?this.shapes[d]:undefined},getCurrentRegionFields:function(){var a=this.currentRegion;return{fieldkey:a.substr(0,1),value:this.values[a.substr(1)],region:a}},changeHighlight:function(a){var b=this.currentRegion,c=this.valueShapes[b],d;delete this.shapes[c];switch(b.substr(0,1)){case"r":d=this.renderRange(b.substr(1),a);break;case"p":d=this.renderPerformance(a);break;case"t":d=this.renderTarget(a)}this.valueShapes[b]=d.id,this.shapes[d.id]=b,this.target.replaceWithShape(c,d)},renderRange:function(a,b){var c=this.values[a],d=Math.round(this.canvasWidth*((c-this.min)/this.range)),e=this.options.get("rangeColors")[a-2];return b&&(e=this.calcHighlightColor(e,this.options)),this.target.drawRect(0,0,d-1,this.canvasHeight-1,e,e)},renderPerformance:function(a){var b=this.values[1],c=Math.round(this.canvasWidth*((b-this.min)/this.range)),d=this.options.get("performanceColor");return a&&(d=this.calcHighlightColor(d,this.options)),this.target.drawRect(0,Math.round(this.canvasHeight*.3),c-1,Math.round(this.canvasHeight*.4)-1,d,d)},renderTarget:function(a){var b=this.values[0],c=Math.round(this.canvasWidth*((b-this.min)/this.range)-this.options.get("targetWidth")/2),d=Math.round(this.canvasHeight*.1),e=this.canvasHeight-d*2,f=this.options.get("targetColor");return a&&(f=this.calcHighlightColor(f,this.options)),this.target.drawRect(c,d,this.options.get("targetWidth")-1,e-1,f,f)},render:function(){var a=this.values.length,b=this.target,c,d;if(!y._super.render.call(this))return;for(c=2;c<a;c++)d=this.renderRange(c).append(),this.shapes[d.id]="r"+c,this.valueShapes["r"+c]=d.id;this.values[1]!==null&&(d=this.renderPerformance().append(),this.shapes[d.id]="p1",this.valueShapes.p1=d.id),this.values[0]!==null&&(d=this.renderTarget().append(),this.shapes[d.id]="t0",this.valueShapes.t0=d.id),b.render()}}),a.fn.sparkline.pie=z=d(a.fn.sparkline._base,{type:"pie",init:function(b,c,d,e,f){var g=0,h;z._super.init.call(this,b,c,d,e,f),this.shapes={},this.valueShapes={},this.values=c=a.map(c,Number),d.get("width")==="auto"&&(this.width=this.height);if(c.length>0)for(h=c.length;h--;)g+=c[h];this.total=g,this.initTarget(),this.radius=Math.floor(Math.min(this.canvasWidth,this.canvasHeight)/2)},getRegion:function(a,b,c){var d=this.target.getShapeAt(a,b,c);return d!==undefined&&this.shapes[d]!==undefined?this.shapes[d]:undefined},getCurrentRegionFields:function(){var a=this.currentRegion;return{isNull:this.values[a]===undefined,value:this.values[a],percent:this.values[a]/this.total*100,color:this.options.get("sliceColors")[a%this.options.get("sliceColors").length],offset:a}},changeHighlight:function(a){var b=this.currentRegion,c=this.renderSlice(b,a),d=this.valueShapes[b];delete this.shapes[d],this.target.replaceWithShape(d,c),this.valueShapes[b]=c.id,this.shapes[c.id]=b},renderSlice:function(a,b){var c=this.target,d=this.options,e=this.radius,f=d.get("borderWidth"),g=d.get("offset"),h=2*Math.PI,i=this.values,j=this.total,k=g?2*Math.PI*(g/360):0,l,m,n,o,p;o=i.length;for(n=0;n<o;n++){l=k,m=k,j>0&&(m=k+h*(i[n]/j));if(a===n)return p=d.get("sliceColors")[n%d.get("sliceColors").length],b&&(p=this.calcHighlightColor(p,d)),c.drawPieSlice(e,e,e-f,l,m,undefined,p);k=m}},render:function(){var a=this.target,b=this.values,c=this.options,d=this.radius,e=c.get("borderWidth"),f,g;if(!z._super.
+render.call(this))return;e&&a.drawCircle(d,d,Math.floor(d-e/2),c.get("borderColor"),undefined,e).append();for(g=b.length;g--;)b[g]&&(f=this.renderSlice(g).append(),this.valueShapes[g]=f.id,this.shapes[f.id]=g);a.render()}}),a.fn.sparkline.box=A=d(a.fn.sparkline._base,{type:"box",init:function(b,c,d,e,f){A._super.init.call(this,b,c,d,e,f),this.values=a.map(c,Number),this.width=d.get("width")==="auto"?"4.0em":e,this.initTarget(),this.values.length||(this.disabled=1)},getRegion:function(){return 1},getCurrentRegionFields:function(){var a=[{field:"lq",value:this.quartiles[0]},{field:"med",value:this.quartiles[1]},{field:"uq",value:this.quartiles[2]}];return this.loutlier!==undefined&&a.push({field:"lo",value:this.loutlier}),this.routlier!==undefined&&a.push({field:"ro",value:this.routlier}),this.lwhisker!==undefined&&a.push({field:"lw",value:this.lwhisker}),this.rwhisker!==undefined&&a.push({field:"rw",value:this.rwhisker}),a},render:function(){var a=this.target,b=this.values,c=b.length,d=this.options,e=this.canvasWidth,f=this.canvasHeight,h=d.get("chartRangeMin")===undefined?Math.min.apply(Math,b):d.get("chartRangeMin"),i=d.get("chartRangeMax")===undefined?Math.max.apply(Math,b):d.get("chartRangeMax"),j=0,k,l,m,n,o,p,q,r,s,t,u;if(!A._super.render.call(this))return;if(d.get("raw"))d.get("showOutliers")&&b.length>5?(l=b[0],k=b[1],n=b[2],o=b[3],p=b[4],q=b[5],r=b[6]):(k=b[0],n=b[1],o=b[2],p=b[3],q=b[4]);else{b.sort(function(a,b){return a-b}),n=g(b,1),o=g(b,2),p=g(b,3),m=p-n;if(d.get("showOutliers")){k=q=undefined;for(s=0;s<c;s++)k===undefined&&b[s]>n-m*d.get("outlierIQR")&&(k=b[s]),b[s]<p+m*d.get("outlierIQR")&&(q=b[s]);l=b[0],r=b[c-1]}else k=b[0],q=b[c-1]}this.quartiles=[n,o,p],this.lwhisker=k,this.rwhisker=q,this.loutlier=l,this.routlier=r,u=e/(i-h+1),d.get("showOutliers")&&(j=Math.ceil(d.get("spotRadius")),e-=2*Math.ceil(d.get("spotRadius")),u=e/(i-h+1),l<k&&a.drawCircle((l-h)*u+j,f/2,d.get("spotRadius"),d.get("outlierLineColor"),d.get("outlierFillColor")).append(),r>q&&a.drawCircle((r-h)*u+j,f/2,d.get("spotRadius"),d.get("outlierLineColor"),d.get("outlierFillColor")).append()),a.drawRect(Math.round((n-h)*u+j),Math.round(f*.1),Math.round((p-n)*u),Math.round(f*.8),d.get("boxLineColor"),d.get("boxFillColor")).append(),a.drawLine(Math.round((k-h)*u+j),Math.round(f/2),Math.round((n-h)*u+j),Math.round(f/2),d.get("lineColor")).append(),a.drawLine(Math.round((k-h)*u+j),Math.round(f/4),Math.round((k-h)*u+j),Math.round(f-f/4),d.get("whiskerColor")).append(),a.drawLine(Math.round((q-h)*u+j),Math.round(f/2),Math.round((p-h)*u+j),Math.round(f/2),d.get("lineColor")).append(),a.drawLine(Math.round((q-h)*u+j),Math.round(f/4),Math.round((q-h)*u+j),Math.round(f-f/4),d.get("whiskerColor")).append(),a.drawLine(Math.round((o-h)*u+j),Math.round(f*.1),Math.round((o-h)*u+j),Math.round(f*.9),d.get("medianColor")).append(),d.get("target")&&(t=Math.ceil(d.get("spotRadius")),a.drawLine(Math.round((d.get("target")-h)*u+j),Math.round(f/2-t),Math.round((d.get("target")-h)*u+j),Math.round(f/2+t),d.get("targetColor")).append(),a.drawLine(Math.round((d.get("target")-h)*u+j-t),Math.round(f/2),Math.round((d.get("target")-h)*u+j+t),Math.round(f/2),d.get("targetColor")).append()),a.render()}}),function(){document.namespaces&&!document.namespaces.v?(a.fn.sparkline.hasVML=!0,document.namespaces.add("v","urn:schemas-microsoft-com:vml","#default#VML")):a.fn.sparkline.hasVML=!1;var b=document.createElement("canvas");a.fn.sparkline.hasCanvas=!!b.getContext&&!!b.getContext("2d")}(),D=d({init:function(a,b,c,d){this.target=a,this.id=b,this.type=c,this.args=d},append:function(){return this.target.appendShape(this),this}}),E=d({_pxregex:/(\d+)(px)?\s*$/i,init:function(b,c,d){if(!b)return;this.width=b,this.height=c,this.target=d,this.lastShapeId=null,d[0]&&(d=d[0]),a.data(d,"_jqs_vcanvas",this)},drawLine:function(a,b,c,d,e,f){return this.drawShape([[a,b],[c,d]],e,f)},drawShape:function(a,b,c,d){return this._genShape("Shape",[a,b,c,d])},drawCircle:function(a,b,c,d,e,f){return this._genShape("Circle",[a,b,c,d,e,f])},drawPieSlice:function(a,b,c,d,e,f,g){return this._genShape("PieSlice",[a,b,c,d,e,f,g])},drawRect:function(a,b,c,d,e,f){return this._genShape("Rect",[a,b,c,d,e,f])},getElement:function(){return this.canvas},getLastShapeId:function(){return this.lastShapeId},reset:function(){alert("reset not implemented")},_insert:function(b,c){a(c).html(b)},_calculatePixelDims:function(b,c,d){var e;e=this._pxregex.exec(c),e?this.pixelHeight=e[1]:this.pixelHeight=a(d).height(),e=this._pxregex.exec(b),e?this.pixelWidth=e[1]:this.pixelWidth=a(d).width()},_genShape:function(a,b){var c=I++;return b.unshift(c),new D(this,c,a,b)},appendShape:function(a){alert("appendShape not implemented")},replaceWithShape:function(a,b){alert("replaceWithShape not implemented")},insertAfterShape:function(a,b){alert("insertAfterShape not implemented")},removeShapeId:function(a){alert("removeShapeId not implemented")},getShapeAt:function(a,b,c){alert("getShapeAt not implemented")},render:function(){alert("render not implemented")}}),F=d(E,{init:function(b,c,d,e){F._super.init.call(this,b,c,d),this.canvas=document.createElement("canvas"),d[0]&&(d=d[0]),a.data(d,"_jqs_vcanvas",this),a(this.canvas).css({display:"inline-block",width:b,height:c,verticalAlign:"top"}),this._insert(this.canvas,d),this._calculatePixelDims(b,c,this.canvas),this.canvas.width=this.pixelWidth,this.canvas.height=this.pixelHeight,this.interact=e,this.shapes={},this.shapeseq=[],this.currentTargetShapeId=undefined,a(this.canvas).css({width:this.pixelWidth,height:this.pixelHeight})},_getContext:function(a,b,c){var d=this.canvas.getContext("2d");return a!==undefined&&(d.strokeStyle=a),d.lineWidth=c===undefined?1:c,b!==undefined&&(d.fillStyle=b),d},reset:function(){var a=this._getContext();a.clearRect(0,0,this.pixelWidth,this.pixelHeight),this.shapes={},this.shapeseq=[],this.currentTargetShapeId=undefined},_drawShape:function(a,b,c,d,e){var f=this._getContext(c,d,e),g,h;f.beginPath(),f.moveTo(b[0][0]+.5,b[0][1]+.5);for(g=1,h=b.length;g<h;g++)f.lineTo(b[g][0]+.5,b[g][1]+.5);c!==undefined&&f.stroke(),d!==undefined&&f.fill(),this.targetX!==undefined&&this.targetY!==undefined&&f.isPointInPath(this.targetX,this.targetY)&&(this.currentTargetShapeId=a)},_drawCircle:function(a,b,c,d,e,f,g){var h=this._getContext(e,f,g);h.beginPath(),h.arc(b,c,d,0,2*Math.PI,!1),this.targetX!==undefined&&this.targetY!==undefined&&h.isPointInPath(this.targetX,this.targetY)&&(this.currentTargetShapeId=a),e!==undefined&&h.stroke(),f!==undefined&&h.fill()},_drawPieSlice:function(a,b,c,d,e,f,g,h){var i=this._getContext(g,h);i.beginPath(),i.moveTo(b,c),i.arc(b,c,d,e,f,!1),i.lineTo(b,c),i.closePath(),g!==undefined&&i.stroke(),h&&i.fill(),this.targetX!==undefined&&this.targetY!==undefined&&i.isPointInPath(this.targetX,this.targetY)&&(this.currentTargetShapeId=a)},_drawRect:function(a,b,c,d,e,f,g){return this._drawShape(a,[[b,c],[b+d,c],[b+d,c+e],[b,c+e],[b,c]],f,g)},appendShape:function(a){return this.shapes[a.id]=a,this.shapeseq.push(a.id),this.lastShapeId=a.id,a.id},replaceWithShape:function(a,b){var c=this.shapeseq,d;this.shapes[b.id]=b;for(d=c.length;d--;)c[d]==a&&(c[d]=b.id);delete this.shapes[a]},replaceWithShapes:function(a,b){var c=this.shapeseq,d={},e,f,g;for(f=a.length;f--;)d[a[f]]=!0;for(f=c.length;f--;)e=c[f],d[e]&&(c.splice(f,1),delete this.shapes[e],g=f);for(f=b.length;f--;)c.splice(g,0,b[f].id),this.shapes[b[f].id]=b[f]},insertAfterShape:function(a,b){var c=this.shapeseq,d;for(d=c.length;d--;)if(c[d]===a){c.splice(d+1,0,b.id),this.shapes[b.id]=b;return}},removeShapeId:function(a){var b=this.shapeseq,c;for(c=b.length;c--;)if(b[c]===a){b.splice(c,1);break}delete this.shapes[a]},getShapeAt:function(a,b,c){return this.targetX=b,this.targetY=c,this.render(),this.currentTargetShapeId},render:function(){var a=this.shapeseq,b=this.shapes,c=a.length,d=this._getContext(),e,f,g;d.clearRect(0,0,this.pixelWidth,this.pixelHeight);for(g=0;g<c;g++)e=a[g],f=b[e],this["_draw"+f.type].apply(this,f.args);this.interact||(this.shapes={},this.shapeseq=[])}}),G=d(E,{init:function(b,c,d){var e;G._super.init.call(this,b,c,d),d[0]&&(d=d[0]),a.data(d,"_jqs_vcanvas",this),this.canvas=document.createElement("span"),a(this.canvas).css({display:"inline-block",position:"relative",overflow:"hidden",width:b,height:c,margin:"0px",padding:"0px",verticalAlign:"top"}),this._insert(this.canvas,d),this._calculatePixelDims(b,c,this.canvas),this.canvas.width=this.pixelWidth,this.canvas.height=this.pixelHeight,e='<v:group coordorigin="0 0" coordsize="'+this.pixelWidth+" "+this.pixelHeight+'"'+' style="position:absolute;top:0;left:0;width:'+this.pixelWidth+"px;height="+this.pixelHeight+'px;"></v:group>',this.canvas.insertAdjacentHTML("beforeEnd",e),this.group=a(this.canvas).children()[0],this.rendered=!1,this.prerender=""},_drawShape:function(a,b,c,d,e){var f=[],g,h,i,j,k,l,m;for(m=0,l=b.length;m<l;m++)f[m]=""+b[m][0]+","+b[m][1];return g=f.splice(0,1),e=e===undefined?1:e,h=c===undefined?' stroked="false" ':' strokeWeight="'+e+'px" strokeColor="'+c+'" ',i=d===undefined?' filled="false"':' fillColor="'+d+'" filled="true" ',j=f[0]===f[f.length-1]?"x ":"",k='<v:shape coordorigin="0 0" coordsize="'+this.pixelWidth+" "+this.pixelHeight+'" '+' id="jqsshape'+a+'" '+h+i+' style="position:absolute;left:0px;top:0px;height:'+this.pixelHeight+"px;width:"+this.pixelWidth+'px;padding:0px;margin:0px;" '+' path="m '+g+" l "+f.join(", ")+" "+j+'e">'+" </v:shape>",k},_drawCircle:function(a,b,c,d,e,f,g){var h,i,j;return b-=d,c-=d,h=e===undefined?' stroked="false" ':' strokeWeight="'+g+'px" strokeColor="'+e+'" ',i=f===undefined?' filled="false"':' fillColor="'+f+'" filled="true" ',j='<v:oval id="jqsshape'+a+'" '+h+i+' style="position:absolute;top:'+c+"px; left:"+b+"px; width:"+d*2+"px; height:"+d*2+'px"></v:oval>',j},_drawPieSlice:function(a,b,c,d,e,f,g,h){var i,j,k,l,m,n,o,p;if(e===f)return"";f-e===2*Math.PI&&(e=0,f=2*Math.PI),j=b+Math.round(Math.cos(e)*d),k=c+Math.round(Math.sin(e)*d),l=b+Math.round(Math.cos(f)*d),m=c+Math.round(Math.sin(f)*d);if(j===l&&k===m){if(f-e<Math.PI)return"";j=l=b+d,k=m=c}return j===l&&k===m&&f-e<Math.PI?"":(i=[b-d,c-d,b+d,c+d,j,k,l,m],n=g===undefined?' stroked="false" ':' strokeWeight="1px" strokeColor="'+g+'" ',o=h===undefined?' filled="false"':' fillColor="'+h+'" filled="true" ',p='<v:shape coordorigin="0 0" coordsize="'+this.pixelWidth+" "+this.pixelHeight+'" '+' id="jqsshape'+a+'" '+n+o+' style="position:absolute;left:0px;top:0px;height:'+this.pixelHeight+"px;width:"+this.pixelWidth+'px;padding:0px;margin:0px;" '+' path="m '+b+","+c+" wa "+i.join(", ")+' x e">'+" </v:shape>",p)},_drawRect:function(a,b,c,d,e,f,g){return this._drawShape(a,[[b,c],[b,c+e],[b+d,c+e],[b+d,c],[b,c]],f,g)},reset:function(){this.group.innerHTML=""},appendShape:function(a){var b=this["_draw"+a.type].apply(this,a.args);return this.rendered?this.group.insertAdjacentHTML("beforeEnd",b):this.prerender+=b,this.lastShapeId=a.id,a.id},replaceWithShape:function(b,c){var d=a("#jqsshape"+b),e=this["_draw"+c.type].apply(this,c.args);d[0].outerHTML=e},replaceWithShapes:function(b,c){var d=a("#jqsshape"+b[0]),e="",f=c.length,g;for(g=0;g<f;g++)e+=this["_draw"+c[g].type].apply(this,c[g].args);d[0].outerHTML=e;for(g=1;g<b.length;g++)a("#jqsshape"+b[g]).remove()},insertAfterShape:function(b,c){var d=a("#jqsshape"+b),e=this["_draw"+c.type].apply(this,c.args);d[0].insertAdjacentHTML("afterEnd",e)},removeShapeId:function(b){var c=a("#jqsshape"+b);this.group.removeChild(c[0])},getShapeAt:function(a,b,c){var d=a.id.substr(8);return d},render:function(){this.rendered||(this.group.innerHTML=this.prerender,this.rendered=!0)}})}); \ No newline at end of file
diff --git a/venv/lib/python3.9/site-packages/pympler/summary.py b/venv/lib/python3.9/site-packages/pympler/summary.py
new file mode 100644
index 00000000..7c9470fd
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/summary.py
@@ -0,0 +1,321 @@
+"""A collection of functions to summarize object information.
+
+This module provides several function which will help you to analyze object
+information which was gathered. Often it is sufficient to work with aggregated
+data instead of handling the entire set of existing objects. For example can a
+memory leak identified simple based on the number and size of existing objects.
+
+A summary contains information about objects in a table-like manner.
+Technically, it is a list of lists. Each of these lists represents a row,
+whereas the first column reflects the object type, the second column the number
+of objects, and the third column the size of all these objects. This allows a
+simple table-like output like the following:
+
+============= ============ =============
+ types # objects total size
+============= ============ =============
+<type 'dict'> 2 560
+ <type 'str'> 3 126
+ <type 'int'> 4 96
+<type 'long'> 2 66
+<type 'list'> 1 40
+============= ============ =============
+
+Another advantage of summaries is that they influence the system you analyze
+only to a minimum. Working with references to existing objects will keep these
+objects alive. Most of the times this is no desired behavior (as it will have
+an impact on the observations). Using summaries reduces this effect greatly.
+
+output representation
+---------------------
+
+The output representation of types is defined in summary.representations.
+Every type defined in this dictionary will be represented as specified. Each
+definition has a list of different representations. The later a representation
+appears in this list, the higher its verbosity level. From types which are not
+defined in summary.representations the default str() representation will be
+used.
+
+Per default, summaries will use the verbosity level 1 for any encountered type.
+The reason is that several computations are done with summaries and rows have
+to remain comparable. Therefore information which reflect an objects state,
+e.g. the current line number of a frame, should not be included. You may add
+more detailed information at higher verbosity levels than 1.
+"""
+
+import re
+import sys
+import types
+
+from pympler.util import stringutils
+from sys import getsizeof
+
+representations = {}
+
+
+def _init_representations():
+ global representations
+ if sys.hexversion < 0x2040000:
+ classobj = [
+ lambda c: "classobj(%s)" % repr(c),
+ ]
+ representations[types.ClassType] = classobj
+ instance = [
+ lambda f: "instance(%s)" % repr(f.__class__),
+ ]
+ representations[types.InstanceType] = instance
+ instancemethod = [
+ lambda i: "instancemethod (%s)" % (repr(i.im_func)),
+ lambda i: "instancemethod (%s, %s)" % (repr(i.im_class),
+ repr(i.im_func)),
+ ]
+ representations[types.MethodType] = instancemethod
+ frame = [
+ lambda f: "frame (codename: %s)" % (f.f_code.co_name),
+ lambda f: "frame (codename: %s, codeline: %s)" %
+ (f.f_code.co_name, f.f_code.co_firstlineno),
+ lambda f: "frame (codename: %s, filename: %s, codeline: %s)" %
+ (f.f_code.co_name, f.f_code.co_filename,
+ f.f_code.co_firstlineno)
+ ]
+ representations[types.FrameType] = frame
+ _dict = [
+ lambda d: str(type(d)),
+ lambda d: "dict, len=%s" % len(d),
+ ]
+ representations[dict] = _dict
+ function = [
+ lambda f: "function (%s)" % f.__name__,
+ lambda f: "function (%s.%s)" % (f.__module__, f.__name__),
+ ]
+ representations[types.FunctionType] = function
+ _list = [
+ lambda l: str(type(l)),
+ lambda l: "list, len=%s" % len(l)
+ ]
+ representations[list] = _list
+ module = [lambda m: "module(%s)" % getattr(
+ m, '__name__', getattr(m, '__file__', 'nameless, id: %d' % id(m))
+ )]
+ representations[types.ModuleType] = module
+ _set = [
+ lambda s: str(type(s)),
+ lambda s: "set, len=%s" % len(s)
+ ]
+ representations[set] = _set
+
+
+_init_representations()
+
+
+def summarize(objects):
+ """Summarize an objects list.
+
+ Return a list of lists, whereas each row consists of::
+ [str(type), number of objects of this type, total size of these objects].
+
+ No guarantee regarding the order is given.
+
+ """
+ count = {}
+ total_size = {}
+ for o in objects:
+ otype = _repr(o)
+ if otype in count:
+ count[otype] += 1
+ total_size[otype] += getsizeof(o)
+ else:
+ count[otype] = 1
+ total_size[otype] = getsizeof(o)
+ rows = []
+ for otype in count:
+ rows.append([otype, count[otype], total_size[otype]])
+ return rows
+
+
+def get_diff(left, right):
+ """Get the difference of two summaries.
+
+ Subtracts the values of the right summary from the values of the left
+ summary.
+ If similar rows appear on both sides, the are included in the summary with
+ 0 for number of elements and total size.
+ If the number of elements of a row of the diff is 0, but the total size is
+ not, it means that objects likely have changed, but not there number, thus
+ resulting in a changed size.
+
+ """
+ res = []
+
+ right_by_key = dict((r[0], r) for r in right)
+ left_by_key = dict((r[0], r) for r in left)
+
+ keys = set(right_by_key)
+ keys.update(left_by_key)
+
+ for key in keys:
+ r = right_by_key.get(key)
+ l = left_by_key.get(key)
+ if l and r:
+ res.append([key, r[1] - l[1], r[2] - l[2]])
+ elif r:
+ res.append(r)
+ elif l:
+ res.append([key, -l[1], -l[2]])
+ else:
+ continue # shouldn't happen
+ return res
+
+
+def format_(rows, limit=15, sort='size', order='descending'):
+ """Format the rows as a summary.
+
+ Keyword arguments:
+ limit -- the maximum number of elements to be listed
+ sort -- sort elements by 'size', 'type', or '#'
+ order -- sort 'ascending' or 'descending'
+ """
+ localrows = []
+ for row in rows:
+ localrows.append(list(row))
+ # input validation
+ sortby = ['type', '#', 'size']
+ if sort not in sortby:
+ raise ValueError("invalid sort, should be one of" + str(sortby))
+ orders = ['ascending', 'descending']
+ if order not in orders:
+ raise ValueError("invalid order, should be one of" + str(orders))
+ # sort rows
+ if sortby.index(sort) == 0:
+ if order == "ascending":
+ localrows.sort(key=lambda x: _repr(x[0]))
+ elif order == "descending":
+ localrows.sort(key=lambda x: _repr(x[0]), reverse=True)
+ else:
+ if order == "ascending":
+ localrows.sort(key=lambda x: x[sortby.index(sort)])
+ elif order == "descending":
+ localrows.sort(key=lambda x: x[sortby.index(sort)], reverse=True)
+ # limit rows
+ localrows = localrows[0:limit]
+ for row in localrows:
+ row[2] = stringutils.pp(row[2])
+ # print rows
+ localrows.insert(0, ["types", "# objects", "total size"])
+ return _format_table(localrows)
+
+
+def _format_table(rows, header=True):
+ """Format a list of lists as a pretty table.
+
+ Keyword arguments:
+ header -- if True the first row is treated as a table header
+
+ inspired by http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267662
+ """
+ border = "="
+ # vertical delimiter
+ vdelim = " | "
+ # padding nr. of spaces are left around the longest element in the
+ # column
+ padding = 1
+ # may be left,center,right
+ justify = 'right'
+ justify = {'left': str.ljust,
+ 'center': str.center,
+ 'right': str.rjust}[justify.lower()]
+ # calculate column widths (longest item in each col
+ # plus "padding" nr of spaces on both sides)
+ cols = zip(*rows)
+ colWidths = [max([len(str(item)) + 2 * padding for item in col])
+ for col in cols]
+ borderline = vdelim.join([w * border for w in colWidths])
+ for row in rows:
+ yield vdelim.join([justify(str(item), width)
+ for (item, width) in zip(row, colWidths)])
+ if header:
+ yield borderline
+ header = False
+
+
+def print_(rows, limit=15, sort='size', order='descending'):
+ """Print the rows as a summary.
+
+ Keyword arguments:
+ limit -- the maximum number of elements to be listed
+ sort -- sort elements by 'size', 'type', or '#'
+ order -- sort 'ascending' or 'descending'
+
+ """
+ for line in format_(rows, limit=limit, sort=sort, order=order):
+ print(line)
+
+
+# regular expressions used by _repr to replace default type representations
+type_repr = re.compile(r"^<(type|class) '(\S+)'>$")
+address = re.compile(r' at 0x[0-9a-f]+')
+
+
+def _repr(o, verbosity=1):
+ """Get meaning object representation.
+
+ This function should be used when the simple str(o) output would result in
+ too general data. E.g. "<type 'instance'" is less meaningful than
+ "instance: Foo".
+
+ Keyword arguments:
+ verbosity -- if True the first row is treated as a table header
+
+ """
+ res = ""
+
+ t = type(o)
+ if (verbosity == 0) or (t not in representations):
+ res = str(t)
+ else:
+ verbosity -= 1
+ if len(representations[t]) <= verbosity:
+ verbosity = len(representations[t]) - 1
+ res = representations[t][verbosity](o)
+
+ res = address.sub('', res)
+ res = type_repr.sub(r'\2', res)
+
+ return res
+
+
+def _traverse(summary, function, *args):
+ """Traverse all objects of a summary and call function with each as a
+ parameter.
+
+ Using this function, the following objects will be traversed:
+ - the summary
+ - each row
+ - each item of a row
+ """
+ function(summary, *args)
+ for row in summary:
+ function(row, *args)
+ for item in row:
+ function(item, *args)
+
+
+def _subtract(summary, o):
+ """Remove object o from the summary by subtracting it's size."""
+ found = False
+ row = [_repr(o), 1, getsizeof(o)]
+ for r in summary:
+ if r[0] == row[0]:
+ (r[1], r[2]) = (r[1] - row[1], r[2] - row[2])
+ found = True
+ if not found:
+ summary.append([row[0], -row[1], -row[2]])
+ return summary
+
+
+def _sweep(summary):
+ """Remove all rows in which the total size and the total number of
+ objects is zero.
+
+ """
+ return [row for row in summary if ((row[2] != 0) or (row[1] != 0))]
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/asized_referents.tpl b/venv/lib/python3.9/site-packages/pympler/templates/asized_referents.tpl
new file mode 100644
index 00000000..6271a4ce
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/asized_referents.tpl
@@ -0,0 +1,9 @@
+%for ref in referents:
+ <div class="referents">
+ <span class="local_name">{{ref.name}}</span>
+ <span class="local_size">{{ref.size}}</span>
+ %if ref.refs:
+ %include('asized_referents', referents=ref.refs)
+ %end
+ </div>
+%end
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/footer.tpl b/venv/lib/python3.9/site-packages/pympler/templates/footer.tpl
new file mode 100644
index 00000000..e1285c43
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/footer.tpl
@@ -0,0 +1,6 @@
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/garbage.tpl b/venv/lib/python3.9/site-packages/pympler/templates/garbage.tpl
new file mode 100644
index 00000000..2aabca6c
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/garbage.tpl
@@ -0,0 +1,33 @@
+%include('header', category='Garbage', title='Garbage')
+
+<h1>Garbage - Cycle {{index}}</h1>
+<table class="tdata" width="100%">
+ <thead>
+ <tr>
+ <th>id</th>
+ <th class="num">size</th>
+ <th>type</th>
+ <th>representation</th>
+ </tr>
+ </thead>
+ <tbody>
+ %for o in objects:
+ <tr>
+ <td>{{'0x%08x' % o.id}}</td>
+ <td class="num">{{o.size}}</td>
+ <td>{{o.type}}</td>
+ <td>{{o.str}}</td>
+ </tr>
+ %end
+ </tbody>
+</table>
+
+<h2>Reference graph</h2>
+
+<img src="/garbage/graph/{{index}}"/>
+
+<h2>Reduced reference graph (cycles only)</h2>
+
+<img src="/garbage/graph/{{index}}?reduce=1"/>
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/garbage_index.tpl b/venv/lib/python3.9/site-packages/pympler/templates/garbage_index.tpl
new file mode 100644
index 00000000..7fbd1930
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/garbage_index.tpl
@@ -0,0 +1,41 @@
+%include('header', category='Garbage', title='Garbage')
+<h1>Garbage - Overview</h1>
+
+<p>This page gives an overview of all objects that would have been
+deleted if those weren't holding circular references to each other
+(e.g. in a doubly linked list).</p>
+
+%if len(graphs):
+ <p>Click on the reference graph titles below to show the objects
+ contained in the respective cycle. If you have <a
+ href="http://www.graphviz.org">graphviz</a> installed, you will
+ also see a visualisation of the reference cycle.</p>
+
+ <p>{{len(graphs)}} reference cycles:</p>
+
+ <table class="tdata">
+ <thead>
+ <tr>
+ <th>Reference graph</th>
+ <th># objects</th>
+ <th># cycle objects</th>
+ <th>Total size</th>
+ </tr>
+ </thead>
+ <tbody>
+ %for graph in graphs:
+ <tr>
+ <td><a href="/garbage/{{graph.index}}">Cycle {{graph.index}}</a></td>
+ <td class="num">{{len(graph.metadata)}}</td>
+ <td class="num">{{graph.num_in_cycles}}</td>
+ <td class="num">{{graph.total_size}}</td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+
+%else:
+ <p>No reference cycles detected.</p>
+%end
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/header.tpl b/venv/lib/python3.9/site-packages/pympler/templates/header.tpl
new file mode 100644
index 00000000..34ee1761
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/header.tpl
@@ -0,0 +1,36 @@
+<html>
+
+<head>
+ <title>Pympler - {{title}}</title>
+ <link rel="stylesheet" type="text/css" href="/static/style.css">
+ <script src="http://code.jquery.com/jquery-1.10.1.min.js" type="text/javascript"></script>
+</head>
+
+%navbar = [
+% ("overview", "/", ""),
+% ("|", "", ""),
+% ("process", "/process", ""),
+% ("|", "", ""),
+% ("tracked objects", "/tracker", ""),
+% ("|", "", ""),
+% ("garbage", "/garbage", ""),
+% ("help", "/help", "right"),]
+
+<body>
+<div class="related">
+ <ul>
+ %for link, href, cls in navbar:
+ <li class="{{cls}}">
+ %if bool(href):
+ <a href="{{href}}"><span>{{link}}</span></a>
+ %else:
+ <span>{{link}}</span>
+ %end
+ </li>
+ %end
+ </ul>
+</div>
+<div class="document">
+<div class="documentwrapper">
+<div class="bodywrapper">
+<div class="body">
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/index.tpl b/venv/lib/python3.9/site-packages/pympler/templates/index.tpl
new file mode 100644
index 00000000..21e6b624
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/index.tpl
@@ -0,0 +1,26 @@
+%include('header', category='Overview', title='Overview')
+
+%from pympler.util.stringutils import pp
+
+<h1>Python application memory profile</h1>
+
+<h2>Process overview</h2>
+
+<table class="tdata">
+ <tbody>
+ <tr>
+ <th>Virtual size:</th>
+ <td class="num">{{pp(processinfo.vsz)}}</td>
+ </tr>
+ <tr>
+ <th>Physical memory size:</th>
+ <td class="num">{{pp(processinfo.rss)}}</td>
+ </tr>
+ <tr>
+ <th>Major pagefaults:</th>
+ <td class="num">{{processinfo.pagefaults}}</td>
+ </tr>
+ </tbody>
+</table>
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.min.js b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.min.js
new file mode 100644
index 00000000..968d3ebd
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.min.js
@@ -0,0 +1,8 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){var cache=this._textCache;for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layer=this.getTextLayer(layerKey),layerCache=cache[layerKey];layer.hide();for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){if(position.active){if(!position.rendered){layer.append(position.element);position.rendered=true}}else{positions.splice(i--,1);if(position.rendered){position.element.detach()}}}if(positions.length==0){delete styleCache[key]}}}}}layer.show()}}};Canvas.prototype.getTextLayer=function(classes){var layer=this.text[classes];if(layer==null){if(this.textContainer==null){this.textContainer=$("<div class='flot-text'></div>").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}layer=this.text[classes]=$("<div></div>").addClass(classes).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return layer};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px/"+font.lineHeight+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var element=$("<div></div>").html(text).css({position:"absolute","max-width":width,top:-9999}).appendTo(this.getTextLayer(layer));if(typeof font==="object"){element.css({font:textStyle,color:font.color})}else if(typeof font==="string"){element.addClass(font)}info=styleCache[text]={width:element.outerWidth(true),height:element.outerHeight(true),element:element,positions:[]};element.detach()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions;if(halign=="center"){x-=info.width/2}else if(halign=="right"){x-=info.width}if(valign=="middle"){y-=info.height/2}else if(valign=="bottom"){y-=info.height}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,rendered:false,element:positions.length?info.element.clone():info.element,x:x,y:y};positions.push(position);position.element.css({top:Math.round(y),left:Math.round(x),"text-align":halign})};Canvas.prototype.removeText=function(layer,x,y,text,font,angle){if(text==null){var layerCache=this._textCache[layer];if(layerCache!=null){for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){position.active=false}}}}}}}else{var positions=this.getTextInfo(layer,text,font,angle).positions;for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=false}}}};function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},surface=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return surface.element};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis)res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left,10),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top,10)}};plot.shutdown=shutdown;plot.destroy=function(){shutdown();placeholder.removeData("plot").empty();series=[];options=null;surface=null;overlay=null;eventHolder=null;ctx=null;octx=null;xaxes=[];yaxes=[];hooks=null;highlights=[];plot=null};plot.resize=function(){var width=placeholder.width(),height=placeholder.height();surface.resize(width,height);overlay.resize(width,height)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;i<hook.length;++i)hook[i].apply(this,args)}function initPlugins(){var classes={Canvas:Canvas};for(var i=0;i<plugins.length;++i){var p=plugins[i];p.init(plot,classes);if(p.options)$.extend(true,options,p.options)}}function parseOptions(opts){$.extend(true,options,opts);if(opts&&opts.colors){options.colors=opts.colors}if(options.xaxis.color==null)options.xaxis.color=$.color.parse(options.grid.color).scale("a",.22).toString();if(options.yaxis.color==null)options.yaxis.color=$.color.parse(options.grid.color).scale("a",.22).toString();if(options.xaxis.tickColor==null)options.xaxis.tickColor=options.grid.tickColor||options.xaxis.color;if(options.yaxis.tickColor==null)options.yaxis.tickColor=options.grid.tickColor||options.yaxis.color;if(options.grid.borderColor==null)options.grid.borderColor=options.grid.color;if(options.grid.tickColor==null)options.grid.tickColor=$.color.parse(options.grid.color).scale("a",.22).toString();var i,axisOptions,axisCount,fontSize=placeholder.css("font-size"),fontSizeDefault=fontSize?+fontSize.replace("px",""):13,fontDefaults={style:placeholder.css("font-style"),size:Math.round(.8*fontSizeDefault),variant:placeholder.css("font-variant"),weight:placeholder.css("font-weight"),family:placeholder.css("font-family")};axisCount=options.xaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.xaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.xaxis,axisOptions);options.xaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}if(!axisOptions.font.lineHeight){axisOptions.font.lineHeight=Math.round(axisOptions.font.size*1.15)}}}axisCount=options.yaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.yaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.yaxis,axisOptions);options.yaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}if(!axisOptions.font.lineHeight){axisOptions.font.lineHeight=Math.round(axisOptions.font.size*1.15)}}}if(options.xaxis.noTicks&&options.xaxis.ticks==null)options.xaxis.ticks=options.xaxis.noTicks;if(options.yaxis.noTicks&&options.yaxis.ticks==null)options.yaxis.ticks=options.yaxis.noTicks;if(options.x2axis){options.xaxes[1]=$.extend(true,{},options.xaxis,options.x2axis);options.xaxes[1].position="top";if(options.x2axis.min==null){options.xaxes[1].min=null}if(options.x2axis.max==null){options.xaxes[1].max=null}}if(options.y2axis){options.yaxes[1]=$.extend(true,{},options.yaxis,options.y2axis);options.yaxes[1].position="right";if(options.y2axis.min==null){options.yaxes[1].min=null}if(options.y2axis.max==null){options.yaxes[1].max=null}}if(options.grid.coloredAreas)options.grid.markings=options.grid.coloredAreas;if(options.grid.coloredAreasColor)options.grid.markingsColor=options.grid.coloredAreasColor;if(options.lines)$.extend(true,options.series.lines,options.lines);if(options.points)$.extend(true,options.series.points,options.points);if(options.bars)$.extend(true,options.series.bars,options.bars);if(options.shadowSize!=null)options.series.shadowSize=options.shadowSize;if(options.highlightColor!=null)options.series.highlightColor=options.highlightColor;for(i=0;i<options.xaxes.length;++i)getOrCreateAxis(xaxes,i+1).options=options.xaxes[i];for(i=0;i<options.yaxes.length;++i)getOrCreateAxis(yaxes,i+1).options=options.yaxes[i];for(var n in hooks)if(options.hooks[n]&&options.hooks[n].length)hooks[n]=hooks[n].concat(options.hooks[n]);executeHooks(hooks.processOptions,[options])}function setData(d){series=parseData(d);fillInSeriesOptions();processData()}function parseData(d){var res=[];for(var i=0;i<d.length;++i){var s=$.extend(true,{},options.series);if(d[i].data!=null){s.data=d[i].data;delete d[i].data;$.extend(true,s,d[i]);d[i].data=s.data}else s.data=d[i];res.push(s)}return res}function axisNumber(obj,coord){var a=obj[coord+"axis"];if(typeof a=="object")a=a.n;if(typeof a!="number")a=1;return a}function allAxes(){return $.grep(xaxes.concat(yaxes),function(a){return a})}function canvasToAxisCoords(pos){var res={},i,axis;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used)res["x"+axis.n]=axis.c2p(pos.left)}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used)res["y"+axis.n]=axis.c2p(pos.top)}if(res.x1!==undefined)res.x=res.x1;if(res.y1!==undefined)res.y=res.y1;return res}function axisToCanvasCoords(pos){var res={},i,axis,key;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used){key="x"+axis.n;if(pos[key]==null&&axis.n==1)key="x";if(pos[key]!=null){res.left=axis.p2c(pos[key]);break}}}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used){key="y"+axis.n;if(pos[key]==null&&axis.n==1)key="y";if(pos[key]!=null){res.top=axis.p2c(pos[key]);break}}}return res}function getOrCreateAxis(axes,number){if(!axes[number-1])axes[number-1]={n:number,direction:axes==xaxes?"x":"y",options:$.extend(true,{},axes==xaxes?options.xaxis:options.yaxis)};return axes[number-1]}function fillInSeriesOptions(){var neededColors=series.length,maxIndex=-1,i;for(i=0;i<series.length;++i){var sc=series[i].color;if(sc!=null){neededColors--;if(typeof sc=="number"&&sc>maxIndex){maxIndex=sc}}}if(neededColors<=maxIndex){neededColors=maxIndex+1}var c,colors=[],colorPool=options.colors,colorPoolSize=colorPool.length,variation=0;for(i=0;i<neededColors;i++){c=$.color.parse(colorPool[i%colorPoolSize]||"#666");if(i%colorPoolSize==0&&i){if(variation>=0){if(variation<.5){variation=-variation-.2}else variation=0}else variation=-variation}colors[i]=c.scale("rgb",1+variation)}var colori=0,s;for(i=0;i<series.length;++i){s=series[i];if(s.color==null){s.color=colors[colori].toString();++colori}else if(typeof s.color=="number")s.color=colors[s.color].toString();if(s.lines.show==null){var v,show=true;for(v in s)if(s[v]&&s[v].show){show=false;break}if(show)s.lines.show=true}if(s.lines.zero==null){s.lines.zero=!!s.lines.fill}s.xaxis=getOrCreateAxis(xaxes,axisNumber(s,"x"));s.yaxis=getOrCreateAxis(yaxes,axisNumber(s,"y"))}}function processData(){var topSentry=Number.POSITIVE_INFINITY,bottomSentry=Number.NEGATIVE_INFINITY,fakeInfinity=Number.MAX_VALUE,i,j,k,m,length,s,points,ps,x,y,axis,val,f,p,data,format;function updateAxis(axis,min,max){if(min<axis.datamin&&min!=-fakeInfinity)axis.datamin=min;if(max>axis.datamax&&max!=fakeInfinity)axis.datamax=max}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i<series.length;++i){s=series[i];s.datapoints={points:[]};executeHooks(hooks.processRawData,[s,s.data,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];data=s.data;format=s.datapoints.format;if(!format){format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||s.lines.show&&s.lines.fill){var autoscale=!!(s.bars.show&&s.bars.zero||s.lines.show&&s.lines.zero);format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}s.datapoints.format=format}if(s.datapoints.pointsize!=null)continue;s.datapoints.pointsize=format.length;ps=s.datapoints.pointsize;points=s.datapoints.points;var insertSteps=s.lines.show&&s.lines.steps;s.xaxis.used=s.yaxis.used=true;for(j=k=0;j<data.length;++j,k+=ps){p=data[j];var nullify=p==null;if(!nullify){for(m=0;m<ps;++m){val=p[m];f=format[m];if(f){if(f.number&&val!=null){val=+val;if(isNaN(val))val=null;else if(val==Infinity)val=fakeInfinity;else if(val==-Infinity)val=-fakeInfinity}if(val==null){if(f.required)nullify=true;if(f.defaultValue!=null)val=f.defaultValue}}points[k+m]=val}}if(nullify){for(m=0;m<ps;++m){val=points[k+m];if(val!=null){f=format[m];if(f.autoscale!==false){if(f.x){updateAxis(s.xaxis,val,val)}if(f.y){updateAxis(s.yaxis,val,val)}}}points[k+m]=null}}else{if(insertSteps&&k>0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;m<ps;++m)points[k+ps+m]=points[k+m];points[k+1]=points[k-ps+1];k+=ps}}}}for(i=0;i<series.length;++i){s=series[i];executeHooks(hooks.processDatapoints,[s,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];points=s.datapoints.points;ps=s.datapoints.pointsize;format=s.datapoints.format;var xmin=topSentry,ymin=topSentry,xmax=bottomSentry,ymax=bottomSentry;for(j=0;j<points.length;j+=ps){if(points[j]==null)continue;for(m=0;m<ps;++m){val=points[j+m];f=format[m];if(!f||f.autoscale===false||val==fakeInfinity||val==-fakeInfinity)continue;if(f.x){if(val<xmin)xmin=val;if(val>xmax)xmax=val}if(f.y){if(val<ymin)ymin=val;if(val>ymax)ymax=val}}}if(s.bars.show){var delta;switch(s.bars.align){case"left":delta=0;break;case"right":delta=-s.bars.barWidth;break;default:delta=-s.bars.barWidth/2}if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry)axis.datamin=null;if(axis.datamax==bottomSentry)axis.datamax=null})}function setupCanvases(){placeholder.css("padding",0).children().filter(function(){return!$(this).hasClass("flot-overlay")&&!$(this).hasClass("flot-base")}).remove();if(placeholder.css("position")=="static")placeholder.css("position","relative");surface=new Canvas("flot-base",placeholder);overlay=new Canvas("flot-overlay",placeholder);ctx=surface.context;octx=overlay.context;eventHolder=$(overlay.element).unbind();var existing=placeholder.data("plot");if(existing){existing.shutdown();overlay.clear()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.bind("mouseleave",onMouseLeave)}if(options.grid.clickable)eventHolder.click(onClick);executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout)clearTimeout(redrawTimeout);eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity)axis.p2c=function(p){return(p-m)*s};else axis.p2c=function(p){return(t(p)-m)*s};if(!it)axis.c2p=function(c){return m+c/s};else axis.c2p=function(c){return it(m+c/s)}}function measureTickLabels(axis){var opts=axis.options,ticks=axis.ticks||[],labelWidth=opts.labelWidth||0,labelHeight=opts.labelHeight||0,maxWidth=labelWidth||(axis.direction=="x"?Math.floor(surface.width/(ticks.length||1)):null),legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=opts.font||"flot-tick-label tickLabel";for(var i=0;i<ticks.length;++i){var t=ticks[i];if(!t.label)continue;var info=surface.getTextInfo(layer,t.label,font,null,maxWidth);labelWidth=Math.max(labelWidth,info.width);labelHeight=Math.max(labelHeight,info.height)}axis.labelWidth=opts.labelWidth||labelWidth;axis.labelHeight=opts.labelHeight||labelHeight}function allocateAxisBoxFirstPhase(axis){var lw=axis.labelWidth,lh=axis.labelHeight,pos=axis.options.position,isXAxis=axis.direction==="x",tickLength=axis.options.tickLength,axisMargin=options.grid.axisMargin,padding=options.grid.labelMargin,innermost=true,outermost=true,first=true,found=false;$.each(isXAxis?xaxes:yaxes,function(i,a){if(a&&(a.show||a.reserveSpace)){if(a===axis){found=true}else if(a.options.position===pos){if(found){outermost=false}else{innermost=false}}if(!found){first=false}}});if(outermost){axisMargin=0}if(tickLength==null){tickLength=first?"full":5}if(!isNaN(+tickLength))padding+=+tickLength;if(isXAxis){lh+=padding;if(pos=="bottom"){plotOffset.bottom+=lh+axisMargin;axis.box={top:surface.height-plotOffset.bottom,height:lh}}else{axis.box={top:plotOffset.top+axisMargin,height:lh};plotOffset.top+=lh+axisMargin}}else{lw+=padding;if(pos=="left"){axis.box={left:plotOffset.left+axisMargin,width:lw};plotOffset.left+=lw+axisMargin}else{plotOffset.right+=lw+axisMargin;axis.box={left:surface.width-plotOffset.right,width:lw}}}axis.position=pos;axis.tickLength=tickLength;axis.box.padding=padding;axis.innermost=innermost}function allocateAxisBoxSecondPhase(axis){if(axis.direction=="x"){axis.box.left=plotOffset.left-axis.labelWidth/2;axis.box.width=surface.width-plotOffset.left-plotOffset.right+axis.labelWidth}else{axis.box.top=plotOffset.top-axis.labelHeight/2;axis.box.height=surface.height-plotOffset.bottom-plotOffset.top+axis.labelHeight}}function adjustLayoutForThingsStickingOut(){var minMargin=options.grid.minBorderMargin,axis,i;if(minMargin==null){minMargin=0;for(i=0;i<series.length;++i)minMargin=Math.max(minMargin,2*(series[i].points.radius+series[i].points.lineWidth/2))}var margins={left:minMargin,right:minMargin,top:minMargin,bottom:minMargin};$.each(allAxes(),function(_,axis){if(axis.reserveSpace&&axis.ticks&&axis.ticks.length){if(axis.direction==="x"){margins.left=Math.max(margins.left,axis.labelWidth/2);margins.right=Math.max(margins.right,axis.labelWidth/2)}else{margins.bottom=Math.max(margins.bottom,axis.labelHeight/2);margins.top=Math.max(margins.top,axis.labelHeight/2)}}});plotOffset.left=Math.ceil(Math.max(margins.left,plotOffset.left));plotOffset.right=Math.ceil(Math.max(margins.right,plotOffset.right));plotOffset.top=Math.ceil(Math.max(margins.top,plotOffset.top));plotOffset.bottom=Math.ceil(Math.max(margins.bottom,plotOffset.bottom))}function setupGrid(){var i,axes=allAxes(),showGrid=options.grid.show;for(var a in plotOffset){var margin=options.grid.margin||0;plotOffset[a]=typeof margin=="number"?margin:margin[a]||0}executeHooks(hooks.processOffset,[plotOffset]);for(var a in plotOffset){if(typeof options.grid.borderWidth=="object"){plotOffset[a]+=showGrid?options.grid.borderWidth[a]:0}else{plotOffset[a]+=showGrid?options.grid.borderWidth:0}}$.each(axes,function(_,axis){var axisOpts=axis.options;axis.show=axisOpts.show==null?axis.used:axisOpts.show;axis.reserveSpace=axisOpts.reserveSpace==null?axis.show:axisOpts.reserveSpace;setRange(axis)});if(showGrid){var allocatedAxes=$.grep(axes,function(axis){return axis.show||axis.reserveSpace});$.each(allocatedAxes,function(_,axis){setupTickGeneration(axis);setTicks(axis);snapRangeToTicks(axis,axis.ticks);measureTickLabels(axis)});for(i=allocatedAxes.length-1;i>=0;--i)allocateAxisBoxFirstPhase(allocatedAxes[i]);adjustLayoutForThingsStickingOut();$.each(allocatedAxes,function(_,axis){allocateAxisBoxSecondPhase(axis)})}plotWidth=surface.width-plotOffset.left-plotOffset.right;plotHeight=surface.height-plotOffset.bottom-plotOffset.top;$.each(axes,function(_,axis){setTransformationHelpers(axis)});if(showGrid){drawAxisLabels()}insertLegend()}function setRange(axis){var opts=axis.options,min=+(opts.min!=null?opts.min:axis.datamin),max=+(opts.max!=null?opts.max:axis.datamax),delta=max-min;if(delta==0){var widen=max==0?1:.01;if(opts.min==null)min-=widen;if(opts.max==null||opts.min!=null)max+=widen}else{var margin=opts.autoscaleMargin;if(margin!=null){if(opts.min==null){min-=delta*margin;if(min<0&&axis.datamin!=null&&axis.datamin>=0)min=0}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0)max=0}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0)noTicks=opts.ticks;else noTicks=.3*Math.sqrt(axis.direction=="x"?surface.width:surface.height);var delta=(axis.max-axis.min)/noTicks,dec=-Math.floor(Math.log(delta)/Math.LN10),maxDec=opts.tickDecimals;if(maxDec!=null&&dec>maxDec){dec=maxDec}var magn=Math.pow(10,-dec),norm=delta/magn,size;if(norm<1.5){size=1}else if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else if(norm<7.5){size=5}else{size=10}size*=magn;if(opts.minTickSize!=null&&size<opts.minTickSize){size=opts.minTickSize}axis.delta=delta;axis.tickDecimals=Math.max(0,maxDec!=null?maxDec:dec);axis.tickSize=opts.tickSize||size;if(opts.mode=="time"&&!axis.tickGenerator){throw new Error("Time mode requires the flot.time plugin.")}if(!axis.tickGenerator){axis.tickGenerator=function(axis){var ticks=[],start=floorInBase(axis.min,axis.tickSize),i=0,v=Number.NaN,prev;do{prev=v;v=start+i*axis.tickSize;ticks.push(v);++i}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(value,axis){var factor=axis.tickDecimals?Math.pow(10,axis.tickDecimals):1;var formatted=""+Math.round(value*factor)/factor;if(axis.tickDecimals!=null){var decimal=formatted.indexOf(".");var precision=decimal==-1?0:formatted.length-decimal-1;if(precision<axis.tickDecimals){return(precision?formatted:formatted+".")+(""+factor).substr(1,axis.tickDecimals-precision)}}return formatted}}if($.isFunction(opts.tickFormatter))axis.tickFormatter=function(v,axis){return""+opts.tickFormatter(v,axis)};if(opts.alignTicksWithAxis!=null){var otherAxis=(axis.direction=="x"?xaxes:yaxes)[opts.alignTicksWithAxis-1];if(otherAxis&&otherAxis.used&&otherAxis!=axis){var niceTicks=axis.tickGenerator(axis);if(niceTicks.length>0){if(opts.min==null)axis.min=Math.min(axis.min,niceTicks[0]);if(opts.max==null&&niceTicks.length>1)axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}axis.tickGenerator=function(axis){var ticks=[],v,i;for(i=0;i<otherAxis.ticks.length;++i){v=(otherAxis.ticks[i].v-otherAxis.min)/(otherAxis.max-otherAxis.min);v=axis.min+v*(axis.max-axis.min);ticks.push(v)}return ticks};if(!axis.mode&&opts.tickDecimals==null){var extraDec=Math.max(0,-Math.floor(Math.log(axis.delta)/Math.LN10)+1),ts=axis.tickGenerator(axis);if(!(ts.length>1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec))))axis.tickDecimals=extraDec}}}}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||typeof oticks=="number"&&oticks>0)ticks=axis.tickGenerator(axis);else if(oticks){if($.isFunction(oticks))ticks=oticks(axis);else ticks=oticks}var i,v;axis.ticks=[];for(i=0;i<ticks.length;++i){var label=null;var t=ticks[i];if(typeof t=="object"){v=+t[0];if(t.length>1)label=t[1]}else v=+t;if(label==null)label=axis.tickFormatter(v,axis);if(!isNaN(v))axis.ticks.push({v:v,label:label})}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null)axis.min=Math.min(axis.min,ticks[0].v);if(axis.options.max==null&&ticks.length>1)axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}function draw(){surface.clear();executeHooks(hooks.drawBackground,[ctx]);var grid=options.grid;if(grid.show&&grid.backgroundColor)drawBackground();if(grid.show&&!grid.aboveData){drawGrid()}for(var i=0;i<series.length;++i){executeHooks(hooks.drawSeries,[ctx,series[i]]);drawSeries(series[i])}executeHooks(hooks.draw,[ctx]);if(grid.show&&grid.aboveData){drawGrid()}surface.render();triggerRedrawOverlay()}function extractRange(ranges,coord){var axis,from,to,key,axes=allAxes();for(var i=0;i<axes.length;++i){axis=axes[i];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?xaxes[0]:yaxes[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i,axes,bw,bc;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;i<markings.length;++i){var m=markings[i],xrange=extractRange(m,"x"),yrange=extractRange(m,"y");if(xrange.from==null)xrange.from=xrange.axis.min;if(xrange.to==null)xrange.to=xrange.axis.max;
+if(yrange.from==null)yrange.from=yrange.axis.min;if(yrange.to==null)yrange.to=yrange.axis.max;if(xrange.to<xrange.axis.min||xrange.from>xrange.axis.max||yrange.to<yrange.axis.min||yrange.from>yrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;j<axes.length;++j){var axis=axes[j],box=axis.box,t=axis.tickLength,x,y,xoff,yoff;if(!axis.show||axis.ticks.length==0)continue;ctx.lineWidth=1;if(axis.direction=="x"){x=0;if(t=="full")y=axis.position=="top"?0:plotHeight;else y=box.top-plotOffset.top+(axis.position=="top"?box.height:0)}else{y=0;if(t=="full")x=axis.position=="left"?0:plotWidth;else x=box.left-plotOffset.left+(axis.position=="left"?box.width:0)}if(!axis.innermost){ctx.strokeStyle=axis.options.color;ctx.beginPath();xoff=yoff=0;if(axis.direction=="x")xoff=plotWidth+1;else yoff=plotHeight+1;if(ctx.lineWidth==1){if(axis.direction=="x"){y=Math.floor(y)+.5}else{x=Math.floor(x)+.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff);ctx.stroke()}ctx.strokeStyle=axis.options.tickColor;ctx.beginPath();for(i=0;i<axis.ticks.length;++i){var v=axis.ticks[i].v;xoff=yoff=0;if(isNaN(v)||v<axis.min||v>axis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;i<axis.ticks.length;++i){tick=axis.ticks[i];if(!tick.label||tick.v<axis.min||tick.v>axis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i<points.length;i+=ps){var x1=points[i-ps],y1=points[i-ps+1],x2=points[i],y2=points[i+1];if(x1==null||x2==null)continue;if(y1<=y2&&y1<axisy.min){if(y2<axisy.min)continue;x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2<axisy.min){if(y1<axisy.min)continue;x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max){if(y2>axisy.max)continue;x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max){if(y1>axisy.max)continue;x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1<=x2&&x1<axisx.min){if(x2<axisx.min)continue;y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else if(x2<=x1&&x2<axisx.min){if(x1<axisx.min)continue;y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(x1!=prevx||y1!=prevy)ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset);prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps)break;i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null)continue;if(x1<=x2&&x1<axisx.min){if(x2<axisx.min)continue;y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else if(x2<=x1&&x2<axisx.min){if(x1<axisx.min)continue;y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}var x1old=x1,x2old=x2;if(y1<=y2&&y1<axisy.min&&y2>=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2<axisy.min&&y1>=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0)plotLine(series.datapoints,0,0,series.xaxis,series.yaxis);ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var x=points[i],y=points[i+1];if(x==null||x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max)continue;ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle")ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false);else symbol(ctx,x,y,radius,shadow);ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw==0)lw=1e-4;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(right<left){tmp=right;right=left;left=tmp;drawLeft=true;drawRight=false}}else{drawLeft=drawRight=drawTop=true;drawBottom=false;left=x+barLeft;right=x+barRight;bottom=b;top=y;if(top<bottom){tmp=top;top=bottom;bottom=tmp;drawBottom=true;drawTop=false}}if(right<axisx.min||left>axisx.max||top<axisy.min||bottom>axisy.max)return;if(left<axisx.min){left=axisx.min;drawLeft=false}if(right>axisx.max){right=axisx.max;drawRight=false}if(bottom<axisy.min){bottom=axisy.min;drawBottom=false}if(top>axisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.fillStyle=fillStyleCallback(bottom,top);c.fillRect(left,top,right-left,bottom-top)}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom);if(drawLeft)c.lineTo(left,top);else c.moveTo(left,top);if(drawTop)c.lineTo(right,top);else c.moveTo(right,top);if(drawRight)c.lineTo(right,bottom);else c.moveTo(right,bottom);if(drawBottom)c.lineTo(left,bottom);else c.moveTo(left,bottom);c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){if(points[i]==null)continue;drawBar(points[i],points[i+1],points[i+2],barLeft,barRight,fillStyleCallback,axisx,axisy,ctx,series.bars.horizontal,series.bars.lineWidth)}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineWidth=series.bars.lineWidth;ctx.strokeStyle=series.color;var barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}var fillStyleCallback=series.bars.fill?function(bottom,top){return getFillStyle(series.bars,series.color,bottom,top)}:null;plotBars(series.datapoints,barLeft,barLeft+series.bars.barWidth,fillStyleCallback,series.xaxis,series.yaxis);ctx.restore()}function getFillStyle(filloptions,seriesColor,bottom,top){var fill=filloptions.fill;if(!fill)return null;if(filloptions.fillColor)return getColorOrGradient(filloptions.fillColor,bottom,top,seriesColor);var c=$.color.parse(seriesColor);c.a=typeof fill=="number"?fill:.4;c.normalize();return c.toString()}function insertLegend(){if(options.legend.container!=null){$(options.legend.container).html("")}else{placeholder.find(".legend").remove()}if(!options.legend.show){return}var fragments=[],entries=[],rowStarted=false,lf=options.legend.labelFormatter,s,label;for(var i=0;i<series.length;++i){s=series[i];if(s.label){label=lf?lf(s.label,s):s.label;if(label){entries.push({label:label,color:s.color})}}}if(options.legend.sorted){if($.isFunction(options.legend.sorted)){entries.sort(options.legend.sorted)}else if(options.legend.sorted=="reverse"){entries.reverse()}else{var ascending=options.legend.sorted!="descending";entries.sort(function(a,b){return a.label==b.label?0:a.label<b.label!=ascending?1:-1})}}for(var i=0;i<entries.length;++i){var entry=entries[i];if(i%options.legend.noColumns==0){if(rowStarted)fragments.push("</tr>");fragments.push("<tr>");rowStarted=true}fragments.push('<td class="legendColorBox"><div style="border:1px solid '+options.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+entry.color+';overflow:hidden"></div></div></td>'+'<td class="legendLabel">'+entry.label+"</td>")}if(rowStarted)fragments.push("</tr>");if(fragments.length==0)return;var table='<table style="font-size:smaller;color:'+options.grid.color+'">'+fragments.join("")+"</table>";if(options.legend.container!=null)$(options.legend.container).html(table);else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null)m=[m,m];if(p.charAt(0)=="n")pos+="top:"+(m[1]+plotOffset.top)+"px;";else if(p.charAt(0)=="s")pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;";if(p.charAt(1)=="e")pos+="right:"+(m[0]+plotOffset.right)+"px;";else if(p.charAt(1)=="w")pos+="left:"+(m[0]+plotOffset.left)+"px;";var legend=$('<div class="legend">'+table.replace('style="','style="position:absolute;'+pos+";")+"</div>").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string")c=$.color.parse(c);else c=$.color.extract(legend,"background-color");c.a=1;c=c.toString()}var div=legend.children();$('<div style="position:absolute;width:'+div.width()+"px;height:"+div.height()+"px;"+pos+"background-color:"+c+';"> </div>').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j,ps;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i]))continue;var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;ps=s.datapoints.pointsize;if(axisx.options.inverseTransform)maxx=Number.MAX_VALUE;if(axisy.options.inverseTransform)maxy=Number.MAX_VALUE;if(s.lines.show||s.points.show){for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1];if(x==null)continue;if(x-mx>maxx||x-mx<-maxx||y-my>maxy||y-my<-maxy)continue;var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist<smallestDistance){smallestDistance=dist;item=[i,j/ps]}}}if(s.bars.show&&!item){var barLeft,barRight;switch(s.bars.align){case"left":barLeft=0;break;case"right":barLeft=-s.bars.barWidth;break;default:barLeft=-s.bars.barWidth/2}barRight=barLeft+s.bars.barWidth;for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1],b=points[j+2];if(x==null)continue;if(series[i].bars.horizontal?mx<=Math.max(b,x)&&mx>=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight:mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))item=[i,j/ps]}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return s["hoverable"]!=false})}function onMouseLeave(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return false})}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s["clickable"]!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left,10);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top,10)}if(options.grid.autoHighlight){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.auto==eventname&&!(item&&h.series==item.series&&h.point[0]==item.datapoint[0]&&h.point[1]==item.datapoint[1]))unhighlight(h.series,h.point)}if(item)highlight(item.series,item.datapoint,eventname)}placeholder.trigger(eventname,[pos,item])}function triggerRedrawOverlay(){var t=options.interaction.redrawOverlayInterval;if(t==-1){drawOverlay();return}if(!redrawTimeout)redrawTimeout=setTimeout(drawOverlay,t)}function drawOverlay(){redrawTimeout=null;octx.save();overlay.clear();octx.translate(plotOffset.left,plotOffset.top);var i,hi;for(i=0;i<highlights.length;++i){hi=highlights[i];if(hi.series.bars.show)drawBarHighlight(hi.series,hi.point);else drawPointHighlight(hi.series,hi.point)}octx.restore();executeHooks(hooks.drawOverlay,[octx])}function highlight(s,point,auto){if(typeof s=="number")s=series[s];if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i==-1){highlights.push({series:s,point:point,auto:auto});triggerRedrawOverlay()}else if(!auto)highlights[i].auto=false}function unhighlight(s,point){if(s==null&&point==null){highlights=[];triggerRedrawOverlay();return}if(typeof s=="number")s=series[s];if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i!=-1){highlights.splice(i,1);triggerRedrawOverlay()}}function indexOfHighlight(s,p){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.series==s&&h.point[0]==p[0]&&h.point[1]==p[1])return i}return-1}function drawPointHighlight(series,point){var x=point[0],y=point[1],axisx=series.xaxis,axisy=series.yaxis,highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString();if(x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max)return;var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=highlightColor;var radius=1.5*pointRadius;x=axisx.p2c(x);y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle")octx.arc(x,y,radius,0,2*Math.PI,false);else series.points.symbol(octx,x,y,radius,false);octx.closePath();octx.stroke()}function drawBarHighlight(series,point){var highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString(),fillStyle=highlightColor,barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=highlightColor;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string")return spec;else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i<l;++i){var c=spec.colors[i];if(typeof c!="string"){var co=$.color.parse(defaultColor);if(c.brightness!=null)co=co.scale("rgb",c.brightness);if(c.opacity!=null)co.a*=c.opacity;c=co.toString()}gradient.addColorStop(i/(l-1),c)}return gradient}}}$.plot=function(placeholder,data,options){var plot=new Plot($(placeholder),data,options,$.plot.plugins);return plot};$.plot.version="0.8.3";$.plot.plugins=[];$.fn.plot=function(data,options){return this.each(function(){$.plot(this,data,options)})};function floorInBase(n,base){return base*Math.floor(n/base)}})(jQuery); \ No newline at end of file
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.stack.min.js b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.stack.min.js
new file mode 100644
index 00000000..920764f5
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.stack.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i<allseries.length;++i){if(s==allseries[i])break;if(allseries[i].stack==s.stack)res=allseries[i]}return res}function stackData(plot,s,datapoints){if(s.stack==null||s.stack===false)return;var other=findMatchingSeries(s,plot.getData());if(!other)return;var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,horizontal=s.bars.horizontal,withbottom=ps>2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m)newpoints.push(points[i+m]);i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m)newpoints.push(points[i+m])}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m)newpoints.push(null);fromgap=true;j+=otherps}else{px=points[i+keyOffset];py=points[i+accumulateOffset];qx=otherpoints[j+keyOffset];qy=otherpoints[j+accumulateOffset];bottom=0;if(px==qx){for(m=0;m<ps;++m)newpoints.push(points[i+m]);newpoints[l+accumulateOffset]+=qy;bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m<ps;++m)newpoints.push(points[i+m]);bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m)newpoints.push(points[i+m]);if(withlines&&j>0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m<ps;++m)newpoints[l+ps+m]=newpoints[l+m];newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(stackData)}$.plot.plugins.push({init:init,options:options,name:"stack",version:"1.2"})})(jQuery); \ No newline at end of file
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.tooltip.min.js b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.tooltip.min.js
new file mode 100644
index 00000000..8e98bdc5
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/jquery.flot.tooltip.min.js
@@ -0,0 +1,12 @@
+/*
+ * jquery.flot.tooltip
+ *
+ * description: easy-to-use tooltips for Flot charts
+ * version: 0.8.4
+ * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround
+ * website: https://github.com/krzysu/flot.tooltip
+ *
+ * build on 2014-08-06
+ * released under MIT License, 2012
+*/
+!function(a){var b={tooltip:!1,tooltipOpts:{id:"flotTip",content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,monthNames:null,dayNames:null,shifts:{x:10,y:20},defaultTheme:!0,lines:!1,onHover:function(){},$compat:!1}},c=function(a){this.tipPosition={x:0,y:0},this.init(a)};c.prototype.init=function(b){function c(a){var c={};c.x=a.pageX,c.y=a.pageY,b.setTooltipPosition(c)}function d(c,d,f){var g=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},h=function(a,b,c,d,e,f,h){if(!h||(h=function(a,b,c,d,e,f){if("undefined"!=typeof c)return{x:c,y:b};if("undefined"!=typeof d)return{x:a,y:d};var g,h=-1/((f-d)/(e-c));return{x:g=(e*(a*h-b+d)+c*(a*-h+b-f))/(h*(e-c)+d-f),y:h*g-h*a+b}}(a,b,c,d,e,f),h.x>=Math.min(c,e)&&h.x<=Math.max(c,e)&&h.y>=Math.min(d,f)&&h.y<=Math.max(d,f))){var i=d-f,j=e-c,k=c*f-d*e;return Math.abs(i*a+j*b+k)/Math.sqrt(i*i+j*j)}var l=g(a,b,c,d),m=g(a,b,e,f);return l>m?m:l};if(f)b.showTooltip(f,d);else if(e.plotOptions.series.lines.show&&e.tooltipOptions.lines===!0){var i=e.plotOptions.grid.mouseActiveRadius,j={distance:i+1};a.each(b.getData(),function(a,c){for(var e=0,f=-1,i=1;i<c.data.length;i++)c.data[i-1][0]<=d.x&&c.data[i][0]>=d.x&&(e=i-1,f=i);if(-1===f)return void b.hideTooltip();var k={x:c.data[e][0],y:c.data[e][1]},l={x:c.data[f][0],y:c.data[f][1]},m=h(c.xaxis.p2c(d.x),c.yaxis.p2c(d.y),c.xaxis.p2c(k.x),c.yaxis.p2c(k.y),c.xaxis.p2c(l.x),c.yaxis.p2c(l.y),!1);if(m<j.distance){var n=g(k.x,k.y,d.x,d.y)<g(d.x,d.y,l.x,l.y)?e:f,o=(c.datapoints.pointsize,[d.x,k.y+(l.y-k.y)*((d.x-k.x)/(l.x-k.x))]),p={datapoint:o,dataIndex:n,series:c,seriesIndex:a};j={distance:m,item:p}}}),j.distance<i+1?b.showTooltip(j.item,d):b.hideTooltip()}else b.hideTooltip()}var e=this,f=a.plot.plugins.length;if(this.plotPlugins=[],f)for(var g=0;f>g;g++)this.plotPlugins.push(a.plot.plugins[g].name);b.hooks.bindEvents.push(function(b,f){if(e.plotOptions=b.getOptions(),e.plotOptions.tooltip!==!1&&"undefined"!=typeof e.plotOptions.tooltip){e.tooltipOptions=e.plotOptions.tooltipOpts,e.tooltipOptions.$compat?(e.wfunc="width",e.hfunc="height"):(e.wfunc="innerWidth",e.hfunc="innerHeight");{e.getDomElement()}a(b.getPlaceholder()).bind("plothover",d),a(f).bind("mousemove",c)}}),b.hooks.shutdown.push(function(b,e){a(b.getPlaceholder()).unbind("plothover",d),a(e).unbind("mousemove",c)}),b.setTooltipPosition=function(b){var c=e.getDomElement(),d=c.outerWidth()+e.tooltipOptions.shifts.x,f=c.outerHeight()+e.tooltipOptions.shifts.y;b.x-a(window).scrollLeft()>a(window)[e.wfunc]()-d&&(b.x-=d),b.y-a(window).scrollTop()>a(window)[e.hfunc]()-f&&(b.y-=f),e.tipPosition.x=b.x,e.tipPosition.y=b.y},b.showTooltip=function(a,c){var d=e.getDomElement(),f=e.stringFormat(e.tooltipOptions.content,a);d.html(f),b.setTooltipPosition({x:c.pageX,y:c.pageY}),d.css({left:e.tipPosition.x+e.tooltipOptions.shifts.x,top:e.tipPosition.y+e.tooltipOptions.shifts.y}).show(),"function"==typeof e.tooltipOptions.onHover&&e.tooltipOptions.onHover(a,d)},b.hideTooltip=function(){e.getDomElement().hide().html("")}},c.prototype.getDomElement=function(){var b=a("#"+this.tooltipOptions.id);return 0===b.length&&(b=a("<div />").attr("id",this.tooltipOptions.id),b.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&b.css({background:"#fff","z-index":"1040",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),b},c.prototype.stringFormat=function(a,b){var c,d,e,f,g=/%p\.{0,1}(\d{0,})/,h=/%s/,i=/%lx/,j=/%ly/,k=/%x\.{0,1}(\d{0,})/,l=/%y\.{0,1}(\d{0,})/,m="%x",n="%y",o="%ct";if("undefined"!=typeof b.series.threshold?(c=b.datapoint[0],d=b.datapoint[1],e=b.datapoint[2]):"undefined"!=typeof b.series.lines&&b.series.lines.steps?(c=b.series.datapoints.points[2*b.dataIndex],d=b.series.datapoints.points[2*b.dataIndex+1],e=""):(c=b.series.data[b.dataIndex][0],d=b.series.data[b.dataIndex][1],e=b.series.data[b.dataIndex][2]),null===b.series.label&&b.series.originSeries&&(b.series.label=b.series.originSeries.label),"function"==typeof a&&(a=a(b.series.label,c,d,b)),"undefined"!=typeof b.series.percent?f=b.series.percent:"undefined"!=typeof b.series.percents&&(f=b.series.percents[b.dataIndex]),"number"==typeof f&&(a=this.adjustValPrecision(g,a,f)),a="undefined"!=typeof b.series.label?a.replace(h,b.series.label):a.replace(h,""),a=this.hasAxisLabel("xaxis",b)?a.replace(i,b.series.xaxis.options.axisLabel):a.replace(i,""),a=this.hasAxisLabel("yaxis",b)?a.replace(j,b.series.yaxis.options.axisLabel):a.replace(j,""),this.isTimeMode("xaxis",b)&&this.isXDateFormat(b)&&(a=a.replace(k,this.timestampToDate(c,this.tooltipOptions.xDateFormat,b.series.xaxis.options))),this.isTimeMode("yaxis",b)&&this.isYDateFormat(b)&&(a=a.replace(l,this.timestampToDate(d,this.tooltipOptions.yDateFormat,b.series.yaxis.options))),"number"==typeof c&&(a=this.adjustValPrecision(k,a,c)),"number"==typeof d&&(a=this.adjustValPrecision(l,a,d)),"undefined"!=typeof b.series.xaxis.ticks){var p;p=this.hasRotatedXAxisTicks(b)?"rotatedTicks":"ticks";var q=b.dataIndex+b.seriesIndex;if(b.series.xaxis[p].length>q&&!this.isTimeMode("xaxis",b)){var r=this.isCategoriesMode("xaxis",b)?b.series.xaxis[p][q].label:b.series.xaxis[p][q].v;r===c&&(a=a.replace(k,b.series.xaxis[p][q].label))}}if("undefined"!=typeof b.series.yaxis.ticks)for(var s in b.series.yaxis.ticks)if(b.series.yaxis.ticks.hasOwnProperty(s)){var t=this.isCategoriesMode("yaxis",b)?b.series.yaxis.ticks[s].label:b.series.yaxis.ticks[s].v;t===d&&(a=a.replace(l,b.series.yaxis.ticks[s].label))}return"undefined"!=typeof b.series.xaxis.tickFormatter&&(a=a.replace(m,b.series.xaxis.tickFormatter(c,b.series.xaxis).replace(/\$/g,"$$"))),"undefined"!=typeof b.series.yaxis.tickFormatter&&(a=a.replace(n,b.series.yaxis.tickFormatter(d,b.series.yaxis).replace(/\$/g,"$$"))),e&&(a=a.replace(o,e)),a},c.prototype.isTimeMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"time"===b.series[a].options.mode},c.prototype.isXDateFormat=function(){return"undefined"!=typeof this.tooltipOptions.xDateFormat&&null!==this.tooltipOptions.xDateFormat},c.prototype.isYDateFormat=function(){return"undefined"!=typeof this.tooltipOptions.yDateFormat&&null!==this.tooltipOptions.yDateFormat},c.prototype.isCategoriesMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"categories"===b.series[a].options.mode},c.prototype.timestampToDate=function(b,c,d){var e=a.plot.dateGenerator(b,d);return a.plot.formatDate(e,c,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},c.prototype.adjustValPrecision=function(a,b,c){var d,e=b.match(a);return null!==e&&""!==RegExp.$1&&(d=RegExp.$1,c=c.toFixed(d),b=b.replace(a,c)),b},c.prototype.hasAxisLabel=function(b,c){return-1!==a.inArray(this.plotPlugins,"axisLabels")&&"undefined"!=typeof c.series[b].options.axisLabel&&c.series[b].options.axisLabel.length>0},c.prototype.hasRotatedXAxisTicks=function(b){return-1!==a.inArray(this.plotPlugins,"tickRotor")&&"undefined"!=typeof b.series.xaxis.rotatedTicks};var d=function(a){new c(a)};a.plot.plugins.push({init:d,options:b,name:"tooltip",version:"0.8.4"})}(jQuery); \ No newline at end of file
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/memory_panel.html b/venv/lib/python3.9/site-packages/pympler/templates/memory_panel.html
new file mode 100644
index 00000000..f68cc379
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/memory_panel.html
@@ -0,0 +1,58 @@
+{% load i18n %}{% load static %}
+<script type="text/javascript" src="{% static 'jquery.sparkline.min.js' %}"></script>
+<table>
+ <colgroup>
+ <col style="width:20%"/>
+ <col/>
+ </colgroup>
+ <thead>
+ <tr>
+ <th>{% trans "Resource" %}</th>
+ <th>{% trans "Value" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for key, value in rows %}
+ <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
+ <td>{{ key|escape }}</td>
+ <td>{{ value|escape }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+
+<table>
+ <colgroup>
+ <col style="width:20%"/>
+ <col/>
+ <col/>
+ </colgroup>
+ <thead>
+ <tr>
+ <th>{% trans "Class" %}</th>
+ <th>{% trans "Number of instances" %} <a class="show_sparkline" href="#">Show sparklines</a></th>
+ <th>{% trans "Total size" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for cls, history, size in classes %}
+ <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
+ <td>{{ cls|escape }}</td>
+ <td id="{{ cls|escape|cut:'.' }}_history" values="{{ history|join:',' }}">
+ {{ history|safeseq|join:', ' }}
+ </td>
+ <td>{{ size|escape }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+<script type="text/javascript">
+ (function ($) {
+ window.jQuery = $; // for jquery.sparkline
+ $("#MemoryPanel .show_sparkline").on('click', function() {
+ {% for cls, _, _ in classes %}
+ $("#{{ cls|escape|cut:'.' }}_history").sparkline('html', {width: '200px'});
+ {% endfor %}
+ });
+ })(jQuery || djdt.jQuery);
+</script>
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/process.tpl b/venv/lib/python3.9/site-packages/pympler/templates/process.tpl
new file mode 100644
index 00000000..3f5d54ff
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/process.tpl
@@ -0,0 +1,89 @@
+%include('header', category='Process', title='Process Information')
+%from pympler.util.stringutils import pp
+
+<h1>Process information</h1>
+
+<table class="tdata">
+ <tbody>
+ <tr>
+ <th>Virtual size:</th>
+ <td class="num">{{pp(info.vsz)}}</td>
+ </tr>
+ <tr>
+ <th>Physical memory size:</th>
+ <td class="num">{{pp(info.rss)}}</td>
+ </tr>
+ <tr>
+ <th>Major pagefaults:</th>
+ <td class="num">{{info.pagefaults}}</td>
+ </tr>
+ %for key, value in info.os_specific:
+ <tr>
+ <th>{{key}}:</th>
+ <td class="num">{{value}}</td>
+ </tr>
+ %end
+ </tbody>
+</table>
+
+<h2>Thread information</h2>
+
+<table class="tdata">
+ <tbody>
+ <tr>
+ <th>ID</th>
+ <th>Name</th>
+ <th>Daemon</th>
+ </tr>
+ %for tinfo in threads:
+ <tr>
+ <td>{{tinfo.ident}}</td>
+ <td>{{tinfo.name}}</td>
+ <td>{{tinfo.daemon}}</td>
+ </tr>
+ %end
+ </tbody>
+</table>
+
+<h2>Thread stacks</h2>
+
+<div class="stacks">
+ %for tinfo in threads:
+ <div class="stacktrace" id="{{tinfo.ident}}">
+ <a class="show_traceback" href="#">Traceback for thread {{tinfo.name}}</a>
+ </div>
+ %end
+</div>
+
+<script type="text/javascript">
+ $(".show_traceback").click(function() {
+ var tid = $(this).parent().attr("id");
+ $.get("/traceback/"+tid, function(data) {
+ $("#"+tid).replaceWith(data);
+ });
+ return false;
+ });
+ $(".stacks").delegate(".expand_local", "click", function() {
+ var oid = $(this).attr("id");
+ $.get("/objects/"+oid, function(data) {
+ $("#"+oid).replaceWith(data);
+ });
+ return false;
+ });
+ $(".stacks").delegate(".expand_ref", "click", function() {
+ var node_id = $(this).attr("id");
+ var oid = node_id.split("_")[0];
+ $.get("/objects/"+oid, function(data) {
+ $("#children_"+node_id).append(data);
+ });
+ $(this).removeClass("expand_ref").addClass("toggle_ref");
+ return false;
+ });
+ $(".stacks").delegate(".toggle_ref", "click", function() {
+ var node_id = $(this).attr("id");
+ $("#children_"+node_id).toggle();
+ return false;
+ });
+</script>
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/referents.tpl b/venv/lib/python3.9/site-packages/pympler/templates/referents.tpl
new file mode 100644
index 00000000..6a880622
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/referents.tpl
@@ -0,0 +1,13 @@
+%from random import randint
+%for name, (ref, type_name, obj_repr, size) in referents.items():
+ %ref = "%s_%s" % (ref, randint(0, 65535))
+ <div class="referents">
+ <a class="expand_ref" id="{{ref}}" href="#">
+ <span class="local_name">{{name}}</span>
+ <span class="local_type">{{type_name}}</span>
+ <span class="local_size">{{size}}</span>
+ <span class="local_value">{{obj_repr}}</span>
+ </a>
+ <span id="children_{{ref}}"/>
+ </div>
+%end
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/stacktrace.tpl b/venv/lib/python3.9/site-packages/pympler/templates/stacktrace.tpl
new file mode 100644
index 00000000..c56588dc
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/stacktrace.tpl
@@ -0,0 +1,30 @@
+<div class="stacktrace">
+ <strong>Stacktrace for thread {{threadid}}</strong>
+ %for frame in stack:
+ <div class="stackframe">
+ <span class="filename">{{frame[1]}}</span>
+ <span class="lineno">{{frame[2]}}</span>
+ <span class="function">{{frame[3]}}</span>
+ %if frame[4]:
+ %context = frame[4]
+ <div class="context">
+ %highlight = len(context) / 2
+ %for idx, line in enumerate(frame[4]):
+ %hl = (idx == highlight) and "highlighted" or ""
+ %if line.strip():
+ <div class="{{hl}}" style="padding-left:{{len(line)-len(line.lstrip())}}em" width="100%">
+ {{line.strip()}}
+ </div>
+ %end
+ %end
+ </div>
+ %end
+ <div class="local">
+ <a class="expand_local" id="{{frame[0]}}" href="#">Show locals</a>
+ </div>
+ </div>
+ %end
+ %if not stack:
+ Cannot retrieve stacktrace for thread {{threadid}}.
+ %end
+</div>
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/style.css b/venv/lib/python3.9/site-packages/pympler/templates/style.css
new file mode 100644
index 00000000..d7fcc6a5
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/style.css
@@ -0,0 +1,992 @@
+/**
+ * Sphinx Doc Design
+ */
+
+body {
+ font-family: "Verdana", "Tahoma", Sans-Serif;
+ font-size: 90%;
+ background-color: #11303d;
+ color: #000;
+ margin: 0;
+ padding: 0;
+}
+
+/* :::: LAYOUT :::: */
+
+div.document {
+ background-color: #158906;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 0;
+}
+
+div.body {
+ background-color: white;
+ padding: 0 20px 16px 20px;
+}
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+
+div.sphinxsidebar {
+ display: none;
+}
+
+div.sphinxsidebar {
+ float: right;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+}
+
+div.clearer {
+ clear: both;
+}
+
+div.footer {
+ color: #fff;
+ width: 100%;
+ padding: 9px 0 9px 0;
+ text-align: center;
+ font-size: 75%;
+}
+
+div.footer a {
+ color: #fff;
+ text-decoration: underline;
+}
+
+div.related {
+ background-color: #5A3D31;
+ color: #fff;
+ width: 100%;
+ height: 30px;
+ line-height: 30px;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+div.related a {
+ color: white;
+}
+
+/* ::: TOC :::: */
+div.sphinxsidebar h3 {
+ color: white;
+ font-size: 1.4em;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+
+div.sphinxsidebar h4 {
+ color: white;
+ font-size: 1.3em;
+ font-weight: normal;
+ margin: 5px 0 0 0;
+ padding: 0;
+}
+
+div.sphinxsidebar p {
+ color: white;
+}
+
+div.sphinxsidebar p.topless {
+ margin: 5px 10px 10px 10px;
+}
+
+div.sphinxsidebar ul {
+ margin: 10px;
+ padding: 0;
+ list-style: none;
+ color: white;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar a {
+ color: #A4FF98;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #A4FF98;
+ font-size: 1em;
+}
+
+/* :::: MODULE CLOUD :::: */
+div.modulecloud {
+ margin: -5px 10px 5px 10px;
+ padding: 10px;
+ line-height: 160%;
+ border: 1px solid #cbe7e5;
+ background-color: #f2fbfd;
+}
+
+div.modulecloud a {
+ padding: 0 5px 0 5px;
+}
+
+/* :::: SEARCH :::: */
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li div.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* :::: COMMON FORM STYLES :::: */
+
+div.actions {
+ padding: 5px 10px 5px 10px;
+ border-top: 1px solid #cbe7e5;
+ border-bottom: 1px solid #cbe7e5;
+ background-color: #e0f6f4;
+}
+
+form dl {
+ color: #333;
+}
+
+form dt {
+ clear: both;
+ float: left;
+ min-width: 110px;
+ margin-right: 10px;
+ padding-top: 2px;
+}
+
+input#homepage {
+ display: none;
+}
+
+div.error {
+ margin: 5px 20px 0 0;
+ padding: 5px;
+ border: 1px solid #d00;
+ font-weight: bold;
+}
+
+/* :::: INLINE COMMENTS :::: */
+
+div.inlinecomments {
+ position: absolute;
+ right: 20px;
+}
+
+div.inlinecomments a.bubble {
+ display: block;
+ float: right;
+ background-image: url(style/comment.png);
+ background-repeat: no-repeat;
+ width: 25px;
+ height: 25px;
+ text-align: center;
+ padding-top: 3px;
+ font-size: 0.9em;
+ line-height: 14px;
+ font-weight: bold;
+ color: black;
+}
+
+div.inlinecomments a.bubble span {
+ display: none;
+}
+
+div.inlinecomments a.emptybubble {
+ background-image: url(style/nocomment.png);
+}
+
+div.inlinecomments a.bubble:hover {
+ background-image: url(style/hovercomment.png);
+ text-decoration: none;
+ color: #3ca0a4;
+}
+
+div.inlinecomments div.comments {
+ float: right;
+ margin: 25px 5px 0 0;
+ max-width: 50em;
+ min-width: 30em;
+ border: 1px solid #2eabb0;
+ background-color: #f2fbfd;
+ z-index: 150;
+}
+
+div#comments {
+ border: 1px solid #2eabb0;
+ margin-top: 20px;
+}
+
+div#comments div.nocomments {
+ padding: 10px;
+ font-weight: bold;
+}
+
+div.inlinecomments div.comments h3,
+div#comments h3 {
+ margin: 0;
+ padding: 0;
+ background-color: #2eabb0;
+ color: white;
+ border: none;
+ padding: 3px;
+}
+
+div.inlinecomments div.comments div.actions {
+ padding: 4px;
+ margin: 0;
+ border-top: none;
+}
+
+div#comments div.comment {
+ margin: 10px;
+ border: 1px solid #2eabb0;
+}
+
+div.inlinecomments div.comment h4,
+div.commentwindow div.comment h4,
+div#comments div.comment h4 {
+ margin: 10px 0 0 0;
+ background-color: #2eabb0;
+ color: white;
+ border: none;
+ padding: 1px 4px 1px 4px;
+}
+
+div#comments div.comment h4 {
+ margin: 0;
+}
+
+div#comments div.comment h4 a {
+ color: #d5f4f4;
+}
+
+div.inlinecomments div.comment div.text,
+div.commentwindow div.comment div.text,
+div#comments div.comment div.text {
+ margin: -5px 0 -5px 0;
+ padding: 0 10px 0 10px;
+}
+
+div.inlinecomments div.comment div.meta,
+div.commentwindow div.comment div.meta,
+div#comments div.comment div.meta {
+ text-align: right;
+ padding: 2px 10px 2px 0;
+ font-size: 95%;
+ color: #538893;
+ border-top: 1px solid #cbe7e5;
+ background-color: #e0f6f4;
+}
+
+div.commentwindow {
+ position: absolute;
+ width: 500px;
+ border: 1px solid #cbe7e5;
+ background-color: #f2fbfd;
+ display: none;
+ z-index: 130;
+}
+
+div.commentwindow h3 {
+ margin: 0;
+ background-color: #2eabb0;
+ color: white;
+ border: none;
+ padding: 5px;
+ font-size: 1.5em;
+ cursor: pointer;
+}
+
+div.commentwindow div.actions {
+ margin: 10px -10px 0 -10px;
+ padding: 4px 10px 4px 10px;
+ color: #538893;
+}
+
+div.commentwindow div.actions input {
+ border: 1px solid #2eabb0;
+ background-color: white;
+ color: #135355;
+ cursor: pointer;
+}
+
+div.commentwindow div.form {
+ padding: 0 10px 0 10px;
+}
+
+div.commentwindow div.form input,
+div.commentwindow div.form textarea {
+ border: 1px solid #3c9ea2;
+ background-color: white;
+ color: black;
+}
+
+div.commentwindow div.error {
+ margin: 10px 5px 10px 5px;
+ background-color: #fbe5dc;
+ display: none;
+}
+
+div.commentwindow div.form textarea {
+ width: 99%;
+}
+
+div.commentwindow div.preview {
+ margin: 10px 0 10px 0;
+ background-color: #70d0d4;
+ padding: 0 1px 1px 25px;
+}
+
+div.commentwindow div.preview h4 {
+ margin: 0 0 -5px -20px;
+ padding: 4px 0 0 4px;
+ color: white;
+ font-size: 1.3em;
+}
+
+div.commentwindow div.preview div.comment {
+ background-color: #f2fbfd;
+}
+
+div.commentwindow div.preview div.comment h4 {
+ margin: 10px 0 0 0!important;
+ padding: 1px 4px 1px 4px!important;
+ font-size: 1.2em;
+}
+
+/* :::: SUGGEST CHANGES :::: */
+div#suggest-changes-box input, div#suggest-changes-box textarea {
+ border: 1px solid #ccc;
+ background-color: white;
+ color: black;
+}
+
+div#suggest-changes-box textarea {
+ width: 99%;
+ height: 400px;
+}
+
+
+/* :::: PREVIEW :::: */
+div.preview {
+ background-image: url(style/preview.png);
+ padding: 0 20px 20px 20px;
+ margin-bottom: 30px;
+}
+
+
+/* :::: INDEX PAGE :::: */
+
+table.contentstable {
+ width: 90%;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* :::: INDEX STYLES :::: */
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+form.pfform {
+ margin: 10px 0 20px 0;
+}
+
+/* :::: GLOBAL STYLES :::: */
+
+.docwarning {
+ background-color: #ffe4e4;
+ padding: 10px;
+ margin: 0 -20px 0 -20px;
+ border-bottom: 1px solid #f66;
+}
+
+p.subhead {
+ font-weight: bold;
+ margin-top: 20px;
+}
+
+a {
+ color: #355F7C;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-weight: bold;
+ margin: 16px -20px 16px -20px;
+ padding: 3px 0 3px 10px;
+}
+
+div.body h1 {
+ margin-top: 0;
+ font-size: 140%;
+ background-color: #E5EDB8;
+ color: #000;
+ border-bottom: 1px solid #ccc;
+}
+div.body h2 {
+ font-size: 120%;
+ background-color: #E5EDB8;
+ color: #000;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+div.body h3 {
+ font-size: 120%;
+ border-bottom: 1px dashed #ccc;
+}
+div.body h4 { font-size: 120%; }
+div.body h5 { font-size: 110%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+ color: #c60f0f;
+ font-size: 0.8em;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+ visibility: hidden;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink {
+ visibility: visible;
+}
+
+a.headerlink:hover {
+ background-color: #c60f0f;
+ color: white;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 100%;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+ul.fakelist {
+ list-style: none;
+ margin: 10px 0 10px 20px;
+ padding: 0;
+}
+
+.field-list ul {
+ padding-left: 1em;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+/* "Footnotes" heading */
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+/* "Topics" */
+
+div.topic {
+ background-color: #eee;
+ border: 1px solid #ccc;
+ padding: 0 7px 0 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* Admonitions */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+div.admonition dl {
+ margin-bottom: 0;
+}
+
+div.admonition p {
+ display: inline;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #f66;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+table.docutils {
+ border: 0;
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 0;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+table.footnote td, table.footnote th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+dl {
+ margin-bottom: 1px;
+ clear: both;
+}
+
+dd p {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.refcount {
+ color: #060;
+}
+
+dt:target,
+.highlight {
+ background-color: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+pre {
+ padding: 5px;
+ background-color: #efc;
+ color: #333;
+ border: 1px solid #ac9;
+ border-left: none;
+ border-right: none;
+ overflow: auto;
+}
+
+td.linenos pre {
+ padding: 5px 0px;
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ margin-left: 0.5em;
+}
+
+table.highlighttable td {
+ padding: 0 0.5em 0 0.5em;
+}
+
+tt {
+ background-color: #ecf0f3;
+ padding: 0 1px 0 1px;
+ font-size: 0.95em;
+}
+
+tt.descname {
+ background-color: transparent;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+tt.descclassname {
+ background-color: transparent;
+}
+
+tt.xref, a tt {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.footnote:target { background-color: #ffa }
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+form.comment {
+ margin: 0;
+ padding: 10px 30px 10px 30px;
+ background-color: #eee;
+}
+
+form.comment h3 {
+ background-color: #326591;
+ color: white;
+ margin: -10px -30px 10px -30px;
+ padding: 5px;
+ font-size: 1.4em;
+}
+
+form.comment input,
+form.comment textarea {
+ border: 1px solid #ccc;
+ padding: 2px;
+ font-size: 100%;
+}
+
+form.comment input[type="text"] {
+ width: 240px;
+}
+
+form.comment textarea {
+ width: 100%;
+ height: 200px;
+ margin-bottom: 10px;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+/* :::: PRINT :::: */
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0;
+ width : 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ div#comments div.new-comment-box,
+ #top-link {
+ display: none;
+ }
+}
+
+/* :::: OWN :::: */
+
+.section ul {
+ margin: 0.2em;
+}
+
+/* Data tables */
+
+table.tdata
+{
+ font-size: 12px;
+ background: #fff;
+ border-collapse: collapse;
+ text-align: left;
+}
+table.tdata th
+{
+ font-size: 14px;
+ font-weight: normal;
+ padding: 8px 8px;
+ background-color: #E5EDB8;
+ border-bottom: 1px solid #ccc;
+}
+
+table.tdata tr:first-child th:first-child
+{
+ border-top-left-radius: 8px;
+}
+
+table.tdata tr:first-child th:last-child
+{
+ border-top-right-radius: 8px;
+}
+
+table.tdata td
+{
+ border-bottom: 1px solid #ccc;
+ padding: 8px 8px;
+}
+
+table.tdata tbody tr:hover td
+{
+ color: #060;
+}
+
+table th.num,
+table td.num
+{
+ text-align: right;
+}
+
+div.stacktrace
+{
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+ border-radius: 6px;
+ border: 1px solid #999;
+ padding: 6px;
+ margin-bottom: 1em;
+}
+
+div.stacktrace div.stackframe
+{
+ margin: 8px;
+}
+
+div.stackframe .context,
+div.stackframe .local
+{
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ padding: 4px;
+ margin-left: 10px;
+ margin-top: 4px;
+ background: #f2f2f2;
+}
+
+div.stackframe div.context div.highlighted
+{
+ background: #E5EDB8;
+}
+
+div.stackframe span.lineno,
+div.stackframe span.filename
+{
+
+}
+
+div.stackframe span.function
+{
+ font-weight: bold;
+}
+
+div.stackframe span.function,
+div.stackframe div.context
+{
+ font-family: monospace;
+}
+
+div.local,
+div.referents
+{
+ margin-top: 4px;
+ margin-bottom: 4px;
+ margin-left: 10px;
+}
+
+div.stackframe div.local
+{
+ background: #fafafa;
+}
+
+div.referents span.local_name,
+div.referents span.local_size
+{
+ font-family: monospace;
+ padding: 1px;
+}
+
+div.referents span.local_value,
+div.referents span.local_size,
+div.referents span.local_type
+{
+ font-family: monospace;
+ margin-left: 1em;
+}
+div.referents span.local_type
+{
+ color: #999;
+}
+div.referents span.local_size
+{
+ color: #03F;
+}
+div.local div.referents a
+{
+ color: #000;
+ text-decoration: none;
+}
+div.local div.referents a:hover
+{
+ background-color: #EEE;
+ color: #090;
+ text-decoration: none;
+}
+
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/tracker.tpl b/venv/lib/python3.9/site-packages/pympler/templates/tracker.tpl
new file mode 100644
index 00000000..8533e9eb
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/tracker.tpl
@@ -0,0 +1,127 @@
+%include('header', category='Tracker', title='Tracked objects')
+
+%from pympler.util.stringutils import pp, pp_timestamp
+%from json import dumps
+
+<h1>Tracked objects</h1>
+
+%if snapshots:
+
+ <h2>Memory distribution over time</h2>
+
+ <div id="memory_chart_flot" style="width:100%; height: 400px"></div>
+
+ <script type="text/javascript" src="/static/jquery.flot.min.js"></script>
+ <script type="text/javascript" src="/static/jquery.flot.stack.min.js"></script>
+ <script type="text/javascript" src="/static/jquery.flot.tooltip.min.js"></script>
+ <script type="text/javascript">
+ function format_size(value) {
+ var val = Math.round(value / (1000*1000));
+ return val.toLocaleString() + ' MB';
+ };
+
+ $(document).ready(function() {
+ var timeseries = {{!timeseries}};
+ var options = {
+ xaxis: {
+ show: false,
+ },
+ yaxis: {
+ tickFormatter: format_size
+ },
+ grid: {
+ hoverable: true
+ },
+ tooltip: true,
+ tooltipOpts: {
+ content: "%s | %y"
+ },
+ legend: {
+ position: "nw"
+ },
+ series: {
+ bars: {
+ show: true,
+ barWidth: .9,
+ fillColor: { colors: [ { opacity: 0.9 }, { opacity: 0.9 } ] },
+ align: "center"
+ },
+ stack: true
+ }
+ };
+ $.plot($('#memory_chart_flot'), timeseries, options);
+ });
+ </script>
+
+ <h2>Snapshots statistics</h2>
+
+ %for sn in snapshots:
+ <h3>{{sn.desc or 'Untitled'}} snapshot at {{pp_timestamp(sn.timestamp)}}</h3>
+ <table class="tdata">
+ <thead>
+ <tr>
+ <th width="20%">Class</th>
+ <th width="20%" class="num">Instance #</th>
+ <th width="20%" class="num">Total</th>
+ <th width="20%" class="num">Average size</th>
+ <th width="20%" class="num">Share</th>
+ </tr>
+ </thead>
+ <tbody>
+ %cnames = list(sn.classes.keys())
+ %cnames.sort()
+ %for cn in cnames:
+ %data = sn.classes[cn]
+ <tr>
+ <td><a href="/tracker/class/{{cn}}">{{cn}}</a></td>
+ <td class="num">{{data['active']}}</td>
+ <td class="num">{{pp(data['sum'])}}</td>
+ <td class="num">{{pp(data['avg'])}}</td>
+ <td class="num">{{'%3.2f%%' % data['pct']}}</td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+
+ %if sn.system_total.available:
+ <h4>Process memory</h4>
+
+ <table class="tdata">
+ <thead>
+ <tr>
+ <th>Type</th>
+ <th class="num">Size</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Virtual memory size</td>
+ <td class="num">{{pp(sn.system_total.vsz)}}</td>
+ </tr>
+ <tr>
+ <td>Resident set size</td>
+ <td class="num">{{pp(sn.system_total.rss)}}</td>
+ </tr>
+ <tr>
+ <td>Pagefaults</td>
+ <td class="num">{{sn.system_total.pagefaults}}</td>
+ </tr>
+ %for key, value in sn.system_total.os_specific:
+ <tr>
+ <td>{{key}}</td>
+ <td class="num">{{value}}</td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+ %end
+ %end
+
+%else:
+
+ <p>No objects are currently tracked. Consult the Pympler documentation for
+ instructions of how to use the classtracker module.</p>
+
+%end
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/templates/tracker_class.tpl b/venv/lib/python3.9/site-packages/pympler/templates/tracker_class.tpl
new file mode 100644
index 00000000..047e730a
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/templates/tracker_class.tpl
@@ -0,0 +1,79 @@
+%include('header', category='Tracker', title=clsname)
+
+%from pympler.util.stringutils import pp, pp_timestamp
+
+<h1>{{clsname}}</h1>
+
+%sizes = [tobj.get_max_size() for tobj in stats.index[clsname]]
+
+<p>{{len(stats.index[clsname])}} instances of {{clsname}} were registered. The
+average size is {{pp(sum(sizes)/len(sizes))}}, the minimal size is
+{{pp(min(sizes))}}, the maximum size is {{pp(max(sizes))}}.</p>
+
+<h2>Coalesced Referents per Snapshot</h2>
+
+%for snapshot in stats.snapshots:
+ %if clsname in snapshot.classes:
+ %merged = snapshot.classes[clsname]['merged']
+ <h3>Snapshot: {{snapshot.desc}}</h3>
+ <p>{{pp(merged.size)}} occupied by instances of class {{clsname}}</p>
+ %if merged.refs:
+ %include('asized_referents', referents=merged.refs)
+ %else:
+ <p>No per-referent sizes recorded.</p>
+ %end
+ %end
+%end
+
+<h2>Instances</h2>
+
+%for tobj in stats.index[clsname]:
+ <table class="tdata" width="100%" rules="rows">
+ <tr>
+ <th width="140px">Instance</th>
+ <td>{{tobj.name}} at {{'0x%08x' % tobj.id}}</td>
+ </tr>
+ %if tobj.repr:
+ <tr>
+ <th>Representation</th>
+ <td>{{tobj.repr}}&nbsp;</td>
+ </tr>
+ %end
+ <tr>
+ <th>Lifetime</th>
+ <td>{{pp_timestamp(tobj.birth)}} - {{pp_timestamp(tobj.death)}}</td>
+ </tr>
+ %if getattr(tobj, 'trace'):
+ <tr>
+ <th>Instantiation</th>
+ <td>
+ % # <div class="stacktrace">
+ %for frame in tobj.trace:
+ <div class="stackframe">
+ <span class="filename">{{frame[0]}}</span>
+ <span class="lineno">{{frame[1]}}</span>
+ <span class="function">{{frame[2]}}</span>
+ <div class="context">{{frame[3][0].strip()}}</div>
+ </div>
+ %end
+ % # </div>
+ </td>
+ </tr>
+ %end
+ %for (timestamp, size) in tobj.snapshots:
+ <tr>
+ <td>{{pp_timestamp(timestamp)}}</td>
+ %if not size.refs:
+ <td>{{pp(size.size)}}</td>
+ %else:
+ <td>
+ {{pp(size.size)}}
+ %include('asized_referents', referents=size.refs)
+ </td>
+ %end
+ </tr>
+ %end
+ </table>
+%end
+
+%include('footer')
diff --git a/venv/lib/python3.9/site-packages/pympler/tracker.py b/venv/lib/python3.9/site-packages/pympler/tracker.py
new file mode 100644
index 00000000..ec88e116
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/tracker.py
@@ -0,0 +1,267 @@
+"""The tracker module allows you to track changes in the memory usage over
+time.
+
+Using the SummaryTracker, you can create summaries and compare them
+with each other. Stored summaries can be ignored during comparison,
+avoiding the observer effect.
+
+The ObjectTracker allows to monitor object creation. You create objects from
+one time and compare with objects from an earlier time.
+
+"""
+import gc
+import inspect
+
+from pympler import muppy, summary
+from pympler.util import compat
+
+
+class SummaryTracker(object):
+ """ Helper class to track changes between two summaries taken.
+
+ Detailed information on single objects will be lost, e.g. object size or
+ object id. But often summaries are sufficient to monitor the memory usage
+ over the lifetime of an application.
+
+ On initialisation, a first summary is taken. Every time `diff` is called,
+ a new summary will be created. Thus, a diff between the new and the last
+ summary can be extracted.
+
+ Be aware that filtering out previous summaries is time-intensive. You
+ should therefore restrict yourself to the number of summaries you really
+ need.
+
+ """
+ def __init__(self, ignore_self=True):
+ """Constructor.
+
+ The number of summaries managed by the tracker has a performance
+ impact on new summaries, iff you decide to exclude them from further
+ summaries. Therefore it is suggested to use them economically.
+
+ Keyword arguments:
+ ignore_self -- summaries managed by this object will be ignored.
+ """
+ self.s0 = summary.summarize(muppy.get_objects())
+ self.summaries = {}
+ self.ignore_self = ignore_self
+
+ def create_summary(self):
+ """Return a summary.
+
+ See also the notes on ignore_self in the class as well as the
+ initializer documentation.
+
+ """
+ if not self.ignore_self:
+ res = summary.summarize(muppy.get_objects())
+ else:
+ # If the user requested the data required to store summaries to be
+ # ignored in the summaries, we need to identify all objects which
+ # are related to each summary stored.
+ # Thus we build a list of all objects used for summary storage as
+ # well as a dictionary which tells us how often an object is
+ # referenced by the summaries.
+ # During this identification process, more objects are referenced,
+ # namely int objects identifying referenced objects as well as the
+ # corresponding count.
+ # For all these objects it will be checked whether they are
+ # referenced from outside the monitor's scope. If not, they will be
+ # subtracted from the snapshot summary, otherwise they are
+ # included (as this indicates that they are relevant to the
+ # application).
+
+ all_of_them = [] # every single object
+ ref_counter = {} # how often it is referenced; (id(o), o) pairs
+
+ def store_info(o):
+ all_of_them.append(o)
+ if id(o) in ref_counter:
+ ref_counter[id(o)] += 1
+ else:
+ ref_counter[id(o)] = 1
+
+ # store infos on every single object related to the summaries
+ store_info(self.summaries)
+ for k, v in self.summaries.items():
+ store_info(k)
+ summary._traverse(v, store_info)
+
+ # do the summary
+ res = summary.summarize(muppy.get_objects())
+
+ # remove ids stored in the ref_counter
+ for _id in ref_counter:
+ # referenced in frame, ref_counter, ref_counter.keys()
+ if len(gc.get_referrers(_id)) == (3):
+ summary._subtract(res, _id)
+ for o in all_of_them:
+ # referenced in frame, summary, all_of_them
+ if len(gc.get_referrers(o)) == (ref_counter[id(o)] + 2):
+ summary._subtract(res, o)
+
+ return res
+
+ def diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+
+ """
+ res = None
+ if summary2 is None:
+ self.s1 = self.create_summary()
+ if summary1 is None:
+ res = summary.get_diff(self.s0, self.s1)
+ else:
+ res = summary.get_diff(summary1, self.s1)
+ self.s0 = self.s1
+ else:
+ if summary1 is not None:
+ res = summary.get_diff(summary1, summary2)
+ else:
+ raise ValueError(
+ "You cannot provide summary2 without summary1.")
+ return summary._sweep(res)
+
+ def print_diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries and print it.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+ """
+ summary.print_(self.diff(summary1=summary1, summary2=summary2))
+
+ def format_diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries and return a list of formatted
+ lines.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+ """
+ return summary.format_(self.diff(summary1=summary1, summary2=summary2))
+
+ def store_summary(self, key):
+ """Store a current summary in self.summaries."""
+ self.summaries[key] = self.create_summary()
+
+
+class ObjectTracker(object):
+ """
+ Helper class to track changes in the set of existing objects.
+
+ Each time you invoke a diff with this tracker, the objects which existed
+ during the last invocation are compared with the objects which exist during
+ the current invocation.
+
+ Please note that in order to do so, strong references to all objects will
+ be stored. This means that none of these objects can be garbage collected.
+ A use case for the ObjectTracker is the monitoring of a state which should
+ be stable, but you see new objects being created nevertheless. With the
+ ObjectTracker you can identify these new objects.
+
+ """
+
+ # Some precaution needs to be taken when handling frame objects (see
+ # warning at http://docs.python.org/lib/inspect-stack.html). All ignore
+ # lists used need to be emptied so no frame objects remain referenced.
+
+ def __init__(self):
+ """On initialisation, the current state of objects is stored.
+
+ Note that all objects which exist at this point in time will not be
+ released until you destroy this ObjectTracker instance.
+ """
+ self.o0 = self._get_objects(ignore=(inspect.currentframe(),))
+
+ def _get_objects(self, ignore=()):
+ """Get all currently existing objects.
+
+ XXX - ToDo: This method is a copy&paste from muppy.get_objects, but
+ some modifications are applied. Specifically, it allows to ignore
+ objects (which includes the current frame).
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ def remove_ignore(objects, ignore=()):
+ # remove all objects listed in the ignore list
+ res = []
+ for o in objects:
+ if not compat.object_in_list(o, ignore):
+ res.append(o)
+ return res
+
+ tmp = gc.get_objects()
+ ignore += (inspect.currentframe(), self, ignore, remove_ignore)
+ if hasattr(self, 'o0'):
+ ignore += (self.o0,)
+ if hasattr(self, 'o1'):
+ ignore += (self.o1,)
+ # this implies that referenced objects are also ignored
+ tmp = remove_ignore(tmp, ignore)
+ res = []
+ for o in tmp:
+ # gc.get_objects returns only container objects, but we also want
+ # the objects referenced by them
+ refs = muppy.get_referents(o)
+ for ref in refs:
+ if not gc.is_tracked(ref):
+ # we already got the container objects, now we only add
+ # non-container objects
+ res.append(ref)
+ res.extend(tmp)
+ res = muppy._remove_duplicates(res)
+ if ignore is not None:
+ # repeat to filter out objects which may have been referenced
+ res = remove_ignore(res, ignore)
+ # manual cleanup, see comment above
+ del ignore
+ return res
+
+ def get_diff(self, ignore=()):
+ """Get the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ self.o1 = self._get_objects(ignore+(inspect.currentframe(),))
+ diff = muppy.get_diff(self.o0, self.o1)
+ self.o0 = self.o1
+ # manual cleanup, see comment above
+ return diff
+
+ def print_diff(self, ignore=()):
+ """Print the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ for line in self.format_diff(ignore+(inspect.currentframe(),)):
+ print(line)
+
+ def format_diff(self, ignore=()):
+ """Format the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ lines = []
+ diff = self.get_diff(ignore+(inspect.currentframe(),))
+ lines.append("Added objects:")
+ for line in summary.format_(summary.summarize(diff['+'])):
+ lines.append(line)
+ lines.append("Removed objects:")
+ for line in summary.format_(summary.summarize(diff['-'])):
+ lines.append(line)
+ return lines
diff --git a/venv/lib/python3.9/site-packages/pympler/util/__init__.py b/venv/lib/python3.9/site-packages/pympler/util/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/util/__init__.py
diff --git a/venv/lib/python3.9/site-packages/pympler/util/bottle.py b/venv/lib/python3.9/site-packages/pympler/util/bottle.py
new file mode 100644
index 00000000..9806efd0
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/util/bottle.py
@@ -0,0 +1,3771 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Bottle is a fast and simple micro-framework for small web applications. It
+offers request dispatching (Routes) with url parameter support, templates,
+a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
+template engines - all in a single file and with no dependencies other than the
+Python Standard Library.
+
+Homepage and documentation: http://bottlepy.org/
+
+Copyright (c) 2016, Marcel Hellkamp.
+License: MIT (see LICENSE for details)
+"""
+
+from __future__ import with_statement
+
+__author__ = 'Marcel Hellkamp'
+__version__ = '0.12.19'
+__license__ = 'MIT'
+
+# The gevent server adapter needs to patch some modules before they are imported
+# This is why we parse the commandline parameters here but handle them later
+if __name__ == '__main__':
+ from optparse import OptionParser
+ _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app")
+ _opt = _cmd_parser.add_option
+ _opt("--version", action="store_true", help="show version number.")
+ _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
+ _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
+ _opt("-p", "--plugin", action="append", help="install additional plugin/s.")
+ _opt("--debug", action="store_true", help="start server in debug mode.")
+ _opt("--reload", action="store_true", help="auto-reload on file changes.")
+ _cmd_options, _cmd_args = _cmd_parser.parse_args()
+ if _cmd_options.server and _cmd_options.server.startswith('gevent'):
+ import gevent.monkey; gevent.monkey.patch_all()
+
+import base64, cgi, email.utils, functools, hmac, itertools, mimetypes,\
+ os, re, subprocess, sys, tempfile, threading, time, warnings, hashlib
+
+from datetime import date as datedate, datetime, timedelta
+from tempfile import TemporaryFile
+from traceback import format_exc, print_exc
+from inspect import getargspec
+from unicodedata import normalize
+
+
+try: from simplejson import dumps as json_dumps, loads as json_lds
+except ImportError: # pragma: no cover
+ try: from json import dumps as json_dumps, loads as json_lds
+ except ImportError:
+ try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds
+ except ImportError:
+ def json_dumps(data):
+ raise ImportError("JSON support requires Python 2.6 or simplejson.")
+ json_lds = json_dumps
+
+
+
+# We now try to fix 2.5/2.6/3.1/3.2 incompatibilities.
+# It ain't pretty but it works... Sorry for the mess.
+
+py = sys.version_info
+py3k = py >= (3, 0, 0)
+py25 = py < (2, 6, 0)
+py31 = (3, 1, 0) <= py < (3, 2, 0)
+
+# Workaround for the missing "as" keyword in py3k.
+def _e(): return sys.exc_info()[1]
+
+# Workaround for the "print is a keyword/function" Python 2/3 dilemma
+# and a fallback for mod_wsgi (resticts stdout/err attribute access)
+try:
+ _stdout, _stderr = sys.stdout.write, sys.stderr.write
+except IOError:
+ _stdout = lambda x: sys.stdout.write(x)
+ _stderr = lambda x: sys.stderr.write(x)
+
+# Lots of stdlib and builtin differences.
+if py3k:
+ import http.client as httplib
+ import _thread as thread
+ from urllib.parse import urljoin, SplitResult as UrlSplitResult
+ from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
+ urlunquote = functools.partial(urlunquote, encoding='latin1')
+ from http.cookies import SimpleCookie
+ if py >= (3, 3, 0):
+ from collections.abc import MutableMapping as DictMixin
+ from types import ModuleType as new_module
+ else:
+ from collections import MutableMapping as DictMixin
+ from imp import new_module
+ import pickle
+ from io import BytesIO
+ from configparser import ConfigParser
+ basestring = str
+ unicode = str
+ json_loads = lambda s: json_lds(touni(s))
+ callable = lambda x: hasattr(x, '__call__')
+ imap = map
+ def _raise(*a): raise a[0](a[1]).with_traceback(a[2])
+else: # 2.x
+ import httplib
+ import thread
+ from urlparse import urljoin, SplitResult as UrlSplitResult
+ from urllib import urlencode, quote as urlquote, unquote as urlunquote
+ from Cookie import SimpleCookie
+ from itertools import imap
+ import cPickle as pickle
+ from imp import new_module
+ from StringIO import StringIO as BytesIO
+ from ConfigParser import SafeConfigParser as ConfigParser
+ if py25:
+ msg = "Python 2.5 support may be dropped in future versions of Bottle."
+ warnings.warn(msg, DeprecationWarning)
+ from UserDict import DictMixin
+ def next(it): return it.next()
+ bytes = str
+ else: # 2.6, 2.7
+ from collections import MutableMapping as DictMixin
+ unicode = unicode
+ json_loads = json_lds
+ eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
+
+# Some helpers for string/byte handling
+def tob(s, enc='utf8'):
+ return s.encode(enc) if isinstance(s, unicode) else bytes(s)
+def touni(s, enc='utf8', err='strict'):
+ return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
+tonat = touni if py3k else tob
+
+# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
+# 3.1 needs a workaround.
+if py31:
+ from io import TextIOWrapper
+ class NCTextIOWrapper(TextIOWrapper):
+ def close(self): pass # Keep wrapped buffer open.
+
+
+# A bug in functools causes it to break if the wrapper is an instance method
+def update_wrapper(wrapper, wrapped, *a, **ka):
+ try: functools.update_wrapper(wrapper, wrapped, *a, **ka)
+ except AttributeError: pass
+
+
+
+# These helpers are used at module level and need to be defined first.
+# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
+
+def depr(message, hard=False):
+ warnings.warn(message, DeprecationWarning, stacklevel=3)
+
+def makelist(data): # This is just to handy
+ if isinstance(data, (tuple, list, set, dict)): return list(data)
+ elif data: return [data]
+ else: return []
+
+
+class DictProperty(object):
+ ''' Property that maps to a key in a local dict-like attribute. '''
+ def __init__(self, attr, key=None, read_only=False):
+ self.attr, self.key, self.read_only = attr, key, read_only
+
+ def __call__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter, self.key = func, self.key or func.__name__
+ return self
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ key, storage = self.key, getattr(obj, self.attr)
+ if key not in storage: storage[key] = self.getter(obj)
+ return storage[key]
+
+ def __set__(self, obj, value):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ getattr(obj, self.attr)[self.key] = value
+
+ def __delete__(self, obj):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ del getattr(obj, self.attr)[self.key]
+
+
+class cached_property(object):
+ ''' A property that is only computed once per instance and then replaces
+ itself with an ordinary attribute. Deleting the attribute resets the
+ property. '''
+
+ def __init__(self, func):
+ self.__doc__ = getattr(func, '__doc__')
+ self.func = func
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ value = obj.__dict__[self.func.__name__] = self.func(obj)
+ return value
+
+
+class lazy_attribute(object):
+ ''' A property that caches itself to the class object. '''
+ def __init__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter = func
+
+ def __get__(self, obj, cls):
+ value = self.getter(cls)
+ setattr(cls, self.__name__, value)
+ return value
+
+
+
+
+
+
+###############################################################################
+# Exceptions and Events ########################################################
+###############################################################################
+
+
+class BottleException(Exception):
+ """ A base class for exceptions used by bottle. """
+ pass
+
+
+
+
+
+
+###############################################################################
+# Routing ######################################################################
+###############################################################################
+
+
+class RouteError(BottleException):
+ """ This is a base class for all routing related exceptions """
+
+
+class RouteReset(BottleException):
+ """ If raised by a plugin or request handler, the route is reset and all
+ plugins are re-applied. """
+
+class RouterUnknownModeError(RouteError): pass
+
+
+class RouteSyntaxError(RouteError):
+ """ The route parser found something not supported by this router. """
+
+
+class RouteBuildError(RouteError):
+ """ The route could not be built. """
+
+
+def _re_flatten(p):
+ ''' Turn all capturing groups in a regular expression pattern into
+ non-capturing groups. '''
+ if '(' not in p: return p
+ return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))',
+ lambda m: m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:', p)
+
+
+class Router(object):
+ ''' A Router is an ordered collection of route->target pairs. It is used to
+ efficiently match WSGI requests against a number of routes and return
+ the first target that satisfies the request. The target may be anything,
+ usually a string, ID or callable object. A route consists of a path-rule
+ and a HTTP method.
+
+ The path-rule is either a static path (e.g. `/contact`) or a dynamic
+ path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
+ and details on the matching order are described in docs:`routing`.
+ '''
+
+ default_pattern = '[^/]+'
+ default_filter = 're'
+
+ #: The current CPython regexp implementation does not allow more
+ #: than 99 matching groups per regular expression.
+ _MAX_GROUPS_PER_PATTERN = 99
+
+ def __init__(self, strict=False):
+ self.rules = [] # All rules in order
+ self._groups = {} # index of regexes to find them in dyna_routes
+ self.builder = {} # Data structure for the url builder
+ self.static = {} # Search structure for static routes
+ self.dyna_routes = {}
+ self.dyna_regexes = {} # Search structure for dynamic routes
+ #: If true, static routes are no longer checked first.
+ self.strict_order = strict
+ self.filters = {
+ 're': lambda conf:
+ (_re_flatten(conf or self.default_pattern), None, None),
+ 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
+ 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
+ 'path': lambda conf: (r'.+?', None, None)}
+
+ def add_filter(self, name, func):
+ ''' Add a filter. The provided function is called with the configuration
+ string as parameter and must return a (regexp, to_python, to_url) tuple.
+ The first element is a string, the last two are callables or None. '''
+ self.filters[name] = func
+
+ rule_syntax = re.compile('(\\\\*)'\
+ '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
+ '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\
+ '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
+
+ def _itertokens(self, rule):
+ offset, prefix = 0, ''
+ for match in self.rule_syntax.finditer(rule):
+ prefix += rule[offset:match.start()]
+ g = match.groups()
+ if len(g[0])%2: # Escaped wildcard
+ prefix += match.group(0)[len(g[0]):]
+ offset = match.end()
+ continue
+ if prefix:
+ yield prefix, None, None
+ name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
+ yield name, filtr or 'default', conf or None
+ offset, prefix = match.end(), ''
+ if offset <= len(rule) or prefix:
+ yield prefix+rule[offset:], None, None
+
+ def add(self, rule, method, target, name=None):
+ ''' Add a new rule or replace the target for an existing rule. '''
+ anons = 0 # Number of anonymous wildcards found
+ keys = [] # Names of keys
+ pattern = '' # Regular expression pattern with named groups
+ filters = [] # Lists of wildcard input filters
+ builder = [] # Data structure for the URL builder
+ is_static = True
+
+ for key, mode, conf in self._itertokens(rule):
+ if mode:
+ is_static = False
+ if mode == 'default': mode = self.default_filter
+ mask, in_filter, out_filter = self.filters[mode](conf)
+ if not key:
+ pattern += '(?:%s)' % mask
+ key = 'anon%d' % anons
+ anons += 1
+ else:
+ pattern += '(?P<%s>%s)' % (key, mask)
+ keys.append(key)
+ if in_filter: filters.append((key, in_filter))
+ builder.append((key, out_filter or str))
+ elif key:
+ pattern += re.escape(key)
+ builder.append((None, key))
+
+ self.builder[rule] = builder
+ if name: self.builder[name] = builder
+
+ if is_static and not self.strict_order:
+ self.static.setdefault(method, {})
+ self.static[method][self.build(rule)] = (target, None)
+ return
+
+ try:
+ re_pattern = re.compile('^(%s)$' % pattern)
+ re_match = re_pattern.match
+ except re.error:
+ raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e()))
+
+ if filters:
+ def getargs(path):
+ url_args = re_match(path).groupdict()
+ for name, wildcard_filter in filters:
+ try:
+ url_args[name] = wildcard_filter(url_args[name])
+ except ValueError:
+ raise HTTPError(400, 'Path has wrong format.')
+ return url_args
+ elif re_pattern.groupindex:
+ def getargs(path):
+ return re_match(path).groupdict()
+ else:
+ getargs = None
+
+ flatpat = _re_flatten(pattern)
+ whole_rule = (rule, flatpat, target, getargs)
+
+ if (flatpat, method) in self._groups:
+ if DEBUG:
+ msg = 'Route <%s %s> overwrites a previously defined route'
+ warnings.warn(msg % (method, rule), RuntimeWarning)
+ self.dyna_routes[method][self._groups[flatpat, method]] = whole_rule
+ else:
+ self.dyna_routes.setdefault(method, []).append(whole_rule)
+ self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
+
+ self._compile(method)
+
+ def _compile(self, method):
+ all_rules = self.dyna_routes[method]
+ comborules = self.dyna_regexes[method] = []
+ maxgroups = self._MAX_GROUPS_PER_PATTERN
+ for x in range(0, len(all_rules), maxgroups):
+ some = all_rules[x:x+maxgroups]
+ combined = (flatpat for (_, flatpat, _, _) in some)
+ combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
+ combined = re.compile(combined).match
+ rules = [(target, getargs) for (_, _, target, getargs) in some]
+ comborules.append((combined, rules))
+
+ def build(self, _name, *anons, **query):
+ ''' Build an URL by filling the wildcards in a rule. '''
+ builder = self.builder.get(_name)
+ if not builder: raise RouteBuildError("No route with that name.", _name)
+ try:
+ for i, value in enumerate(anons): query['anon%d'%i] = value
+ url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder])
+ return url if not query else url+'?'+urlencode(query)
+ except KeyError:
+ raise RouteBuildError('Missing URL argument: %r' % _e().args[0])
+
+ def match(self, environ):
+ ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
+ verb = environ['REQUEST_METHOD'].upper()
+ path = environ['PATH_INFO'] or '/'
+ target = None
+ if verb == 'HEAD':
+ methods = ['PROXY', verb, 'GET', 'ANY']
+ else:
+ methods = ['PROXY', verb, 'ANY']
+
+ for method in methods:
+ if method in self.static and path in self.static[method]:
+ target, getargs = self.static[method][path]
+ return target, getargs(path) if getargs else {}
+ elif method in self.dyna_regexes:
+ for combined, rules in self.dyna_regexes[method]:
+ match = combined(path)
+ if match:
+ target, getargs = rules[match.lastindex - 1]
+ return target, getargs(path) if getargs else {}
+
+ # No matching route found. Collect alternative methods for 405 response
+ allowed = set([])
+ nocheck = set(methods)
+ for method in set(self.static) - nocheck:
+ if path in self.static[method]:
+ allowed.add(method)
+ for method in set(self.dyna_regexes) - allowed - nocheck:
+ for combined, rules in self.dyna_regexes[method]:
+ match = combined(path)
+ if match:
+ allowed.add(method)
+ if allowed:
+ allow_header = ",".join(sorted(allowed))
+ raise HTTPError(405, "Method not allowed.", Allow=allow_header)
+
+ # No matching route and no alternative method found. We give up
+ raise HTTPError(404, "Not found: " + repr(path))
+
+
+
+
+
+
+class Route(object):
+ ''' This class wraps a route callback along with route specific metadata and
+ configuration and applies Plugins on demand. It is also responsible for
+ turing an URL path rule into a regular expression usable by the Router.
+ '''
+
+ def __init__(self, app, rule, method, callback, name=None,
+ plugins=None, skiplist=None, **config):
+ #: The application this route is installed to.
+ self.app = app
+ #: The path-rule string (e.g. ``/wiki/:page``).
+ self.rule = rule
+ #: The HTTP method as a string (e.g. ``GET``).
+ self.method = method
+ #: The original callback with no plugins applied. Useful for introspection.
+ self.callback = callback
+ #: The name of the route (if specified) or ``None``.
+ self.name = name or None
+ #: A list of route-specific plugins (see :meth:`Bottle.route`).
+ self.plugins = plugins or []
+ #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
+ self.skiplist = skiplist or []
+ #: Additional keyword arguments passed to the :meth:`Bottle.route`
+ #: decorator are stored in this dictionary. Used for route-specific
+ #: plugin configuration and meta-data.
+ self.config = ConfigDict().load_dict(config, make_namespaces=True)
+
+ def __call__(self, *a, **ka):
+ depr("Some APIs changed to return Route() instances instead of"\
+ " callables. Make sure to use the Route.call method and not to"\
+ " call Route instances directly.") #0.12
+ return self.call(*a, **ka)
+
+ @cached_property
+ def call(self):
+ ''' The route callback with all plugins applied. This property is
+ created on demand and then cached to speed up subsequent requests.'''
+ return self._make_callback()
+
+ def reset(self):
+ ''' Forget any cached values. The next time :attr:`call` is accessed,
+ all plugins are re-applied. '''
+ self.__dict__.pop('call', None)
+
+ def prepare(self):
+ ''' Do all on-demand work immediately (useful for debugging).'''
+ self.call
+
+ @property
+ def _context(self):
+ depr('Switch to Plugin API v2 and access the Route object directly.') #0.12
+ return dict(rule=self.rule, method=self.method, callback=self.callback,
+ name=self.name, app=self.app, config=self.config,
+ apply=self.plugins, skip=self.skiplist)
+
+ def all_plugins(self):
+ ''' Yield all Plugins affecting this route. '''
+ unique = set()
+ for p in reversed(self.app.plugins + self.plugins):
+ if True in self.skiplist: break
+ name = getattr(p, 'name', False)
+ if name and (name in self.skiplist or name in unique): continue
+ if p in self.skiplist or type(p) in self.skiplist: continue
+ if name: unique.add(name)
+ yield p
+
+ def _make_callback(self):
+ callback = self.callback
+ for plugin in self.all_plugins():
+ try:
+ if hasattr(plugin, 'apply'):
+ api = getattr(plugin, 'api', 1)
+ context = self if api > 1 else self._context
+ callback = plugin.apply(callback, context)
+ else:
+ callback = plugin(callback)
+ except RouteReset: # Try again with changed configuration.
+ return self._make_callback()
+ if not callback is self.callback:
+ update_wrapper(callback, self.callback)
+ return callback
+
+ def get_undecorated_callback(self):
+ ''' Return the callback. If the callback is a decorated function, try to
+ recover the original function. '''
+ func = self.callback
+ func = getattr(func, '__func__' if py3k else 'im_func', func)
+ closure_attr = '__closure__' if py3k else 'func_closure'
+ while hasattr(func, closure_attr) and getattr(func, closure_attr):
+ func = getattr(func, closure_attr)[0].cell_contents
+ return func
+
+ def get_callback_args(self):
+ ''' Return a list of argument names the callback (most likely) accepts
+ as keyword arguments. If the callback is a decorated function, try
+ to recover the original function before inspection. '''
+ return getargspec(self.get_undecorated_callback())[0]
+
+ def get_config(self, key, default=None):
+ ''' Lookup a config field and return its value, first checking the
+ route.config, then route.app.config.'''
+ for conf in (self.config, self.app.conifg):
+ if key in conf: return conf[key]
+ return default
+
+ def __repr__(self):
+ cb = self.get_undecorated_callback()
+ return '<%s %r %r>' % (self.method, self.rule, cb)
+
+
+
+
+
+
+###############################################################################
+# Application Object ###########################################################
+###############################################################################
+
+
+class Bottle(object):
+ """ Each Bottle object represents a single, distinct web application and
+ consists of routes, callbacks, plugins, resources and configuration.
+ Instances are callable WSGI applications.
+
+ :param catchall: If true (default), handle all exceptions. Turn off to
+ let debugging middleware handle exceptions.
+ """
+
+ def __init__(self, catchall=True, autojson=True):
+
+ #: A :class:`ConfigDict` for app specific configuration.
+ self.config = ConfigDict()
+ self.config._on_change = functools.partial(self.trigger_hook, 'config')
+ self.config.meta_set('autojson', 'validate', bool)
+ self.config.meta_set('catchall', 'validate', bool)
+ self.config['catchall'] = catchall
+ self.config['autojson'] = autojson
+
+ #: A :class:`ResourceManager` for application files
+ self.resources = ResourceManager()
+
+ self.routes = [] # List of installed :class:`Route` instances.
+ self.router = Router() # Maps requests to :class:`Route` instances.
+ self.error_handler = {}
+
+ # Core plugins
+ self.plugins = [] # List of installed plugins.
+ if self.config['autojson']:
+ self.install(JSONPlugin())
+ self.install(TemplatePlugin())
+
+ #: If true, most exceptions are caught and returned as :exc:`HTTPError`
+ catchall = DictProperty('config', 'catchall')
+
+ __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
+ __hook_reversed = 'after_request'
+
+ @cached_property
+ def _hooks(self):
+ return dict((name, []) for name in self.__hook_names)
+
+ def add_hook(self, name, func):
+ ''' Attach a callback to a hook. Three hooks are currently implemented:
+
+ before_request
+ Executed once before each request. The request context is
+ available, but no routing has happened yet.
+ after_request
+ Executed once after each request regardless of its outcome.
+ app_reset
+ Called whenever :meth:`Bottle.reset` is called.
+ '''
+ if name in self.__hook_reversed:
+ self._hooks[name].insert(0, func)
+ else:
+ self._hooks[name].append(func)
+
+ def remove_hook(self, name, func):
+ ''' Remove a callback from a hook. '''
+ if name in self._hooks and func in self._hooks[name]:
+ self._hooks[name].remove(func)
+ return True
+
+ def trigger_hook(self, __name, *args, **kwargs):
+ ''' Trigger a hook and return a list of results. '''
+ return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
+
+ def hook(self, name):
+ """ Return a decorator that attaches a callback to a hook. See
+ :meth:`add_hook` for details."""
+ def decorator(func):
+ self.add_hook(name, func)
+ return func
+ return decorator
+
+ def mount(self, prefix, app, **options):
+ ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific
+ URL prefix. Example::
+
+ root_app.mount('/admin/', admin_app)
+
+ :param prefix: path prefix or `mount-point`. If it ends in a slash,
+ that slash is mandatory.
+ :param app: an instance of :class:`Bottle` or a WSGI application.
+
+ All other parameters are passed to the underlying :meth:`route` call.
+ '''
+ if isinstance(app, basestring):
+ depr('Parameter order of Bottle.mount() changed.', True) # 0.10
+
+ segments = [p for p in prefix.split('/') if p]
+ if not segments: raise ValueError('Empty path prefix.')
+ path_depth = len(segments)
+
+ def mountpoint_wrapper():
+ try:
+ request.path_shift(path_depth)
+ rs = HTTPResponse([])
+ def start_response(status, headerlist, exc_info=None):
+ if exc_info:
+ try:
+ _raise(*exc_info)
+ finally:
+ exc_info = None
+ rs.status = status
+ for name, value in headerlist: rs.add_header(name, value)
+ return rs.body.append
+ body = app(request.environ, start_response)
+ if body and rs.body: body = itertools.chain(rs.body, body)
+ rs.body = body or rs.body
+ return rs
+ finally:
+ request.path_shift(-path_depth)
+
+ options.setdefault('skip', True)
+ options.setdefault('method', 'PROXY')
+ options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
+ options['callback'] = mountpoint_wrapper
+
+ self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
+ if not prefix.endswith('/'):
+ self.route('/' + '/'.join(segments), **options)
+
+ def merge(self, routes):
+ ''' Merge the routes of another :class:`Bottle` application or a list of
+ :class:`Route` objects into this application. The routes keep their
+ 'owner', meaning that the :data:`Route.app` attribute is not
+ changed. '''
+ if isinstance(routes, Bottle):
+ routes = routes.routes
+ for route in routes:
+ self.add_route(route)
+
+ def install(self, plugin):
+ ''' Add a plugin to the list of plugins and prepare it for being
+ applied to all routes of this application. A plugin may be a simple
+ decorator or an object that implements the :class:`Plugin` API.
+ '''
+ if hasattr(plugin, 'setup'): plugin.setup(self)
+ if not callable(plugin) and not hasattr(plugin, 'apply'):
+ raise TypeError("Plugins must be callable or implement .apply()")
+ self.plugins.append(plugin)
+ self.reset()
+ return plugin
+
+ def uninstall(self, plugin):
+ ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type
+ object to remove all plugins that match that type, a string to remove
+ all plugins with a matching ``name`` attribute or ``True`` to remove all
+ plugins. Return the list of removed plugins. '''
+ removed, remove = [], plugin
+ for i, plugin in list(enumerate(self.plugins))[::-1]:
+ if remove is True or remove is plugin or remove is type(plugin) \
+ or getattr(plugin, 'name', True) == remove:
+ removed.append(plugin)
+ del self.plugins[i]
+ if hasattr(plugin, 'close'): plugin.close()
+ if removed: self.reset()
+ return removed
+
+ def reset(self, route=None):
+ ''' Reset all routes (force plugins to be re-applied) and clear all
+ caches. If an ID or route object is given, only that specific route
+ is affected. '''
+ if route is None: routes = self.routes
+ elif isinstance(route, Route): routes = [route]
+ else: routes = [self.routes[route]]
+ for route in routes: route.reset()
+ if DEBUG:
+ for route in routes: route.prepare()
+ self.trigger_hook('app_reset')
+
+ def close(self):
+ ''' Close the application and all installed plugins. '''
+ for plugin in self.plugins:
+ if hasattr(plugin, 'close'): plugin.close()
+ self.stopped = True
+
+ def run(self, **kwargs):
+ ''' Calls :func:`run` with the same parameters. '''
+ run(self, **kwargs)
+
+ def match(self, environ):
+ """ Search for a matching route and return a (:class:`Route` , urlargs)
+ tuple. The second value is a dictionary with parameters extracted
+ from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
+ return self.router.match(environ)
+
+ def get_url(self, routename, **kargs):
+ """ Return a string that matches a named route """
+ scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
+ location = self.router.build(routename, **kargs).lstrip('/')
+ return urljoin(urljoin('/', scriptname), location)
+
+ def add_route(self, route):
+ ''' Add a route object, but do not change the :data:`Route.app`
+ attribute.'''
+ self.routes.append(route)
+ self.router.add(route.rule, route.method, route, name=route.name)
+ if DEBUG: route.prepare()
+
+ def route(self, path=None, method='GET', callback=None, name=None,
+ apply=None, skip=None, **config):
+ """ A decorator to bind a function to a request URL. Example::
+
+ @app.route('/hello/:name')
+ def hello(name):
+ return 'Hello %s' % name
+
+ The ``:name`` part is a wildcard. See :class:`Router` for syntax
+ details.
+
+ :param path: Request path or a list of paths to listen to. If no
+ path is specified, it is automatically generated from the
+ signature of the function.
+ :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
+ methods to listen to. (default: `GET`)
+ :param callback: An optional shortcut to avoid the decorator
+ syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
+ :param name: The name for this route. (default: None)
+ :param apply: A decorator or plugin or a list of plugins. These are
+ applied to the route callback in addition to installed plugins.
+ :param skip: A list of plugins, plugin classes or names. Matching
+ plugins are not installed to this route. ``True`` skips all.
+
+ Any additional keyword arguments are stored as route-specific
+ configuration and passed to plugins (see :meth:`Plugin.apply`).
+ """
+ if callable(path): path, callback = None, path
+ plugins = makelist(apply)
+ skiplist = makelist(skip)
+ def decorator(callback):
+ # TODO: Documentation and tests
+ if isinstance(callback, basestring): callback = load(callback)
+ for rule in makelist(path) or yieldroutes(callback):
+ for verb in makelist(method):
+ verb = verb.upper()
+ route = Route(self, rule, verb, callback, name=name,
+ plugins=plugins, skiplist=skiplist, **config)
+ self.add_route(route)
+ return callback
+ return decorator(callback) if callback else decorator
+
+ def get(self, path=None, method='GET', **options):
+ """ Equals :meth:`route`. """
+ return self.route(path, method, **options)
+
+ def post(self, path=None, method='POST', **options):
+ """ Equals :meth:`route` with a ``POST`` method parameter. """
+ return self.route(path, method, **options)
+
+ def put(self, path=None, method='PUT', **options):
+ """ Equals :meth:`route` with a ``PUT`` method parameter. """
+ return self.route(path, method, **options)
+
+ def delete(self, path=None, method='DELETE', **options):
+ """ Equals :meth:`route` with a ``DELETE`` method parameter. """
+ return self.route(path, method, **options)
+
+ def error(self, code=500):
+ """ Decorator: Register an output handler for a HTTP error code"""
+ def wrapper(handler):
+ self.error_handler[int(code)] = handler
+ return handler
+ return wrapper
+
+ def default_error_handler(self, res):
+ return tob(template(ERROR_PAGE_TEMPLATE, e=res))
+
+ def _handle(self, environ):
+ path = environ['bottle.raw_path'] = environ['PATH_INFO']
+ if py3k:
+ try:
+ environ['PATH_INFO'] = path.encode('latin1').decode('utf8')
+ except UnicodeError:
+ return HTTPError(400, 'Invalid path string. Expected UTF-8')
+
+ try:
+ environ['bottle.app'] = self
+ request.bind(environ)
+ response.bind()
+ try:
+ self.trigger_hook('before_request')
+ route, args = self.router.match(environ)
+ environ['route.handle'] = route
+ environ['bottle.route'] = route
+ environ['route.url_args'] = args
+ return route.call(**args)
+ finally:
+ self.trigger_hook('after_request')
+
+ except HTTPResponse:
+ return _e()
+ except RouteReset:
+ route.reset()
+ return self._handle(environ)
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ stacktrace = format_exc()
+ environ['wsgi.errors'].write(stacktrace)
+ return HTTPError(500, "Internal Server Error", _e(), stacktrace)
+
+ def _cast(self, out, peek=None):
+ """ Try to convert the parameter into something WSGI compatible and set
+ correct HTTP headers when possible.
+ Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
+ iterable of strings and iterable of unicodes
+ """
+
+ # Empty output is done here
+ if not out:
+ if 'Content-Length' not in response:
+ response['Content-Length'] = 0
+ return []
+ # Join lists of byte or unicode strings. Mixed lists are NOT supported
+ if isinstance(out, (tuple, list))\
+ and isinstance(out[0], (bytes, unicode)):
+ out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
+ # Encode unicode strings
+ if isinstance(out, unicode):
+ out = out.encode(response.charset)
+ # Byte Strings are just returned
+ if isinstance(out, bytes):
+ if 'Content-Length' not in response:
+ response['Content-Length'] = len(out)
+ return [out]
+ # HTTPError or HTTPException (recursive, because they may wrap anything)
+ # TODO: Handle these explicitly in handle() or make them iterable.
+ if isinstance(out, HTTPError):
+ out.apply(response)
+ out = self.error_handler.get(out.status_code, self.default_error_handler)(out)
+ return self._cast(out)
+ if isinstance(out, HTTPResponse):
+ out.apply(response)
+ return self._cast(out.body)
+
+ # File-like objects.
+ if hasattr(out, 'read'):
+ if 'wsgi.file_wrapper' in request.environ:
+ return request.environ['wsgi.file_wrapper'](out)
+ elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
+ return WSGIFileWrapper(out)
+
+ # Handle Iterables. We peek into them to detect their inner type.
+ try:
+ iout = iter(out)
+ first = next(iout)
+ while not first:
+ first = next(iout)
+ except StopIteration:
+ return self._cast('')
+ except HTTPResponse:
+ first = _e()
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ first = HTTPError(500, 'Unhandled exception', _e(), format_exc())
+
+ # These are the inner types allowed in iterator or generator objects.
+ if isinstance(first, HTTPResponse):
+ return self._cast(first)
+ elif isinstance(first, bytes):
+ new_iter = itertools.chain([first], iout)
+ elif isinstance(first, unicode):
+ encoder = lambda x: x.encode(response.charset)
+ new_iter = imap(encoder, itertools.chain([first], iout))
+ else:
+ msg = 'Unsupported response type: %s' % type(first)
+ return self._cast(HTTPError(500, msg))
+ if hasattr(out, 'close'):
+ new_iter = _closeiter(new_iter, out.close)
+ return new_iter
+
+ def wsgi(self, environ, start_response):
+ """ The bottle WSGI-interface. """
+ try:
+ out = self._cast(self._handle(environ))
+ # rfc2616 section 4.3
+ if response._status_code in (100, 101, 204, 304)\
+ or environ['REQUEST_METHOD'] == 'HEAD':
+ if hasattr(out, 'close'): out.close()
+ out = []
+ start_response(response._status_line, response.headerlist)
+ return out
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ err = '<h1>Critical error while processing request: %s</h1>' \
+ % html_escape(environ.get('PATH_INFO', '/'))
+ if DEBUG:
+ err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
+ '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
+ % (html_escape(repr(_e())), html_escape(format_exc()))
+ environ['wsgi.errors'].write(err)
+ headers = [('Content-Type', 'text/html; charset=UTF-8')]
+ start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
+ return [tob(err)]
+
+ def __call__(self, environ, start_response):
+ ''' Each instance of :class:'Bottle' is a WSGI application. '''
+ return self.wsgi(environ, start_response)
+
+
+
+
+
+
+###############################################################################
+# HTTP and WSGI Tools ##########################################################
+###############################################################################
+
+class BaseRequest(object):
+ """ A wrapper for WSGI environment dictionaries that adds a lot of
+ convenient access methods and properties. Most of them are read-only.
+
+ Adding new attributes to a request actually adds them to the environ
+ dictionary (as 'bottle.request.ext.<name>'). This is the recommended
+ way to store and access request-specific data.
+ """
+
+ __slots__ = ('environ')
+
+ #: Maximum size of memory buffer for :attr:`body` in bytes.
+ MEMFILE_MAX = 102400
+
+ def __init__(self, environ=None):
+ """ Wrap a WSGI environ dictionary. """
+ #: The wrapped WSGI environ dictionary. This is the only real attribute.
+ #: All other attributes actually are read-only properties.
+ self.environ = {} if environ is None else environ
+ self.environ['bottle.request'] = self
+
+ @DictProperty('environ', 'bottle.app', read_only=True)
+ def app(self):
+ ''' Bottle application handling this request. '''
+ raise RuntimeError('This request is not connected to an application.')
+
+ @DictProperty('environ', 'bottle.route', read_only=True)
+ def route(self):
+ """ The bottle :class:`Route` object that matches this request. """
+ raise RuntimeError('This request is not connected to a route.')
+
+ @DictProperty('environ', 'route.url_args', read_only=True)
+ def url_args(self):
+ """ The arguments extracted from the URL. """
+ raise RuntimeError('This request is not connected to a route.')
+
+ @property
+ def path(self):
+ ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
+ broken clients and avoid the "empty path" edge case). '''
+ return '/' + self.environ.get('PATH_INFO','').lstrip('/')
+
+ @property
+ def method(self):
+ ''' The ``REQUEST_METHOD`` value as an uppercase string. '''
+ return self.environ.get('REQUEST_METHOD', 'GET').upper()
+
+ @DictProperty('environ', 'bottle.request.headers', read_only=True)
+ def headers(self):
+ ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to
+ HTTP request headers. '''
+ return WSGIHeaderDict(self.environ)
+
+ def get_header(self, name, default=None):
+ ''' Return the value of a request header, or a given default value. '''
+ return self.headers.get(name, default)
+
+ @DictProperty('environ', 'bottle.request.cookies', read_only=True)
+ def cookies(self):
+ """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
+ decoded. Use :meth:`get_cookie` if you expect signed cookies. """
+ cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')).values()
+ return FormsDict((c.key, c.value) for c in cookies)
+
+ def get_cookie(self, key, default=None, secret=None):
+ """ Return the content of a cookie. To read a `Signed Cookie`, the
+ `secret` must match the one used to create the cookie (see
+ :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
+ cookie or wrong signature), return a default value. """
+ value = self.cookies.get(key)
+ if secret and value:
+ dec = cookie_decode(value, secret) # (key, value) tuple or None
+ return dec[1] if dec and dec[0] == key else default
+ return value or default
+
+ @DictProperty('environ', 'bottle.request.query', read_only=True)
+ def query(self):
+ ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These
+ values are sometimes called "URL arguments" or "GET parameters", but
+ not to be confused with "URL wildcards" as they are provided by the
+ :class:`Router`. '''
+ get = self.environ['bottle.get'] = FormsDict()
+ pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
+ for key, value in pairs:
+ get[key] = value
+ return get
+
+ @DictProperty('environ', 'bottle.request.forms', read_only=True)
+ def forms(self):
+ """ Form values parsed from an `url-encoded` or `multipart/form-data`
+ encoded POST or PUT request body. The result is returned as a
+ :class:`FormsDict`. All keys and values are strings. File uploads
+ are stored separately in :attr:`files`. """
+ forms = FormsDict()
+ for name, item in self.POST.allitems():
+ if not isinstance(item, FileUpload):
+ forms[name] = item
+ return forms
+
+ @DictProperty('environ', 'bottle.request.params', read_only=True)
+ def params(self):
+ """ A :class:`FormsDict` with the combined values of :attr:`query` and
+ :attr:`forms`. File uploads are stored in :attr:`files`. """
+ params = FormsDict()
+ for key, value in self.query.allitems():
+ params[key] = value
+ for key, value in self.forms.allitems():
+ params[key] = value
+ return params
+
+ @DictProperty('environ', 'bottle.request.files', read_only=True)
+ def files(self):
+ """ File uploads parsed from `multipart/form-data` encoded POST or PUT
+ request body. The values are instances of :class:`FileUpload`.
+
+ """
+ files = FormsDict()
+ for name, item in self.POST.allitems():
+ if isinstance(item, FileUpload):
+ files[name] = item
+ return files
+
+ @DictProperty('environ', 'bottle.request.json', read_only=True)
+ def json(self):
+ ''' If the ``Content-Type`` header is ``application/json``, this
+ property holds the parsed content of the request body. Only requests
+ smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
+ exhaustion. '''
+ ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
+ if ctype == 'application/json':
+ b = self._get_body_string()
+ if not b:
+ return None
+ return json_loads(b)
+ return None
+
+ def _iter_body(self, read, bufsize):
+ maxread = max(0, self.content_length)
+ while maxread:
+ part = read(min(maxread, bufsize))
+ if not part: break
+ yield part
+ maxread -= len(part)
+
+ def _iter_chunked(self, read, bufsize):
+ err = HTTPError(400, 'Error while parsing chunked transfer body.')
+ rn, sem, bs = tob('\r\n'), tob(';'), tob('')
+ while True:
+ header = read(1)
+ while header[-2:] != rn:
+ c = read(1)
+ header += c
+ if not c: raise err
+ if len(header) > bufsize: raise err
+ size, _, _ = header.partition(sem)
+ try:
+ maxread = int(tonat(size.strip()), 16)
+ except ValueError:
+ raise err
+ if maxread == 0: break
+ buff = bs
+ while maxread > 0:
+ if not buff:
+ buff = read(min(maxread, bufsize))
+ part, buff = buff[:maxread], buff[maxread:]
+ if not part: raise err
+ yield part
+ maxread -= len(part)
+ if read(2) != rn:
+ raise err
+
+ @DictProperty('environ', 'bottle.request.body', read_only=True)
+ def _body(self):
+ body_iter = self._iter_chunked if self.chunked else self._iter_body
+ read_func = self.environ['wsgi.input'].read
+ body, body_size, is_temp_file = BytesIO(), 0, False
+ for part in body_iter(read_func, self.MEMFILE_MAX):
+ body.write(part)
+ body_size += len(part)
+ if not is_temp_file and body_size > self.MEMFILE_MAX:
+ body, tmp = TemporaryFile(mode='w+b'), body
+ body.write(tmp.getvalue())
+ del tmp
+ is_temp_file = True
+ self.environ['wsgi.input'] = body
+ body.seek(0)
+ return body
+
+ def _get_body_string(self):
+ ''' read body until content-length or MEMFILE_MAX into a string. Raise
+ HTTPError(413) on requests that are to large. '''
+ clen = self.content_length
+ if clen > self.MEMFILE_MAX:
+ raise HTTPError(413, 'Request to large')
+ if clen < 0: clen = self.MEMFILE_MAX + 1
+ data = self.body.read(clen)
+ if len(data) > self.MEMFILE_MAX: # Fail fast
+ raise HTTPError(413, 'Request to large')
+ return data
+
+ @property
+ def body(self):
+ """ The HTTP request body as a seek-able file-like object. Depending on
+ :attr:`MEMFILE_MAX`, this is either a temporary file or a
+ :class:`io.BytesIO` instance. Accessing this property for the first
+ time reads and replaces the ``wsgi.input`` environ variable.
+ Subsequent accesses just do a `seek(0)` on the file object. """
+ self._body.seek(0)
+ return self._body
+
+ @property
+ def chunked(self):
+ ''' True if Chunked transfer encoding was. '''
+ return 'chunked' in self.environ.get('HTTP_TRANSFER_ENCODING', '').lower()
+
+ #: An alias for :attr:`query`.
+ GET = query
+
+ @DictProperty('environ', 'bottle.request.post', read_only=True)
+ def POST(self):
+ """ The values of :attr:`forms` and :attr:`files` combined into a single
+ :class:`FormsDict`. Values are either strings (form values) or
+ instances of :class:`cgi.FieldStorage` (file uploads).
+ """
+ post = FormsDict()
+ # We default to application/x-www-form-urlencoded for everything that
+ # is not multipart and take the fast path (also: 3.1 workaround)
+ if not self.content_type.startswith('multipart/'):
+ pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
+ for key, value in pairs:
+ post[key] = value
+ return post
+
+ safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
+ for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
+ if key in self.environ: safe_env[key] = self.environ[key]
+ args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
+ if py31:
+ args['fp'] = NCTextIOWrapper(args['fp'], encoding='utf8',
+ newline='\n')
+ elif py3k:
+ args['encoding'] = 'utf8'
+ data = cgi.FieldStorage(**args)
+ self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394#msg207958
+ data = data.list or []
+ for item in data:
+ if item.filename:
+ post[item.name] = FileUpload(item.file, item.name,
+ item.filename, item.headers)
+ else:
+ post[item.name] = item.value
+ return post
+
+ @property
+ def url(self):
+ """ The full request URI including hostname and scheme. If your app
+ lives behind a reverse proxy or load balancer and you get confusing
+ results, make sure that the ``X-Forwarded-Host`` header is set
+ correctly. """
+ return self.urlparts.geturl()
+
+ @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
+ def urlparts(self):
+ ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
+ The tuple contains (scheme, host, path, query_string and fragment),
+ but the fragment is always empty because it is not visible to the
+ server. '''
+ env = self.environ
+ http = env.get('HTTP_X_FORWARDED_PROTO') or env.get('wsgi.url_scheme', 'http')
+ host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
+ if not host:
+ # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
+ host = env.get('SERVER_NAME', '127.0.0.1')
+ port = env.get('SERVER_PORT')
+ if port and port != ('80' if http == 'http' else '443'):
+ host += ':' + port
+ path = urlquote(self.fullpath)
+ return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
+
+ @property
+ def fullpath(self):
+ """ Request path including :attr:`script_name` (if present). """
+ return urljoin(self.script_name, self.path.lstrip('/'))
+
+ @property
+ def query_string(self):
+ """ The raw :attr:`query` part of the URL (everything in between ``?``
+ and ``#``) as a string. """
+ return self.environ.get('QUERY_STRING', '')
+
+ @property
+ def script_name(self):
+ ''' The initial portion of the URL's `path` that was removed by a higher
+ level (server or routing middleware) before the application was
+ called. This script path is returned with leading and tailing
+ slashes. '''
+ script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
+ return '/' + script_name + '/' if script_name else '/'
+
+ def path_shift(self, shift=1):
+ ''' Shift path segments from :attr:`path` to :attr:`script_name` and
+ vice versa.
+
+ :param shift: The number of path segments to shift. May be negative
+ to change the shift direction. (default: 1)
+ '''
+ script = self.environ.get('SCRIPT_NAME','/')
+ self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift)
+
+ @property
+ def content_length(self):
+ ''' The request body length as an integer. The client is responsible to
+ set this header. Otherwise, the real length of the body is unknown
+ and -1 is returned. In this case, :attr:`body` will be empty. '''
+ return int(self.environ.get('CONTENT_LENGTH') or -1)
+
+ @property
+ def content_type(self):
+ ''' The Content-Type header as a lowercase-string (default: empty). '''
+ return self.environ.get('CONTENT_TYPE', '').lower()
+
+ @property
+ def is_xhr(self):
+ ''' True if the request was triggered by a XMLHttpRequest. This only
+ works with JavaScript libraries that support the `X-Requested-With`
+ header (most of the popular libraries do). '''
+ requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','')
+ return requested_with.lower() == 'xmlhttprequest'
+
+ @property
+ def is_ajax(self):
+ ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. '''
+ return self.is_xhr
+
+ @property
+ def auth(self):
+ """ HTTP authentication data as a (user, password) tuple. This
+ implementation currently supports basic (not digest) authentication
+ only. If the authentication happened at a higher level (e.g. in the
+ front web-server or a middleware), the password field is None, but
+ the user field is looked up from the ``REMOTE_USER`` environ
+ variable. On any errors, None is returned. """
+ basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION',''))
+ if basic: return basic
+ ruser = self.environ.get('REMOTE_USER')
+ if ruser: return (ruser, None)
+ return None
+
+ @property
+ def remote_route(self):
+ """ A list of all IPs that were involved in this request, starting with
+ the client IP and followed by zero or more proxies. This does only
+ work if all proxies support the ```X-Forwarded-For`` header. Note
+ that this information can be forged by malicious clients. """
+ proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
+ if proxy: return [ip.strip() for ip in proxy.split(',')]
+ remote = self.environ.get('REMOTE_ADDR')
+ return [remote] if remote else []
+
+ @property
+ def remote_addr(self):
+ """ The client IP as a string. Note that this information can be forged
+ by malicious clients. """
+ route = self.remote_route
+ return route[0] if route else None
+
+ def copy(self):
+ """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
+ return Request(self.environ.copy())
+
+ def get(self, value, default=None): return self.environ.get(value, default)
+ def __getitem__(self, key): return self.environ[key]
+ def __delitem__(self, key): self[key] = ""; del(self.environ[key])
+ def __iter__(self): return iter(self.environ)
+ def __len__(self): return len(self.environ)
+ def keys(self): return self.environ.keys()
+ def __setitem__(self, key, value):
+ """ Change an environ value and clear all caches that depend on it. """
+
+ if self.environ.get('bottle.request.readonly'):
+ raise KeyError('The environ dictionary is read-only.')
+
+ self.environ[key] = value
+ todelete = ()
+
+ if key == 'wsgi.input':
+ todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
+ elif key == 'QUERY_STRING':
+ todelete = ('query', 'params')
+ elif key.startswith('HTTP_'):
+ todelete = ('headers', 'cookies')
+
+ for key in todelete:
+ self.environ.pop('bottle.request.'+key, None)
+
+ def __repr__(self):
+ return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
+
+ def __getattr__(self, name):
+ ''' Search in self.environ for additional user defined attributes. '''
+ try:
+ var = self.environ['bottle.request.ext.%s'%name]
+ return var.__get__(self) if hasattr(var, '__get__') else var
+ except KeyError:
+ raise AttributeError('Attribute %r not defined.' % name)
+
+ def __setattr__(self, name, value):
+ if name == 'environ': return object.__setattr__(self, name, value)
+ self.environ['bottle.request.ext.%s'%name] = value
+
+
+def _hkey(key):
+ if '\n' in key or '\r' in key or '\0' in key:
+ raise ValueError("Header names must not contain control characters: %r" % key)
+ return key.title().replace('_', '-')
+
+
+def _hval(value):
+ value = tonat(value)
+ if '\n' in value or '\r' in value or '\0' in value:
+ raise ValueError("Header value must not contain control characters: %r" % value)
+ return value
+
+
+
+class HeaderProperty(object):
+ def __init__(self, name, reader=None, writer=None, default=''):
+ self.name, self.default = name, default
+ self.reader, self.writer = reader, writer
+ self.__doc__ = 'Current value of the %r header.' % name.title()
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ value = obj.get_header(self.name, self.default)
+ return self.reader(value) if self.reader else value
+
+ def __set__(self, obj, value):
+ obj[self.name] = self.writer(value) if self.writer else value
+
+ def __delete__(self, obj):
+ del obj[self.name]
+
+
+class BaseResponse(object):
+ """ Storage class for a response body as well as headers and cookies.
+
+ This class does support dict-like case-insensitive item-access to
+ headers, but is NOT a dict. Most notably, iterating over a response
+ yields parts of the body and not the headers.
+
+ :param body: The response body as one of the supported types.
+ :param status: Either an HTTP status code (e.g. 200) or a status line
+ including the reason phrase (e.g. '200 OK').
+ :param headers: A dictionary or a list of name-value pairs.
+
+ Additional keyword arguments are added to the list of headers.
+ Underscores in the header name are replaced with dashes.
+ """
+
+ default_status = 200
+ default_content_type = 'text/html; charset=UTF-8'
+
+ # Header blacklist for specific response codes
+ # (rfc2616 section 10.2.3 and 10.3.5)
+ bad_headers = {
+ 204: set(('Content-Type',)),
+ 304: set(('Allow', 'Content-Encoding', 'Content-Language',
+ 'Content-Length', 'Content-Range', 'Content-Type',
+ 'Content-Md5', 'Last-Modified'))}
+
+ def __init__(self, body='', status=None, headers=None, **more_headers):
+ self._cookies = None
+ self._headers = {}
+ self.body = body
+ self.status = status or self.default_status
+ if headers:
+ if isinstance(headers, dict):
+ headers = headers.items()
+ for name, value in headers:
+ self.add_header(name, value)
+ if more_headers:
+ for name, value in more_headers.items():
+ self.add_header(name, value)
+
+ def copy(self, cls=None):
+ ''' Returns a copy of self. '''
+ cls = cls or BaseResponse
+ assert issubclass(cls, BaseResponse)
+ copy = cls()
+ copy.status = self.status
+ copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
+ if self._cookies:
+ copy._cookies = SimpleCookie()
+ copy._cookies.load(self._cookies.output(header=''))
+ return copy
+
+ def __iter__(self):
+ return iter(self.body)
+
+ def close(self):
+ if hasattr(self.body, 'close'):
+ self.body.close()
+
+ @property
+ def status_line(self):
+ ''' The HTTP status line as a string (e.g. ``404 Not Found``).'''
+ return self._status_line
+
+ @property
+ def status_code(self):
+ ''' The HTTP status code as an integer (e.g. 404).'''
+ return self._status_code
+
+ def _set_status(self, status):
+ if isinstance(status, int):
+ code, status = status, _HTTP_STATUS_LINES.get(status)
+ elif ' ' in status:
+ status = status.strip()
+ code = int(status.split()[0])
+ else:
+ raise ValueError('String status line without a reason phrase.')
+ if not 100 <= code <= 999: raise ValueError('Status code out of range.')
+ self._status_code = code
+ self._status_line = str(status or ('%d Unknown' % code))
+
+ def _get_status(self):
+ return self._status_line
+
+ status = property(_get_status, _set_status, None,
+ ''' A writeable property to change the HTTP response status. It accepts
+ either a numeric code (100-999) or a string with a custom reason
+ phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
+ :data:`status_code` are updated accordingly. The return value is
+ always a status string. ''')
+ del _get_status, _set_status
+
+ @property
+ def headers(self):
+ ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like
+ view on the response headers. '''
+ hdict = HeaderDict()
+ hdict.dict = self._headers
+ return hdict
+
+ def __contains__(self, name): return _hkey(name) in self._headers
+ def __delitem__(self, name): del self._headers[_hkey(name)]
+ def __getitem__(self, name): return self._headers[_hkey(name)][-1]
+ def __setitem__(self, name, value): self._headers[_hkey(name)] = [_hval(value)]
+
+ def get_header(self, name, default=None):
+ ''' Return the value of a previously defined header. If there is no
+ header with that name, return a default value. '''
+ return self._headers.get(_hkey(name), [default])[-1]
+
+ def set_header(self, name, value):
+ ''' Create a new response header, replacing any previously defined
+ headers with the same name. '''
+ self._headers[_hkey(name)] = [_hval(value)]
+
+ def add_header(self, name, value):
+ ''' Add an additional response header, not removing duplicates. '''
+ self._headers.setdefault(_hkey(name), []).append(_hval(value))
+
+ def iter_headers(self):
+ ''' Yield (header, value) tuples, skipping headers that are not
+ allowed with the current response status code. '''
+ return self.headerlist
+
+ @property
+ def headerlist(self):
+ """ WSGI conform list of (header, value) tuples. """
+ out = []
+ headers = list(self._headers.items())
+ if 'Content-Type' not in self._headers:
+ headers.append(('Content-Type', [self.default_content_type]))
+ if self._status_code in self.bad_headers:
+ bad_headers = self.bad_headers[self._status_code]
+ headers = [h for h in headers if h[0] not in bad_headers]
+ out += [(name, val) for (name, vals) in headers for val in vals]
+ if self._cookies:
+ for c in self._cookies.values():
+ out.append(('Set-Cookie', _hval(c.OutputString())))
+ if py3k:
+ out = [(k, v.encode('utf8').decode('latin1')) for (k, v) in out]
+ return out
+
+ content_type = HeaderProperty('Content-Type')
+ content_length = HeaderProperty('Content-Length', reader=int)
+ expires = HeaderProperty('Expires',
+ reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
+ writer=lambda x: http_date(x))
+
+ @property
+ def charset(self, default='UTF-8'):
+ """ Return the charset specified in the content-type header (default: utf8). """
+ if 'charset=' in self.content_type:
+ return self.content_type.split('charset=')[-1].split(';')[0].strip()
+ return default
+
+ def set_cookie(self, name, value, secret=None, **options):
+ ''' Create a new cookie or replace an old one. If the `secret` parameter is
+ set, create a `Signed Cookie` (described below).
+
+ :param name: the name of the cookie.
+ :param value: the value of the cookie.
+ :param secret: a signature key required for signed cookies.
+
+ Additionally, this method accepts all RFC 2109 attributes that are
+ supported by :class:`cookie.Morsel`, including:
+
+ :param max_age: maximum age in seconds. (default: None)
+ :param expires: a datetime object or UNIX timestamp. (default: None)
+ :param domain: the domain that is allowed to read the cookie.
+ (default: current domain)
+ :param path: limits the cookie to a given path (default: current path)
+ :param secure: limit the cookie to HTTPS connections (default: off).
+ :param httponly: prevents client-side javascript to read this cookie
+ (default: off, requires Python 2.6 or newer).
+
+ If neither `expires` nor `max_age` is set (default), the cookie will
+ expire at the end of the browser session (as soon as the browser
+ window is closed).
+
+ Signed cookies may store any pickle-able object and are
+ cryptographically signed to prevent manipulation. Keep in mind that
+ cookies are limited to 4kb in most browsers.
+
+ Warning: Signed cookies are not encrypted (the client can still see
+ the content) and not copy-protected (the client can restore an old
+ cookie). The main intention is to make pickling and unpickling
+ save, not to store secret information at client side.
+ '''
+ if not self._cookies:
+ self._cookies = SimpleCookie()
+
+ if secret:
+ value = touni(cookie_encode((name, value), secret))
+ elif not isinstance(value, basestring):
+ raise TypeError('Secret key missing for non-string Cookie.')
+
+ if len(value) > 4096: raise ValueError('Cookie value to long.')
+ self._cookies[name] = value
+
+ for key, value in options.items():
+ if key == 'max_age':
+ if isinstance(value, timedelta):
+ value = value.seconds + value.days * 24 * 3600
+ if key == 'expires':
+ if isinstance(value, (datedate, datetime)):
+ value = value.timetuple()
+ elif isinstance(value, (int, float)):
+ value = time.gmtime(value)
+ value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
+ self._cookies[name][key.replace('_', '-')] = value
+
+ def delete_cookie(self, key, **kwargs):
+ ''' Delete a cookie. Be sure to use the same `domain` and `path`
+ settings as used to create the cookie. '''
+ kwargs['max_age'] = -1
+ kwargs['expires'] = 0
+ self.set_cookie(key, '', **kwargs)
+
+ def __repr__(self):
+ out = ''
+ for name, value in self.headerlist:
+ out += '%s: %s\n' % (name.title(), value.strip())
+ return out
+
+
+def local_property(name=None):
+ if name: depr('local_property() is deprecated and will be removed.') #0.12
+ ls = threading.local()
+ def fget(self):
+ try: return ls.var
+ except AttributeError:
+ raise RuntimeError("Request context not initialized.")
+ def fset(self, value): ls.var = value
+ def fdel(self): del ls.var
+ return property(fget, fset, fdel, 'Thread-local property')
+
+
+class LocalRequest(BaseRequest):
+ ''' A thread-local subclass of :class:`BaseRequest` with a different
+ set of attributes for each thread. There is usually only one global
+ instance of this class (:data:`request`). If accessed during a
+ request/response cycle, this instance always refers to the *current*
+ request (even on a multithreaded server). '''
+ bind = BaseRequest.__init__
+ environ = local_property()
+
+
+class LocalResponse(BaseResponse):
+ ''' A thread-local subclass of :class:`BaseResponse` with a different
+ set of attributes for each thread. There is usually only one global
+ instance of this class (:data:`response`). Its attributes are used
+ to build the HTTP response at the end of the request/response cycle.
+ '''
+ bind = BaseResponse.__init__
+ _status_line = local_property()
+ _status_code = local_property()
+ _cookies = local_property()
+ _headers = local_property()
+ body = local_property()
+
+
+Request = BaseRequest
+Response = BaseResponse
+
+
+class HTTPResponse(Response, BottleException):
+ def __init__(self, body='', status=None, headers=None, **more_headers):
+ super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
+
+ def apply(self, response):
+ response._status_code = self._status_code
+ response._status_line = self._status_line
+ response._headers = self._headers
+ response._cookies = self._cookies
+ response.body = self.body
+
+
+class HTTPError(HTTPResponse):
+ default_status = 500
+ def __init__(self, status=None, body=None, exception=None, traceback=None,
+ **options):
+ self.exception = exception
+ self.traceback = traceback
+ super(HTTPError, self).__init__(body, status, **options)
+
+
+
+
+
+###############################################################################
+# Plugins ######################################################################
+###############################################################################
+
+class PluginError(BottleException): pass
+
+
+class JSONPlugin(object):
+ name = 'json'
+ api = 2
+
+ def __init__(self, json_dumps=json_dumps):
+ self.json_dumps = json_dumps
+
+ def apply(self, callback, route):
+ dumps = self.json_dumps
+ if not dumps: return callback
+ def wrapper(*a, **ka):
+ try:
+ rv = callback(*a, **ka)
+ except HTTPError:
+ rv = _e()
+
+ if isinstance(rv, dict):
+ #Attempt to serialize, raises exception on failure
+ json_response = dumps(rv)
+ #Set content type only if serialization succesful
+ response.content_type = 'application/json'
+ return json_response
+ elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
+ rv.body = dumps(rv.body)
+ rv.content_type = 'application/json'
+ return rv
+
+ return wrapper
+
+
+class TemplatePlugin(object):
+ ''' This plugin applies the :func:`view` decorator to all routes with a
+ `template` config parameter. If the parameter is a tuple, the second
+ element must be a dict with additional options (e.g. `template_engine`)
+ or default variables for the template. '''
+ name = 'template'
+ api = 2
+
+ def apply(self, callback, route):
+ conf = route.config.get('template')
+ if isinstance(conf, (tuple, list)) and len(conf) == 2:
+ return view(conf[0], **conf[1])(callback)
+ elif isinstance(conf, str):
+ return view(conf)(callback)
+ else:
+ return callback
+
+
+#: Not a plugin, but part of the plugin API. TODO: Find a better place.
+class _ImportRedirect(object):
+ def __init__(self, name, impmask):
+ ''' Create a virtual package that redirects imports (see PEP 302). '''
+ self.name = name
+ self.impmask = impmask
+ self.module = sys.modules.setdefault(name, new_module(name))
+ self.module.__dict__.update({'__file__': __file__, '__path__': [],
+ '__all__': [], '__loader__': self})
+ sys.meta_path.append(self)
+
+ def find_module(self, fullname, path=None):
+ if '.' not in fullname: return
+ packname = fullname.rsplit('.', 1)[0]
+ if packname != self.name: return
+ return self
+
+ def load_module(self, fullname):
+ if fullname in sys.modules: return sys.modules[fullname]
+ modname = fullname.rsplit('.', 1)[1]
+ realname = self.impmask % modname
+ __import__(realname)
+ module = sys.modules[fullname] = sys.modules[realname]
+ setattr(self.module, modname, module)
+ module.__loader__ = self
+ return module
+
+
+
+
+
+
+###############################################################################
+# Common Utilities #############################################################
+###############################################################################
+
+
+class MultiDict(DictMixin):
+ """ This dict stores multiple values per key, but behaves exactly like a
+ normal dict in that it returns only the newest value for any given key.
+ There are special methods available to access the full list of values.
+ """
+
+ def __init__(self, *a, **k):
+ self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
+
+ def __len__(self): return len(self.dict)
+ def __iter__(self): return iter(self.dict)
+ def __contains__(self, key): return key in self.dict
+ def __delitem__(self, key): del self.dict[key]
+ def __getitem__(self, key): return self.dict[key][-1]
+ def __setitem__(self, key, value): self.append(key, value)
+ def keys(self): return self.dict.keys()
+
+ if py3k:
+ def values(self): return (v[-1] for v in self.dict.values())
+ def items(self): return ((k, v[-1]) for k, v in self.dict.items())
+ def allitems(self):
+ return ((k, v) for k, vl in self.dict.items() for v in vl)
+ iterkeys = keys
+ itervalues = values
+ iteritems = items
+ iterallitems = allitems
+
+ else:
+ def values(self): return [v[-1] for v in self.dict.values()]
+ def items(self): return [(k, v[-1]) for k, v in self.dict.items()]
+ def iterkeys(self): return self.dict.iterkeys()
+ def itervalues(self): return (v[-1] for v in self.dict.itervalues())
+ def iteritems(self):
+ return ((k, v[-1]) for k, v in self.dict.iteritems())
+ def iterallitems(self):
+ return ((k, v) for k, vl in self.dict.iteritems() for v in vl)
+ def allitems(self):
+ return [(k, v) for k, vl in self.dict.iteritems() for v in vl]
+
+ def get(self, key, default=None, index=-1, type=None):
+ ''' Return the most recent value for a key.
+
+ :param default: The default value to be returned if the key is not
+ present or the type conversion fails.
+ :param index: An index for the list of available values.
+ :param type: If defined, this callable is used to cast the value
+ into a specific type. Exception are suppressed and result in
+ the default value to be returned.
+ '''
+ try:
+ val = self.dict[key][index]
+ return type(val) if type else val
+ except Exception:
+ pass
+ return default
+
+ def append(self, key, value):
+ ''' Add a new value to the list of values for this key. '''
+ self.dict.setdefault(key, []).append(value)
+
+ def replace(self, key, value):
+ ''' Replace the list of values with a single value. '''
+ self.dict[key] = [value]
+
+ def getall(self, key):
+ ''' Return a (possibly empty) list of values for a key. '''
+ return self.dict.get(key) or []
+
+ #: Aliases for WTForms to mimic other multi-dict APIs (Django)
+ getone = get
+ getlist = getall
+
+
+class FormsDict(MultiDict):
+ ''' This :class:`MultiDict` subclass is used to store request form data.
+ Additionally to the normal dict-like item access methods (which return
+ unmodified data as native strings), this container also supports
+ attribute-like access to its values. Attributes are automatically de-
+ or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
+ attributes default to an empty string. '''
+
+ #: Encoding used for attribute values.
+ input_encoding = 'utf8'
+ #: If true (default), unicode strings are first encoded with `latin1`
+ #: and then decoded to match :attr:`input_encoding`.
+ recode_unicode = True
+
+ def _fix(self, s, encoding=None):
+ if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI
+ return s.encode('latin1').decode(encoding or self.input_encoding)
+ elif isinstance(s, bytes): # Python 2 WSGI
+ return s.decode(encoding or self.input_encoding)
+ else:
+ return s
+
+ def decode(self, encoding=None):
+ ''' Returns a copy with all keys and values de- or recoded to match
+ :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
+ unicode dictionary. '''
+ copy = FormsDict()
+ enc = copy.input_encoding = encoding or self.input_encoding
+ copy.recode_unicode = False
+ for key, value in self.allitems():
+ copy.append(self._fix(key, enc), self._fix(value, enc))
+ return copy
+
+ def getunicode(self, name, default=None, encoding=None):
+ ''' Return the value as a unicode string, or the default. '''
+ try:
+ return self._fix(self[name], encoding)
+ except (UnicodeError, KeyError):
+ return default
+
+ def __getattr__(self, name, default=unicode()):
+ # Without this guard, pickle generates a cryptic TypeError:
+ if name.startswith('__') and name.endswith('__'):
+ return super(FormsDict, self).__getattr__(name)
+ return self.getunicode(name, default=default)
+
+class HeaderDict(MultiDict):
+ """ A case-insensitive version of :class:`MultiDict` that defaults to
+ replace the old value instead of appending it. """
+
+ def __init__(self, *a, **ka):
+ self.dict = {}
+ if a or ka: self.update(*a, **ka)
+
+ def __contains__(self, key): return _hkey(key) in self.dict
+ def __delitem__(self, key): del self.dict[_hkey(key)]
+ def __getitem__(self, key): return self.dict[_hkey(key)][-1]
+ def __setitem__(self, key, value): self.dict[_hkey(key)] = [_hval(value)]
+ def append(self, key, value): self.dict.setdefault(_hkey(key), []).append(_hval(value))
+ def replace(self, key, value): self.dict[_hkey(key)] = [_hval(value)]
+ def getall(self, key): return self.dict.get(_hkey(key)) or []
+ def get(self, key, default=None, index=-1):
+ return MultiDict.get(self, _hkey(key), default, index)
+ def filter(self, names):
+ for name in (_hkey(n) for n in names):
+ if name in self.dict:
+ del self.dict[name]
+
+
+class WSGIHeaderDict(DictMixin):
+ ''' This dict-like class wraps a WSGI environ dict and provides convenient
+ access to HTTP_* fields. Keys and values are native strings
+ (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
+ environment contains non-native string values, these are de- or encoded
+ using a lossless 'latin1' character set.
+
+ The API will remain stable even on changes to the relevant PEPs.
+ Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
+ that uses non-native strings.)
+ '''
+ #: List of keys that do not have a ``HTTP_`` prefix.
+ cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
+
+ def __init__(self, environ):
+ self.environ = environ
+
+ def _ekey(self, key):
+ ''' Translate header field name to CGI/WSGI environ key. '''
+ key = key.replace('-','_').upper()
+ if key in self.cgikeys:
+ return key
+ return 'HTTP_' + key
+
+ def raw(self, key, default=None):
+ ''' Return the header value as is (may be bytes or unicode). '''
+ return self.environ.get(self._ekey(key), default)
+
+ def __getitem__(self, key):
+ return tonat(self.environ[self._ekey(key)], 'latin1')
+
+ def __setitem__(self, key, value):
+ raise TypeError("%s is read-only." % self.__class__)
+
+ def __delitem__(self, key):
+ raise TypeError("%s is read-only." % self.__class__)
+
+ def __iter__(self):
+ for key in self.environ:
+ if key[:5] == 'HTTP_':
+ yield key[5:].replace('_', '-').title()
+ elif key in self.cgikeys:
+ yield key.replace('_', '-').title()
+
+ def keys(self): return [x for x in self]
+ def __len__(self): return len(self.keys())
+ def __contains__(self, key): return self._ekey(key) in self.environ
+
+
+
+class ConfigDict(dict):
+ ''' A dict-like configuration storage with additional support for
+ namespaces, validators, meta-data, on_change listeners and more.
+
+ This storage is optimized for fast read access. Retrieving a key
+ or using non-altering dict methods (e.g. `dict.get()`) has no overhead
+ compared to a native dict.
+ '''
+ __slots__ = ('_meta', '_on_change')
+
+ class Namespace(DictMixin):
+
+ def __init__(self, config, namespace):
+ self._config = config
+ self._prefix = namespace
+
+ def __getitem__(self, key):
+ depr('Accessing namespaces as dicts is discouraged. '
+ 'Only use flat item access: '
+ 'cfg["names"]["pace"]["key"] -> cfg["name.space.key"]') #0.12
+ return self._config[self._prefix + '.' + key]
+
+ def __setitem__(self, key, value):
+ self._config[self._prefix + '.' + key] = value
+
+ def __delitem__(self, key):
+ del self._config[self._prefix + '.' + key]
+
+ def __iter__(self):
+ ns_prefix = self._prefix + '.'
+ for key in self._config:
+ ns, dot, name = key.rpartition('.')
+ if ns == self._prefix and name:
+ yield name
+
+ def keys(self): return [x for x in self]
+ def __len__(self): return len(self.keys())
+ def __contains__(self, key): return self._prefix + '.' + key in self._config
+ def __repr__(self): return '<Config.Namespace %s.*>' % self._prefix
+ def __str__(self): return '<Config.Namespace %s.*>' % self._prefix
+
+ # Deprecated ConfigDict features
+ def __getattr__(self, key):
+ depr('Attribute access is deprecated.') #0.12
+ if key not in self and key[0].isupper():
+ self[key] = ConfigDict.Namespace(self._config, self._prefix + '.' + key)
+ if key not in self and key.startswith('__'):
+ raise AttributeError(key)
+ return self.get(key)
+
+ def __setattr__(self, key, value):
+ if key in ('_config', '_prefix'):
+ self.__dict__[key] = value
+ return
+ depr('Attribute assignment is deprecated.') #0.12
+ if hasattr(DictMixin, key):
+ raise AttributeError('Read-only attribute.')
+ if key in self and self[key] and isinstance(self[key], self.__class__):
+ raise AttributeError('Non-empty namespace attribute.')
+ self[key] = value
+
+ def __delattr__(self, key):
+ if key in self:
+ val = self.pop(key)
+ if isinstance(val, self.__class__):
+ prefix = key + '.'
+ for key in self:
+ if key.startswith(prefix):
+ del self[prefix+key]
+
+ def __call__(self, *a, **ka):
+ depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
+ self.update(*a, **ka)
+ return self
+
+ def __init__(self, *a, **ka):
+ self._meta = {}
+ self._on_change = lambda name, value: None
+ if a or ka:
+ depr('Constructor does no longer accept parameters.') #0.12
+ self.update(*a, **ka)
+
+ def load_config(self, filename):
+ ''' Load values from an *.ini style config file.
+
+ If the config file contains sections, their names are used as
+ namespaces for the values within. The two special sections
+ ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix).
+ '''
+ conf = ConfigParser()
+ conf.read(filename)
+ for section in conf.sections():
+ for key, value in conf.items(section):
+ if section not in ('DEFAULT', 'bottle'):
+ key = section + '.' + key
+ self[key] = value
+ return self
+
+ def load_dict(self, source, namespace='', make_namespaces=False):
+ ''' Import values from a dictionary structure. Nesting can be used to
+ represent namespaces.
+
+ >>> ConfigDict().load_dict({'name': {'space': {'key': 'value'}}})
+ {'name.space.key': 'value'}
+ '''
+ stack = [(namespace, source)]
+ while stack:
+ prefix, source = stack.pop()
+ if not isinstance(source, dict):
+ raise TypeError('Source is not a dict (r)' % type(key))
+ for key, value in source.items():
+ if not isinstance(key, basestring):
+ raise TypeError('Key is not a string (%r)' % type(key))
+ full_key = prefix + '.' + key if prefix else key
+ if isinstance(value, dict):
+ stack.append((full_key, value))
+ if make_namespaces:
+ self[full_key] = self.Namespace(self, full_key)
+ else:
+ self[full_key] = value
+ return self
+
+ def update(self, *a, **ka):
+ ''' If the first parameter is a string, all keys are prefixed with this
+ namespace. Apart from that it works just as the usual dict.update().
+ Example: ``update('some.namespace', key='value')`` '''
+ prefix = ''
+ if a and isinstance(a[0], basestring):
+ prefix = a[0].strip('.') + '.'
+ a = a[1:]
+ for key, value in dict(*a, **ka).items():
+ self[prefix+key] = value
+
+ def setdefault(self, key, value):
+ if key not in self:
+ self[key] = value
+ return self[key]
+
+ def __setitem__(self, key, value):
+ if not isinstance(key, basestring):
+ raise TypeError('Key has type %r (not a string)' % type(key))
+
+ value = self.meta_get(key, 'filter', lambda x: x)(value)
+ if key in self and self[key] is value:
+ return
+ self._on_change(key, value)
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+
+ def clear(self):
+ for key in self:
+ del self[key]
+
+ def meta_get(self, key, metafield, default=None):
+ ''' Return the value of a meta field for a key. '''
+ return self._meta.get(key, {}).get(metafield, default)
+
+ def meta_set(self, key, metafield, value):
+ ''' Set the meta field for a key to a new value. This triggers the
+ on-change handler for existing keys. '''
+ self._meta.setdefault(key, {})[metafield] = value
+ if key in self:
+ self[key] = self[key]
+
+ def meta_list(self, key):
+ ''' Return an iterable of meta field names defined for a key. '''
+ return self._meta.get(key, {}).keys()
+
+ # Deprecated ConfigDict features
+ def __getattr__(self, key):
+ depr('Attribute access is deprecated.') #0.12
+ if key not in self and key[0].isupper():
+ self[key] = self.Namespace(self, key)
+ if key not in self and key.startswith('__'):
+ raise AttributeError(key)
+ return self.get(key)
+
+ def __setattr__(self, key, value):
+ if key in self.__slots__:
+ return dict.__setattr__(self, key, value)
+ depr('Attribute assignment is deprecated.') #0.12
+ if hasattr(dict, key):
+ raise AttributeError('Read-only attribute.')
+ if key in self and self[key] and isinstance(self[key], self.Namespace):
+ raise AttributeError('Non-empty namespace attribute.')
+ self[key] = value
+
+ def __delattr__(self, key):
+ if key in self:
+ val = self.pop(key)
+ if isinstance(val, self.Namespace):
+ prefix = key + '.'
+ for key in self:
+ if key.startswith(prefix):
+ del self[prefix+key]
+
+ def __call__(self, *a, **ka):
+ depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
+ self.update(*a, **ka)
+ return self
+
+
+
+class AppStack(list):
+ """ A stack-like list. Calling it returns the head of the stack. """
+
+ def __call__(self):
+ """ Return the current default application. """
+ return self[-1]
+
+ def push(self, value=None):
+ """ Add a new :class:`Bottle` instance to the stack """
+ if not isinstance(value, Bottle):
+ value = Bottle()
+ self.append(value)
+ return value
+
+
+class WSGIFileWrapper(object):
+
+ def __init__(self, fp, buffer_size=1024*64):
+ self.fp, self.buffer_size = fp, buffer_size
+ for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
+ if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
+
+ def __iter__(self):
+ buff, read = self.buffer_size, self.read
+ while True:
+ part = read(buff)
+ if not part: return
+ yield part
+
+
+class _closeiter(object):
+ ''' This only exists to be able to attach a .close method to iterators that
+ do not support attribute assignment (most of itertools). '''
+
+ def __init__(self, iterator, close=None):
+ self.iterator = iterator
+ self.close_callbacks = makelist(close)
+
+ def __iter__(self):
+ return iter(self.iterator)
+
+ def close(self):
+ for func in self.close_callbacks:
+ func()
+
+
+class ResourceManager(object):
+ ''' This class manages a list of search paths and helps to find and open
+ application-bound resources (files).
+
+ :param base: default value for :meth:`add_path` calls.
+ :param opener: callable used to open resources.
+ :param cachemode: controls which lookups are cached. One of 'all',
+ 'found' or 'none'.
+ '''
+
+ def __init__(self, base='./', opener=open, cachemode='all'):
+ self.opener = open
+ self.base = base
+ self.cachemode = cachemode
+
+ #: A list of search paths. See :meth:`add_path` for details.
+ self.path = []
+ #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
+ self.cache = {}
+
+ def add_path(self, path, base=None, index=None, create=False):
+ ''' Add a new path to the list of search paths. Return False if the
+ path does not exist.
+
+ :param path: The new search path. Relative paths are turned into
+ an absolute and normalized form. If the path looks like a file
+ (not ending in `/`), the filename is stripped off.
+ :param base: Path used to absolutize relative search paths.
+ Defaults to :attr:`base` which defaults to ``os.getcwd()``.
+ :param index: Position within the list of search paths. Defaults
+ to last index (appends to the list).
+
+ The `base` parameter makes it easy to reference files installed
+ along with a python module or package::
+
+ res.add_path('./resources/', __file__)
+ '''
+ base = os.path.abspath(os.path.dirname(base or self.base))
+ path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
+ path += os.sep
+ if path in self.path:
+ self.path.remove(path)
+ if create and not os.path.isdir(path):
+ os.makedirs(path)
+ if index is None:
+ self.path.append(path)
+ else:
+ self.path.insert(index, path)
+ self.cache.clear()
+ return os.path.exists(path)
+
+ def __iter__(self):
+ ''' Iterate over all existing files in all registered paths. '''
+ search = self.path[:]
+ while search:
+ path = search.pop()
+ if not os.path.isdir(path): continue
+ for name in os.listdir(path):
+ full = os.path.join(path, name)
+ if os.path.isdir(full): search.append(full)
+ else: yield full
+
+ def lookup(self, name):
+ ''' Search for a resource and return an absolute file path, or `None`.
+
+ The :attr:`path` list is searched in order. The first match is
+ returend. Symlinks are followed. The result is cached to speed up
+ future lookups. '''
+ if name not in self.cache or DEBUG:
+ for path in self.path:
+ fpath = os.path.join(path, name)
+ if os.path.isfile(fpath):
+ if self.cachemode in ('all', 'found'):
+ self.cache[name] = fpath
+ return fpath
+ if self.cachemode == 'all':
+ self.cache[name] = None
+ return self.cache[name]
+
+ def open(self, name, mode='r', *args, **kwargs):
+ ''' Find a resource and return a file object, or raise IOError. '''
+ fname = self.lookup(name)
+ if not fname: raise IOError("Resource %r not found." % name)
+ return self.opener(fname, mode=mode, *args, **kwargs)
+
+
+class FileUpload(object):
+
+ def __init__(self, fileobj, name, filename, headers=None):
+ ''' Wrapper for file uploads. '''
+ #: Open file(-like) object (BytesIO buffer or temporary file)
+ self.file = fileobj
+ #: Name of the upload form field
+ self.name = name
+ #: Raw filename as sent by the client (may contain unsafe characters)
+ self.raw_filename = filename
+ #: A :class:`HeaderDict` with additional headers (e.g. content-type)
+ self.headers = HeaderDict(headers) if headers else HeaderDict()
+
+ content_type = HeaderProperty('Content-Type')
+ content_length = HeaderProperty('Content-Length', reader=int, default=-1)
+
+ def get_header(self, name, default=None):
+ """ Return the value of a header within the mulripart part. """
+ return self.headers.get(name, default)
+
+ @cached_property
+ def filename(self):
+ ''' Name of the file on the client file system, but normalized to ensure
+ file system compatibility. An empty filename is returned as 'empty'.
+
+ Only ASCII letters, digits, dashes, underscores and dots are
+ allowed in the final filename. Accents are removed, if possible.
+ Whitespace is replaced by a single dash. Leading or tailing dots
+ or dashes are removed. The filename is limited to 255 characters.
+ '''
+ fname = self.raw_filename
+ if not isinstance(fname, unicode):
+ fname = fname.decode('utf8', 'ignore')
+ fname = normalize('NFKD', fname).encode('ASCII', 'ignore').decode('ASCII')
+ fname = os.path.basename(fname.replace('\\', os.path.sep))
+ fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
+ fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
+ return fname[:255] or 'empty'
+
+ def _copy_file(self, fp, chunk_size=2**16):
+ read, write, offset = self.file.read, fp.write, self.file.tell()
+ while 1:
+ buf = read(chunk_size)
+ if not buf: break
+ write(buf)
+ self.file.seek(offset)
+
+ def save(self, destination, overwrite=False, chunk_size=2**16):
+ ''' Save file to disk or copy its content to an open file(-like) object.
+ If *destination* is a directory, :attr:`filename` is added to the
+ path. Existing files are not overwritten by default (IOError).
+
+ :param destination: File path, directory or file(-like) object.
+ :param overwrite: If True, replace existing files. (default: False)
+ :param chunk_size: Bytes to read at a time. (default: 64kb)
+ '''
+ if isinstance(destination, basestring): # Except file-likes here
+ if os.path.isdir(destination):
+ destination = os.path.join(destination, self.filename)
+ if not overwrite and os.path.exists(destination):
+ raise IOError('File exists.')
+ with open(destination, 'wb') as fp:
+ self._copy_file(fp, chunk_size)
+ else:
+ self._copy_file(destination, chunk_size)
+
+
+
+
+
+
+###############################################################################
+# Application Helper ###########################################################
+###############################################################################
+
+
+def abort(code=500, text='Unknown Error.'):
+ """ Aborts execution and causes a HTTP error. """
+ raise HTTPError(code, text)
+
+
+def redirect(url, code=None):
+ """ Aborts execution and causes a 303 or 302 redirect, depending on
+ the HTTP protocol version. """
+ if not code:
+ code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
+ res = response.copy(cls=HTTPResponse)
+ res.status = code
+ res.body = ""
+ res.set_header('Location', urljoin(request.url, url))
+ raise res
+
+
+def _file_iter_range(fp, offset, bytes, maxread=1024*1024):
+ ''' Yield chunks from a range in a file. No chunk is bigger than maxread.'''
+ fp.seek(offset)
+ while bytes > 0:
+ part = fp.read(min(bytes, maxread))
+ if not part: break
+ bytes -= len(part)
+ yield part
+
+
+def static_file(filename, root, mimetype='auto', download=False, charset='UTF-8'):
+ """ Open a file in a safe way and return :exc:`HTTPResponse` with status
+ code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
+ ``Content-Length`` and ``Last-Modified`` headers are set if possible.
+ Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
+ requests.
+
+ :param filename: Name or path of the file to send.
+ :param root: Root path for file lookups. Should be an absolute directory
+ path.
+ :param mimetype: Defines the content-type header (default: guess from
+ file extension)
+ :param download: If True, ask the browser to open a `Save as...` dialog
+ instead of opening the file with the associated program. You can
+ specify a custom filename as a string. If not specified, the
+ original filename is used (default: False).
+ :param charset: The charset to use for files with a ``text/*``
+ mime-type. (default: UTF-8)
+ """
+
+ root = os.path.abspath(root) + os.sep
+ filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
+ headers = dict()
+
+ if not filename.startswith(root):
+ return HTTPError(403, "Access denied.")
+ if not os.path.exists(filename) or not os.path.isfile(filename):
+ return HTTPError(404, "File does not exist.")
+ if not os.access(filename, os.R_OK):
+ return HTTPError(403, "You do not have permission to access this file.")
+
+ if mimetype == 'auto':
+ mimetype, encoding = mimetypes.guess_type(filename)
+ if encoding: headers['Content-Encoding'] = encoding
+
+ if mimetype:
+ if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
+ mimetype += '; charset=%s' % charset
+ headers['Content-Type'] = mimetype
+
+ if download:
+ download = os.path.basename(filename if download == True else download)
+ headers['Content-Disposition'] = 'attachment; filename="%s"' % download
+
+ stats = os.stat(filename)
+ headers['Content-Length'] = clen = stats.st_size
+ lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
+ headers['Last-Modified'] = lm
+
+ ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
+ if ims:
+ ims = parse_date(ims.split(";")[0].strip())
+ if ims is not None and ims >= int(stats.st_mtime):
+ headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+ return HTTPResponse(status=304, **headers)
+
+ body = '' if request.method == 'HEAD' else open(filename, 'rb')
+
+ headers["Accept-Ranges"] = "bytes"
+ ranges = request.environ.get('HTTP_RANGE')
+ if 'HTTP_RANGE' in request.environ:
+ ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen))
+ if not ranges:
+ return HTTPError(416, "Requested Range Not Satisfiable")
+ offset, end = ranges[0]
+ headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen)
+ headers["Content-Length"] = str(end-offset)
+ if body: body = _file_iter_range(body, offset, end-offset)
+ return HTTPResponse(body, status=206, **headers)
+ return HTTPResponse(body, **headers)
+
+
+
+
+
+
+###############################################################################
+# HTTP Utilities and MISC (TODO) ###############################################
+###############################################################################
+
+
+def debug(mode=True):
+ """ Change the debug level.
+ There is only one debug level supported at the moment."""
+ global DEBUG
+ if mode: warnings.simplefilter('default')
+ DEBUG = bool(mode)
+
+def http_date(value):
+ if isinstance(value, (datedate, datetime)):
+ value = value.utctimetuple()
+ elif isinstance(value, (int, float)):
+ value = time.gmtime(value)
+ if not isinstance(value, basestring):
+ value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
+ return value
+
+def parse_date(ims):
+ """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
+ try:
+ ts = email.utils.parsedate_tz(ims)
+ return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone
+ except (TypeError, ValueError, IndexError, OverflowError):
+ return None
+
+def parse_auth(header):
+ """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
+ try:
+ method, data = header.split(None, 1)
+ if method.lower() == 'basic':
+ user, pwd = touni(base64.b64decode(tob(data))).split(':',1)
+ return user, pwd
+ except (KeyError, ValueError):
+ return None
+
+def parse_range_header(header, maxlen=0):
+ ''' Yield (start, end) ranges parsed from a HTTP Range header. Skip
+ unsatisfiable ranges. The end index is non-inclusive.'''
+ if not header or header[:6] != 'bytes=': return
+ ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
+ for start, end in ranges:
+ try:
+ if not start: # bytes=-100 -> last 100 bytes
+ start, end = max(0, maxlen-int(end)), maxlen
+ elif not end: # bytes=100- -> all but the first 99 bytes
+ start, end = int(start), maxlen
+ else: # bytes=100-200 -> bytes 100-200 (inclusive)
+ start, end = int(start), min(int(end)+1, maxlen)
+ if 0 <= start < end <= maxlen:
+ yield start, end
+ except ValueError:
+ pass
+
+def _parse_qsl(qs):
+ r = []
+ for pair in qs.split('&'):
+ if not pair: continue
+ nv = pair.split('=', 1)
+ if len(nv) != 2: nv.append('')
+ key = urlunquote(nv[0].replace('+', ' '))
+ value = urlunquote(nv[1].replace('+', ' '))
+ r.append((key, value))
+ return r
+
+def _lscmp(a, b):
+ ''' Compares two strings in a cryptographically safe way:
+ Runtime is not affected by length of common prefix. '''
+ return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b)
+
+
+def cookie_encode(data, key):
+ ''' Encode and sign a pickle-able object. Return a (byte) string '''
+ msg = base64.b64encode(pickle.dumps(data, -1))
+ sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
+ return tob('!') + sig + tob('?') + msg
+
+
+def cookie_decode(data, key):
+ ''' Verify and decode an encoded string. Return an object or None.'''
+ data = tob(data)
+ if cookie_is_encoded(data):
+ sig, msg = data.split(tob('?'), 1)
+ if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
+ return pickle.loads(base64.b64decode(msg))
+ return None
+
+
+def cookie_is_encoded(data):
+ ''' Return True if the argument looks like a encoded cookie.'''
+ return bool(data.startswith(tob('!')) and tob('?') in data)
+
+
+def html_escape(string):
+ ''' Escape HTML special characters ``&<>`` and quotes ``'"``. '''
+ return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')\
+ .replace('"','&quot;').replace("'",'&#039;')
+
+
+def html_quote(string):
+ ''' Escape and quote a string to be used as an HTTP attribute.'''
+ return '"%s"' % html_escape(string).replace('\n','&#10;')\
+ .replace('\r','&#13;').replace('\t','&#9;')
+
+
+def yieldroutes(func):
+ """ Return a generator for routes that match the signature (name, args)
+ of the func parameter. This may yield more than one route if the function
+ takes optional keyword arguments. The output is best described by example::
+
+ a() -> '/a'
+ b(x, y) -> '/b/<x>/<y>'
+ c(x, y=5) -> '/c/<x>' and '/c/<x>/<y>'
+ d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
+ """
+ path = '/' + func.__name__.replace('__','/').lstrip('/')
+ spec = getargspec(func)
+ argc = len(spec[0]) - len(spec[3] or [])
+ path += ('/<%s>' * argc) % tuple(spec[0][:argc])
+ yield path
+ for arg in spec[0][argc:]:
+ path += '/<%s>' % arg
+ yield path
+
+
+def path_shift(script_name, path_info, shift=1):
+ ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
+
+ :return: The modified paths.
+ :param script_name: The SCRIPT_NAME path.
+ :param script_name: The PATH_INFO path.
+ :param shift: The number of path fragments to shift. May be negative to
+ change the shift direction. (default: 1)
+ '''
+ if shift == 0: return script_name, path_info
+ pathlist = path_info.strip('/').split('/')
+ scriptlist = script_name.strip('/').split('/')
+ if pathlist and pathlist[0] == '': pathlist = []
+ if scriptlist and scriptlist[0] == '': scriptlist = []
+ if shift > 0 and shift <= len(pathlist):
+ moved = pathlist[:shift]
+ scriptlist = scriptlist + moved
+ pathlist = pathlist[shift:]
+ elif shift < 0 and shift >= -len(scriptlist):
+ moved = scriptlist[shift:]
+ pathlist = moved + pathlist
+ scriptlist = scriptlist[:shift]
+ else:
+ empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
+ raise AssertionError("Cannot shift. Nothing left from %s" % empty)
+ new_script_name = '/' + '/'.join(scriptlist)
+ new_path_info = '/' + '/'.join(pathlist)
+ if path_info.endswith('/') and pathlist: new_path_info += '/'
+ return new_script_name, new_path_info
+
+
+def auth_basic(check, realm="private", text="Access denied"):
+ ''' Callback decorator to require HTTP auth (basic).
+ TODO: Add route(check_auth=...) parameter. '''
+ def decorator(func):
+ def wrapper(*a, **ka):
+ user, password = request.auth or (None, None)
+ if user is None or not check(user, password):
+ err = HTTPError(401, text)
+ err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
+ return err
+ return func(*a, **ka)
+ return wrapper
+ return decorator
+
+
+# Shortcuts for common Bottle methods.
+# They all refer to the current default application.
+
+def make_default_app_wrapper(name):
+ ''' Return a callable that relays calls to the current default app. '''
+ @functools.wraps(getattr(Bottle, name))
+ def wrapper(*a, **ka):
+ return getattr(app(), name)(*a, **ka)
+ return wrapper
+
+route = make_default_app_wrapper('route')
+get = make_default_app_wrapper('get')
+post = make_default_app_wrapper('post')
+put = make_default_app_wrapper('put')
+delete = make_default_app_wrapper('delete')
+error = make_default_app_wrapper('error')
+mount = make_default_app_wrapper('mount')
+hook = make_default_app_wrapper('hook')
+install = make_default_app_wrapper('install')
+uninstall = make_default_app_wrapper('uninstall')
+url = make_default_app_wrapper('get_url')
+
+
+
+
+
+
+
+###############################################################################
+# Server Adapter ###############################################################
+###############################################################################
+
+
+class ServerAdapter(object):
+ quiet = False
+ def __init__(self, host='127.0.0.1', port=8080, **options):
+ self.options = options
+ self.host = host
+ self.port = int(port)
+
+ def run(self, handler): # pragma: no cover
+ pass
+
+ def __repr__(self):
+ args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()])
+ return "%s(%s)" % (self.__class__.__name__, args)
+
+
+class CGIServer(ServerAdapter):
+ quiet = True
+ def run(self, handler): # pragma: no cover
+ from wsgiref.handlers import CGIHandler
+ def fixed_environ(environ, start_response):
+ environ.setdefault('PATH_INFO', '')
+ return handler(environ, start_response)
+ CGIHandler().run(fixed_environ)
+
+
+class FlupFCGIServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ import flup.server.fcgi
+ self.options.setdefault('bindAddress', (self.host, self.port))
+ flup.server.fcgi.WSGIServer(handler, **self.options).run()
+
+
+class WSGIRefServer(ServerAdapter):
+ def run(self, app): # pragma: no cover
+ from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
+ from wsgiref.simple_server import make_server
+ import socket
+
+ class FixedHandler(WSGIRequestHandler):
+ def address_string(self): # Prevent reverse DNS lookups please.
+ return self.client_address[0]
+ def log_request(*args, **kw):
+ if not self.quiet:
+ return WSGIRequestHandler.log_request(*args, **kw)
+
+ handler_cls = self.options.get('handler_class', FixedHandler)
+ server_cls = self.options.get('server_class', WSGIServer)
+
+ if ':' in self.host: # Fix wsgiref for IPv6 addresses.
+ if getattr(server_cls, 'address_family') == socket.AF_INET:
+ class server_cls(server_cls):
+ address_family = socket.AF_INET6
+
+ srv = make_server(self.host, self.port, app, server_cls, handler_cls)
+ srv.serve_forever()
+
+
+class CherryPyServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ from cherrypy import wsgiserver
+ self.options['bind_addr'] = (self.host, self.port)
+ self.options['wsgi_app'] = handler
+
+ certfile = self.options.get('certfile')
+ if certfile:
+ del self.options['certfile']
+ keyfile = self.options.get('keyfile')
+ if keyfile:
+ del self.options['keyfile']
+
+ server = wsgiserver.CherryPyWSGIServer(**self.options)
+ if certfile:
+ server.ssl_certificate = certfile
+ if keyfile:
+ server.ssl_private_key = keyfile
+
+ try:
+ server.start()
+ finally:
+ server.stop()
+
+
+class WaitressServer(ServerAdapter):
+ def run(self, handler):
+ from waitress import serve
+ serve(handler, host=self.host, port=self.port)
+
+
+class PasteServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ from paste import httpserver
+ from paste.translogger import TransLogger
+ handler = TransLogger(handler, setup_console_handler=(not self.quiet))
+ httpserver.serve(handler, host=self.host, port=str(self.port),
+ **self.options)
+
+
+class MeinheldServer(ServerAdapter):
+ def run(self, handler):
+ from meinheld import server
+ server.listen((self.host, self.port))
+ server.run(handler)
+
+
+class FapwsServer(ServerAdapter):
+ """ Extremely fast webserver using libev. See http://www.fapws.org/ """
+ def run(self, handler): # pragma: no cover
+ import fapws._evwsgi as evwsgi
+ from fapws import base, config
+ port = self.port
+ if float(config.SERVER_IDENT[-2:]) > 0.4:
+ # fapws3 silently changed its API in 0.5
+ port = str(port)
+ evwsgi.start(self.host, port)
+ # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
+ if 'BOTTLE_CHILD' in os.environ and not self.quiet:
+ _stderr("WARNING: Auto-reloading does not work with Fapws3.\n")
+ _stderr(" (Fapws3 breaks python thread support)\n")
+ evwsgi.set_base_module(base)
+ def app(environ, start_response):
+ environ['wsgi.multiprocess'] = False
+ return handler(environ, start_response)
+ evwsgi.wsgi_cb(('', app))
+ evwsgi.run()
+
+
+class TornadoServer(ServerAdapter):
+ """ The super hyped asynchronous server by facebook. Untested. """
+ def run(self, handler): # pragma: no cover
+ import tornado.wsgi, tornado.httpserver, tornado.ioloop
+ container = tornado.wsgi.WSGIContainer(handler)
+ server = tornado.httpserver.HTTPServer(container)
+ server.listen(port=self.port,address=self.host)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+class AppEngineServer(ServerAdapter):
+ """ Adapter for Google App Engine. """
+ quiet = True
+ def run(self, handler):
+ from google.appengine.ext.webapp import util
+ # A main() function in the handler script enables 'App Caching'.
+ # Lets makes sure it is there. This _really_ improves performance.
+ module = sys.modules.get('__main__')
+ if module and not hasattr(module, 'main'):
+ module.main = lambda: util.run_wsgi_app(handler)
+ util.run_wsgi_app(handler)
+
+
+class TwistedServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from twisted.web import server, wsgi
+ from twisted.python.threadpool import ThreadPool
+ from twisted.internet import reactor
+ thread_pool = ThreadPool()
+ thread_pool.start()
+ reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
+ factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
+ reactor.listenTCP(self.port, factory, interface=self.host)
+ reactor.run()
+
+
+class DieselServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from diesel.protocols.wsgi import WSGIApplication
+ app = WSGIApplication(handler, port=self.port)
+ app.run()
+
+
+class GeventServer(ServerAdapter):
+ """ Untested. Options:
+
+ * `fast` (default: False) uses libevent's http server, but has some
+ issues: No streaming, no pipelining, no SSL.
+ * See gevent.wsgi.WSGIServer() documentation for more options.
+ """
+ def run(self, handler):
+ from gevent import pywsgi, local
+ if not isinstance(threading.local(), local.local):
+ msg = "Bottle requires gevent.monkey.patch_all() (before import)"
+ raise RuntimeError(msg)
+ if self.options.pop('fast', None):
+ depr('The "fast" option has been deprecated and removed by Gevent.')
+ if self.quiet:
+ self.options['log'] = None
+ address = (self.host, self.port)
+ server = pywsgi.WSGIServer(address, handler, **self.options)
+ if 'BOTTLE_CHILD' in os.environ:
+ import signal
+ signal.signal(signal.SIGINT, lambda s, f: server.stop())
+ server.serve_forever()
+
+
+class GeventSocketIOServer(ServerAdapter):
+ def run(self,handler):
+ from socketio import server
+ address = (self.host, self.port)
+ server.SocketIOServer(address, handler, **self.options).serve_forever()
+
+
+class GunicornServer(ServerAdapter):
+ """ Untested. See http://gunicorn.org/configure.html for options. """
+ def run(self, handler):
+ from gunicorn.app.base import Application
+
+ config = {'bind': "%s:%d" % (self.host, int(self.port))}
+ config.update(self.options)
+
+ class GunicornApplication(Application):
+ def init(self, parser, opts, args):
+ return config
+
+ def load(self):
+ return handler
+
+ GunicornApplication().run()
+
+
+class EventletServer(ServerAdapter):
+ """ Untested """
+ def run(self, handler):
+ from eventlet import wsgi, listen
+ try:
+ wsgi.server(listen((self.host, self.port)), handler,
+ log_output=(not self.quiet))
+ except TypeError:
+ # Fallback, if we have old version of eventlet
+ wsgi.server(listen((self.host, self.port)), handler)
+
+
+class RocketServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from rocket import Rocket
+ server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler })
+ server.start()
+
+
+class BjoernServer(ServerAdapter):
+ """ Fast server written in C: https://github.com/jonashaag/bjoern """
+ def run(self, handler):
+ from bjoern import run
+ run(handler, self.host, self.port)
+
+
+class AutoServer(ServerAdapter):
+ """ Untested. """
+ adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer]
+ def run(self, handler):
+ for sa in self.adapters:
+ try:
+ return sa(self.host, self.port, **self.options).run(handler)
+ except ImportError:
+ pass
+
+server_names = {
+ 'cgi': CGIServer,
+ 'flup': FlupFCGIServer,
+ 'wsgiref': WSGIRefServer,
+ 'waitress': WaitressServer,
+ 'cherrypy': CherryPyServer,
+ 'paste': PasteServer,
+ 'fapws3': FapwsServer,
+ 'tornado': TornadoServer,
+ 'gae': AppEngineServer,
+ 'twisted': TwistedServer,
+ 'diesel': DieselServer,
+ 'meinheld': MeinheldServer,
+ 'gunicorn': GunicornServer,
+ 'eventlet': EventletServer,
+ 'gevent': GeventServer,
+ 'geventSocketIO':GeventSocketIOServer,
+ 'rocket': RocketServer,
+ 'bjoern' : BjoernServer,
+ 'auto': AutoServer,
+}
+
+
+
+
+
+
+###############################################################################
+# Application Control ##########################################################
+###############################################################################
+
+
+def load(target, **namespace):
+ """ Import a module or fetch an object from a module.
+
+ * ``package.module`` returns `module` as a module object.
+ * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
+ * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
+
+ The last form accepts not only function calls, but any type of
+ expression. Keyword arguments passed to this function are available as
+ local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
+ """
+ module, target = target.split(":", 1) if ':' in target else (target, None)
+ if module not in sys.modules: __import__(module)
+ if not target: return sys.modules[module]
+ if target.isalnum(): return getattr(sys.modules[module], target)
+ package_name = module.split('.')[0]
+ namespace[package_name] = sys.modules[package_name]
+ return eval('%s.%s' % (module, target), namespace)
+
+
+def load_app(target):
+ """ Load a bottle application from a module and make sure that the import
+ does not affect the current default application, but returns a separate
+ application object. See :func:`load` for the target parameter. """
+ global NORUN; NORUN, nr_old = True, NORUN
+ try:
+ tmp = default_app.push() # Create a new "default application"
+ rv = load(target) # Import the target module
+ return rv if callable(rv) else tmp
+ finally:
+ default_app.remove(tmp) # Remove the temporary added default application
+ NORUN = nr_old
+
+_debug = debug
+def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
+ interval=1, reloader=False, quiet=False, plugins=None,
+ debug=None, **kargs):
+ """ Start a server instance. This method blocks until the server terminates.
+
+ :param app: WSGI application or target string supported by
+ :func:`load_app`. (default: :func:`default_app`)
+ :param server: Server adapter to use. See :data:`server_names` keys
+ for valid names or pass a :class:`ServerAdapter` subclass.
+ (default: `wsgiref`)
+ :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
+ all interfaces including the external one. (default: 127.0.0.1)
+ :param port: Server port to bind to. Values below 1024 require root
+ privileges. (default: 8080)
+ :param reloader: Start auto-reloading server? (default: False)
+ :param interval: Auto-reloader interval in seconds (default: 1)
+ :param quiet: Suppress output to stdout and stderr? (default: False)
+ :param options: Options passed to the server adapter.
+ """
+ if NORUN: return
+ if reloader and not os.environ.get('BOTTLE_CHILD'):
+ try:
+ lockfile = None
+ fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
+ os.close(fd) # We only need this file to exist. We never write to it
+ while os.path.exists(lockfile):
+ args = [sys.executable] + sys.argv
+ environ = os.environ.copy()
+ environ['BOTTLE_CHILD'] = 'true'
+ environ['BOTTLE_LOCKFILE'] = lockfile
+ p = subprocess.Popen(args, env=environ)
+ while p.poll() is None: # Busy wait...
+ os.utime(lockfile, None) # I am alive!
+ time.sleep(interval)
+ if p.poll() != 3:
+ if os.path.exists(lockfile): os.unlink(lockfile)
+ sys.exit(p.poll())
+ except KeyboardInterrupt:
+ pass
+ finally:
+ if os.path.exists(lockfile):
+ os.unlink(lockfile)
+ return
+
+ try:
+ if debug is not None: _debug(debug)
+ app = app or default_app()
+ if isinstance(app, basestring):
+ app = load_app(app)
+ if not callable(app):
+ raise ValueError("Application is not callable: %r" % app)
+
+ for plugin in plugins or []:
+ app.install(plugin)
+
+ if server in server_names:
+ server = server_names.get(server)
+ if isinstance(server, basestring):
+ server = load(server)
+ if isinstance(server, type):
+ server = server(host=host, port=port, **kargs)
+ if not isinstance(server, ServerAdapter):
+ raise ValueError("Unknown or unsupported server: %r" % server)
+
+ server.quiet = server.quiet or quiet
+ if not server.quiet:
+ _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server)))
+ _stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
+ _stderr("Hit Ctrl-C to quit.\n\n")
+
+ if reloader:
+ lockfile = os.environ.get('BOTTLE_LOCKFILE')
+ bgcheck = FileCheckerThread(lockfile, interval)
+ with bgcheck:
+ server.run(app)
+ if bgcheck.status == 'reload':
+ sys.exit(3)
+ else:
+ server.run(app)
+ except KeyboardInterrupt:
+ pass
+ except (SystemExit, MemoryError):
+ raise
+ except:
+ if not reloader: raise
+ if not getattr(server, 'quiet', quiet):
+ print_exc()
+ time.sleep(interval)
+ sys.exit(3)
+
+
+
+class FileCheckerThread(threading.Thread):
+ ''' Interrupt main-thread as soon as a changed module file is detected,
+ the lockfile gets deleted or gets to old. '''
+
+ def __init__(self, lockfile, interval):
+ threading.Thread.__init__(self)
+ self.lockfile, self.interval = lockfile, interval
+ #: Is one of 'reload', 'error' or 'exit'
+ self.status = None
+
+ def run(self):
+ exists = os.path.exists
+ mtime = lambda path: os.stat(path).st_mtime
+ files = dict()
+
+ for module in list(sys.modules.values()):
+ path = getattr(module, '__file__', '') or ''
+ if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
+ if path and exists(path): files[path] = mtime(path)
+
+ while not self.status:
+ if not exists(self.lockfile)\
+ or mtime(self.lockfile) < time.time() - self.interval - 5:
+ self.status = 'error'
+ thread.interrupt_main()
+ for path, lmtime in list(files.items()):
+ if not exists(path) or mtime(path) > lmtime:
+ self.status = 'reload'
+ thread.interrupt_main()
+ break
+ time.sleep(self.interval)
+
+ def __enter__(self):
+ self.start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if not self.status: self.status = 'exit' # silent exit
+ self.join()
+ return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
+
+
+
+
+
+###############################################################################
+# Template Adapters ############################################################
+###############################################################################
+
+
+class TemplateError(HTTPError):
+ def __init__(self, message):
+ HTTPError.__init__(self, 500, message)
+
+
+class BaseTemplate(object):
+ """ Base class and minimal API for template adapters """
+ extensions = ['tpl','html','thtml','stpl']
+ settings = {} #used in prepare()
+ defaults = {} #used in render()
+
+ def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings):
+ """ Create a new template.
+ If the source parameter (str or buffer) is missing, the name argument
+ is used to guess a template filename. Subclasses can assume that
+ self.source and/or self.filename are set. Both are strings.
+ The lookup, encoding and settings parameters are stored as instance
+ variables.
+ The lookup parameter stores a list containing directory paths.
+ The encoding parameter should be used to decode byte strings or files.
+ The settings parameter contains a dict for engine-specific settings.
+ """
+ self.name = name
+ self.source = source.read() if hasattr(source, 'read') else source
+ self.filename = source.filename if hasattr(source, 'filename') else None
+ self.lookup = [os.path.abspath(x) for x in lookup]
+ self.encoding = encoding
+ self.settings = self.settings.copy() # Copy from class variable
+ self.settings.update(settings) # Apply
+ if not self.source and self.name:
+ self.filename = self.search(self.name, self.lookup)
+ if not self.filename:
+ raise TemplateError('Template %s not found.' % repr(name))
+ if not self.source and not self.filename:
+ raise TemplateError('No template specified.')
+ self.prepare(**self.settings)
+
+ @classmethod
+ def search(cls, name, lookup=[]):
+ """ Search name in all directories specified in lookup.
+ First without, then with common extensions. Return first hit. """
+ if not lookup:
+ depr('The template lookup path list should not be empty.') #0.12
+ lookup = ['.']
+
+ if os.path.isabs(name) and os.path.isfile(name):
+ depr('Absolute template path names are deprecated.') #0.12
+ return os.path.abspath(name)
+
+ for spath in lookup:
+ spath = os.path.abspath(spath) + os.sep
+ fname = os.path.abspath(os.path.join(spath, name))
+ if not fname.startswith(spath): continue
+ if os.path.isfile(fname): return fname
+ for ext in cls.extensions:
+ if os.path.isfile('%s.%s' % (fname, ext)):
+ return '%s.%s' % (fname, ext)
+
+ @classmethod
+ def global_config(cls, key, *args):
+ ''' This reads or sets the global settings stored in class.settings. '''
+ if args:
+ cls.settings = cls.settings.copy() # Make settings local to class
+ cls.settings[key] = args[0]
+ else:
+ return cls.settings[key]
+
+ def prepare(self, **options):
+ """ Run preparations (parsing, caching, ...).
+ It should be possible to call this again to refresh a template or to
+ update settings.
+ """
+ raise NotImplementedError
+
+ def render(self, *args, **kwargs):
+ """ Render the template with the specified local variables and return
+ a single byte or unicode string. If it is a byte string, the encoding
+ must match self.encoding. This method must be thread-safe!
+ Local variables may be provided in dictionaries (args)
+ or directly, as keywords (kwargs).
+ """
+ raise NotImplementedError
+
+
+class MakoTemplate(BaseTemplate):
+ def prepare(self, **options):
+ from mako.template import Template
+ from mako.lookup import TemplateLookup
+ options.update({'input_encoding':self.encoding})
+ options.setdefault('format_exceptions', bool(DEBUG))
+ lookup = TemplateLookup(directories=self.lookup, **options)
+ if self.source:
+ self.tpl = Template(self.source, lookup=lookup, **options)
+ else:
+ self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ _defaults = self.defaults.copy()
+ _defaults.update(kwargs)
+ return self.tpl.render(**_defaults)
+
+
+class CheetahTemplate(BaseTemplate):
+ def prepare(self, **options):
+ from Cheetah.Template import Template
+ self.context = threading.local()
+ self.context.vars = {}
+ options['searchList'] = [self.context.vars]
+ if self.source:
+ self.tpl = Template(source=self.source, **options)
+ else:
+ self.tpl = Template(file=self.filename, **options)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ self.context.vars.update(self.defaults)
+ self.context.vars.update(kwargs)
+ out = str(self.tpl)
+ self.context.vars.clear()
+ return out
+
+
+class Jinja2Template(BaseTemplate):
+ def prepare(self, filters=None, tests=None, globals={}, **kwargs):
+ from jinja2 import Environment, FunctionLoader
+ if 'prefix' in kwargs: # TODO: to be removed after a while
+ raise RuntimeError('The keyword argument `prefix` has been removed. '
+ 'Use the full jinja2 environment name line_statement_prefix instead.')
+ self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
+ if filters: self.env.filters.update(filters)
+ if tests: self.env.tests.update(tests)
+ if globals: self.env.globals.update(globals)
+ if self.source:
+ self.tpl = self.env.from_string(self.source)
+ else:
+ self.tpl = self.env.get_template(self.filename)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ _defaults = self.defaults.copy()
+ _defaults.update(kwargs)
+ return self.tpl.render(**_defaults)
+
+ def loader(self, name):
+ fname = self.search(name, self.lookup)
+ if not fname: return
+ with open(fname, "rb") as f:
+ return f.read().decode(self.encoding)
+
+
+class SimpleTemplate(BaseTemplate):
+
+ def prepare(self, escape_func=html_escape, noescape=False, syntax=None, **ka):
+ self.cache = {}
+ enc = self.encoding
+ self._str = lambda x: touni(x, enc)
+ self._escape = lambda x: escape_func(touni(x, enc))
+ self.syntax = syntax
+ if noescape:
+ self._str, self._escape = self._escape, self._str
+
+ @cached_property
+ def co(self):
+ return compile(self.code, self.filename or '<string>', 'exec')
+
+ @cached_property
+ def code(self):
+ source = self.source
+ if not source:
+ with open(self.filename, 'rb') as f:
+ source = f.read()
+ try:
+ source, encoding = touni(source), 'utf8'
+ except UnicodeError:
+ depr('Template encodings other than utf8 are no longer supported.') #0.11
+ source, encoding = touni(source, 'latin1'), 'latin1'
+ parser = StplParser(source, encoding=encoding, syntax=self.syntax)
+ code = parser.translate()
+ self.encoding = parser.encoding
+ return code
+
+ def _rebase(self, _env, _name=None, **kwargs):
+ if _name is None:
+ depr('Rebase function called without arguments.'
+ ' You were probably looking for {{base}}?', True) #0.12
+ _env['_rebase'] = (_name, kwargs)
+
+ def _include(self, _env, _name=None, **kwargs):
+ if _name is None:
+ depr('Rebase function called without arguments.'
+ ' You were probably looking for {{base}}?', True) #0.12
+ env = _env.copy()
+ env.update(kwargs)
+ if _name not in self.cache:
+ self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
+ return self.cache[_name].execute(env['_stdout'], env)
+
+ def execute(self, _stdout, kwargs):
+ env = self.defaults.copy()
+ env.update(kwargs)
+ env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
+ 'include': functools.partial(self._include, env),
+ 'rebase': functools.partial(self._rebase, env), '_rebase': None,
+ '_str': self._str, '_escape': self._escape, 'get': env.get,
+ 'setdefault': env.setdefault, 'defined': env.__contains__ })
+ eval(self.co, env)
+ if env.get('_rebase'):
+ subtpl, rargs = env.pop('_rebase')
+ rargs['base'] = ''.join(_stdout) #copy stdout
+ del _stdout[:] # clear stdout
+ return self._include(env, subtpl, **rargs)
+ return env
+
+ def render(self, *args, **kwargs):
+ """ Render the template using keyword arguments as local variables. """
+ env = {}; stdout = []
+ for dictarg in args: env.update(dictarg)
+ env.update(kwargs)
+ self.execute(stdout, env)
+ return ''.join(stdout)
+
+
+class StplSyntaxError(TemplateError): pass
+
+
+class StplParser(object):
+ ''' Parser for stpl templates. '''
+ _re_cache = {} #: Cache for compiled re patterns
+ # This huge pile of voodoo magic splits python code into 8 different tokens.
+ # 1: All kinds of python strings (trust me, it works)
+ _re_tok = '([urbURB]?(?:\'\'(?!\')|""(?!")|\'{6}|"{6}' \
+ '|\'(?:[^\\\\\']|\\\\.)+?\'|"(?:[^\\\\"]|\\\\.)+?"' \
+ '|\'{3}(?:[^\\\\]|\\\\.|\\n)+?\'{3}' \
+ '|"{3}(?:[^\\\\]|\\\\.|\\n)+?"{3}))'
+ _re_inl = _re_tok.replace('|\\n','') # We re-use this string pattern later
+ # 2: Comments (until end of line, but not the newline itself)
+ _re_tok += '|(#.*)'
+ # 3,4: Open and close grouping tokens
+ _re_tok += '|([\\[\\{\\(])'
+ _re_tok += '|([\\]\\}\\)])'
+ # 5,6: Keywords that start or continue a python block (only start of line)
+ _re_tok += '|^([ \\t]*(?:if|for|while|with|try|def|class)\\b)' \
+ '|^([ \\t]*(?:elif|else|except|finally)\\b)'
+ # 7: Our special 'end' keyword (but only if it stands alone)
+ _re_tok += '|((?:^|;)[ \\t]*end[ \\t]*(?=(?:%(block_close)s[ \\t]*)?\\r?$|;|#))'
+ # 8: A customizable end-of-code-block template token (only end of line)
+ _re_tok += '|(%(block_close)s[ \\t]*(?=\\r?$))'
+ # 9: And finally, a single newline. The 10th token is 'everything else'
+ _re_tok += '|(\\r?\\n)'
+
+ # Match the start tokens of code areas in a template
+ _re_split = '(?m)^[ \t]*(\\\\?)((%(line_start)s)|(%(block_start)s))(%%?)'
+ # Match inline statements (may contain python strings)
+ _re_inl = '(?m)%%(inline_start)s((?:%s|[^\'"\n]*?)+)%%(inline_end)s' % _re_inl
+ _re_tok = '(?m)' + _re_tok
+
+ default_syntax = '<% %> % {{ }}'
+
+ def __init__(self, source, syntax=None, encoding='utf8'):
+ self.source, self.encoding = touni(source, encoding), encoding
+ self.set_syntax(syntax or self.default_syntax)
+ self.code_buffer, self.text_buffer = [], []
+ self.lineno, self.offset = 1, 0
+ self.indent, self.indent_mod = 0, 0
+ self.paren_depth = 0
+
+ def get_syntax(self):
+ ''' Tokens as a space separated string (default: <% %> % {{ }}) '''
+ return self._syntax
+
+ def set_syntax(self, syntax):
+ self._syntax = syntax
+ self._tokens = syntax.split()
+ if not syntax in self._re_cache:
+ names = 'block_start block_close line_start inline_start inline_end'
+ etokens = map(re.escape, self._tokens)
+ pattern_vars = dict(zip(names.split(), etokens))
+ patterns = (self._re_split, self._re_tok, self._re_inl)
+ patterns = [re.compile(p%pattern_vars) for p in patterns]
+ self._re_cache[syntax] = patterns
+ self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
+
+ syntax = property(get_syntax, set_syntax)
+
+ def translate(self):
+ if self.offset: raise RuntimeError('Parser is a one time instance.')
+ while True:
+ m = self.re_split.search(self.source[self.offset:])
+ if m:
+ text = self.source[self.offset:self.offset+m.start()]
+ self.text_buffer.append(text)
+ self.offset += m.end()
+ if m.group(1): # New escape syntax
+ line, sep, _ = self.source[self.offset:].partition('\n')
+ self.text_buffer.append(m.group(2)+m.group(5)+line+sep)
+ self.offset += len(line+sep)+1
+ continue
+ elif m.group(5): # Old escape syntax
+ depr('Escape code lines with a backslash.') #0.12
+ line, sep, _ = self.source[self.offset:].partition('\n')
+ self.text_buffer.append(m.group(2)+line+sep)
+ self.offset += len(line+sep)+1
+ continue
+ self.flush_text()
+ self.read_code(multiline=bool(m.group(4)))
+ else: break
+ self.text_buffer.append(self.source[self.offset:])
+ self.flush_text()
+ return ''.join(self.code_buffer)
+
+ def read_code(self, multiline):
+ code_line, comment = '', ''
+ while True:
+ m = self.re_tok.search(self.source[self.offset:])
+ if not m:
+ code_line += self.source[self.offset:]
+ self.offset = len(self.source)
+ self.write_code(code_line.strip(), comment)
+ return
+ code_line += self.source[self.offset:self.offset+m.start()]
+ self.offset += m.end()
+ _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups()
+ if (code_line or self.paren_depth > 0) and (_blk1 or _blk2): # a if b else c
+ code_line += _blk1 or _blk2
+ continue
+ if _str: # Python string
+ code_line += _str
+ elif _com: # Python comment (up to EOL)
+ comment = _com
+ if multiline and _com.strip().endswith(self._tokens[1]):
+ multiline = False # Allow end-of-block in comments
+ elif _po: # open parenthesis
+ self.paren_depth += 1
+ code_line += _po
+ elif _pc: # close parenthesis
+ if self.paren_depth > 0:
+ # we could check for matching parentheses here, but it's
+ # easier to leave that to python - just check counts
+ self.paren_depth -= 1
+ code_line += _pc
+ elif _blk1: # Start-block keyword (if/for/while/def/try/...)
+ code_line, self.indent_mod = _blk1, -1
+ self.indent += 1
+ elif _blk2: # Continue-block keyword (else/elif/except/...)
+ code_line, self.indent_mod = _blk2, -1
+ elif _end: # The non-standard 'end'-keyword (ends a block)
+ self.indent -= 1
+ elif _cend: # The end-code-block template token (usually '%>')
+ if multiline: multiline = False
+ else: code_line += _cend
+ else: # \n
+ self.write_code(code_line.strip(), comment)
+ self.lineno += 1
+ code_line, comment, self.indent_mod = '', '', 0
+ if not multiline:
+ break
+
+ def flush_text(self):
+ text = ''.join(self.text_buffer)
+ del self.text_buffer[:]
+ if not text: return
+ parts, pos, nl = [], 0, '\\\n'+' '*self.indent
+ for m in self.re_inl.finditer(text):
+ prefix, pos = text[pos:m.start()], m.end()
+ if prefix:
+ parts.append(nl.join(map(repr, prefix.splitlines(True))))
+ if prefix.endswith('\n'): parts[-1] += nl
+ parts.append(self.process_inline(m.group(1).strip()))
+ if pos < len(text):
+ prefix = text[pos:]
+ lines = prefix.splitlines(True)
+ if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
+ elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
+ parts.append(nl.join(map(repr, lines)))
+ code = '_printlist((%s,))' % ', '.join(parts)
+ self.lineno += code.count('\n')+1
+ self.write_code(code)
+
+ def process_inline(self, chunk):
+ if chunk[0] == '!': return '_str(%s)' % chunk[1:]
+ return '_escape(%s)' % chunk
+
+ def write_code(self, line, comment=''):
+ line, comment = self.fix_backward_compatibility(line, comment)
+ code = ' ' * (self.indent+self.indent_mod)
+ code += line.lstrip() + comment + '\n'
+ self.code_buffer.append(code)
+
+ def fix_backward_compatibility(self, line, comment):
+ parts = line.strip().split(None, 2)
+ if parts and parts[0] in ('include', 'rebase'):
+ depr('The include and rebase keywords are functions now.') #0.12
+ if len(parts) == 1: return "_printlist([base])", comment
+ elif len(parts) == 2: return "_=%s(%r)" % tuple(parts), comment
+ else: return "_=%s(%r, %s)" % tuple(parts), comment
+ if self.lineno <= 2 and not line.strip() and 'coding' in comment:
+ m = re.match(r"#.*coding[:=]\s*([-\w.]+)", comment)
+ if m:
+ depr('PEP263 encoding strings in templates are deprecated.') #0.12
+ enc = m.group(1)
+ self.source = self.source.encode(self.encoding).decode(enc)
+ self.encoding = enc
+ return line, comment.replace('coding','coding*')
+ return line, comment
+
+
+def template(*args, **kwargs):
+ '''
+ Get a rendered template as a string iterator.
+ You can use a name, a filename or a template string as first parameter.
+ Template rendering arguments can be passed as dictionaries
+ or directly (as keyword arguments).
+ '''
+ tpl = args[0] if args else None
+ adapter = kwargs.pop('template_adapter', SimpleTemplate)
+ lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
+ tplid = (id(lookup), tpl)
+ if tplid not in TEMPLATES or DEBUG:
+ settings = kwargs.pop('template_settings', {})
+ if isinstance(tpl, adapter):
+ TEMPLATES[tplid] = tpl
+ if settings: TEMPLATES[tplid].prepare(**settings)
+ elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
+ TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
+ else:
+ TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
+ if not TEMPLATES[tplid]:
+ abort(500, 'Template (%s) not found' % tpl)
+ for dictarg in args[1:]: kwargs.update(dictarg)
+ return TEMPLATES[tplid].render(kwargs)
+
+mako_template = functools.partial(template, template_adapter=MakoTemplate)
+cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
+jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
+
+
+def view(tpl_name, **defaults):
+ ''' Decorator: renders a template for a handler.
+ The handler can control its behavior like that:
+
+ - return a dict of template vars to fill out the template
+ - return something other than a dict and the view decorator will not
+ process the template, but return the handler result as is.
+ This includes returning a HTTPResponse(dict) to get,
+ for instance, JSON with autojson or other castfilters.
+ '''
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ result = func(*args, **kwargs)
+ if isinstance(result, (dict, DictMixin)):
+ tplvars = defaults.copy()
+ tplvars.update(result)
+ return template(tpl_name, **tplvars)
+ elif result is None:
+ return template(tpl_name, defaults)
+ return result
+ return wrapper
+ return decorator
+
+mako_view = functools.partial(view, template_adapter=MakoTemplate)
+cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
+jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
+
+
+
+
+
+
+###############################################################################
+# Constants and Globals ########################################################
+###############################################################################
+
+
+TEMPLATE_PATH = ['./', './views/']
+TEMPLATES = {}
+DEBUG = False
+NORUN = False # If set, run() does nothing. Used by load_app()
+
+#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
+HTTP_CODES = httplib.responses
+HTTP_CODES[418] = "I'm a teapot" # RFC 2324
+HTTP_CODES[422] = "Unprocessable Entity" # RFC 4918
+HTTP_CODES[428] = "Precondition Required"
+HTTP_CODES[429] = "Too Many Requests"
+HTTP_CODES[431] = "Request Header Fields Too Large"
+HTTP_CODES[511] = "Network Authentication Required"
+_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items())
+
+#: The default template used for error pages. Override with @error()
+ERROR_PAGE_TEMPLATE = """
+%%try:
+ %%from %s import DEBUG, HTTP_CODES, request, touni
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+ <html>
+ <head>
+ <title>Error: {{e.status}}</title>
+ <style type="text/css">
+ html {background-color: #eee; font-family: sans;}
+ body {background-color: #fff; border: 1px solid #ddd;
+ padding: 15px; margin: 15px;}
+ pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
+ </style>
+ </head>
+ <body>
+ <h1>Error: {{e.status}}</h1>
+ <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
+ caused an error:</p>
+ <pre>{{e.body}}</pre>
+ %%if DEBUG and e.exception:
+ <h2>Exception:</h2>
+ <pre>{{repr(e.exception)}}</pre>
+ %%end
+ %%if DEBUG and e.traceback:
+ <h2>Traceback:</h2>
+ <pre>{{e.traceback}}</pre>
+ %%end
+ </body>
+ </html>
+%%except ImportError:
+ <b>ImportError:</b> Could not generate the error page. Please add bottle to
+ the import path.
+%%end
+""" % __name__
+
+#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
+#: request callback, this instance always refers to the *current* request
+#: (even on a multithreaded server).
+request = LocalRequest()
+
+#: A thread-safe instance of :class:`LocalResponse`. It is used to change the
+#: HTTP response for the *current* request.
+response = LocalResponse()
+
+#: A thread-safe namespace. Not used by Bottle.
+local = threading.local()
+
+# Initialize app stack (create first empty Bottle app)
+# BC: 0.6.4 and needed for run()
+app = default_app = AppStack()
+app.push()
+
+#: A virtual package that redirects import statements.
+#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
+ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else __name__+".ext", 'bottle_%s').module
+
+if __name__ == '__main__':
+ opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
+ if opt.version:
+ _stdout('Bottle %s\n'%__version__)
+ sys.exit(0)
+ if not args:
+ parser.print_help()
+ _stderr('\nError: No application specified.\n')
+ sys.exit(1)
+
+ sys.path.insert(0, '.')
+ sys.modules.setdefault('bottle', sys.modules['__main__'])
+
+ host, port = (opt.bind or 'localhost'), 8080
+ if ':' in host and host.rfind(']') < host.rfind(':'):
+ host, port = host.rsplit(':', 1)
+ host = host.strip('[]')
+
+ run(args[0], host=host, port=int(port), server=opt.server,
+ reloader=opt.reload, plugins=opt.plugin, debug=opt.debug)
+
+
+
+
+# THE END
diff --git a/venv/lib/python3.9/site-packages/pympler/util/compat.py b/venv/lib/python3.9/site-packages/pympler/util/compat.py
new file mode 100644
index 00000000..8065f3b2
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/util/compat.py
@@ -0,0 +1,23 @@
+"""
+Compatibility layer to allow Pympler being used from different Python versions.
+"""
+
+from typing import Any, Iterable
+
+try:
+ import tkinter
+except ImportError:
+ tkinter = None # type: ignore
+
+
+# Helper functions
+
+def object_in_list(obj: Any, l: Iterable) -> bool:
+ """Returns True if object o is in list.
+
+ Required compatibility function to handle WeakSet objects.
+ """
+ for o in l:
+ if o is obj:
+ return True
+ return False
diff --git a/venv/lib/python3.9/site-packages/pympler/util/stringutils.py b/venv/lib/python3.9/site-packages/pympler/util/stringutils.py
new file mode 100644
index 00000000..7b09e8c3
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/util/stringutils.py
@@ -0,0 +1,77 @@
+"""
+String utility functions.
+"""
+
+from typing import Any, Optional, Union
+
+
+def safe_repr(obj: Any, clip: Optional[int] = None) -> str:
+ """
+ Convert object to string representation, yielding the same result a `repr`
+ but catches all exceptions and returns 'N/A' instead of raising the
+ exception. Strings may be truncated by providing `clip`.
+
+ >>> safe_repr(42)
+ '42'
+ >>> safe_repr('Clipped text', clip=8)
+ 'Clip..xt'
+ >>> safe_repr([1,2,3,4], clip=8)
+ '[1,2..4]'
+ """
+ try:
+ s = repr(obj)
+ if not clip or len(s) <= clip:
+ return s
+ else:
+ return s[:clip - 4] + '..' + s[-2:]
+ except:
+ return 'N/A'
+
+
+def trunc(obj: str, max: int, left: bool = False) -> str:
+ """
+ Convert `obj` to string, eliminate newlines and truncate the string to
+ `max` characters. If there are more characters in the string add ``...`` to
+ the string. With `left=True`, the string can be truncated at the beginning.
+
+ @note: Does not catch exceptions when converting `obj` to string with
+ `str`.
+
+ >>> trunc('This is a long text.', 8)
+ This ...
+ >>> trunc('This is a long text.', 8, left=True)
+ ...text.
+ """
+ s = str(obj)
+ s = s.replace('\n', '|')
+ if len(s) > max:
+ if left:
+ return '...' + s[len(s) - max + 3:]
+ else:
+ return s[:(max - 3)] + '...'
+ else:
+ return s
+
+
+def pp(i: Union[int, float], base: int = 1024) -> str:
+ """
+ Pretty-print the integer `i` as a human-readable size representation.
+ """
+ degree = 0
+ pattern = "%4d %s"
+ while i > base:
+ pattern = "%7.2f %s"
+ i = i / float(base)
+ degree += 1
+ scales = ['B', 'KB', 'MB', 'GB', 'TB', 'EB']
+ return pattern % (i, scales[degree])
+
+
+def pp_timestamp(t: Optional[float]) -> str:
+ """
+ Get a friendly timestamp represented as a string.
+ """
+ if t is None:
+ return ''
+ h, m, s = int(t / 3600), int(t / 60 % 60), t % 60
+ return "%02d:%02d:%05.2f" % (h, m, s)
diff --git a/venv/lib/python3.9/site-packages/pympler/web.py b/venv/lib/python3.9/site-packages/pympler/web.py
new file mode 100644
index 00000000..12888ab0
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/web.py
@@ -0,0 +1,346 @@
+"""
+This module provides a web-based memory profiling interface. The Pympler web
+frontend exposes process information, tracker statistics, and garbage graphs.
+The web frontend uses `Bottle <http://bottlepy.org>`_, a lightweight Python
+web framework. Bottle is packaged with Pympler.
+
+The web server can be invoked almost as easily as setting a breakpoint using
+*pdb*::
+
+ from pympler.web import start_profiler
+ start_profiler()
+
+Calling ``start_profiler`` suspends the current thread and executes the Pympler
+web server, exposing profiling data and various facilities of the Pympler
+library via a graphic interface.
+"""
+
+import sys
+import os
+import threading
+
+from inspect import getouterframes
+from json import dumps
+from shutil import rmtree
+from tempfile import mkdtemp
+from threading import Thread
+from weakref import WeakValueDictionary
+from wsgiref.simple_server import make_server
+
+from pympler import asizeof
+from pympler.garbagegraph import GarbageGraph
+from pympler.process import get_current_threads, ProcessMemoryInfo
+
+from pympler.util.stringutils import safe_repr
+
+# Prefer the installed version of bottle.py. If bottle.py is not installed
+# fallback to the vendored version.
+try:
+ import bottle
+except ImportError:
+ from pympler.util import bottle
+
+
+class ServerState(threading.local):
+ """
+ Represents the state of a running server. Needs to be thread local so
+ multiple servers can be started in different threads without interfering
+ with each other.
+
+ Cache internal structures (garbage graphs, tracker statistics).
+ """
+ def __init__(self):
+ self.server = None
+ self.stats = None
+ self.garbage_graphs = None
+ self.id2ref = WeakValueDictionary()
+ self.id2obj = dict()
+
+ def clear_cache(self):
+ self.garbage_graphs = None
+
+
+server = ServerState()
+
+
+def get_ref(obj):
+ """
+ Get string reference to object. Stores a weak reference in a dictionary
+ using the object's id as the key. If the object cannot be weakly
+ referenced (e.g. dictionaries, frame objects), store a strong references
+ in a classic dictionary.
+
+ Returns the object's id as a string.
+ """
+ oid = id(obj)
+ try:
+ server.id2ref[oid] = obj
+ except TypeError:
+ server.id2obj[oid] = obj
+ return str(oid)
+
+
+def get_obj(ref):
+ """Get object from string reference."""
+ oid = int(ref)
+ return server.id2ref.get(oid) or server.id2obj[oid]
+
+
+pympler_path = os.path.dirname(os.path.abspath(__file__))
+static_files = os.path.join(pympler_path, 'templates')
+
+bottle.TEMPLATE_PATH.append(static_files)
+
+
+@bottle.route('/')
+@bottle.view('index')
+def root():
+ """Get overview."""
+ pmi = ProcessMemoryInfo()
+ return dict(processinfo=pmi)
+
+
+@bottle.route('/process')
+@bottle.view('process')
+def process():
+ """Get process overview."""
+ pmi = ProcessMemoryInfo()
+ threads = get_current_threads()
+ return dict(info=pmi, threads=threads)
+
+
+@bottle.route('/tracker')
+@bottle.view('tracker')
+def tracker_index():
+ """Get tracker overview."""
+ stats = server.stats
+ if stats and stats.snapshots:
+ stats.annotate()
+ timeseries = []
+ for cls in stats.tracked_classes:
+ series = []
+ for snapshot in stats.snapshots:
+ series.append(snapshot.classes.get(cls, {}).get('sum', 0))
+ timeseries.append((cls, series))
+
+ series = [s.overhead for s in stats.snapshots]
+ timeseries.append(("Profiling overhead", series))
+
+ if stats.snapshots[0].system_total.data_segment:
+ # Assume tracked data resides in the data segment
+ series = [s.system_total.data_segment - s.tracked_total - s.overhead
+ for s in stats.snapshots]
+ timeseries.append(("Data segment", series))
+ series = [s.system_total.code_segment for s in stats.snapshots]
+ timeseries.append(("Code segment", series))
+ series = [s.system_total.stack_segment for s in stats.snapshots]
+ timeseries.append(("Stack segment", series))
+ series = [s.system_total.shared_segment for s in stats.snapshots]
+ timeseries.append(("Shared memory", series))
+ else:
+ series = [s.total - s.tracked_total - s.overhead
+ for s in stats.snapshots]
+ timeseries.append(("Other", series))
+ timeseries = [dict(label=label, data=list(enumerate(data)))
+ for label, data in timeseries]
+ return dict(snapshots=stats.snapshots, timeseries=dumps(timeseries))
+ else:
+ return dict(snapshots=[])
+
+
+@bottle.route('/tracker/class/<clsname>')
+@bottle.view('tracker_class')
+def tracker_class(clsname):
+ """Get class instance details."""
+ stats = server.stats
+ if not stats:
+ bottle.redirect('/tracker')
+ stats.annotate()
+ return dict(stats=stats, clsname=clsname)
+
+
+@bottle.route('/refresh')
+def refresh():
+ """Clear all cached information."""
+ server.clear_cache()
+ bottle.redirect('/')
+
+
+@bottle.route('/traceback/<threadid>')
+@bottle.view('stacktrace')
+def get_traceback(threadid):
+ threadid = int(threadid)
+ frames = sys._current_frames()
+ if threadid in frames:
+ frame = frames[threadid]
+ stack = getouterframes(frame, 5)
+ stack.reverse()
+ stack = [(get_ref(f[0].f_locals),) + f[1:] for f in stack]
+ else:
+ stack = []
+ return dict(stack=stack, threadid=threadid)
+
+
+@bottle.route('/objects/<oid>')
+@bottle.view('referents')
+def get_obj_referents(oid):
+ referents = {}
+ obj = get_obj(oid)
+ if type(obj) is dict:
+ named_objects = asizeof.named_refs(obj)
+ else:
+ refs = asizeof._getreferents(obj)
+ named_objects = [(repr(type(x)), x) for x in refs]
+ for name, o in named_objects:
+ referents[name] = (get_ref(o), type(o).__name__,
+ safe_repr(o, clip=48), asizeof.asizeof(o))
+ return dict(referents=referents)
+
+
+@bottle.route('/static/<filename>')
+def static_file(filename):
+ """Get static files (CSS-files)."""
+ return bottle.static_file(filename, root=static_files)
+
+
+def _compute_garbage_graphs():
+ """
+ Retrieve garbage graph objects from cache, compute if cache is cold.
+ """
+ if server.garbage_graphs is None:
+ server.garbage_graphs = GarbageGraph().split_and_sort()
+ return server.garbage_graphs
+
+
+@bottle.route('/garbage')
+@bottle.view('garbage_index')
+def garbage_index():
+ """Get garbage overview."""
+ garbage_graphs = _compute_garbage_graphs()
+ return dict(graphs=garbage_graphs)
+
+
+@bottle.route('/garbage/<index:int>')
+@bottle.view('garbage')
+def garbage_cycle(index):
+ """Get reference cycle details."""
+ graph = _compute_garbage_graphs()[int(index)]
+ graph.reduce_to_cycles()
+ objects = graph.metadata
+ objects.sort(key=lambda x: -x.size)
+ return dict(objects=objects, index=index)
+
+
+def _get_graph(graph, filename):
+ """Retrieve or render a graph."""
+ try:
+ rendered = graph.rendered_file
+ except AttributeError:
+ try:
+ graph.render(os.path.join(server.tmpdir, filename), format='png')
+ rendered = filename
+ except OSError:
+ rendered = None
+ graph.rendered_file = rendered
+ return rendered
+
+
+@bottle.route('/garbage/graph/<index:int>')
+def garbage_graph(index):
+ """Get graph representation of reference cycle."""
+ graph = _compute_garbage_graphs()[int(index)]
+ reduce_graph = bottle.request.GET.get('reduce', '')
+ if reduce_graph:
+ graph = graph.reduce_to_cycles()
+ if not graph:
+ return None
+ filename = 'garbage%so%s.png' % (index, reduce_graph)
+ rendered_file = _get_graph(graph, filename)
+ if rendered_file:
+ return bottle.static_file(rendered_file, root=server.tmpdir)
+ else:
+ return None
+
+
+@bottle.route('/help')
+def show_documentation():
+ """Redirect to online documentation."""
+ bottle.redirect('https://pympler.readthedocs.io/en/latest/')
+
+
+class PymplerServer(bottle.ServerAdapter):
+ """Simple WSGI server."""
+ def run(self, handler):
+ self.server = make_server(self.host, self.port, handler)
+ self.server.serve_forever()
+
+
+def start_profiler(host='localhost', port=8090, tracker=None, stats=None,
+ debug=False, **kwargs):
+ """
+ Start the web server to show profiling data. The function suspends the
+ Python application (the current thread) until the web server is stopped.
+
+ The only way to stop the server is to signal the running thread, e.g. press
+ Ctrl+C in the console. If this isn't feasible for your application use
+ `start_in_background` instead.
+
+ During the execution of the web server, profiling data is (lazily) cached
+ to improve performance. For example, garbage graphs are rendered when the
+ garbage profiling data is requested and are simply retransmitted upon later
+ requests.
+
+ The web server can display profiling data from previously taken snapshots
+ when `tracker` or `stats` is specified. The former is useful for profiling
+ a running application, the latter for off-line analysis. Requires existing
+ snapshots taken with
+ :py:meth:`~pympler.classtracker.ClassTracker.create_snapshot` or
+ :py:meth:`~pympler.classtracker.ClassTracker.start_periodic_snapshots`.
+
+ :param host: the host where the server shall run, default is localhost
+ :param port: server listens on the specified port, default is 8090 to allow
+ coexistance with common web applications
+ :param tracker: `ClassTracker` instance, browse profiling data (on-line
+ analysis)
+ :param stats: `Stats` instance, analyze `ClassTracker` profiling dumps
+ (useful for off-line analysis)
+ """
+ if tracker and not stats:
+ server.stats = tracker.stats
+ else:
+ server.stats = stats
+ try:
+ server.tmpdir = mkdtemp(prefix='pympler')
+ server.server = PymplerServer(host=host, port=port, **kwargs)
+ bottle.debug(debug)
+ bottle.run(server=server.server)
+ finally:
+ rmtree(server.tmpdir)
+
+
+class ProfilerThread(Thread):
+ """Encapsulates a thread to run the web server."""
+ def __init__(self, group=None, target=None, name='Pympler web frontend',
+ **kwargs):
+ super(ProfilerThread, self).__init__(group=group,
+ target=target,
+ name=name)
+ self.kwargs = kwargs
+ self.daemon = True
+
+ def run(self):
+ start_profiler(**self.kwargs)
+
+
+def start_in_background(**kwargs):
+ """
+ Start the web server in the background. A new thread is created which
+ serves the profiling interface without suspending the current application.
+
+ For the documentation of the parameters see `start_profiler`.
+
+ Returns the created thread object.
+ """
+ thread = ProfilerThread(**kwargs)
+ thread.start()
+ return thread