Skip to content

Added Abstract Endpoint#8

Closed
isivaselvan wants to merge 6 commits into
mainfrom
Sivaselvan32/AbsEndpoint
Closed

Added Abstract Endpoint#8
isivaselvan wants to merge 6 commits into
mainfrom
Sivaselvan32/AbsEndpoint

Conversation

@isivaselvan

Copy link
Copy Markdown
Collaborator

Description

Added Abstract Endpoint

Testing plan

Unit tests for endpoint added

External links

Output from tests

Including output from tests may require access to a TFE instance. Ignore this section if you have no environment to test against.

Rollback Plan

Changes to Security Controls

PCI review checklist

  • I have documented a clear reason for, and description of, the change I am making.

  • If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.

  • If applicable, I've documented the impact of any changes to security controls.

    Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.

If you have any questions, please contact your direct supervisor, GRC (#team-grc), or the PCI working group (#proj-pci-reboot). You can also find more information at PCI Compliance.

@isivaselvan isivaselvan requested a review from a team as a code owner August 29, 2025 06:54
@isivaselvan isivaselvan requested a review from Copilot August 29, 2025 06:54

This comment was marked as outdated.

@taru-garg-2000 taru-garg-2000 requested a review from Copilot August 29, 2025 10:22

This comment was marked as outdated.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Comment thread tfe/endpoint.py Outdated
T = TypeVar("T", bound="Response")


class Response(ABC):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to fully implement this? Currently there is no implementation or any sort of relation with endpoint class.

Comment thread tfe/endpoint.py
def __init__(self, client: Session) -> None:
self._http_client = client

def _make_request(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we checking for response codes / raising any errors for those HTTP errors?

), # Test case insensitive
],
)
def test_make_request_all_methods(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again no test cases for error codes

This comment was marked as outdated.

@isivaselvan isivaselvan requested a review from Copilot September 2, 2025 10:24

This comment was marked as outdated.

Comment thread tests/units/test_endpoint.py Outdated
Comment on lines +93 to +142
def test_get_method(self, endpoint, mock_response):
"""Test the _get convenience method."""
endpoint._http_client.get.return_value = mock_response

response = endpoint._get("/test/path")

endpoint._http_client.get.assert_called_once_with("/test/path")
assert response == mock_response

def test_post_method(self, endpoint, mock_response):
"""Test the _post convenience method."""
endpoint._http_client.post.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._post("/test/path", test_data)

endpoint._http_client.post.assert_called_once_with("/test/path", json=test_data)
assert response == mock_response

def test_put_method(self, endpoint, mock_response):
"""Test the _put convenience method."""
endpoint._http_client.put.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._put("/test/path", test_data)

endpoint._http_client.put.assert_called_once_with("/test/path", json=test_data)
assert response == mock_response

def test_patch_method(self, endpoint, mock_response):
"""Test the _patch convenience method."""
endpoint._http_client.patch.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._patch("/test/path", test_data)

endpoint._http_client.patch.assert_called_once_with(
"/test/path", json=test_data
)
assert response == mock_response

def test_delete_method(self, endpoint, mock_response):
"""Test the _delete convenience method."""
endpoint._http_client.delete.return_value = mock_response

response = endpoint._delete("/test/path")

endpoint._http_client.delete.assert_called_once_with("/test/path")
assert response == mock_response

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests look redundant as we are testing this above as well ?

Comment thread tfe/endpoint.py Outdated
Comment on lines +36 to +143
def _handle_connection_error(
self, method: str, path: str, error: Exception
) -> NoReturn:
"""Handle connection-related errors."""
logger.error(
"Connection error while making %s request to %s: %s", method, path, error
)
raise TFEConnectionException(
message="Failed to connect to TFE API",
method=method,
path=path,
cause=error,
)

def _handle_timeout_error(
self, method: str, path: str, error: Exception
) -> NoReturn:
"""Handle timeout errors."""
logger.error(
"Timeout error while making %s request to %s: %s", method, path, error
)
raise TFETimeoutException(
message="Request timed out", method=method, path=path, cause=error
)

def _handle_http_error(
self, method: str, path: str, error: exceptions.HTTPError
) -> NoReturn:
"""Handle HTTP errors with specific status codes."""
status_code = error.response.status_code if error.response else None
error_data = self._extract_error_data(error.response)

logger.error(
"HTTP error while making %s request to %s: %s (Status: %s)",
method,
path,
error,
status_code,
)

# Map status code to specific exception
STATUS_CODE_MAPPING: dict[int, tuple[type[TFEEndpointException], str]] = {
401: (
TFEUnauthorizedException,
"Authentication failed - invalid or missing token",
),
403: (TFEForbiddenException, "Access forbidden - insufficient permissions"),
404: (TFENotFoundException, "Resource not found"),
422: (TFEValidationException, "Validation failed"),
}

# Handle 5xx server errors
if status_code and 500 <= status_code < 600:
exception_class: type[TFEEndpointException] = TFEServerException
message = "TFE server error"
else:
# Get exception class and message from mapping, or use default
if status_code is not None:
exception_class, message = STATUS_CODE_MAPPING.get(
status_code, (TFEEndpointException, "HTTP error occurred")
)
else:
exception_class, message = TFEEndpointException, "HTTP error occurred"

# Special handling for validation errors (422)
if status_code == 422:
parsed_errors = (
self.parse_tfe_error_response(error_data) if error_data else {}
)
message = parsed_errors.get("message", message)

raise exception_class(
message=message,
status_code=status_code,
error_data=error_data,
method=method,
path=path,
cause=error,
)

def _handle_request_error(
self, method: str, path: str, error: Exception
) -> NoReturn:
"""Handle general request errors."""
logger.error(
"Request error while making %s request to %s: %s", method, path, error
)
raise TFEEndpointException(
message=f"Request failed: {str(error)}",
method=method,
path=path,
cause=error,
)

def _handle_unexpected_error(
self, method: str, path: str, error: Exception
) -> NoReturn:
"""Handle unexpected errors."""
logger.error(
"Unexpected error while making %s request to %s: %s", method, path, error
)
raise TFEEndpointException(
message=f"Unexpected error occurred during {method} request",
method=method,
path=path,
cause=error,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we have dedicated classes for each error type then this should be implemented by that class itself and not in the endpoint class

@isivaselvan isivaselvan requested a review from Copilot September 3, 2025 12:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces abstract endpoint functionality to the Terraform Enterprise Python client by adding a new exception hierarchy, error handling utilities, and a base endpoint class.

  • Added comprehensive exception classes for different HTTP error types
  • Implemented error parsing utilities for TFE API responses
  • Created base endpoint class with common HTTP request functionality

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tfe/exception.py Defines exception hierarchy with TFEEndpointException base class and specific error types
tfe/error_utils.py Provides utilities for parsing TFE error responses and handling HTTP errors
tfe/endpoint.py Implements base Endpoint class with HTTP request handling and error management
tfe/init.py Removes existing client imports (entire file deleted)
tests/units/test_exception.py Unit tests for exception classes and error message formatting
tests/units/test_error_utils.py Unit tests for error parsing and HTTP error handling functions
tests/units/test_endpoint.py Unit tests for endpoint HTTP methods and error scenarios
Comments suppressed due to low confidence (1)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread tfe/endpoint.py
@iam404

iam404 commented Sep 5, 2025

Copy link
Copy Markdown
Member
  1. Name 'Endpoint' is vague. This class is a transport wrapper over HTTP. Rename to HTTPTransport (or _HTTPTransport ).
  2. Default headers of TFE are missing. Example: User-Agent: python-tfe/sdk-version or similar if TFE have any guidelines.
  3. Transport layer should also cover HTTP2, TLS, proxies, redirects ...etc

@taru-garg-hashicorp Retries / rate limiting / backoff / timeouts will be part of same transport wrapper right ?

@taru-garg-2000

Copy link
Copy Markdown
Contributor
  1. Name 'Endpoint' is vague. This class is a transport wrapper over HTTP. Rename to HTTPTransport (or _HTTPTransport ).
  2. Default headers of TFE are missing. Example: User-Agent: python-tfe/sdk or similar if TFE have any guidelines.
  3. Transport layer should also cover HTTP2, TLS, proxies, redirects ...etc

@taru-garg-hashicorp Retries / rate limiting / backoff / timeouts will be part of same transport wrapper right ?

Retries, Rate limiting, will be a part of the request session, which is held by the client. While it will be passed to the Endpoint abstract, endpoint does not own those parameters

@iam404 iam404 closed this Sep 15, 2025
@iam404 iam404 deleted the Sivaselvan32/AbsEndpoint branch October 28, 2025 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants