Skip to main content

Tools/clinic/clinic.py

cpython 3.14 @ ab2d84fe1023/Tools/clinic/clinic.py

Argument Clinic is CPython's code generator for argument parsing. It reads /*[clinic input] ... [clinic start generated code]*/ blocks embedded in C source files, parses a small DSL that describes function signatures and converters, and emits the C boilerplate that would otherwise be written by hand: PyArg_ParseTuple/PyArg_ParseTupleAndKeywords calls, docstring macros, _Py_clinic_ impl declarations, and the checksum sentinel that prevents stale output from silently surviving edits.

The tool was restructured for CPython 3.12 into a libclinic/ package. clinic.py itself is only an 11-line entry point that delegates to libclinic.cli.main(). The substantive code lives across about 7,458 lines spread over 19 modules inside libclinic/.

Map

LinesSymbolRole
13-60BlockDataclass representing one verbatim or clinic block extracted from a source file
61-155BlockParserIterator that splits a C source file into verbatim and clinic block tokens
131-155BlockParser.parse_clinic_blockReads DSL text up to the [clinic start generated code] sentinel and validates checksum
63-217ClinicTop-level orchestrator: holds destinations, drives DSLParser, writes output
161-217Clinic.parseFeeds blocks into the DSL parser and collects rendered output per destination
241-267DSLParserState-machine parser for the clinic DSL inside a block
403-406at_classmethodSets function kind to CLASS_METHOD
442-445at_staticmethodSets function kind to STATIC_METHOD
906-919parse_converterResolves a converter name to a CConverter subclass via eval_ast_expr
210-252int_converterEmits i format unit; handles accept for bool/int subclasses
528-631str_converterEmits s, z, y, s* etc. depending on accept and zeroes flags
492-527object_converterEmits O or O!; optionally accepts a typecheck subtype
865-984self_converterHandles the implicit self parameter; emits no format unit
1-942parse_args.pyGenerates the full PyArg_Parse* or _PyArg_Parse* call body from a Function
73-115BlockPrinter.print_blockWrites a rendered block to the output file, including the checksum line
201-231CodeGenAccumulates #ifndef guards and #include directives for limited API mode
40-66parse_fileEntry point per file: detects limited API marker, instantiates Clinic, writes result
95-197run_clinicDrives batch mode (--make), converter listing, and single-file processing

Reading

DSL block structure

A clinic block is a C block comment with a specific shape. The opening delimiter is /*[clinic input] and the closing generated-code fence is [clinic start generated code]. Everything between those two fences is the DSL input. Everything after the fence up to [clinic end generated code: ...] is the previously generated output and a checksum:

# CPython: Tools/clinic/libclinic/block_parser.py:131 BlockParser.parse_clinic_block
def parse_clinic_block(self, dsl_name: str) -> Block:
...
# The checksum in the end marker is validated here.
# compute_checksum(input_text) must match what was written last time.

The checksum format in source looks like:

/*[clinic end generated code: output=3f2d348b8b9b1234 input=a9049054013a1b77]*/

output= is the SHA-1 prefix of the generated text; input= is the SHA-1 prefix of the DSL block itself. BlockPrinter.print_block (codegen.py:73) writes this line via libclinic.compute_checksum().

CConverter hierarchy

Every parameter type maps to a CConverter subclass. The format_unit class attribute decides what single-character code (or multi-character code for buffer types) goes into the format string. converter_init is called with the keyword arguments the user wrote in the DSL annotation. parse_arg emits the C snippet that reads from the argument list into the local variable:

# CPython: Tools/clinic/libclinic/converters.py:210 int_converter
class int_converter(CConverter):
type = 'int'
default_type = int
format_unit = 'i'
c_ignored_default = "-1"

def converter_init(self, *, accept={int}, ...):
...

# CPython: Tools/clinic/libclinic/converters.py:528 str_converter
class str_converter(CConverter):
type = 'const char *'
default_type = (str, Null, NoneType)
format_unit = 's'

def converter_init(self, *, accept={str}, zeroes=False):
...

@classmethod and @staticmethod decorators

The DSL supports @classmethod and @staticmethod annotations above the function name. DSLParser.at_classmethod (dsl_parser.py:403) sets function.kind = CLASS_METHOD; at_staticmethod (dsl_parser.py:442) sets STATIC_METHOD. Both reject conversion if the function is already a getter/setter. The self_converter class suppresses the self parameter from the format string for static methods while keeping the defining_class slot for class methods.

# CPython: Tools/clinic/libclinic/dsl_parser.py:403 DSLParser.at_classmethod
def at_classmethod(self) -> None:
if self.kind is not CALLABLE:
fail("@classmethod must be before other decorators")
self.kind = CLASS_METHOD

# CPython: Tools/clinic/libclinic/dsl_parser.py:442 DSLParser.at_staticmethod
def at_staticmethod(self) -> None:
if self.kind is not CALLABLE:
fail("@staticmethod must be before other decorators")
self.kind = STATIC_METHOD

Limited API mode output

When a source file defines Py_LIMITED_API (detected via regex in cli.py:parse_file), Clinic is instantiated with limited_capi=True. CodeGen then omits internal header includes and routes argument parsing through the public PyArg_Parse* forms instead of the internal _PyArg_Parse* forms. The #ifndef Py_LIMITED_API guards around inline helpers are also suppressed:

# CPython: Tools/clinic/libclinic/cli.py:40 parse_file
LIMITED_CAPI_REGEX = re.compile(r'# *define +Py_LIMITED_API')

def parse_file(filename: str, *, limited_capi: bool = False, ...) -> None:
with open(filename) as f:
raw = f.read()
if LIMITED_CAPI_REGEX.search(raw):
limited_capi = True
clinic = Clinic(language, ..., limited_capi=limited_capi)

gopy notes

gopy does not use Argument Clinic. The Go toolchain has no equivalent of the C macro layer that Clinic generates; function signatures and argument validation are written directly in Go. The libclinic/ package is therefore not ported and carries no counterpart in the gopy tree.

Understanding Clinic is still useful for reading any CPython C source that has been clinified: the /*[clinic input]*/ block is the authoritative description of a function's signature, and the generated code below it is derived mechanically from it.