diff --git a/.coveragerc b/.coveragerc index dcd3739..81ba1c7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,3 +10,4 @@ exclude_also = [run] omit = **/blurb/__main__.py + **/blurb/_version.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0dc0bab..e535eb6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,8 +14,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-python@v5 with: python-version: "3.x" - cache: pip - - uses: pre-commit/action@v3.0.1 + - uses: tox-dev/action-pre-commit-uv@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index befd5af..a9d7511 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,9 @@ on: permissions: contents: read +env: + FORCE_COLOR: 1 + jobs: # Always build & lint package. build-package: @@ -21,6 +24,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - uses: hynek/build-and-inspect-python-package@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c2d790..2802763 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,9 @@ name: Test on: [push, pull_request, workflow_dispatch] +permissions: + contents: read + env: FORCE_COLOR: 1 @@ -11,32 +14,28 @@ jobs: strategy: fail-fast: false matrix: - # Temporarily remove 3.13 pending: - # https://github.com/pytest-dev/pyfakefs/issues/1017 - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - cache: pip - - name: Install dependencies - run: | - python --version - python -m pip install -U pip - python -m pip install -U tox + - name: Install uv + uses: hynek/setup-cached-uv@v2 - name: Tox tests run: | - tox -e py + uvx --with tox-uv tox -e py - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: ${{ matrix.python-version }} name: Python ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index 009c104..353ced5 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ ENV/ # pytest .pytest_cache/ + +# hatch-vcs +src/*/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cf530b..c076739 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,28 +1,40 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: + - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer + - id: forbid-submodules - id: trailing-whitespace + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.29.2 + hooks: + - id: check-dependabot + - id: check-github-workflows + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.2 + hooks: + - id: actionlint + - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.7.0 + rev: 2.2.4 hooks: - id: pyproject-fmt - additional_dependencies: [tox] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.16 + rev: v0.20.2 hooks: - id: validate-pyproject - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.3.1 + rev: 1.4.1 hooks: - id: tox-ini-fmt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9b810a9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +## 2.0.0 + +* Move 'blurb test' subcommand into test suite by @hugovk in https://github.com/python/blurb/pull/37 +* Add support for Python 3.14 by @ezio-melotti in https://github.com/python/blurb/pull/40 +* Validate gh-issue is int before checking range, and that gh-issue or bpo exists by @hugovk in https://github.com/python/blurb/pull/35 +* Replace `safe_mkdir(path)` with `os.makedirs(path, exist_ok=True)` by @hugovk in https://github.com/python/blurb/pull/38 +* Test version handling functions by @hugovk in https://github.com/python/blurb/pull/36 +* CI: Lint and test via uv by @hugovk in https://github.com/python/blurb/pull/32 + +## 1.3.0 + +* Add support for Python 3.13 by @hugovk in https://github.com/python/blurb/pull/26 +* Drop support for Python 3.8 by @hugovk in https://github.com/python/blurb/pull/27 +* Generate digital attestations for PyPI (PEP 740) by @hugovk in https://github.com/python/blurb/pull/28 +* Allow running blurb test from blurb-* directories by @hroncok in https://github.com/python/blurb/pull/24 +* Add `version` subcommand by @hugovk in https://github.com/python/blurb/pull/29 +* Generate `__version__` at build to avoid slow `importlib.metadata` import by @hugovk in https://github.com/python/blurb/pull/30 + +## 1.2.1 + +- Fix `python3 -m blurb`. +- Undocument removed `blurb split`. + +## 1.2.0 + +- Replace spaces with underscores in news directory. +- Drop support for Python 3.7. +- Remove `blurb split` command. +- Replace `gh-issue-NNNN:` with `gh-NNNN:` in the output. +- Accept GitHub issues numbered only 32426 or above. +- Improve error checking when parsing a Blurb. +- Loosen README check for CPython forks. +- Move code from `python/core-workflow` to own `python/blurb` repo. +- Deploy to PyPI via Trusted Publishers. + +## 1.1.0 + +- Support GitHub Issues in addition to b.p.o (bugs.python.org). + If `gh-issue` is in the metadata, then the filename will contain + `gh-issue-` instead of `bpo-`. + +## 1.0.7 + +- When word wrapping, don't break on long words or hyphens. +- Use the `-f` flag when adding **blurb** files to a Git + commit. This forces them to be added, even when the files + might normally be ignored based on a `.gitignore` directive. +- Explicitly support the `-help` command-line option. +- Fix Travis CI integration. diff --git a/README.md b/README.md index 74bf91b..c5e6abc 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ and automatically uses the correct file paths. You can install **blurb** from PyPI using `pip`. Alternatively, simply add `blurb` to a directory on your path. -**blurb**'s only dependency is Python 3.8+. - ## Files used by blurb @@ -191,32 +189,6 @@ uses the name of the directory CPython is checked out to. version I'm releasing, and using this shortcut saves me some typing.) -### blurb split - -`blurb split` only needs to be run once per-branch, ever. -It reads in `Misc/NEWS` -and splits it into individual `.rst` files. -The text files are stored as follows:: - - Misc/NEWS.d/.rst - -`` is the version number of Python where the -change was committed. Pre-release versions are denoted -with an abbreviation: `a` for alphas, `b` for betas, -and `rc` for release candidates. - -The individual `.rst` files actually (usually) -contain multiple entries. Each entry is delimited by a -single line containing `..` by itself. - -The assumption is, at the point we convert over to *blurb*, -we'll run `blurb split` on each active branch, -remove `Misc/NEWS` from the repo entirely, -never run `blurb split` ever again, -and ride off into the sunset, confident that the world is now -a better place. - - ## The "next" directory @@ -237,36 +209,11 @@ the right thing. If `NEWS` entries were already written to the final version directory, you'd have to move those around as part of the cherry-picking process. -## Changelog - -### 1.2.0 - -- Replace spaces with underscores in news directory. -- Drop support for Python 3.7. -- Remove `blurb split` command. -- Replace `gh-issue-NNNN:` with `gh-NNNN:` in the output. -- Accept GitHub issues numbered only 32426 or above. -- Improve error checking when parsing a Blurb. -- Loosen README check for CPython forks. -- Move code from `python/core-workflow` to own `python/blurb` repo. -- Deploy to PyPI via Trusted Publishers. - -### 1.1.0 - -- Support GitHub Issues in addition to b.p.o (bugs.python.org). - If "gh-issue" is in the metadata, then the filename will contain - "gh-issue-" instead of "bpo-". - -### 1.0.7 - -- When word wrapping, don't break on long words or hyphens. -- Use the `-f` flag when adding **blurb** files to a Git - commit. This forces them to be added, even when the files - might normally be ignored based on a `.gitignore` directive. -- Explicitly support the `-help` command-line option. -- Fix Travis CI integration. - ## Copyright **blurb** is Copyright 2015-2018 by Larry Hastings. Licensed to the PSF under a contributor agreement. + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). diff --git a/pyproject.toml b/pyproject.toml index 22371b6..d6f0669 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,38 +9,46 @@ requires = [ name = "blurb" description = "Command-line tool to manage CPython Misc/NEWS.d entries." readme = "README.md" -maintainers = [{name = "Python Core Developers", email="core-workflow@mail.python.org"}] -authors = [{ name="Larry Hastings", email="larry@hastings.org"}] -requires-python = ">=3.8" +maintainers = [ + { name = "Python Core Developers", email = "core-workflow@mail.python.org" }, +] +authors = [ + { name = "Larry Hastings", email = "larry@hastings.org" }, +] +requires-python = ">=3.9" classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "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 :: 3.13", + "Programming Language :: Python :: 3.14", ] dynamic = [ "version", ] -[project.optional-dependencies] -tests = [ +optional-dependencies.tests = [ "pyfakefs", "pytest", "pytest-cov", + "time-machine", ] -[project.urls] -Changelog = "https://github.com/python/blurb/blob/main/README.md#changelog" -Homepage = "https://github.com/python/blurb" -Source = "https://github.com/python/blurb" -[project.scripts] -blurb = "blurb.blurb:main" +urls.Changelog = "https://github.com/python/blurb/blob/main/CHANGELOG.md" +urls.Homepage = "https://github.com/python/blurb" +urls.Source = "https://github.com/python/blurb" +scripts.blurb = "blurb.blurb:main" [tool.hatch] version.source = "vcs" +[tool.hatch.build.hooks.vcs] +version-file = "src/blurb/_version.py" + [tool.hatch.version.raw-options] local_scheme = "no-local-version" + +[tool.pyproject-fmt] +max_supported_python = "3.14" diff --git a/src/blurb/__init__.py b/src/blurb/__init__.py index 63dfcac..8dee4bf 100644 --- a/src/blurb/__init__.py +++ b/src/blurb/__init__.py @@ -1,3 +1 @@ -import importlib.metadata - -__version__ = importlib.metadata.version(__name__) +from ._version import __version__ diff --git a/src/blurb/__main__.py b/src/blurb/__main__.py index 6aff72b..a173b94 100644 --- a/src/blurb/__main__.py +++ b/src/blurb/__main__.py @@ -1,5 +1,5 @@ -"""Run blurb using `python3 blurb/`.""" -import blurb +"""Run blurb using ``python3 -m blurb``.""" +from blurb import blurb if __name__ == '__main__': diff --git a/src/blurb/blurb.py b/src/blurb/blurb.py index 4930bb4..b3998b5 100755 --- a/src/blurb/blurb.py +++ b/src/blurb/blurb.py @@ -57,7 +57,6 @@ import tempfile import textwrap import time -import unittest from . import __version__ @@ -140,7 +139,6 @@ def unsanitize_section(section): return _unsanitize_section.get(section, section) def next_filename_unsanitize_sections(filename): - s = filename for key, value in _unsanitize_section.items(): for separator in "/\\": key = f"{separator}{key}{separator}" @@ -268,11 +266,6 @@ def __exit__(self, *args): os.chdir(self.previous_cwd) -def safe_mkdir(path): - if not os.path.exists(path): - os.makedirs(path) - - def version_key(element): fields = list(element.split(".")) if len(fields) == 1: @@ -482,14 +475,14 @@ def finish_entry(): # we'll complain about the *first* error # we see in the blurb file, which is a # better user experience. - if key == "gh-issue" and int(value) < lowest_possible_gh_issue_number: - throw(f"The gh-issue number must be {lowest_possible_gh_issue_number} or above, not a PR number.") - if key in issue_keys: try: int(value) except (TypeError, ValueError): - throw(f"Invalid {issue_keys[key]} issue number! ({value!r})") + throw(f"Invalid {issue_keys[key]} number: {value!r}") + + if key == "gh-issue" and int(value) < lowest_possible_gh_issue_number: + throw(f"Invalid gh-issue number: {value!r} (must be >= {lowest_possible_gh_issue_number})") if key == "section": if no_changes: @@ -497,7 +490,10 @@ def finish_entry(): if value not in sections: throw(f"Invalid section {value!r}! You must use one of the predefined sections.") - if not 'section' in metadata: + if "gh-issue" not in metadata and "bpo" not in metadata: + throw("'gh-issue:' or 'bpo:' must be specified in the metadata!") + + if 'section' not in metadata: throw("No 'section' specified. You must provide one!") self.append((metadata, text)) @@ -557,7 +553,7 @@ def __str__(self): def save(self, path): dirname = os.path.dirname(path) - safe_mkdir(dirname) + os.makedirs(dirname, exist_ok=True) text = str(self) with open(path, "wt", encoding="utf-8") as file: @@ -639,42 +635,6 @@ def save_next(self): return filename -tests_run = 0 - -class TestParserPasses(unittest.TestCase): - directory = "blurb/tests/pass" - - def filename_test(self, filename): - b = Blurbs() - b.load(filename) - self.assertTrue(b) - if os.path.exists(filename + '.res'): - with open(filename + '.res', encoding='utf-8') as file: - expected = file.read() - self.assertEqual(str(b), expected) - - def test_files(self): - global tests_run - with pushd(self.directory): - for filename in glob.glob("*"): - if filename[-4:] == '.res': - self.assertTrue(os.path.exists(filename[:-4]), filename) - continue - self.filename_test(filename) - print(".", end="") - sys.stdout.flush() - tests_run += 1 - - -class TestParserFailures(TestParserPasses): - directory = "blurb/tests/fail" - - def filename_test(self, filename): - b = Blurbs() - with self.assertRaises(Exception): - b.load(filename) - - readme_re = re.compile(r"This is \w+ version \d+\.\d+").match def chdir_to_repo_root(): @@ -742,6 +702,13 @@ def get_subcommand(subcommand): +@subcommand +def version(): + """Print blurb version.""" + print("blurb version", __version__) + + + @subcommand def help(subcommand=None): """ @@ -816,37 +783,19 @@ def help(subcommand=None): print(doc) sys.exit(0) -# Make "blurb --help" work. +# Make "blurb --help/--version/-V" work. subcommands["--help"] = help +subcommands["--version"] = version +subcommands["-V"] = version -@subcommand -def test(*args): - """ -Run unit tests. Only works inside source repo, not when installed. - """ - # unittest.main doesn't work because this isn't a module - # so we'll do it ourselves - - while not os.path.isdir("blurb"): - old_dir = os.getcwd() - os.chdir("..") - if old_dir == os.getcwd(): - # we reached the root and never found it! - sys.exit("Error: Couldn't find the root of your blurb repo!") - - print("-" * 79) - - for clsname, cls in sorted(globals().items()): - if clsname.startswith("Test") and isinstance(cls, type): - o = cls() - for fnname in sorted(dir(o)): - if fnname.startswith("test"): - fn = getattr(o, fnname) - if callable(fn): - fn() - print() - print(tests_run, "tests passed.") +def _find_blurb_dir(): + if os.path.isdir("blurb"): + return "blurb" + for path in glob.iglob("blurb-*"): + if os.path.isdir(path): + return path + return None def find_editor(): @@ -1156,12 +1105,12 @@ def populate(): Creates and populates the Misc/NEWS.d directory tree. """ os.chdir("Misc") - safe_mkdir("NEWS.d/next") + os.makedirs("NEWS.d/next", exist_ok=True) for section in sections: dir_name = sanitize_section(section) dir_path = f"NEWS.d/next/{dir_name}" - safe_mkdir(dir_path) + os.makedirs(dir_path, exist_ok=True) readme_path = f"NEWS.d/next/{dir_name}/README.rst" with open(readme_path, "wt", encoding="utf-8") as readme: readme.write(f"Put news entry ``blurb`` files for the *{section}* section in this directory.\n") @@ -1205,7 +1154,7 @@ def main(): fn = get_subcommand(subcommand) # hack - if fn in (test, help): + if fn in (help, version): sys.exit(fn(*args)) try: diff --git a/tests/test_blurb.py b/tests/test_blurb.py index 1c18a9f..caf0f4e 100644 --- a/tests/test_blurb.py +++ b/tests/test_blurb.py @@ -1,5 +1,5 @@ import pytest -from pyfakefs.fake_filesystem import FakeFilesystem +import time_machine from blurb import blurb @@ -45,6 +45,116 @@ def test_unsanitize_section_changed(section, expected): assert unsanitized == expected +@pytest.mark.parametrize( + "body, subsequent_indent, expected", + ( + ( + "This is a test of the textwrap_body function with a string. It should wrap the text to 79 characters.", + "", + "This is a test of the textwrap_body function with a string. It should wrap\n" + "the text to 79 characters.\n", + ), + ( + [ + "This is a test of the textwrap_body function", + "with an iterable of strings.", + "It should wrap the text to 79 characters.", + ], + "", + "This is a test of the textwrap_body function with an iterable of strings. It\n" + "should wrap the text to 79 characters.\n", + ), + ( + "This is a test of the textwrap_body function with a string and subsequent indent.", + " ", + "This is a test of the textwrap_body function with a string and subsequent\n" + " indent.\n", + ), + ( + "This is a test of the textwrap_body function with a bullet list and subsequent indent. The list should not be wrapped.\n" + "\n" + "* Item 1\n" + "* Item 2\n", + " ", + "This is a test of the textwrap_body function with a bullet list and\n" + " subsequent indent. The list should not be wrapped.\n" + "\n" + " * Item 1\n" + " * Item 2\n", + ), + ), +) +def test_textwrap_body(body, subsequent_indent, expected): + assert blurb.textwrap_body(body, subsequent_indent=subsequent_indent) == expected + + +@time_machine.travel("2025-01-07") +def test_current_date(): + assert blurb.current_date() == "2025-01-07" + + +@time_machine.travel("2025-01-07 16:28:41") +def test_sortable_datetime(): + assert blurb.sortable_datetime() == "2025-01-07-16-28-41" + + +@pytest.mark.parametrize( + "version1, version2", + ( + ("2", "3"), + ("3.5.0a1", "3.5.0b1"), + ("3.5.0a1", "3.5.0rc1"), + ("3.5.0a1", "3.5.0"), + ("3.6.0b1", "3.6.0b2"), + ("3.6.0b1", "3.6.0rc1"), + ("3.6.0b1", "3.6.0"), + ("3.7.0rc1", "3.7.0rc2"), + ("3.7.0rc1", "3.7.0"), + ("3.8", "3.8.1"), + ), +) +def test_version_key(version1, version2): + # Act + key1 = blurb.version_key(version1) + key2 = blurb.version_key(version2) + + # Assert + assert key1 < key2 + + +def test_glob_versions(fs): + # Arrange + fake_version_blurbs = ( + "Misc/NEWS.d/3.7.0.rst", + "Misc/NEWS.d/3.7.0a1.rst", + "Misc/NEWS.d/3.7.0a2.rst", + "Misc/NEWS.d/3.7.0b1.rst", + "Misc/NEWS.d/3.7.0b2.rst", + "Misc/NEWS.d/3.7.0rc1.rst", + "Misc/NEWS.d/3.7.0rc2.rst", + "Misc/NEWS.d/3.9.0b1.rst", + "Misc/NEWS.d/3.12.0a1.rst", + ) + for fn in fake_version_blurbs: + fs.create_file(fn) + + # Act + versions = blurb.glob_versions() + + # Assert + assert versions == [ + "3.12.0a1", + "3.9.0b1", + "3.7.0", + "3.7.0rc2", + "3.7.0rc1", + "3.7.0b2", + "3.7.0b1", + "3.7.0a2", + "3.7.0a1", + ] + + def test_glob_blurbs_next(fs): # Arrange fake_news_entries = ( @@ -104,6 +214,22 @@ def test_glob_blurbs_sort_order(fs): assert filenames == expected +@pytest.mark.parametrize( + "version, expected", + ( + ("next", "next"), + ("3.12.0a1", "3.12.0 alpha 1"), + ("3.12.0b2", "3.12.0 beta 2"), + ("3.12.0rc2", "3.12.0 release candidate 2"), + ("3.12.0", "3.12.0 final"), + ("3.12.1", "3.12.1 final"), + ), +) +def test_printable_version(version, expected): + # Act / Assert + assert blurb.printable_version(version) == expected + + @pytest.mark.parametrize( "news_entry, expected_section", ( @@ -179,3 +305,77 @@ def test_extract_next_filename(news_entry, expected_path, fs): # Assert assert path == expected_path + + +def test_version(capfd): + # Act + blurb.version() + + # Assert + captured = capfd.readouterr() + assert captured.out.startswith("blurb version ") + + +def test_parse(): + # Arrange + contents = ".. gh-issue: 123456\n.. section: IDLE\nHello world!" + blurbs = blurb.Blurbs() + + # Act + blurbs.parse(contents) + + # Assert + metadata, body = blurbs[0] + assert metadata["gh-issue"] == "123456" + assert metadata["section"] == "IDLE" + assert body == "Hello world!\n" + + +@pytest.mark.parametrize( + "contents, expected_error", + ( + ( + "", + r"Blurb 'body' text must not be empty!", + ), + ( + "gh-issue: Hello world!", + r"Blurb 'body' can't start with 'gh-'!", + ), + ( + ".. gh-issue: 1\n.. section: IDLE\nHello world!", + r"Invalid gh-issue number: '1' \(must be >= 32426\)", + ), + ( + ".. bpo: one-two\n.. section: IDLE\nHello world!", + r"Invalid bpo number: 'one-two'", + ), + ( + ".. gh-issue: one-two\n.. section: IDLE\nHello world!", + r"Invalid GitHub number: 'one-two'", + ), + ( + ".. gh-issue: 123456\n.. section: Funky Kong\nHello world!", + r"Invalid section 'Funky Kong'! You must use one of the predefined sections", + ), + ( + ".. gh-issue: 123456\nHello world!", + r"No 'section' specified. You must provide one!", + ), + ( + ".. gh-issue: 123456\n.. section: IDLE\n.. section: IDLE\nHello world!", + r"Blurb metadata sets 'section' twice!", + ), + ( + ".. section: IDLE\nHello world!", + r"'gh-issue:' or 'bpo:' must be specified in the metadata!", + ), + ), +) +def test_parse_no_body(contents, expected_error): + # Arrange + blurbs = blurb.Blurbs() + + # Act / Assert + with pytest.raises(blurb.BlurbError, match=expected_error): + blurbs.parse(contents) diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..4b5b3f3 --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,36 @@ +import glob +import os + +import pytest + +from blurb.blurb import Blurbs, pushd + + +class TestParserPasses: + directory = "tests/pass" + + def filename_test(self, filename): + b = Blurbs() + b.load(filename) + assert b + if os.path.exists(filename + ".res"): + with open(filename + ".res", encoding="utf-8") as file: + expected = file.read() + assert str(b) == expected + + def test_files(self): + with pushd(self.directory): + for filename in glob.glob("*"): + if filename.endswith(".res"): + assert os.path.exists(filename[:-4]), filename + continue + self.filename_test(filename) + + +class TestParserFailures(TestParserPasses): + directory = "tests/fail" + + def filename_test(self, filename): + b = Blurbs() + with pytest.raises(Exception): + b.load(filename) diff --git a/tox.ini b/tox.ini index 29d8a8a..e9c60df 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ requires = tox>=4.2 env_list = - py{313, 312, 311, 310, 39, 38} + py{314, 313, 312, 311, 310, 39} [testenv] extras = @@ -17,5 +17,7 @@ commands = --cov-report term \ --cov-report xml \ {posargs} - blurb test blurb help + blurb --version + {envpython} -I -m blurb help + {envpython} -I -m blurb version pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy