fix(python_codegen): emit service methods without PascalCase prefix #40

Merged
timur merged 2 commits from feat/python-codegen-strip-service-prefix into development 2026-04-21 21:17:51 +00:00
Owner

Summary

Fixes lhumina_code/hero_rpc#29.

Service methods declared on an OSchema service block (e.g. LogicService.play_start) were previously emitted as LogicService_play_start in the generated Python client — ugly, not what the issue expected, and effectively unused (every flow template hand-rolls raw socket/JSON-RPC instead of importing the generated client).

This PR:

  • Strips the PascalCase service prefix in to_python_method_name so the generated method is play_start, workflow_clone, workflow_version_fetch, etc. CRUD (play.list) and management (rpc.discover) methods are untouched — they keep their lowercase prefixes. The wire method name stays intact inside _call(...).
  • Prefers description over summary for docstrings, because hero_rpc's openrpc.json generator writes the real oschema comment into description while summary duplicates the method name. Hand-authored specs (hero_router's own static spec) only set summary, so the fallback keeps them working.
  • Adds examples/regen_python_client so a service's cached Python files can be regenerated from a static OpenRPC spec without rebuilding + restarting the service behind it — useful for local iteration and for this PR's verification step.

JS and Rust generators are untouched.

Verification

$ cargo run --example regen_python_client -- \
    --spec hero_logic/crates/hero_logic/src/logic/core/openrpc.json \
    --service hero_logic
regenerated Python client + interface for hero_logic

$ grep 'play_start\|workflow_clone\|workflow_version_fetch' \
    ~/.hero/var/router/python/hero_logic_client.py
    def workflow_clone(self, source_workflow_sid: str, new_name: str) -> Any:
        return self._call("LogicService.workflow_clone", {...})
    def workflow_version_fetch(self, sid: str) -> Any:
        return self._call("LogicService.workflow_version_fetch", {...})
    def play_start(self, workflow_sid: str, input_data: str, name: str) -> Any:
        return self._call("LogicService.play_start", {...})

All 25 LogicService methods are now present with typed signatures and description-sourced docstrings. No LogicService_ prefix remains.

New tests:

  • test_python_method_name extended to cover PascalCase stripping, lowercase passthrough, nested dots, and hyphens
  • test_service_methods_emitted_without_prefix — end-to-end assert on generator output

One pre-existing test (test_generate_from_router_spec) was already failing on development before this change (asserts 21 methods but the spec now has a different count) — not addressed here.

Size comparison (for the hero_rpc#29 ask)

Every Python node in hero_logic/crates/hero_logic/templates/*.json today pastes a local rpc() helper with a UDS class to make JSON-RPC-over-Unix-socket calls — ~18 lines of boilerplate per node. Across the 5 flow templates: 12 such Python nodes, ~95 total boilerplate lines.

With the new generated client, each node collapses to:

from hero_logic_client import HeroLogicClient
logic = HeroLogicClient()

≈ 2 lines instead of 18 → ~190 lines of boilerplate deletable when the templates are rewritten.

Template rewrites are explicitly not in this PR — they're scheduled for deletion in lhumina_code/hero_logic#13 per the flows-as-python epic (hero_logic#10). Touching them here would waste work.

Follow-up issues (already filed)

  • hero_rpc#31 — move Python client generation out of hero_router into hero_rpc (build-time, .oschema-authoritative). This PR is the tactical unblock; #31 is the proper architectural home.
  • hero_rpc#32 — rpc.discover can serve a stale cached spec. Orthogonal — this PR regenerates from the static openrpc.json, so it's unaffected. A separate PR will address the live-spec freshness.

Test plan

  • cargo test -p hero_router --lib python_codegen — 7/7 new/updated tests pass
  • cargo build -p hero_router --example regen_python_client
  • Example runs end-to-end against hero_logic's static spec
  • DoD grep matches play_start|workflow_clone|workflow_version_fetch in the regenerated client
  • No LogicService_ prefix in the regenerated client
  • Full router integration test with live hero_logic rebuild (depends on hero_logic rebuild; out of scope — see hero_rpc#32)

🤖 Generated with Claude Code

## Summary Fixes lhumina_code/hero_rpc#29. Service methods declared on an OSchema `service` block (e.g. `LogicService.play_start`) were previously emitted as `LogicService_play_start` in the generated Python client — ugly, not what the issue expected, and effectively unused (every flow template hand-rolls raw socket/JSON-RPC instead of importing the generated client). This PR: - **Strips the PascalCase service prefix** in `to_python_method_name` so the generated method is `play_start`, `workflow_clone`, `workflow_version_fetch`, etc. CRUD (`play.list`) and management (`rpc.discover`) methods are untouched — they keep their lowercase prefixes. The wire method name stays intact inside `_call(...)`. - **Prefers `description` over `summary` for docstrings**, because `hero_rpc`'s openrpc.json generator writes the real oschema comment into `description` while `summary` duplicates the method name. Hand-authored specs (hero_router's own static spec) only set `summary`, so the fallback keeps them working. - **Adds `examples/regen_python_client`** so a service's cached Python files can be regenerated from a static OpenRPC spec without rebuilding + restarting the service behind it — useful for local iteration and for this PR's verification step. JS and Rust generators are untouched. ## Verification ``` $ cargo run --example regen_python_client -- \ --spec hero_logic/crates/hero_logic/src/logic/core/openrpc.json \ --service hero_logic regenerated Python client + interface for hero_logic $ grep 'play_start\|workflow_clone\|workflow_version_fetch' \ ~/.hero/var/router/python/hero_logic_client.py def workflow_clone(self, source_workflow_sid: str, new_name: str) -> Any: return self._call("LogicService.workflow_clone", {...}) def workflow_version_fetch(self, sid: str) -> Any: return self._call("LogicService.workflow_version_fetch", {...}) def play_start(self, workflow_sid: str, input_data: str, name: str) -> Any: return self._call("LogicService.play_start", {...}) ``` All 25 LogicService methods are now present with typed signatures and description-sourced docstrings. No `LogicService_` prefix remains. New tests: - `test_python_method_name` extended to cover PascalCase stripping, lowercase passthrough, nested dots, and hyphens - `test_service_methods_emitted_without_prefix` — end-to-end assert on generator output One pre-existing test (`test_generate_from_router_spec`) was already failing on `development` before this change (asserts `21 methods` but the spec now has a different count) — not addressed here. ## Size comparison (for the hero_rpc#29 ask) Every Python node in `hero_logic/crates/hero_logic/templates/*.json` today pastes a local `rpc()` helper with a `UDS` class to make JSON-RPC-over-Unix-socket calls — ~18 lines of boilerplate per node. Across the 5 flow templates: **12 such Python nodes, ~95 total boilerplate lines**. With the new generated client, each node collapses to: ```python from hero_logic_client import HeroLogicClient logic = HeroLogicClient() ``` ≈ 2 lines instead of 18 → **~190 lines of boilerplate deletable** when the templates are rewritten. Template rewrites are explicitly *not* in this PR — they're scheduled for deletion in lhumina_code/hero_logic#13 per the flows-as-python epic (hero_logic#10). Touching them here would waste work. ## Follow-up issues (already filed) - hero_rpc#31 — move Python client generation out of hero_router into hero_rpc (build-time, .oschema-authoritative). This PR is the tactical unblock; #31 is the proper architectural home. - hero_rpc#32 — `rpc.discover` can serve a stale cached spec. Orthogonal — this PR regenerates from the static openrpc.json, so it's unaffected. A separate PR will address the live-spec freshness. ## Test plan - [x] `cargo test -p hero_router --lib python_codegen` — 7/7 new/updated tests pass - [x] `cargo build -p hero_router --example regen_python_client` - [x] Example runs end-to-end against `hero_logic`'s static spec - [x] DoD grep matches `play_start|workflow_clone|workflow_version_fetch` in the regenerated client - [x] No `LogicService_` prefix in the regenerated client - [ ] Full router integration test with live `hero_logic` rebuild (depends on hero_logic rebuild; out of scope — see hero_rpc#32) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(python_codegen): strip PascalCase service prefix from method names
Some checks failed
Build & Test / check (pull_request) Failing after 1m36s
9a1574af70
Service methods declared on an .oschema `service` block (e.g.
`LogicService.play_start`) were previously emitted as
`LogicService_play_start`, making generated Python clients awkward. Strip
the PascalCase prefix so callers write `client.play_start(...)`. CRUD
(`play.list`) and management (`rpc.discover`) methods keep their
lowercase prefixes — the wire method name is always preserved inside
`_call(...)`.

Also prefer `description` over `summary` for method docstrings, since
hero_rpc's openrpc.json generator writes the real comment into
`description` while `summary` duplicates the method name.

Adds `examples/regen_python_client` so a service's cached Python files
can be regenerated from a static OpenRPC spec without rebuilding +
restarting the service behind it.

Refs lhumina_code/hero_rpc#29

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test(python_codegen): derive expected method count from spec
All checks were successful
Build & Test / check (pull_request) Successful in 1m43s
7cad41dbcc
`test_generate_from_router_spec` hardcoded "# hero_router interface — 21
methods" and broke whenever the static openrpc.json gained or lost a
method. Derive the count from the spec so the test stays correct. This
failure was already blocking CI on `development` before hero_rpc#29.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
timur merged commit 31ec54cd36 into development 2026-04-21 21:17:51 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_router!40
No description provided.