Skip to content

Commit 640c0ca

Browse files
authored
Upgrade tooling (poetry -> uv, pre-commit -> prek, circleci -> gha, black -> ruff, mypy -> pyright) (#12)
1 parent fb32d17 commit 640c0ca

16 files changed

Lines changed: 557 additions & 1237 deletions

File tree

.circleci/config.yml

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

.github/workflows/ci.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
11+
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Set up Python ${{ matrix.python-version }}
15+
uses: actions/setup-python@v4
16+
with:
17+
python-version: ${{ matrix.python-version }}
18+
- name: Install uv
19+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
20+
- name: Run tests
21+
run: |
22+
source ~/.cargo/env
23+
make test

.github/workflows/release.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Upload Python Package
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
release-build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
# Installs the official uv GitHub Action
18+
- name: Install uv
19+
uses: astral-sh/setup-uv@v5
20+
with:
21+
enable-cache: true
22+
23+
- name: Build release distributions
24+
run: uv build
25+
26+
- name: Upload distributions
27+
uses: actions/upload-artifact@v4
28+
with:
29+
name: release-dists
30+
path: dist/
31+
32+
pypi-publish:
33+
runs-on: ubuntu-latest
34+
needs: release-build
35+
permissions:
36+
# IMPORTANT: this permission is mandatory for Trusted Publishing (OIDC)
37+
id-token: write
38+
39+
environment:
40+
name: pypi
41+
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
42+
# url: https://pypi.org/p/YOURPROJECT
43+
44+
steps:
45+
- name: Retrieve release distributions
46+
uses: actions/download-artifact@v4
47+
with:
48+
name: release-dists
49+
path: dist/
50+
51+
# uv needs to be available in this job as well to run `uv publish`
52+
- name: Install uv
53+
uses: astral-sh/setup-uv@v5
54+
55+
- name: Publish release distributions to PyPI
56+
run: uv publish

.pre-commit-config.yaml

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

Makefile

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
.PHONY: dev
1+
PHONY: dev
22
dev:
3-
tox -e install-hooks
3+
uvx prek install
4+
5+
.PHONY: lint
6+
lint:
7+
uvx prek --all-files
48

59
.PHONY: test
610
test:
7-
tox -e unit
11+
uv run pytest --doctest-modules --doctest-glob="*.md"
12+
uv run pyright
813

914
.PHONY: release
1015
release: clean
11-
python setup.py sdist bdist_wheel
12-
echo "Checking dist:"
13-
twine check dist/*
14-
# "Are you sure you want to publish the ^ release? Press any key to continue."
15-
read
16-
twine upload dist/*
16+
uv build
17+
uv publish
1718

1819
.PHONY: clean
1920
clean:
20-
rm -rf build dist .coverage .mypy_cache .pytest_cache __pycache__ .tox
21+
rm -rf build dist .coverage .mypy_cache .pytest_cache __pycache__ .tox .venv .git/hooks/pre-commit

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99

1010
## Prerequisites
1111

12-
The only requirement is using **Python 3.8+**. You can verify this by running:
12+
The only requirement is using **Python 3.10+**. You can verify this by running:
1313

1414
```bash
1515
$ python --version
16-
Python 3.8.18
16+
Python 3.10.18
1717
```
1818

1919
## Installation
@@ -27,7 +27,7 @@ You can verify you're running the latest package version by running:
2727
```python
2828
>>> import disjoint_set
2929
>>> disjoint_set.__version__
30-
'0.8.0'
30+
'0.9.0'
3131

3232
```
3333

disjoint_set/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from .main import DisjointSet
2-
from .main import InvalidInitialMappingError
1+
from .main import DisjointSet, InvalidInitialMappingError
32

43
name = "disjoint_set"
54
__all__ = ["DisjointSet", "InvalidInitialMappingError"]
6-
__version__ = "0.8.0"
5+
__version__ = "0.9.0"

disjoint_set/main.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from __future__ import annotations
22

3+
import sys
34
from collections import defaultdict
4-
from typing import Any
5-
from typing import DefaultDict
6-
from typing import Generic
7-
from typing import Iterable
8-
from typing import Iterator
9-
from typing import TypeVar
5+
from collections.abc import Iterable, Iterator
6+
from typing import Any, Generic, TypeVar
7+
8+
if sys.version_info >= (3, 12):
9+
from typing import override
10+
else:
11+
from typing_extensions import override
1012

1113
from disjoint_set.utils import IdentityDict
1214

@@ -18,20 +20,20 @@ class InvalidInitialMappingError(RuntimeError):
1820

1921
def __init__(
2022
self,
21-
msg=(
23+
msg: str = (
2224
"The mapping passed during ther DisjointSet initialization must have been wrong. "
2325
"Check that all keys are mapping to other keys and not some external values."
2426
),
25-
*args,
26-
**kwargs,
27+
*args: Any,
28+
**kwargs: Any,
2729
):
2830
super().__init__(msg, *args, **kwargs)
2931

3032

3133
class DisjointSet(Generic[T]):
3234
"""A disjoint set data structure."""
3335

34-
def __init__(self, *args, **kwargs) -> None:
36+
def __init__(self, *args: Any, **kwargs: Any) -> None:
3537
"""
3638
Disjoint set data structure.
3739
@@ -65,7 +67,8 @@ def __bool__(self) -> bool:
6567
def __getitem__(self, element: T) -> T:
6668
return self.find(element)
6769

68-
def __eq__(self, other: Any) -> bool:
70+
@override
71+
def __eq__(self, other: object) -> bool:
6972
"""
7073
Return True if both DistjoinSet structures are equivalent.
7174
@@ -76,8 +79,11 @@ def __eq__(self, other: Any) -> bool:
7679
if not isinstance(other, DisjointSet):
7780
return False
7881

79-
return {tuple(x) for x in self.itersets()} == {tuple(x) for x in other.itersets()}
82+
return {tuple(x) for x in self.itersets()} == {
83+
tuple(x) for x in other.itersets()
84+
}
8085

86+
@override
8187
def __repr__(self) -> str:
8288
"""
8389
Print self in a reproducible way.
@@ -88,6 +94,7 @@ def __repr__(self) -> str:
8894
sets = {key: val for key, val in self}
8995
return f"{self.__class__.__name__}({sets})"
9096

97+
@override
9198
def __str__(self) -> str:
9299
return "{classname}({values})".format(
93100
classname=self.__class__.__name__,
@@ -102,7 +109,9 @@ def __iter__(self) -> Iterator[tuple[T, T]]:
102109
except RuntimeError as e:
103110
raise InvalidInitialMappingError() from e
104111

105-
def itersets(self, with_canonical_elements: bool = False) -> Iterator[set[T] | tuple[T, set[T]]]:
112+
def itersets(
113+
self, with_canonical_elements: bool = False
114+
) -> Iterator[set[T] | tuple[T, set[T]]]:
106115
"""
107116
Yield sets of connected components.
108117
@@ -114,7 +123,7 @@ def itersets(self, with_canonical_elements: bool = False) -> Iterator[set[T] | t
114123
>>> list(ds.itersets(with_canonical_elements=True))
115124
[(2, {1, 2})]
116125
"""
117-
element_classes: DefaultDict[T, set[T]] = defaultdict(set)
126+
element_classes: defaultdict[T, set[T]] = defaultdict(set)
118127
for element in self._data:
119128
element_classes[self.find(element)].add(element)
120129

disjoint_set/utils.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
from typing import Dict
2-
from typing import TypeVar
1+
from typing import Any, TypeVar
32

43
T = TypeVar("T")
54

65

7-
class IdentityDict(Dict[T, T]):
6+
class IdentityDict(dict[T, T]):
87
"""A defaultdict implementation which places the requested key as its value in case it's missing."""
98

10-
def __init__(self, *args, **kwargs):
9+
def __init__(self, *args: Any, **kwargs: Any):
1110
super().__init__(*args, **kwargs)
1211

1312
def __missing__(self, key: T) -> T:

0 commit comments

Comments
 (0)