main
1from __future__ import annotations
2
3import sys
4from typing import Iterator
5from pathlib import Path
6
7import rich
8import griffe
9from rich.text import Text
10from rich.style import Style
11
12
13def public_members(obj: griffe.Object | griffe.Alias) -> dict[str, griffe.Object | griffe.Alias]:
14 if isinstance(obj, griffe.Alias):
15 # ignore imports for now, they're technically part of the public API
16 # but we don't have good preventative measures in place to prevent
17 # changing them
18 return {}
19
20 return {name: value for name, value in obj.all_members.items() if not name.startswith("_")}
21
22
23def find_breaking_changes(
24 new_obj: griffe.Object | griffe.Alias,
25 old_obj: griffe.Object | griffe.Alias,
26 *,
27 path: list[str],
28) -> Iterator[Text | str]:
29 new_members = public_members(new_obj)
30 old_members = public_members(old_obj)
31
32 for name, old_member in old_members.items():
33 if isinstance(old_member, griffe.Alias) and len(path) > 2:
34 # ignore imports in `/types/` for now, they're technically part of the public API
35 # but we don't have good preventative measures in place to prevent changing them
36 continue
37
38 new_member = new_members.get(name)
39 if new_member is None:
40 cls_name = old_member.__class__.__name__
41 yield Text(f"({cls_name})", style=Style(color="rgb(119, 119, 119)"))
42 yield from [" " for _ in range(10 - len(cls_name))]
43 yield f" {'.'.join(path)}.{name}"
44 yield "\n"
45 continue
46
47 yield from find_breaking_changes(new_member, old_member, path=[*path, name])
48
49
50def main() -> None:
51 try:
52 against_ref = sys.argv[1]
53 except IndexError as err:
54 raise RuntimeError("You must specify a base ref to run breaking change detection against") from err
55
56 package = griffe.load(
57 "openai",
58 search_paths=[Path(__file__).parent.parent.joinpath("src")],
59 )
60 old_package = griffe.load_git(
61 "openai",
62 ref=against_ref,
63 search_paths=["src"],
64 )
65 assert isinstance(package, griffe.Module)
66 assert isinstance(old_package, griffe.Module)
67
68 output = list(find_breaking_changes(package, old_package, path=["openai"]))
69 if output:
70 rich.print(Text("Breaking changes detected!", style=Style(color="rgb(165, 79, 87)")))
71 rich.print()
72
73 for text in output:
74 rich.print(text, end="")
75
76 sys.exit(1)
77
78
79main()