Skip to content

Commit b1236a4

Browse files
authored
gh-113317, AC: Add libclinic.function (#116807)
Move Module, Class, Function and Parameter classes to a new libclinic.function module. Move VersionTuple and Sentinels to libclinic.utils.
1 parent a76288a commit b1236a4

File tree

4 files changed

+270
-241
lines changed

4 files changed

+270
-241
lines changed

Tools/clinic/clinic.py

Lines changed: 8 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import builtins as bltns
1313
import collections
1414
import contextlib
15-
import copy
1615
import dataclasses as dc
1716
import enum
1817
import functools
@@ -50,7 +49,14 @@
5049
# Local imports.
5150
import libclinic
5251
import libclinic.cpp
53-
from libclinic import ClinicError, fail, warn
52+
from libclinic import (
53+
ClinicError, Sentinels, VersionTuple,
54+
fail, warn, unspecified, unknown)
55+
from libclinic.function import (
56+
Module, Class, Function, Parameter,
57+
ClassDict, ModuleDict, FunctionKind,
58+
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
59+
GETTER, SETTER)
5460

5561

5662
# TODO:
@@ -70,18 +76,6 @@
7076
LIMITED_CAPI_REGEX = re.compile(r'# *define +Py_LIMITED_API')
7177

7278

73-
class Sentinels(enum.Enum):
74-
unspecified = "unspecified"
75-
unknown = "unknown"
76-
77-
def __repr__(self) -> str:
78-
return f"<{self.value.capitalize()}>"
79-
80-
81-
unspecified: Final = Sentinels.unspecified
82-
unknown: Final = Sentinels.unknown
83-
84-
8579
# This one needs to be a distinct class, unlike the other two
8680
class Null:
8781
def __repr__(self) -> str:
@@ -2096,9 +2090,7 @@ def dump(self) -> str:
20962090
extensions['py'] = PythonLanguage
20972091

20982092

2099-
ClassDict = dict[str, "Class"]
21002093
DestinationDict = dict[str, Destination]
2101-
ModuleDict = dict[str, "Module"]
21022094

21032095

21042096
class Parser(Protocol):
@@ -2418,38 +2410,6 @@ def parse(self, block: Block) -> None:
24182410
block.output = s.getvalue()
24192411

24202412

2421-
@dc.dataclass(repr=False)
2422-
class Module:
2423-
name: str
2424-
module: Module | Clinic
2425-
2426-
def __post_init__(self) -> None:
2427-
self.parent = self.module
2428-
self.modules: ModuleDict = {}
2429-
self.classes: ClassDict = {}
2430-
self.functions: list[Function] = []
2431-
2432-
def __repr__(self) -> str:
2433-
return "<clinic.Module " + repr(self.name) + " at " + str(id(self)) + ">"
2434-
2435-
2436-
@dc.dataclass(repr=False)
2437-
class Class:
2438-
name: str
2439-
module: Module | Clinic
2440-
cls: Class | None
2441-
typedef: str
2442-
type_object: str
2443-
2444-
def __post_init__(self) -> None:
2445-
self.parent = self.cls or self.module
2446-
self.classes: ClassDict = {}
2447-
self.functions: list[Function] = []
2448-
2449-
def __repr__(self) -> str:
2450-
return "<clinic.Class " + repr(self.name) + " at " + str(id(self)) + ">"
2451-
2452-
24532413
unsupported_special_methods: set[str] = set("""
24542414
24552415
__abs__
@@ -2522,201 +2482,9 @@ def __repr__(self) -> str:
25222482
""".strip().split())
25232483

25242484

2525-
class FunctionKind(enum.Enum):
2526-
INVALID = enum.auto()
2527-
CALLABLE = enum.auto()
2528-
STATIC_METHOD = enum.auto()
2529-
CLASS_METHOD = enum.auto()
2530-
METHOD_INIT = enum.auto()
2531-
METHOD_NEW = enum.auto()
2532-
GETTER = enum.auto()
2533-
SETTER = enum.auto()
2534-
2535-
@functools.cached_property
2536-
def new_or_init(self) -> bool:
2537-
return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW}
2538-
2539-
def __repr__(self) -> str:
2540-
return f"<clinic.FunctionKind.{self.name}>"
2541-
2542-
2543-
INVALID: Final = FunctionKind.INVALID
2544-
CALLABLE: Final = FunctionKind.CALLABLE
2545-
STATIC_METHOD: Final = FunctionKind.STATIC_METHOD
2546-
CLASS_METHOD: Final = FunctionKind.CLASS_METHOD
2547-
METHOD_INIT: Final = FunctionKind.METHOD_INIT
2548-
METHOD_NEW: Final = FunctionKind.METHOD_NEW
2549-
GETTER: Final = FunctionKind.GETTER
2550-
SETTER: Final = FunctionKind.SETTER
2551-
2552-
ParamDict = dict[str, "Parameter"]
25532485
ReturnConverterType = Callable[..., "CReturnConverter"]
25542486

25552487

2556-
@dc.dataclass(repr=False)
2557-
class Function:
2558-
"""
2559-
Mutable duck type for inspect.Function.
2560-
2561-
docstring - a str containing
2562-
* embedded line breaks
2563-
* text outdented to the left margin
2564-
* no trailing whitespace.
2565-
It will always be true that
2566-
(not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring))
2567-
"""
2568-
parameters: ParamDict = dc.field(default_factory=dict)
2569-
_: dc.KW_ONLY
2570-
name: str
2571-
module: Module | Clinic
2572-
cls: Class | None
2573-
c_basename: str
2574-
full_name: str
2575-
return_converter: CReturnConverter
2576-
kind: FunctionKind
2577-
coexist: bool
2578-
return_annotation: object = inspect.Signature.empty
2579-
docstring: str = ''
2580-
# docstring_only means "don't generate a machine-readable
2581-
# signature, just a normal docstring". it's True for
2582-
# functions with optional groups because we can't represent
2583-
# those accurately with inspect.Signature in 3.4.
2584-
docstring_only: bool = False
2585-
critical_section: bool = False
2586-
target_critical_section: list[str] = dc.field(default_factory=list)
2587-
2588-
def __post_init__(self) -> None:
2589-
self.parent = self.cls or self.module
2590-
self.self_converter: self_converter | None = None
2591-
self.__render_parameters__: list[Parameter] | None = None
2592-
2593-
@functools.cached_property
2594-
def displayname(self) -> str:
2595-
"""Pretty-printable name."""
2596-
if self.kind.new_or_init:
2597-
assert isinstance(self.cls, Class)
2598-
return self.cls.name
2599-
else:
2600-
return self.name
2601-
2602-
@functools.cached_property
2603-
def fulldisplayname(self) -> str:
2604-
parent: Class | Module | Clinic | None
2605-
if self.kind.new_or_init:
2606-
parent = getattr(self.cls, "parent", None)
2607-
else:
2608-
parent = self.parent
2609-
name = self.displayname
2610-
while isinstance(parent, (Module, Class)):
2611-
name = f"{parent.name}.{name}"
2612-
parent = parent.parent
2613-
return name
2614-
2615-
@property
2616-
def render_parameters(self) -> list[Parameter]:
2617-
if not self.__render_parameters__:
2618-
l: list[Parameter] = []
2619-
self.__render_parameters__ = l
2620-
for p in self.parameters.values():
2621-
p = p.copy()
2622-
p.converter.pre_render()
2623-
l.append(p)
2624-
return self.__render_parameters__
2625-
2626-
@property
2627-
def methoddef_flags(self) -> str | None:
2628-
if self.kind.new_or_init:
2629-
return None
2630-
flags = []
2631-
match self.kind:
2632-
case FunctionKind.CLASS_METHOD:
2633-
flags.append('METH_CLASS')
2634-
case FunctionKind.STATIC_METHOD:
2635-
flags.append('METH_STATIC')
2636-
case _ as kind:
2637-
acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER}
2638-
assert kind in acceptable_kinds, f"unknown kind: {kind!r}"
2639-
if self.coexist:
2640-
flags.append('METH_COEXIST')
2641-
return '|'.join(flags)
2642-
2643-
def __repr__(self) -> str:
2644-
return f'<clinic.Function {self.name!r}>'
2645-
2646-
def copy(self, **overrides: Any) -> Function:
2647-
f = dc.replace(self, **overrides)
2648-
f.parameters = {
2649-
name: value.copy(function=f)
2650-
for name, value in f.parameters.items()
2651-
}
2652-
return f
2653-
2654-
2655-
VersionTuple = tuple[int, int]
2656-
2657-
2658-
@dc.dataclass(repr=False, slots=True)
2659-
class Parameter:
2660-
"""
2661-
Mutable duck type of inspect.Parameter.
2662-
"""
2663-
name: str
2664-
kind: inspect._ParameterKind
2665-
_: dc.KW_ONLY
2666-
default: object = inspect.Parameter.empty
2667-
function: Function
2668-
converter: CConverter
2669-
annotation: object = inspect.Parameter.empty
2670-
docstring: str = ''
2671-
group: int = 0
2672-
# (`None` signifies that there is no deprecation)
2673-
deprecated_positional: VersionTuple | None = None
2674-
deprecated_keyword: VersionTuple | None = None
2675-
right_bracket_count: int = dc.field(init=False, default=0)
2676-
2677-
def __repr__(self) -> str:
2678-
return f'<clinic.Parameter {self.name!r}>'
2679-
2680-
def is_keyword_only(self) -> bool:
2681-
return self.kind == inspect.Parameter.KEYWORD_ONLY
2682-
2683-
def is_positional_only(self) -> bool:
2684-
return self.kind == inspect.Parameter.POSITIONAL_ONLY
2685-
2686-
def is_vararg(self) -> bool:
2687-
return self.kind == inspect.Parameter.VAR_POSITIONAL
2688-
2689-
def is_optional(self) -> bool:
2690-
return not self.is_vararg() and (self.default is not unspecified)
2691-
2692-
def copy(
2693-
self,
2694-
/,
2695-
*,
2696-
converter: CConverter | None = None,
2697-
function: Function | None = None,
2698-
**overrides: Any
2699-
) -> Parameter:
2700-
function = function or self.function
2701-
if not converter:
2702-
converter = copy.copy(self.converter)
2703-
converter.function = function
2704-
return dc.replace(self, **overrides, function=function, converter=converter)
2705-
2706-
def get_displayname(self, i: int) -> str:
2707-
if i == 0:
2708-
return 'argument'
2709-
if not self.is_positional_only():
2710-
return f'argument {self.name!r}'
2711-
else:
2712-
return f'argument {i}'
2713-
2714-
def render_docstring(self) -> str:
2715-
lines = [f" {self.name}"]
2716-
lines.extend(f" {line}" for line in self.docstring.split("\n"))
2717-
return "\n".join(lines).rstrip()
2718-
2719-
27202488
CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"])
27212489

27222490
def add_c_converter(

Tools/clinic/libclinic/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
compute_checksum,
2929
create_regex,
3030
write_file,
31+
VersionTuple,
32+
Sentinels,
33+
unspecified,
34+
unknown,
3135
)
3236

3337

@@ -60,6 +64,10 @@
6064
"compute_checksum",
6165
"create_regex",
6266
"write_file",
67+
"VersionTuple",
68+
"Sentinels",
69+
"unspecified",
70+
"unknown",
6371
]
6472

6573

0 commit comments

Comments
 (0)