Skip to content

Commit 9eb9b73

Browse files
committed
Merge pull request #1 from pjdelport/initial-version
Initial version
2 parents 791b633 + 77d7f1d commit 9eb9b73

File tree

10 files changed

+656
-0
lines changed

10 files changed

+656
-0
lines changed

.coveragerc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# http://coverage.readthedocs.org/en/latest/config.html
2+
[run]
3+
branch = True
4+
5+
source =
6+
src
7+
tests
8+
9+
omit =
10+
# This is purely backported test support code:
11+
# we don't use all its functionality.
12+
tests/script_helper.py

.travis.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
language: python
2+
cache: pip
3+
4+
matrix:
5+
include:
6+
- { python: '2.7', env: TOXENV=py27 }
7+
- { python: '3.4', env: TOXENV=py34 }
8+
- { python: '3.5', env: TOXENV=py35 }
9+
- { python: 'pypy', env: TOXENV=pypy }
10+
- { python: 'pypy3', env: TOXENV=pypy3 }
11+
12+
# Report coverage for the latest Python 3 and 3 versions, and for PyPy,
13+
# to cover all the interesting code paths.
14+
- { python: '2.7', env: TOXENV=py27-codecov }
15+
- { python: '3.5', env: TOXENV=py35-codecov }
16+
- { python: 'pypy', env: TOXENV=pypy-codecov }
17+
18+
allow_failures:
19+
# PyPy3 on Travis seems to be broken, as of 2016-02.
20+
#
21+
# See: https://github.com/travis-ci/travis-ci/issues/4306
22+
#
23+
- python: 'pypy3'
24+
25+
# Avoid overriding the default install step,
26+
# so that automatic pip caching works.
27+
#
28+
# See: https://github.com/travis-ci/travis-ci/issues/3239
29+
#
30+
before_script:
31+
- pip install tox
32+
33+
script:
34+
- tox

HACKING.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
============================
2+
Working on backports.weakref
3+
============================
4+
5+
6+
Running the tests
7+
=================
8+
9+
Running ``tox``, ``detox``, or ``pytest`` should all work.
10+
11+
With ``unittest``::
12+
13+
python -m unittest discover tests
14+
15+
16+
Coverage
17+
========
18+
19+
With ``coverage``::
20+
21+
coverage run -m unittest discover tests
22+
coverage report
23+
coverage html
24+
25+
With ``pytest`` and ``pytest-cov``::
26+
27+
py.test --cov
28+
py.test --cov --cov-report=html
29+

README.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=================
2+
backports.weakref
3+
=================
4+
5+
This package provides backports of new features in Python's weakref_ module
6+
under the backports_ namespace.
7+
8+
.. _weakref: https://docs.python.org/3.5/library/weakref.html
9+
.. _backports: https://pypi.python.org/pypi/backports
10+
11+
.. image:: https://img.shields.io/pypi/v/backports.weakref.svg
12+
:target: https://pypi.python.org/pypi/backports.weakref
13+
14+
.. image:: https://img.shields.io/badge/source-GitHub-lightgrey.svg
15+
:target: https://github.com/pjdelport/backports.weakref
16+
17+
.. image:: https://img.shields.io/github/issues/pjdelport/backports.weakref.svg
18+
:target: https://github.com/pjdelport/backports.weakref/issues?q=is:open
19+
20+
.. image:: https://travis-ci.org/pjdelport/backports.weakref.svg?branch=master
21+
:target: https://travis-ci.org/pjdelport/backports.weakref
22+
23+
.. image:: https://codecov.io/github/pjdelport/backports.weakref/coverage.svg?branch=master
24+
:target: https://codecov.io/github/pjdelport/backports.weakref?branch=master
25+
26+
27+
Supported Python versions
28+
=========================
29+
30+
* CPython: 2.7, 3.4, 3.5
31+
* PyPy
32+
33+
34+
Backported functionality
35+
========================
36+
37+
* `weakref.finalize`_ (new in Python 3.4)
38+
39+
.. _`weakref.finalize`: https://docs.python.org/3.5/library/weakref.html#weakref.finalize
40+
41+
42+
Contributing
43+
============
44+
45+
See `<HACKING.rst>`__.

setup.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# coding: utf-8
2+
from setuptools import setup, find_packages
3+
4+
setup(
5+
name='backports.weakref',
6+
description="Backport of new features in Python's weakref module",
7+
url='https://github.com/pjdelport/backports.weakref',
8+
9+
author=u'Piët Delport',
10+
author_email='pjdelport@gmail.com',
11+
12+
package_dir={'': 'src'},
13+
packages=find_packages('src'),
14+
15+
setup_requires=['setuptools_scm'],
16+
use_scm_version=True,
17+
18+
license='Python Software Foundation License',
19+
classifiers=[
20+
'Development Status :: 6 - Mature',
21+
'Intended Audience :: Developers',
22+
'License :: OSI Approved :: Python Software Foundation License',
23+
'Programming Language :: Python :: 2',
24+
'Programming Language :: Python :: 2.7',
25+
'Programming Language :: Python :: 3',
26+
'Programming Language :: Python :: 3.4',
27+
'Programming Language :: Python :: 3.5',
28+
'Topic :: Software Development :: Libraries :: Python Modules',
29+
],
30+
)

src/backports/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# See https://pypi.python.org/pypi/backports
2+
3+
from pkgutil import extend_path
4+
__path__ = extend_path(__path__, __name__)

src/backports/weakref.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Partial backport of Python 3.5's weakref module:
3+
4+
finalize (new in Python 3.4)
5+
6+
Backport modifications are marked with marked with "XXX backport".
7+
"""
8+
from __future__ import absolute_import
9+
10+
import itertools
11+
import sys
12+
from weakref import ref
13+
14+
__all__ = ['finalize']
15+
16+
17+
class finalize(object):
18+
"""Class for finalization of weakrefable objects
19+
20+
finalize(obj, func, *args, **kwargs) returns a callable finalizer
21+
object which will be called when obj is garbage collected. The
22+
first time the finalizer is called it evaluates func(*arg, **kwargs)
23+
and returns the result. After this the finalizer is dead, and
24+
calling it just returns None.
25+
26+
When the program exits any remaining finalizers for which the
27+
atexit attribute is true will be run in reverse order of creation.
28+
By default atexit is true.
29+
"""
30+
31+
# Finalizer objects don't have any state of their own. They are
32+
# just used as keys to lookup _Info objects in the registry. This
33+
# ensures that they cannot be part of a ref-cycle.
34+
35+
__slots__ = ()
36+
_registry = {}
37+
_shutdown = False
38+
_index_iter = itertools.count()
39+
_dirty = False
40+
_registered_with_atexit = False
41+
42+
class _Info(object):
43+
__slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
44+
45+
def __init__(self, obj, func, *args, **kwargs):
46+
if not self._registered_with_atexit:
47+
# We may register the exit function more than once because
48+
# of a thread race, but that is harmless
49+
import atexit
50+
atexit.register(self._exitfunc)
51+
finalize._registered_with_atexit = True
52+
info = self._Info()
53+
info.weakref = ref(obj, self)
54+
info.func = func
55+
info.args = args
56+
info.kwargs = kwargs or None
57+
info.atexit = True
58+
info.index = next(self._index_iter)
59+
self._registry[self] = info
60+
finalize._dirty = True
61+
62+
def __call__(self, _=None):
63+
"""If alive then mark as dead and return func(*args, **kwargs);
64+
otherwise return None"""
65+
info = self._registry.pop(self, None)
66+
if info and not self._shutdown:
67+
return info.func(*info.args, **(info.kwargs or {}))
68+
69+
def detach(self):
70+
"""If alive then mark as dead and return (obj, func, args, kwargs);
71+
otherwise return None"""
72+
info = self._registry.get(self)
73+
obj = info and info.weakref()
74+
if obj is not None and self._registry.pop(self, None):
75+
return (obj, info.func, info.args, info.kwargs or {})
76+
77+
def peek(self):
78+
"""If alive then return (obj, func, args, kwargs);
79+
otherwise return None"""
80+
info = self._registry.get(self)
81+
obj = info and info.weakref()
82+
if obj is not None:
83+
return (obj, info.func, info.args, info.kwargs or {})
84+
85+
@property
86+
def alive(self):
87+
"""Whether finalizer is alive"""
88+
return self in self._registry
89+
90+
@property
91+
def atexit(self):
92+
"""Whether finalizer should be called at exit"""
93+
info = self._registry.get(self)
94+
return bool(info) and info.atexit
95+
96+
@atexit.setter
97+
def atexit(self, value):
98+
info = self._registry.get(self)
99+
if info:
100+
info.atexit = bool(value)
101+
102+
def __repr__(self):
103+
info = self._registry.get(self)
104+
obj = info and info.weakref()
105+
if obj is None:
106+
return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
107+
else:
108+
return '<%s object at %#x; for %r at %#x>' % \
109+
(type(self).__name__, id(self), type(obj).__name__, id(obj))
110+
111+
@classmethod
112+
def _select_for_exit(cls):
113+
# Return live finalizers marked for exit, oldest first
114+
L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
115+
L.sort(key=lambda item:item[1].index)
116+
return [f for (f,i) in L]
117+
118+
@classmethod
119+
def _exitfunc(cls):
120+
# At shutdown invoke finalizers for which atexit is true.
121+
# This is called once all other non-daemonic threads have been
122+
# joined.
123+
reenable_gc = False
124+
try:
125+
if cls._registry:
126+
import gc
127+
if gc.isenabled():
128+
reenable_gc = True
129+
gc.disable()
130+
pending = None
131+
while True:
132+
if pending is None or finalize._dirty:
133+
pending = cls._select_for_exit()
134+
finalize._dirty = False
135+
if not pending:
136+
break
137+
f = pending.pop()
138+
try:
139+
# gc is disabled, so (assuming no daemonic
140+
# threads) the following is the only line in
141+
# this function which might trigger creation
142+
# of a new finalizer
143+
f()
144+
except Exception:
145+
sys.excepthook(*sys.exc_info())
146+
assert f not in cls._registry
147+
finally:
148+
# prevent any more finalizers from executing during shutdown
149+
finalize._shutdown = True
150+
if reenable_gc:
151+
gc.enable()

0 commit comments

Comments
 (0)