From bccec94f3ef4a5e0aa63aa01ea138cc91f402554 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 13:49:25 +0100 Subject: [PATCH 01/10] tox: drop python versions below 3.8 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 41830fe..0749f80 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,34,35,36,37,38,py,py3}, checkqa +envlist = py{38,39,310,311,py3}, checkqa [testenv] deps = From 5f525e7989be8af69bf20eca9d4768c9f708a419 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 13:49:12 +0100 Subject: [PATCH 02/10] ci: replace travis with github actions --- .github/workflows/lint.yml | 50 ++++++++++++++++++++ .github/workflows/release.yml | 28 +++++++++++ .github/workflows/tests.yaml | 85 +++++++++++++++++++++++++++++++++ .travis.yml | 89 ----------------------------------- 4 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..6c99590 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,50 @@ +name: lint + +on: + push: + branches: + - "master" + - "release-*" + pull_request: + branches: + - "master" + - "release-*" + workflow_dispatch: + +jobs: + tests: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, windows-latest, macos-latest] + pyv: ["3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.pyv }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.pyv }} + + - name: set PY_CACHE_KEY + run: echo "PY_CACHE_KEY=$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV + - name: Cache .tox + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.tox/checkqa + key: "tox-lint|${{ matrix.os }}|${{ env.PY_CACHE_KEY }}|${{ hashFiles('tox.ini', 'setup.*') }}" + + - name: Update pip/setuptools + run: | + pip install -U pip setuptools + + - name: Install tox + run: python -m pip install tox + + - name: Version information + run: python -m pip list + + - name: Lint + run: tox -v -e checkqa diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..181e70a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Publish package to PyPI + +on: + release: + types: + - published + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Install requirements + run: | + pip install -U pip twine build + - name: Build + run: python -m build + - run: check-manifest + - run: twine check dist/* + - name: Publish to PyPI + env: + TWINE_USERNAME: "__token__" + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload dist/* diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..d20936e --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,85 @@ +name: Tests + +on: + push: + branches: + - "master" + - "release-*" + pull_request: + branches: + - "master" + - "release-*" + workflow_dispatch: + +env: + PYTEST_ADDOPTS: "-vv --cov-report=xml:coverage-ci.xml" + PIP_DISABLE_PIP_VERSION_CHECK: true + +defaults: + run: + shell: bash + +jobs: + tests: + name: Tests + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, windows-latest, macos-latest] + python: ["3.8"] + tox_env: ["coverage"] + include: + - tox_env: "py39-coverage" + os: ubuntu-20.04 + python: "3.9" + - tox_env: "py310-coverage" + python: "3.10" + os: ubuntu-20.04 + - tox_env: "py311-coverage" + python: "3.11" + os: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: set PY_CACHE_KEY + run: echo "PY_CACHE_KEY=$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV + - name: Cache .tox + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.tox/${{ matrix.tox_env }} + key: "tox|${{ matrix.os }}|${{ matrix.tox_env }}|${{ env.PY_CACHE_KEY }}|${{ hashFiles('tox.ini', 'setup.*') }}" + + - name: Update tools and print info + run: | + pip install -U pip setuptools virtualenv + pip list + + - name: Install tox + run: pip install tox + + - name: Setup tox environment + id: setup_tox + run: tox --notest -v -e ${{ matrix.tox_env }} + + - name: Run tests + run: | + python -m tox -v -e ${{ matrix.tox_env }} + + - name: Report coverage + if: always() && (contains(matrix.tox_env, 'coverage') && (steps.setup_tox.outcome == 'success')) + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: ./coverage-ci.xml + flags: ${{ runner.os }} + name: ${{ matrix.tox_env }} + fail_ci_if_error: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 257243e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,89 +0,0 @@ -dist: xenial -language: python - -env: - global: - - PYTEST_ADDOPTS="-vv --cov --cov-report=xml" - -stages: - - name: test - if: tag IS NOT present - - name: release - if: tag IS present - -jobs: - include: - - os: windows - language: shell - env: - - PATH=/c/Python38:/c/Python38/Scripts:$PATH - - TOXENV=py38-coverage - before_install: - - choco install --no-progress python - - - os: osx - osx_image: xcode10.2 - language: generic - env: TOXENV=py37-coverage - before_install: - - ln -sfn "$(which python3)" /usr/local/bin/python - - python -V - - test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37 - - - python: '2.7' - env: TOXENV=py27-coverage - - python: '3.4' - env: TOXENV=py34-coverage - - python: '3.5' - env: TOXENV=py35-coverage - - python: '3.6' - env: TOXENV=py36-coverage - - python: '3.7' - env: TOXENV=py37-coverage - - python: '3.8' - env: TOXENV=py38-coverage - - python: 'pypy' - env: TOXENV=pypy-coverage - - python: 'pypy3' - env: TOXENV=pypy3-coverage - - - python: '3.7' - env: TOXENV=checkqa - - - stage: release - script: skip - install: skip - after_success: true - env: - deploy: - provider: pypi - distributions: sdist bdist_wheel - user: __token__ - password: - secure: "GCyMei2qFzd9AN0EmT9AGqO0zQFtab8Yff4O9zmpDn34hk7TRhQdAHqPXTj0GovYjN783y31jQdVPbEsFiXUAtEu6rfOBwTtVvNCHGVdDQ0nhZFZVwYD3NfhaV1UCq/ahs5AdUEARAPbR8lviH4PMByrMs3x+ul+bHfZ70QlD1xvC/wlkZ+C/FWc5WiKbkqM5W/CUJoOnX7C5Cx/cI/VZI8X3N77t1J7fW4CEvk3nvU9CW8gDCcuJhq4Hr4oW85PsSCcJagwo1im3WSK+5rNTFlihoE1kGYtrDlWFrNFruAwobk9LSjk+GKTZqD6PFxilON/hiKavxHNYEBwwnfvpDTK87lQHU1LuLOjNMDn8pPOj8uvvKrx9y2BgtFcJzEq9oudtJOKYcxoVpLm/tmqB4QzlTWpOKXk769Sk7lZM9n+psu6wtAd1X8GH5qFon5z0YnNmaNFew5bKs3R/L3Eav1OyskA0zi4f/h8s98apnY4AGX7ul/xxoJhp3OXiSN75fMI6SUiNZLFgRUmFNqJ6pzCqHDbV0y60EeH+5BBLIdKc/D+YsuqDZYAjkN4ze6JVzGtxSSK9tuZyKJJ7zPXT1qdZxXRF0XOHRcVTxl+tNBncCmVvrJmf8QvQ6FteShZqTu3qfWWAubCOGVrxr0aVVZkYR6izNrAsp+J2/ETs5Q=" - on: - tags: true - repo: pdbpp/fancycompleter - -install: - - pip install tox==3.12.1 - # NOTE: need to upgrade virtualenv to allow "Direct url requirement" with - # installation in tox. - - pip install virtualenv==16.6.0 - -script: - - tox - -after_script: - - | - if [[ "${TOXENV%-coverage}" != "$TOXENV" ]]; then - curl --version - curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash > codecov.sh - bash codecov.sh -Z -X fix -f coverage.xml -n $TOXENV -F "${TRAVIS_OS_NAME}" - fi - -# Only master and releases. PRs are used otherwise. -branches: - only: - - master - - /^\d+\.\d+(\.\d+)?(-\S*)?$/ From 35ce95750d0613e0de1c1e8201a25de66b0b84c2 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 13:50:16 +0100 Subject: [PATCH 03/10] setup.py: drop python versions below 3.8 --- setup.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index 7d90960..31dfe36 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,32 @@ from setuptools import setup setup( - name='fancycompleter', + name="fancycompleter", setup_requires="setupmeta", versioning="devcommit", maintainer="Daniel Hahler", - url='https://github.com/pdbpp/fancycompleter', - author='Antonio Cuni', - author_email='anto.cuni@gmail.com', - py_modules=['fancycompleter'], - license='BSD', - description='colorful TAB completion for Python prompt', - keywords='rlcompleter prompt tab color completion', + url="https://github.com/pdbpp/fancycompleter", + author="Antonio Cuni", + author_email="anto.cuni@gmail.com", + py_modules=["fancycompleter"], + license="BSD", + description="colorful TAB completion for Python prompt", + keywords="rlcompleter prompt tab color completion", classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", "License :: OSI Approved :: BSD License", - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Intended Audience :: Developers", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Topic :: Utilities", - ], + ], install_requires=[ "pyrepl @ git+https://github.com/pdbpp/pyrepl@master#egg=pyrepl", "pyreadline;platform_system=='Windows'", - ] + ], ) From d9f4ea6128e71a03c85a1dca477b02e202885f66 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:22:08 +0100 Subject: [PATCH 04/10] add gitignore --- .gitignore | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a790198 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +*.manifest +*.spec +pip-log.txt +pip-delete-this-directory.txt +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +*.mo +*.pot +*.log + +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +.mypy_cache/ +.dmypy.json +dmypy.json + +.ruff_cache/ From 38a43e1ac122a4a7d620c17dbd2986cd8aa3be6d Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:24:39 +0100 Subject: [PATCH 05/10] tox: use ruff as linter --- pyproject.toml | 19 +++++++++++++++++++ tox.ini | 7 ++----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..942e860 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +ignore = ["F401"] diff --git a/tox.ini b/tox.ini index 0749f80..db2c1c7 100644 --- a/tox.ini +++ b/tox.ini @@ -11,8 +11,8 @@ setenv = [testenv:checkqa] deps = - flake8 -commands = flake8 setup.py fancycompleter.py testing + ruff +commands = ruff check setup.py fancycompleter.py testing [pytest] addopts = -ra @@ -28,6 +28,3 @@ source = . */lib/python*/site-packages/ */pypy*/site-packages/ *\Lib\site-packages\ - -[flake8] -max-line-length = 88 From 70b728f06199a1880f64e6b14ef6ddd3aa486920 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:25:33 +0100 Subject: [PATCH 06/10] format with black/isort --- fancycompleter.py | 200 ++++++++++++++++++++------------------- misc/fancycompleterrc.py | 5 +- 2 files changed, 108 insertions(+), 97 deletions(-) diff --git a/fancycompleter.py b/fancycompleter.py index f6319ca..de2b8a4 100644 --- a/fancycompleter.py +++ b/fancycompleter.py @@ -1,13 +1,12 @@ """ fancycompleter: colorful TAB completion for Python prompt """ -from __future__ import with_statement -from __future__ import print_function +from __future__ import print_function, with_statement +import os.path import rlcompleter import sys import types -import os.path from itertools import count PY3K = sys.version_info[0] >= 3 @@ -33,7 +32,6 @@ class LazyVersion(object): - def __init__(self, pkg): self.pkg = pkg self.__version = None @@ -46,15 +44,15 @@ def version(self): def _load_version(self): try: - from pkg_resources import get_distribution, DistributionNotFound + from pkg_resources import DistributionNotFound, get_distribution except ImportError: - return 'N/A' + return "N/A" # try: return get_distribution(self.pkg).version except DistributionNotFound: # package is not installed - return 'N/A' + return "N/A" def __repr__(self): return self.version @@ -72,22 +70,22 @@ def __ne__(self, other): class Color: - black = '30' - darkred = '31' - darkgreen = '32' - brown = '33' - darkblue = '34' - purple = '35' - teal = '36' - lightgray = '37' - darkgray = '30;01' - red = '31;01' - green = '32;01' - yellow = '33;01' - blue = '34;01' - fuchsia = '35;01' - turquoise = '36;01' - white = '37;01' + black = "30" + darkred = "31" + darkgreen = "32" + brown = "33" + darkblue = "34" + purple = "35" + teal = "36" + lightgray = "37" + darkgray = "30;01" + red = "31;01" + green = "32;01" + yellow = "33;01" + blue = "34;01" + fuchsia = "35;01" + turquoise = "36;01" + white = "37;01" @classmethod def set(cls, color, string): @@ -95,14 +93,13 @@ def set(cls, color, string): color = getattr(cls, color) except AttributeError: pass - return '\x1b[%sm%s\x1b[00m' % (color, string) + return "\x1b[%sm%s\x1b[00m" % (color, string) class DefaultConfig: - consider_getitems = True prefer_pyrepl = True - use_colors = 'auto' + use_colors = "auto" readline = None # set by setup() using_pyrepl = False # overwritten by find_pyrepl @@ -112,13 +109,10 @@ class DefaultConfig: type((42).__add__): Color.turquoise, type(int.__add__): Color.turquoise, type(str.replace): Color.turquoise, - types.FunctionType: Color.blue, types.BuiltinFunctionType: Color.blue, - ClassType: Color.fuchsia, type: Color.fuchsia, - types.ModuleType: Color.teal, type(None): Color.lightgray, str: Color.green, @@ -136,12 +130,12 @@ class DefaultConfig: def find_pyrepl(self): try: - import pyrepl.readline import pyrepl.completing_reader + import pyrepl.readline except ImportError: return None self.using_pyrepl = True - if hasattr(pyrepl.completing_reader, 'stripcolor'): + if hasattr(pyrepl.completing_reader, "stripcolor"): # modern version of pyrepl return pyrepl.readline, True else: @@ -150,11 +144,12 @@ def find_pyrepl(self): def find_pyreadline(self): try: import readline + import pyreadline # noqa: F401 # XXX: needed really? from pyreadline.modes import basemode except ImportError: return None - if hasattr(basemode, 'stripcolor'): + if hasattr(basemode, "stripcolor"): # modern version of pyreadline; see: # https://github.com/pyreadline/pyreadline/pull/48 return readline, True @@ -166,22 +161,23 @@ def find_best_readline(self): result = self.find_pyrepl() if result: return result - if sys.platform == 'win32': + if sys.platform == "win32": result = self.find_pyreadline() if result: return result import readline + return readline, False # by default readline does not support colors def setup(self): self.readline, supports_color = self.find_best_readline() - if self.use_colors == 'auto': + if self.use_colors == "auto": self.use_colors = supports_color def my_execfile(filename, mydict): with open(filename) as f: - code = compile(f.read(), filename, 'exec') + code = compile(f.read(), filename, "exec") exec(code, mydict) @@ -193,7 +189,7 @@ def get_config(self, Config): if Config is not None: return Config() # try to load config from the ~/filename file - filename = '~/' + self.config_filename + filename = "~/" + self.config_filename rcfile = os.path.normpath(os.path.expanduser(filename)) if not os.path.exists(rcfile): return self.DefaultConfig() @@ -222,7 +218,10 @@ def get_config(self, Config): tb = tb.tb_next err_fname = tb.tb_frame.f_code.co_filename err_lnum = tb.tb_lineno - err += " (%s:%d)" % (err_fname, err_lnum,) + err += " (%s:%d)" % ( + err_fname, + err_lnum, + ) sys.stderr.write("** %s **\n" % err) return self.DefaultConfig() @@ -237,25 +236,25 @@ class Completer(rlcompleter.Completer, ConfigurableClass): """ DefaultConfig = DefaultConfig - config_filename = '.fancycompleterrc.py' + config_filename = ".fancycompleterrc.py" def __init__(self, namespace=None, Config=None): rlcompleter.Completer.__init__(self, namespace) self.config = self.get_config(Config) self.config.setup() readline = self.config.readline - if hasattr(readline, '_setup'): + if hasattr(readline, "_setup"): # this is needed to offer pyrepl a better chance to patch # raw_input. Usually, it does at import time, but is we are under # pytest with output captured, at import time we don't have a # terminal and thus the raw_input hook is not installed readline._setup() if self.config.use_colors: - readline.parse_and_bind('set dont-escape-ctrl-chars on') + readline.parse_and_bind("set dont-escape-ctrl-chars on") if self.config.consider_getitems: delims = readline.get_completer_delims() - delims = delims.replace('[', '') - delims = delims.replace(']', '') + delims = delims.replace("[", "") + delims = delims.replace("]", "") readline.set_completer_delims(delims) def complete(self, text, state): @@ -264,7 +263,7 @@ def complete(self, text, state): http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496812 """ if text == "": - return ['\t', None][state] + return ["\t", None][state] else: return rlcompleter.Completer.complete(self, text, state) @@ -275,6 +274,7 @@ def _callable_postfix(self, val, word): def global_matches(self, text): import keyword + names = rlcompleter.Completer.global_matches(self, text) prefix = commonprefix(names) if prefix and prefix != text: @@ -283,7 +283,7 @@ def global_matches(self, text): names.sort() values = [] for name in names: - clean_name = name.rstrip(': ') + clean_name = name.rstrip(": ") if clean_name in keyword.kwlist: values.append(None) else: @@ -296,8 +296,8 @@ def global_matches(self, text): return names def attr_matches(self, text): - expr, attr = text.rsplit('.', 1) - if '(' in expr or ')' in expr: # don't call functions + expr, attr = text.rsplit(".", 1) + if "(" in expr or ")" in expr: # don't call functions return [] try: thisobject = eval(expr, self.namespace) @@ -308,23 +308,22 @@ def attr_matches(self, text): words = set(dir(thisobject)) words.discard("__builtins__") - if hasattr(thisobject, '__class__'): - words.add('__class__') + if hasattr(thisobject, "__class__"): + words.add("__class__") words.update(rlcompleter.get_class_members(thisobject.__class__)) names = [] values = [] n = len(attr) - if attr == '': - noprefix = '_' - elif attr == '_': - noprefix = '__' + if attr == "": + noprefix = "_" + elif attr == "_": + noprefix = "__" else: noprefix = None words = sorted(words) while True: for word in words: - if (word[:n] == attr and - not (noprefix and word[:n+1] == noprefix)): + if word[:n] == attr and not (noprefix and word[: n + 1] == noprefix): try: val = getattr(thisobject, word) except Exception: @@ -334,14 +333,14 @@ def attr_matches(self, text): # this is needed because pyrepl doesn't like unicode # completions: as soon as it finds something which is not str, # it stops. - word = word.encode('utf-8') + word = word.encode("utf-8") names.append(word) values.append(val) if names or not noprefix: break - if noprefix == '_': - noprefix = '__' + if noprefix == "_": + noprefix = "__" else: noprefix = None @@ -349,26 +348,27 @@ def attr_matches(self, text): return [] if len(names) == 1: - return ['%s.%s' % (expr, names[0])] # only option, no coloring. + return ["%s.%s" % (expr, names[0])] # only option, no coloring. prefix = commonprefix(names) if prefix and prefix != attr: - return ['%s.%s' % (expr, prefix)] # autocomplete prefix + return ["%s.%s" % (expr, prefix)] # autocomplete prefix if self.config.use_colors: return self.color_matches(names, values) if prefix: - names += [' '] + names += [" "] return names def color_matches(self, names, values): - matches = [self.color_for_obj(i, name, obj) - for i, name, obj - in izip(count(), names, values)] + matches = [ + self.color_for_obj(i, name, obj) + for i, name, obj in izip(count(), names, values) + ] # We add a space at the end to prevent the automatic completion of the # common prefix, which is the ANSI ESCAPE sequence. - return matches + [' '] + return matches + [" "] def color_for_obj(self, i, name, value): t = type(value) @@ -379,19 +379,18 @@ def color_for_obj(self, i, name, value): color = _color break else: - color = '00' + color = "00" # hack: prepend an (increasing) fake escape sequence, # so that readline can sort the matches correctly. - return '\x1b[%03d;00m' % i + Color.set(color, name) + return "\x1b[%03d;00m" % i + Color.set(color, name) -def commonprefix(names, base=''): - """ return the common prefix of all 'names' starting with 'base' - """ +def commonprefix(names, base=""): + """return the common prefix of all 'names' starting with 'base'""" if base: names = [x for x in names if x.startswith(base)] if not names: - return '' + return "" s1 = min(names) s2 = max(names) for i, c in enumerate(s1): @@ -403,12 +402,12 @@ def commonprefix(names, base=''): def has_leopard_libedit(config): # Detect if we are using Leopard's libedit. # Adapted from IPython's rlineimpl.py. - if config.using_pyrepl or sys.platform != 'darwin': + if config.using_pyrepl or sys.platform != "darwin": return False # Official Python docs state that 'libedit' is in the docstring for # libedit readline. - return config.readline.__doc__ and 'libedit' in config.readline.__doc__ + return config.readline.__doc__ and "libedit" in config.readline.__doc__ def setup(): @@ -420,33 +419,37 @@ def setup(): if has_leopard_libedit(completer.config): readline.parse_and_bind("bind ^I rl_complete") else: - readline.parse_and_bind('tab: complete') + readline.parse_and_bind("tab: complete") readline.set_completer(completer.complete) return completer def interact_pyrepl(): import sys + from pyrepl import readline from pyrepl.simple_interact import run_multiline_interactive_console - sys.modules['readline'] = readline + + sys.modules["readline"] = readline run_multiline_interactive_console() def setup_history(completer, persist_history): import atexit + readline = completer.config.readline # if isinstance(persist_history, (str, unicode)): filename = persist_history else: - filename = '~/.history.py' + filename = "~/.history.py" filename = os.path.expanduser(filename) if os.path.isfile(filename): readline.read_history_file(filename) def save_history(): readline.write_history_file(filename) + atexit.register(save_history) @@ -471,10 +474,11 @@ def interact(persist_history=None): By default, pyrepl is preferred and automatically used if found. """ import sys + completer = setup() if persist_history: setup_history(completer, persist_history) - if completer.config.using_pyrepl and '__pypy__' not in sys.builtin_module_names: + if completer.config.using_pyrepl and "__pypy__" not in sys.builtin_module_names: # if we are on PyPy, we don't need to run a "fake" interpeter, as the # standard one is fake enough :-) interact_pyrepl() @@ -487,59 +491,65 @@ class Installer(object): """ def __init__(self, basepath, force): - fname = os.path.join(basepath, 'python_startup.py') + fname = os.path.join(basepath, "python_startup.py") self.filename = os.path.expanduser(fname) self.force = force def check(self): - PYTHONSTARTUP = os.environ.get('PYTHONSTARTUP') + PYTHONSTARTUP = os.environ.get("PYTHONSTARTUP") if PYTHONSTARTUP: - return 'PYTHONSTARTUP already defined: %s' % PYTHONSTARTUP + return "PYTHONSTARTUP already defined: %s" % PYTHONSTARTUP if os.path.exists(self.filename): - return '%s already exists' % self.filename + return "%s already exists" % self.filename def install(self): import textwrap + error = self.check() if error and not self.force: print(error) - print('Use --force to overwrite.') + print("Use --force to overwrite.") return False - with open(self.filename, 'w') as f: - f.write(textwrap.dedent(""" + with open(self.filename, "w") as f: + f.write( + textwrap.dedent( + """ import fancycompleter fancycompleter.interact(persist_history=True) - """)) + """ + ) + ) self.set_env_var() return True def set_env_var(self): - if sys.platform == 'win32': + if sys.platform == "win32": os.system('SETX PYTHONSTARTUP "%s"' % self.filename) - print('%PYTHONSTARTUP% set to', self.filename) + print("%PYTHONSTARTUP% set to", self.filename) else: - print('startup file written to', self.filename) - print('Append this line to your ~/.bashrc:') - print(' export PYTHONSTARTUP=%s' % self.filename) + print("startup file written to", self.filename) + print("Append this line to your ~/.bashrc:") + print(" export PYTHONSTARTUP=%s" % self.filename) + +if __name__ == "__main__": -if __name__ == '__main__': def usage(): - print('Usage: python -m fancycompleter install [-f|--force]') + print("Usage: python -m fancycompleter install [-f|--force]") sys.exit(1) cmd = None force = False for item in sys.argv[1:]: - if item in ('install',): + if item in ("install",): cmd = item - elif item in ('-f', '--force'): + elif item in ("-f", "--force"): force = True else: usage() # - if cmd == 'install': - installer = Installer('~', force) + if cmd == "install": + installer = Installer("~", force) installer.install() else: usage() diff --git a/misc/fancycompleterrc.py b/misc/fancycompleterrc.py index eab209c..d20589b 100644 --- a/misc/fancycompleterrc.py +++ b/misc/fancycompleterrc.py @@ -1,5 +1,6 @@ from fancycompleter import DefaultConfig + class Config(DefaultConfig): - prefer_pyrepl = False # force fancycompleter to use the standard readline - use_colors = True # you need a patched libreadline for this + prefer_pyrepl = False # force fancycompleter to use the standard readline + use_colors = True # you need a patched libreadline for this From 7a4e7334c3b8b59905578a7c89799ba4e37ccedc Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:27:59 +0100 Subject: [PATCH 07/10] get rid of python2 code --- fancycompleter.py | 52 ++++++++++------------------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/fancycompleter.py b/fancycompleter.py index de2b8a4..7d73bd6 100644 --- a/fancycompleter.py +++ b/fancycompleter.py @@ -1,37 +1,18 @@ """ fancycompleter: colorful TAB completion for Python prompt """ -from __future__ import print_function, with_statement +import contextlib import os.path import rlcompleter import sys import types from itertools import count -PY3K = sys.version_info[0] >= 3 +izip = zip -# python3 compatibility -# --------------------- -try: - from itertools import izip -except ImportError: - izip = zip -try: - from types import ClassType -except ImportError: - ClassType = type - -try: - unicode -except NameError: - unicode = str - -# ---------------------- - - -class LazyVersion(object): +class LazyVersion: def __init__(self, pkg): self.pkg = pkg self.__version = None @@ -89,11 +70,9 @@ class Color: @classmethod def set(cls, color, string): - try: + with contextlib.suppress(AttributeError): color = getattr(cls, color) - except AttributeError: - pass - return "\x1b[%sm%s\x1b[00m" % (color, string) + return f"\x1b[{color}m{string}\x1b[00m" class DefaultConfig: @@ -200,7 +179,7 @@ def get_config(self, Config): except Exception as exc: import traceback - sys.stderr.write("** error when importing %s: %r **\n" % (filename, exc)) + sys.stderr.write(f"** error when importing {filename}: {exc!r} **\n") traceback.print_tb(sys.exc_info()[2]) return self.DefaultConfig() @@ -212,7 +191,7 @@ def get_config(self, Config): try: return Config() except Exception as exc: - err = "error when setting up Config from %s: %s" % (filename, exc) + err = f"error when setting up Config from {filename}: {exc}" tb = sys.exc_info()[2] if tb and tb.tb_next: tb = tb.tb_next @@ -329,30 +308,21 @@ def attr_matches(self, text): except Exception: val = None # Include even if attribute not set - if not PY3K and isinstance(word, unicode): - # this is needed because pyrepl doesn't like unicode - # completions: as soon as it finds something which is not str, - # it stops. - word = word.encode("utf-8") - names.append(word) values.append(val) if names or not noprefix: break - if noprefix == "_": - noprefix = "__" - else: - noprefix = None + noprefix = "__" if noprefix == "_" else None if not names: return [] if len(names) == 1: - return ["%s.%s" % (expr, names[0])] # only option, no coloring. + return [f"{expr}.{names[0]}"] # only option, no coloring. prefix = commonprefix(names) if prefix and prefix != attr: - return ["%s.%s" % (expr, prefix)] # autocomplete prefix + return [f"{expr}.{prefix}"] # autocomplete prefix if self.config.use_colors: return self.color_matches(names, values) @@ -485,7 +455,7 @@ def interact(persist_history=None): sys.exit() -class Installer(object): +class Installer: """ Helper to install fancycompleter in PYTHONSTARTUP """ From 4bc88eef09fcd81d736b83a8c0c8aaff1010c390 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:35:03 +0100 Subject: [PATCH 08/10] misc cleanup --- fancycompleter.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/fancycompleter.py b/fancycompleter.py index 7d73bd6..c602730 100644 --- a/fancycompleter.py +++ b/fancycompleter.py @@ -8,6 +8,7 @@ import sys import types from itertools import count +from typing import Optional izip = zip @@ -90,12 +91,10 @@ class DefaultConfig: type(str.replace): Color.turquoise, types.FunctionType: Color.blue, types.BuiltinFunctionType: Color.blue, - ClassType: Color.fuchsia, type: Color.fuchsia, types.ModuleType: Color.teal, type(None): Color.lightgray, str: Color.green, - unicode: Color.green, int: Color.yellow, float: Color.yellow, complex: Color.yellow, @@ -380,10 +379,8 @@ def has_leopard_libedit(config): return config.readline.__doc__ and "libedit" in config.readline.__doc__ -def setup(): - """ - Install fancycompleter as the default completer for readline. - """ +def setup() -> Completer: + """Install fancycompleter as the default completer for readline.""" completer = Completer() readline = completer.config.readline if has_leopard_libedit(completer.config): @@ -404,15 +401,13 @@ def interact_pyrepl(): run_multiline_interactive_console() -def setup_history(completer, persist_history): +def setup_history(completer, persist_history: str): import atexit readline = completer.config.readline - # - if isinstance(persist_history, (str, unicode)): - filename = persist_history - else: - filename = "~/.history.py" + + filename = persist_history if isinstance(persist_history, str) else "~/.history.py" + filename = os.path.expanduser(filename) if os.path.isfile(filename): readline.read_history_file(filename) @@ -423,7 +418,7 @@ def save_history(): atexit.register(save_history) -def interact(persist_history=None): +def interact(persist_history: Optional[str] = None): """ Main entry point for fancycompleter: run an interactive Python session after installing fancycompleter. @@ -446,7 +441,7 @@ def interact(persist_history=None): import sys completer = setup() - if persist_history: + if persist_history is not None: setup_history(completer, persist_history) if completer.config.using_pyrepl and "__pypy__" not in sys.builtin_module_names: # if we are on PyPy, we don't need to run a "fake" interpeter, as the @@ -456,9 +451,7 @@ def interact(persist_history=None): class Installer: - """ - Helper to install fancycompleter in PYTHONSTARTUP - """ + """Helper to install fancycompleter in PYTHONSTARTUP""" def __init__(self, basepath, force): fname = os.path.join(basepath, "python_startup.py") From a7006b24e4039892c61de53702ab749a615ebbd3 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:40:05 +0100 Subject: [PATCH 09/10] tests: format with black/isort --- testing/conftest.py | 1 - testing/test_configurableclass.py | 20 +-- testing/test_fancycompleter.py | 201 ++++++++++++++++-------------- 3 files changed, 115 insertions(+), 107 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index 7951f24..4cd4765 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,6 +1,5 @@ import pytest - pytest_plugins = ["pytester"] diff --git a/testing/test_configurableclass.py b/testing/test_configurableclass.py index a10340c..1d6b250 100644 --- a/testing/test_configurableclass.py +++ b/testing/test_configurableclass.py @@ -44,12 +44,14 @@ class MyCfg(ConfigurableClass): assert isinstance(cfg.get_config(None), DefaultCfg) out, err = capsys.readouterr() assert out == "" - LineMatcher(err.splitlines()).fnmatch_lines([ - "[*][*] error when importing ~/.mycfg: Exception('my_execfile_exc'*) [*][*]", - ' File */fancycompleter.py", line *, in get_config', - ' my_execfile(rcfile, mydict)', - ' File */fancycompleter.py", line *, in my_execfile', - ' exec(code, mydict)', - ' File "*/test_config0/.mycfg", line 1, in ', - " raise Exception('my_execfile_exc')", - ]) + LineMatcher(err.splitlines()).fnmatch_lines( + [ + "[*][*] error when importing ~/.mycfg: Exception('my_execfile_exc'*) [*][*]", + ' File */fancycompleter.py", line *, in get_config', + " my_execfile(rcfile, mydict)", + ' File */fancycompleter.py", line *, in my_execfile', + " exec(code, mydict)", + ' File "*/test_config0/.mycfg", line 1, in ', + " raise Exception('my_execfile_exc')", + ] + ) diff --git a/testing/test_fancycompleter.py b/testing/test_fancycompleter.py index 4c2d3c8..4b892d6 100644 --- a/testing/test_fancycompleter.py +++ b/testing/test_fancycompleter.py @@ -1,7 +1,13 @@ import sys -from fancycompleter import (Color, Completer, DefaultConfig, Installer, - LazyVersion, commonprefix) +from fancycompleter import ( + Color, + Completer, + DefaultConfig, + Installer, + LazyVersion, + commonprefix, +) class ConfigForTest(DefaultConfig): @@ -13,20 +19,20 @@ class ColorConfig(DefaultConfig): def test_commonprefix(): - assert commonprefix(['isalpha', 'isdigit', 'foo']) == '' - assert commonprefix(['isalpha', 'isdigit']) == 'is' - assert commonprefix(['isalpha', 'isdigit', 'foo'], base='i') == 'is' - assert commonprefix([]) == '' - assert commonprefix(['aaa', 'bbb'], base='x') == '' + assert commonprefix(["isalpha", "isdigit", "foo"]) == "" + assert commonprefix(["isalpha", "isdigit"]) == "is" + assert commonprefix(["isalpha", "isdigit", "foo"], base="i") == "is" + assert commonprefix([]) == "" + assert commonprefix(["aaa", "bbb"], base="x") == "" def test_complete_attribute(): - compl = Completer({'a': None}, ConfigForTest) - assert compl.attr_matches('a.') == ['a.__'] - matches = compl.attr_matches('a.__') - assert 'a.__class__' not in matches - assert '__class__' in matches - assert compl.attr_matches('a.__class') == ['a.__class__'] + compl = Completer({"a": None}, ConfigForTest) + assert compl.attr_matches("a.") == ["a.__"] + matches = compl.attr_matches("a.__") + assert "a.__class__" not in matches + assert "__class__" in matches + assert compl.attr_matches("a.__class") == ["a.__class__"] def test_complete_attribute_prefix(): @@ -34,84 +40,85 @@ class C(object): attr = 1 _attr = 2 __attr__attr = 3 - compl = Completer({'a': C}, ConfigForTest) - assert compl.attr_matches('a.') == ['attr', 'mro'] - assert compl.attr_matches('a._') == ['_C__attr__attr', '_attr', ' '] - matches = compl.attr_matches('a.__') - assert 'a.__class__' not in matches - assert '__class__' in matches - assert compl.attr_matches('a.__class') == ['a.__class__'] - compl = Completer({'a': None}, ConfigForTest) - assert compl.attr_matches('a._') == ['a.__'] + compl = Completer({"a": C}, ConfigForTest) + assert compl.attr_matches("a.") == ["attr", "mro"] + assert compl.attr_matches("a._") == ["_C__attr__attr", "_attr", " "] + matches = compl.attr_matches("a.__") + assert "a.__class__" not in matches + assert "__class__" in matches + assert compl.attr_matches("a.__class") == ["a.__class__"] + + compl = Completer({"a": None}, ConfigForTest) + assert compl.attr_matches("a._") == ["a.__"] def test_complete_attribute_colored(): - compl = Completer({'a': 42}, ColorConfig) - matches = compl.attr_matches('a.__') + compl = Completer({"a": 42}, ColorConfig) + matches = compl.attr_matches("a.__") assert len(matches) > 2 expected_color = compl.config.color_by_type.get(type(compl.__class__)) - assert expected_color == '35;01' - expected_part = Color.set(expected_color, '__class__') + assert expected_color == "35;01" + expected_part = Color.set(expected_color, "__class__") for match in matches: if expected_part in match: break else: assert False, matches - assert ' ' in matches + assert " " in matches def test_complete_colored_single_match(): """No coloring, via commonprefix.""" - compl = Completer({'foobar': 42}, ColorConfig) - matches = compl.global_matches('foob') - assert matches == ['foobar'] + compl = Completer({"foobar": 42}, ColorConfig) + matches = compl.global_matches("foob") + assert matches == ["foobar"] def test_does_not_color_single_match(): class obj: msgs = [] - compl = Completer({'obj': obj}, ColorConfig) - matches = compl.attr_matches('obj.msgs') - assert matches == ['obj.msgs'] + compl = Completer({"obj": obj}, ColorConfig) + matches = compl.attr_matches("obj.msgs") + assert matches == ["obj.msgs"] def test_complete_global(): - compl = Completer({'foobar': 1, 'foobazzz': 2}, ConfigForTest) - assert compl.global_matches('foo') == ['fooba'] - matches = compl.global_matches('fooba') - assert set(matches) == set(['foobar', 'foobazzz']) - assert compl.global_matches('foobaz') == ['foobazzz'] - assert compl.global_matches('nothing') == [] + compl = Completer({"foobar": 1, "foobazzz": 2}, ConfigForTest) + assert compl.global_matches("foo") == ["fooba"] + matches = compl.global_matches("fooba") + assert set(matches) == set(["foobar", "foobazzz"]) + assert compl.global_matches("foobaz") == ["foobazzz"] + assert compl.global_matches("nothing") == [] def test_complete_global_colored(): - compl = Completer({'foobar': 1, 'foobazzz': 2}, ColorConfig) - assert compl.global_matches('foo') == ['fooba'] - matches = compl.global_matches('fooba') + compl = Completer({"foobar": 1, "foobazzz": 2}, ColorConfig) + assert compl.global_matches("foo") == ["fooba"] + matches = compl.global_matches("fooba") assert set(matches) == { - ' ', - '\x1b[001;00m\x1b[33;01mfoobazzz\x1b[00m', - '\x1b[000;00m\x1b[33;01mfoobar\x1b[00m', + " ", + "\x1b[001;00m\x1b[33;01mfoobazzz\x1b[00m", + "\x1b[000;00m\x1b[33;01mfoobar\x1b[00m", } - assert compl.global_matches('foobaz') == ['foobazzz'] - assert compl.global_matches('nothing') == [] + assert compl.global_matches("foobaz") == ["foobazzz"] + assert compl.global_matches("nothing") == [] def test_complete_global_colored_exception(): - compl = Completer({'tryme': ValueError()}, ColorConfig) + compl = Completer({"tryme": ValueError()}, ColorConfig) if sys.version_info >= (3, 6): - assert compl.global_matches('try') == [ - '\x1b[000;00m\x1b[37mtry:\x1b[00m', - '\x1b[001;00m\x1b[31;01mtryme\x1b[00m', - ' ' + assert compl.global_matches("try") == [ + "\x1b[000;00m\x1b[37mtry:\x1b[00m", + "\x1b[001;00m\x1b[31;01mtryme\x1b[00m", + " ", ] else: - assert compl.global_matches('try') == [ - '\x1b[000;00m\x1b[37mtry\x1b[00m', - '\x1b[001;00m\x1b[31;01mtryme\x1b[00m', - ' ' + assert compl.global_matches("try") == [ + "\x1b[000;00m\x1b[37mtry\x1b[00m", + "\x1b[001;00m\x1b[31;01mtryme\x1b[00m", + " ", ] @@ -119,12 +126,13 @@ def test_complete_global_exception(monkeypatch): import rlcompleter def rlcompleter_global_matches(self, text): - return ['trigger_exception!', 'nameerror', 'valid'] + return ["trigger_exception!", "nameerror", "valid"] - monkeypatch.setattr(rlcompleter.Completer, 'global_matches', - rlcompleter_global_matches) + monkeypatch.setattr( + rlcompleter.Completer, "global_matches", rlcompleter_global_matches + ) - compl = Completer({'valid': 42}, ColorConfig) + compl = Completer({"valid": 42}, ColorConfig) assert compl.global_matches("") == [ "\x1b[000;00m\x1b[31;01mnameerror\x1b[00m", "\x1b[001;00m\x1b[31;01mtrigger_exception!\x1b[00m", @@ -142,12 +150,12 @@ class Config(ColorConfig): def test_complete_with_indexer(): - compl = Completer({'lst': [None, 2, 3]}, ConfigForTest) - assert compl.attr_matches('lst[0].') == ['lst[0].__'] - matches = compl.attr_matches('lst[0].__') - assert 'lst[0].__class__' not in matches - assert '__class__' in matches - assert compl.attr_matches('lst[0].__class') == ['lst[0].__class__'] + compl = Completer({"lst": [None, 2, 3]}, ConfigForTest) + assert compl.attr_matches("lst[0].") == ["lst[0].__"] + matches = compl.attr_matches("lst[0].__") + assert "lst[0].__class__" not in matches + assert "__class__" in matches + assert compl.attr_matches("lst[0].__class") == ["lst[0].__class__"] def test_autocomplete(): @@ -157,50 +165,51 @@ class A: abc_2 = None abc_3 = None bbb = None - compl = Completer({'A': A}, ConfigForTest) + + compl = Completer({"A": A}, ConfigForTest) # # in this case, we want to display all attributes which start with # 'a'. MOREOVER, we also include a space to prevent readline to # automatically insert the common prefix (which will the the ANSI escape # sequence if we use colors) - matches = compl.attr_matches('A.a') - assert sorted(matches) == [' ', 'aaa', 'abc_1', 'abc_2', 'abc_3'] + matches = compl.attr_matches("A.a") + assert sorted(matches) == [" ", "aaa", "abc_1", "abc_2", "abc_3"] # # IF there is an actual common prefix, we return just it, so that readline # will insert it into place - matches = compl.attr_matches('A.ab') - assert matches == ['A.abc_'] + matches = compl.attr_matches("A.ab") + assert matches == ["A.abc_"] # # finally, at the next TAB, we display again all the completions available # for this common prefix. Agai, we insert a spurious space to prevent the # automatic completion of ANSI sequences - matches = compl.attr_matches('A.abc_') - assert sorted(matches) == [' ', 'abc_1', 'abc_2', 'abc_3'] + matches = compl.attr_matches("A.abc_") + assert sorted(matches) == [" ", "abc_1", "abc_2", "abc_3"] def test_complete_exception(): compl = Completer({}, ConfigForTest) - assert compl.attr_matches('xxx.') == [] + assert compl.attr_matches("xxx.") == [] def test_complete_invalid_attr(): - compl = Completer({'str': str}, ConfigForTest) - assert compl.attr_matches('str.xx') == [] + compl = Completer({"str": str}, ConfigForTest) + assert compl.attr_matches("str.xx") == [] def test_complete_function_skipped(): - compl = Completer({'str': str}, ConfigForTest) - assert compl.attr_matches('str.split().') == [] + compl = Completer({"str": str}, ConfigForTest) + assert compl.attr_matches("str.split().") == [] def test_unicode_in___dir__(): class Foo(object): def __dir__(self): - return [u'hello', 'world'] + return ["hello", "world"] - compl = Completer({'a': Foo()}, ConfigForTest) - matches = compl.attr_matches('a.') - assert matches == ['hello', 'world'] + compl = Completer({"a": Foo()}, ConfigForTest) + matches = compl.attr_matches("a.") + assert matches == ["hello", "world"] assert type(matches[0]) is str @@ -212,21 +221,20 @@ def set_env_var(self): class TestInstaller(object): - def test_check(self, monkeypatch, tmpdir): installer = MyInstaller(str(tmpdir), force=False) - monkeypatch.setenv('PYTHONSTARTUP', '') + monkeypatch.setenv("PYTHONSTARTUP", "") assert installer.check() is None - f = tmpdir.join('python_startup.py').ensure(file=True) - assert installer.check() == '%s already exists' % f - monkeypatch.setenv('PYTHONSTARTUP', 'foo') - assert installer.check() == 'PYTHONSTARTUP already defined: foo' + f = tmpdir.join("python_startup.py").ensure(file=True) + assert installer.check() == "%s already exists" % f + monkeypatch.setenv("PYTHONSTARTUP", "foo") + assert installer.check() == "PYTHONSTARTUP already defined: foo" def test_install(self, monkeypatch, tmpdir): installer = MyInstaller(str(tmpdir), force=False) - monkeypatch.setenv('PYTHONSTARTUP', '') + monkeypatch.setenv("PYTHONSTARTUP", "") assert installer.install() - assert 'fancycompleter' in tmpdir.join('python_startup.py').read() + assert "fancycompleter" in tmpdir.join("python_startup.py").read() assert installer.env_var == 1 # # the second time, it fails because the file already exists @@ -240,18 +248,17 @@ def test_install(self, monkeypatch, tmpdir): class TestLazyVersion(object): - class MyLazyVersion(LazyVersion): __count = 0 def _load_version(self): assert self.__count == 0 self.__count += 1 - return '0.1' + return "0.1" def test_lazy_version(self): - ver = self.MyLazyVersion('foo') - assert repr(ver) == '0.1' - assert str(ver) == '0.1' - assert ver == '0.1' - assert not ver != '0.1' + ver = self.MyLazyVersion("foo") + assert repr(ver) == "0.1" + assert str(ver) == "0.1" + assert ver == "0.1" + assert not ver != "0.1" From 61be9a029484082f07206c34a5a7da9c238223b2 Mon Sep 17 00:00:00 2001 From: bretello Date: Sun, 29 Oct 2023 16:40:15 +0100 Subject: [PATCH 10/10] mv testing tests --- {testing => tests}/__init__.py | 0 {testing => tests}/conftest.py | 0 {testing => tests}/test_configurableclass.py | 0 {testing => tests}/test_fancycompleter.py | 0 tox.ini | 6 +++--- 5 files changed, 3 insertions(+), 3 deletions(-) rename {testing => tests}/__init__.py (100%) rename {testing => tests}/conftest.py (100%) rename {testing => tests}/test_configurableclass.py (100%) rename {testing => tests}/test_fancycompleter.py (100%) diff --git a/testing/__init__.py b/tests/__init__.py similarity index 100% rename from testing/__init__.py rename to tests/__init__.py diff --git a/testing/conftest.py b/tests/conftest.py similarity index 100% rename from testing/conftest.py rename to tests/conftest.py diff --git a/testing/test_configurableclass.py b/tests/test_configurableclass.py similarity index 100% rename from testing/test_configurableclass.py rename to tests/test_configurableclass.py diff --git a/testing/test_fancycompleter.py b/tests/test_fancycompleter.py similarity index 100% rename from testing/test_fancycompleter.py rename to tests/test_fancycompleter.py diff --git a/tox.ini b/tox.ini index db2c1c7..2c17afe 100644 --- a/tox.ini +++ b/tox.ini @@ -12,14 +12,14 @@ setenv = [testenv:checkqa] deps = ruff -commands = ruff check setup.py fancycompleter.py testing +commands = ruff check setup.py fancycompleter.py tests [pytest] addopts = -ra -testpaths = testing +testpaths = tests [coverage:run] -include = */fancycompleter.py, testing/* +include = */fancycompleter.py, tests/* parallel = 1 branch = 1