csv_parts module#
CsvParts#
Resolve passives from a CSV of approved parts. Name it in a chain as a path, or as a resource inside an installed package:
[tool.jitx.parts]
chain = ["csv:parts/approved-parts.csv"]
PartSource([CsvParts("parts/approved-parts.csv")])
PartSource([CsvParts.from_package("acme_parts", "approved.csv")])
The header is the vocabulary. category, mpn and manufacturer are identity, datasheet is reported but not queryable, and every other column becomes a queryable attribute under its own name:
category,mpn,manufacturer,resistance,case,tolerance,price
resistor,RC0402FR-0710KL,Yageo,10000,0402,0.01,0.0032
resistor,ERA-2AEB103X,Panasonic,10 kohm,0402,0.001,0.0900
A dimensioned column takes a bare number or one carrying its own unit. A bare number is given an engineering prefix, so it reaches the schematic as 10 kohm rather than 10000 ohm; a written unit is used as written. Adding a column is all it takes to filter or rank on it, here or in a design.
Being column-driven rather than hardcoded is also what makes this the one to read first when writing another provider: the vocabulary comes from the data, so what is left in the file is the protocol and nothing else.
- class CsvPart(identity, payload)[source]#
Bases:
objectA resolved part from a CSV row. Implements ResolvedPart.
- Parameters:
identity (PartIdentity)
- identity: PartIdentity#
- class CsvParts(path, root=None, package=None)[source]#
Bases:
CategoryProviderResolve passives from a CSV of approved parts.
category, mpn and manufacturer are consumed as identity and datasheet as payload; every other column becomes an attribute under its own name, so the file’s header is the vocabulary. Dimensioned columns are parsed into quantities.
A row is a candidate when match says so, and candidates are ordered by the query’s rank. Filtering and ordering cover every column the file has, which is the provider invariant: whatever it exposes, it serves.
- root: str | None = None#
Directory path is relative to, when it came from project config.
Deliberately absent from config(), and so from the chain digest: where a project happens to be checked out is not a design decision, and baking an absolute path into the digest would give two developers different chains for the same project and never let the lock match.
- package: str | None = None#
Installed package path is a resource of, for a catalogue shipped by a company-wide pip package. Read through importlib.resources, so it works from a wheel, an editable install or a zip import alike.
Unlike root, this one is in config(): which package a catalogue comes from is a design decision, where its files happen to land on disk is not.
- name: str#
` and the file’s basename, or the package and basename for a resource.
Two catalogues with the same basename in different directories collide, which is legal; PartSource.names() suffixes duplicates positionally.
- Type:
`csv
- classmethod from_package(package, resource)[source]#
A catalogue shipped inside an installed package.
The spelling for a company-wide approved-parts list: publish a chain from the package that carries the CSV, and name the file as a resource rather than as a path.
PartSource([CsvParts.from_package(“acme_parts”, “approved.csv”)])
The package name and resource reach the chain digest; the filesystem location does not, so every developer and every CI box computes the same chain for the same installed version.
- config()[source]#
What identifies this catalogue, and nothing that does not.
The path or resource name, and the package when there is one: changing either is a decision. root is excluded, and so is wherever a package resolves to on disk, so the digest is the same on every machine.
The package’s version is excluded too, deliberately. A chain published by a package is pinned by dependency version, not by the lockfile; asking the lock to defend against an upgrade as well would pin the same thing twice, in two files, with no way to tell which is authoritative.
- property file: Any#
a package resource, or a filesystem path.
A Traversable in the package case rather than a Path, since the file need not be on the filesystem at all.
- Type:
Where to read from
- resolve_one(query)[source]#
Answer one query. config and rehydrate are still yours to write.
- Return type:
Resolved|NoMatch|Unsupported|Deny- Parameters:
query (CanonicalQuery)
- distinct(query, key)[source]#
Values of key among matching rows, in sorted order.
- Return type:
- Parameters:
query (CanonicalQuery)
key (str)
- rehydrate(identity, payload)[source]#
Rebuild a part from a lockfile entry.
The payload carries its own version, so this provider can disown a payload it no longer understands rather than the core guessing.
- Return type:
- Parameters:
identity (PartIdentity)