From a330f77bfa44591a1adad4f0afee49daac07f083 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 18 Oct 2024 11:43:37 -0400 Subject: [PATCH] Migrate to a modern Python project where all configuration is in pyproject.toml This includes the following: - Remove setup.cfg - Remove setup.py - Update pyproject.toml with project and tool settings - Add build module to Pipfile for building using pyproject.toml - Add ruff fast linter/formatter to Pipfile - Some very initial minor fixes based on ruff - Update invoke tasks in tasks.py to not use setup.py --- .gitignore | 3 + Pipfile | 3 + cmd2/__init__.py | 1 + cmd2/argparse_completer.py | 2 +- cmd2/cmd2.py | 2 +- pyproject.toml | 213 ++++++++++++++++++++++++++++++++++++- setup.cfg | 57 ---------- setup.py | 110 ------------------- tasks.py | 17 ++- tests/test_completion.py | 4 +- 10 files changed, 236 insertions(+), 176 deletions(-) delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/.gitignore b/.gitignore index 11b71aa3..c3c267ce 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ Pipfile.lock # pyenv version file .python-version + +# uv +uv.lock diff --git a/Pipfile b/Pipfile index b9cdea27..f3b932ff 100644 --- a/Pipfile +++ b/Pipfile @@ -10,11 +10,13 @@ wcwidth = ">=0.1.7" [dev-packages] black = "*" +build = "*" cmd2 = {editable = true,path = "."} cmd2_ext_test = {editable = true,path = "plugins/ext_test"} codecov = "*" doc8 = "*" flake8 = "*" +Flake8-pyproject = "*" gnureadline = {version = "*",sys_platform = "== 'darwin'"} invoke = "*" ipython = "*" @@ -24,6 +26,7 @@ pyreadline3 = {version = ">=3.4",sys_platform = "== 'win32'"} pytest = "*" pytest-cov = "*" pytest-mock = "*" +ruff = "*" sphinx = "*" sphinx-autobuild = "*" sphinx-rtd-theme = "*" diff --git a/cmd2/__init__.py b/cmd2/__init__.py index 8f1f030e..e12827ae 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -106,4 +106,5 @@ 'CompletionMode', 'CustomCompletionSettings', 'Settable', + 'PassThroughException', ] diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index e4601307..9bb86e9f 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -92,7 +92,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: return False # Flags have to start with a prefix character - if not token[0] in parser.prefix_chars: + if token[0] not in parser.prefix_chars: return False # If it looks like a negative number, it is not a flag unless there are negative-number-like flags diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 566d7878..5b033d04 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -5537,7 +5537,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] """ # cmdloop() expects to be run in the main thread to support extensive use of KeyboardInterrupts throughout the # other built-in functions. You are free to override cmdloop, but much of cmd2's features will be limited. - if not threading.current_thread() is threading.main_thread(): + if threading.current_thread() is not threading.main_thread(): raise RuntimeError("cmdloop must be run in the main thread") # Register signal handlers diff --git a/pyproject.toml b/pyproject.toml index de8bb406..6458817c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,87 @@ [build-system] -requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] +requires = [ + "setuptools>=64", + "setuptools-scm>=8", +] +build-backend = "setuptools.build_meta" + +[project] +name = "cmd2" +description = "Quickly build feature-rich and user-friendly interactive command line applications in Python" +readme = "README.md" +license.file = "LICENSE" +authors = [ { name = "cmd2 Contributors" } ] +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries", +] +keywords = [ + "cmd", + "command", + "console", + "prompt", +] +dynamic = [ + "version", +] +dependencies = [ + "pyperclip", + "wcwidth", + 'gnureadline ; platform_system == "Darwin"', + 'pyreadline3 ; platform_system == "Windows"', +] +[project.optional-dependencies] +dev = [ + "codecov", + "doc8", + "flake8", + "Flake8-pyproject", + "black", + "isort", + "invoke", + "mypy", + "nox", + "pytest", + "pytest-cov", + "pytest-mock", + "sphinx", + "sphinx-rtd-theme", + "sphinx-autobuild", + "twine", +] +test = [ + "codecov", + "coverage", + "pytest", + "pytest-cov", + "pytest-mock", +] +validate = [ + "flake8", + "Flake8-pyproject", + "mypy", + "types-setuptools", +] +[project.urls] +documentation = "https://cmd2.readthedocs.io/" +repository = "https://github.com/python-cmd2/cmd2" [tool.black] skip-string-normalization = true @@ -25,3 +107,132 @@ exclude = ''' | htmlcov )/ ''' + +[tool.doc8] +ignore-path = [ + "__pycache__", + "*.egg", + ".git", + ".idea", + ".nox", + ".pytest_cache", + ".tox", + ".venv", + ".vscode", + "build", + "cmd2", + "cmd2.egg-info", + "dist", + "docs/_build", + "examples", + "htmlcov", + "plugins", + "tests", +] +max-line-length = 120 +verbose = 0 + +[tool.flake8] +count = true +ignore = ['E203', 'E704', 'W503'] +max-complexity = 26 +max-line-length = 127 +per-file-ignores = [ + '__init__.py:F401', +] +show-source = true +statistics = true +exclude = [ + "__pycache__", + ".eggs", + "*.eggs", + ".git", + ".idea", + ".nox", + ".pytest_cache", + ".tox", + ".venv", + ".vscode", + "build", + "dist", + "htmlcov", +] + +[tool.isort] +profile = "black" +force_grid_wrap = 0 +include_trailing_comma = true +line_length = 1 +multi_line_output = 3 +use_parentheses = true +skip = [ + "__pycache", + ".eggs", + ".git", + ".idea", + ".nox", + ".tox", + ".venv", + ".vscode", + "cmd2/__init__.py", + "build", + "dist", + "htmlcov", +] + +[tool.mypy] +disallow_incomplete_defs = true +disallow_untyped_calls = true +disallow_untyped_defs = true +exclude = [ + "examples", + "plugins", +] +strict = true +show_column_numbers = true +show_error_codes = true +show_error_context = true +warn_redundant_casts = true +warn_return_any = true +warn_unused_ignores = false +warn_unreachable = true + +[tool.pytest.ini_options] +minversion = "8" +addopts = "--cov=cmd2 --cov-append --cov-report=term --cov-report=html" +testpaths = [ + "tests", +] + +[tool.ruff] +src = ["cmd2"] +fix = true +line-length = 127 +lint.preview = true +exclude=[ + ".git", + "__pycache__", + "dist", + "docs/_build", + "build", +] + +[tool.ruff.format] +docstring-code-format = true +#quote-style = "double" + +[tool.ruff.lint.isort] +force-single-line = true +from-first = false +lines-between-types = 1 +order-by-type = true + +[tool.setuptools.package-data] +"cmd2" = ["py.typed"] + +[tool.setuptools.packages.find] +where = ["cmd2"] +namespaces = false + +[tool.setuptools_scm] + diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5f26578f..00000000 --- a/setup.cfg +++ /dev/null @@ -1,57 +0,0 @@ -[tool:pytest] -testpaths = - tests -addopts = - --cov=cmd2 - --cov-append - --cov-report=term - --cov-report=html - -[flake8] -count = True -ignore = E203,W503,E704 -max-complexity = 26 -max-line-length = 127 -show-source = True -statistics = True -exclude = - .git - __pycache__ - .tox - .nox - .eggs - *.eggs, - .venv, - .idea, - .pytest_cache, - .vscode, - build, - dist, - htmlcov - -[isort] -line_length = 1 -skip = cmd2/__init__.py,.git,__pycache,.tox,.nox,.venv,.eggs,.idea,.vscode,build,dist.htmlcov -profile = black -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true - -[doc8] -ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg,plugins -max-line-length=120 -verbose=0 - -[mypy] -disallow_incomplete_defs = True -disallow_untyped_defs = True -disallow_untyped_calls = True -warn_redundant_casts = True -warn_unused_ignores = False -warn_return_any = True -warn_unreachable = True -strict = True -show_error_context = True -show_column_numbers = True -show_error_codes = True diff --git a/setup.py b/setup.py deleted file mode 100755 index 749f254a..00000000 --- a/setup.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# coding=utf-8 -""" -Setuptools setup file, used to install or test 'cmd2' -""" -import codecs - -from setuptools import ( - setup, -) - -DESCRIPTION = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python" - -with codecs.open('README.md', encoding='utf8') as f: - LONG_DESCRIPTION = f.read() - -CLASSIFIERS = list( - filter( - None, - map( - str.strip, - """ -Development Status :: 5 - Production/Stable -Environment :: Console -Operating System :: OS Independent -Intended Audience :: Developers -Intended Audience :: System Administrators -License :: OSI Approved :: MIT License -Programming Language :: Python -Programming Language :: Python :: 3 -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 -Programming Language :: Python :: 3.10 -Programming Language :: Python :: 3.11 -Programming Language :: Python :: 3.12 -Programming Language :: Python :: Implementation :: CPython -Topic :: Software Development :: Libraries :: Python Modules -""".splitlines(), - ), - ) -) # noqa: E128 - -SETUP_REQUIRES = ['setuptools >= 34.4', 'setuptools_scm >= 3.0'] - -INSTALL_REQUIRES = [ - 'pyperclip >= 1.6', - 'wcwidth >= 0.1.7', -] - -EXTRAS_REQUIRE = { - # Windows also requires pyreadline3 to ensure tab completion works - ":sys_platform=='win32'": ['pyreadline3'], - # Extra dependencies for running unit tests - 'test': [ - "gnureadline; sys_platform=='darwin'", # include gnureadline on macOS to ensure it is available in nox env - 'codecov', - 'coverage', - 'pytest>=4.6', - 'pytest-cov', - 'pytest-mock', - ], - # development only dependencies: install with 'pip install -e .[dev]' - 'dev': [ - 'codecov', - 'doc8', - 'flake8', - 'black', - 'isort', - 'invoke', - 'mypy', - 'nox', - "pytest>=4.6", - 'pytest-cov', - 'pytest-mock', - 'sphinx', - 'sphinx-rtd-theme', - 'sphinx-autobuild', - 'twine>=1.11', - ], - 'validate': [ - 'flake8', - 'mypy', - 'types-setuptools', - ], -} - -PACKAGE_DATA = { - 'cmd2': ['py.typed'], -} - -setup( - name="cmd2", - use_scm_version={'git_describe_command': 'git describe --dirty --tags --long --exclude plugin-*'}, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - long_description_content_type='text/markdown', - classifiers=CLASSIFIERS, - author='Catherine Devlin', - author_email='catherine.devlin@gmail.com', - url='https://github.com/python-cmd2/cmd2', - license='MIT', - platforms=['any'], - package_data=PACKAGE_DATA, - packages=['cmd2'], - keywords='command prompt console cmd', - python_requires='>=3.8', - setup_requires=SETUP_REQUIRES, - install_requires=INSTALL_REQUIRES, - extras_require=EXTRAS_REQUIRE, -) diff --git a/tasks.py b/tasks.py index e0eb16a0..b86af9ed 100644 --- a/tasks.py +++ b/tasks.py @@ -99,8 +99,6 @@ def mypy(context): """Run mypy optional static type checker""" with context.cd(TASK_ROOT_STR): context.run("mypy cmd2") - with context.cd(str(TASK_ROOT / 'examples')): - context.run("mypy decorator_example.py") namespace.add_task(mypy) @@ -310,7 +308,7 @@ def validatetag(context): def sdist(context): """Create a source distribution""" with context.cd(TASK_ROOT_STR): - context.run('python setup.py sdist') + context.run('python -m build --sdist') namespace.add_task(sdist) @@ -320,7 +318,7 @@ def sdist(context): def wheel(context): """Build a wheel distribution""" with context.cd(TASK_ROOT_STR): - context.run('python setup.py bdist_wheel') + context.run('python -m build') namespace.add_task(wheel) @@ -366,3 +364,14 @@ def format(context): namespace.add_task(format) + + +# Black and isort auto-formatting +@invoke.task() +def ruff(context): + """Run ruff auto-formatter and linter""" + with context.cd(TASK_ROOT_STR): + context.run("ruff check") + + +namespace.add_task(ruff) diff --git a/tests/test_completion.py b/tests/test_completion.py index 2eebaaeb..f7e0b585 100755 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -274,7 +274,7 @@ def test_cmd2_help_completion_nomatch(cmd2_app): def test_set_allow_style_completion(cmd2_app): """Confirm that completing allow_style presents AllowStyle strings""" text = '' - line = 'set allow_style'.format(text) + line = 'set allow_style'.format() endidx = len(line) begidx = endidx - len(text) @@ -288,7 +288,7 @@ def test_set_allow_style_completion(cmd2_app): def test_set_bool_completion(cmd2_app): """Confirm that completing a boolean Settable presents true and false strings""" text = '' - line = 'set debug'.format(text) + line = 'set debug'.format() endidx = len(line) begidx = endidx - len(text)