Add diff support to blackd (#969)
This commit is contained in:
parent
b65af236cf
commit
df6e1a41f7
@ -838,6 +838,9 @@ which if present, should have the value `1`, otherwise the request is rejected w
|
|||||||
|
|
||||||
The headers controlling how code is formatted are:
|
The headers controlling how code is formatted are:
|
||||||
|
|
||||||
|
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
|
||||||
|
response, mentioning the name of the problematic header in the message body.
|
||||||
|
|
||||||
- `X-Line-Length`: corresponds to the `--line-length` command line flag.
|
- `X-Line-Length`: corresponds to the `--line-length` command line flag.
|
||||||
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
|
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
|
||||||
command line flag. If present and its value is not the empty string, no string
|
command line flag. If present and its value is not the empty string, no string
|
||||||
@ -849,6 +852,8 @@ The headers controlling how code is formatted are:
|
|||||||
a set of comma-separated Python versions, optionally prefixed with `py`. For example,
|
a set of comma-separated Python versions, optionally prefixed with `py`. For example,
|
||||||
to request code that is compatible with Python 3.5 and 3.6, set the header to
|
to request code that is compatible with Python 3.5 and 3.6, set the header to
|
||||||
`py3.5,py3.6`.
|
`py3.5,py3.6`.
|
||||||
|
- `X-Diff`: corresponds to the `--diff` command line flag. If present, a diff of the
|
||||||
|
formats will be output.
|
||||||
|
|
||||||
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
|
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
|
||||||
response, mentioning the name of the problematic header in the message body.
|
response, mentioning the name of the problematic header in the message body.
|
||||||
@ -1034,6 +1039,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
|
|||||||
|
|
||||||
- `blackd` now returns the version of _Black_ in the response headers (#1013)
|
- `blackd` now returns the version of _Black_ in the response headers (#1013)
|
||||||
|
|
||||||
|
- `blackd` can now output the diff of formats on source code when the `X-Diff` header is
|
||||||
|
provided (#969)
|
||||||
|
|
||||||
### 19.3b0
|
### 19.3b0
|
||||||
|
|
||||||
- new option `--target-version` to control which Python versions _Black_-formatted code
|
- new option `--target-version` to control which Python versions _Black_-formatted code
|
||||||
|
18
blackd.py
18
blackd.py
@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from concurrent.futures import Executor, ProcessPoolExecutor
|
from concurrent.futures import Executor, ProcessPoolExecutor
|
||||||
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
from multiprocessing import freeze_support
|
from multiprocessing import freeze_support
|
||||||
@ -21,6 +22,7 @@
|
|||||||
PYTHON_VARIANT_HEADER = "X-Python-Variant"
|
PYTHON_VARIANT_HEADER = "X-Python-Variant"
|
||||||
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
|
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
|
||||||
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
|
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
|
||||||
|
DIFF_HEADER = "X-Diff"
|
||||||
|
|
||||||
BLACK_HEADERS = [
|
BLACK_HEADERS = [
|
||||||
PROTOCOL_VERSION_HEADER,
|
PROTOCOL_VERSION_HEADER,
|
||||||
@ -28,6 +30,7 @@
|
|||||||
PYTHON_VARIANT_HEADER,
|
PYTHON_VARIANT_HEADER,
|
||||||
SKIP_STRING_NORMALIZATION_HEADER,
|
SKIP_STRING_NORMALIZATION_HEADER,
|
||||||
FAST_OR_SAFE_HEADER,
|
FAST_OR_SAFE_HEADER,
|
||||||
|
DIFF_HEADER,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Response headers
|
# Response headers
|
||||||
@ -112,10 +115,25 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
|
|||||||
req_bytes = await request.content.read()
|
req_bytes = await request.content.read()
|
||||||
charset = request.charset if request.charset is not None else "utf8"
|
charset = request.charset if request.charset is not None else "utf8"
|
||||||
req_str = req_bytes.decode(charset)
|
req_str = req_bytes.decode(charset)
|
||||||
|
then = datetime.utcnow()
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
formatted_str = await loop.run_in_executor(
|
formatted_str = await loop.run_in_executor(
|
||||||
executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
|
executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Only output the diff in the HTTP response
|
||||||
|
only_diff = bool(request.headers.get(DIFF_HEADER, False))
|
||||||
|
if only_diff:
|
||||||
|
now = datetime.utcnow()
|
||||||
|
src_name = f"In\t{then} +0000"
|
||||||
|
dst_name = f"Out\t{now} +0000"
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
formatted_str = await loop.run_in_executor(
|
||||||
|
executor,
|
||||||
|
partial(black.diff, req_str, formatted_str, src_name, dst_name),
|
||||||
|
)
|
||||||
|
|
||||||
return web.Response(
|
return web.Response(
|
||||||
content_type=request.content_type,
|
content_type=request.content_type,
|
||||||
charset=charset,
|
charset=charset,
|
||||||
|
14
tests/data/blackd_diff.diff
Normal file
14
tests/data/blackd_diff.diff
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
--- [Deterministic header]
|
||||||
|
+++ [Deterministic header]
|
||||||
|
@@ -1,7 +1,6 @@
|
||||||
|
-def abc ():
|
||||||
|
- return ["hello", "world",
|
||||||
|
- "!"]
|
||||||
|
+def abc():
|
||||||
|
+ return ["hello", "world", "!"]
|
||||||
|
|
||||||
|
-print( "Incorrect formatting"
|
||||||
|
-)
|
||||||
|
|
||||||
|
+print("Incorrect formatting")
|
||||||
|
+
|
6
tests/data/blackd_diff.py
Normal file
6
tests/data/blackd_diff.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
def abc ():
|
||||||
|
return ["hello", "world",
|
||||||
|
"!"]
|
||||||
|
|
||||||
|
print( "Incorrect formatting"
|
||||||
|
)
|
@ -35,6 +35,7 @@
|
|||||||
fs = partial(black.format_str, mode=black.FileMode())
|
fs = partial(black.format_str, mode=black.FileMode())
|
||||||
THIS_FILE = Path(__file__)
|
THIS_FILE = Path(__file__)
|
||||||
THIS_DIR = THIS_FILE.parent
|
THIS_DIR = THIS_FILE.parent
|
||||||
|
DETERMINISTIC_HEADER = "[Deterministic header]"
|
||||||
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
|
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
|
||||||
PY36_ARGS = [
|
PY36_ARGS = [
|
||||||
f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
|
f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
|
||||||
@ -259,7 +260,7 @@ def test_piping_diff(self) -> None:
|
|||||||
black.main, args, input=BytesIO(source.encode("utf8"))
|
black.main, args, input=BytesIO(source.encode("utf8"))
|
||||||
)
|
)
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
actual = diff_header.sub("[Deterministic header]", result.output)
|
actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
|
||||||
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@ -340,7 +341,7 @@ def test_expression_diff(self) -> None:
|
|||||||
finally:
|
finally:
|
||||||
os.unlink(tmp_file)
|
os.unlink(tmp_file)
|
||||||
actual = result.output
|
actual = result.output
|
||||||
actual = diff_header.sub("[Deterministic header]", actual)
|
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
|
||||||
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
actual = actual.rstrip() + "\n" # the diff output has a trailing space
|
||||||
if expected != actual:
|
if expected != actual:
|
||||||
dump = black.dump_to_file(actual)
|
dump = black.dump_to_file(actual)
|
||||||
@ -1689,6 +1690,27 @@ async def test_blackd_pyi(self) -> None:
|
|||||||
self.assertEqual(response.status, 200)
|
self.assertEqual(response.status, 200)
|
||||||
self.assertEqual(await response.text(), expected)
|
self.assertEqual(await response.text(), expected)
|
||||||
|
|
||||||
|
@skip_if_exception("ClientOSError")
|
||||||
|
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_blackd_diff(self) -> None:
|
||||||
|
diff_header = re.compile(
|
||||||
|
rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
|
||||||
|
rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
|
||||||
|
)
|
||||||
|
|
||||||
|
source, _ = read_data("blackd_diff.py")
|
||||||
|
expected, _ = read_data("blackd_diff.diff")
|
||||||
|
|
||||||
|
response = await self.client.post(
|
||||||
|
"/", data=source, headers={blackd.DIFF_HEADER: "true"}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
|
||||||
|
actual = await response.text()
|
||||||
|
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
@skip_if_exception("ClientOSError")
|
@skip_if_exception("ClientOSError")
|
||||||
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
|
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
|
Loading…
Reference in New Issue
Block a user