aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrank Feng <frankfeng@google.com>2022-06-09 21:33:42 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-06-09 21:33:42 +0000
commitbc2d4c9d3c538078284f2159efe652d7f8e4fc13 (patch)
tree28f4b594992c67f3ecc26dff01540b94db8beaa3
parentffe302e4231ab8882d9a8ea68c86ed2a04a28fd8 (diff)
parent7805d425eeaa9ee41f8b188eaffddea7e1fc50ad (diff)
downloadtimeout-decorator-bc2d4c9d3c538078284f2159efe652d7f8e4fc13.tar.gz
Merge remote-tracking branch 'goog/mirror-aosp-master' into bp_timeout am: e2a8f4c150 am: 7805d425eeandroid-13.0.0_r83android-13.0.0_r82android-13.0.0_r81android-13.0.0_r80android-13.0.0_r79android-13.0.0_r78android-13.0.0_r77android-13.0.0_r76android-13.0.0_r75android-13.0.0_r74android-13.0.0_r73android-13.0.0_r72android-13.0.0_r71android-13.0.0_r70android-13.0.0_r69android-13.0.0_r68android-13.0.0_r67android-13.0.0_r66android-13.0.0_r65android-13.0.0_r64android-13.0.0_r63android-13.0.0_r62android-13.0.0_r61android-13.0.0_r60android-13.0.0_r59android-13.0.0_r58android-13.0.0_r56android-13.0.0_r54android-13.0.0_r53android-13.0.0_r52android-13.0.0_r51android-13.0.0_r50android-13.0.0_r49android-13.0.0_r48android-13.0.0_r47android-13.0.0_r46android-13.0.0_r45android-13.0.0_r44android-13.0.0_r43android-13.0.0_r42android-13.0.0_r41android-13.0.0_r40android-13.0.0_r39android-13.0.0_r38android-13.0.0_r37android-13.0.0_r36android-13.0.0_r35android-13.0.0_r34android-13.0.0_r33android-13.0.0_r32android13-qpr3-s9-releaseandroid13-qpr3-s8-releaseandroid13-qpr3-s7-releaseandroid13-qpr3-s6-releaseandroid13-qpr3-s5-releaseandroid13-qpr3-s4-releaseandroid13-qpr3-s3-releaseandroid13-qpr3-s2-releaseandroid13-qpr3-s14-releaseandroid13-qpr3-s13-releaseandroid13-qpr3-s12-releaseandroid13-qpr3-s11-releaseandroid13-qpr3-s10-releaseandroid13-qpr3-s1-releaseandroid13-qpr3-releaseandroid13-qpr3-c-s8-releaseandroid13-qpr3-c-s7-releaseandroid13-qpr3-c-s6-releaseandroid13-qpr3-c-s5-releaseandroid13-qpr3-c-s4-releaseandroid13-qpr3-c-s3-releaseandroid13-qpr3-c-s2-releaseandroid13-qpr3-c-s12-releaseandroid13-qpr3-c-s11-releaseandroid13-qpr3-c-s10-releaseandroid13-qpr3-c-s1-releaseandroid13-qpr2-s9-releaseandroid13-qpr2-s8-releaseandroid13-qpr2-s7-releaseandroid13-qpr2-s6-releaseandroid13-qpr2-s5-releaseandroid13-qpr2-s3-releaseandroid13-qpr2-s2-releaseandroid13-qpr2-s12-releaseandroid13-qpr2-s11-releaseandroid13-qpr2-s10-releaseandroid13-qpr2-s1-releaseandroid13-qpr2-releaseandroid13-qpr2-b-s1-releaseandroid13-d4-s2-releaseandroid13-d4-s1-releaseandroid13-d4-release
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/python/timeout-decorator/+/18819311 Change-Id: I823b8095e352ef94989293db16d63231f699f031 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.gitignore43
-rw-r--r--.travis.yml21
-rw-r--r--Android.bp29
-rw-r--r--CHANGES.rst18
l---------LICENSE1
-rw-r--r--LICENSE.txt22
-rw-r--r--MANIFEST.in1
-rw-r--r--METADATA16
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--Makefile11
-rw-r--r--NOTICE22
-rw-r--r--OWNERS8
-rw-r--r--README.rst111
-rw-r--r--setup.py38
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_timeout_decorator.py120
-rw-r--r--timeout_decorator/Android.bp33
-rw-r--r--timeout_decorator/__init__.py7
-rw-r--r--timeout_decorator/timeout_decorator.py175
-rw-r--r--tox.ini17
20 files changed, 693 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0b7661f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+*.py[cod]
+*.swp
+*~
+venv
+.env
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+.cache
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+.ropeproject/
+.vscode/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..02adf35
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+language: python
+sudo: false
+python:
+- '2.7'
+- '3.6'
+- '3.7'
+- '3.8'
+install:
+- pip install python-coveralls tox tox-travis
+script: tox --recreate
+after_success:
+- pip install -e .
+- py.test --cov=timeout_decorator --cov-report=term-missing tests
+- coveralls
+deploy:
+ provider: pypi
+ user: png
+ password:
+ secure: ZXoq3kgfu+IICjhhmQZr0s0xE6bvWzH04GjdE/VL4BxdDdGI4fHEwudGEjzLXJbt2d09vNOO67Nqam+MwPWtq+WZEP69g/Fhyy4kbkuUl/CMeqashQzU/N+3lwv97Y2qvzTUwDnSoz4zyBFu67SSrovKruFsYaiH00bwvWcvLa0=
+ on:
+ python: 2.7
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..535ec1d
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["external_python_timeout_decorator_license"],
+}
+
+license {
+ name: "external_python_timeout_decorator_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-MIT",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
diff --git a/CHANGES.rst b/CHANGES.rst
new file mode 100644
index 0000000..1fd3175
--- /dev/null
+++ b/CHANGES.rst
@@ -0,0 +1,18 @@
+Changelog
+=========
+
+0.3.1
+-----
+- Fixed issue with PicklingError causes the timeout to never be reached.
+
+0.3.0
+-----
+
+- Added optional threading support via python multiprocessing (bubenkoff)
+- Switched to pytest test runner (bubenkoff)
+
+
+0.2.1
+-----
+
+- Initial public release
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..85de3d4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE.txt \ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..671c599
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2014 Patrick Ng
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..bb37a27
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include *.rst
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..5323ad6
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,16 @@
+name: "timeout-decorator"
+description:
+ "timeout-decorator"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://github.com/pnpnpn/timeout-decorator"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/pnpnpn/timeout-decorator"
+ }
+ version: "9fbc3ef5b6f8f8cba2eb7ba795813d6ec543e265"
+ last_upgrade_date { year: 2022 month: 4 day: 25 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..017607e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+# create virtual environment
+venv:
+ virtualenv venv
+
+# install all needed for development
+develop: venv
+ venv/bin/pip install -e . -r requirements-testing.txt tox
+
+# clean the development envrironment
+clean:
+ -rm -rf venv
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..671c599
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2014 Patrick Ng
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..eb86f14
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,8 @@
+# Android side engprod team
+jdesprez@google.com
+frankfeng@google.com
+murj@google.com
+
+# Mobly team - use for mobly bugs
+angli@google.com
+lancefluger@google.com
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..2dcea7b
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,111 @@
+Timeout decorator
+=================
+
+|Build Status| |Pypi Status| |Coveralls Status|
+
+Installation
+------------
+
+From source code:
+
+::
+
+ python setup.py install
+
+From pypi:
+
+::
+
+ pip install timeout-decorator
+
+Usage
+-----
+
+::
+
+ import time
+ import timeout_decorator
+
+ @timeout_decorator.timeout(5)
+ def mytest():
+ print("Start")
+ for i in range(1,10):
+ time.sleep(1)
+ print("{} seconds have passed".format(i))
+
+ if __name__ == '__main__':
+ mytest()
+
+Specify an alternate exception to raise on timeout:
+
+::
+
+ import time
+ import timeout_decorator
+
+ @timeout_decorator.timeout(5, timeout_exception=StopIteration)
+ def mytest():
+ print("Start")
+ for i in range(1,10):
+ time.sleep(1)
+ print("{} seconds have passed".format(i))
+
+ if __name__ == '__main__':
+ mytest()
+
+Multithreading
+--------------
+
+By default, timeout-decorator uses signals to limit the execution time
+of the given function. This appoach does not work if your function is
+executed not in a main thread (for example if it's a worker thread of
+the web application). There is alternative timeout strategy for this
+case - by using multiprocessing. To use it, just pass
+``use_signals=False`` to the timeout decorator function:
+
+::
+
+ import time
+ import timeout_decorator
+
+ @timeout_decorator.timeout(5, use_signals=False)
+ def mytest():
+ print "Start"
+ for i in range(1,10):
+ time.sleep(1)
+ print("{} seconds have passed".format(i))
+
+ if __name__ == '__main__':
+ mytest()
+
+.. warning::
+ Make sure that in case of multiprocessing strategy for timeout, your function does not return objects which cannot
+ be pickled, otherwise it will fail at marshalling it between master and child processes.
+
+
+Acknowledgement
+---------------
+
+Derived from
+http://www.saltycrane.com/blog/2010/04/using-python-timeout-decorator-uploading-s3/
+and https://code.google.com/p/verse-quiz/source/browse/trunk/timeout.py
+
+Contribute
+----------
+
+I would love for you to fork and send me pull request for this project.
+Please contribute.
+
+License
+-------
+
+This software is licensed under the `MIT license <http://en.wikipedia.org/wiki/MIT_License>`_
+
+See `License file <https://github.com/pnpnpn/timeout-decorator/blob/master/LICENSE.txt>`_
+
+.. |Build Status| image:: https://travis-ci.org/pnpnpn/timeout-decorator.svg?branch=master
+ :target: https://travis-ci.org/pnpnpn/timeout-decorator
+.. |Pypi Status| image:: https://badge.fury.io/py/timeout-decorator.svg
+ :target: https://badge.fury.io/py/timeout-decorator
+.. |Coveralls Status| image:: https://coveralls.io/repos/pnpnpn/timeout-decorator/badge.png?branch=master
+ :target: https://coveralls.io/r/pnpnpn/timeout-decorator
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..32771a8
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,38 @@
+"""Setuptools entry point."""
+import codecs
+import os
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+
+CLASSIFIERS = [
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules'
+]
+
+dirname = os.path.dirname(__file__)
+
+long_description = (
+ codecs.open(os.path.join(dirname, 'README.rst'), encoding='utf-8').read() + '\n' +
+ codecs.open(os.path.join(dirname, 'CHANGES.rst'), encoding='utf-8').read()
+)
+
+setup(
+ name='timeout-decorator',
+ version='0.5.0',
+ description='Timeout decorator',
+ long_description=long_description,
+ author='Patrick Ng',
+ author_email='pn.appdev@gmail.com',
+ url='https://github.com/pnpnpn/timeout-decorator',
+ packages=['timeout_decorator'],
+ install_requires=[],
+ classifiers=CLASSIFIERS)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_timeout_decorator.py b/tests/test_timeout_decorator.py
new file mode 100644
index 0000000..2c2d9c7
--- /dev/null
+++ b/tests/test_timeout_decorator.py
@@ -0,0 +1,120 @@
+"""Timeout decorator tests."""
+import time
+
+import pytest
+
+from timeout_decorator import timeout, TimeoutError
+
+
+@pytest.fixture(params=[False, True])
+def use_signals(request):
+ """Use signals for timing out or not."""
+ return request.param
+
+
+def test_timeout_decorator_arg(use_signals):
+ @timeout(1, use_signals=use_signals)
+ def f():
+ time.sleep(2)
+ with pytest.raises(TimeoutError):
+ f()
+
+
+def test_timeout_class_method(use_signals):
+ class c():
+ @timeout(1, use_signals=use_signals)
+ def f(self):
+ time.sleep(2)
+ with pytest.raises(TimeoutError):
+ c().f()
+
+
+def test_timeout_kwargs(use_signals):
+ @timeout(3, use_signals=use_signals)
+ def f():
+ time.sleep(2)
+ with pytest.raises(TimeoutError):
+ f(timeout=1)
+
+
+def test_timeout_alternate_exception(use_signals):
+ @timeout(3, use_signals=use_signals, timeout_exception=StopIteration)
+ def f():
+ time.sleep(2)
+ with pytest.raises(StopIteration):
+ f(timeout=1)
+
+
+def test_timeout_kwargs_with_initial_timeout_none(use_signals):
+ @timeout(use_signals=use_signals)
+ def f():
+ time.sleep(2)
+ with pytest.raises(TimeoutError):
+ f(timeout=1)
+
+
+def test_timeout_no_seconds(use_signals):
+ @timeout(use_signals=use_signals)
+ def f():
+ time.sleep(0.1)
+ f()
+
+
+def test_timeout_partial_seconds(use_signals):
+ @timeout(0.2, use_signals=use_signals)
+ def f():
+ time.sleep(0.5)
+ with pytest.raises(TimeoutError):
+ f()
+
+
+def test_timeout_ok(use_signals):
+ @timeout(seconds=2, use_signals=use_signals)
+ def f():
+ time.sleep(1)
+ f()
+
+
+def test_function_name(use_signals):
+ @timeout(seconds=2, use_signals=use_signals)
+ def func_name():
+ pass
+
+ assert func_name.__name__ == 'func_name'
+
+
+def test_timeout_pickle_error():
+ """Test that when a pickle error occurs a timeout error is raised."""
+ @timeout(seconds=1, use_signals=False)
+ def f():
+ time.sleep(0.1)
+
+ class Test(object):
+ pass
+ return Test()
+ with pytest.raises(TimeoutError):
+ f()
+
+
+def test_timeout_custom_exception_message():
+ @timeout(seconds=1, exception_message="Custom fail message")
+ def f():
+ time.sleep(2)
+ with pytest.raises(TimeoutError, match="Custom fail message"):
+ f()
+
+
+def test_timeout_custom_exception_with_message():
+ @timeout(seconds=1, timeout_exception=RuntimeError, exception_message="Custom fail message")
+ def f():
+ time.sleep(2)
+ with pytest.raises(RuntimeError, match="Custom fail message"):
+ f()
+
+
+def test_timeout_default_exception_message():
+ @timeout(seconds=1)
+ def f():
+ time.sleep(2)
+ with pytest.raises(TimeoutError, match="Timed Out"):
+ f()
diff --git a/timeout_decorator/Android.bp b/timeout_decorator/Android.bp
new file mode 100644
index 0000000..b70e430
--- /dev/null
+++ b/timeout_decorator/Android.bp
@@ -0,0 +1,33 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+ default_applicable_licenses: ["external_python_timeout_decorator_license"],
+}
+
+python_library {
+ name: "py-timeout-decorator",
+ host_supported: true,
+ srcs: [
+ "timeout_decorator.py",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+ pkg_path: "timeout_decorator",
+}
diff --git a/timeout_decorator/__init__.py b/timeout_decorator/__init__.py
new file mode 100644
index 0000000..4c8254b
--- /dev/null
+++ b/timeout_decorator/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from .timeout_decorator import timeout
+from .timeout_decorator import TimeoutError
+
+__title__ = 'timeout_decorator'
+__version__ = '0.5.0'
diff --git a/timeout_decorator/timeout_decorator.py b/timeout_decorator/timeout_decorator.py
new file mode 100644
index 0000000..42b6686
--- /dev/null
+++ b/timeout_decorator/timeout_decorator.py
@@ -0,0 +1,175 @@
+"""
+Timeout decorator.
+
+ :copyright: (c) 2012-2013 by PN.
+ :license: MIT, see LICENSE for more details.
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from __future__ import division
+
+import sys
+import time
+import multiprocessing
+import signal
+from functools import wraps
+
+############################################################
+# Timeout
+############################################################
+
+# http://www.saltycrane.com/blog/2010/04/using-python-timeout-decorator-uploading-s3/
+# Used work of Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>
+# in https://code.google.com/p/verse-quiz/source/browse/trunk/timeout.py
+
+
+class TimeoutError(AssertionError):
+
+ """Thrown when a timeout occurs in the `timeout` context manager."""
+
+ def __init__(self, value="Timed Out"):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+def _raise_exception(exception, exception_message):
+ """ This function checks if a exception message is given.
+
+ If there is no exception message, the default behaviour is maintained.
+ If there is an exception message, the message is passed to the exception with the 'value' keyword.
+ """
+ if exception_message is None:
+ raise exception()
+ else:
+ raise exception(exception_message)
+
+
+def timeout(seconds=None, use_signals=True, timeout_exception=TimeoutError, exception_message=None):
+ """Add a timeout parameter to a function and return it.
+
+ :param seconds: optional time limit in seconds or fractions of a second. If None is passed, no timeout is applied.
+ This adds some flexibility to the usage: you can disable timing out depending on the settings.
+ :type seconds: float
+ :param use_signals: flag indicating whether signals should be used for timing function out or the multiprocessing
+ When using multiprocessing, timeout granularity is limited to 10ths of a second.
+ :type use_signals: bool
+
+ :raises: TimeoutError if time limit is reached
+
+ It is illegal to pass anything other than a function as the first
+ parameter. The function is wrapped and returned to the caller.
+ """
+ def decorate(function):
+
+ if use_signals:
+ def handler(signum, frame):
+ _raise_exception(timeout_exception, exception_message)
+
+ @wraps(function)
+ def new_function(*args, **kwargs):
+ new_seconds = kwargs.pop('timeout', seconds)
+ if new_seconds:
+ old = signal.signal(signal.SIGALRM, handler)
+ signal.setitimer(signal.ITIMER_REAL, new_seconds)
+
+ if not seconds:
+ return function(*args, **kwargs)
+
+ try:
+ return function(*args, **kwargs)
+ finally:
+ if new_seconds:
+ signal.setitimer(signal.ITIMER_REAL, 0)
+ signal.signal(signal.SIGALRM, old)
+ return new_function
+ else:
+ @wraps(function)
+ def new_function(*args, **kwargs):
+ timeout_wrapper = _Timeout(function, timeout_exception, exception_message, seconds)
+ return timeout_wrapper(*args, **kwargs)
+ return new_function
+
+ return decorate
+
+
+def _target(queue, function, *args, **kwargs):
+ """Run a function with arguments and return output via a queue.
+
+ This is a helper function for the Process created in _Timeout. It runs
+ the function with positional arguments and keyword arguments and then
+ returns the function's output by way of a queue. If an exception gets
+ raised, it is returned to _Timeout to be raised by the value property.
+ """
+ try:
+ queue.put((True, function(*args, **kwargs)))
+ except:
+ queue.put((False, sys.exc_info()[1]))
+
+
+class _Timeout(object):
+
+ """Wrap a function and add a timeout (limit) attribute to it.
+
+ Instances of this class are automatically generated by the add_timeout
+ function defined above. Wrapping a function allows asynchronous calls
+ to be made and termination of execution after a timeout has passed.
+ """
+
+ def __init__(self, function, timeout_exception, exception_message, limit):
+ """Initialize instance in preparation for being called."""
+ self.__limit = limit
+ self.__function = function
+ self.__timeout_exception = timeout_exception
+ self.__exception_message = exception_message
+ self.__name__ = function.__name__
+ self.__doc__ = function.__doc__
+ self.__timeout = time.time()
+ self.__process = multiprocessing.Process()
+ self.__queue = multiprocessing.Queue()
+
+ def __call__(self, *args, **kwargs):
+ """Execute the embedded function object asynchronously.
+
+ The function given to the constructor is transparently called and
+ requires that "ready" be intermittently polled. If and when it is
+ True, the "value" property may then be checked for returned data.
+ """
+ self.__limit = kwargs.pop('timeout', self.__limit)
+ self.__queue = multiprocessing.Queue(1)
+ args = (self.__queue, self.__function) + args
+ self.__process = multiprocessing.Process(target=_target,
+ args=args,
+ kwargs=kwargs)
+ self.__process.daemon = True
+ self.__process.start()
+ if self.__limit is not None:
+ self.__timeout = self.__limit + time.time()
+ while not self.ready:
+ time.sleep(0.01)
+ return self.value
+
+ def cancel(self):
+ """Terminate any possible execution of the embedded function."""
+ if self.__process.is_alive():
+ self.__process.terminate()
+
+ _raise_exception(self.__timeout_exception, self.__exception_message)
+
+ @property
+ def ready(self):
+ """Read-only property indicating status of "value" property."""
+ if self.__limit and self.__timeout < time.time():
+ self.cancel()
+ return self.__queue.full() and not self.__queue.empty()
+
+ @property
+ def value(self):
+ """Read-only property containing data returned from function."""
+ if self.ready is True:
+ flag, load = self.__queue.get()
+ if flag:
+ return load
+ raise load
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..232b10b
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,17 @@
+[tox]
+distshare={homedir}/.tox/distshare
+envlist=py{27,36,37,38}
+skip_missing_interpreters=true
+indexserver=
+ pypi = https://pypi.python.org/simple
+
+[testenv]
+commands=
+ py.test timeout_decorator tests
+deps =
+ pytest
+ pytest-pep8
+
+[pytest]
+addopts = -vvl
+pep8maxlinelength=120