Skip to content

Commit

Permalink
Merge pull request #1 from pjdelport/initial-version
Browse files Browse the repository at this point in the history
Initial version
  • Loading branch information
PiDelport committed Mar 8, 2016
2 parents 791b633 + 77d7f1d commit 9eb9b73
Show file tree
Hide file tree
Showing 10 changed files with 656 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# http://coverage.readthedocs.org/en/latest/config.html
[run]
branch = True

source =
src
tests

omit =
# This is purely backported test support code:
# we don't use all its functionality.
tests/script_helper.py
34 changes: 34 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
language: python
cache: pip

matrix:
include:
- { python: '2.7', env: TOXENV=py27 }
- { python: '3.4', env: TOXENV=py34 }
- { python: '3.5', env: TOXENV=py35 }
- { python: 'pypy', env: TOXENV=pypy }
- { python: 'pypy3', env: TOXENV=pypy3 }

# Report coverage for the latest Python 3 and 3 versions, and for PyPy,
# to cover all the interesting code paths.
- { python: '2.7', env: TOXENV=py27-codecov }
- { python: '3.5', env: TOXENV=py35-codecov }
- { python: 'pypy', env: TOXENV=pypy-codecov }

allow_failures:
# PyPy3 on Travis seems to be broken, as of 2016-02.
#
# See: https://github.com/travis-ci/travis-ci/issues/4306
#
- python: 'pypy3'

# Avoid overriding the default install step,
# so that automatic pip caching works.
#
# See: https://github.com/travis-ci/travis-ci/issues/3239
#
before_script:
- pip install tox

script:
- tox
29 changes: 29 additions & 0 deletions HACKING.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
============================
Working on backports.weakref
============================


Running the tests
=================

Running ``tox``, ``detox``, or ``pytest`` should all work.

With ``unittest``::

python -m unittest discover tests


Coverage
========

With ``coverage``::

coverage run -m unittest discover tests
coverage report
coverage html

With ``pytest`` and ``pytest-cov``::

py.test --cov
py.test --cov --cov-report=html

45 changes: 45 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
=================
backports.weakref
=================

This package provides backports of new features in Python's weakref_ module
under the backports_ namespace.

.. _weakref: https://docs.python.org/3.5/library/weakref.html
.. _backports: https://pypi.python.org/pypi/backports

.. image:: https://img.shields.io/pypi/v/backports.weakref.svg
:target: https://pypi.python.org/pypi/backports.weakref

.. image:: https://img.shields.io/badge/source-GitHub-lightgrey.svg
:target: https://github.com/pjdelport/backports.weakref

.. image:: https://img.shields.io/github/issues/pjdelport/backports.weakref.svg
:target: https://github.com/pjdelport/backports.weakref/issues?q=is:open

.. image:: https://travis-ci.org/pjdelport/backports.weakref.svg?branch=master
:target: https://travis-ci.org/pjdelport/backports.weakref

.. image:: https://codecov.io/github/pjdelport/backports.weakref/coverage.svg?branch=master
:target: https://codecov.io/github/pjdelport/backports.weakref?branch=master


Supported Python versions
=========================

* CPython: 2.7, 3.4, 3.5
* PyPy


Backported functionality
========================

* `weakref.finalize`_ (new in Python 3.4)

.. _`weakref.finalize`: https://docs.python.org/3.5/library/weakref.html#weakref.finalize


Contributing
============

See `<HACKING.rst>`__.
30 changes: 30 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding: utf-8
from setuptools import setup, find_packages

setup(
name='backports.weakref',
description="Backport of new features in Python's weakref module",
url='https://github.com/pjdelport/backports.weakref',

author=u'Piët Delport',
author_email='pjdelport@gmail.com',

package_dir={'': 'src'},
packages=find_packages('src'),

setup_requires=['setuptools_scm'],
use_scm_version=True,

license='Python Software Foundation License',
classifiers=[
'Development Status :: 6 - Mature',
'Intended Audience :: Developers',
'License :: OSI Approved :: Python Software Foundation License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Software Development :: Libraries :: Python Modules',
],
)
4 changes: 4 additions & 0 deletions src/backports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# See https://pypi.python.org/pypi/backports

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
151 changes: 151 additions & 0 deletions src/backports/weakref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Partial backport of Python 3.5's weakref module:
finalize (new in Python 3.4)
Backport modifications are marked with marked with "XXX backport".
"""
from __future__ import absolute_import

import itertools
import sys
from weakref import ref

__all__ = ['finalize']


class finalize(object):
"""Class for finalization of weakrefable objects
finalize(obj, func, *args, **kwargs) returns a callable finalizer
object which will be called when obj is garbage collected. The
first time the finalizer is called it evaluates func(*arg, **kwargs)
and returns the result. After this the finalizer is dead, and
calling it just returns None.
When the program exits any remaining finalizers for which the
atexit attribute is true will be run in reverse order of creation.
By default atexit is true.
"""

# Finalizer objects don't have any state of their own. They are
# just used as keys to lookup _Info objects in the registry. This
# ensures that they cannot be part of a ref-cycle.

__slots__ = ()
_registry = {}
_shutdown = False
_index_iter = itertools.count()
_dirty = False
_registered_with_atexit = False

class _Info(object):
__slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")

def __init__(self, obj, func, *args, **kwargs):
if not self._registered_with_atexit:
# We may register the exit function more than once because
# of a thread race, but that is harmless
import atexit
atexit.register(self._exitfunc)
finalize._registered_with_atexit = True
info = self._Info()
info.weakref = ref(obj, self)
info.func = func
info.args = args
info.kwargs = kwargs or None
info.atexit = True
info.index = next(self._index_iter)
self._registry[self] = info
finalize._dirty = True

def __call__(self, _=None):
"""If alive then mark as dead and return func(*args, **kwargs);
otherwise return None"""
info = self._registry.pop(self, None)
if info and not self._shutdown:
return info.func(*info.args, **(info.kwargs or {}))

def detach(self):
"""If alive then mark as dead and return (obj, func, args, kwargs);
otherwise return None"""
info = self._registry.get(self)
obj = info and info.weakref()
if obj is not None and self._registry.pop(self, None):
return (obj, info.func, info.args, info.kwargs or {})

def peek(self):
"""If alive then return (obj, func, args, kwargs);
otherwise return None"""
info = self._registry.get(self)
obj = info and info.weakref()
if obj is not None:
return (obj, info.func, info.args, info.kwargs or {})

@property
def alive(self):
"""Whether finalizer is alive"""
return self in self._registry

@property
def atexit(self):
"""Whether finalizer should be called at exit"""
info = self._registry.get(self)
return bool(info) and info.atexit

@atexit.setter
def atexit(self, value):
info = self._registry.get(self)
if info:
info.atexit = bool(value)

def __repr__(self):
info = self._registry.get(self)
obj = info and info.weakref()
if obj is None:
return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
else:
return '<%s object at %#x; for %r at %#x>' % \
(type(self).__name__, id(self), type(obj).__name__, id(obj))

@classmethod
def _select_for_exit(cls):
# Return live finalizers marked for exit, oldest first
L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
L.sort(key=lambda item:item[1].index)
return [f for (f,i) in L]

@classmethod
def _exitfunc(cls):
# At shutdown invoke finalizers for which atexit is true.
# This is called once all other non-daemonic threads have been
# joined.
reenable_gc = False
try:
if cls._registry:
import gc
if gc.isenabled():
reenable_gc = True
gc.disable()
pending = None
while True:
if pending is None or finalize._dirty:
pending = cls._select_for_exit()
finalize._dirty = False
if not pending:
break
f = pending.pop()
try:
# gc is disabled, so (assuming no daemonic
# threads) the following is the only line in
# this function which might trigger creation
# of a new finalizer
f()
except Exception:
sys.excepthook(*sys.exc_info())
assert f not in cls._registry
finally:
# prevent any more finalizers from executing during shutdown
finalize._shutdown = True
if reenable_gc:
gc.enable()
Loading

0 comments on commit 9eb9b73

Please sign in to comment.