Loading proto/pathcompextended.proto +11 −17 Original line number Diff line number Diff line Loading @@ -23,22 +23,23 @@ import "google/protobuf/empty.proto"; service PathCompExtendedService { // Health checks rpc HealthCheck (google.protobuf.Empty) returns (LivenessProbe); rpc Reset (google.protobuf.Empty) returns (GenericMessage); // Network Context rpc CreateNetworkContext (NetworkContext) returns (google.protobuf.Empty); rpc DeleteNetworkContext (UUID) returns (google.protobuf.Empty); rpc CreateNetworkContext (NetworkContext) returns (GenericMessage); rpc DeleteNetworkContext (UUID) returns (GenericMessage); rpc GetNetworkContext (google.protobuf.Empty) returns (NetworkContext); rpc GetSpecificNetworkContext (UUID) returns (NetworkTopology); // Transport Optical Slice rpc CreateTransportOpticalSlice (IetfNetworkSlice) returns (TransportOpticalSlice); rpc DeleteTransportOpticalSlice (UUID) returns (google.protobuf.Empty); rpc DeleteTransportOpticalSlice (UUID) returns (GenericMessage); rpc GetTransportOpticalSlices (google.protobuf.Empty) returns (stream TransportOpticalSlice); rpc GetTransportOpticalSlice (UUID) returns (TransportOpticalSlice); // Transport Network Slice L3 rpc CreateTransportNetworkSliceL3 (IetfNetworkSlice) returns (TransportNetworkSliceL3); rpc DeleteTransportNetworkSliceL3 (UUID) returns (google.protobuf.Empty); rpc DeleteTransportNetworkSliceL3 (UUID) returns (GenericMessage); rpc GetTransportNetworkSlicesL3 (google.protobuf.Empty) returns (stream TransportNetworkSliceL3); rpc GetTransportNetworkSliceL3 (UUID) returns (TransportNetworkSliceL3); Loading @@ -47,19 +48,7 @@ service PathCompExtendedService { // Network Context Operations // Transport Optical Slice operations // Transport network Slice L3 operations // Datamodel definitions // Message definitions // Network Context Loading Loading @@ -103,3 +92,8 @@ message UUID { string value = 1; } message GenericMessage { string message = 1; bool ifTrueIsJsonStringified = 2; } src/nbi/service/pathcompextended/Resources.py +45 −15 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ from pathcompextended.client.PathCompExtendedClient import PathCompExtendedClien from common.proto.context_pb2 import Empty from common.proto.pathcompextended_pb2 import ( IetfNetworkSlice, LivenessProbe, NetworkContext, NetworkTopology, TransportNetworkSliceL3, TransportOpticalSlice, UUID TransportNetworkSliceL3, TransportOpticalSlice, UUID, GenericMessage ) from common.tools.grpc.Tools import grpc_message_to_json_string Loading @@ -38,6 +38,34 @@ class Index(_Resource): LOGGER.info("Operation GET /pathcompextended/") return {'status': 'PathCompExtended service is available'} # ---------------------------------------------------------------------------------- # # -- Health ------------------------------------------------------------------------ # # # # ---------------------------------------------------------------------------------- # class Health(_Resource): def get(self): LOGGER.info("Operation GET /health") try: probe = self.pathcompextended_client.HealthCheck(None) LOGGER.debug(grpc_message_to_json_string(probe)) output = json.loads(grpc_message_to_json_string(probe)) return jsonify(output) except Exception as e: LOGGER.error(f"Error checking health: {e}", exc_info=True) return jsonify({'error': str(e), 'status': 'unhealthy'}), 500 class Reset(_Resource): def post(self): LOGGER.info("Operation POST /reset") # TODO: implement # ---------------------------------------------------------------------------------- # # -- NetworkContext ---------------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class NetworkContext(_Resource): def get(self): Loading Loading @@ -72,7 +100,12 @@ class NetworkContext(_Resource): result = self.pathcompextended_client.CreateNetworkContext(net_context_msg) LOGGER.debug(grpc_message_to_json_string(result)) if result.ifTrueIsJsonStringified: output = json.loads(grpc_message_to_json_string(result.message)) else: output = json.loads(grpc_message_to_json_string(result)) return jsonify(output), 201 except Exception as e: LOGGER.error(f"Error creating network context: {e}", exc_info=True) Loading Loading @@ -106,19 +139,10 @@ class NetworkContextDetails(_Resource): LOGGER.error(f"Error deleting network context {id}: {e}", exc_info=True) return jsonify({'error': str(e)}), 500 class Health(_Resource): def get(self): LOGGER.info("Operation GET /health") try: probe = self.pathcompextended_client.HealthCheck(None) LOGGER.debug(grpc_message_to_json_string(probe)) output = json.loads(grpc_message_to_json_string(probe)) return jsonify(output) except Exception as e: LOGGER.error(f"Error checking health: {e}", exc_info=True) return jsonify({'error': str(e), 'status': 'unhealthy'}), 500 # ---------------------------------------------------------------------------------- # # -- TransportOpticalSlice --------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class TransportOpticalSlice(_Resource): def get(self): Loading Loading @@ -158,6 +182,7 @@ class TransportOpticalSlice(_Resource): return jsonify({'error': str(e)}), 500 class TransportOpticalSliceDetails(_Resource): def get(self, id: str): LOGGER.info(f"Operation GET /transport-optical-slice/{id}") Loading Loading @@ -185,6 +210,10 @@ class TransportOpticalSliceDetails(_Resource): LOGGER.error(f"Error deleting transport optical slice {id}: {e}", exc_info=True) return jsonify({'error': str(e)}), 500 # ---------------------------------------------------------------------------------- # # -- TransportNetworkSliceL3 ------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class TransportNetworkSliceL3(_Resource): def get(self): Loading Loading @@ -224,6 +253,7 @@ class TransportNetworkSliceL3(_Resource): return jsonify({'error': str(e)}), 500 class TransportNetworkSliceL3Details(_Resource): def get(self, id: str): LOGGER.info(f"Operation GET /transport-network-slice-l3/{id}") Loading src/pathcompextended/client/PathCompExtendedClient.py +14 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty from common.proto.pathcompextended_pb2 import ( IetfNetworkSlice, LivenessProbe, NetworkContext, NetworkTopology, TransportNetworkSliceL3, TransportOpticalSlice, UUID TransportNetworkSliceL3, TransportOpticalSlice, UUID, GenericMessage ) from common.proto.pathcompextended_pb2_grpc import PathCompExtendedServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential Loading Loading @@ -64,19 +64,28 @@ class PathCompExtendedClient: LOGGER.debug('HealthCheck result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def Reset(self, request: Empty = None) -> GenericMessage: if request is None: request = Empty() LOGGER.debug('Reset request') response = self.stub.Reset(request) LOGGER.debug('Reset result: {:s}'.format(grpc_message_to_json_string(response))) return response # ------------------------------------------------------------------------ # # -- Network Context Operations ------------------------------------------ # # ------------------------------------------------------------------------ # @RETRY_DECORATOR def CreateNetworkContext(self, request: NetworkContext) -> Empty: def CreateNetworkContext(self, request: NetworkContext) -> GenericMessage: LOGGER.debug('CreateNetworkContext request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.CreateNetworkContext(request) LOGGER.debug('CreateNetworkContext result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def DeleteNetworkContext(self, request: UUID) -> Empty: def DeleteNetworkContext(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteNetworkContext request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteNetworkContext(request) LOGGER.debug('DeleteNetworkContext result: {:s}'.format(grpc_message_to_json_string(response))) Loading Loading @@ -110,7 +119,7 @@ class PathCompExtendedClient: return response @RETRY_DECORATOR def DeleteTransportOpticalSlice(self, request: UUID) -> Empty: def DeleteTransportOpticalSlice(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteTransportOpticalSlice request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteTransportOpticalSlice(request) LOGGER.debug('DeleteTransportOpticalSlice result: {:s}'.format(grpc_message_to_json_string(response))) Loading Loading @@ -144,7 +153,7 @@ class PathCompExtendedClient: return response @RETRY_DECORATOR def DeleteTransportNetworkSliceL3(self, request: UUID) -> Empty: def DeleteTransportNetworkSliceL3(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteTransportNetworkSliceL3 request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteTransportNetworkSliceL3(request) LOGGER.debug('DeleteTransportNetworkSliceL3 result: {:s}'.format(grpc_message_to_json_string(response))) Loading src/pathcompextended/connector/hrat.py +9 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from typing import Dict, List, Optional, Any from pydantic import BaseModel, Field from pathcompextended.connector.api_client import APIClient from pathcompextended.connector.imp_general import GeneralMethods import pathcompextended.Config as Config # Import base models Loading Loading @@ -230,13 +231,17 @@ class HRAT: base_api_url = config.get('HRAT_BASEURL', Config.HRAT_BASEURL) self.base_api_url = base_api_url # Expose a general endpoints helper that can be reused by higher layers # (e.g., gRPC servicer) without having to recreate HTTP methods. self.general_endpoints_api = GeneralMethods(self.api, self.base_api_url) LOGGER.debug("HRAT initialized with url: {:s}".format(url)) # ======================================================================== # NETWORK CONTEXT METHODS # ======================================================================== def network_context(self, request: NetworkContextPostRequest) -> NetworkContextPostResponse: def post_network_context(self, request: NetworkContextPostRequest) -> NetworkContextPostResponse: """ Send network context to H-RAT. Loading Loading @@ -329,7 +334,7 @@ class HRAT: # TRANSPORT OPTICAL SLICE METHODS # ======================================================================== def transport_optical_slice(self, request: TransportOpticalSlicePostRequest) -> TransportOpticalSlicePostResponse: def post_transport_optical_slice(self, request: TransportOpticalSlicePostRequest) -> TransportOpticalSlicePostResponse: """ Request transport optical slice computation from H-RAT. Loading Loading @@ -444,7 +449,7 @@ class HRAT: # TRANSPORT NETWORK SLICE L3 METHODS # ======================================================================== def transport_network_slice_l3(self, request: TransportNetworkSliceL3PostRequest) -> TransportNetworkSliceL3PostResponse: def post_transport_network_slice_l3(self, request: TransportNetworkSliceL3PostRequest) -> TransportNetworkSliceL3PostResponse: """ Request transport network slice L3 computation from H-RAT. Loading Loading @@ -585,7 +590,7 @@ class HRAT: # EXTERNAL KPIs METHODS # ======================================================================== def external_kpis(self, kpis: Dict[str, Any]) -> ExternalKpisPostResponse: def post_external_kpis(self, kpis: Dict[str, Any]) -> ExternalKpisPostResponse: """ Send external KPIs to H-RAT. Loading src/pathcompextended/connector/imp_general.py 0 → 100644 +34 −0 Original line number Diff line number Diff line from typing import Any, Dict, Optional from pathcompextended.connector.api_client import APIClient class GeneralMethods: """ Helper for performing generic HTTP operations against the H-RAT REST API. This class is intentionally self-contained and does not import the HRAT wrapper itself to avoid circular dependencies. It can be reused by resource-specific connector classes. """ def __init__(self, api_client: APIClient, base_api_url: str) -> None: self._api_client = api_client self._base_api_url = base_api_url def _build_endpoint(self, relative_path: str) -> str: relative_path = relative_path.lstrip("/") return f"{self._base_api_url}/{relative_path}" def get(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.get(endpoint, body=body) def post(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.post(endpoint, body=body or {}) def delete(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.delete(endpoint, body=body) No newline at end of file Loading
proto/pathcompextended.proto +11 −17 Original line number Diff line number Diff line Loading @@ -23,22 +23,23 @@ import "google/protobuf/empty.proto"; service PathCompExtendedService { // Health checks rpc HealthCheck (google.protobuf.Empty) returns (LivenessProbe); rpc Reset (google.protobuf.Empty) returns (GenericMessage); // Network Context rpc CreateNetworkContext (NetworkContext) returns (google.protobuf.Empty); rpc DeleteNetworkContext (UUID) returns (google.protobuf.Empty); rpc CreateNetworkContext (NetworkContext) returns (GenericMessage); rpc DeleteNetworkContext (UUID) returns (GenericMessage); rpc GetNetworkContext (google.protobuf.Empty) returns (NetworkContext); rpc GetSpecificNetworkContext (UUID) returns (NetworkTopology); // Transport Optical Slice rpc CreateTransportOpticalSlice (IetfNetworkSlice) returns (TransportOpticalSlice); rpc DeleteTransportOpticalSlice (UUID) returns (google.protobuf.Empty); rpc DeleteTransportOpticalSlice (UUID) returns (GenericMessage); rpc GetTransportOpticalSlices (google.protobuf.Empty) returns (stream TransportOpticalSlice); rpc GetTransportOpticalSlice (UUID) returns (TransportOpticalSlice); // Transport Network Slice L3 rpc CreateTransportNetworkSliceL3 (IetfNetworkSlice) returns (TransportNetworkSliceL3); rpc DeleteTransportNetworkSliceL3 (UUID) returns (google.protobuf.Empty); rpc DeleteTransportNetworkSliceL3 (UUID) returns (GenericMessage); rpc GetTransportNetworkSlicesL3 (google.protobuf.Empty) returns (stream TransportNetworkSliceL3); rpc GetTransportNetworkSliceL3 (UUID) returns (TransportNetworkSliceL3); Loading @@ -47,19 +48,7 @@ service PathCompExtendedService { // Network Context Operations // Transport Optical Slice operations // Transport network Slice L3 operations // Datamodel definitions // Message definitions // Network Context Loading Loading @@ -103,3 +92,8 @@ message UUID { string value = 1; } message GenericMessage { string message = 1; bool ifTrueIsJsonStringified = 2; }
src/nbi/service/pathcompextended/Resources.py +45 −15 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ from pathcompextended.client.PathCompExtendedClient import PathCompExtendedClien from common.proto.context_pb2 import Empty from common.proto.pathcompextended_pb2 import ( IetfNetworkSlice, LivenessProbe, NetworkContext, NetworkTopology, TransportNetworkSliceL3, TransportOpticalSlice, UUID TransportNetworkSliceL3, TransportOpticalSlice, UUID, GenericMessage ) from common.tools.grpc.Tools import grpc_message_to_json_string Loading @@ -38,6 +38,34 @@ class Index(_Resource): LOGGER.info("Operation GET /pathcompextended/") return {'status': 'PathCompExtended service is available'} # ---------------------------------------------------------------------------------- # # -- Health ------------------------------------------------------------------------ # # # # ---------------------------------------------------------------------------------- # class Health(_Resource): def get(self): LOGGER.info("Operation GET /health") try: probe = self.pathcompextended_client.HealthCheck(None) LOGGER.debug(grpc_message_to_json_string(probe)) output = json.loads(grpc_message_to_json_string(probe)) return jsonify(output) except Exception as e: LOGGER.error(f"Error checking health: {e}", exc_info=True) return jsonify({'error': str(e), 'status': 'unhealthy'}), 500 class Reset(_Resource): def post(self): LOGGER.info("Operation POST /reset") # TODO: implement # ---------------------------------------------------------------------------------- # # -- NetworkContext ---------------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class NetworkContext(_Resource): def get(self): Loading Loading @@ -72,7 +100,12 @@ class NetworkContext(_Resource): result = self.pathcompextended_client.CreateNetworkContext(net_context_msg) LOGGER.debug(grpc_message_to_json_string(result)) if result.ifTrueIsJsonStringified: output = json.loads(grpc_message_to_json_string(result.message)) else: output = json.loads(grpc_message_to_json_string(result)) return jsonify(output), 201 except Exception as e: LOGGER.error(f"Error creating network context: {e}", exc_info=True) Loading Loading @@ -106,19 +139,10 @@ class NetworkContextDetails(_Resource): LOGGER.error(f"Error deleting network context {id}: {e}", exc_info=True) return jsonify({'error': str(e)}), 500 class Health(_Resource): def get(self): LOGGER.info("Operation GET /health") try: probe = self.pathcompextended_client.HealthCheck(None) LOGGER.debug(grpc_message_to_json_string(probe)) output = json.loads(grpc_message_to_json_string(probe)) return jsonify(output) except Exception as e: LOGGER.error(f"Error checking health: {e}", exc_info=True) return jsonify({'error': str(e), 'status': 'unhealthy'}), 500 # ---------------------------------------------------------------------------------- # # -- TransportOpticalSlice --------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class TransportOpticalSlice(_Resource): def get(self): Loading Loading @@ -158,6 +182,7 @@ class TransportOpticalSlice(_Resource): return jsonify({'error': str(e)}), 500 class TransportOpticalSliceDetails(_Resource): def get(self, id: str): LOGGER.info(f"Operation GET /transport-optical-slice/{id}") Loading Loading @@ -185,6 +210,10 @@ class TransportOpticalSliceDetails(_Resource): LOGGER.error(f"Error deleting transport optical slice {id}: {e}", exc_info=True) return jsonify({'error': str(e)}), 500 # ---------------------------------------------------------------------------------- # # -- TransportNetworkSliceL3 ------------------------------------------------------- # # # # ---------------------------------------------------------------------------------- # class TransportNetworkSliceL3(_Resource): def get(self): Loading Loading @@ -224,6 +253,7 @@ class TransportNetworkSliceL3(_Resource): return jsonify({'error': str(e)}), 500 class TransportNetworkSliceL3Details(_Resource): def get(self, id: str): LOGGER.info(f"Operation GET /transport-network-slice-l3/{id}") Loading
src/pathcompextended/client/PathCompExtendedClient.py +14 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty from common.proto.pathcompextended_pb2 import ( IetfNetworkSlice, LivenessProbe, NetworkContext, NetworkTopology, TransportNetworkSliceL3, TransportOpticalSlice, UUID TransportNetworkSliceL3, TransportOpticalSlice, UUID, GenericMessage ) from common.proto.pathcompextended_pb2_grpc import PathCompExtendedServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential Loading Loading @@ -64,19 +64,28 @@ class PathCompExtendedClient: LOGGER.debug('HealthCheck result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def Reset(self, request: Empty = None) -> GenericMessage: if request is None: request = Empty() LOGGER.debug('Reset request') response = self.stub.Reset(request) LOGGER.debug('Reset result: {:s}'.format(grpc_message_to_json_string(response))) return response # ------------------------------------------------------------------------ # # -- Network Context Operations ------------------------------------------ # # ------------------------------------------------------------------------ # @RETRY_DECORATOR def CreateNetworkContext(self, request: NetworkContext) -> Empty: def CreateNetworkContext(self, request: NetworkContext) -> GenericMessage: LOGGER.debug('CreateNetworkContext request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.CreateNetworkContext(request) LOGGER.debug('CreateNetworkContext result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR def DeleteNetworkContext(self, request: UUID) -> Empty: def DeleteNetworkContext(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteNetworkContext request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteNetworkContext(request) LOGGER.debug('DeleteNetworkContext result: {:s}'.format(grpc_message_to_json_string(response))) Loading Loading @@ -110,7 +119,7 @@ class PathCompExtendedClient: return response @RETRY_DECORATOR def DeleteTransportOpticalSlice(self, request: UUID) -> Empty: def DeleteTransportOpticalSlice(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteTransportOpticalSlice request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteTransportOpticalSlice(request) LOGGER.debug('DeleteTransportOpticalSlice result: {:s}'.format(grpc_message_to_json_string(response))) Loading Loading @@ -144,7 +153,7 @@ class PathCompExtendedClient: return response @RETRY_DECORATOR def DeleteTransportNetworkSliceL3(self, request: UUID) -> Empty: def DeleteTransportNetworkSliceL3(self, request: UUID) -> GenericMessage: LOGGER.debug('DeleteTransportNetworkSliceL3 request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.DeleteTransportNetworkSliceL3(request) LOGGER.debug('DeleteTransportNetworkSliceL3 result: {:s}'.format(grpc_message_to_json_string(response))) Loading
src/pathcompextended/connector/hrat.py +9 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from typing import Dict, List, Optional, Any from pydantic import BaseModel, Field from pathcompextended.connector.api_client import APIClient from pathcompextended.connector.imp_general import GeneralMethods import pathcompextended.Config as Config # Import base models Loading Loading @@ -230,13 +231,17 @@ class HRAT: base_api_url = config.get('HRAT_BASEURL', Config.HRAT_BASEURL) self.base_api_url = base_api_url # Expose a general endpoints helper that can be reused by higher layers # (e.g., gRPC servicer) without having to recreate HTTP methods. self.general_endpoints_api = GeneralMethods(self.api, self.base_api_url) LOGGER.debug("HRAT initialized with url: {:s}".format(url)) # ======================================================================== # NETWORK CONTEXT METHODS # ======================================================================== def network_context(self, request: NetworkContextPostRequest) -> NetworkContextPostResponse: def post_network_context(self, request: NetworkContextPostRequest) -> NetworkContextPostResponse: """ Send network context to H-RAT. Loading Loading @@ -329,7 +334,7 @@ class HRAT: # TRANSPORT OPTICAL SLICE METHODS # ======================================================================== def transport_optical_slice(self, request: TransportOpticalSlicePostRequest) -> TransportOpticalSlicePostResponse: def post_transport_optical_slice(self, request: TransportOpticalSlicePostRequest) -> TransportOpticalSlicePostResponse: """ Request transport optical slice computation from H-RAT. Loading Loading @@ -444,7 +449,7 @@ class HRAT: # TRANSPORT NETWORK SLICE L3 METHODS # ======================================================================== def transport_network_slice_l3(self, request: TransportNetworkSliceL3PostRequest) -> TransportNetworkSliceL3PostResponse: def post_transport_network_slice_l3(self, request: TransportNetworkSliceL3PostRequest) -> TransportNetworkSliceL3PostResponse: """ Request transport network slice L3 computation from H-RAT. Loading Loading @@ -585,7 +590,7 @@ class HRAT: # EXTERNAL KPIs METHODS # ======================================================================== def external_kpis(self, kpis: Dict[str, Any]) -> ExternalKpisPostResponse: def post_external_kpis(self, kpis: Dict[str, Any]) -> ExternalKpisPostResponse: """ Send external KPIs to H-RAT. Loading
src/pathcompextended/connector/imp_general.py 0 → 100644 +34 −0 Original line number Diff line number Diff line from typing import Any, Dict, Optional from pathcompextended.connector.api_client import APIClient class GeneralMethods: """ Helper for performing generic HTTP operations against the H-RAT REST API. This class is intentionally self-contained and does not import the HRAT wrapper itself to avoid circular dependencies. It can be reused by resource-specific connector classes. """ def __init__(self, api_client: APIClient, base_api_url: str) -> None: self._api_client = api_client self._base_api_url = base_api_url def _build_endpoint(self, relative_path: str) -> str: relative_path = relative_path.lstrip("/") return f"{self._base_api_url}/{relative_path}" def get(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.get(endpoint, body=body) def post(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.post(endpoint, body=body or {}) def delete(self, relative_path: str, body: Optional[Dict[str, Any]] = None): endpoint = self._build_endpoint(relative_path) return self._api_client.delete(endpoint, body=body) No newline at end of file