Skip to content

Public modern typing

The modern API ships inline annotations and a fuzzyroutines/py.typed marker under PEP 561. Supported CPython versions are 3.13 and 3.14. Import modern names from fuzzyroutines or their focused modules; both paths preserve the same types and runtime objects.

MembershipScalar accepts built-in floats and numbers.Real implementations, including integers and fractions.Fraction. The union explicitly includes float because static checkers do not model every built-in number as a numbers.Real subclass. Runtime validation still rejects booleans, non-finite coordinates, and invalid membership grades. Static annotations do not encode these numerical restrictions.

MembershipCallable is structural: a function, callable object, or bound method must accept every MembershipScalar coordinate and return a MembershipScalar. A callback annotated only for float promises less than this complete domain. Use the public scalar alias for custom membership callbacks:

from fractions import Fraction

from fuzzyroutines import (
    ContinuousUniverse,
    MembershipCallable,
    MembershipScalar,
    ScalarFuzzySet,
)


def CustomMembership(coordinate: MembershipScalar) -> MembershipScalar:
    """Return a constant rational membership grade."""

    return Fraction(1, 2)


membership: MembershipCallable = CustomMembership
fuzzySet = ScalarFuzzySet(ContinuousUniverse(0, 1, True, True), membership)
grade: MembershipScalar = fuzzySet.Membership(0.5)

Scalar results remain MembershipScalar so annotations do not require coercion of rational or other real-valued arithmetic to floats. Analytical property operations require a stock MembershipFunction or a trusted analytical source; a generic callback does not carry the analytical evidence those operations need. DeriveProperties selects ContinuousFuzzyProperties or DiscreteFuzzyProperties according to the supplied universe. Sampled operations return their distinct sampled evidence types. Scale lookup returns LinguisticTerm | None, so callers must handle a missing term.

Typeshed models numbers.Real arithmetic through a complex-compatible interface whose inferred results are wider than real scalar operations. At validated internal arithmetic boundaries, a narrow cast(float, value) gives the checker a real-arithmetic view. typing.cast returns the same object at runtime; it does not call float(value) or change rational arithmetic. Public results retain the broader scalar contract, and modern diagnostics remain fully checked.

Immutable dataclass fields and read-only membership parameter mappings are visible to the checker. Operator and analysis functions require their specific policy and domain types; supplying a different policy family is a type error.

Reproducible gate

Install the pinned checker separately from runtime dependencies:

python -m pip install -r requirements-typing.txt
python -m tools.typecheck

The fixed mypy 1.19.1 version supports the declared Python versions and PEP 695 aliases; its transitive checker dependencies are pinned in the same file. Source checks disable incremental reuse, so successful results do not depend on an earlier cache. The CI wheel builder and build backend also use fixed development-only versions with build isolation disabled.

mypy.ini uses strict checking, diagnostic codes, and unused-ignore reporting. It explicitly covers the package root, every modern module (including internal numeric validation), the checker command, and all static consumers. It has no global error suppression. The historical FuzzyRoutines facade, _legacy adapters, and executable Examples module have a narrowly scoped skipped-import boundary. They retain their runtime compatibility contract and are outside the modern typing promise.

The consumer fixtures in tests/typing include valid imports, numeric inputs, custom callbacks, policy use, immutable results, and exact return-type assertions. Expected-invalid calls and assignments carry a specific diagnostic code. If a signature becomes Any or otherwise accepts an invalid use, the formerly necessary ignore becomes unused and the gate fails.

Installed wheel boundary

The Public modern typing workflow checks source contracts and builds a wheel on both supported Python versions. It installs that wheel and the pinned checker in a new virtual environment, then runs:

/path/to/isolated/environment/bin/python /path/to/checkout/tools/typecheck.py --installed

This mode verifies that Python resolves a package outside the checkout and that the installed package includes py.typed. It copies every consumer into a temporary directory outside the repository and runs mypy there with isolated Python path handling, cleared MYPYPATH and PYTHONPATH, and a fresh cache. Consumers therefore exercise the wheel annotations instead of accidentally finding source files in the checkout. Temporary probes are removed after either success or failure; failed imports and checker results preserve their nonzero exit status.