Skip to content

Commit c4fedda

Browse files
authored
Add reprs for py classes (#146)
* Add reprs for py classes and dataclass-esque constructor for WriteOptions * Switch to ruff instead of black and isort
1 parent 47c5931 commit c4fedda

File tree

8 files changed

+119
-28
lines changed

8 files changed

+119
-28
lines changed

.github/workflows/python-test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ jobs:
5656
run: |
5757
source .venv/bin/activate
5858
mypy
59-
isort . --check
60-
black . --check
59+
ruff check .
60+
ruff format . --check --diff
6161
6262
- name: Run tests
6363
run: |

python/conftest.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import fsspec
66
import pytest
77

8+
from hdfs_native import Client
89
from hdfs_native.fsspec import HdfsFileSystem
910

1011

@@ -35,10 +36,15 @@ def minidfs():
3536

3637
try:
3738
child.communicate(input="\n", timeout=30)
38-
except:
39+
except: # noqa: E722
3940
child.kill()
4041

4142

43+
@pytest.fixture(scope="module")
44+
def client(minidfs: str) -> Client:
45+
return Client(minidfs)
46+
47+
4248
@pytest.fixture(scope="module")
4349
def fs(minidfs: str) -> HdfsFileSystem:
4450
url = urllib.parse.urlparse(minidfs)

python/hdfs_native/__init__.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import io
22
import os
3-
from typing import Dict, Iterator, Optional
3+
from typing import TYPE_CHECKING, Dict, Iterator, Optional
44

55
# For some reason mypy doesn't think this exists
66
from typing_extensions import Buffer # type: ignore
77

8-
from ._internal import *
8+
from ._internal import RawClient, WriteOptions
99

10+
if TYPE_CHECKING:
11+
from ._internal import ContentSummary, FileStatus, RawFileReader, RawFileWriter
1012

11-
class FileReader(io.RawIOBase):
1213

14+
class FileReader(io.RawIOBase):
1315
def __init__(self, inner: "RawFileReader"):
1416
self.inner = inner
1517

@@ -64,7 +66,6 @@ def close(self) -> None:
6466

6567

6668
class FileWriter(io.RawIOBase):
67-
6869
def __init__(self, inner: "RawFileWriter"):
6970
self.inner = inner
7071

@@ -87,7 +88,6 @@ def __exit__(self, *_args):
8788

8889

8990
class Client:
90-
9191
def __init__(
9292
self,
9393
url: Optional[str] = None,

python/hdfs_native/_internal.pyi

+9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ class WriteOptions:
3030
overwrite: bool
3131
create_parent: bool
3232

33+
def __init__(
34+
self,
35+
block_size: Optional[int] = None,
36+
replication: Optional[int] = None,
37+
permission: Optional[int] = None,
38+
overwrite: Optional[bool] = None,
39+
create_parent: Optional[bool] = None,
40+
): ...
41+
3342
class RawFileReader:
3443
def file_length(self) -> int:
3544
"""Returns the size of the file"""

python/pyproject.toml

+6-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ dependencies = [
2020

2121
[project.optional-dependencies]
2222
devel = [
23-
"mypy~=1.8.0",
24-
"ruff~=0.4.8",
25-
"pytest~=7.4",
26-
"black~=24.4",
27-
"isort~=5.13"
23+
"mypy~=1.13.0",
24+
"ruff~=0.7.2",
25+
"pytest~=8.3",
2826
]
2927

3028
[project.urls]
@@ -53,3 +51,6 @@ testpaths = [
5351
"tests",
5452
"hdfs_native",
5553
]
54+
55+
[tool.ruff.lint]
56+
extend-select = ["I"]

python/src/lib.rs

+70-3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ impl From<FileStatus> for PyFileStatus {
5050
}
5151
}
5252

53+
#[pymethods]
54+
impl PyFileStatus {
55+
/// Return a dataclass-esque format for the repr
56+
fn __repr__(&self) -> String {
57+
format!("FileStatus(path='{}', length={}, isdir={}, permission={}, owner={}, group={}, modification_time={}, access_time={}, replication={}, blocksize={})",
58+
self.path,
59+
self.length,
60+
self.isdir,
61+
self.permission,
62+
self.owner,
63+
self.group,
64+
self.modification_time,
65+
self.access_time,
66+
self.replication.map(|r| r.to_string()).unwrap_or("None".to_string()),
67+
self.blocksize.map(|r| r.to_string()).unwrap_or("None".to_string())
68+
)
69+
}
70+
}
71+
5372
#[pyclass(name = "FileStatusIter")]
5473
struct PyFileStatusIter {
5574
inner: ListStatusIterator,
@@ -96,6 +115,21 @@ impl From<ContentSummary> for PyContentSummary {
96115
}
97116
}
98117

118+
#[pymethods]
119+
impl PyContentSummary {
120+
/// Return a dataclass-esque format for the repr
121+
fn __repr__(&self) -> String {
122+
format!("ContentSummary(length={}, file_count={}, directory_count={}, quota={}, space_consumed={}, space_quota={})",
123+
self.length,
124+
self.file_count,
125+
self.directory_count,
126+
self.quota,
127+
self.space_consumed,
128+
self.space_quota,
129+
)
130+
}
131+
}
132+
99133
#[pyclass]
100134
struct RawFileReader {
101135
inner: FileReader,
@@ -173,9 +207,42 @@ impl From<WriteOptions> for PyWriteOptions {
173207
#[pymethods]
174208
impl PyWriteOptions {
175209
#[new]
176-
#[pyo3(signature = ())]
177-
pub fn new() -> Self {
178-
Self::from(WriteOptions::default())
210+
pub fn new(
211+
block_size: Option<u64>,
212+
replication: Option<u32>,
213+
permission: Option<u32>,
214+
overwrite: Option<bool>,
215+
create_parent: Option<bool>,
216+
) -> Self {
217+
let mut write_options = WriteOptions::default();
218+
if let Some(block_size) = block_size {
219+
write_options = write_options.block_size(block_size);
220+
}
221+
if let Some(replication) = replication {
222+
write_options = write_options.replication(replication);
223+
}
224+
if let Some(permission) = permission {
225+
write_options = write_options.permission(permission);
226+
}
227+
if let Some(overwrite) = overwrite {
228+
write_options = write_options.overwrite(overwrite);
229+
}
230+
if let Some(create_parent) = create_parent {
231+
write_options = write_options.create_parent(create_parent);
232+
}
233+
234+
PyWriteOptions::from(write_options)
235+
}
236+
237+
/// Return a dataclass-esque format for the repr
238+
fn __repr__(&self) -> String {
239+
format!("WriteOptions(block_size={}, replication={}, permission={}, overwrite={}, create_parent={})",
240+
self.block_size.map(|x| x.to_string()).unwrap_or("None".to_string()),
241+
self.replication.map(|x| x.to_string()).unwrap_or("None".to_string()),
242+
self.permission,
243+
self.overwrite,
244+
self.create_parent
245+
)
179246
}
180247
}
181248

python/tests/test_fsspec.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,16 @@ def test_dirs(fs: HdfsFileSystem):
1010
fs.mkdir("/testdir")
1111
assert fs.info("/testdir")["type"] == "directory"
1212

13-
try:
13+
with pytest.raises(FileExistsError):
1414
fs.makedirs("/testdir", exist_ok=False)
15-
assert False, '"/testdir" already exists, should fail'
16-
except:
17-
pass
1815

1916
fs.makedirs("/testdir", exist_ok=True)
2017

2118
fs.mkdir("/testdir/nested/dir")
2219
assert fs.info("/testdir/nested/dir")["type"] == "directory"
2320

24-
try:
21+
with pytest.raises(RuntimeError):
2522
fs.mkdir("/testdir/nested2/dir", create_parents=False)
26-
assert False, "Should fail to make dir because parent doesn't exist"
27-
except:
28-
pass
2923

3024
with pytest.raises(RuntimeError):
3125
fs.rm("/testdir", recursive=False)

python/tests/test_integration.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
from hdfs_native import Client, WriteOptions
44

55

6-
def test_integration(minidfs: str):
7-
client = Client(minidfs)
8-
client.create("/testfile", WriteOptions()).close()
6+
def test_integration(client: Client):
7+
client.create("/testfile").close()
98
file_info = client.get_file_info("/testfile")
109

1110
assert file_info.path == "/testfile"
@@ -25,7 +24,7 @@ def test_integration(minidfs: str):
2524
file_list = list(client.list_status("/", False))
2625
assert len(file_list) == 0
2726

28-
with client.create("/testfile", WriteOptions()) as file:
27+
with client.create("/testfile") as file:
2928
data = io.BytesIO()
3029

3130
for i in range(0, 32 * 1024 * 1024):
@@ -101,3 +100,18 @@ def test_integration(minidfs: str):
101100
assert content_summary.length == 33 * 1024 * 1024 * 4
102101

103102
client.delete("/testfile", False)
103+
104+
105+
def test_write_options(client: Client):
106+
with client.create("/testfile") as file:
107+
file.write(b"abcd")
108+
109+
client.create(
110+
"/testfile",
111+
WriteOptions(overwrite=True, permission=0o700, block_size=1024 * 1024),
112+
).close()
113+
114+
file_info = client.get_file_info("/testfile")
115+
assert file_info.length == 0
116+
assert file_info.permission == 0o700
117+
assert file_info.blocksize == 1024 * 1024

0 commit comments

Comments
 (0)