read-pyproject
v0.3.1
Published
Parse and normalize pyproject.toml metadata into strictly typed JavaScript objects.
Maintainers
Readme
read-pyproject
Parse and normalize pyproject.toml metadata into strictly typed JavaScript objects.
Overview
A typed pyproject.toml reader for Node.js.
Highlights:
Typed output
The returned object is deeply typed via Zod schema inference, giving you autocomplete and type safety for[project],[build-system],[dependency-groups], and 30+[tool.*]sections.Normalization
The mess ofkebab-case,snake_case, andPascalCasekeys are converted tocamelCasein the output by default, and some other fields (likelicenseandreadmevalues) are sensibly normalized.Flexibly strict
Control how unknown keys are handled with three modes:'passthrough'(default, keeps everything),'strip'(silently removes unknown keys), or'error'(throws on unknown keys).Broad tool coverage
Typed schemas for 30+ common[tool.*]sections. Unrecognized tools pass through asunknownby default.
Note that this library currently only reads, it does not write changes back to the .toml file.
Getting started
Dependencies
Node 20.17.0+
Installation
npm install read-pyprojectQuick start
import { readPyproject } from 'read-pyproject'
const pyproject = await readPyproject('/path/to/project')
console.log(pyproject.project?.name) // Normalized PEP 503 name
console.log(pyproject) // The rest of the object...Or parse a TOML string directly:
import { parsePyproject } from 'read-pyproject'
const toml = `
[project]
name = "my-package"
version = "1.0.0"
`
const pyproject = parsePyproject(toml)Usage
API
readPyproject(pathOrDirectory?, options?)
Read, parse, validate, and normalize a pyproject.toml file.
Parameters
pathOrDirectory— A file path or directory. If a directory, appends/pyproject.toml. Defaults toprocess.cwd().options— Optional configuration object:camelCase— Convert keys to camelCase (trueby default). Set tofalseto get raw TOML keys.unknownKeyPolicy— How to handle unknown keys:'passthrough'(default),'strip', or'error'.
Returns
Promise<PyprojectData>whencamelCaseistrue(default)Promise<RawPyprojectData>whencamelCaseisfalse
The return type is inferred from the camelCase option via function overloads.
Throws
PyprojectError on missing files, invalid TOML, or validation failures (in 'error' mode).
Examples
import { readPyproject } from 'read-pyproject'
// Read from current directory (camelCase keys by default)
await readPyproject()
// Read from a specific file
await readPyproject('/path/to/pyproject.toml')
// Read from a directory
await readPyproject('/path/to/project')
// Get raw TOML keys (no camelCase conversion)
const raw = await readPyproject('/path/to/project', { camelCase: false })
raw['build-system']?.['build-backend'] // Raw kebab-case keys
// Reject unknown keys
await readPyproject('/path/to/project', { unknownKeyPolicy: 'error' })
// Strip unknown keys from the output
await readPyproject('/path/to/project', { unknownKeyPolicy: 'strip' })parsePyproject(content, options?)
Parse, validate, and normalize a pyproject.toml content string. This is the synchronous counterpart to readPyproject — it accepts a TOML string instead of reading from the filesystem.
Parameters
content— Apyproject.tomlcontent string.options— Optional configuration object:camelCase— Convert keys to camelCase (trueby default). Set tofalseto get raw TOML keys.unknownKeyPolicy— How to handle unknown keys:'passthrough'(default),'strip', or'error'.
Returns
PyprojectDatawhencamelCaseistrue(default)RawPyprojectDatawhencamelCaseisfalse
The return type is inferred from the camelCase option via function overloads.
Throws
PyprojectError on invalid TOML or validation failures (in 'error' mode).
Examples
import { parsePyproject } from 'read-pyproject'
// Parse a TOML string (camelCase keys by default)
const pyproject = parsePyproject('[project]\nname = "my-package"')
// Get raw TOML keys (no camelCase conversion)
const raw = parsePyproject(tomlString, { camelCase: false })
// Reject unknown keys
parsePyproject(tomlString, { unknownKeyPolicy: 'error' })PyprojectError
Custom error class thrown for file read errors, TOML parse errors, and validation failures. Includes a filePath property for context.
setLogger(logger?)
Inject a custom logger. Accepts a LogLayer instance or a Console-like object.
Normalization
All kebab-case, snake_case, and PascalCase keys in the TOML are converted to camelCase in the output by default. Pass
{ camelCase: false }to disable this and receive raw TOML keys instead.project.nameis normalized per PEP 503 (lowercased, runs of[-_.]+collapsed to a single-). The original name is available asproject.rawName. This normalization is always applied regardless of thecamelCaseoption.project.readmeis normalized to a string when the readme is file-based ("README.md"stays as-is,{ file: "README.md", content-type: "..." }collapses to"README.md"). Inline-text readmes ({ text: "...", content-type: "..." }) are kept as a{ text, contentType? }object.project.licenseis normalized from an SPDX string ("MIT") to{ spdx }, validated and corrected via spdx-correct. Legacy table-form licenses ({ file }or{ text }) pass through as-is.
Supported [tool.*] sections
The following tools have typed schemas:
autopep8, bandit, black, bumpversion, check-wheel-contents, cibuildwheel, codespell, comfy, commitizen, coverage, dagster, distutils, docformatter, flake8, flit, hatch, isort, jupyter-releaser, mypy, pdm, pixi, poe, poetry, pydocstyle, pylint, pyright, pytest, ruff, setuptools, setuptools_scm, tbump, towncrier, uv, yapf
Unknown tools pass through as unknown by default.
Background
Motivation
I wanted something like read-pkg or pkg-types, but for pyproject.toml files.
It's a bit strange to work across language ecosystems like this, but I had occasion to do so for some other Node-based projects related to project metadata extraction, specifically metascope.
Implementation notes
The project consists of a number of Zod schemas responsible for validating and normalizing data found in a pyproject.toml. Schemas output raw TOML keys; camelCase conversion is handled centrally by a recursive deepCamelCaseKeys function that knows which paths contain user-defined record keys (like package names or file paths) and skips those. Type-level camelCase conversion uses CamelCasedPropertiesDeep from type-fest, and function overloads ensure the return type matches the camelCase option.
Forcing the rather dynamic and extensible data structure found in pyproject.toml into a TypeScript straightjacket is likely futile, but an LLM makes the project at least somewhat tractable.
Maintainers
@kitschpatrol
Slop factor
High.
An initial human-crafted specification was implemented by LLM. The output has been subject to only moderate post-facto human scrutiny.
Contributing
Issues and pull requests are welcome.
License
MIT © Eric Mika
