Two releases' worth of work, published together. Read the breaking changes
first — one of them was destroying data.
⚠️ Breaking
DbResource.delete() was deleting the entire table
Node.delete([101]) removed every node, not node 101 — and with them every
element attached to those nodes. One call could empty a model. It has behaved
this way since the first commit, through 11 releases, and no mocked test could
have caught it: the request shape was exactly what the manual documents.
The manual's ID-keyed DELETE {endpoint} body is ignored by the server. The
undocumented per-id URL, DELETE {endpoint}/{id}, removes exactly one record.
delete() now uses it, issuing one request per id.
- Return type changed:
{id: response}instead of a single response body. - Deleting an id that doesn't exist is a harmless no-op, so no need to check first.
- The old whole-table behaviour is still available, deliberately named:
DbResource.delete_all().
If you have code that called delete() expecting a single record to go, it was
doing something much worse than you thought — and it needs no change now beyond
the return value.
Hyper-S (-M1) endpoints are Civil NX only
Hyper-S is the solver MIDASIT introduced with Civil NX. The SDK offered all 21
-M1 endpoints to Gen clients as well, where the best possible outcome was a
404 for the caller to interpret. Live testing: 13/13 implemented endpoints
answer under Civil NX 2026 and 404 under Gen NX 2026, as do all 8 unimplemented
stubs.
A Gen client now raises ProductMismatchError before issuing the request. Use
the new HYPER_S_ONLY constant in db/base.py — kept separate from
CIVIL_ONLY because Hyper-S is expected to reach Gen NX eventually, and that
day this should be one line to widen.
Fixed
doc.analyze()no longer reports a failed solve as success. The solver
reports failure as{"message": "... Analysis failed."}— the same key a
success uses for"... command complete", with noerrorobject, so
v0.12.0's client-level check could not see it. Every result table would
otherwise come back empty with nothing pointing at the cause.
Added
DbResource.delete_all()— empty a table on purpose.HYPER_S_ONLYindb/base.py.scripts/live_readonly_sweep.py— GETs every endpoint the SDK wraps against
a live session. Read-only, so it is safe against an open model.scripts/live_crud_check.py— create → read → update → delete round trips,
verifying the server echoes back what was written. This is what found the
delete()bug.
Verification
Live-verified endpoints went from 10/390 to 295/390, across both products,
recorded per endpoint in docs/coverage.json. Gen and Civil each reproduced
their earlier sweep numbers exactly, on a real 710-node analyzed model rather
than a blank one.
Write paths now have round-trip coverage on 10 resources: Civil 10/10,
Gen 9/9.
Live-server behaviour worth knowing
All of this is new in docs/live_verification_notes.md:
- Every path in this API resolves on the machine running NX, which is often
not the machine running your code.EXPORT_PATH,/doc/SAVEAS,/doc/OPEN,
report and image paths all go there. A path that doesn't exist on that machine
raises a modal dialog there and blocks the session, while the HTTP call
still answerscommand complete. Build paths from
verify_connection()["user"]; verify a write with/doc/OPEN, never
os.path.exists(). /db/CONS'sCONSTRAINTmust be 7 characters. Six is rejected; eight is
silently truncated to seven, giving you a support you never asked for./db/STLDignores the"Assign"key and renumbers sequentially, while
/db/NODEhonours it. Code that writes a record and reads it back by the key
it sent will miss for load cases.- Error bodies arrive under 201 as well as 200.
/doc/NEWand the*-ANALdesign checks can kill the application, with a
licence dialog that holds the licence until the process is restarted properly.
Seven hypotheses were tested and eliminated — including reopening the exact
model that crashed — so there is no precondition to check. Don't point them at
a session holding work that isn't saved.
Compatibility
Python 3.9+, requests only. 637 tests, no live server needed.