Skip to content

Commit 792c0a6

Browse files
authored
Merge branch 'main' into add-gh-flags
2 parents 1f789b0 + e7ea718 commit 792c0a6

10 files changed

Lines changed: 266 additions & 101 deletions

File tree

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ exclude_also =
1010
[run]
1111
omit =
1212
**/blurb/__main__.py
13+
**/blurb/_version.py

.github/workflows/lint.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ jobs:
1414

1515
steps:
1616
- uses: actions/checkout@v4
17+
with:
18+
persist-credentials: false
1719
- uses: actions/setup-python@v5
1820
with:
1921
python-version: "3.x"
20-
cache: pip
21-
- uses: pre-commit/action@v3.0.1
22+
- uses: tox-dev/action-pre-commit-uv@v1

.github/workflows/release.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
- uses: actions/checkout@v4
2525
with:
2626
fetch-depth: 0
27+
persist-credentials: false
2728

2829
- uses: hynek/build-and-inspect-python-package@v2
2930

@@ -50,7 +51,6 @@ jobs:
5051
- name: Publish to Test PyPI
5152
uses: pypa/gh-action-pypi-publish@release/v1
5253
with:
53-
attestations: true
5454
repository-url: https://test.pypi.org/legacy/
5555

5656
# Publish to PyPI on GitHub Releases.
@@ -82,5 +82,3 @@ jobs:
8282

8383
- name: Publish to PyPI
8484
uses: pypa/gh-action-pypi-publish@release/v1
85-
with:
86-
attestations: true

.github/workflows/test.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,28 @@ jobs:
1414
strategy:
1515
fail-fast: false
1616
matrix:
17-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1818

1919
steps:
2020
- uses: actions/checkout@v4
21+
with:
22+
persist-credentials: false
2123

2224
- name: Set up Python ${{ matrix.python-version }}
2325
uses: actions/setup-python@v5
2426
with:
2527
python-version: ${{ matrix.python-version }}
2628
allow-prereleases: true
27-
cache: pip
2829

29-
- name: Install dependencies
30-
run: |
31-
python --version
32-
python -m pip install -U pip
33-
python -m pip install -U tox
30+
- name: Install uv
31+
uses: hynek/setup-cached-uv@v2
3432

3533
- name: Tox tests
3634
run: |
37-
tox -e py
35+
uvx --with tox-uv tox -e py
3836
3937
- name: Upload coverage
40-
uses: codecov/codecov-action@v4
38+
uses: codecov/codecov-action@v5
4139
with:
4240
flags: ${{ matrix.python-version }}
4341
name: Python ${{ matrix.python-version }}

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
# Changelog
22

3-
## 1.3.1
3+
## 2.1.0
44

55
- Add the `-i/--issue` and `-s/--section` options to the `add` command.
66
This lets you pre-fill the `gh-issue` and `section` fields in the template.
77
Added by @picnixz in https://github.com/python/blurb/pull/16.
88

9+
## 2.0.0
10+
11+
* Move 'blurb test' subcommand into test suite by @hugovk in https://github.com/python/blurb/pull/37
12+
* Add support for Python 3.14 by @ezio-melotti in https://github.com/python/blurb/pull/40
13+
* 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
14+
* Replace `safe_mkdir(path)` with `os.makedirs(path, exist_ok=True)` by @hugovk in https://github.com/python/blurb/pull/38
15+
* Test version handling functions by @hugovk in https://github.com/python/blurb/pull/36
16+
* CI: Lint and test via uv by @hugovk in https://github.com/python/blurb/pull/32
17+
918
## 1.3.0
1019

1120
* Add support for Python 3.13 by @hugovk in https://github.com/python/blurb/pull/26

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ classifiers = [
2525
"Programming Language :: Python :: 3.11",
2626
"Programming Language :: Python :: 3.12",
2727
"Programming Language :: Python :: 3.13",
28+
"Programming Language :: Python :: 3.14",
2829
]
2930
dynamic = [
3031
"version",
@@ -33,6 +34,7 @@ optional-dependencies.tests = [
3334
"pyfakefs",
3435
"pytest",
3536
"pytest-cov",
37+
"time-machine",
3638
]
3739
urls.Changelog = "https://github.com/python/blurb/blob/main/CHANGELOG.md"
3840
urls.Homepage = "https://github.com/python/blurb"
@@ -49,4 +51,4 @@ version-file = "src/blurb/_version.py"
4951
local_scheme = "no-local-version"
5052

5153
[tool.pyproject-fmt]
52-
max_supported_python = "3.13"
54+
max_supported_python = "3.14"

src/blurb/blurb.py

Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
import tempfile
5858
import textwrap
5959
import time
60-
import unittest
6160

6261
from . import __version__
6362

@@ -143,7 +142,6 @@ def unsanitize_section(section):
143142
return _unsanitize_section.get(section, section)
144143

145144
def next_filename_unsanitize_sections(filename):
146-
s = filename
147145
for key, value in _unsanitize_section.items():
148146
for separator in "/\\":
149147
key = f"{separator}{key}{separator}"
@@ -271,11 +269,6 @@ def __exit__(self, *args):
271269
os.chdir(self.previous_cwd)
272270

273271

274-
def safe_mkdir(path):
275-
if not os.path.exists(path):
276-
os.makedirs(path)
277-
278-
279272
def version_key(element):
280273
fields = list(element.split("."))
281274
if len(fields) == 1:
@@ -485,22 +478,25 @@ def finish_entry():
485478
# we'll complain about the *first* error
486479
# we see in the blurb file, which is a
487480
# better user experience.
488-
if key == "gh-issue" and int(value) < lowest_possible_gh_issue_number:
489-
throw(f"The gh-issue number must be {lowest_possible_gh_issue_number} or above, not a PR number.")
490-
491481
if key in issue_keys:
492482
try:
493483
int(value)
494484
except (TypeError, ValueError):
495-
throw(f"Invalid {issue_keys[key]} issue number! ({value!r})")
485+
throw(f"Invalid {issue_keys[key]} number: {value!r}")
486+
487+
if key == "gh-issue" and int(value) < lowest_possible_gh_issue_number:
488+
throw(f"Invalid gh-issue number: {value!r} (must be >= {lowest_possible_gh_issue_number})")
496489

497490
if key == "section":
498491
if no_changes:
499492
continue
500493
if value not in sections:
501494
throw(f"Invalid section {value!r}! You must use one of the predefined sections.")
502495

503-
if not 'section' in metadata:
496+
if "gh-issue" not in metadata and "bpo" not in metadata:
497+
throw("'gh-issue:' or 'bpo:' must be specified in the metadata!")
498+
499+
if 'section' not in metadata:
504500
throw("No 'section' specified. You must provide one!")
505501

506502
self.append((metadata, text))
@@ -560,7 +556,7 @@ def __str__(self):
560556

561557
def save(self, path):
562558
dirname = os.path.dirname(path)
563-
safe_mkdir(dirname)
559+
os.makedirs(dirname, exist_ok=True)
564560

565561
text = str(self)
566562
with open(path, "wt", encoding="utf-8") as file:
@@ -642,42 +638,6 @@ def save_next(self):
642638
return filename
643639

644640

645-
tests_run = 0
646-
647-
class TestParserPasses(unittest.TestCase):
648-
directory = "tests/pass"
649-
650-
def filename_test(self, filename):
651-
b = Blurbs()
652-
b.load(filename)
653-
self.assertTrue(b)
654-
if os.path.exists(filename + '.res'):
655-
with open(filename + '.res', encoding='utf-8') as file:
656-
expected = file.read()
657-
self.assertEqual(str(b), expected)
658-
659-
def test_files(self):
660-
global tests_run
661-
with pushd(self.directory):
662-
for filename in glob.glob("*"):
663-
if filename[-4:] == '.res':
664-
self.assertTrue(os.path.exists(filename[:-4]), filename)
665-
continue
666-
self.filename_test(filename)
667-
print(".", end="")
668-
sys.stdout.flush()
669-
tests_run += 1
670-
671-
672-
class TestParserFailures(TestParserPasses):
673-
directory = "tests/fail"
674-
675-
def filename_test(self, filename):
676-
b = Blurbs()
677-
with self.assertRaises(Exception):
678-
b.load(filename)
679-
680-
681641
readme_re = re.compile(r"This is \w+ version \d+\.\d+").match
682642

683643
def chdir_to_repo_root():
@@ -848,36 +808,6 @@ def _find_blurb_dir():
848808
return None
849809

850810

851-
@subcommand
852-
def test(*args):
853-
"""
854-
Run unit tests. Only works inside source repo, not when installed.
855-
"""
856-
# unittest.main doesn't work because this isn't a module
857-
# so we'll do it ourselves
858-
859-
while (blurb_dir := _find_blurb_dir()) is None:
860-
old_dir = os.getcwd()
861-
os.chdir("..")
862-
if old_dir == os.getcwd():
863-
# we reached the root and never found it!
864-
sys.exit("Error: Couldn't find the root of your blurb repo!")
865-
os.chdir(blurb_dir)
866-
867-
print("-" * 79)
868-
869-
for clsname, cls in sorted(globals().items()):
870-
if clsname.startswith("Test") and isinstance(cls, type):
871-
o = cls()
872-
for fnname in sorted(dir(o)):
873-
if fnname.startswith("test"):
874-
fn = getattr(o, fnname)
875-
if callable(fn):
876-
fn()
877-
print()
878-
print(tests_run, "tests passed.")
879-
880-
881811
def find_editor():
882812
for var in 'GIT_EDITOR', 'EDITOR':
883813
editor = os.environ.get(var)
@@ -1349,12 +1279,12 @@ def populate():
13491279
Creates and populates the Misc/NEWS.d directory tree.
13501280
"""
13511281
os.chdir("Misc")
1352-
safe_mkdir("NEWS.d/next")
1282+
os.makedirs("NEWS.d/next", exist_ok=True)
13531283

13541284
for section in sections:
13551285
dir_name = sanitize_section(section)
13561286
dir_path = f"NEWS.d/next/{dir_name}"
1357-
safe_mkdir(dir_path)
1287+
os.makedirs(dir_path, exist_ok=True)
13581288
readme_path = f"NEWS.d/next/{dir_name}/README.rst"
13591289
with open(readme_path, "wt", encoding="utf-8") as readme:
13601290
readme.write(f"Put news entry ``blurb`` files for the *{section}* section in this directory.\n")
@@ -1398,7 +1328,7 @@ def main():
13981328
fn = get_subcommand(subcommand)
13991329

14001330
# hack
1401-
if fn in (help, test, version):
1331+
if fn in (help, version):
14021332
sys.exit(fn(*args))
14031333

14041334
try:

0 commit comments

Comments
 (0)