Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions ocpi/core/endpoints/v_2_1_1/cpo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from ocpi.core.endpoints.v_2_1_1.utils import cpo_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_1_1.schemas import Endpoint

# OCPI 2.1.1 Endpoint schema has no InterfaceRole field; credentials SENDER
# role is implicit and not separately advertised (InterfaceRole is 2.2.1+).

CREDENTIALS_AND_REGISTRATION = cpo_generator.generate_endpoint(
ModuleID.credentials_and_registration,
Expand All @@ -15,11 +19,14 @@

TOKENS = cpo_generator.generate_endpoint(ModuleID.tokens)

ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.sessions: SESSIONS,
ModuleID.tokens: TOKENS,
}
COMMANDS = cpo_generator.generate_endpoint(ModuleID.commands)

ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
LOCATIONS,
CDRS,
TARIFFS,
SESSIONS,
TOKENS,
COMMANDS,
]
19 changes: 10 additions & 9 deletions ocpi/core/endpoints/v_2_1_1/emsp.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ocpi.core.endpoints.v_2_1_1.utils import emsp_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_1_1.schemas import Endpoint

CREDENTIALS_AND_REGISTRATION = emsp_generator.generate_endpoint(
ModuleID.credentials_and_registration,
Expand All @@ -17,12 +18,12 @@

COMMANDS = emsp_generator.generate_endpoint(ModuleID.commands)

ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.sessions: SESSIONS,
ModuleID.tokens: TOKENS,
ModuleID.commands: COMMANDS,
}
ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
LOCATIONS,
CDRS,
TARIFFS,
SESSIONS,
TOKENS,
COMMANDS,
]
34 changes: 23 additions & 11 deletions ocpi/core/endpoints/v_2_2_1/cpo.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from ocpi.core.endpoints.v_2_2_1.utils import cpo_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_2_1.schemas import InterfaceRole
from ocpi.modules.versions.v_2_2_1.schemas import Endpoint, InterfaceRole

CREDENTIALS_AND_REGISTRATION = cpo_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.receiver,
)

CREDENTIALS_SENDER = cpo_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.sender,
)

LOCATIONS = cpo_generator.generate_endpoint(
ModuleID.locations,
InterfaceRole.sender,
Expand All @@ -32,6 +37,11 @@
InterfaceRole.receiver,
)

COMMANDS = cpo_generator.generate_endpoint(
ModuleID.commands,
InterfaceRole.receiver,
)

HUB_CLIENT_INFO = cpo_generator.generate_endpoint(
ModuleID.hub_client_info,
InterfaceRole.receiver,
Expand All @@ -43,13 +53,15 @@
)


ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.sessions: SESSIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.tokens: TOKENS,
ModuleID.hub_client_info: HUB_CLIENT_INFO,
ModuleID.charging_profile: CHARGING_PROFILE,
}
ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
CREDENTIALS_SENDER,
LOCATIONS,
SESSIONS,
CDRS,
TARIFFS,
TOKENS,
COMMANDS,
HUB_CLIENT_INFO,
CHARGING_PROFILE,
]
28 changes: 16 additions & 12 deletions ocpi/core/endpoints/v_2_2_1/emsp.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from ocpi.core.endpoints.v_2_2_1.utils import emsp_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_2_1.schemas import InterfaceRole
from ocpi.modules.versions.v_2_2_1.schemas import Endpoint, InterfaceRole

# eMSP advertises credentials as RECEIVER only. Adding credentials SENDER would
# allow the eMSP to push proactive credential updates to registered CPOs, but
# this is not required for current integrations (Payter, etc.). Add
# CREDENTIALS_SENDER here if an eMSP-initiated credential update flow is needed.
CREDENTIALS_AND_REGISTRATION = emsp_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.receiver,
Expand Down Expand Up @@ -47,14 +51,14 @@
InterfaceRole.sender,
)

ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.sessions: SESSIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.commands: COMMANDS,
ModuleID.tokens: TOKENS,
ModuleID.hub_client_info: HUB_CLIENT_INFO,
ModuleID.charging_profile: CHARGING_PROFILE,
}
ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
LOCATIONS,
SESSIONS,
CDRS,
TARIFFS,
COMMANDS,
TOKENS,
HUB_CLIENT_INFO,
CHARGING_PROFILE,
]
44 changes: 31 additions & 13 deletions ocpi/core/endpoints/v_2_3_0/cpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

from ocpi.core.endpoints.v_2_3_0.utils import cpo_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_3_0.schemas import InterfaceRole
from ocpi.modules.versions.v_2_3_0.schemas import Endpoint, InterfaceRole

CREDENTIALS_AND_REGISTRATION = cpo_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.receiver,
)

CREDENTIALS_SENDER = cpo_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.sender,
)

LOCATIONS = cpo_generator.generate_endpoint(
ModuleID.locations,
InterfaceRole.sender,
Expand All @@ -34,6 +39,11 @@
InterfaceRole.receiver,
)

COMMANDS = cpo_generator.generate_endpoint(
ModuleID.commands,
InterfaceRole.receiver,
)

HUB_CLIENT_INFO = cpo_generator.generate_endpoint(
ModuleID.hub_client_info,
InterfaceRole.receiver,
Expand All @@ -50,22 +60,30 @@
InterfaceRole.receiver,
)

PAYMENTS_SENDER = cpo_generator.generate_endpoint(
ModuleID.payments,
InterfaceRole.sender,
)

# New in OCPI 2.3.0 - Booking extension
BOOKINGS = cpo_generator.generate_endpoint(
ModuleID.bookings,
InterfaceRole.receiver,
)


ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.sessions: SESSIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.tokens: TOKENS,
ModuleID.hub_client_info: HUB_CLIENT_INFO,
ModuleID.charging_profile: CHARGING_PROFILE,
ModuleID.payments: PAYMENTS,
ModuleID.bookings: BOOKINGS,
}
ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
CREDENTIALS_SENDER,
LOCATIONS,
SESSIONS,
CDRS,
TARIFFS,
TOKENS,
COMMANDS,
HUB_CLIENT_INFO,
CHARGING_PROFILE,
PAYMENTS,
PAYMENTS_SENDER,
BOOKINGS,
]
30 changes: 17 additions & 13 deletions ocpi/core/endpoints/v_2_3_0/emsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

from ocpi.core.endpoints.v_2_3_0.utils import emsp_generator
from ocpi.core.enums import ModuleID
from ocpi.modules.versions.v_2_3_0.schemas import InterfaceRole
from ocpi.modules.versions.v_2_3_0.schemas import Endpoint, InterfaceRole

# eMSP advertises credentials as RECEIVER only. Adding credentials SENDER would
# allow the eMSP to push proactive credential updates to registered CPOs, but
# this is not required for current integrations (Payter, etc.). Add
# CREDENTIALS_SENDER here if an eMSP-initiated credential update flow is needed.
CREDENTIALS_AND_REGISTRATION = emsp_generator.generate_endpoint(
ModuleID.credentials_and_registration,
InterfaceRole.receiver,
Expand Down Expand Up @@ -56,15 +60,15 @@
)


ENDPOINTS_LIST = {
ModuleID.credentials_and_registration: CREDENTIALS_AND_REGISTRATION,
ModuleID.locations: LOCATIONS,
ModuleID.sessions: SESSIONS,
ModuleID.cdrs: CDRS,
ModuleID.tariffs: TARIFFS,
ModuleID.tokens: TOKENS,
ModuleID.commands: COMMANDS,
ModuleID.hub_client_info: HUB_CLIENT_INFO,
ModuleID.charging_profile: CHARGING_PROFILE,
ModuleID.bookings: BOOKINGS,
}
ENDPOINTS_LIST: list[Endpoint] = [
CREDENTIALS_AND_REGISTRATION,
LOCATIONS,
SESSIONS,
CDRS,
TARIFFS,
TOKENS,
COMMANDS,
HUB_CLIENT_INFO,
CHARGING_PROFILE,
BOOKINGS,
]
56 changes: 54 additions & 2 deletions ocpi/core/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,41 @@
from ocpi.modules.versions.enums import VersionNumber
from ocpi.modules.versions.v_2_2_1.enums import InterfaceRole

# Ordered from newest to oldest. Update this list when new OCPI versions are added.
_VERSION_PREFERENCE = ["2.3.0", "2.2.1", "2.1.1"]


def _pick_version_details_url(
versions_list: list[dict], requested: VersionNumber
) -> str | None:
"""Pick the best version details URL from an OCPI /versions response.

Tries the requested version first, then falls back to the highest
mutually supported version from _VERSION_PREFERENCE. The fallback may
select a version newer than ``requested`` when the receiver does not
support the requested version but does support a higher one.

Entries missing either the ``version`` or ``url`` key are silently skipped.
"""
by_version = {
v["version"]: v["url"] for v in versions_list if "version" in v and "url" in v
}

if requested.value in by_version:
return by_version[requested.value]

for v in _VERSION_PREFERENCE:
if v in by_version:
return by_version[v]

return None


def client_url(module_id: ModuleID, object_id: str, base_url: str) -> str:
if module_id == ModuleID.cdrs:
return base_url
return f"{base_url}{settings.COUNTRY_CODE}/{settings.PARTY_ID}/{object_id}"
base = base_url.rstrip("/")
return f"{base}/{settings.COUNTRY_CODE}/{settings.PARTY_ID}/{object_id}"


def client_method(module_id: ModuleID) -> str:
Expand Down Expand Up @@ -106,7 +136,29 @@ async def push_object(
headers={"authorization": client_auth_token},
)
logger.info(f"Response status_code - `{response.status_code}`")
endpoints = response.json()["data"]["endpoints"]
response.raise_for_status()
response_data = response.json()["data"]

# If response is a versions list, negotiate version and
# fetch the details URL for the best mutual version.
if isinstance(response_data, list):
details_url = _pick_version_details_url(response_data, version)
if not details_url:
raise ValueError(
f"No mutual OCPI version found. "
f"Requested {version.value}, receiver supports: "
f"{[v.get('version') for v in response_data]}"
)
logger.info(f"Resolved version details URL: {details_url}")
response = await client.get(
details_url,
headers={"authorization": client_auth_token},
)
logger.info(f"Version details response: {response.status_code}")
response.raise_for_status()
response_data = response.json()["data"]

endpoints = response_data["endpoints"]
logger.debug(f"Endpoints response data - `{endpoints}`")

# get object data
Expand Down
16 changes: 10 additions & 6 deletions ocpi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def get_application(
version_endpoints[version] = []

if RoleEnum.cpo in roles:
cpo_modules_with_router: set[ModuleID] = set()
for module in modules:
cpo_router = mapped_version["cpo_router"].get(module) # type: ignore[attr-defined]
if cpo_router:
Expand All @@ -233,11 +234,13 @@ def get_application(
prefix=f"/{settings.OCPI_PREFIX}/cpo/{version.value}",
tags=[f"CPO {version.value}"],
)
endpoint = ENDPOINTS[version][RoleEnum.cpo].get(module) # type: ignore[index]
if endpoint:
version_endpoints[version].append(endpoint)
cpo_modules_with_router.add(module)
for endpoint in ENDPOINTS[version][RoleEnum.cpo]: # type: ignore[index]
if endpoint.identifier in cpo_modules_with_router:
version_endpoints[version].append(endpoint)

if RoleEnum.emsp in roles:
emsp_modules_with_router: set[ModuleID] = set()
for module in modules:
emsp_router = mapped_version["emsp_router"].get(module) # type: ignore[attr-defined]
if emsp_router:
Expand All @@ -246,9 +249,10 @@ def get_application(
prefix=f"/{settings.OCPI_PREFIX}/emsp/{version.value}",
tags=[f"EMSP {version.value}"],
)
endpoint = ENDPOINTS[version][RoleEnum.emsp].get(module) # type: ignore[index]
if endpoint:
version_endpoints[version].append(endpoint)
emsp_modules_with_router.add(module)
for endpoint in ENDPOINTS[version][RoleEnum.emsp]: # type: ignore[index]
if endpoint.identifier in emsp_modules_with_router:
version_endpoints[version].append(endpoint)

if RoleEnum.ptp in roles:
for module in modules:
Expand Down
Loading
Loading