plesty.sdk.commands.mock_test ============================= .. py:module:: plesty.sdk.commands.mock_test .. autoapi-nested-parse:: Generate a module's mock pipeline test from what its device declares. ``plesty init field-test`` asks six to eight questions because every one of them is a safety judgement about a real instrument that no schema inspection can make. This command asks nothing. Nothing here can move a stage or fire an exposure, and everything it needs is either declared by the device or settled by trying it: - the device's ``doc_model()`` — the parameters and operations, read through the same reader the field test uses, so the two cannot disagree - the package name — the module, and the test file name - a construction probe — how to reach the device without one The probe is the design. A device module is hardware-free in one of several ways: ``address="mock"`` selects an in-memory simulator in one reference module, ``use_mock_solver=True`` does it in the other. Which one applies is knowable by constructing the device and looking, so the strategies are tried in order and each is verified *the way the gates will verify it* — connect, read ``is_operatable``, call ``identity()`` and ``check_errors()``. A strategy is written into the file only once it has passed the checks the file will make, which is why a generated test that is written passes. Where no strategy works the command refuses and says what it tried. It does not fall back to asking: a developer given a question the probe could not answer would be guessing at the same thing, and a file built on a guess is the failure this approach exists to avoid — it looks complete and tests less than it claims. Attributes ---------- .. autoapisummary:: plesty.sdk.commands.mock_test._STRATEGIES plesty.sdk.commands.mock_test._DOC_DEVICE_NOTE Classes ------- .. autoapisummary:: plesty.sdk.commands.mock_test.MockStrategy plesty.sdk.commands.mock_test.MockTestScaffold Functions --------- .. autoapisummary:: plesty.sdk.commands.mock_test._never_ask plesty.sdk.commands.mock_test._accepted_kwargs plesty.sdk.commands.mock_test._verify plesty.sdk.commands.mock_test._needs_unique plesty.sdk.commands.mock_test._import_block plesty.sdk.commands.mock_test._device_block plesty.sdk.commands.mock_test._pipeline_args plesty.sdk.commands.mock_test._asset plesty.sdk.commands.mock_test.scaffold_mock_test Module Contents --------------- .. py:data:: _STRATEGIES :type: tuple[tuple[str, dict[str, Any], str], Ellipsis] .. py:data:: _DOC_DEVICE_NOTE :value: 'The module exports ``DocDevice``, the pre-addressed hardware-free factory gate d3 requires, and... .. py:class:: MockStrategy How the generated test reaches the device without hardware. :ivar name: Short label, quoted when the command reports what it found. :ivar factory: The exported name the file constructs — ``Device`` or ``DocDevice``. :ivar kwargs: Keyword arguments that make the construction hardware-free. :ivar note: What this strategy is, for the generated file's docstring. :ivar unique_kwarg: A keyword argument needing a fresh value per instance, when constructing twice over otherwise fails. The gates construct repeatedly, and a module keying a registry on its address or host collides on the second one. :ivar unwrap: ``DocDevice`` is ``partial(Device, ...)`` rather than a class. ``DevicePipeline`` constructs from a class and names it by ``__name__``, which a partial has not, so the file passes ``Device`` and forwards the partial's arguments — the same construction, read from ``DocDevice`` at run time so it cannot drift from it. .. py:attribute:: name :type: str .. py:attribute:: factory :type: str .. py:attribute:: kwargs :type: dict[str, Any] .. py:attribute:: note :type: str :value: '' .. py:attribute:: unique_kwarg :type: str | None :value: None .. py:attribute:: unwrap :type: bool :value: False .. py:class:: MockTestScaffold(project_root: pathlib.Path) Build a module's mock pipeline test from its own declarations. :param project_root: Root directory of the device module. Store the project root and the reader shared with the field test. .. py:attribute:: project_root .. py:attribute:: _reader .. py:method:: candidates() -> plesty.sdk.commands.field_test.Candidates Return what the device declares, read as the field test reads it. :returns: The module's parameters, operations and package name. :raises ScaffoldError: If the module cannot be imported or built doc-free. .. py:attribute:: FILENAME :value: 'test_mock_pipeline.py' .. py:attribute:: MARKER :value: 'Generated by ``plesty init mock-test``' .. py:method:: filename(candidates: plesty.sdk.commands.field_test.Candidates) -> str Return the test file name. :param candidates: What the device declared; unused, kept so callers read the same way as the field-test scaffold. :returns: The bare file name. .. py:method:: probe(candidates: plesty.sdk.commands.field_test.Candidates) -> MockStrategy Find a construction that connects, identifies and reports no errors. :param candidates: What the device declared. :returns: The first strategy that passed every check. :raises ScaffoldError: If none did, listing what was tried and why each failed. .. py:method:: _export(candidates: plesty.sdk.commands.field_test.Candidates, name: str, required: bool = True) -> Any Return a name exported by the module. :param candidates: What the device declared, for the module name. :param name: The exported name to look up. :param required: Refuse when it is missing. :returns: The exported object, or ``None`` when absent and not required. :raises ScaffoldError: If the name is required and the module has none. .. py:method:: render(candidates: plesty.sdk.commands.field_test.Candidates, strategy: MockStrategy) -> str Emit the test file. :param candidates: What the device declared. :param strategy: How the file reaches the device. :returns: The file content. .. py:method:: write(content: str, candidates: plesty.sdk.commands.field_test.Candidates, force: bool = False) -> pathlib.Path Write the test file. :param content: The rendered file. :param candidates: What the device declared, for the file name. :param force: Replace an existing file. :returns: The path written. :raises ScaffoldError: If the file exists and *force* is not set. .. py:function:: _never_ask(*args: Any, **kwargs: Any) -> Any Refuse to prompt. The field-test scaffold takes an ``ask`` because its questions are the point of it. This command has none, and reaching a prompt would mean a code path assumed one. :raises AssertionError: Always. .. py:function:: _accepted_kwargs(device_cls: Any) -> set[str] Return the keyword arguments a device constructor accepts. :param device_cls: The device class. :returns: The accepted names. A constructor taking ``**kwargs`` accepts anything, which is reported as every candidate name so a strategy is tried rather than skipped. .. py:function:: _verify(factory: Any, kwargs: dict[str, Any]) -> str | None Run the lifecycle, identity and error checks against a construction. These are three of the eight gates. Verifying here is what makes a generated file that is written a generated file that passes. :param factory: The device class or factory to call. :param kwargs: Keyword arguments to call it with. :returns: ``None`` when every check passed, otherwise why it did not. .. py:function:: _needs_unique(device_cls: Any, kwargs: dict[str, Any], accepted: set[str]) -> str | None Return the keyword argument needing a fresh value per instance. The gates construct the device several times over. A module keying its resource registry on the address or host collides on the second construction, which is why both reference modules hand-wrote a uniquifier. Rather than assume, this constructs twice and looks. :param device_cls: The device class. :param kwargs: The strategy's keyword arguments. :param accepted: The constructor's accepted keyword names. :returns: The name to vary, or ``None`` when repeated construction is fine. .. py:function:: _import_block(candidates: plesty.sdk.commands.field_test.Candidates, strategy: MockStrategy) -> str Return the import lines the generated file needs. :param candidates: What the device declared. :param strategy: How the file reaches the device. :returns: The import block. .. py:function:: _device_block(candidates: plesty.sdk.commands.field_test.Candidates, strategy: MockStrategy) -> str Return the device subclass the pipeline is given, when one is needed. :param candidates: What the device declared. :param strategy: How the file reaches the device. :returns: The block, empty when the exported factory can be used as it stands. .. py:function:: _pipeline_args(candidates: plesty.sdk.commands.field_test.Candidates, strategy: MockStrategy) -> str Return the arguments of the ``DevicePipeline(...)`` call. :param candidates: What the device declared. :param strategy: How the file reaches the device. :returns: The argument text, on one line. .. py:function:: _asset(name: str) -> Any Return a packaged asset. :param name: File name under ``plesty/sdk/assets/init``. :returns: The traversable asset. .. py:function:: scaffold_mock_test(project_root: pathlib.Path, force: bool = False) -> pathlib.Path Probe the module and write its mock pipeline test. :param project_root: Root directory of the device module. :param force: Replace an existing file. :returns: The path written. :raises ScaffoldError: If the module cannot be read, or no hardware-free construction of it works.