Add gomc-rest Python package with bundled server (Pattern B)#1
Merged
Conversation
Client-independent groundwork for gomc-rest-python. Adds the package metadata and the platform binary resolver that locates the bundled gomc-rest server. The HTTP layer is delegated to gomc-rest-client; the launch() wiring is intentionally deferred until the in-progress gomc-rest-client update lands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
Add the deferred client-dependent layer now that gomc-rest-client is updated: - _server.py: spawn bundled gomc-rest on a free port, wait for /health, verify version support, stop via context manager / atexit. - __init__.py: launch() returning a managed Server; re-export PLCClient and exceptions. - Pin gomc-rest-client>=0.9.0 (requires bundled server v1.3.0+). - README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
launch()/Server gain server_mode (default False). Default binds the bundled server to 127.0.0.1 so no other app or host can reach the unauthenticated REST API; server_mode=True binds all interfaces so other apps on a trusted network can call it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
gomc-rest and gomc-rest-client now support token auth. launch()/Server generate a random token per launch, pass it via -token to the bundled server, and set it on the PLCClient. This makes the default effectively script-only: another process on the same host that learns the port still cannot call the API without the token. - token param: None auto-generates; explicit string to share; "" disables. - Expose server.token; re-export GomcRestUnauthorizedError. - Pin gomc-rest-client>=0.10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
- GOMC_REST_VERSION pins the bundled server (v1.3.0, the client's minimum). - scripts/vendor_binaries.py downloads the binaries from the matching gomc-rest GitHub release (all, or one via --only) into the package. - release.yml builds one platform-specific wheel per OS (win_amd64, manylinux2014 x86_64/aarch64), each bundling only its matching binary, and publishes to PyPI via trusted publishing on a v* tag. - binaries/.gitignore keeps downloaded binaries out of git. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
…oke test P1: bump GOMC_REST_VERSION to v1.4.0 (v1.3.0 lacks -token and is below the client's MINIMUM_SUPPORTED_GOMC_REST_VERSION), and add a smoke test that launches with default settings. P2: - token="" now disables auth correctly: distinguish unspecified (None -> generate) from "" (disable), and always pass -token explicitly (empty when disabled) so an inherited GOMCR_TOKEN can't enable server-side auth while the client stays unauthenticated. - Wrap post-Popen startup validation in try/except and close() the subprocess on any failure, since the caller never receives a Server. - close() now also releases the PLCClient HTTP session and atexit.unregister()s itself, so repeated launch/close no longer accumulates connections/objects. - release.yml verifies the pushed tag matches project.version before building. Add ci.yml to vendor the Linux binary and run the smoke test on PRs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
… docs - _binaries.py: chmod only when the executable bit is actually missing, so a non-owner running a root-installed, already-executable binary no longer hits PermissionError; raise a clear error if a needed chmod is denied. (P1) - pyproject.toml: cap gomc-rest-client to >=0.10.0,<0.11 so a future client raising its minimum server version can't break installs of this package. (P2) - _server.py: pass the token via GOMCR_TOKEN in a copied env instead of the -token argv, so it isn't visible in the process list / /proc cmdline. (P2) - release.yml: gate the publish job on github.ref_type == 'tag' so workflow_dispatch builds but never publishes from an arbitrary branch. (P2) - README: bundled version v1.4.0, dependency cap, documented threat model (same-OS-user can read the env), and a release procedure that bumps both project.version and __version__ to match the tag. (P2) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
P1 (glibc tag): the v1.4.0 amd64 binary is dynamically linked and needs GLIBC_2.34, so tag its wheel manylinux_2_34_x86_64 instead of manylinux2014 (glibc 2.17), which would let pip install it on systems where it fails with 'GLIBC_2.34 not found'. The arm64 binary is statically linked (verified: no dynamic section), so the wider manylinux2014_aarch64 tag stays correct. Add a floor-glibc-amd64 CI job that runs the smoke test inside a glibc-2.34 container so the binary is proven to start at its declared floor. P2 (supply chain): pin the trusted SHA-256 of each release asset in checksums/<version>.sha256 (committed, not fetched alongside the binary) and verify every download in vendor_binaries.py, failing closed on mismatch or a missing pin. Document adding the checksum file when bumping the bundled server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a new Python package that bundles the
gomc-restserver binary and auto-launches it as a subprocess, providing a seamless interface for communicating with Mitsubishi PLCs. This implements "Pattern B" — users never need to manually start or distribute the server executable.Key Changes
Core server lifecycle management (
src/gomc_rest/_server.py):Serverclass that spawns the bundledgomc-restbinary on a free loopback portatexithandlerserver_modeto bind all interfaces for network accessPublic API (
src/gomc_rest/__init__.py):launch()function as the primary entry pointPLCClientand exception types fromgomc-rest-clientPlatform-specific binary resolution (
src/gomc_rest/_binaries.py):Binary vendoring (
scripts/vendor_binaries.py):gomc-restbinaries from GitHub releasesGOMC_REST_VERSIONfile or environment variableRelease automation (
.github/workflows/release.yml):Project configuration (
pyproject.toml):gomc-rest-client>=0.10.0Documentation (
README.md):Notable Implementation Details
https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy