Skip to content

Commit 158c2e4

Browse files
authored
Modernize package build/metadata (#5)
- Move package metadata from setup.cfg to PEP 621 in pyproject.toml - Switch build from setuptools to hatchling with hatch-vcs - Update templates to also use pyproject and hatchling - Test on 3.12 and 3.13 - Fix tests and lint
1 parent 9c8160e commit 158c2e4

File tree

9 files changed

+135
-117
lines changed

9 files changed

+135
-117
lines changed

.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[flake8]
2+
ignore = E203, E231, E266, E302, E501, W503
3+
max-line-length = 88

.github/workflows/build.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
fail-fast: false
1616
matrix:
17-
python-version: ["3.11", "3.12"]
17+
python-version: ["3.12", "3.13"]
1818
os: [macOS-latest, ubuntu-latest, windows-latest]
1919

2020
steps:
@@ -25,10 +25,7 @@ jobs:
2525
with:
2626
python-version: ${{ matrix.python-version }}
2727
- name: Install
28-
run: |
29-
python -m pip install --upgrade pip
30-
make setup
31-
pip install -U .
28+
run: make install
3229
- name: Test
3330
run: make test
3431
- name: Lint

Makefile

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
PYTHON?=python
2-
SOURCES=squatter setup.py
2+
SOURCES=squatter
3+
4+
UV:=$(shell uv --version)
5+
ifdef UV
6+
VENV:=uv venv
7+
PIP:=uv pip
8+
else
9+
VENV:=python -m venv
10+
PIP:=python -m pip
11+
endif
312

413
.PHONY: venv
514
venv:
6-
$(PYTHON) -m venv .venv
7-
source .venv/bin/activate && make setup
15+
$(VENV) .venv
16+
source .venv/bin/activate && make install
817
@echo 'run `source .venv/bin/activate` to use virtualenv'
918

1019
# The rest of these are intended to be run within the venv, where python points
1120
# to whatever was used to set up the venv.
1221

13-
.PHONY: setup
14-
setup:
15-
python -m pip install -Ur requirements-dev.txt
22+
.PHONY: install
23+
install:
24+
$(PIP) install -Ue .[dev]
1625

1726
.PHONY: test
1827
test:
@@ -27,10 +36,10 @@ format:
2736
lint:
2837
python -m ufmt check $(SOURCES)
2938
python -m flake8 $(SOURCES)
30-
mypy --strict squatter
39+
python -m mypy squatter
3140

3241
.PHONY: release
3342
release:
3443
rm -rf dist
35-
python setup.py sdist bdist_wheel
36-
twine upload dist/*
44+
hatch build
45+
hatch publish

pyproject.toml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[build-system]
2+
requires = ["hatchling", "hatch-vcs"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "squatter"
7+
readme = "README.md"
8+
authors = [
9+
{name="Amethyst Reese", email="amethyst@n7.gg"},
10+
{name="Tim Hatch"},
11+
]
12+
license = "MIT"
13+
license-files = ["LICENSE"]
14+
dynamic = ["version", "description"]
15+
requires-python = ">=3.12"
16+
dependencies = [
17+
"click >= 8",
18+
"hatch >= 1.14",
19+
]
20+
21+
[project.optional-dependencies]
22+
dev = [
23+
"black==25.1.0",
24+
"coverage==7.8.0",
25+
"flake8==7.2.0",
26+
"mypy==1.15.0",
27+
"tox==4.26.0",
28+
"ufmt==2.8.0",
29+
"usort==1.0.8",
30+
"volatile==2.1.0",
31+
]
32+
33+
[project.scripts]
34+
squatter = "squatter.__main__:cli"
35+
36+
[project.urls]
37+
Home = "https://github.com/python-packaging/squatter"
38+
39+
[tool.hatch.version]
40+
source = "vcs"
41+
42+
[tool.mypy]
43+
ignore_missing_imports = true
44+
strict = true
45+
46+
[tool.tox]
47+
env_list = ["3.12", "3.13"]
48+
49+
[tool.tox.env_run_base]
50+
commands = [["make", "test"]]
51+
extras = ["dev"]
52+
# set_env = { COVERAGE_FILE="{env:env_dir}/.coverage" }
53+
allowlist_externals = ["make"]

requirements-dev.txt

Lines changed: 0 additions & 10 deletions
This file was deleted.

setup.cfg

Lines changed: 0 additions & 55 deletions
This file was deleted.

setup.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

squatter/templates.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import glob
2-
import sys
31
from pathlib import Path
42
from subprocess import check_call, check_output
53
from typing import Optional
64

7-
SETUP_PY_TMPL = """\
8-
from setuptools import setup
5+
PYPROJECT_TEMPLATE = """\
6+
["build-system"]
7+
requires = ["hatchling"]
8+
build-backend = "hatchling.build"
9+
10+
[project]
11+
name = {package_name!r}
12+
description = "coming soon"
13+
version = "0.0.0a1"
14+
authors = [
15+
{{name={author!r}, email={author_email!r} }},
16+
]
17+
"""
918

10-
setup(
11-
name={package_name!r},
12-
description="coming soon",
13-
version="0.0.0a1",
14-
author={author!r},
15-
author_email={author_email!r},
16-
)
19+
INIT_TEMPLATE = """\
20+
'''coming soon'''
1721
"""
1822

1923

@@ -36,15 +40,16 @@ def generate(
3640
["git", "config", "user.email"], encoding="utf-8"
3741
).strip()
3842

39-
data = SETUP_PY_TMPL.format(**locals())
40-
(Path(self.staging_directory) / "setup.py").write_text(data)
43+
data = PYPROJECT_TEMPLATE.format(**locals())
44+
(Path(self.staging_directory) / "pyproject.toml").write_text(data)
45+
46+
pkg_dir = Path(self.staging_directory) / package_name.replace("-", "_")
47+
pkg_dir.mkdir(parents=True, exist_ok=True)
48+
(pkg_dir / "__init__.py").write_text(INIT_TEMPLATE)
4149

4250
def sdist(self) -> None:
43-
check_call([sys.executable, "setup.py", "sdist"], cwd=self.staging_directory)
51+
check_call(["hatch", "build"], cwd=self.staging_directory)
4452

4553
def upload(self) -> None:
4654
self.sdist()
47-
check_call(
48-
["twine", "upload"] + glob.glob(f"{self.staging_directory}/dist/*.tar.gz"),
49-
cwd=self.staging_directory,
50-
)
55+
check_call(["hatch", "upload"], cwd=self.staging_directory)

squatter/tests/__init__.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import tarfile
12
import unittest
23
from pathlib import Path
34
from subprocess import check_call
@@ -18,17 +19,26 @@ def test_env_smoke(self) -> None:
1819
env.generate("foo", "Author Name", "email@example.com")
1920
env.sdist()
2021

22+
tarballs = list(Path(d).rglob("*.tar.gz"))
2123
self.assertEqual(
2224
[Path(d) / "dist" / "foo-0.0.0a1.tar.gz"],
23-
list(Path(d).rglob("*.tar.gz")),
25+
tarballs,
2426
)
2527

26-
egg_info = list(Path(d).rglob("PKG-INFO"))
27-
self.assertEqual(1, len(egg_info))
28-
egg_info_text = egg_info[0].read_text()
29-
self.assertIn("\nName: foo\n", egg_info_text)
30-
self.assertIn("\nAuthor: Author Name\n", egg_info_text)
31-
self.assertIn("\nAuthor-email: email@example.com\n", egg_info_text)
28+
with tarfile.open(tarballs[0]) as tar:
29+
members = [
30+
member
31+
for member in tar.getmembers()
32+
if member.name.endswith("PKG-INFO")
33+
]
34+
pkg_info = tar.extractfile(members[0])
35+
assert pkg_info is not None
36+
egg_info_text = pkg_info.read()
37+
38+
self.assertIn(b"\nName: foo\n", egg_info_text)
39+
self.assertIn(
40+
b"\nAuthor-email: Author Name <email@example.com>\n", egg_info_text
41+
)
3242

3343
@patch("squatter.templates.check_output")
3444
def test_env_git_prompts(self, check_output_mock: Any) -> None:
@@ -43,17 +53,26 @@ def test_env_git_prompts(self, check_output_mock: Any) -> None:
4353
env.generate("foo")
4454
env.sdist()
4555

56+
tarballs = list(Path(d).rglob("*.tar.gz"))
4657
self.assertEqual(
4758
[Path(d) / "dist" / "foo-0.0.0a1.tar.gz"],
48-
list(Path(d).rglob("*.tar.gz")),
59+
tarballs,
4960
)
5061

51-
egg_info = list(Path(d).rglob("PKG-INFO"))
52-
self.assertEqual(1, len(egg_info))
53-
egg_info_text = egg_info[0].read_text()
54-
self.assertIn("\nName: foo\n", egg_info_text)
55-
self.assertIn("\nAuthor: Bob\n", egg_info_text)
56-
self.assertIn("\nAuthor-email: email@example.com\n", egg_info_text)
62+
with tarfile.open(tarballs[0]) as tar:
63+
members = [
64+
member
65+
for member in tar.getmembers()
66+
if member.name.endswith("PKG-INFO")
67+
]
68+
pkg_info = tar.extractfile(members[0])
69+
assert pkg_info is not None
70+
egg_info_text = pkg_info.read()
71+
72+
self.assertIn(b"\nName: foo\n", egg_info_text)
73+
self.assertIn(
74+
b"\nAuthor-email: Bob <email@example.com>\n", egg_info_text
75+
)
5776

5877
@patch("squatter.templates.check_output")
5978
@patch("squatter.templates.check_call")
@@ -68,11 +87,11 @@ def test_cli_functional(self, check_call_mock: Any, check_output_mock: Any) -> N
6887

6988
def patched_check_call(cmd: List[str], **kwargs: Any) -> Any:
7089
nonlocal uploads
71-
if cmd[0] != "twine":
90+
if cmd[0] != "hatch":
7291
return check_call(cmd, **kwargs)
7392
else:
74-
assert cmd[-1].endswith(".tar.gz")
75-
uploads += 1
93+
if "upload" in cmd:
94+
uploads += 1
7695

7796
check_call_mock.side_effect = patched_check_call
7897

0 commit comments

Comments
 (0)