Skip to content

solidworks_mcp.selftest.documents

solidworks_mcp.selftest.documents

Keep the selftest away from the user's documents.

The guard notes which documents are open before the selftest starts and gives it a fresh temporary folder to save into. On the way out, also after an error, it closes, unsaved, only the documents the selftest made, then deletes the folder. A document that was open before is never activated, saved or closed.

Attributes

_CLOSE_ORDER module-attribute

_CLOSE_ORDER = {'Drawing': 0, 'Assembly': 1}

Classes

DocumentGuard

DocumentGuard(call: ToolCaller)

An async context manager owning the selftest's folder and documents.

Use call to list, activate and close documents.

Parameters:

Name Type Description Default
call ToolCaller

The selftest's tool caller.

required
Source code in src/solidworks_mcp/selftest/documents.py
def __init__(self, call: ToolCaller) -> None:
    """Use ``call`` to list, activate and close documents.

    Args:
        call: The selftest's tool caller.
    """
    self._call = call
    self._baseline: set[str] = set()
    self._folder: Path | None = None
    self.problems: list[str] = []
Attributes
folder property
folder: Path

The selftest's temporary folder; only inside async with.

Raises:

Type Description
RuntimeError

The guard has not been entered.

Methods:
__aenter__ async
__aenter__() -> DocumentGuard

Note the open documents and make the folder.

Returns:

Name Type Description
DocumentGuard DocumentGuard

This guard, with folder set.

Source code in src/solidworks_mcp/selftest/documents.py
async def __aenter__(self) -> DocumentGuard:
    """Note the open documents and make the folder.

    Returns:
        DocumentGuard: This guard, with ``folder`` set.
    """
    self._baseline = {d["title"] for d in await self._documents() if d["title"]}
    self._folder = Path(tempfile.mkdtemp(prefix="solidworks-mcp-selftest-"))
    return self
__aexit__ async
__aexit__(exc_type: type[BaseException] | None, exc: BaseException | None, traceback: TracebackType | None) -> None

Close the selftest's documents and delete its folder.

Parameters:

Name Type Description Default
exc_type type[BaseException] | None

The exception's type, if one is propagating.

required
exc BaseException | None

The exception, if one is propagating.

required
traceback TracebackType | None

Its traceback.

required
Source code in src/solidworks_mcp/selftest/documents.py
async def __aexit__(
    self,
    exc_type: type[BaseException] | None,
    exc: BaseException | None,
    traceback: TracebackType | None,
) -> None:
    """Close the selftest's documents and delete its folder.

    Args:
        exc_type: The exception's type, if one is propagating.
        exc: The exception, if one is propagating.
        traceback: Its traceback.
    """
    await self.cleanup()
cleanup async
cleanup() -> None

Close the selftest's documents, unsaved, then delete its folder.

A failure is recorded in problems, never raised, so it can't hide the results it follows.

Source code in src/solidworks_mcp/selftest/documents.py
async def cleanup(self) -> None:
    """Close the selftest's documents, unsaved, then delete its folder.

    A failure is recorded in ``problems``, never raised, so it can't hide
    the results it follows.
    """
    try:
        documents = await self._documents()
    except ToolFailed as exc:
        self.problems.append(f"could not list the open documents: {exc.message}")
        documents = []
    ours = sorted(
        (d for d in documents if self.is_ours(d)),
        key=lambda d: _CLOSE_ORDER.get(str(d.get("type")), 2),
    )
    for document in ours:
        try:
            await self._call(
                "activate_document",
                {"title_or_path": document["title"]},
                record=False,
            )
            await self._call("close_model", {"save": False}, record=False)
        except ToolFailed as exc:
            self.problems.append(
                f"could not close {document['title']}: {exc.message}"
            )
    shutil.rmtree(self.folder, ignore_errors=True)
    if self.folder.exists():
        self.problems.append(f"could not delete {self.folder}")
    self._folder = None
is_ours
is_ours(document: dict[str, Any]) -> bool

Whether the selftest made document.

Parameters:

Name Type Description Default
document dict[str, Any]

An entry from list_open_documents.

required

Returns:

Name Type Description
bool bool

True when it is new since the start and was never saved,

bool

or was saved inside the selftest's folder.

Source code in src/solidworks_mcp/selftest/documents.py
def is_ours(self, document: dict[str, Any]) -> bool:
    """Whether the selftest made ``document``.

    Args:
        document: An entry from ``list_open_documents``.

    Returns:
        bool: True when it is new since the start and was never saved,
        or was saved inside the selftest's folder.
    """
    if not document.get("title") or document["title"] in self._baseline:
        return False
    path = document.get("path") or ""
    if not path:
        return True
    folder = os.path.normcase(str(self.folder.resolve()))
    return os.path.normcase(str(Path(path).resolve())).startswith(folder + os.sep)

ToolCaller

ToolCaller(client: Any)

Call MCP tools by name with dict input, as a client does; log each call.

Wrap a connected fastmcp.Client.

Parameters:

Name Type Description Default
client Any

The client, already entered.

required
Source code in src/solidworks_mcp/selftest/results.py
def __init__(self, client: Any) -> None:
    """Wrap a connected ``fastmcp.Client``.

    Args:
        client: The client, already entered.
    """
    self._client = client
    self.calls: list[str] = []
Methods:
__call__ async
__call__(name: str, args: dict[str, Any] | None = None, record: bool = True) -> dict[str, Any]

Call name and return its reply; raise when it reports an error.

Parameters:

Name Type Description Default
name str

The tool's name.

required
args dict[str, Any] | None

Its input, sent as input_data; None for a tool without input.

None
record bool

Whether the call is listed in the report.

True

Returns:

Type Description
dict[str, Any]

dict[str, Any]: The tool's reply.

Raises:

Type Description
ToolFailed

The tool replied with status: error.

Source code in src/solidworks_mcp/selftest/results.py
async def __call__(
    self, name: str, args: dict[str, Any] | None = None, record: bool = True
) -> dict[str, Any]:
    """Call ``name`` and return its reply; raise when it reports an error.

    Args:
        name: The tool's name.
        args: Its input, sent as ``input_data``; ``None`` for a tool
            without input.
        record: Whether the call is listed in the report.

    Returns:
        dict[str, Any]: The tool's reply.

    Raises:
        ToolFailed: The tool replied with ``status: error``.
    """
    if record:
        self.calls.append(name)
    result = await self._client.call_tool(
        name, {} if args is None else {"input_data": args}, raise_on_error=False
    )
    reply = tool_reply(result)
    if reply.get("status") != "success":
        raise ToolFailed(name, str(reply.get("message") or "no message"))
    return reply

ToolFailed

ToolFailed(tool: str, message: str)

Bases: Exception

A tool call returned status: error.

Remember which tool failed and its message.

Parameters:

Name Type Description Default
tool str

The tool's name.

required
message str

The tool's error message.

required
Source code in src/solidworks_mcp/selftest/results.py
def __init__(self, tool: str, message: str) -> None:
    """Remember which tool failed and its message.

    Args:
        tool: The tool's name.
        message: The tool's error message.
    """
    super().__init__(f"{tool} failed: {message}")
    self.tool = tool
    self.message = message
Methods: