Skip to content

Commit f27579e

Browse files
chore(internal): use ruff instead of black for formatting (#241)
1 parent cdd7134 commit f27579e

File tree

9 files changed

+51
-133
lines changed

9 files changed

+51
-133
lines changed

api.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,7 @@ Methods:
8484
Types:
8585

8686
```python
87-
from finch.types.hris import (
88-
PayStatement,
89-
PayStatementResponse,
90-
PayStatementResponseBody,
91-
)
87+
from finch.types.hris import PayStatement, PayStatementResponse, PayStatementResponseBody
9288
```
9389

9490
Methods:

bin/blacken-docs.py renamed to bin/ruffen-docs.py

+23-107
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
# fork of https://github.com/asottile/blacken-docs implementing https://github.com/asottile/blacken-docs/issues/170
1+
# fork of https://github.com/asottile/blacken-docs adapted for ruff
22
from __future__ import annotations
33

44
import re
5+
import sys
56
import argparse
67
import textwrap
78
import contextlib
9+
import subprocess
810
from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
911

10-
import black
11-
from black.mode import TargetVersion
12-
from black.const import DEFAULT_LINE_LENGTH
13-
1412
MD_RE = re.compile(
1513
r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)",
1614
re.DOTALL | re.MULTILINE,
@@ -19,55 +17,12 @@
1917
r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)",
2018
re.DOTALL | re.MULTILINE,
2119
)
22-
RST_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy"))
23-
BLOCK_TYPES = "(code|code-block|sourcecode|ipython)"
24-
DOCTEST_TYPES = "(testsetup|testcleanup|testcode)"
25-
RST_RE = re.compile(
26-
rf"(?P<before>"
27-
rf"^(?P<indent> *)\.\. ("
28-
rf"jupyter-execute::|"
29-
rf"{BLOCK_TYPES}:: (?P<lang>\w+)|"
30-
rf"{DOCTEST_TYPES}::.*"
31-
rf")\n"
32-
rf"((?P=indent) +:.*\n)*"
33-
rf"\n*"
34-
rf")"
35-
rf"(?P<code>(^((?P=indent) +.*)?\n)+)",
36-
re.MULTILINE,
37-
)
38-
RST_PYCON_RE = re.compile(
39-
r"(?P<before>"
40-
r"(?P<indent> *)\.\. ((code|code-block):: pycon|doctest::.*)\n"
41-
r"((?P=indent) +:.*\n)*"
42-
r"\n*"
43-
r")"
44-
r"(?P<code>(^((?P=indent) +.*)?(\n|$))+)",
45-
re.MULTILINE,
46-
)
4720
PYCON_PREFIX = ">>> "
4821
PYCON_CONTINUATION_PREFIX = "..."
4922
PYCON_CONTINUATION_RE = re.compile(
5023
rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
5124
)
52-
LATEX_RE = re.compile(
53-
r"(?P<before>^(?P<indent> *)\\begin{minted}{python}\n)"
54-
r"(?P<code>.*?)"
55-
r"(?P<after>^(?P=indent)\\end{minted}\s*$)",
56-
re.DOTALL | re.MULTILINE,
57-
)
58-
LATEX_PYCON_RE = re.compile(
59-
r"(?P<before>^(?P<indent> *)\\begin{minted}{pycon}\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)\\end{minted}\s*$)",
60-
re.DOTALL | re.MULTILINE,
61-
)
62-
PYTHONTEX_LANG = r"(?P<lang>pyblock|pycode|pyconsole|pyverbatim)"
63-
PYTHONTEX_RE = re.compile(
64-
rf"(?P<before>^(?P<indent> *)\\begin{{{PYTHONTEX_LANG}}}\n)"
65-
rf"(?P<code>.*?)"
66-
rf"(?P<after>^(?P=indent)\\end{{(?P=lang)}}\s*$)",
67-
re.DOTALL | re.MULTILINE,
68-
)
69-
INDENT_RE = re.compile("^ +(?=[^ ])", re.MULTILINE)
70-
TRAILING_NL_RE = re.compile(r"\n+\Z", re.MULTILINE)
25+
DEFAULT_LINE_LENGTH = 100
7126

7227

7328
class CodeBlockError(NamedTuple):
@@ -77,7 +32,6 @@ class CodeBlockError(NamedTuple):
7732

7833
def format_str(
7934
src: str,
80-
black_mode: black.FileMode,
8135
) -> tuple[str, Sequence[CodeBlockError]]:
8236
errors: list[CodeBlockError] = []
8337

@@ -91,24 +45,10 @@ def _collect_error(match: Match[str]) -> Generator[None, None, None]:
9145
def _md_match(match: Match[str]) -> str:
9246
code = textwrap.dedent(match["code"])
9347
with _collect_error(match):
94-
code = black.format_str(code, mode=black_mode)
48+
code = format_code_block(code)
9549
code = textwrap.indent(code, match["indent"])
9650
return f'{match["before"]}{code}{match["after"]}'
9751

98-
def _rst_match(match: Match[str]) -> str:
99-
lang = match["lang"]
100-
if lang is not None and lang not in RST_PY_LANGS:
101-
return match[0]
102-
min_indent = min(INDENT_RE.findall(match["code"]))
103-
trailing_ws_match = TRAILING_NL_RE.search(match["code"])
104-
assert trailing_ws_match
105-
trailing_ws = trailing_ws_match.group()
106-
code = textwrap.dedent(match["code"])
107-
with _collect_error(match):
108-
code = black.format_str(code, mode=black_mode)
109-
code = textwrap.indent(code, min_indent)
110-
return f'{match["before"]}{code.rstrip()}{trailing_ws}'
111-
11252
def _pycon_match(match: Match[str]) -> str:
11353
code = ""
11454
fragment = cast(Optional[str], None)
@@ -119,7 +59,7 @@ def finish_fragment() -> None:
11959

12060
if fragment is not None:
12161
with _collect_error(match):
122-
fragment = black.format_str(fragment, mode=black_mode)
62+
fragment = format_code_block(fragment)
12363
fragment_lines = fragment.splitlines()
12464
code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
12565
for line in fragment_lines[1:]:
@@ -159,42 +99,33 @@ def _md_pycon_match(match: Match[str]) -> str:
15999
code = textwrap.indent(code, match["indent"])
160100
return f'{match["before"]}{code}{match["after"]}'
161101

162-
def _rst_pycon_match(match: Match[str]) -> str:
163-
code = _pycon_match(match)
164-
min_indent = min(INDENT_RE.findall(match["code"]))
165-
code = textwrap.indent(code, min_indent)
166-
return f'{match["before"]}{code}'
167-
168-
def _latex_match(match: Match[str]) -> str:
169-
code = textwrap.dedent(match["code"])
170-
with _collect_error(match):
171-
code = black.format_str(code, mode=black_mode)
172-
code = textwrap.indent(code, match["indent"])
173-
return f'{match["before"]}{code}{match["after"]}'
174-
175-
def _latex_pycon_match(match: Match[str]) -> str:
176-
code = _pycon_match(match)
177-
code = textwrap.indent(code, match["indent"])
178-
return f'{match["before"]}{code}{match["after"]}'
179-
180102
src = MD_RE.sub(_md_match, src)
181103
src = MD_PYCON_RE.sub(_md_pycon_match, src)
182-
src = RST_RE.sub(_rst_match, src)
183-
src = RST_PYCON_RE.sub(_rst_pycon_match, src)
184-
src = LATEX_RE.sub(_latex_match, src)
185-
src = LATEX_PYCON_RE.sub(_latex_pycon_match, src)
186-
src = PYTHONTEX_RE.sub(_latex_match, src)
187104
return src, errors
188105

189106

107+
def format_code_block(code: str) -> str:
108+
return subprocess.check_output(
109+
[
110+
sys.executable,
111+
"-m",
112+
"ruff",
113+
"format",
114+
"--stdin-filename=script.py",
115+
f"--line-length={DEFAULT_LINE_LENGTH}",
116+
],
117+
encoding="utf-8",
118+
input=code,
119+
)
120+
121+
190122
def format_file(
191123
filename: str,
192-
black_mode: black.FileMode,
193124
skip_errors: bool,
194125
) -> int:
195126
with open(filename, encoding="UTF-8") as f:
196127
contents = f.read()
197-
new_contents, errors = format_str(contents, black_mode)
128+
new_contents, errors = format_str(contents)
198129
for error in errors:
199130
lineno = contents[: error.offset].count("\n") + 1
200131
print(f"{filename}:{lineno}: code block parse error {error.exc}")
@@ -217,15 +148,6 @@ def main(argv: Sequence[str] | None = None) -> int:
217148
type=int,
218149
default=DEFAULT_LINE_LENGTH,
219150
)
220-
parser.add_argument(
221-
"-t",
222-
"--target-version",
223-
action="append",
224-
type=lambda v: TargetVersion[v.upper()],
225-
default=[],
226-
help=f"choices: {[v.name.lower() for v in TargetVersion]}",
227-
dest="target_versions",
228-
)
229151
parser.add_argument(
230152
"-S",
231153
"--skip-string-normalization",
@@ -235,15 +157,9 @@ def main(argv: Sequence[str] | None = None) -> int:
235157
parser.add_argument("filenames", nargs="*")
236158
args = parser.parse_args(argv)
237159

238-
black_mode = black.FileMode(
239-
target_versions=set(args.target_versions),
240-
line_length=args.line_length,
241-
string_normalization=not args.skip_string_normalization,
242-
)
243-
244160
retv = 0
245161
for filename in args.filenames:
246-
retv |= format_file(filename, black_mode, skip_errors=args.skip_errors)
162+
retv |= format_file(filename, skip_errors=args.skip_errors)
247163
return retv
248164

249165

pyproject.toml

+7-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ managed = true
4949
dev-dependencies = [
5050
"pyright",
5151
"mypy",
52-
"black",
5352
"respx",
5453
"pytest",
5554
"pytest-asyncio",
@@ -64,17 +63,18 @@ dev-dependencies = [
6463

6564
[tool.rye.scripts]
6665
format = { chain = [
67-
"format:black",
68-
"format:docs",
6966
"format:ruff",
67+
"format:docs",
68+
"fix:ruff",
7069
"format:isort",
7170
]}
7271
"format:black" = "black ."
73-
"format:docs" = "python bin/blacken-docs.py README.md api.md"
74-
"format:ruff" = "ruff --fix ."
72+
"format:docs" = "python bin/ruffen-docs.py README.md api.md"
73+
"format:ruff" = "ruff format"
7574
"format:isort" = "isort ."
7675

7776
"check:ruff" = "ruff ."
77+
"fix:ruff" = "ruff --fix ."
7878

7979
typecheck = { chain = [
8080
"typecheck:pyright",
@@ -160,6 +160,8 @@ unfixable = [
160160
]
161161
ignore-init-module-imports = true
162162

163+
[tool.ruff.format]
164+
docstring-code-format = true
163165

164166
[tool.ruff.per-file-ignores]
165167
"bin/**.py" = ["T201", "T203"]

requirements-dev.lock

+1-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ annotated-types==0.6.0
1111
anyio==4.1.0
1212
argcomplete==3.1.2
1313
attrs==23.1.0
14-
black==23.3.0
1514
certifi==2023.7.22
16-
click==8.1.7
1715
colorlog==6.7.0
1816
dirty-equals==0.6.0
1917
distlib==0.3.7
@@ -32,7 +30,6 @@ mypy-extensions==1.0.0
3230
nodeenv==1.8.0
3331
nox==2023.4.22
3432
packaging==23.2
35-
pathspec==0.11.2
3633
platformdirs==3.11.0
3734
pluggy==1.3.0
3835
py==1.11.0
@@ -44,7 +41,7 @@ pytest-asyncio==0.21.1
4441
python-dateutil==2.8.2
4542
pytz==2023.3.post1
4643
respx==0.20.2
47-
ruff==0.1.7
44+
ruff==0.1.9
4845
six==1.16.0
4946
sniffio==1.3.0
5047
time-machine==2.9.0

src/finch/_models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class RootModel(GenericModel, Generic[_T]):
382382
383383
For example:
384384
```py
385-
validated = RootModel[int](__root__='5').__root__
385+
validated = RootModel[int](__root__="5").__root__
386386
# validated: 5
387387
```
388388
"""

src/finch/_types.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,13 @@ class NotGiven:
278278
For example:
279279
280280
```py
281-
def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...
281+
def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response:
282+
...
283+
282284
283-
get(timeout=1) # 1s timeout
284-
get(timeout=None) # No timeout
285-
get() # Default timeout behavior, which may not be statically known at the method definition.
285+
get(timeout=1) # 1s timeout
286+
get(timeout=None) # No timeout
287+
get() # Default timeout behavior, which may not be statically known at the method definition.
286288
```
287289
"""
288290

@@ -304,14 +306,14 @@ class Omit:
304306
305307
```py
306308
# as the default `Content-Type` header is `application/json` that will be sent
307-
client.post('/upload/files', files={'file': b'my raw file content'})
309+
client.post("/upload/files", files={"file": b"my raw file content"})
308310
309311
# you can't explicitly override the header as it has to be dynamically generated
310312
# to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
311-
client.post(..., headers={'Content-Type': 'multipart/form-data'})
313+
client.post(..., headers={"Content-Type": "multipart/form-data"})
312314
313315
# instead you can remove the default `application/json` header by passing Omit
314-
client.post(..., headers={'Content-Type': Omit()})
316+
client.post(..., headers={"Content-Type": Omit()})
315317
```
316318
"""
317319

src/finch/_utils/_transform.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ def transform(
8080
8181
```py
8282
class Params(TypedDict, total=False):
83-
card_id: Required[Annotated[str, PropertyInfo(alias='cardID')]]
83+
card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
8484
85-
transformed = transform({'card_id': '<my card ID>'}, Params)
85+
86+
transformed = transform({"card_id": "<my card ID>"}, Params)
8687
# {'cardID': '<my card ID>'}
8788
```
8889

src/finch/_utils/_utils.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,15 @@ def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
211211
def foo(*, a: str) -> str:
212212
...
213213
214+
214215
@overload
215216
def foo(*, b: bool) -> str:
216217
...
217218
219+
218220
# This enforces the same constraints that a static type checker would
219221
# i.e. that either a or b must be passed to the function
220-
@required_args(['a'], ['b'])
222+
@required_args(["a"], ["b"])
221223
def foo(*, a: str | None = None, b: bool | None = None) -> str:
222224
...
223225
```

tests/test_transform.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ class DateDictWithRequiredAlias(TypedDict, total=False):
189189

190190
def test_datetime_with_alias() -> None:
191191
assert transform({"required_prop": None}, DateDictWithRequiredAlias) == {"prop": None} # type: ignore[comparison-overlap]
192-
assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap]
192+
assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == {
193+
"prop": "2023-02-23"
194+
} # type: ignore[comparison-overlap]
193195

194196

195197
class MyModel(BaseModel):

0 commit comments

Comments
 (0)