Skip to content

Commit 14a6dee

Browse files
feat: add CI/CD, dev tooling, and Pythonic dunder methods
- Add GitHub Actions CI with lint and test jobs - Add ruff (linter/formatter), ty (type checker), pytest-cov - Add pre-commit hooks for ruff and ty - Add __repr__, __len__, __contains__ to all data structures - Add Comparable type bound to MinStack for type safety - Update README with new tooling commands and requirements
1 parent 5cd48fe commit 14a6dee

12 files changed

Lines changed: 429 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: astral-sh/setup-uv@v6
16+
17+
- name: Install dependencies
18+
run: uv sync --group dev
19+
20+
- name: Ruff check
21+
run: uv run ruff check .
22+
23+
- name: Ruff format check
24+
run: uv run ruff format --check .
25+
26+
- name: Type check with ty
27+
run: uv run ty check
28+
29+
test:
30+
runs-on: ubuntu-latest
31+
strategy:
32+
matrix:
33+
python-version: ["3.13"]
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- uses: astral-sh/setup-uv@v6
38+
39+
- name: Set up Python ${{ matrix.python-version }}
40+
run: uv python install ${{ matrix.python-version }}
41+
42+
- name: Install dependencies
43+
run: uv sync --group dev
44+
45+
- name: Run tests
46+
run: uv run pytest tests/ -v

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.15.6
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
- repo: local
9+
hooks:
10+
- id: ty
11+
name: ty type check
12+
entry: uv run ty check
13+
language: system
14+
types: [python]
15+
pass_filenames: false

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ A collection of classic algorithms and data structures implemented in Python.
1212
| `MinStack` | Extends `ArrayStack` with O(1) `get_min()` tracking via an auxiliary stack. |
1313

1414
```python
15-
from data_structures.stacks.array_stack import ArrayStack
16-
from data_structures.stacks.min_stack import MinStack
15+
from data_structures.stack.array_stack import ArrayStack
16+
from data_structures.stack.min_stack import MinStack
1717

1818
s = ArrayStack()
1919
s.push(1)
@@ -59,15 +59,28 @@ list(dll) # [1, 2]
5959

6060
- Python >= 3.13
6161
- [uv](https://docs.astral.sh/uv/) (package manager)
62+
- [ruff](https://docs.astral.sh/ruff/) (linter & formatter)
63+
- [ty](https://docs.astral.sh/ty/) (type checker)
64+
- [pre-commit](https://pre-commit.com/) (git hooks)
6265

6366
## Getting Started
6467

6568
```bash
6669
# Install dependencies
6770
uv sync --group dev
6871

69-
# Run tests
72+
# Run tests (with coverage)
7073
uv run pytest tests/ -v
74+
75+
# Lint & format
76+
uv run ruff check .
77+
uv run ruff format .
78+
79+
# Type check
80+
uv run ty check
81+
82+
# Run all pre-commit hooks
83+
uv run pre-commit run --all-files
7184
```
7285

7386
## Project Structure
@@ -85,4 +98,6 @@ data_structures/
8598
tests/
8699
test_stacks.py # pytest suite for stack implementations
87100
test_linked_lists.py # pytest suite for linked list implementations
101+
.pre-commit-config.yaml # pre-commit hooks (ruff, ty)
102+
pyproject.toml # project config, tools settings
88103
```

data_structures/linked_list/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ def size(self) -> int:
3939
def __iter__(self) -> Iterator[T]:
4040
"""Iterate over all elements from head to tail."""
4141

42+
@abstractmethod
43+
def __len__(self) -> int:
44+
"""Return the number of elements. O(1)."""
45+
46+
@abstractmethod
47+
def __contains__(self, item: object) -> bool:
48+
"""Return ``True`` if *item* is in the list. O(n)."""
49+
50+
@abstractmethod
51+
def __repr__(self) -> str:
52+
"""Return a string representation."""
53+
4254

4355
class IDoublyLinkedList[T](ISinglyLinkedList[T]):
4456
"""Abstract Doubly Linked List interface.

data_structures/linked_list/doubly_linked_list.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,12 @@ def __iter__(self) -> Iterator[T]:
8888
while node is not None:
8989
yield node.value
9090
node = node.next
91+
92+
def __len__(self) -> int:
93+
return self._size
94+
95+
def __contains__(self, item: object) -> bool:
96+
return any(v == item for v in self)
97+
98+
def __repr__(self) -> str:
99+
return f"{type(self).__name__}({list(self)})"

data_structures/linked_list/singly_linked_list.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ def __iter__(self) -> Iterator[T]:
4646
while node is not None:
4747
yield node.value
4848
node = node.next
49+
50+
def __len__(self) -> int:
51+
return self._size
52+
53+
def __contains__(self, item: object) -> bool:
54+
return any(v == item for v in self)
55+
56+
def __repr__(self) -> str:
57+
return f"{type(self).__name__}({list(self)})"

data_structures/stack/array_stack.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,12 @@ def is_empty(self) -> bool:
2525

2626
def size(self) -> int:
2727
return len(self._items)
28+
29+
def __len__(self) -> int:
30+
return self.size()
31+
32+
def __contains__(self, item: object) -> bool:
33+
return item in self._items
34+
35+
def __repr__(self) -> str:
36+
return f"{type(self).__name__}({list(self._items)})"

data_structures/stack/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,15 @@ def is_empty(self) -> bool:
4040
@abstractmethod
4141
def size(self) -> int:
4242
"""Return the number of elements. O(1)."""
43+
44+
@abstractmethod
45+
def __len__(self) -> int:
46+
"""Return the number of elements. O(1)."""
47+
48+
@abstractmethod
49+
def __contains__(self, item: object) -> bool:
50+
"""Return ``True`` if *item* is in the stack. O(n)."""
51+
52+
@abstractmethod
53+
def __repr__(self) -> str:
54+
"""Return a string representation."""

data_structures/stack/min_stack.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
from typing import Protocol, runtime_checkable
2+
13
from .array_stack import ArrayStack
24
from .base import StackEmptyError
35

46

5-
class MinStack[T](ArrayStack):
7+
@runtime_checkable
8+
class Comparable(Protocol):
9+
def __lt__(self, other: "Comparable") -> bool: ...
10+
11+
12+
class MinStack[T: Comparable](ArrayStack):
613
"""ArrayStack that also tracks the minimum element in O(1)."""
714

815
def __init__(self) -> None:

pyproject.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,20 @@ requires-python = ">=3.13"
77
dependencies = []
88

99
[dependency-groups]
10-
dev = ["pytest"]
10+
dev = [
11+
"pre-commit>=4.5.1",
12+
"pytest",
13+
"pytest-cov>=7.0.0",
14+
"ruff>=0.15.6",
15+
"ty>=0.0.22",
16+
]
17+
18+
[tool.ruff]
19+
line-length = 88
20+
21+
[tool.ruff.lint]
22+
select = ["E", "F", "W", "I"]
1123

1224
[tool.pytest.ini_options]
1325
pythonpath = ["."]
26+
addopts = "--cov=data_structures --cov-report=term-missing"

0 commit comments

Comments
 (0)