pyproject.tomlsetup.py based project?This specification defines a metadata format that can be embedded in single-filePython scripts to assist launchers, IDEs and other external tools which may needto interact with such scripts.
This specification defines a metadata comment block format (loosely inspired byreStructuredText Directives).
Any Python script may have top-level comment blocks that MUST start with theline#///TYPE whereTYPE determines how to process the content. Thatis: a single#, followed by a single space, followed by three forwardslashes, followed by a single space, followed by the type of metadata. BlockMUST end with the line#///. That is: a single#, followed by a singlespace, followed by three forward slashes. TheTYPE MUST only consist ofASCII letters, numbers and hyphens.
Every line between these two lines (#///TYPE and#///) MUST be acomment starting with#. If there are characters after the# then thefirst character MUST be a space. The embedded content is formed by taking awaythe first two characters of each line if the second character is a space,otherwise just the first character (which means the line consists of only asingle#).
Precedence for an ending line#/// is given when the next line is nota valid embedded content line as described above. For example, the followingis a single fully valid block:
# /// some-toml# embedded-csharp = """# /// <summary># /// text# ///# /// </summary># public class MyClass { }# """# ///
A starting line MUST NOT be placed between another starting line and its endingline. In such cases tools MAY produce an error. Unclosed blocks MUST be ignored.
When there are multiple comment blocks of the sameTYPE defined, tools MUSTproduce an error.
Tools reading embedded metadata MAY respect the standard Python encodingdeclaration. If they choose not to do so, they MUST process the file as UTF-8.
This is the canonical regular expression that MAY be used to parse themetadata:
(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$
In circumstances where there is a discrepancy between the text specificationand the regular expression, the text specification takes precedence.
Tools MUST NOT read from metadata blocks with types that have not beenstandardized by this specification.
The first type of metadata block is namedscript, which containsscript metadata (dependency data and tool configuration).
This document MAY include the top-level fieldsdependencies andrequires-python,and MAY optionally include a[tool] table.
The[tool] table MAY be used by any tool, script runner or otherwise, to configurebehavior. It has the same semantics as the[tool] table in pyproject.toml.
The top-level fields are:
dependencies: A list of strings that specifies the runtime dependenciesof the script. Each entry MUST be a validdependency specifier.
requires-python: A string that specifies the Python version(s) with whichthe script is compatible. The value of this field MUST be a validversion specifier.
Script runners MUST error if the specifieddependencies cannot be provided.Script runners SHOULD error if no version of Python that satisfies the specifiedrequires-python can be provided.
The following is an example of a script with embedded metadata:
# /// script# requires-python = ">=3.11"# dependencies = [# "requests<3",# "rich",# ]# ///importrequestsfromrich.prettyimportpprintresp=requests.get("https://peps.python.org/api/peps.json")data=resp.json()pprint([(k,v["title"])fork,vindata.items()][:10])
The following is an example of how to read the metadata on Python 3.11 orhigher.
importreimporttomllibREGEX=r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'defread(script:str)->dict|None:name='script'matches=list(filter(lambdam:m.group('type')==name,re.finditer(REGEX,script)))iflen(matches)>1:raiseValueError(f'Multiple{name} blocks found')eliflen(matches)==1:content=''.join(line[2:]ifline.startswith('# ')elseline[1:]forlineinmatches[0].group('content').splitlines(keepends=True))returntomllib.loads(content)else:returnNone
Often tools will edit dependencies like package managers or dependency updateautomation in CI. The following is a crude example of modifying the contentusing thetomlkitlibrary.
importreimporttomlkitREGEX=r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'defadd(script:str,dependency:str)->str:match=re.search(REGEX,script)content=''.join(line[2:]ifline.startswith('# ')elseline[1:]forlineinmatch.group('content').splitlines(keepends=True))config=tomlkit.parse(content)config['dependencies'].append(dependency)new_content=''.join(f'#{line}'ifline.strip()elsef'#{line}'forlineintomlkit.dumps(config).splitlines(keepends=True))start,end=match.span('content')returnscript[:start]+new_content+script[end:]
Note that this example used a library that preserves TOML formatting. This isnot a requirement for editing by any means but rather is a “nice to have”feature.
The following is an example of how to read a stream of arbitrary metadatablocks.
importrefromtypingimportIteratorREGEX=r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'defstream(script:str)->Iterator[tuple[str,str]]:formatchinre.finditer(REGEX,script):yieldmatch.group('type'),''.join(line[2:]ifline.startswith('# ')elseline[1:]forlineinmatch.group('content').splitlines(keepends=True))
Tools that support managing different versions of Python should attempt to usethe highest available version of Python that is compatible with the script’srequires-python metadata, if defined.
October 2023: This specification was conditionally approved throughPEP 723.
January 2024: Through amendments toPEP 723, thepyproject metadatablock type was renamed toscript, and the[run] table was dropped,making thedependencies andrequires-python keystop-level. Additionally, the specification is no longer provisional.