From e52c35e34899bc21a389aa7f4fe5084423cf538c Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Thu, 25 Jan 2024 12:04:54 -0500 Subject: [PATCH 01/16] Ci - Normalize accented text twice. (#143) * pre normalize, upversion node support in ci/cd, more test --- .github/workflows/ci.yml | 4 ++-- .github/workflows/dev.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- CHANGELOG.md | 5 +++-- slugify/__version__.py | 2 +- slugify/slugify.py | 9 ++++++--- test.py | 4 ++++ 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36959b0..71bc219 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c12b80a..88791a7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -17,9 +17,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb66dc5..7a9c77e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 395e538..eb60bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Work in progress -- Added typing to API and expose `py.typed`. -- Formally support 3.12 +## 8.0.2 + +- Normalize text before converting to unicode. (@chuckyblack - thx) ## 8.0.1 diff --git a/slugify/__version__.py b/slugify/__version__.py index a558d9b..dbbff9f 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.1' +__version__ = '8.0.2' diff --git a/slugify/slugify.py b/slugify/slugify.py index 21bdaeb..9242e3e 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -118,8 +118,11 @@ def slugify( # replace quotes with dashes - pre-process text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) - # decode unicode - if not allow_unicode: + # normalize text, convert to unicode if required + if allow_unicode: + text = unicodedata.normalize('NFKC', text) + else: + text = unicodedata.normalize('NFKD', text) text = unidecode.unidecode(text) # ensure text is still in unicode @@ -144,7 +147,7 @@ def slugify( except Exception: pass - # translate + # re normalize text if allow_unicode: text = unicodedata.normalize('NFKC', text) else: diff --git a/test.py b/test.py index 931f38f..2534499 100644 --- a/test.py +++ b/test.py @@ -36,6 +36,10 @@ def test_phonetic_conversion_of_eastern_scripts(self): self.assertEqual(r, "ying-shi-ma") def test_accented_text(self): + txt = '𝐚́́𝕒́àáâäãąā' + r = slugify(txt) + self.assertEqual(r, "aaaaaaaaa") + txt = 'C\'est déjà l\'été.' r = slugify(txt) self.assertEqual(r, "c-est-deja-l-ete") From 26b81c2e224ebb65c7fba40d37d17d762be3782f Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Wed, 31 Jan 2024 13:16:35 -0500 Subject: [PATCH 02/16] Drop compatibility for unsupported Python Version (#147) Drop compatibility for unsupported Python Version (8.0.3) ---- Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com> --- CHANGELOG.md | 5 ++++- slugify/__main__.py | 2 +- slugify/__version__.py | 2 +- slugify/slugify.py | 6 +----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb60bee..d64cb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -## Work in progress +## 8.0.3 + +- Drop compatibility for unsupported Python Version (@Viicos - thx) +- Fix pattern types. ## 8.0.2 diff --git a/slugify/__main__.py b/slugify/__main__.py index d31a6bb..4cc4616 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import, annotations, print_function +from __future__ import annotations import argparse import sys diff --git a/slugify/__version__.py b/slugify/__version__.py index dbbff9f..12c76b6 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.2' +__version__ = '8.0.3' diff --git a/slugify/slugify.py b/slugify/slugify.py index 9242e3e..09c7e07 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,7 +1,6 @@ from __future__ import annotations import re -import sys import unicodedata from collections.abc import Iterable from html.entities import name2codepoint @@ -83,7 +82,7 @@ def slugify( separator: str = DEFAULT_SEPARATOR, save_order: bool = False, stopwords: Iterable[str] = (), - regex_pattern: str | None = None, + regex_pattern: re.Pattern[str] | str | None = None, lowercase: bool = True, replacements: Iterable[Iterable[str]] = (), allow_unicode: bool = False, @@ -153,9 +152,6 @@ def slugify( else: text = unicodedata.normalize('NFKD', text) - if sys.version_info < (3,): - text = text.encode('ascii', 'ignore') - # make the text lowercase (optional) if lowercase: text = text.lower() From 243354893ee8cc270aaae82eff5fe3ae37481439 Mon Sep 17 00:00:00 2001 From: Val N Date: Thu, 8 Feb 2024 13:31:20 -0500 Subject: [PATCH 03/16] Uppercase handling of special chars (#149) * Remove compatibility with unsupported Python versions (#146) * fix uppercase pre-translations (#148) Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com> Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ slugify/__version__.py | 2 +- slugify/special.py | 1 - test.py | 5 ++++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d64cb0e..9aa0ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.4 + +- Properly handle uppercase special characters (@mib1185 - thx) + ## 8.0.3 - Drop compatibility for unsupported Python Version (@Viicos - thx) diff --git a/slugify/__version__.py b/slugify/__version__.py index 12c76b6..854038e 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.3' +__version__ = '8.0.4' diff --git a/slugify/special.py b/slugify/special.py index 0b602cf..918cb2a 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -9,7 +9,6 @@ def add_uppercase_char(char_list: list[tuple[str, str]]) -> list[tuple[str, str] upper_dict = char.upper(), xlate.capitalize() if upper_dict not in char_list and char != upper_dict[0]: char_list.insert(0, upper_dict) - return char_list return char_list diff --git a/test.py b/test.py index 2534499..d13ef94 100644 --- a/test.py +++ b/test.py @@ -4,6 +4,7 @@ import unittest from contextlib import contextmanager +from slugify import PRE_TRANSLATIONS from slugify import slugify from slugify import smart_truncate from slugify.__main__ import slugify_params, parse_args @@ -236,9 +237,11 @@ def test_replacements_german_umlaut_custom(self): r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) self.assertEqual(r, "ueber-ueber-german-umlaut") + def test_pre_translation(self): + self.assertEqual(PRE_TRANSLATIONS, [('Ю', 'U'), ('Щ', 'Sch'), ('У', 'Y'), ('Х', 'H'), ('Я', 'Ya'), ('Ё', 'E'), ('ё', 'e'), ('я', 'ya'), ('х', 'h'), ('у', 'y'), ('щ', 'sch'), ('ю', 'u'), ('Ü', 'Ue'), ('Ö', 'Oe'), ('Ä', 'Ae'), ('ä', 'ae'), ('ö', 'oe'), ('ü', 'ue'), ('Ϋ́', 'Y'), ('Ϋ', 'Y'), ('Ύ', 'Y'), ('Υ', 'Y'), ('Χ', 'Ch'), ('χ', 'ch'), ('Ξ', 'X'), ('ϒ', 'Y'), ('υ', 'y'), ('ύ', 'y'), ('ϋ', 'y'), ('ΰ', 'y')]) -class TestSlugifyUnicode(unittest.TestCase): +class TestSlugifyUnicode(unittest.TestCase): def test_extraneous_seperators(self): txt = "This is a test ---" From efa13c6596b4c55a6cd1b0fe5d3ecf0de793e431 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 1 Mar 2024 13:19:24 -0500 Subject: [PATCH 04/16] add tea file --- tea.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tea.yml diff --git a/tea.yml b/tea.yml new file mode 100644 index 0000000..dda3df9 --- /dev/null +++ b/tea.yml @@ -0,0 +1,7 @@ +# https://tea.xyz/what-is-this-file +--- +version: 1.0.0 +codeOwners: + - '0xaC8Bb28685BD43FD784DC902E132829c6C6DafA2' +quorum: 1 + From 872b37509399a7f02e53f46ad9881f63f66d334b Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 1 Mar 2024 13:21:15 -0500 Subject: [PATCH 05/16] rename tea --- tea.yml => tea.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tea.yml => tea.yaml (100%) diff --git a/tea.yml b/tea.yaml similarity index 100% rename from tea.yml rename to tea.yaml From 6f381ac152ec1bf2ba7686cbf7d0c23b3d5d370b Mon Sep 17 00:00:00 2001 From: Mark Steward Date: Fri, 26 Sep 2025 16:19:57 +0100 Subject: [PATCH 06/16] Explain `save_order` more clearly (#157) --- README.md | 12 ++++++++---- slugify/slugify.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0dfbd92..c796aa4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ def slugify( :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) :param max_length (int): output string length :param word_boundary (bool): truncates to end of full words (length may be shorter than max_length) - :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param save_order (bool): when set, does not include shorter subsequent words even if they fit :param separator (str): separator between words :param stopwords (iterable): words to discount :param regex_pattern (str): regex pattern for disallowed characters @@ -108,9 +108,13 @@ txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=20, word_boundary=True, separator=".") self.assertEqual(r, "jaja.lol.mememeoo.a") -txt = 'one two three four five' -r = slugify(txt, max_length=13, word_boundary=True, save_order=True) -self.assertEqual(r, "one-two-three") +txt = 'one two three four' +r = slugify(txt, max_length=12, word_boundary=True, save_order=False) +self.assertEqual(r, "one-two-four") + +txt = 'one two three four' +r = slugify(txt, max_length=12, word_boundary=True, save_order=True) +self.assertEqual(r, "one-two") txt = 'the quick brown fox jumps over the lazy dog' r = slugify(txt, stopwords=['the']) diff --git a/slugify/slugify.py b/slugify/slugify.py index 09c7e07..67b31c8 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -95,7 +95,7 @@ def slugify( :param hexadecimal (bool): converts html hexadecimal to unicode :param max_length (int): output string length :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length - :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param save_order (bool): when set, does not include shorter subsequent words even if they fit :param separator (str): separator between words :param stopwords (iterable): words to discount :param regex_pattern (str): regex pattern for disallowed characters From d5643b16caf3f312d39574dccf49763da0908eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 26 Sep 2025 09:20:36 -0600 Subject: [PATCH 07/16] Add Python 3.13 to test matrix and add classifier to the setup (#154) --- .github/workflows/ci.yml | 2 +- .github/workflows/dev.yml | 2 +- .github/workflows/main.yml | 2 +- setup.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71bc219..cb6f443 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 88791a7..6046bd2 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a9c77e..d1a8f9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index 9661638..293ea26 100755 --- a/setup.py +++ b/setup.py @@ -84,6 +84,7 @@ def status(s): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 32a390f5b2145b4fab2532d00f59b26f0acac132 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 15 Oct 2025 17:41:24 -0400 Subject: [PATCH 08/16] Remove references to `easy_install`, which is deprecated --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c796aa4..b0100f2 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,11 @@ However, there is an alternative decoding package called [Unidecode](https://git # How to install - easy_install python-slugify |OR| easy_install python-slugify[unidecode] - -- OR -- - pip install python-slugify |OR| pip install python-slugify[unidecode] + pip install python-slugify + + # OR + + pip install python-slugify[unidecode] # Options From b7b9fa02bb01962d47054bf6525514db1bf8a165 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 08:40:22 -0600 Subject: [PATCH 09/16] Support py3.14; drop py3.9 and lower --- .github/workflows/ci.yml | 8 +++++++- .github/workflows/dev.yml | 8 +++++++- .github/workflows/main.yml | 8 +++++++- CHANGELOG.md | 5 +++++ setup.py | 6 ++---- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb6f443..263e360 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 6046bd2..55119d6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1a8f9c..a794f5e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa0ef2..f062ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased + +- Support Python 3.14. +- Drop support for Python 3.9 and lower. + ## 8.0.4 - Properly handle uppercase special characters (@mib1185 - thx) diff --git a/setup.py b/setup.py index 293ea26..1d0e2d5 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ package = 'slugify' -python_requires = ">=3.7" +python_requires = ">=3.10" here = os.path.abspath(os.path.dirname(__file__)) install_requires = ['text-unidecode>=1.3'] @@ -78,13 +78,11 @@ def status(s): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - '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', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 1ef698fa7a265ec8971d0b641fb6e735dcd667dc Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 14:54:54 -0600 Subject: [PATCH 10/16] Reintroduce tox and thoroughly test the project This commit introduces the following major changes: * Use tox. * Test the project against both `unidecode` and `text_unidecode`. Previously, `unidecode` was untested. * Drop support for Python 3.7, 3.8, and 3.9. CI was unable to run because Python 3.7 is no longer available. In addition, Python 3.9 and lower are all end-of-life. * Test against PyPy 3.11 and Python 3.14. * Test type annotations using mypy. Previously, type annotations were untested. * Install and test all supported Python interpreters in CI at once. * Use tox, and the tox-uv plugin, exclusively in CI. This eliminates differences between local and CI testing. * Run CI against pull requests. * Collect, combine, and report coverage for all Python versions and dependencies, locally and in CI. In addition, this commit introduces the following minor changes: * Fix typing issues identified by mypy. Note that `text_unidecode` is untyped and must be type-ignored. * Remove almost all coverage `pragma: nocover` lines. * Fix incorrect copy/pasted comments in `ci.yml` and `main.yml`. * Upgrade `actions/checkout` to v5 and `actions/setup-python` to v6. --- .github/workflows/ci.yml | 2 +- .github/workflows/main.yml | 41 +++++++--------------- CHANGELOG.md | 4 +++ pyproject.toml | 41 ++++++++++++++++++++++ slugify/__main__.py | 6 ++-- slugify/slugify.py | 4 +-- test.py | 2 +- tox.ini | 69 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 pyproject.toml create mode 100644 tox.ini diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 263e360..37e11a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -# Run on push only for dev/sandbox +# Run on push only for ci/staging # Otherwise it may trigger concurrently `push & pull_request` on PRs. on: push: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a794f5e..ba50793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,48 +1,33 @@ name: Main -# Run on push only for dev/sandbox -# Otherwise it may trigger concurrently `push & pull_request` on PRs. on: + pull_request: null push: branches: - master jobs: build: - name: Python ${{ matrix.python }} + name: Linux runs-on: ubuntu-latest - strategy: - matrix: - python: - - "3.10" - - "3.11" - - "3.12" - - "3.13" - - "3.14" - - "pypy3.11" - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python }} + python-version: | + pypy3.11 + 3.10 + 3.11 + 3.12 + 3.13 + 3.14 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -e . - pip install coveralls --upgrade - - name: Run flake8 - run: | - pip install flake8 --upgrade - flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - - name: Run pycodestyle - run: | - pip install pycodestyle --upgrade - pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + python -m pip install coveralls tox tox-uv - name: Run test run: | - coverage run --source=slugify test.py + tox - name: Coveralls run: coveralls --service=github env: diff --git a/CHANGELOG.md b/CHANGELOG.md index f062ed0..400399f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ - Support Python 3.14. - Drop support for Python 3.9 and lower. +- Use tox for local test runs and in CI. +- Test the project against both `unidecode` and `text_unidecode`. +- Fix type annotation issues identified by mypy. +- Run CI against pull requests. ## 8.0.4 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..de62727 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +# coverage +# -------- + +[tool.coverage.run] +relative_files = true +parallel = true +branch = true +source = [ + "slugify", + "test", +] + +[tool.coverage.paths] +source = [ + "src", + "*/site-packages", +] + +[tool.coverage.report] +skip_covered = true +fail_under = 97 + + +# mypy +# ---- + +[tool.mypy] +packages = "slugify" +strict = true +sqlite_cache = true + + +# pytest +# ------ + +[tool.pytest.ini_options] +testpaths = ["test.py"] +addopts = "--color=yes" +filterwarnings = [ + "error", +] diff --git a/slugify/__main__.py b/slugify/__main__.py index 4cc4616..4e6b3d9 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -47,7 +47,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: parser.error("Input strings and --stdin cannot work together") if args.replacements: - def split_check(repl): + def split_check(repl: str) -> list[str]: SEP = '->' if SEP not in repl: parser.error("Replacements must be of the form: ORIGINAL{SEP}REPLACED".format(SEP=SEP)) @@ -82,7 +82,7 @@ def slugify_params(args: argparse.Namespace) -> dict[str, Any]: ) -def main(argv: list[str] | None = None): # pragma: no cover +def main(argv: list[str] | None = None) -> None: """ Run this program """ if argv is None: argv = sys.argv @@ -94,5 +94,5 @@ def main(argv: list[str] | None = None): # pragma: no cover sys.exit(-1) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': main() diff --git a/slugify/slugify.py b/slugify/slugify.py index 67b31c8..9b5f27f 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -8,7 +8,7 @@ try: import unidecode except ImportError: - import text_unidecode as unidecode + import text_unidecode as unidecode # type: ignore[import-untyped, no-redef] __all__ = ['slugify', 'smart_truncate'] @@ -67,7 +67,7 @@ def smart_truncate( else: if save_order: break - if not truncated: # pragma: no cover + if not truncated: truncated = string[:max_length] return truncated.strip(separator) diff --git a/test.py b/test.py index d13ef94..fcec4b6 100644 --- a/test.py +++ b/test.py @@ -653,5 +653,5 @@ def test_multivalued_options_with_text(self): self.assertEqual(params['stopwords'], ['the', 'in', 'a', 'hurry']) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: nocover unittest.main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0c16f5e --- /dev/null +++ b/tox.ini @@ -0,0 +1,69 @@ +[tox] +env_list = + coverage-erase + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode} + pypy{3.11}-{unidecode, text_unidecode} + coverage-report + coverage-html + mypy + pycodestyle + +[testenv] +depends = + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode}: coverage-erase + pypy{3.11}-{unidecode, text_unidecode}: coverage-erase +deps = + coverage[toml] + pytest + unidecode: pip + unidecode: unidecode +commands_pre: + # If testing unidecode, ensure text_unidecode is unavailable. + unidecode: pip uninstall --yes text_unidecode +commands = + coverage run -m pytest test.py + +[testenv:coverage_base] +deps = + coverage[toml] + +[testenv:coverage-erase] +base = coverage_base +commands = + coverage erase + +[testenv:coverage-report] +base = coverage_base +depends = + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode} + pypy{3.11}-{unidecode, text_unidecode} +commands_pre = + - coverage combine +commands = + coverage report + +[testenv:coverage-html] +base = coverage_base +depends = + coverage-report +commands = + coverage html --fail-under=0 + +[testenv:mypy] +deps = + mypy + unidecode +commands = + mypy + +[testenv:pycodestyle] +deps = + pycodestyle +commands = + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + +[testenv:flake8] +deps = + flake8 +commands = + flake8 --ignore=E501,F403,F401,E241,E225,E128 slugify/ setup.py test.py From 13843e1a613a11383903d75e306549aba7f496d1 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:27:03 -0500 Subject: [PATCH 11/16] Remove deprecated `codecs.open()` usage `codecs.open()` is deprecated and throws a `DeprecationWarning`: ``` :20: DeprecationWarning: codecs.open() is deprecated. Use open() instead. :23: DeprecationWarning: codecs.open() is deprecated. Use open() instead. ``` --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1d0e2d5..083f7a3 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ import os import sys -from codecs import open from shutil import rmtree from setuptools import setup @@ -17,10 +16,10 @@ test_requires = [] about = {} -with open(os.path.join(here, package, '__version__.py'), 'r', 'utf-8') as f: +with open(os.path.join(here, package, '__version__.py'), 'r', encoding='utf-8') as f: exec(f.read(), about) -with open('README.md', 'r', 'utf-8') as f: +with open('README.md', 'r', encoding='utf-8') as f: readme = f.read() From d2b069438a32eaf9ee6f1fee1d37e1b183612e3d Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:29:49 -0500 Subject: [PATCH 12/16] Remove the unknown `tests_require` option The `tests_require` option throws a `UserWarning`: ``` /.../site-packages/setuptools/_distutils/dist.py:289: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) ``` --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 083f7a3..eff6a78 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,6 @@ install_requires = ['text-unidecode>=1.3'] extras_requires = {'unidecode': ['Unidecode>=1.1.1']} -test_requires = [] about = {} with open(os.path.join(here, package, '__version__.py'), 'r', encoding='utf-8') as f: @@ -65,7 +64,6 @@ def status(s): include_package_data=True, python_requires=python_requires, install_requires=install_requires, - tests_require=test_requires, extras_require=extras_requires, zip_safe=False, cmdclass={}, From 85b9be8111b3ea17a71e109c62be9c37e723e89f Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:37:46 -0500 Subject: [PATCH 13/16] Resolve deprecated `License` trove classifier usage This resolves build warnings thrown by setuptools: ``` /.../site-packages/setuptools/dist.py:759: SetuptoolsDeprecationWarning: License classifiers are deprecated. !! ******************************************************************************** Please consider removing the following classifiers in favor of a SPDX license expression: License :: OSI Approved :: MIT License See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details. ******************************************************************************** !! ``` --- setup.py | 1 - slugify/__version__.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index eff6a78..32f44dd 100755 --- a/setup.py +++ b/setup.py @@ -72,7 +72,6 @@ def status(s): 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.10', diff --git a/slugify/__version__.py b/slugify/__version__.py index 854038e..a9cd778 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -3,6 +3,6 @@ __author_email__ = 'info@neekware.com' __description__ = 'A Python slugify application that also handles Unicode' __url__ = 'https://github.com/un33k/python-slugify' -__license__ = 'MIT' +__license__ = 'SPDX-License-Identifier: MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' __version__ = '8.0.4' From 406c65b8ae06b7ababcc1c624364b6b30cf67097 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:40:18 -0500 Subject: [PATCH 14/16] Resolve deprecation warnings thrown when installing in editable mode Running `pip install -e.` displays the following deprecation warnings: ``` DEPRECATION: Legacy editable install of python-slugify==8.0.4 from file:///.../pr-python-slugify (setup.py develop) is deprecated. pip 25.3 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools >= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457 ``` --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index de62727..1c02bfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +[build-system] +requires = ["setuptools>=61.2"] +build-backend = "setuptools.build_meta" + + # coverage # -------- From 733c5bbc5efd275b01799747d460447e94ef75d6 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 16:49:27 -0600 Subject: [PATCH 15/16] Summarize the build warnings fixes in the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 400399f..537460e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Test the project against both `unidecode` and `text_unidecode`. - Fix type annotation issues identified by mypy. - Run CI against pull requests. +- Fix package build warnings. ## 8.0.4 From 4faa5f996724c93ee1c4c8a06d461b98f9cd5ebf Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 7 Jan 2026 07:28:44 -0600 Subject: [PATCH 16/16] Fix the README badge to refer to the `main.yml` workflow results --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0100f2..e5123f1 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml/badge.svg +[status-image]: https://github.com/un33k/python-slugify/actions/workflows/main.yml/badge.svg [status-link]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify