query module#
Queries and predicates#
A query says what a part has to do. ResistorQuery,
CapacitorQuery and InductorQuery carry the typed fields
for their category; the category components take the same keywords, so a query
object is only needed to share one.
Field values are predicates – Exactly, OneOf,
Range with its AtLeast() / AtMost() shorthands,
Tolerance, Exists – and bare values and tuples coerce, so
the simple cases stay simple:
Resistor(resistance=10 * kohm, case=("0402", "0603"))
Resistor(resistance=Range(9 * kohm, 11 * kohm), tolerance=AtMost(0.01))
rank= orders the matches rather than filtering them, applied
lexicographically, and a bare key name means Minimize(). A keyword the
category does not name lands in extras and reaches providers as written, so a
provider-specific attribute needs no ceremony – in the constructor as much as in
PartQuery.update(), which returns a modified copy.
A query is an immutable, hashable jitx.context.Context, so one assigned
in a Design or Circuit class body supplies the defaults for everything
constructed below it:
class Board(Design):
shared = ResistorQuery(case="0402", aec_q200=True)
That is the spelling the untyped keys mostly serve: a shared query carries house policy, while the value of a part is per instance.
Dimensioned values are PlainQuantity everywhere and keep
their units all the way to the provider. Only PartQuery.digest() reduces
them, so that 10 * kohm and 10000 * ohm are one query and share one
lockfile entry.
match(), satisfies() and rank_by() are where
predicate-against-value semantics are defined. A provider uses them rather than
reimplementing them, which is why they are re-exported from
jitxlib.parts.provider.
- is_quantity(value)[source]#
Narrowing helper. PlainQuantity is generic, so a bare isinstance does not narrow for a type checker.
- class Predicate[source]#
Bases:
objectBase for query value constraints. Use one of the subclasses below, or pass a bare value or tuple and let
coerce()pick one.
- class Range(min=None, max=None)[source]#
Bases:
PredicateClosed interval; either bound may be None for open-ended.
Bounds are quantities for dimensioned attributes, plain numbers for dimensionless ones, and must not be mixed within one Range.
- Parameters:
- class Tolerance(typ, plus, minus)[source]#
Bases:
PredicateTypical value with asymmetric bounds, mirroring
jitx.Toleranced.plus and minus are increments from typ to the maximum and minimum, not relative fractions, and None means unbounded on that side. Unlike Toleranced, all three may be quantities.
>>> Tolerance(10 * kohm, 500 * ohm, 500 * ohm) # 10k +/- 500R >>> Tolerance.percent(10 * kohm, 5) # 10k +/- 5% >>> Tolerance(10 * kohm, None, 500 * ohm) # unbounded above
- Parameters:
- class Exists(present=True)[source]#
Bases:
PredicateMatches on presence (or, with present=False, absence) of the key.
- Parameters:
present (bool)
- coerce(value)[source]#
Turn a bare value into a predicate.
Scalars become Exactly, tuples and lists become OneOf, and a predicate passes through unchanged.
- class Rank(key, direction='min')[source]#
Bases:
objectOrder results by key. Not a filter; never excludes a part.
- Parameters:
key (str)
direction ('min' | 'max')
- direction: 'min' | 'max' = 'min'#
- type Filter = Predicate | float | int | str | bool | PlainQuantity | tuple[float | int | str | bool | PlainQuantity, ...] | list[float | int | str | bool | PlainQuantity]#
- satisfies(predicate, value, key='')[source]#
Whether one value satisfies one predicate.
predicate of None is no constraint and always matches. Presence of the key is the caller’s business, except for Exists, which is about exactly that; see
match().
- satisfies_or_true(predicate, value, key='')[source]#
satisfies, but a None predicate is no constraint.
What a generator provider wants: it synthesized a value and needs to know whether the query would have accepted it, with unconstrained keys passing.
- match(filters, attributes, identity=None)[source]#
Whether a candidate satisfies every filter.
Identity keys (mpn, manufacturer, category) are read from identity rather than from attributes, since they are fields of PartIdentity; a filter on one of them with identity=None does not match.
A missing key never matches, except Exists(False). A dimensionality mismatch raises
DimensionMismatchrather than returning False, because it is a bug and not a non-match.
- rank_by(items, ranks, key)[source]#
Order items by ranks, applied lexicographically.
key reaches the attribute mapping for an item, since callers rank things that are not themselves attribute maps. Items missing a ranked key sort last, whichever direction is asked for: absent is not better than known.
- round_significant(value, digits=12)[source]#
Round to digits significant digits, to shed float representation noise.
Two callers, same purpose. The digest needs it so that two spellings of one value hash alike: 100 * nF and 1e-7 * F have base-unit magnitudes that differ in the last bits. A provider rescaling a value for display needs it because the rescale has the same problem in reverse: 1e-7 F converts to 99.99999999999999 nanofarad, which is not what anyone wants on a schematic.
- class CanonicalQuery(category, value_key, filters, rank, limit, digest, count=1)[source]#
Bases:
objectThe flat, category-tagged form that crosses the provider boundary.
Quantities keep their units here; only digest reduces them. lock_scope is deliberately absent, since it has no bearing on which parts match.
- Parameters:
- count: int = 1#
How many components in this batch asked this exact question.
Demand, not a constraint: it says nothing about which parts match, takes no part in the digest, and a provider that ignores it is correct. It is here for the provider that cannot do its job without it – minimum order quantities, bulk rebates, consolidating a reel – which are properties of the order rather than of one query, and are not expressible from a batch that has had its duplicates collapsed.
Two caveats, both structural. Batching is best effort, so an early flush splits one order into several batches and no batch can be assumed to be the whole design. And a query the lock answers never reaches a provider at all, so on a run with a populated lock the batch is the unpinned remainder, not the order – except in check’s clean-run call, which carries every query the design asked precisely so the comparison is against a whole-order resolution. An optimization whose answer depends on the whole order is therefore in tension with pinning by nature: adding one component could legitimately move every selection, which is what a lock exists to prevent. A provider that makes that trade owns it.
- class PartQuery(**kwargs)[source]#
Bases:
ContextBase query. Subclasses add typed fields for their category.
Usable as a design-context default, but never resolvable on its own: it has no value_key, so no category component will accept it.
The constructor takes what update takes: a keyword the category does not name lands in extras rather than raising.
- mpn: Final[Filter | None] = None#
Manufacturer part number. Filtering on it asks for one specific part.
- extras: Final[tuple[tuple[str, Filter], ...]] = ()#
Filters on attributes the core does not name, such as lcsc or aec_q200.
Pass them as loose keywords or as a mapping; this pairs form is what they are stored as.
- rank: Final[RankSpec] = ()#
How to order the matches, applied lexicographically.
A bare key minimizes, so rank=”price” means Minimize(“price”).
- classmethod from_query(query)[source]#
Build a category query from a more general one.
Nothing is dropped: base fields carry over, extras carries over, and a field that is not typed on the target lands in extras rather than being discarded. An unserved key then surfaces at resolution time with the aggregated diagnostic, which is the same treatment a typo gets.
- classmethod resolve_context(query=None, **kwargs)[source]#
The Resistor(query=None, **kwargs) path.
Takes the explicit query if given, else the active one from the design context, else an empty one; then applies kwargs.
- update(**kwargs)[source]#
Return a copy with kwargs applied.
Unknown keywords land in extras; every other field is replaced, never merged, rank included.
- canonical(limit=1)[source]#
Flatten to the form a provider sees. Units are kept.
- Return type:
- Parameters:
limit (int)
- class PassiveQuery(**kwargs)[source]#
Bases:
PartQueryShared by the two-terminal passive categories.
- class ResistorQuery(**kwargs)[source]#
Bases:
PassiveQueryQuery for a resistor.
- class CapacitorQuery(**kwargs)[source]#
Bases:
PassiveQueryQuery for a capacitor.
- class InductorQuery(**kwargs)[source]#
Bases:
PassiveQueryQuery for an inductor.