Skip to content

Commit a370d1d

Browse files
committed
Use uv
1 parent 8b4dfdd commit a370d1d

File tree

11 files changed

+1701
-138
lines changed

11 files changed

+1701
-138
lines changed

.github/workflows/release.yml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
name: Upload Python Package
1+
name: Release and Publish
22

33
on:
44
release:
55
types: [created]
66

77
jobs:
8-
pypi-publish:
9-
name: upload release to PyPI
8+
test:
9+
uses: ./.github/workflows/test-reusable.yml
1010

11+
pypi-publish:
12+
name: Upload release to PyPI
13+
needs: test # Only run after all tests pass
1114
runs-on: ubuntu-latest
12-
# Specifying a GitHub environment is optional, but strongly encouraged
1315
environment: release
1416
permissions:
1517
# IMPORTANT: this permission is mandatory for trusted publishing
1618
id-token: write
1719

1820
steps:
19-
# retrieve your distributions here
20-
- uses: actions/checkout@v3
21+
- uses: actions/checkout@v4
2122

22-
- name: Set up Python 3.11
23-
uses: actions/setup-python@v4
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
2425
with:
25-
python-version: "3.11"
26+
python-version: "3.12" # Use stable version for building
2627

27-
- name: Install dependencies
28-
run: |
29-
python -m pip install --upgrade pip
30-
pip install hatch
28+
- name: Install uv
29+
uses: astral-sh/setup-uv@v4
3130

3231
- name: Build package distributions
3332
run: |
34-
hatch build
33+
uv build
3534
3635
- name: Publish package distributions to PyPI
3736
uses: pypa/gh-action-pypi-publish@release/v1
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Test (Reusable)
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
python-versions:
7+
description: 'JSON array of Python versions to test'
8+
required: false
9+
type: string
10+
default: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]'
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: ${{ fromJson(inputs.python-versions) }}
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Install uv
28+
uses: astral-sh/setup-uv@v4
29+
with:
30+
enable-cache: true
31+
32+
- name: Install dependencies
33+
run: |
34+
uv sync --all-groups --python ${{ matrix.python-version }}
35+
36+
- name: Lint with ruff
37+
run: |
38+
# stop the build if there are Python syntax errors or undefined names
39+
uv run ruff check --output-format=github --select=E9,F63,F7,F82 .
40+
# default set of ruff rules with GitHub Annotations
41+
uv run ruff check --output-format=github .
42+
43+
- name: Test with pytest
44+
run: |
45+
uv run pytest
46+
47+
test-summary:
48+
name: Test Suite Status
49+
runs-on: ubuntu-latest
50+
needs: test
51+
if: always()
52+
steps:
53+
- name: Check test matrix status
54+
run: |
55+
if [[ "${{ needs.test.result }}" == "success" ]]; then
56+
echo "All tests passed successfully!"
57+
exit 0
58+
else
59+
echo "Tests failed or were cancelled"
60+
exit 1
61+
fi

.github/workflows/test.yml

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,7 @@
11
name: Test
22

3-
on: [push]
3+
on: [push, pull_request]
44

55
jobs:
6-
build:
7-
8-
runs-on: ubuntu-latest
9-
strategy:
10-
matrix:
11-
python-version: ["3.8", "3.9", "3.10", "3.11"]
12-
13-
steps:
14-
- uses: actions/checkout@v3
15-
16-
- name: Set up Python ${{ matrix.python-version }}
17-
uses: actions/setup-python@v4
18-
with:
19-
python-version: ${{ matrix.python-version }}
20-
21-
- name: Install dependencies
22-
run: |
23-
python -m pip install --upgrade pip
24-
pip install -r requirements-dev.lock
25-
26-
- name: Lint with ruff
27-
run: |
28-
# stop the build if there are Python syntax errors or undefined names
29-
ruff --format=github --select=E9,F63,F7,F82 --target-version=py37 .
30-
# default set of ruff rules with GitHub Annotations
31-
ruff --format=github --target-version=py37 .
32-
- name: Test with pytest
33-
run: |
34-
pytest
6+
test:
7+
uses: ./.github/workflows/test-reusable.yml

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1+
3.14

README.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
# python-usernames
22

3-
[![Build
4-
Status](https://travis-ci.org/theskumar/python-usernames.svg?branch=v0.1.0)](https://travis-ci.org/theskumar/python-usernames)
5-
[![Coverage
6-
Status](https://coveralls.io/repos/theskumar/python-usernames/badge.svg?branch=master&service=github)](https://coveralls.io/github/theskumar/python-usernames?branch=master)
7-
[![PyPI
8-
version](https://badge.fury.io/py/python-usernames.svg)](http://badge.fury.io/py/python-usernames)
3+
[![Test](https://github.com/theskumar/python-usernames/actions/workflows/test.yml/badge.svg)](https://github.com/theskumar/python-usernames/actions/workflows/test.yml)
4+
[![PyPI version](https://badge.fury.io/py/python-usernames.svg)](https://pypi.org/project/python-usernames/)
5+
[![Python Versions](https://img.shields.io/pypi/pyversions/python-usernames.svg)](https://pypi.org/project/python-usernames/)
96

107
Python library to validate usernames suitable for use in public facing
118
applications where use can choose login names and sub-domains.
@@ -75,6 +72,43 @@ is checked. We don't try to be smart to avoid [Scunthorpe
7572
problem](https://en.wikipedia.org/wiki/Scunthorpe_problem). If you can
7673
come up with a algorithm/solution, please create an issue/pr :).
7774

75+
## Development
76+
77+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management and packaging.
78+
79+
### Setup
80+
81+
1. Install uv:
82+
```bash
83+
curl -LsSf https://astral.sh/uv/install.sh | sh
84+
```
85+
86+
2. Clone the repository and sync dependencies:
87+
```bash
88+
git clone https://github.com/theskumar/python-usernames.git
89+
cd python-usernames
90+
uv sync --all-groups
91+
```
92+
93+
### Running Tests
94+
95+
```bash
96+
uv run pytest
97+
```
98+
99+
### Linting
100+
101+
```bash
102+
uv run ruff check .
103+
uv run black --check .
104+
```
105+
106+
### Building
107+
108+
```bash
109+
uv build
110+
```
111+
78112
## License
79113

80114
MIT

pyproject.toml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,12 @@ Tracker = "https://github.com/theskumar/python-usernames/issues"
5757
requires = ["hatchling"]
5858
build-backend = "hatchling.build"
5959

60-
[tool.rye]
61-
managed = true
62-
dev-dependencies = [
63-
"pytest~=7.3.2",
64-
"black~=23.3.0",
65-
"ruff~=0.0.272",
66-
"hatch~=1.7.0",
60+
[dependency-groups]
61+
dev = [
62+
"pytest>=7.3.2",
63+
"black>=23.3.0",
64+
"ruff>=0.0.272",
65+
"hatch>=1.7.0",
6766
]
6867

6968
[tool.hatch.metadata]

requirements-dev.lock

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

requirements.lock

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

src/python_usernames/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from .validators import is_safe_username
1+
from python_usernames.__about__ import VERSION as __version__
2+
from python_usernames.validators import is_safe_username
23

3-
__version__ = "0.4.1"
4-
5-
__all__ = ["is_safe_username"]
4+
__all__ = ["is_safe_username", "__version__"]

src/python_usernames/validators.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from functools import lru_cache
23

34
from .reserved_words import get_reserved_words
45

@@ -17,34 +18,40 @@
1718
)
1819

1920

21+
@lru_cache(maxsize=1)
22+
def _get_cached_reserved_words():
23+
"""Cache reserved words to avoid repeated file I/O."""
24+
return get_reserved_words()
25+
26+
2027
def is_safe_username(
2128
username: str, whitelist=None, blacklist=None, regex=username_regex, max_length=None
2229
) -> bool:
23-
# check for max length
30+
# check for max length (fastest check first)
2431
if max_length and len(username) > max_length:
2532
return False
2633

2734
# check against provided regex
28-
if not re.match(regex, username):
35+
if not regex.match(username):
2936
return False
3037

31-
# ensure the word is not in the blacklist and is not a reserved word
32-
if whitelist is None:
33-
whitelist = []
38+
username_lower = username.lower()
3439

35-
if blacklist is None:
36-
blacklist = []
40+
# Fast path: no custom lists
41+
if not whitelist and not blacklist:
42+
return username_lower not in _get_cached_reserved_words()
3743

38-
default_words = get_reserved_words()
44+
# Start with reserved words
45+
forbidden = _get_cached_reserved_words().copy()
3946

40-
whitelist = set(
41-
[each_whitelisted_name.lower() for each_whitelisted_name in whitelist]
42-
)
43-
blacklist = set(
44-
[each_blacklisted_name.lower() for each_blacklisted_name in blacklist]
45-
)
47+
# Apply whitelist (remove from forbidden)
48+
if whitelist:
49+
whitelist_set = {w.lower() for w in whitelist}
50+
forbidden -= whitelist_set
4651

47-
default_words = default_words - whitelist
48-
default_words = default_words.union(blacklist)
52+
# Apply blacklist (add to forbidden)
53+
if blacklist:
54+
blacklist_set = {w.lower() for w in blacklist}
55+
forbidden |= blacklist_set
4956

50-
return False if username.lower() in default_words else True
57+
return username_lower not in forbidden

0 commit comments

Comments
 (0)