From fe9b1f319c78c92a4a42e4f88ca95a045ae41e21 Mon Sep 17 00:00:00 2001 From: Antoine Cezar Date: Thu, 13 Nov 2025 18:52:15 +0100 Subject: [PATCH 1/3] Fix docs index title --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 5e4ee10..a4e9721 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ contain the root `toctree` directive. Welcome to Timeoutcontext's documentation! -====================================== +========================================== Contents: From 4c28e70323a9d188beceaff1ebf07296a02239e4 Mon Sep 17 00:00:00 2001 From: Antoine Cezar Date: Thu, 13 Nov 2025 17:42:42 +0100 Subject: [PATCH 2/3] Remove sphinx generated files --- docs/modules.rst | 7 ------- docs/timeoutcontext.rst | 10 ---------- 2 files changed, 17 deletions(-) delete mode 100644 docs/modules.rst delete mode 100644 docs/timeoutcontext.rst 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: From e6ac90cd4dd283fb1b183161ef8cc79ea2a3f2e1 Mon Sep 17 00:00:00 2001 From: Antoine Cezar Date: Thu, 13 Nov 2025 17:42:42 +0100 Subject: [PATCH 3/3] Release v2.0.0 - 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 #8 "ImportError: No module named contextdecorator" - Fix #11 "ModuleNotFoundError: No module named 'pkg_resources'" - Use github action instead of travis --- .github/workflows/test.yml | 41 +++++++++ .gitignore | 21 +++++ .travis.yml | 17 ---- CONTRIBUTING.rst | 40 +++------ HISTORY.rst | 14 ++++ Makefile | 84 ------------------- README.rst | 6 +- docs/Makefile | 2 +- docs/conf.py | 147 ++++++++++++++++++--------------- docs/index.rst | 1 + docs/installation.rst | 8 +- justfile | 100 ++++++++++++++++++++++ pyproject.toml | 39 +++++++++ requirements.txt | 1 - requirements_dev.txt | 3 - setup.cfg | 2 - setup.py | 53 ------------ src/timeoutcontext/__init__.py | 9 ++ src/timeoutcontext/_timeout.py | 71 ++++++++++++++++ src/timeoutcontext/py.typed | 0 tests/__init__.py | 1 - tests/test_timeout.py | 70 +++++++--------- timeoutcontext/__init__.py | 15 ---- timeoutcontext/_timeout.py | 78 ----------------- tox.ini | 9 -- 25 files changed, 422 insertions(+), 410 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore delete mode 100644 .travis.yml delete mode 100644 Makefile create mode 100644 justfile create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 requirements_dev.txt delete mode 100644 setup.cfg delete mode 100755 setup.py create mode 100755 src/timeoutcontext/__init__.py create mode 100755 src/timeoutcontext/_timeout.py create mode 100644 src/timeoutcontext/py.typed delete mode 100755 timeoutcontext/__init__.py delete mode 100755 timeoutcontext/_timeout.py delete mode 100644 tox.ini 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 a4e9721..dbedd66 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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/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