• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

psf / black / 7692220850

29 Jan 2024 06:46AM UTC coverage: 96.45%. Remained the same
7692220850

Pull #4192

github

web-flow
Bump peter-evans/create-or-update-comment from 3.1.0 to 4.0.0

Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 3.1.0 to 4.0.0.
- [Release notes](https://github.com/peter-evans/create-or-update-comment/releases)
- [Commits](https://github.com/peter-evans/create-or-update-comment/compare/23ff15729...71345be02)

---
updated-dependencies:
- dependency-name: peter-evans/create-or-update-comment
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4192: Bump peter-evans/create-or-update-comment from 3.1.0 to 4.0.0

3021 of 3232 branches covered (0.0%)

7145 of 7408 relevant lines covered (96.45%)

4.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.03
/src/blackd/__init__.py
1
import asyncio
5✔
2
import logging
5✔
3
from concurrent.futures import Executor, ProcessPoolExecutor
5✔
4
from datetime import datetime, timezone
5✔
5
from functools import partial
5✔
6
from multiprocessing import freeze_support
5✔
7
from typing import Set, Tuple
5✔
8

9
try:
5✔
10
    from aiohttp import web
5✔
11
    from multidict import MultiMapping
5✔
12

13
    from .middlewares import cors
5✔
14
except ImportError as ie:
×
15
    raise ImportError(
×
16
        f"aiohttp dependency is not installed: {ie}. "
17
        + "Please re-install black with the '[d]' extra install "
18
        + "to obtain aiohttp_cors: `pip install black[d]`"
19
    ) from None
20

21
import click
5✔
22

23
import black
5✔
24
from _black_version import version as __version__
5✔
25
from black.concurrency import maybe_install_uvloop
5✔
26

27
# This is used internally by tests to shut down the server prematurely
28
_stop_signal = asyncio.Event()
5✔
29

30
# Request headers
31
PROTOCOL_VERSION_HEADER = "X-Protocol-Version"
5✔
32
LINE_LENGTH_HEADER = "X-Line-Length"
5✔
33
PYTHON_VARIANT_HEADER = "X-Python-Variant"
5✔
34
SKIP_SOURCE_FIRST_LINE = "X-Skip-Source-First-Line"
5✔
35
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
5✔
36
SKIP_MAGIC_TRAILING_COMMA = "X-Skip-Magic-Trailing-Comma"
5✔
37
PREVIEW = "X-Preview"
5✔
38
UNSTABLE = "X-Unstable"
5✔
39
ENABLE_UNSTABLE_FEATURE = "X-Enable-Unstable-Feature"
5✔
40
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
5✔
41
DIFF_HEADER = "X-Diff"
5✔
42

43
BLACK_HEADERS = [
5✔
44
    PROTOCOL_VERSION_HEADER,
45
    LINE_LENGTH_HEADER,
46
    PYTHON_VARIANT_HEADER,
47
    SKIP_SOURCE_FIRST_LINE,
48
    SKIP_STRING_NORMALIZATION_HEADER,
49
    SKIP_MAGIC_TRAILING_COMMA,
50
    PREVIEW,
51
    UNSTABLE,
52
    ENABLE_UNSTABLE_FEATURE,
53
    FAST_OR_SAFE_HEADER,
54
    DIFF_HEADER,
55
]
56

57
# Response headers
58
BLACK_VERSION_HEADER = "X-Black-Version"
5✔
59

60

61
class HeaderError(Exception):
5✔
62
    pass
5✔
63

64

65
class InvalidVariantHeader(Exception):
5✔
66
    pass
5✔
67

68

69
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
5✔
70
@click.option(
5✔
71
    "--bind-host",
72
    type=str,
73
    help="Address to bind the server to.",
74
    default="localhost",
75
    show_default=True,
76
)
77
@click.option(
5✔
78
    "--bind-port", type=int, help="Port to listen on", default=45484, show_default=True
79
)
80
@click.version_option(version=black.__version__)
5✔
81
def main(bind_host: str, bind_port: int) -> None:
5✔
82
    logging.basicConfig(level=logging.INFO)
5✔
83
    app = make_app()
5✔
84
    ver = black.__version__
5✔
85
    black.out(f"blackd version {ver} listening on {bind_host} port {bind_port}")
5✔
86
    web.run_app(app, host=bind_host, port=bind_port, handle_signals=True, print=None)
5✔
87

88

89
def make_app() -> web.Application:
5✔
90
    app = web.Application(
5✔
91
        middlewares=[cors(allow_headers=(*BLACK_HEADERS, "Content-Type"))]
92
    )
93
    executor = ProcessPoolExecutor()
5✔
94
    app.add_routes([web.post("/", partial(handle, executor=executor))])
5✔
95
    return app
5✔
96

97

98
async def handle(request: web.Request, executor: Executor) -> web.Response:
5✔
99
    headers = {BLACK_VERSION_HEADER: __version__}
5✔
100
    try:
5✔
101
        if request.headers.get(PROTOCOL_VERSION_HEADER, "1") != "1":
5✔
102
            return web.Response(
5✔
103
                status=501, text="This server only supports protocol version 1"
104
            )
105

106
        fast = False
5✔
107
        if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast":
5!
108
            fast = True
×
109
        try:
5✔
110
            mode = parse_mode(request.headers)
5✔
111
        except HeaderError as e:
5✔
112
            return web.Response(status=400, text=e.args[0])
5✔
113
        req_bytes = await request.content.read()
5✔
114
        charset = request.charset if request.charset is not None else "utf8"
5✔
115
        req_str = req_bytes.decode(charset)
5✔
116
        then = datetime.now(timezone.utc)
5✔
117

118
        header = ""
5✔
119
        if mode.skip_source_first_line:
5✔
120
            first_newline_position: int = req_str.find("\n") + 1
5✔
121
            header = req_str[:first_newline_position]
5✔
122
            req_str = req_str[first_newline_position:]
5✔
123

124
        loop = asyncio.get_event_loop()
5✔
125
        formatted_str = await loop.run_in_executor(
5✔
126
            executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
127
        )
128

129
        # Preserve CRLF line endings
130
        nl = req_str.find("\n")
5✔
131
        if nl > 0 and req_str[nl - 1] == "\r":
5✔
132
            formatted_str = formatted_str.replace("\n", "\r\n")
5✔
133
            # If, after swapping line endings, nothing changed, then say so
134
            if formatted_str == req_str:
5✔
135
                raise black.NothingChanged
5✔
136

137
        # Put the source first line back
138
        req_str = header + req_str
5✔
139
        formatted_str = header + formatted_str
5✔
140

141
        # Only output the diff in the HTTP response
142
        only_diff = bool(request.headers.get(DIFF_HEADER, False))
5✔
143
        if only_diff:
5✔
144
            now = datetime.now(timezone.utc)
5✔
145
            src_name = f"In\t{then}"
5✔
146
            dst_name = f"Out\t{now}"
5✔
147
            loop = asyncio.get_event_loop()
5✔
148
            formatted_str = await loop.run_in_executor(
5✔
149
                executor,
150
                partial(black.diff, req_str, formatted_str, src_name, dst_name),
151
            )
152

153
        return web.Response(
5✔
154
            content_type=request.content_type,
155
            charset=charset,
156
            headers=headers,
157
            text=formatted_str,
158
        )
159
    except black.NothingChanged:
5✔
160
        return web.Response(status=204, headers=headers)
5✔
161
    except black.InvalidInput as e:
5!
162
        return web.Response(status=400, headers=headers, text=str(e))
5✔
163
    except Exception as e:
×
164
        logging.exception("Exception during handling a request")
×
165
        return web.Response(status=500, headers=headers, text=str(e))
×
166

167

168
def parse_mode(headers: MultiMapping[str]) -> black.Mode:
5✔
169
    try:
5✔
170
        line_length = int(headers.get(LINE_LENGTH_HEADER, black.DEFAULT_LINE_LENGTH))
5✔
171
    except ValueError:
5✔
172
        raise HeaderError("Invalid line length header value") from None
5✔
173

174
    if PYTHON_VARIANT_HEADER in headers:
5✔
175
        value = headers[PYTHON_VARIANT_HEADER]
5✔
176
        try:
5✔
177
            pyi, versions = parse_python_variant_header(value)
5✔
178
        except InvalidVariantHeader as e:
5✔
179
            raise HeaderError(
5✔
180
                f"Invalid value for {PYTHON_VARIANT_HEADER}: {e.args[0]}",
181
            ) from None
182
    else:
183
        pyi = False
5✔
184
        versions = set()
5✔
185

186
    skip_string_normalization = bool(
5✔
187
        headers.get(SKIP_STRING_NORMALIZATION_HEADER, False)
188
    )
189
    skip_magic_trailing_comma = bool(headers.get(SKIP_MAGIC_TRAILING_COMMA, False))
5✔
190
    skip_source_first_line = bool(headers.get(SKIP_SOURCE_FIRST_LINE, False))
5✔
191

192
    preview = bool(headers.get(PREVIEW, False))
5✔
193
    unstable = bool(headers.get(UNSTABLE, False))
5✔
194
    enable_features: Set[black.Preview] = set()
5✔
195
    enable_unstable_features = headers.get(ENABLE_UNSTABLE_FEATURE, "").split(",")
5✔
196
    for piece in enable_unstable_features:
5✔
197
        piece = piece.strip()
5✔
198
        if piece:
5!
199
            try:
×
200
                enable_features.add(black.Preview[piece])
×
201
            except KeyError:
×
202
                raise HeaderError(
×
203
                    f"Invalid value for {ENABLE_UNSTABLE_FEATURE}: {piece}",
204
                ) from None
205

206
    return black.FileMode(
5✔
207
        target_versions=versions,
208
        is_pyi=pyi,
209
        line_length=line_length,
210
        skip_source_first_line=skip_source_first_line,
211
        string_normalization=not skip_string_normalization,
212
        magic_trailing_comma=not skip_magic_trailing_comma,
213
        preview=preview,
214
        unstable=unstable,
215
        enabled_features=enable_features,
216
    )
217

218

219
def parse_python_variant_header(value: str) -> Tuple[bool, Set[black.TargetVersion]]:
5✔
220
    if value == "pyi":
5✔
221
        return True, set()
5✔
222
    else:
223
        versions = set()
5✔
224
        for version in value.split(","):
5✔
225
            if version.startswith("py"):
5✔
226
                version = version[len("py") :]
5✔
227
            if "." in version:
5✔
228
                major_str, *rest = version.split(".")
5✔
229
            else:
230
                major_str = version[0]
5✔
231
                rest = [version[1:]] if len(version) > 1 else []
5✔
232
            try:
5✔
233
                major = int(major_str)
5✔
234
                if major not in (2, 3):
5✔
235
                    raise InvalidVariantHeader("major version must be 2 or 3")
5✔
236
                if len(rest) > 0:
5✔
237
                    minor = int(rest[0])
5✔
238
                    if major == 2:
5✔
239
                        raise InvalidVariantHeader("Python 2 is not supported")
5✔
240
                else:
241
                    # Default to lowest supported minor version.
242
                    minor = 7 if major == 2 else 3
5✔
243
                version_str = f"PY{major}{minor}"
5✔
244
                if major == 3 and not hasattr(black.TargetVersion, version_str):
5✔
245
                    raise InvalidVariantHeader(f"3.{minor} is not supported")
5✔
246
                versions.add(black.TargetVersion[version_str])
5✔
247
            except (KeyError, ValueError):
5✔
248
                raise InvalidVariantHeader("expected e.g. '3.7', 'py3.5'") from None
5✔
249
        return False, versions
5✔
250

251

252
def patched_main() -> None:
5✔
253
    maybe_install_uvloop()
×
254
    freeze_support()
×
255
    main()
×
256

257

258
if __name__ == "__main__":
5!
259
    patched_main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc