Skip to content

Commit 9be2147

Browse files
authored
chore: Improve DX configs (#16)
1 parent 70f909f commit 9be2147

18 files changed

Lines changed: 893 additions & 60 deletions

.github/workflows/ci.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,29 @@ jobs:
2525
steps:
2626
- name: Checkout the revision
2727
uses: actions/checkout@v4
28-
- name: Set up Python as Runtime
29-
uses: actions/setup-python@v5
30-
with:
31-
python-version: ${{ matrix.python-version }}
32-
allow-prereleases: true
3328
- name: Set up Rust toolchain
3429
uses: actions-rust-lang/setup-rust-toolchain@v1
3530
with:
3631
toolchain: stable
3732
override: true
3833
- name: Install protobuf compiler
39-
run: |
40-
sudo apt update
41-
sudo apt install -y protobuf-compiler
34+
uses: arduino/setup-protoc@v3
35+
with:
36+
version: "33.2"
37+
repo-token: ${{ secrets.GITHUB_TOKEN }}
38+
- name: Install uv and set the Python version
39+
uses: astral-sh/setup-uv@v7
40+
with:
41+
enable-cache: true
42+
python-version: ${{ matrix.python-version }}
4243
- name: Install dependencies and build the package
4344
run: |
44-
python -m pip install --upgrade pip
45-
python -m pip install -r requirements.txt
46-
python -m maturin build
47-
python -m pip install .
45+
uv sync --locked --all-extras
46+
uv run maturin build
47+
uv pip install .
4848
- name: Test
4949
run: |
50-
python -m pytest
50+
uv run pytest
5151
5252
release-linux:
5353
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')

Makefile

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,52 @@
1+
# Build targets
12
build:
2-
maturin build
3+
uv run maturin build
34

45
install:
5-
maturin build
6-
pip install .
6+
uv sync --all-extras
7+
uv run maturin develop
78

9+
# Test targets
10+
test:
11+
uv run pytest
12+
13+
# Utility targets
814
etcd-clear:
915
etcdctl del "" --from-key=true
1016

11-
fmt:
17+
# Python formatting and linting
18+
fmt-py:
19+
uv run ruff format tests/ etcd_client.pyi
20+
21+
lint-py:
22+
uv run ruff check tests/ etcd_client.pyi
23+
24+
fix-py:
25+
uv run ruff format tests/ etcd_client.pyi
26+
uv run ruff check --fix tests/ etcd_client.pyi
27+
28+
typecheck:
29+
uv run mypy tests/ etcd_client.pyi
30+
31+
# Rust formatting and linting
32+
fmt-rust:
1233
cargo fmt
1334

14-
lint:
35+
lint-rust:
1536
cargo clippy
37+
38+
fix-rust:
39+
cargo fmt
40+
cargo clippy --fix --allow-dirty --allow-staged
41+
42+
# Combined targets
43+
fmt: fmt-py fmt-rust
44+
45+
lint: lint-py typecheck lint-rust
46+
47+
fix: fix-py fix-rust
48+
49+
check: lint
50+
@echo "All checks passed!"
51+
52+
.PHONY: build install test etcd-clear fmt-py lint-py fix-py typecheck fmt-rust lint-rust fix-rust fmt lint fix check

README.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,63 @@ distributions under the `dist/` directory.
237237

238238
## How to develop and test
239239

240-
(TODO: run maturin for an editable setup)
240+
### Setup development environment
241+
242+
This project uses [uv](https://docs.astral.sh/uv/) for fast Python package management.
243+
244+
```bash
245+
# Install uv (if not already installed)
246+
curl -LsSf https://astral.sh/uv/install.sh | sh
247+
248+
# Install all dependencies and build the package
249+
make install
250+
251+
# Or manually:
252+
uv sync --all-extras # Installs all dependencies from pyproject.toml
253+
uv run maturin develop # Builds and installs the Rust extension
254+
```
255+
256+
### Code quality checks
257+
258+
This project uses ruff for linting/formatting and mypy for type checking:
259+
260+
```bash
261+
# Format Python code
262+
make fmt-py
263+
264+
# Lint Python code
265+
make lint-py
266+
267+
# Auto-fix Python issues (format + fixable lints)
268+
make fix-py
269+
270+
# Type check Python code
271+
make typecheck
272+
273+
# Auto-fix Rust issues (format + fixable clippy lints)
274+
make fix-rust
275+
276+
# Auto-fix all issues (Python + Rust)
277+
make fix
278+
279+
# Format all code (Python + Rust)
280+
make fmt
281+
282+
# Lint all code (Python + Rust)
283+
make lint
284+
285+
# Run all checks (Python + Rust)
286+
make check
287+
```
288+
289+
### Running tests
290+
291+
```bash
292+
# Run tests using uv
293+
make test
294+
295+
# Or directly with uv
296+
uv run pytest
297+
298+
# The tests use testcontainers to automatically spin up etcd
299+
```

etcd_client.pyi

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class Client:
123123
""" """
124124
async def __aenter__(self) -> "Communicator":
125125
""" """
126-
async def __aexit__(self, *args) -> None:
126+
async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
127127
""" """
128128

129129
class ConnectOptions:
@@ -242,14 +242,6 @@ class Communicator:
242242
last compaction revision.
243243
"""
244244

245-
class Watch:
246-
""" """
247-
248-
async def __aiter__(self) -> AsyncIterator["Watch"]:
249-
""" """
250-
async def __anext__(self) -> "WatchEvent":
251-
""" """
252-
253245
class WatchEvent:
254246
""" """
255247

@@ -259,6 +251,7 @@ class WatchEvent:
259251
prev_value: Optional[bytes]
260252

261253
def __init__(
254+
self,
262255
key: bytes,
263256
value: bytes,
264257
event: "WatchEventType",
@@ -275,16 +268,6 @@ class WatchEventType:
275268
"""
276269
"""
277270

278-
class CondVar:
279-
""" """
280-
281-
def __init__(self) -> None:
282-
""" """
283-
async def wait(self) -> None:
284-
""" """
285-
async def notify_waiters(self) -> None:
286-
""" """
287-
288271
class ClientError(Exception):
289272
""" """
290273

pyproject.toml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,77 @@ classifiers = [
2121
"Programming Language :: Python :: 3.13",
2222
"Programming Language :: Python :: 3.14",
2323
]
24+
dependencies = [
25+
"maturin>=1.10.2",
26+
"pytest>=8.4.1,<9",
27+
"pytest-asyncio>=1.1.0,<2",
28+
"trafaret>=2.1,<3",
29+
"testcontainers>=4.12.0,<5",
30+
]
2431

2532
[project.urls]
2633
homepage = "https://github.com/lablup/etcd-client-py"
2734
repository = "https://github.com/lablup/etcd-client-py"
2835

36+
[project.optional-dependencies]
37+
dev = [
38+
"ruff>=0.8.5",
39+
"mypy>=1.13.0",
40+
]
41+
2942
[build-system]
3043
requires = ["maturin>=1.7,<2.0"]
3144
build-backend = "maturin"
45+
46+
# === Tool Configuration ===
47+
48+
[tool.ruff]
49+
target-version = "py310"
50+
line-length = 100
51+
extend-exclude = ["*.pyi"]
52+
preview = true
53+
54+
[tool.ruff.lint]
55+
preview = true
56+
ignore = [
57+
"E501", # Line too long (formatter handles)
58+
]
59+
60+
[tool.ruff.lint.isort]
61+
known-first-party = ["etcd_client", "tests"]
62+
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
63+
64+
[tool.mypy]
65+
python_version = "3.11"
66+
warn_return_any = true
67+
warn_unused_configs = true
68+
warn_redundant_casts = true
69+
warn_unused_ignores = true
70+
warn_no_return = true
71+
warn_unreachable = true
72+
strict_equality = true
73+
extra_checks = true
74+
check_untyped_defs = true
75+
disallow_untyped_calls = false
76+
disallow_untyped_defs = false
77+
disallow_incomplete_defs = true
78+
disallow_untyped_decorators = false
79+
show_error_codes = true
80+
show_column_numbers = true
81+
pretty = true
82+
explicit_package_bases = true
83+
namespace_packages = true
84+
85+
[[tool.mypy.overrides]]
86+
module = [
87+
"trafaret.*",
88+
"testcontainers.*",
89+
"pytest.*",
90+
]
91+
ignore_missing_imports = true
92+
93+
[[tool.mypy.overrides]]
94+
module = "tests.*"
95+
disallow_incomplete_defs = false
96+
no_implicit_optional = false
97+
disable_error_code = ["union-attr"]

requirements.txt

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

src/client.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl PyClient {
8181
connect_options: Option<PyConnectOptions>,
8282
lock_options: Option<PyEtcdLockOption>,
8383
) -> Self {
84-
let connect_options = connect_options.unwrap_or(PyConnectOptions::default());
84+
let connect_options = connect_options.unwrap_or_default();
8585
Self {
8686
endpoints,
8787
connect_options,
@@ -147,7 +147,11 @@ impl PyClient {
147147
}
148148

149149
#[pyo3(signature = (*_args))]
150-
fn __aexit__<'a>(&'a self, py: Python<'a>, _args: &Bound<'a, PyTuple>) -> PyResult<Bound<'a, PyAny>> {
150+
fn __aexit__<'a>(
151+
&'a self,
152+
py: Python<'a>,
153+
_args: &Bound<'a, PyTuple>,
154+
) -> PyResult<Bound<'a, PyAny>> {
151155
let lock_options = self.lock_options.clone();
152156

153157
let lock_manager = if lock_options.is_some() {

src/communicator.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ impl PyCommunicator {
5555
})
5656
}
5757

58-
fn put<'a>(&'a self, py: Python<'a>, key: Vec<u8>, value: Vec<u8>) -> PyResult<Bound<'a, PyAny>> {
58+
fn put<'a>(
59+
&'a self,
60+
py: Python<'a>,
61+
key: Vec<u8>,
62+
value: Vec<u8>,
63+
) -> PyResult<Bound<'a, PyAny>> {
5964
let client = self.0.clone();
6065
future_into_py(py, async move {
6166
let mut client = client.lock().await;

src/compare.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ impl PyCompareOp {
2727
}
2828
}
2929

30-
pub fn __richcmp__(&self, py: Python, rhs: &PyCompareOp, op: PyO3CompareOp) -> PyResult<Py<PyAny>> {
30+
pub fn __richcmp__(
31+
&self,
32+
py: Python,
33+
rhs: &PyCompareOp,
34+
op: PyO3CompareOp,
35+
) -> PyResult<Py<PyAny>> {
3136
match op {
3237
PyO3CompareOp::Eq => (self.0 == rhs.0)
3338
.into_pyobject(py)

src/error.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use pyo3::{create_exception, exceptions::PyException, types::{PyDict, PyDictMethods}, PyErr, Python, *};
1+
use pyo3::{
2+
create_exception,
3+
exceptions::PyException,
4+
types::{PyDict, PyDictMethods},
5+
PyErr, Python, *,
6+
};
27
use std::fmt::Debug;
38

49
create_exception!(etcd_client, ClientError, PyException);

0 commit comments

Comments
 (0)