diff --git a/.github/workflows/integtests.yaml b/.github/workflows/integtests.yaml
index cd1b305..11badb5 100644
--- a/.github/workflows/integtests.yaml
+++ b/.github/workflows/integtests.yaml
@@ -8,8 +8,8 @@ on:
jobs:
- # Single source of truth: the oldest supported Python is whatever setup.py
- # declares in `python_requires`. Every job below derives its cross-version
+ # Single source of truth: the oldest supported Python is whatever pyproject.toml
+ # declares in `requires-python`. Every job below derives its cross-version
# target from this output instead of hardcoding a version.
min-python:
runs-on: ubuntu-latest
@@ -17,10 +17,10 @@ jobs:
version: ${{ steps.minpy.outputs.version }}
steps:
- uses: actions/checkout@v4
- - name: Read minimum supported Python from setup.py
+ - name: Read minimum supported Python from pyproject.toml
id: minpy
run: |
- version=$(grep python_requires setup.py | grep -oE '[0-9]+\.[0-9]+' | head -1)
+ version=$(grep requires-python pyproject.toml | grep -oE '[0-9]+\.[0-9]+' | head -1)
echo "Minimum supported Python: $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
@@ -45,7 +45,7 @@ jobs:
- name: Simple fades run (newest Python)
run: |
cd /fades
- bin/fades -v -d pytest -x pytest --version
+ python3 -m fades -v -d pytest -x pytest --version
- name: Using the oldest supported Python
env:
OLDEST: ${{ needs.min-python.outputs.version }}
@@ -53,7 +53,7 @@ jobs:
cd /fades
uv python install "$OLDEST"
OLD=$(uv python find "$OLDEST")
- python bin/fades -v --python="$OLD" -d pytest -x pytest -v --integtest-pyversion="$OLDEST" tests/integtest.py
+ python -m fades -v --python="$OLD" -d pytest -x pytest -v --integtest-pyversion="$OLDEST" tests/integtest.py
fedora:
needs: min-python
@@ -73,7 +73,7 @@ jobs:
- name: Simple fades run
run: |
cd /fades
- bin/fades -v -d pytest -x pytest --version
+ python3 -m fades -v -d pytest -x pytest --version
- name: Using the oldest supported Python
env:
OLDEST: ${{ needs.min-python.outputs.version }}
@@ -81,7 +81,7 @@ jobs:
cd /fades
uv python install "$OLDEST"
OLD=$(uv python find "$OLDEST")
- python3 bin/fades -v --python="$OLD" -d pytest -x pytest -v --integtest-pyversion="$OLDEST" tests/integtest.py
+ python3 -m fades -v --python="$OLD" -d pytest -x pytest -v --integtest-pyversion="$OLDEST" tests/integtest.py
native-windows:
needs: min-python
@@ -89,7 +89,7 @@ jobs:
matrix:
# just a selection otherwise it's too much
# - latest OS (left here even if it's only one to simplify upgrading later)
- # - oldest (from setup.py) and newest Python
+ # - oldest (from pyproject.toml) and newest Python
os: [windows-2025]
python-version: ["${{ needs.min-python.outputs.version }}", "3.13"]
@@ -114,11 +114,11 @@ jobs:
${{ steps.matrixpy.outputs.python-path }} -m pip install -U packaging
- name: Simple fades run
run: |
- ${{ steps.matrixpy.outputs.python-path }} bin/fades -v -d pytest -x pytest --version
+ ${{ steps.matrixpy.outputs.python-path }} -m fades -v -d pytest -x pytest --version
- name: Using a different Python
run: |
- ${{ steps.matrixpy.outputs.python-path }} bin/fades -v --python=${{ steps.otherpy.outputs.python-path }} -d pytest -x pytest -v --integtest-pyversion=${{ needs.min-python.outputs.version }} tests/integtest.py
+ ${{ steps.matrixpy.outputs.python-path }} -m fades -v --python=${{ steps.otherpy.outputs.python-path }} -d pytest -x pytest -v --integtest-pyversion=${{ needs.min-python.outputs.version }} tests/integtest.py
native-generic:
needs: min-python
@@ -126,7 +126,7 @@ jobs:
matrix:
# just a selection otherwise it's too much
# - latest OSes
- # - oldest (from setup.py) and newest Python
+ # - oldest (from pyproject.toml) and newest Python
os: [ubuntu-24.04, macos-15]
python-version: ["${{ needs.min-python.outputs.version }}", "3.13"]
@@ -151,8 +151,8 @@ jobs:
${{ steps.matrixpy.outputs.python-path }} -m pip install -U packaging
- name: Simple fades run
run: |
- ${{ steps.matrixpy.outputs.python-path }} bin/fades -v -d pytest -x pytest --version
+ ${{ steps.matrixpy.outputs.python-path }} -m fades -v -d pytest -x pytest --version
- name: Using a different Python
run: |
- ${{ steps.matrixpy.outputs.python-path }} bin/fades -v --python=${{ steps.otherpy.outputs.python-path }} -d pytest -x pytest -v --integtest-pyversion=${{ needs.min-python.outputs.version }} tests/integtest.py
+ ${{ steps.matrixpy.outputs.python-path }} -m fades -v --python=${{ steps.otherpy.outputs.python-path }} -d pytest -x pytest -v --integtest-pyversion=${{ needs.min-python.outputs.version }} tests/integtest.py
diff --git a/HOWTO_RELEASE.txt b/HOWTO_RELEASE.txt
index dc4d9ad..0e62e28 100644
--- a/HOWTO_RELEASE.txt
+++ b/HOWTO_RELEASE.txt
@@ -1,11 +1,10 @@
Steps before a release is done
------------------------------
-Check all is crispy
+Check all is crispy (building needs the 'build' package: pip install build)
rm -rf build dist
- ./setup.py clean build
- ./setup.py clean sdist
+ python3 -m fades -d build -m build
Edit the ``fades/_version.py`` file properly, then tag and commit/push
@@ -21,7 +20,7 @@ How to release it to PyPI
Dead simple:
rm -rf build dist
- ./setup.py clean sdist
+ python3 -m fades -d build -m build
fades -d twine -x twine upload dist/fades-*
@@ -31,7 +30,7 @@ How to create a .deb
Create the tarball:
rm -rf build dist
- ./setup.py clean sdist
+ python3 -m fades -d build -m build --sdist
Copy this tarball to a clean dir, renaming as "orig"
@@ -155,9 +154,9 @@ Read the Docs
- go and login there, go to "fades" project
- in "Versions" tab, activate the new version (corresponding to the release in github)
- in "Administrator" tab, go to option "Advanced configuration" at the left, choose latest release as "default version"
-- verify all latest is seen in
+- verify all latest is seen in
- http://fades.rtfd.org/
+ http://fades.rtfd.org/
How to sign the files
diff --git a/bin/fades b/bin/fades
deleted file mode 100755
index 60ca5c5..0000000
--- a/bin/fades
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Copyright 2014 Facundo Batista, Nicolás Demarchi
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 3, as published
-# by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranties of
-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-# PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see .
-#
-# For further info, check https://github.com/PyAr/fades
-
-"""Script to run the 'fades' utility."""
-
-import os
-import sys
-
-try:
- import packaging
-except ImportError:
- print("Import failed for `packaging` dependency. Please do `pip3 install packaging` and try again")
- exit(-1)
-
-# small hack to allow fades to be run directly from the project, using code
-# from project itself, not anything already installed in the system
-parent_dir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
-if os.path.basename(parent_dir).startswith('fades'):
- # inside the project or an opened tarball!!
- sys.path.insert(0, parent_dir)
-
-from fades import main, FadesError # noqa (imports after fixing the path, not at the top)
-
-try:
- rc = main.go()
-except FadesError:
- sys.exit(-1)
-
-sys.exit(rc)
diff --git a/bin/fades.cmd b/bin/fades.cmd
deleted file mode 100644
index ca77f9c..0000000
--- a/bin/fades.cmd
+++ /dev/null
@@ -1,20 +0,0 @@
-::
-:: Copyright 2018 Facundo Batista, Nicolás Demarchi
-::
-:: This program is free software: you can redistribute it and/or modify it
-:: under the terms of the GNU General Public License version 3, as published
-:: by the Free Software Foundation.
-::
-:: This program is distributed in the hope that it will be useful, but
-:: WITHOUT ANY WARRANTY; without even the implied warranties of
-:: MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-:: PURPOSE. See the GNU General Public License for more details.
-::
-:: You should have received a copy of the GNU General Public License along
-:: with this program. If not, see .
-::
-:: For further info, check https://github.com/PyAr/fades
-
-:: Script to run the 'fades' utility in Windows
-
-python -m fades %*
diff --git a/build_readme b/build_readme
index be671c9..68e2614 100755
--- a/build_readme
+++ b/build_readme
@@ -1,2 +1 @@
-FADES='./bin/fades -r requirements.txt'
-python3 setup.py --long-description | $FADES -x rst2html5 > README.html
+python3 -m fades -r requirements.txt -x rst2html5 < README.rst > README.html
diff --git a/fades/__main__.py b/fades/__main__.py
index 66933c2..42f8716 100644
--- a/fades/__main__.py
+++ b/fades/__main__.py
@@ -1,12 +1,5 @@
"""Init file to allow execution of fades as a module."""
-import sys
+from fades.main import console_entry_point
-from fades import main, FadesError
-
-try:
- rc = main.go()
-except FadesError:
- sys.exit(-1)
-
-sys.exit(rc)
+console_entry_point()
diff --git a/fades/main.py b/fades/main.py
index 7256691..b937b65 100644
--- a/fades/main.py
+++ b/fades/main.py
@@ -506,3 +506,16 @@ def _signal_handler(signum, _):
if rc:
logger.debug("Child process not finished correctly: returncode=%d", rc)
return rc
+
+
+def console_entry_point():
+ """Run fades as a command line program, handling its own errors.
+
+ This is the target of the 'fades' console script declared in
+ pyproject.toml and is also used to run fades as a module.
+ """
+ try:
+ rc = go()
+ except FadesError:
+ sys.exit(-1)
+ sys.exit(rc)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..865c3b4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,90 @@
+# Copyright 2014-2026 Facundo Batista, Nicolás Demarchi
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+#
+# For further info, check https://github.com/PyAr/fades
+
+[build-system]
+requires = ["setuptools>=61"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "fades"
+dynamic = ["version"]
+description = "A system that automatically handles the virtualenvs in the cases normally found when writing scripts and simple programs, and even helps to administer big projects."
+readme = "README.rst"
+license = {text = "GPL-3"}
+authors = [
+ {name = "Facundo Batista", email = "facundo@taniquetil.com.ar"},
+ {name = "Nicolás Demarchi", email = "mail@gilgamezh.me"},
+]
+requires-python = ">=3.10"
+keywords = ["virtualenv", "utils", "utility", "scripts"]
+dependencies = ["packaging"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+
+ "Environment :: Console",
+
+ "Intended Audience :: Developers",
+ "Intended Audience :: System Administrators",
+
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+
+ "Natural Language :: English",
+ "Natural Language :: Spanish",
+
+ "Operating System :: MacOS",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX",
+ "Operating System :: POSIX :: Linux",
+
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: Implementation",
+ "Programming Language :: Python :: Implementation :: CPython",
+
+ "Topic :: Software Development",
+ "Topic :: Utilities",
+]
+
+[project.optional-dependencies]
+pyxdg = ["pyxdg"]
+
+[project.urls]
+Homepage = "https://github.com/PyAr/fades"
+Documentation = "https://fades.readthedocs.io/"
+Download = "https://github.com/PyAr/fades/releases"
+
+[project.scripts]
+fades = "fades.main:console_entry_point"
+
+[tool.setuptools]
+packages = ["fades"]
+
+[tool.setuptools.dynamic]
+version = {attr = "fades._version.__version__"}
+
+# NOTE: the man page is installed under /share/man/man1 via data_files
+# declared in setup.py -- PEP 621 / pyproject.toml has no equivalent for it, and
+# modern setuptools no longer accepts data_files in this file. That tiny setup.py
+# is the only remaining piece of the legacy build; everything else lives here.
diff --git a/setup.py b/setup.py
old mode 100755
new mode 100644
index 737ea0a..a615ad6
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi
+# Copyright 2014-2026 Facundo Batista, Nicolás Demarchi
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
@@ -16,138 +16,16 @@
#
# For further info, check https://github.com/PyAr/fades
-"""Build tar.gz for fades.
+"""Shim over the declarative pyproject.toml, only for the man page.
-Needed packages to run (using Debian/Ubuntu package names):
-
- python3
- python3-xdg (optional)
+All the packaging metadata lives in ``pyproject.toml``. The single thing that
+cannot be expressed there is installing the man page under
+``/share/man/man1`` (there is no PEP 621 mechanism for ``data_files``,
+and modern setuptools rejects it in ``pyproject.toml``). That's the only reason
+this file still exists; it is consumed by ``pip``, the Debian ``pybuild`` flow
+and the snap build.
"""
-import os
-import re
-import shutil
-import sys
-
-from distutils.core import setup
-from setuptools.command.install import install
-
-
-def get_version():
- """Retrieves package version from the file."""
- with open('fades/_version.py') as fh:
- m = re.search(r"\(([^']*)\)", fh.read())
- if m is None:
- raise ValueError("Unrecognized version in 'fades/_version.py'")
- return m.groups()[0].replace(', ', '.')
-
-
-# the different scripts according to the platform
-SCRIPT_WIN = 'bin/fades.cmd'
-SCRIPT_REST = 'bin/fades'
-
-
-class CustomInstall(install):
- """Custom installation to fix script info and install man."""
-
- def initialize_options(self):
- """Run parent initialization and then fix the scripts var."""
- install.initialize_options(self)
-
- # leave the proper script according to the platform
- script = SCRIPT_WIN if sys.platform == "win32" else SCRIPT_REST
- self.distribution.scripts = [script]
-
- def run(self):
- """Run parent install, and then save the man file."""
- install.run(self)
-
- # man directory
- if self._custom_man_dir is not None:
- if not os.path.exists(self._custom_man_dir):
- os.makedirs(self._custom_man_dir)
- shutil.copy("man/fades.1", self._custom_man_dir)
-
- def finalize_options(self):
- """Alter the installation path."""
- install.finalize_options(self)
- if self.prefix is None:
- # no place for man page (like in a 'snap')
- man_dir = None
- else:
- man_dir = os.path.join(self.prefix, "share", "man", "man1")
-
- # if we have 'root', put the building path also under it (used normally
- # by pbuilder)
- if self.root is not None:
- man_dir = os.path.join(self.root, man_dir[1:])
- self._custom_man_dir = man_dir
-
-
-setup(
- name='fades',
- version=get_version(),
- license='GPL-3',
- author='Facundo Batista, Nicolás Demarchi',
- author_email='facundo@taniquetil.com.ar, mail@gilgamezh.me',
- description=(
- 'A system that automatically handles the virtualenvs in the cases '
- 'normally found when writing scripts and simple programs, and '
- 'even helps to administer big projects.'),
- long_description=open('README.rst').read(),
- url='https://github.com/PyAr/fades',
- download_url="https://github.com/PyAr/fades/releases", # Release download URL.
- packages=["fades"],
- scripts=[SCRIPT_WIN, SCRIPT_REST],
- keywords="virtualenv utils utility scripts", # to get found easily on PyPI results, etc.
- cmdclass={
- 'install': CustomInstall,
- },
- install_requires=['setuptools'],
- tests_require=['logassert', 'pyxdg', 'pyuca', 'pytest', 'flake8',
- 'pep257', 'rst2html5'], # what unittests require
- python_requires='>=3.10', # Minimum Python version supported.
- extras_require={
- 'pyxdg': 'pyxdg',
- 'packaging': 'packaging',
- },
-
- # https://pypi.python.org/pypi?%3Aaction=list_classifiers
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- # 'Development Status :: 6 - Mature',
- # 'Development Status :: 7 - Inactive',
-
- 'Environment :: Console',
-
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
-
- 'License :: OSI Approved :: GNU General Public License (GPL)',
- 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
- 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
-
- 'Natural Language :: English',
- 'Natural Language :: Spanish',
-
- 'Operating System :: MacOS',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: Microsoft',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: POSIX',
- 'Operating System :: POSIX :: Linux',
-
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.10',
- 'Programming Language :: Python :: 3.11',
- 'Programming Language :: Python :: 3.12',
- 'Programming Language :: Python :: 3.13',
- 'Programming Language :: Python :: Implementation',
- 'Programming Language :: Python :: Implementation :: CPython',
+from setuptools import setup
- 'Topic :: Software Development',
- 'Topic :: Utilities',
- ],
-)
+setup(data_files=[("share/man/man1", ["man/fades.1"])])
diff --git a/test b/test
index c9f758b..74ec9de 100755
--- a/test
+++ b/test
@@ -10,7 +10,7 @@ else
TARGET_TESTS=""
fi
-./bin/fades -r requirements.txt -d pytest-cov -x pytest --cov=fades $TARGET_TESTS
+python3 -m fades -r requirements.txt -d pytest-cov -x pytest --cov=fades $TARGET_TESTS
# check if we are using exit() in the code.
if grep -r -n ' exit(' --include="*.py" .; then echo 'Please use sys.exit() instead of exit(). https://github.com/PyAr/fades/issues/280'; fi
diff --git a/testdev b/testdev
index 8c38e68..8831865 100755
--- a/testdev
+++ b/testdev
@@ -10,4 +10,4 @@ else
TARGET_TESTS=""
fi
-./bin/fades -r requirements.txt -x pytest -s $TARGET_TESTS
+python3 -m fades -r requirements.txt -x pytest -s $TARGET_TESTS
diff --git a/testdev.bat b/testdev.bat
index e0c3bb7..42d2b0c 100644
--- a/testdev.bat
+++ b/testdev.bat
@@ -8,4 +8,4 @@ if not [%*] == [] (
set TARGET_TESTS=fades tests
)
-bin\fades -r requirements.txt -x pytest -v %TARGET_TESTS%
+python -m fades -r requirements.txt -x pytest -v %TARGET_TESTS%