Commit e1b60b22
Changed files (8)
src
openai
tests
bin/blacken-docs.py → bin/ruffen-docs.py
@@ -1,16 +1,14 @@
-# fork of https://github.com/asottile/blacken-docs implementing https://github.com/asottile/blacken-docs/issues/170
+# fork of https://github.com/asottile/blacken-docs adapted for ruff
from __future__ import annotations
import re
+import sys
import argparse
import textwrap
import contextlib
+import subprocess
from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
-import black
-from black.mode import TargetVersion
-from black.const import DEFAULT_LINE_LENGTH
-
MD_RE = re.compile(
r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)",
re.DOTALL | re.MULTILINE,
@@ -19,55 +17,12 @@ MD_PYCON_RE = re.compile(
r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)",
re.DOTALL | re.MULTILINE,
)
-RST_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy"))
-BLOCK_TYPES = "(code|code-block|sourcecode|ipython)"
-DOCTEST_TYPES = "(testsetup|testcleanup|testcode)"
-RST_RE = re.compile(
- rf"(?P<before>"
- rf"^(?P<indent> *)\.\. ("
- rf"jupyter-execute::|"
- rf"{BLOCK_TYPES}:: (?P<lang>\w+)|"
- rf"{DOCTEST_TYPES}::.*"
- rf")\n"
- rf"((?P=indent) +:.*\n)*"
- rf"\n*"
- rf")"
- rf"(?P<code>(^((?P=indent) +.*)?\n)+)",
- re.MULTILINE,
-)
-RST_PYCON_RE = re.compile(
- r"(?P<before>"
- r"(?P<indent> *)\.\. ((code|code-block):: pycon|doctest::.*)\n"
- r"((?P=indent) +:.*\n)*"
- r"\n*"
- r")"
- r"(?P<code>(^((?P=indent) +.*)?(\n|$))+)",
- re.MULTILINE,
-)
PYCON_PREFIX = ">>> "
PYCON_CONTINUATION_PREFIX = "..."
PYCON_CONTINUATION_RE = re.compile(
rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
)
-LATEX_RE = re.compile(
- r"(?P<before>^(?P<indent> *)\\begin{minted}{python}\n)"
- r"(?P<code>.*?)"
- r"(?P<after>^(?P=indent)\\end{minted}\s*$)",
- re.DOTALL | re.MULTILINE,
-)
-LATEX_PYCON_RE = re.compile(
- r"(?P<before>^(?P<indent> *)\\begin{minted}{pycon}\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)\\end{minted}\s*$)",
- re.DOTALL | re.MULTILINE,
-)
-PYTHONTEX_LANG = r"(?P<lang>pyblock|pycode|pyconsole|pyverbatim)"
-PYTHONTEX_RE = re.compile(
- rf"(?P<before>^(?P<indent> *)\\begin{{{PYTHONTEX_LANG}}}\n)"
- rf"(?P<code>.*?)"
- rf"(?P<after>^(?P=indent)\\end{{(?P=lang)}}\s*$)",
- re.DOTALL | re.MULTILINE,
-)
-INDENT_RE = re.compile("^ +(?=[^ ])", re.MULTILINE)
-TRAILING_NL_RE = re.compile(r"\n+\Z", re.MULTILINE)
+DEFAULT_LINE_LENGTH = 100
class CodeBlockError(NamedTuple):
@@ -77,7 +32,6 @@ class CodeBlockError(NamedTuple):
def format_str(
src: str,
- black_mode: black.FileMode,
) -> tuple[str, Sequence[CodeBlockError]]:
errors: list[CodeBlockError] = []
@@ -91,24 +45,10 @@ def format_str(
def _md_match(match: Match[str]) -> str:
code = textwrap.dedent(match["code"])
with _collect_error(match):
- code = black.format_str(code, mode=black_mode)
+ code = format_code_block(code)
code = textwrap.indent(code, match["indent"])
return f'{match["before"]}{code}{match["after"]}'
- def _rst_match(match: Match[str]) -> str:
- lang = match["lang"]
- if lang is not None and lang not in RST_PY_LANGS:
- return match[0]
- min_indent = min(INDENT_RE.findall(match["code"]))
- trailing_ws_match = TRAILING_NL_RE.search(match["code"])
- assert trailing_ws_match
- trailing_ws = trailing_ws_match.group()
- code = textwrap.dedent(match["code"])
- with _collect_error(match):
- code = black.format_str(code, mode=black_mode)
- code = textwrap.indent(code, min_indent)
- return f'{match["before"]}{code.rstrip()}{trailing_ws}'
-
def _pycon_match(match: Match[str]) -> str:
code = ""
fragment = cast(Optional[str], None)
@@ -119,7 +59,7 @@ def format_str(
if fragment is not None:
with _collect_error(match):
- fragment = black.format_str(fragment, mode=black_mode)
+ fragment = format_code_block(fragment)
fragment_lines = fragment.splitlines()
code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
for line in fragment_lines[1:]:
@@ -159,42 +99,33 @@ def format_str(
code = textwrap.indent(code, match["indent"])
return f'{match["before"]}{code}{match["after"]}'
- def _rst_pycon_match(match: Match[str]) -> str:
- code = _pycon_match(match)
- min_indent = min(INDENT_RE.findall(match["code"]))
- code = textwrap.indent(code, min_indent)
- return f'{match["before"]}{code}'
-
- def _latex_match(match: Match[str]) -> str:
- code = textwrap.dedent(match["code"])
- with _collect_error(match):
- code = black.format_str(code, mode=black_mode)
- code = textwrap.indent(code, match["indent"])
- return f'{match["before"]}{code}{match["after"]}'
-
- def _latex_pycon_match(match: Match[str]) -> str:
- code = _pycon_match(match)
- code = textwrap.indent(code, match["indent"])
- return f'{match["before"]}{code}{match["after"]}'
-
src = MD_RE.sub(_md_match, src)
src = MD_PYCON_RE.sub(_md_pycon_match, src)
- src = RST_RE.sub(_rst_match, src)
- src = RST_PYCON_RE.sub(_rst_pycon_match, src)
- src = LATEX_RE.sub(_latex_match, src)
- src = LATEX_PYCON_RE.sub(_latex_pycon_match, src)
- src = PYTHONTEX_RE.sub(_latex_match, src)
return src, errors
+def format_code_block(code: str) -> str:
+ return subprocess.check_output(
+ [
+ sys.executable,
+ "-m",
+ "ruff",
+ "format",
+ "--stdin-filename=script.py",
+ f"--line-length={DEFAULT_LINE_LENGTH}",
+ ],
+ encoding="utf-8",
+ input=code,
+ )
+
+
def format_file(
filename: str,
- black_mode: black.FileMode,
skip_errors: bool,
) -> int:
with open(filename, encoding="UTF-8") as f:
contents = f.read()
- new_contents, errors = format_str(contents, black_mode)
+ new_contents, errors = format_str(contents)
for error in errors:
lineno = contents[: error.offset].count("\n") + 1
print(f"{filename}:{lineno}: code block parse error {error.exc}")
@@ -217,15 +148,6 @@ def main(argv: Sequence[str] | None = None) -> int:
type=int,
default=DEFAULT_LINE_LENGTH,
)
- parser.add_argument(
- "-t",
- "--target-version",
- action="append",
- type=lambda v: TargetVersion[v.upper()],
- default=[],
- help=f"choices: {[v.name.lower() for v in TargetVersion]}",
- dest="target_versions",
- )
parser.add_argument(
"-S",
"--skip-string-normalization",
@@ -235,15 +157,9 @@ def main(argv: Sequence[str] | None = None) -> int:
parser.add_argument("filenames", nargs="*")
args = parser.parse_args(argv)
- black_mode = black.FileMode(
- target_versions=set(args.target_versions),
- line_length=args.line_length,
- string_normalization=not args.skip_string_normalization,
- )
-
retv = 0
for filename in args.filenames:
- retv |= format_file(filename, black_mode, skip_errors=args.skip_errors)
+ retv |= format_file(filename, skip_errors=args.skip_errors)
return retv
src/openai/_utils/_transform.py
@@ -80,9 +80,10 @@ def transform(
```py
class Params(TypedDict, total=False):
- card_id: Required[Annotated[str, PropertyInfo(alias='cardID')]]
+ card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
- transformed = transform({'card_id': '<my card ID>'}, Params)
+
+ transformed = transform({"card_id": "<my card ID>"}, Params)
# {'cardID': '<my card ID>'}
```
src/openai/_utils/_utils.py
@@ -211,13 +211,15 @@ def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
def foo(*, a: str) -> str:
...
+
@overload
def foo(*, b: bool) -> str:
...
+
# This enforces the same constraints that a static type checker would
# i.e. that either a or b must be passed to the function
- @required_args(['a'], ['b'])
+ @required_args(["a"], ["b"])
def foo(*, a: str | None = None, b: bool | None = None) -> str:
...
```
src/openai/_models.py
@@ -382,7 +382,7 @@ elif not TYPE_CHECKING: # TODO: condition is weird
For example:
```py
- validated = RootModel[int](__root__='5').__root__
+ validated = RootModel[int](__root__="5").__root__
# validated: 5
```
"""
src/openai/_types.py
@@ -278,11 +278,13 @@ class NotGiven:
For example:
```py
- def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...
+ def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response:
+ ...
+
- get(timeout=1) # 1s timeout
- get(timeout=None) # No timeout
- get() # Default timeout behavior, which may not be statically known at the method definition.
+ get(timeout=1) # 1s timeout
+ get(timeout=None) # No timeout
+ get() # Default timeout behavior, which may not be statically known at the method definition.
```
"""
@@ -304,14 +306,14 @@ class Omit:
```py
# as the default `Content-Type` header is `application/json` that will be sent
- client.post('/upload/files', files={'file': b'my raw file content'})
+ client.post("/upload/files", files={"file": b"my raw file content"})
# you can't explicitly override the header as it has to be dynamically generated
# to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
- client.post(..., headers={'Content-Type': 'multipart/form-data'})
+ client.post(..., headers={"Content-Type": "multipart/form-data"})
# instead you can remove the default `application/json` header by passing Omit
- client.post(..., headers={'Content-Type': Omit()})
+ client.post(..., headers={"Content-Type": Omit()})
```
"""
tests/test_transform.py
@@ -189,7 +189,9 @@ class DateDictWithRequiredAlias(TypedDict, total=False):
def test_datetime_with_alias() -> None:
assert transform({"required_prop": None}, DateDictWithRequiredAlias) == {"prop": None} # type: ignore[comparison-overlap]
- assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap]
+ assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == {
+ "prop": "2023-02-23"
+ } # type: ignore[comparison-overlap]
class MyModel(BaseModel):
pyproject.toml
@@ -51,7 +51,6 @@ managed = true
dev-dependencies = [
"pyright",
"mypy",
- "black",
"respx",
"pytest",
"pytest-asyncio",
@@ -67,17 +66,18 @@ dev-dependencies = [
[tool.rye.scripts]
format = { chain = [
- "format:black",
- "format:docs",
"format:ruff",
+ "format:docs",
+ "fix:ruff",
"format:isort",
]}
"format:black" = "black ."
-"format:docs" = "python bin/blacken-docs.py README.md api.md"
-"format:ruff" = "ruff --fix ."
+"format:docs" = "python bin/ruffen-docs.py README.md api.md"
+"format:ruff" = "ruff format"
"format:isort" = "isort ."
"check:ruff" = "ruff ."
+"fix:ruff" = "ruff --fix ."
typecheck = { chain = [
"typecheck:pyright",
@@ -163,6 +163,8 @@ unfixable = [
]
ignore-init-module-imports = true
+[tool.ruff.format]
+docstring-code-format = true
[tool.ruff.per-file-ignores]
"bin/**.py" = ["T201", "T203"]
requirements-dev.lock
@@ -13,11 +13,9 @@ argcomplete==3.1.2
attrs==23.1.0
azure-core==1.29.6
azure-identity==1.15.0
-black==23.3.0
certifi==2023.7.22
cffi==1.16.0
charset-normalizer==3.3.2
-click==8.1.7
colorlog==6.7.0
cryptography==41.0.7
dirty-equals==0.6.0
@@ -42,7 +40,6 @@ numpy==1.26.2
packaging==23.2
pandas==2.1.4
pandas-stubs==2.1.4.231218
-pathspec==0.11.2
platformdirs==3.11.0
pluggy==1.3.0
portalocker==2.8.2
@@ -58,7 +55,7 @@ python-dateutil==2.8.2
pytz==2023.3.post1
requests==2.31.0
respx==0.20.2
-ruff==0.1.7
+ruff==0.1.9
six==1.16.0
sniffio==1.3.0
time-machine==2.9.0