diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..b17804e
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,41 @@
+name: check
+on: [push]
+jobs:
+ install:
+ name: python
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 3
+ matrix:
+ python-version:
+ - "3.10"
+ - "3.11"
+ - "3.13"
+ env:
+ UV_PYTHON: ${{ matrix.python-version }}
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Install just
+ uses: extractions/setup-just@v3
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Install the project
+ run: just install
+
+ - name: Check python formating
+ run: just check-python-formating
+
+ - name: Check python typing
+ run: just check-python-typing
+
+ - name: Check python docstrings
+ run: just check-python-docstrings
+
+ - name: Check toml formating
+ run: just check-toml-formating
+
+ - name: Run tests
+ run: just test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f45abdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# Python-generated files
+__pycache__/
+*.py[oc]
+build/
+dist/
+wheels/
+*.egg-info
+
+# uv generated files
+uv.lock
+
+# Virtual environments
+.venv
+
+# coverage generated files
+.coverage
+htmlcov/
+
+# Sphinx generated files
+docs/_build/
+docs/api/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ca5e95e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# Config file for automatic testing at travis-ci.org
-
-language: python
-
-python:
- - "3.4"
- - "3.3"
- - "2.7"
-
-before_script:
- - pip install coverage
-
-script: coverage run --source timeoutcontext setup.py test
-
-after_success:
- - pip install coveralls
- - coveralls
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 032f71e..a761ade 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -57,37 +57,22 @@ Get Started!
Ready to contribute? Here's how to set up `timeoutcontext` for local development.
-1. Fork the `timeoutcontext` repo on GitHub.
-2. Clone your fork locally::
+1. Install `link uv `
+2. Install `link just `
+3. Fork the `timeoutcontext` repo on GitHub.
+4. Clone your fork locally
+5. Install the project in a developement virtualenv::
- $ git clone git@github.com:your_name_here/timeoutcontext.git
-
-3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
-
- $ mkvirtualenv timeoutcontext
$ cd timeoutcontext/
- $ pip install -r requirements_test.txt
- $ make develop
-
-4. Create a branch for local development::
-
- $ git checkout -b name-of-your-bugfix-or-feature
-
- Now you can make your changes locally.
-
-5. When you're done making changes, check that your changes pass flake8 doctest and the tests, including testing other Python versions with tox::
-
- $ make lint
- $ make test
- $ make doctest
- $ make test-all
+ $ just install
-6. Commit your changes and push your branch to GitHub::
+6. Make your changes.
+6. Check your changes::
- $ git add .
- $ git commit -m "Your detailed description of your changes."
- $ git push origin name-of-your-bugfix-or-feature
+ $ just check-all
+ $ just test
+7. Commit your changes and push your branch to GitHub.
7. Submit a pull request through the GitHub website.
Pull Request Guidelines
@@ -99,6 +84,3 @@ Before you submit a pull request, check that it meets these guidelines:
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
-3. The pull request should work for Python 2.7 and 3.x. Check
- https://travis-ci.org/AntoineCezar/timeoutcontext/pull_requests
- and make sure that the tests pass for all supported Python versions.
diff --git a/HISTORY.rst b/HISTORY.rst
index a99fb0c..89461cc 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,20 @@
History
-------
+2.0.0 (2025-11-14)
+------------------
+
+* Require python >= 3.9
+* Use uv instead of setuptools
+* Use pytest as test launcher
+* use ruff instead of flake8
+* Use just instead of make
+* Drop tox
+* Add typing
+* Fix `link "ImportError: No module named contextdecorator" `
+* Fix `link "ModuleNotFoundError: No module named 'pkg_resources'" `
+* Use github action instead of travis
+
1.2.0 (2018-03-11)
------------------
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 3e75c03..0000000
--- a/Makefile
+++ /dev/null
@@ -1,84 +0,0 @@
-.PHONY: clean-pyc clean-build docs clean
-BROWSER="firefox"
-
-help:
- @echo "clean - remove all build, test, coverage and Python artifacts"
- @echo "clean-build - remove build artifacts"
- @echo "clean-pyc - remove Python file artifacts"
- @echo "clean-test - remove test and coverage artifacts"
- @echo "lint - check style with flake8"
- @echo "test - run tests quickly with the default Python"
- @echo "test-all - run tests on every Python version with tox"
- @echo "coverage - check code coverage quickly with the default Python"
- @echo "docs - generate Sphinx HTML documentation, including API docs"
- @echo "release - package and upload a release"
- @echo "dist - package"
- @echo "develop - link the package into the active Python's site-packages"
- @echo "install - install the package to the active Python's site-packages"
-
-clean: clean-build clean-pyc clean-test clean-coverage
-
-clean-build:
- rm -fr build/
- rm -fr dist/
- rm -fr .eggs/
- find . -name '*.egg-info' -exec rm -fr {} +
- find . -name '*.egg' -exec rm -f {} +
-
-clean-pyc:
- find . -name '*.pyc' -exec rm -f {} +
- find . -name '*.pyo' -exec rm -f {} +
- find . -name '*~' -exec rm -f {} +
- find . -name '__pycache__' -exec rm -fr {} +
-
-clean-test:
- rm -fr .tox/
-
-clean-coverage:
- rm -f .coverage
- rm -fr htmlcov/
-
-lint:
- flake8 timeoutcontext tests
-
-test:
- python setup.py test
-
-doctest:
- python -m doctest timeoutcontext/_timeout.py
-
-test-all:
- tox
-
-coverage: clean-coverage .coverage
- coverage report -m
-
-coverage-html: clean-coverage .coverage
- coverage html
- $(BROWSER) htmlcov/index.html
-
-.coverage:
- coverage run --source timeoutcontext setup.py test
-
-docs:
- rm -f docs/timeoutcontext.rst
- rm -f docs/modules.rst
- sphinx-apidoc -o docs/ timeoutcontext
- $(MAKE) -C docs clean
- $(MAKE) -C docs html
- $(BROWSER) docs/_build/html/index.html
-
-release: clean
- python setup.py sdist upload
- python setup.py bdist_wheel upload
-
-dist: clean
- python setup.py sdist
- python setup.py bdist_wheel
- ls -l dist
-
-develop: clean
- python setup.py develop
-
-install: clean
- python setup.py install
diff --git a/README.rst b/README.rst
index 7471005..d8384de 100644
--- a/README.rst
+++ b/README.rst
@@ -30,9 +30,7 @@ As a context manager:
import sys
from time import sleep
- from timeoutcontext import timeout
- if sys.version_info < (3, 3):
- from timeoutcontext._timeout import TimeoutError
+ rom timeoutcontext import timeout
try:
with timeout(1):
@@ -47,8 +45,6 @@ As a decorator:
import sys
from time import sleep
from timeoutcontext import timeout
- if sys.version_info < (3, 3):
- from timeoutcontext._timeout import TimeoutError
@timeout(1)
def wait():
diff --git a/docs/Makefile b/docs/Makefile
index 4f36d01..b3e3367 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -47,7 +47,7 @@ help:
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
- rm -rf $(BUILDDIR)/*
+ rm -rf $(BUILDDIR)/* api/
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
diff --git a/docs/conf.py b/docs/conf.py
index 65c124b..70c627c 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,14 +13,14 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys
import os
+import sys
# If extensions (or modules to document with autodoc) are in another
# directory, add these directories to sys.path here. If the directory is
# relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
# Get the project root dir, which is the parent dir of this
cwd = os.getcwd()
@@ -36,27 +36,31 @@
# -- General configuration ---------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.viewcode",
+ "sphinxcontrib.apidoc",
+]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'Timeoutcontext'
-copyright = u'2016, Antoine Cezar'
+project = "Timeoutcontext"
+copyright = "2016, Antoine Cezar"
# The version info for the project you're documenting, acts as replacement
# for |version| and |release|, also used in various other places throughout
@@ -69,126 +73,126 @@
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
-#language = None
+# language = None
# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built
# documents.
-#keep_warnings = False
+# keep_warnings = False
# -- Options for HTML output -------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as
# html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the
# top of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon
# of the docs. This file should be a Windows icon file (.ico) being
# 16x16 or 32x32 pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin
# "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names
# to template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer.
# Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer.
# Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages
# will contain a tag referring to it. The value of this option
# must be the base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'timeoutcontextdoc'
+htmlhelp_basename = "timeoutcontextdoc"
# -- Options for LaTeX output ------------------------------------------
@@ -196,10 +200,8 @@
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
-
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
-
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
@@ -208,30 +210,34 @@
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
- ('index', 'timeoutcontext.tex',
- u'Timeoutcontext Documentation',
- u'Antoine Cezar', 'manual'),
+ (
+ "index",
+ "timeoutcontext.tex",
+ "Timeoutcontext Documentation",
+ "Antoine Cezar",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at
# the top of the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings
# are parts, not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output ------------------------------------
@@ -239,13 +245,11 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'timeoutcontext',
- u'Timeoutcontext Documentation',
- [u'Antoine Cezar'], 1)
+ ("index", "timeoutcontext", "Timeoutcontext Documentation", ["Antoine Cezar"], 1)
]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output ----------------------------------------
@@ -254,22 +258,31 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ('index', 'timeoutcontext',
- u'Timeoutcontext Documentation',
- u'Antoine Cezar',
- 'timeoutcontext',
- 'One line description of project.',
- 'Miscellaneous'),
+ (
+ "index",
+ "timeoutcontext",
+ "Timeoutcontext Documentation",
+ "Antoine Cezar",
+ "timeoutcontext",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
+
+# -- Options for apidoc output ----------------------------------------
+apidoc_module_dir = "../src/"
+# apidoc_output_dir = "api"
+# apidoc_excluded_paths = ["tests"]
+# apidoc_separate_modules = True
diff --git a/docs/index.rst b/docs/index.rst
index 5e4ee10..dbedd66 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,7 +4,7 @@
contain the root `toctree` directive.
Welcome to Timeoutcontext's documentation!
-======================================
+==========================================
Contents:
@@ -16,6 +16,7 @@ Contents:
contributing
authors
history
+ api/modules
Indices and tables
==================
diff --git a/docs/installation.rst b/docs/installation.rst
index 7b1a8b4..a07cf5a 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -4,9 +4,9 @@ Installation
At the command line::
- $ easy_install timeoutcontext
+ $ pip install timeoutcontext
-Or, if you have virtualenvwrapper installed::
+Or, if in a virtualenv installed::
- $ mkvirtualenv timeoutcontext
- $ pip install timeoutcontext
+ $ python -m venv my_venv
+ $ ./my_venv/bin/pip install timeoutcontext
diff --git a/docs/modules.rst b/docs/modules.rst
deleted file mode 100644
index fc70cd4..0000000
--- a/docs/modules.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-timeoutcontext
-==============
-
-.. toctree::
- :maxdepth: 4
-
- timeoutcontext
diff --git a/docs/timeoutcontext.rst b/docs/timeoutcontext.rst
deleted file mode 100644
index 1534380..0000000
--- a/docs/timeoutcontext.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-timeoutcontext package
-======================
-
-Module contents
----------------
-
-.. automodule:: timeoutcontext
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..44bbd85
--- /dev/null
+++ b/justfile
@@ -0,0 +1,100 @@
+# Print help
+help:
+ just --list --unsorted
+
+# Install / update the project's development virtualenv
+install:
+ uv sync --dev
+
+[group('lint')]
+check-all: check-python-formating check-python-typing check-python-docstrings check-toml-formating
+
+# Check python formating
+[group('lint')]
+check-python-formating:
+ uv run ruff check --select=F401,I001 src tests docs
+ uv run ruff format --check src tests docs
+
+# Check python typing
+[group('lint')]
+check-python-typing:
+ uv run mypy --strict src
+ uv run mypy tests
+
+# Check python docstrings
+[group('lint')]
+check-python-docstrings:
+ uv run pytest \
+ --ignore="./tests" \
+ --doctest-modules \
+
+# Check toml formating
+[group('lint')]
+check-toml-formating:
+ uv run tombi format --check
+
+[group('lint')]
+fix-all: fix-python-formating fix-toml-formating
+
+# Fix python formating
+[group('lint')]
+fix-python-formating:
+ uv run ruff check --fix --select=F401,I001 src tests docs
+ uv run ruff format src tests docs
+
+# Fix toml formating
+[group('lint')]
+fix-toml-formating:
+ uv run tombi format
+
+# Run tests
+[group('test')]
+test *ARGS:
+ uv run pytest {{ ARGS }}
+
+# Run and report code coverage
+[group('test')]
+coverage: coverage-run coverage-report
+
+# Run code coverage measurement
+[group('test')]
+coverage-run *ARGS: clean-coverage
+ uv run pytest --cov=timeoutcontext {{ ARGS }}
+
+# Report code coverage
+[group('test')]
+coverage-report *ARGS="-m":
+ uv run coverage report {{ ARGS }}
+
+# Report code coverage in html
+[group('test')]
+coverage-report-html *ARGS:
+ just coverage-report html {{ ARGS }}
+ $(BROWSER) htmlcov/index.html
+
+# Remove coverage artifacts
+[group('test')]
+clean-coverage:
+ rm -f .coverage htmlcov/
+
+# Build documentation
+[group('build')]
+build-docs: clean-docs
+ mkdir -p docs/_static # Avoids missing dir warning
+ make -C docs html SPHINXBUILD="uv run sphinx-build"
+ echo "docs/_build/html/index.html"
+
+# Remove docs build artifacts
+[group('build')]
+clean-docs:
+ make -C docs clean SPHINXBUILD="uv run sphinx-build"
+
+# Build package
+[group('build')]
+build-dist: clean-dist
+ uv build
+
+# Remove package build artifacts
+[group('build')]
+clean-dist:
+ rm -fr dist/
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..be7ae13
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,39 @@
+[project]
+name = "timeoutcontext"
+version = "2.0.0"
+description = "A signal based timeout context manager"
+readme = "README.rst"
+requires-python = ">=3.10"
+license = "BSD-2-Clause"
+authors = [
+ { name = "Antoine Cezar", email = "AntoineCezar@users.noreply.github.com" }
+]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Natural Language :: English",
+ "Programming Language :: Python :: 3.10",
+]
+dependencies = []
+
+[project.urls]
+Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
+Documentation = "http://timeoutcontext.readthedocs.org/"
+Homepage = "https://github.com/AntoineCezar/timeoutcontext"
+Issues = "https://github.com/AntoineCezar/timeoutcontext/issues"
+Repository = "http://timeoutcontext.readthedocs.org/"
+
+[dependency-groups]
+dev = [
+ "coverage>=7.10.7",
+ "mypy>=1.18.2",
+ "pytest>=8.4.2",
+ "ruff>=0.14.4",
+ "sphinxcontrib-apidoc>=0.6.0",
+ "tombi>=0.6.48",
+]
+
+[build-system]
+requires = ["uv_build>=0.9.7,<0.10.0"]
+build-backend = "uv_build"
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index ddd4287..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-wheel==0.23.0
diff --git a/requirements_dev.txt b/requirements_dev.txt
deleted file mode 100644
index eeac76b..0000000
--- a/requirements_dev.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-coverage
-flake8
-tox
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 5e40900..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[wheel]
-universal = 1
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 00fc198..0000000
--- a/setup.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-
-from setuptools import setup
-
-
-with open('README.rst') as readme_file:
- readme = readme_file.read()
-
-with open('HISTORY.rst') as history_file:
- history = history_file.read().replace('.. :changelog:', '')
-
-requirements = [
-]
-
-if sys.version_info < (3, 2):
- requirements.append('contextdecorator')
-
-test_requirements = [
- 'mock'
-]
-
-setup(
- name='timeoutcontext',
- version='1.2.0',
- description="A signal based timeout context manager",
- long_description=readme + '\n\n' + history,
- author="Antoine Cezar",
- author_email='antoine@cezar.fr',
- url='https://github.com/AntoineCezar/timeoutcontext',
- packages=[
- 'timeoutcontext',
- ],
- package_dir={'timeoutcontext':
- 'timeoutcontext'},
- include_package_data=True,
- install_requires=requirements,
- license="BSD",
- zip_safe=False,
- keywords='timeoutcontext',
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Natural Language :: English',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- ],
- test_suite='tests',
- tests_require=test_requirements
-)
diff --git a/src/timeoutcontext/__init__.py b/src/timeoutcontext/__init__.py
new file mode 100755
index 0000000..8b92d9c
--- /dev/null
+++ b/src/timeoutcontext/__init__.py
@@ -0,0 +1,9 @@
+import importlib.metadata
+
+from ._timeout import timeout
+
+__version__ = importlib.metadata.version("timeoutcontext")
+
+__all__ = [
+ "timeout",
+]
diff --git a/src/timeoutcontext/_timeout.py b/src/timeoutcontext/_timeout.py
new file mode 100755
index 0000000..8196a4a
--- /dev/null
+++ b/src/timeoutcontext/_timeout.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+import signal
+from contextlib import ContextDecorator
+from types import FrameType, TracebackType
+from typing import Any
+
+
+def raise_timeout(signum: int, frame: FrameType | None) -> Any:
+ raise TimeoutError()
+
+
+class timeout(ContextDecorator):
+ """Raises TimeoutError when the gien time in seconds elapsed.
+
+ As a context manager:
+
+ >>> import sys
+ >>> from time import sleep
+ >>> from timeoutcontext import timeout
+ >>>
+ >>> try:
+ ... with timeout(1):
+ ... sleep(2)
+ ... except TimeoutError:
+ ... print('timeout')
+ ...
+ timeout
+
+ As a decorator:
+
+ >>> import sys
+ >>> from time import sleep
+ >>> from timeoutcontext import timeout
+ >>>
+ >>> @timeout(1)
+ ... def wait():
+ ... sleep(2)
+ ...
+ >>> try:
+ ... wait()
+ ... except TimeoutError:
+ ... print('timeout')
+ ...
+ timeout
+ """
+
+ def __init__(self, seconds: float | None) -> None:
+ self._seconds = seconds
+
+ def __enter__(self) -> timeout:
+ if self._seconds:
+ self._replace_alarm_handler()
+ signal.setitimer(signal.ITIMER_REAL, self._seconds)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_value: BaseException | None,
+ traceback: TracebackType | None,
+ ) -> None:
+ if self._seconds:
+ self._restore_alarm_handler()
+ signal.alarm(0)
+
+ def _replace_alarm_handler(self) -> None:
+ self._old_alarm_handler = signal.signal(signal.SIGALRM, raise_timeout)
+
+ def _restore_alarm_handler(self) -> None:
+ signal.signal(signal.SIGALRM, self._old_alarm_handler)
diff --git a/src/timeoutcontext/py.typed b/src/timeoutcontext/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/tests/__init__.py b/tests/__init__.py
index 40a96af..e69de29 100755
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/tests/test_timeout.py b/tests/test_timeout.py
index 7e978c3..0ccc870 100755
--- a/tests/test_timeout.py
+++ b/tests/test_timeout.py
@@ -1,33 +1,24 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-import unittest
import time
+import unittest
from contextlib import contextmanager
-
-from mock import patch
+from unittest.mock import patch
from timeoutcontext._timeout import (
raise_timeout,
timeout,
)
-if sys.version_info < (3, 3):
- from timeoutcontext._timeout import TimeoutError
class BaseTestCase(unittest.TestCase):
-
@contextmanager
def assertNotRaises(self, exc_type):
try:
yield None
except exc_type:
- raise self.failureException('{} raised'.format(exc_type.__name__))
+ raise self.failureException("{} raised".format(exc_type.__name__))
class TestTimeoutAsAContextManager(BaseTestCase):
-
def test_it_raise_timeout_exception_when_time_is_out(self):
with self.assertRaises(TimeoutError):
with timeout(1):
@@ -43,67 +34,65 @@ def test_it_does_not_timeout_when_given_time_is_none(self):
with timeout(None):
time.sleep(1)
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_replace_alarm_handler_when_seconds_is_none(self, signal_mock):
- with timeout(None):
- signal_mock.signal.assert_not_called()
+ with timeout(None):
+ signal_mock.signal.assert_not_called()
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_set_alarm_when_seconds_is_none(self, signal_mock):
- with timeout(None):
- signal_mock.alarm.assert_not_called()
+ with timeout(None):
+ signal_mock.alarm.assert_not_called()
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_restore_alarm_handler_when_seconds_is_none(self, signal_mock):
- with timeout(None):
- pass
+ with timeout(None):
+ pass
- signal_mock.signal.assert_not_called()
+ signal_mock.signal.assert_not_called()
def test_it_does_not_timeout_when_given_time_is_zero(self):
with self.assertNotRaises(TimeoutError):
with timeout(0):
time.sleep(1)
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_replace_alarm_handler_when_seconds_is_zero(self, signal_mock):
- with timeout(0):
- signal_mock.signal.assert_not_called()
+ with timeout(0):
+ signal_mock.signal.assert_not_called()
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_set_alarm_when_seconds_is_zero(self, signal_mock):
- with timeout(0):
- signal_mock.setitimer.assert_not_called()
+ with timeout(0):
+ signal_mock.setitimer.assert_not_called()
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_does_not_restore_alarm_handler_when_seconds_is_zero(self, signal_mock):
- with timeout(0):
- pass
+ with timeout(0):
+ pass
- signal_mock.signal.assert_not_called()
+ signal_mock.signal.assert_not_called()
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_replace_alarm_handler_on_enter(self, signal_mock):
with timeout(2):
- signal_mock.signal.assert_called_with(signal_mock.SIGALRM,
- raise_timeout)
+ signal_mock.signal.assert_called_with(signal_mock.SIGALRM, raise_timeout)
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_request_alarm_to_be_sent_in_given_seconds_on_enter(self, signal_mock):
with timeout(2):
signal_mock.setitimer.assert_called_with(signal_mock.ITIMER_REAL, 2)
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_restore_alarm_handler_on_exit(self, signal_mock):
old_alarm_handler = signal_mock.signal()
with timeout(2):
pass
- signal_mock.signal.assert_called_with(signal_mock.SIGALRM,
- old_alarm_handler)
+ signal_mock.signal.assert_called_with(signal_mock.SIGALRM, old_alarm_handler)
- @patch('timeoutcontext._timeout.signal')
+ @patch("timeoutcontext._timeout.signal")
def test_it_resets_alarm_on_exit(self, signal_mock):
with timeout(2):
pass
@@ -112,7 +101,6 @@ def test_it_resets_alarm_on_exit(self, signal_mock):
class TestTimeoutAsADecorator(BaseTestCase):
-
def test_it_raise_timeout_exception_when_time_is_out(self):
@timeout(1)
def decorated():
diff --git a/timeoutcontext/__init__.py b/timeoutcontext/__init__.py
deleted file mode 100755
index 9f7958c..0000000
--- a/timeoutcontext/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-import sys
-import pkg_resources
-
-from ._timeout import timeout
-if sys.version_info < (3, 3):
- from timeoutcontext._timeout import TimeoutError
-
-
-__version__ = pkg_resources.get_distribution(__package__).version
-
-__all__ = [
- 'timeout',
- 'TimeoutError',
-]
diff --git a/timeoutcontext/_timeout.py b/timeoutcontext/_timeout.py
deleted file mode 100755
index 0a137b9..0000000
--- a/timeoutcontext/_timeout.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import signal
-if sys.version_info < (3, 2):
- from contextdecorator import ContextDecorator
-else:
- from contextlib import ContextDecorator
-
-
-if sys.version_info < (3, 3):
- class TimeoutError(Exception):
- pass
-
-
-def raise_timeout(signum, frame):
- raise TimeoutError()
-
-
-class timeout(ContextDecorator):
- """Raises TimeoutError when the gien time in seconds elapsed.
-
- As a context manager:
-
- >>> import sys
- >>> from time import sleep
- >>> from timeoutcontext import timeout
- >>> if sys.version_info < (3, 3):
- ... from timeoutcontext import TimeoutError
- >>>
- >>> try:
- ... with timeout(1):
- ... sleep(2)
- ... except TimeoutError:
- ... print('timeout')
- ...
- timeout
-
- As a decorator:
-
- >>> import sys
- >>> from time import sleep
- >>> from timeoutcontext import timeout
- >>> if sys.version_info < (3, 3):
- ... from timeoutcontext import TimeoutError
- >>>
- >>> @timeout(1)
- ... def wait():
- ... sleep(2)
- ...
- >>> try:
- ... wait()
- ... except TimeoutError:
- ... print('timeout')
- ...
- timeout
- """
-
- def __init__(self, seconds):
- self._seconds = seconds
-
- def __enter__(self):
- if self._seconds:
- self._replace_alarm_handler()
- signal.setitimer(signal.ITIMER_REAL, self._seconds)
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- if self._seconds:
- self._restore_alarm_handler()
- signal.alarm(0)
-
- def _replace_alarm_handler(self):
- self._old_alarm_handler = signal.signal(signal.SIGALRM,
- raise_timeout)
-
- def _restore_alarm_handler(self):
- signal.signal(signal.SIGALRM, self._old_alarm_handler)
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 1f8bf71..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[tox]
-envlist = py27, py3
-
-[testenv]
-setenv =
- PYTHONPATH = {toxinidir}:{toxinidir}/timeoutcontext
-commands = python setup.py test
-deps =
- -r{toxinidir}/requirements.txt