Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
git config --global user.name "Tester"
git config --global user.email "test@example.com"

- uses: astral-sh/setup-uv@v10.3.2
- uses: astral-sh/setup-uv@v10.3.3
- run: uvx --with tox-uv tox -e py,docs,style
- run: uv build
- uses: pypa/gh-action-pypi-publish@release/v1
5 changes: 2 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ jobs:
git config --global user.name "Tester"
git config --global user.email "test@example.com"

- uses: astral-sh/setup-uv@v10.3.2
- uses: astral-sh/setup-uv@v10.3.3
- run: uvx --with tox-uv tox -e py

- uses: coverallsapp/github-action@v2
with:
file: .tox/test-reports/coverage.xml
flag-name: python-${{ matrix.python-version }}
parallel: true

coveralls-finish:
Expand All @@ -59,5 +58,5 @@ jobs:
with:
python-version: "3.15"

- uses: astral-sh/setup-uv@v10.3.2
- uses: astral-sh/setup-uv@v10.3.3
- run: uvx --with tox-uv tox -e docs,style
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 4.0.1

- Leaner sdist: no longer bundles `tests/`, `examples/`, `docs/` and `tox.ini`

- Internal project modernizations:

- Code modernized for py3.10+ (f-strings, removed py2 leftovers), enabled ruff `UP` rule

- Using [check-sdist](https://pypi.org/project/check-sdist/) via `twine check --strict`

- Bumped GitHub actions

## 4.0.0 - 2026-10-27

### Changed
Expand Down
6 changes: 1 addition & 5 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
include LICENSE
include CHANGELOG.md
include README.rst
include tox.ini
recursive-include docs *.md
graft examples
graft tests
global-exclude __pycache__ *.py[cod]
prune tests
6 changes: 4 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ time, a bug breaks other people's builds: test coverage is kept at 100%.
oldest and newest Python, and `coverage` combines both runs: that's the cheap way to get full
coverage locally, CI covers the versions in between.

`tox -e docs` runs `check-manifest`, and the README render check PyPI relies on. Files added to
the repo must be covered by [MANIFEST.in](../MANIFEST.in) or ignored in `tox.ini`.
`tox -e docs` runs `check-sdist` (sdist contents must match git-tracked files), then builds the
sdist and wheel and runs `twine check --strict` on them (the README render check PyPI relies on).
Files added to the repo must be covered by [MANIFEST.in](../MANIFEST.in) or listed under
`[tool.check-sdist]` in [pyproject.toml](../pyproject.toml).

## Scenario snapshots

Expand Down
2 changes: 1 addition & 1 deletion examples/single/expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
\_: (single.py:5 ) Someone someone@example.com
author_email: (auto-adjust ) someone@example.com
description: (README.rst:1) A package implemented by a single .py file
\_: (setup.py:3 ) Unicode chars in setup.py 😀 😎
\_: (setup.py:2 ) Unicode chars in setup.py 😀 😎
download_url: (missing ) - Consider specifying 'download_url'
license: (single.py:4 ) MIT
long_description: (README.rst ) single: A package implemented by a single .py file ================================================== This example shows: * `name` ...
Expand Down
1 change: 0 additions & 1 deletion examples/single/setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# coding=utf-8
"""
Unicode chars in setup.py 😀 😎
"""
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[tool.check-sdist]
git-only = [".gitignore", "AGENTS.md", "docs/", "examples/", "tests/", "tox.ini"]

[tool.ruff]
cache-dir = ".tox/.ruff_cache"
line-length = 140
target-version = "py310"

[tool.ruff.lint]
ignore = ["RUF005", "RUF012"]
Expand Down Expand Up @@ -38,6 +42,7 @@ select = [
"TCH", # flake8-type-checking
# "TD", # flake8-todos
"TRY", # tryceratops
"UP", # pyupgrade
"W", # pycodestyle warnings
]
[tool.ruff.lint.per-file-ignores]
Expand Down
45 changes: 22 additions & 23 deletions setupmeta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
PKGID = "[A-Za-z0-9][-A-Za-z0-9_.]*"

# Simplistic parsing of known formats used in requirements.txt
RE_SIMPLE_PIN = re.compile(r"^(%s)\s*==\s*([^;\s]+)\s*(;.*)?$" % PKGID)
RE_SIMPLE_PIN = re.compile(rf"^({PKGID})\s*==\s*([^;\s]+)\s*(;.*)?$")
RE_WORDS = re.compile(r"\W+")
RE_PKG_NAME = re.compile(r"^(%s)$" % PKGID)
RE_PKG_NAME = re.compile(rf"^({PKGID})$")

ABSTRACT = "abstract"
INDIRECT = "indirect"
Expand Down Expand Up @@ -268,11 +268,11 @@ def quoted(text):
def _strs(value, bracket, first, sep, quote, indent):
"""Stringified iterable"""
if isinstance(value, dict):
rep = sep.join("%s: %s" % (stringify(k, quote=quote), stringify(v, quote=quote, indent=indent)) for k, v in sorted(value.items()))
return "{%s%s%s}" % (first, rep, first[:-4])
rep = sep.join(f"{stringify(k, quote=quote)}: {stringify(v, quote=quote, indent=indent)}" for k, v in sorted(value.items()))
return f"{{{first}{rep}{first[:-4]}}}"

quote = quote or quote is None
return "%s%s%s%s%s" % (bracket[0], first, sep.join(stringify(s, quote=quote, indent=indent) for s in value), first[:-4], bracket[1])
return f"{bracket[0]}{first}{sep.join(stringify(s, quote=quote, indent=indent) for s in value)}{first[:-4]}{bracket[1]}"


def _strm(value, bracket, quote, indent, chars=80):
Expand All @@ -281,8 +281,8 @@ def _strm(value, bracket, quote, indent, chars=80):
if len(value) <= 1 or len(result) <= chars:
return result

sep = ",\n%s" % indent if indent else ", "
first = "\n%s" % indent if indent else ""
sep = f",\n{indent}" if indent else ", "
first = f"\n{indent}" if indent else ""
return _strs(value, bracket, first, sep, quote, indent)


Expand All @@ -298,12 +298,12 @@ def stringify(value, quote=None, indent=""):
return _strm(value, "{}", quote=quote, indent=indent)

if callable(value):
return "function '%s'" % value.__name__
return f"function '{value.__name__}'"

if quote:
return quoted("%s" % value)
return quoted(f"{value}")

return "%s" % value
return f"{value}"


def listify(text, separator=None):
Expand Down Expand Up @@ -335,17 +335,17 @@ def readlines(relative_path, limit=0):
try:
result = []
full_path = project_path(relative_path)
with open(full_path, "rt") as fh:
with open(full_path) as fh:
for line in fh:
limit -= 1
if limit == 0:
break

result.append(line)

trace("read %s lines from %s" % (len(result), relative_path))
trace(f"read {len(result)} lines from {relative_path}")

except IOError:
except OSError:
return None

else:
Expand Down Expand Up @@ -406,7 +406,7 @@ def standard_req(line):
return line


class ReqEntry(object):
class ReqEntry:
def __init__(self, parent, source_path, line_number, parent_section, line):
"""
:param RequirementsFile parent: Requirements.txt file where this line came from
Expand Down Expand Up @@ -473,8 +473,8 @@ def __init__(self, parent, source_path, line_number, parent_section, line):
prev = self.requirement
name = m.group(1)
spec = m.group(3)
self.requirement = name if not spec else "%s%s" % (name, spec)
trace(" abstracted [%s] -> [%s]" % (prev, self.requirement))
self.requirement = name if not spec else f"{name}{spec}"
trace(f" abstracted [{prev}] -> [{self.requirement}]")
self.abstracted = True

def __repr__(self):
Expand All @@ -501,13 +501,13 @@ def section(self):

@property
def source_description(self):
msg = "from %s:%s" % (self.source or "adhoc", self.line_number)
msg = f"from {self.source or 'adhoc'}:{self.line_number}"
if self.abstracted is not None:
if self.local_section:
msg += ", '%s' stated on line" % self.local_section
msg += f", '{self.local_section}' stated on line"

elif self.parent_section:
msg += ", in '%s' section" % self.parent_section
msg += f", in '{self.parent_section}' section"

elif self.abstracted:
msg += ", abstracted by default"
Expand Down Expand Up @@ -548,11 +548,10 @@ def iterate_req_txt(seen, parent, source_path, lines):

continue

trace(" req entry: %s" % req_entry)
trace(f" req entry: {req_entry}")
if req_entry.refers and req_entry.refers not in seen:
seen.add(req_entry.refers)
for r in iterate_req_txt(seen, parent, req_entry.refers, readlines(req_entry.refers)):
yield r
yield from iterate_req_txt(seen, parent, req_entry.refers, readlines(req_entry.refers))

elif req_entry.requirement not in seen:
seen.add(req_entry.requirement)
Expand Down Expand Up @@ -632,7 +631,7 @@ def find_requirements(*relative_paths):
path = project_path(path)
if os.path.isfile(path):
do_abstract = not path.endswith(".in")
trace("found requirements: %s %s" % (path, " (auto-abstracted)" if do_abstract else ""))
trace(f"found requirements: {path} {' (auto-abstracted)' if do_abstract else ''}")
r = RequirementsFile.from_file(path, do_abstract=do_abstract)
if r is not None:
return r
Expand Down
43 changes: 20 additions & 23 deletions setupmeta/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,16 @@ def _show_requirements_synopsis(self):
"""Show how many requirements were auto-abstracted or ignored, if any"""
reqs = self.setupmeta.requirements and self.setupmeta.requirements.install_requires
if reqs and reqs.filled_requirements and (reqs.abstracted or reqs.ignored):
message = "[setupmeta] install_requires: %s abstracted, %s ignored, %s untouched" % (
len(reqs.abstracted),
len(reqs.ignored),
len(reqs.untouched),
)
print(message)
counts = f"{len(reqs.abstracted)} abstracted, {len(reqs.ignored)} ignored, {len(reqs.untouched)} untouched"
print(f"[setupmeta] install_requires: {counts}")

def _show_git_status(self):
if self.setupmeta.versioning:
scm = self.setupmeta.versioning.scm
if scm:
diff = scm.get_diff_report()
if diff:
print("Pending changes:\n%s" % diff)
print(f"Pending changes:\n{diff}")


@MetaCommand
Expand Down Expand Up @@ -141,13 +137,13 @@ def finalize_options(self):

def check_recommend(self, key, hint=None):
if key not in self.setupmeta.definitions:
hint = ", %s" % hint if hint else ""
self.setupmeta.auto_fill(key, "- Consider specifying '%s'%s" % (key, hint), "missing")
hint = f", {hint}" if hint else ""
self.setupmeta.auto_fill(key, f"- Consider specifying '{key}'{hint}", "missing")

def represented_req(self, name, source_description, align):
name = '"%s",' % name
name = f'"{name}",'
if source_description:
fmt = "%%-%ss# %%s" % align
fmt = f"%-{align}s# %s"
name = fmt % (name, source_description)

return name
Expand All @@ -172,9 +168,10 @@ def show_requirements(self, setup_key, requirements):
for i, name in enumerate(names):
content.append(self.represented_req(name, source_descriptions[i], longest_name))

content = "[\n %s\n ]," % "\n ".join(content).strip()
indent = "\n "
content = f"[{indent}{indent.join(content).strip()}\n ],"

print(" %s=%s" % (setup_key, content))
print(f" {setup_key}={content}")

def show_dependencies(self):
"""Copy-pastable code snippet with install_requires"""
Expand All @@ -193,7 +190,7 @@ def show_expanded_python(self):

version = definitions.get("version")
if version:
print("__version__ = %s\n\n" % setupmeta.stringify(version.value, quote=True))
print(f"__version__ = {setupmeta.stringify(version.value, quote=True)}\n\n")

print("setup(")

Expand All @@ -215,13 +212,13 @@ def show_expanded_python(self):
elif definition.key == "download_url":
if version and version.value in definition.value:
definition.value = definition.value.replace(version.value, "%s")
definition.value = "%s %% __version__" % setupmeta.stringify(setupmeta.short(definition.value), quote=True)
definition.value = f"{setupmeta.stringify(setupmeta.short(definition.value), quote=True)} % __version__"

else:
definition.value = setupmeta.stringify(definition.value, quote=True, indent=" ")

elif definition.key == "long_description":
definition.value = "open(%s).read()" % setupmeta.stringify(setupmeta.short(definition.source), quote=True)
definition.value = f"open({setupmeta.stringify(setupmeta.short(definition.source), quote=True)}).read()"

elif definition.key == "version":
definition.value = "__version__"
Expand All @@ -235,18 +232,18 @@ def show_expanded_python(self):
longest = longest_line([d.value for d in defs])
for definition in defs:
if definition.key == "versioning":
line = " # versioning=%s," % definition.value
line = f" # versioning={definition.value},"

else:
line = " %s=%s," % (definition.key, definition.value)
line = f" {definition.key}={definition.value},"

source = definition.actual_source
if source and source != "explicit":
comment = "# from %s" % setupmeta.short(source)
comment = f"# from {setupmeta.short(source)}"
rest, _, last_line = line.rpartition("\n")
padding = " " * max(1, longest - len(last_line))
last_line = "%s%s%s" % (last_line, padding, comment)
line = "%s\n%s" % (rest, last_line) if rest else last_line
last_line = f"{last_line}{padding}{comment}"
line = f"{rest}\n{last_line}" if rest else last_line

print(line)

Expand Down Expand Up @@ -279,7 +276,7 @@ def run(self):
longest_key = min(30, max(len(key) for key in definitions))
sources = [s for d in definitions.values() for s in d.sources]
longest_source = min(40, max(len(s.source) for s in sources))
form = "%%%ss: (%%%ss) %%s" % (longest_key, -longest_source)
form = f"%{longest_key}s: (%{-longest_source}s) %s"
max_chars = max(60, self.chars - longest_key - longest_source - 5)

for definition in sorted(definitions.values()):
Expand All @@ -288,7 +285,7 @@ def run(self):
prefix = "\\_"

elif source.key not in setupmeta.MetaDefs.all_fields:
prefix = "%s*" % source.key
prefix = f"{source.key}*"

else:
prefix = source.key
Expand Down
6 changes: 3 additions & 3 deletions setupmeta/license.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ def match(self, contents):
pre = "Affero "

if version:
post = "v%s" % version[0]
post = f"v{version[0]}"

short = "%sGPL%s" % (pre and pre[0], post)
short = f"{pre and pre[0]}GPL{post}"

if short == "Apache" and version:
# Most project seem to abbreviate this to "Apache 2.0"
short = "%s %s" % (self.short, version)
short = f"{self.short} {version}"

return short

Expand Down
Loading
Loading