Skip to content

Commit 900a857

Browse files
authored
Modernize: Format code with ruff and fix Python version (#52)
* Format code with ruff Apply ruff formatting to standardize code style across the project. * Fix README: Update Python version requirement to 3.11+ Align README with pyproject.toml which requires Python 3.11+. The previous documentation incorrectly stated Python 3.8 or higher. * CI: Add ruff format check to lint job Add `ruff format --check` to verify code formatting in CI. This ensures all code follows the project's formatting standards.
1 parent ddf7566 commit 900a857

File tree

11 files changed

+123
-153
lines changed

11 files changed

+123
-153
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
run: uv pip install -e ".[dev]"
3434
- name: Run ruff
3535
run: uv run ruff check .
36+
- name: Run ruff format check
37+
run: uv run ruff format --check .
3638
- name: Run mypy
3739
run: uv run mypy jsoncsv
3840

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Just use them.
1313

1414
## Requirements
1515

16-
- Python 3.8 or higher
16+
- Python 3.11 or higher
1717

1818
## Quick Start
1919

jsoncsv/dumptool.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def load_headers(
6767
class DumpExcel(Dump, ReadHeadersMixin):
6868
def initialize(self, **kwargs: Any) -> None:
6969
super().initialize(**kwargs)
70-
self._read_row = kwargs.get('read_row')
71-
self._sort_type = kwargs.get('sort_type')
70+
self._read_row = kwargs.get("read_row")
71+
self._sort_type = kwargs.get("sort_type")
7272

7373
def prepare(self) -> None:
7474
headers, datas = self.load_headers(self.fin, self._read_row, self._sort_type)
@@ -104,10 +104,7 @@ def write_headers(self) -> None:
104104
self.csv_writer.writeheader()
105105

106106
def write_obj(self, obj: dict[str, JsonType]) -> None:
107-
patched_obj: dict[str, str] = {
108-
key: self.patch_value(value)
109-
for key, value in obj.items()
110-
}
107+
patched_obj: dict[str, str] = {key: self.patch_value(value) for key, value in obj.items()}
111108
assert self.csv_writer is not None
112109
self.csv_writer.writerow(patched_obj)
113110

@@ -121,8 +118,8 @@ class DumpXLS(DumpExcel):
121118
def initialize(self, **kwargs: Any) -> None:
122119
super().initialize(**kwargs)
123120

124-
self.sheet = kwargs.get('sheet', 'Sheet1')
125-
self.wb = xlwt.Workbook(encoding='utf-8')
121+
self.sheet = kwargs.get("sheet", "Sheet1")
122+
self.wb = xlwt.Workbook(encoding="utf-8")
126123
self.ws = self.wb.add_sheet(self.sheet)
127124
self.row = 0
128125
self.cloumn = 0

jsoncsv/jsontool.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
)
2020

2121
__all__ = [
22-
'convert_json',
23-
'expand',
24-
'restore',
22+
"convert_json",
23+
"expand",
24+
"restore",
2525
]
2626

2727
# Type alias for the func parameter in convert_json
@@ -90,7 +90,7 @@ def from_leaf(leafs: Iterable[LeafInputType]) -> JsonType:
9090
return dict(child) # type: ignore[arg-type]
9191

9292

93-
def expand(origin: JsonType, separator: str = '.', safe: bool = False) -> dict[str, JsonType]:
93+
def expand(origin: JsonType, separator: str = ".", safe: bool = False) -> dict[str, JsonType]:
9494
root = origin
9595
leafs = gen_leaf(root)
9696

@@ -105,15 +105,15 @@ def expand(origin: JsonType, separator: str = '.', safe: bool = False) -> dict[s
105105
return expobj
106106

107107

108-
def restore(expobj: dict[str, JsonType], separator: str = '.', safe: bool = False) -> JsonType:
108+
def restore(expobj: dict[str, JsonType], separator: str = ".", safe: bool = False) -> JsonType:
109109
leafs: list[tuple[DecodedPathType, JsonType]] = []
110110

111111
items = expobj.items()
112112

113113
for key, value in items:
114114
path: DecodedPathType = decode_safe_key(key, separator) if safe else key.split(separator)
115115

116-
if key == '':
116+
if key == "":
117117
path = []
118118

119119
leafs.append((path, value))
@@ -126,13 +126,13 @@ def convert_json(
126126
fin: io.TextIOBase,
127127
fout: io.TextIOBase,
128128
func: ConvertFunc,
129-
separator: str = '.',
129+
separator: str = ".",
130130
safe: bool = False,
131131
json_array: bool = False,
132132
) -> None:
133-
'''
133+
"""
134134
ensure fin/fout is TextIO
135-
'''
135+
"""
136136

137137
if func not in [expand, restore]:
138138
raise ValueError("unknow convert_json type")
@@ -158,4 +158,4 @@ def gen_objs_from_array() -> Iterator[JsonType]:
158158
new = func(obj, separator=separator, safe=safe)
159159
content = json.dumps(new, ensure_ascii=False)
160160
fout.write(content)
161-
fout.write('\n')
161+
fout.write("\n")

jsoncsv/main.py

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,22 @@
1515

1616
def separator_type(sep: str) -> str:
1717
if len(sep) != 1:
18-
raise click.BadOptionUsage(option_name='separator',
19-
message='separator can only be a char')
18+
raise click.BadOptionUsage(option_name="separator", message="separator can only be a char")
2019
if sep == unit_char:
21-
raise click.BadOptionUsage(option_name='separator',
22-
message='separator can not be `\\` ')
20+
raise click.BadOptionUsage(option_name="separator", message="separator can not be `\\` ")
2321
return sep
2422

2523

2624
@click.command()
27-
@click.option('-A',
28-
'--array',
29-
'json_array',
30-
is_flag=True,
31-
default=False,
32-
help='read input file as json array')
33-
@click.option('-s',
34-
'--sep',
35-
'separator',
36-
type=separator_type,
37-
default='.',
38-
help='separator')
39-
@click.option('--safe', is_flag=True, help='use safe mode')
40-
@click.option('-r',
41-
'--restore',
42-
'restore',
43-
is_flag=True,
44-
help='restore expanded json')
45-
@click.option('-e',
46-
'--expand',
47-
'expand',
48-
is_flag=True,
49-
help='expand json (default True)')
50-
@click.argument('input', type=click.File('r', encoding='utf-8'), default='-')
51-
@click.argument('output', type=click.File('w', encoding='utf-8'), default='-')
25+
@click.option(
26+
"-A", "--array", "json_array", is_flag=True, default=False, help="read input file as json array"
27+
)
28+
@click.option("-s", "--sep", "separator", type=separator_type, default=".", help="separator")
29+
@click.option("--safe", is_flag=True, help="use safe mode")
30+
@click.option("-r", "--restore", "restore", is_flag=True, help="restore expanded json")
31+
@click.option("-e", "--expand", "expand", is_flag=True, help="expand json (default True)")
32+
@click.argument("input", type=click.File("r", encoding="utf-8"), default="-")
33+
@click.argument("output", type=click.File("w", encoding="utf-8"), default="-")
5234
def jsoncsv(
5335
output: io.TextIOBase,
5436
input: io.TextIOBase,
@@ -59,42 +41,34 @@ def jsoncsv(
5941
json_array: bool,
6042
) -> None:
6143
if expand and restore:
62-
raise click.UsageError('can not choose both, default is `-e`')
44+
raise click.UsageError("can not choose both, default is `-e`")
6345

6446
func: Callable[..., Any]
6547
func = expand_fn if not restore else restore_fn
6648

67-
convert_json(input,
68-
output,
69-
func,
70-
separator=separator,
71-
safe=safe,
72-
json_array=json_array)
49+
convert_json(input, output, func, separator=separator, safe=safe, json_array=json_array)
7350

7451
input.close()
7552
output.close()
7653

7754

7855
@click.command()
79-
@click.option('-t',
80-
'--type',
81-
'type_',
82-
type=click.Choice(['csv', 'xls']),
83-
default='csv',
84-
help='choose dump format')
85-
@click.option('-r',
86-
'--row',
87-
type=int,
88-
default=None,
89-
help='number of pre-read `row` lines to load `headers`')
90-
@click.option('-s',
91-
'--sort',
92-
'sort_',
93-
is_flag=True,
94-
default=False,
95-
help='enable sort the headers keys')
96-
@click.argument('input', type=click.File('r', encoding='utf-8'), default='-')
97-
@click.argument('output', type=click.Path(), default='-')
56+
@click.option(
57+
"-t",
58+
"--type",
59+
"type_",
60+
type=click.Choice(["csv", "xls"]),
61+
default="csv",
62+
help="choose dump format",
63+
)
64+
@click.option(
65+
"-r", "--row", type=int, default=None, help="number of pre-read `row` lines to load `headers`"
66+
)
67+
@click.option(
68+
"-s", "--sort", "sort_", is_flag=True, default=False, help="enable sort the headers keys"
69+
)
70+
@click.argument("input", type=click.File("r", encoding="utf-8"), default="-")
71+
@click.argument("output", type=click.Path(), default="-")
9872
def mkexcel(
9973
output: str,
10074
input: io.TextIOBase,
@@ -107,13 +81,13 @@ def mkexcel(
10781
klass = dumptool.DumpXLS
10882

10983
# Open file in appropriate mode based on type
110-
if output == '-':
111-
fout: Any = sys.stdout.buffer if type_ == 'xls' else sys.stdout
84+
if output == "-":
85+
fout: Any = sys.stdout.buffer if type_ == "xls" else sys.stdout
11286
dump_excel(input, fout, klass, read_row=row, sort_type=sort_)
11387
else:
114-
mode = 'wb' if type_ == 'xls' else 'w'
115-
encoding = None if type_ == 'xls' else 'utf-8'
116-
newline = '' if type_ == 'csv' else None
88+
mode = "wb" if type_ == "xls" else "w"
89+
encoding = None if type_ == "xls" else "utf-8"
90+
newline = "" if type_ == "csv" else None
11791
with open(output, mode, encoding=encoding, newline=newline) as fout:
11892
dump_excel(input, fout, klass, read_row=row, sort_type=sort_)
11993

jsoncsv/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
# 2016.11.20
33

44
# Type aliases for JSON data structures
5-
JsonType = dict[str, 'JsonType'] | list['JsonType'] | str | int | float | bool | None
5+
JsonType = dict[str, "JsonType"] | list["JsonType"] | str | int | float | bool | None
66
PathType = list[int | str] # Can contain ints (array indices) or strings (dict keys)
77
DecodedPathType = list[str] # Decoded paths from keys are always strings
88
LeafType = tuple[PathType, JsonType]
99
# Type for leafs that can contain either PathType (from gen_leaf) or DecodedPathType (from restore)
1010
LeafInputType = LeafType | tuple[DecodedPathType, JsonType]
1111

12-
unit_char = '\\'
12+
unit_char = "\\"
1313

1414

1515
def encode_safe_key(path: list[str], separator: str) -> str:
@@ -20,13 +20,13 @@ def encode_safe_key(path: list[str], separator: str) -> str:
2020

2121
def decode_safe_key(key: str, separator: str) -> list[str]:
2222
path: list[str] = []
23-
p = ''
23+
p = ""
2424
escape = False
2525

2626
for char in key:
2727
if escape and char == separator:
2828
path.append(p)
29-
p = ''
29+
p = ""
3030
escape = False
3131
elif escape and char == unit_char:
3232
p += unit_char
@@ -36,6 +36,6 @@ def decode_safe_key(key: str, separator: str) -> list[str]:
3636
else:
3737
p += char
3838

39-
if p != '':
39+
if p != "":
4040
path.append(p)
4141
return path

tests/test_dumptool.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,54 @@
77

88

99
class TestDumpTool(unittest.TestCase):
10-
1110
# FIXME (使用虚拟文件)
1211
def test_dumpexcel_csv(self):
13-
with open('./fixture/files/expand.1.json', encoding='utf-8') as fin, \
14-
open('./fixture/files/tmp.output.1.csv', 'w', encoding='utf-8', newline='') as fout:
12+
with (
13+
open("./fixture/files/expand.1.json", encoding="utf-8") as fin,
14+
open("./fixture/files/tmp.output.1.csv", "w", encoding="utf-8", newline="") as fout,
15+
):
1516
dump_excel(fin, fout, DumpCSV)
1617

17-
with open('./fixture/files/output.1.csv', encoding='utf-8') as output, \
18-
open('./fixture/files/tmp.output.1.csv', encoding='utf-8') as fout:
18+
with (
19+
open("./fixture/files/output.1.csv", encoding="utf-8") as output,
20+
open("./fixture/files/tmp.output.1.csv", encoding="utf-8") as fout,
21+
):
1922
self.assertEqual(output.read(), fout.read())
2023

2124
def test_dumpexcel_csv_with_sort(self):
22-
with open('./fixture/files/expand.1.json', encoding='utf-8') as fin, \
23-
open('./fixture/files/tmp.output.1.sort.csv', 'w', encoding='utf-8', newline='') as fout:
25+
with (
26+
open("./fixture/files/expand.1.json", encoding="utf-8") as fin,
27+
open(
28+
"./fixture/files/tmp.output.1.sort.csv", "w", encoding="utf-8", newline=""
29+
) as fout,
30+
):
2431
dump_excel(fin, fout, DumpCSV, sort_type=True)
2532

26-
with open('./fixture/files/output.1.sort.csv', encoding='utf-8') as output, \
27-
open('./fixture/files/tmp.output.1.sort.csv', encoding='utf-8') as fout:
33+
with (
34+
open("./fixture/files/output.1.sort.csv", encoding="utf-8") as output,
35+
open("./fixture/files/tmp.output.1.sort.csv", encoding="utf-8") as fout,
36+
):
2837
self.assertEqual(output.read(), fout.read())
2938

3039
def test_dumpcexcel_xls(self):
31-
with open('./fixture/files/expand.1.json', encoding='utf-8') as fin, \
32-
open('./fixture/files/tmp.output.1.xls', 'wb') as fout:
40+
with (
41+
open("./fixture/files/expand.1.json", encoding="utf-8") as fin,
42+
open("./fixture/files/tmp.output.1.xls", "wb") as fout,
43+
):
3344
dump_excel(fin, fout, DumpXLS)
3445

3546
def test_dump_csv_with_non_ascii(self):
36-
with open('./fixture/files/expand.2.json', encoding='utf-8') as fin, \
37-
open('./fixture/files/tmp.output.2.csv', 'w', encoding='utf-8', newline='') as fout:
47+
with (
48+
open("./fixture/files/expand.2.json", encoding="utf-8") as fin,
49+
open("./fixture/files/tmp.output.2.csv", "w", encoding="utf-8", newline="") as fout,
50+
):
3851
dump_excel(fin, fout, DumpCSV)
3952

4053
def test_dump_xls_with_non_ascii(self):
41-
with open('./fixture/files/expand.2.json', encoding='utf-8') as fin, \
42-
open('./fixture/files/tmp.output.2.xls', 'wb') as fout:
54+
with (
55+
open("./fixture/files/expand.2.json", encoding="utf-8") as fin,
56+
open("./fixture/files/tmp.output.2.xls", "wb") as fout,
57+
):
4358
dump_excel(fin, fout, DumpXLS)
4459

4560
def test_dump_xls_with_dict(self):

tests/test_escape.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,28 @@
77

88

99
class Testescape(unittest.TestCase):
10-
1110
def test_all(self):
12-
path = ['A', 'B', '..', '\\.\\ww']
11+
path = ["A", "B", "..", "\\.\\ww"]
1312

14-
for sep in 'AB.w':
13+
for sep in "AB.w":
1514
key = encode_safe_key(path, sep)
1615
_path = decode_safe_key(key, sep)
1716

1817
self.assertListEqual(path, _path)
1918

2019
def test_encode(self):
21-
path = ['A', 'B', 'C', 'www.xxx.com']
22-
sep = '.'
20+
path = ["A", "B", "C", "www.xxx.com"]
21+
sep = "."
2322
key = encode_safe_key(path, sep)
2423

25-
self.assertEqual(key, 'A\\.B\\.C\\.www.xxx.com')
24+
self.assertEqual(key, "A\\.B\\.C\\.www.xxx.com")
2625

2726
def test_decode(self):
28-
key = 'A\\.B\\.C\\.www.xxx.com'
29-
sep = '.'
27+
key = "A\\.B\\.C\\.www.xxx.com"
28+
sep = "."
3029
path = decode_safe_key(key, sep)
3130

32-
self.assertEqual(path[0], 'A')
33-
self.assertEqual(path[1], 'B')
34-
self.assertEqual(path[2], 'C')
35-
self.assertEqual(path[3], 'www.xxx.com')
31+
self.assertEqual(path[0], "A")
32+
self.assertEqual(path[1], "B")
33+
self.assertEqual(path[2], "C")
34+
self.assertEqual(path[3], "www.xxx.com")

0 commit comments

Comments
 (0)