fix: Commands CPO version details + push version negotiation#23
Merged
alfonsosastre merged 7 commits intomainfrom Feb 25, 2026
Merged
fix: Commands CPO version details + push version negotiation#23alfonsosastre merged 7 commits intomainfrom
alfonsosastre merged 7 commits intomainfrom
Conversation
push_object now accepts both a versions URL and a version details URL
as endpoints_url. When a versions list is returned (data is a list),
it automatically picks the best mutual version and fetches the details
URL to discover endpoints. This follows the proper OCPI spec flow:
1. GET /versions → list of supported versions with details URLs
2. Pick best mutual version
3. GET /{version}/details → list of endpoints
4. Push to the correct module endpoint
Co-authored-by: Cursor <cursoragent@cursor.com>
The endpoint URL from version details doesn't include a trailing slash, causing the country_code to be concatenated directly (e.g. locationsDE/ELU/... instead of locations/DE/ELU/...). Co-authored-by: Cursor <cursoragent@cursor.com>
…ions (#22) * feat: add Commands (receiver) to CPO version details for all OCPI versions Add ModuleID.commands with InterfaceRole.receiver to the CPO ENDPOINTS_LIST for OCPI 2.1.1, 2.2.1, and 2.3.0. Without this, Payter and other eMSP/PTP partners could not discover the Commands endpoint via GET version details, blocking START_SESSION/STOP_SESSION flows. Also add credentials SENDER and payments SENDER roles to CPO endpoints for 2.2.1 and 2.3.0, as recommended by Payter to support proactive credential updates and payment data push. To support multiple interface roles per module (e.g. credentials with both SENDER and RECEIVER), convert ENDPOINTS_LIST from dict[ModuleID, Endpoint] to list[Endpoint] in all six endpoint files (CPO + eMSP for each version). Update main.py to iterate the list instead of using dict.get(). All existing tests pass. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: address review suggestions for commands CPO endpoint PR - Add list[Endpoint] type annotation to ENDPOINTS_LIST in all 6 endpoint files (CPO + eMSP × 3 versions) so mypy catches accidental reversion - Add comment in v_2_1_1/cpo.py explaining why credentials SENDER is omitted (InterfaceRole is a 2.2.1+ concept) - Add regression tests for commands RECEIVER discovery in version details for all three OCPI versions; add credentials SENDER/RECEIVER tests for 2.2.1 and 2.3.0; add payments SENDER/RECEIVER test for 2.3.0 - Fix tests/test_modules/mocks/async_client.py to use list iteration instead of dict key lookup after ENDPOINTS_LIST dict→list migration All 457 tests pass. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Alfonso Sastre <alfonso@elumobility.com> Co-authored-by: Cursor <cursoragent@cursor.com>
- Guard missing "url" key in _pick_version_details_url so malformed version entries are skipped instead of raising KeyError - Clarify docstring: fallback may select a version newer than requested - Add response.raise_for_status() after both HTTP fetches in push_object so 4xx/5xx responses raise httpx.HTTPStatusError instead of silently failing with a KeyError on response.json()["data"] - Add raise_for_status() to MockResponse in async_client.py (required after wiring raise_for_status into push_object); also add httpx/ MagicMock imports and replace bare next() with next(..., None) + assertion for a clearer failure message - Add 11 new tests covering: _pick_version_details_url (exact match, fallback, no mutual version, empty list, missing url/version keys), push_object version negotiation two-GET flow, no-mutual-version ValueError, and HTTP error propagation on both GET requests Co-authored-by: Cursor <cursoragent@cursor.com>
…ntenance note - Add comment in v_2_2_1/emsp.py and v_2_3_0/emsp.py explaining why credentials is RECEIVER-only for eMSP and when to add SENDER if needed - Add comment on _VERSION_PREFERENCE in push.py noting it must be updated when new OCPI versions are added Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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
This PR consolidates 4 fixes/features onto
main:ModuleID.commandswithInterfaceRole.receiverto CPOENDPOINTS_LISTfor OCPI 2.1.1, 2.2.1, and 2.3.0; also addscredentials SENDER(2.2.1, 2.3.0) andpayments SENDER(2.3.0) as recommended by Payter; convertsENDPOINTS_LISTfromdicttolistto support multiple interface roles per moduleENDPOINTS_LIST, explanatory comment in v2.1.1, regression tests for Commands/credentials discovery,async_client.pymock fixContext
Payter was unable to start charge sessions because the Commands module was not advertised in the CPO version details. Error on their side:
The
START_SESSION/STOP_SESSIONhandler was already implemented inelu_ocpi/crud.py— it just wasn't discoverable.Test plan
GET /ocpi/2.2.1/detailsreturnscommandswith roleRECEIVERGET /ocpi/2.2.1/detailsreturnscredentialswith bothSENDERandRECEIVERSTART_SESSIONMade with Cursor