Skip to content
Open
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
6 changes: 6 additions & 0 deletions backend_py/primary/primary/routers/well/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def convert_wellbore_completion_to_schema(
wellbore_completion: WellboreCompletion,
) -> schemas.WellboreCompletion:
return schemas.WellboreCompletion(
wellboreUuid=wellbore_completion.wellbore_uuid,
uniqueWellboreIdentifier=wellbore_completion.wellbore_id,
mdTop=wellbore_completion.md_top,
mdBottom=wellbore_completion.md_bottom,
tvdTop=wellbore_completion.tvd_top,
Expand All @@ -98,6 +100,8 @@ def convert_wellbore_casing_to_schema(
wellbore_casing: WellboreCasing,
) -> schemas.WellboreCasing:
return schemas.WellboreCasing(
wellboreUuid=wellbore_casing.wellbore_uuid,
uniqueWellboreIdentifier=wellbore_casing.wellbore_id,
itemType=wellbore_casing.item_type,
diameterNumeric=wellbore_casing.diameter_numeric,
diameterInner=wellbore_casing.diameter_inner,
Expand All @@ -115,6 +119,8 @@ def convert_wellbore_perforation_to_schema(
wellbore_perforation: WellborePerforation,
) -> schemas.WellborePerforation:
return schemas.WellborePerforation(
wellboreUuid=wellbore_perforation.wellbore_uuid,
uniqueWellboreIdentifier=wellbore_perforation.wellbore_id,
mdTop=wellbore_perforation.md_top,
mdBottom=wellbore_perforation.md_bottom,
tvdTop=wellbore_perforation.tvd_top,
Expand Down
31 changes: 15 additions & 16 deletions backend_py/primary/primary/routers/well/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,18 @@ async def get_wellbore_stratigraphic_columns(
async def get_wellbore_completions(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
wellbore_uuid: str = Query(description="Wellbore uuid"),
wellbore_uuids: List[str] = Query(description="List of wellbore uuids"),
# fmt:on
) -> List[schemas.WellboreCompletion]:
"""Get well bore completions for a single well bore"""
"""Get wellbore completions"""

# Handle DROGON
if is_drogon_identifier(wellbore_uuid=wellbore_uuid):
# Handle DROGON (if any of the wellbores are from drogon, return empty list)
if any(is_drogon_identifier(wellbore_uuid=uuid) for uuid in wellbore_uuids):
return []

well_access = SsdlWellAccess(authenticated_user.get_ssdl_access_token())

wellbore_completions = await well_access.get_completions_for_wellbore_async(wellbore_uuid=wellbore_uuid)
wellbore_completions = await well_access.get_completions_for_wellbores_async(wellbore_uuids=wellbore_uuids)
return [
converters.convert_wellbore_completion_to_schema(wellbore_completion)
for wellbore_completion in wellbore_completions
Expand All @@ -215,18 +215,18 @@ async def get_wellbore_completions(
async def get_wellbore_casings(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
wellbore_uuid: str = Query(description="Wellbore uuid"),
wellbore_uuids: List[str] = Query(description="List of wellbore uuids"),
# fmt:on
) -> List[schemas.WellboreCasing]:
"""Get well bore casings for a single well bore"""
"""Get wellbore casings"""

# Handle DROGON
if is_drogon_identifier(wellbore_uuid=wellbore_uuid):
# Handle DROGON (if any of the wellbores are from drogon, return empty list)
if any(is_drogon_identifier(wellbore_uuid=uuid) for uuid in wellbore_uuids):
return []

well_access = SsdlWellAccess(authenticated_user.get_ssdl_access_token())

wellbore_casings = await well_access.get_casings_for_wellbore_async(wellbore_uuid=wellbore_uuid)
wellbore_casings = await well_access.get_casings_for_wellbores_async(wellbore_uuids=wellbore_uuids)

return [converters.convert_wellbore_casing_to_schema(wellbore_casing) for wellbore_casing in wellbore_casings]

Expand All @@ -235,19 +235,18 @@ async def get_wellbore_casings(
async def get_wellbore_perforations(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
wellbore_uuid: str = Query(description="Wellbore uuid"),
wellbore_uuids: List[str] = Query(description="List of wellbore uuids"),
# fmt:on
) -> List[schemas.WellborePerforation]:
"""Get well bore casing for a single well bore"""
"""Get wellbore perforations"""

# Handle DROGON
if is_drogon_identifier(wellbore_uuid=wellbore_uuid):
# Handle DROGON (if any of the wellbores are from drogon, return empty list)
if any(is_drogon_identifier(wellbore_uuid=uuid) for uuid in wellbore_uuids):
return []

well_access = SsdlWellAccess(authenticated_user.get_ssdl_access_token())

wellbore_perforations = await well_access.get_perforations_for_wellbore_async(wellbore_uuid=wellbore_uuid)

wellbore_perforations = await well_access.get_perforations_for_wellbores_async(wellbore_uuids=wellbore_uuids)
return [
converters.convert_wellbore_perforation_to_schema(wellbore_perforation)
for wellbore_perforation in wellbore_perforations
Expand Down
6 changes: 6 additions & 0 deletions backend_py/primary/primary/routers/well/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class WellborePick(BaseModel):


class WellboreCompletion(BaseModel):
wellboreUuid: str
uniqueWellboreIdentifier: str
mdTop: float
mdBottom: float
tvdTop: float | None
Expand All @@ -70,6 +72,8 @@ class WellboreCompletion(BaseModel):


class WellboreCasing(BaseModel):
wellboreUuid: str
uniqueWellboreIdentifier: str
itemType: str # Casing type
diameterNumeric: float
diameterInner: float
Expand All @@ -83,6 +87,8 @@ class WellboreCasing(BaseModel):


class WellborePerforation(BaseModel):
wellboreUuid: str
uniqueWellboreIdentifier: str
mdTop: float
mdBottom: float
tvdTop: float
Expand Down

This file was deleted.

107 changes: 107 additions & 0 deletions backend_py/primary/primary/services/ssdl_access/_ssdl_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import logging
from typing import List, Optional

from webviz_pkg.core_utils.perf_timer import PerfTimer

from primary import config
from primary.services.utils.httpx_async_client_wrapper import HTTPX_ASYNC_CLIENT_WRAPPER
from primary.services.service_exceptions import (
Service,
InvalidDataError,
InvalidParameterError,
AuthorizationError,
)

LOGGER = logging.getLogger(__name__)


async def ssdl_get_request_async(access_token: str, endpoint: str, params: Optional[dict] = None) -> List[dict]:
"""Convenience function for GET requests. Always returns a list of dictionaries."""
result = await _ssdl_request_async(access_token, endpoint, method="GET", params=params)
# GET requests always return List[dict], so we can safely cast
return result if isinstance(result, list) else [result]


async def ssdl_post_request_async(
access_token: str,
endpoint: str,
data: Optional[List[str]] = None,
params: Optional[dict] = None,
) -> dict:
"""Convenience function for POST requests. Always returns a single dictionary."""
result = await _ssdl_request_async(access_token, endpoint, method="POST", data=data, params=params)
# POST requests always return dict, so we can safely cast
return result if isinstance(result, dict) else result[0]
Comment on lines +33 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're not casting here? Is this comment outdated?



async def _ssdl_request_async(
access_token: str,
endpoint: str,
method: str = "GET",
data: Optional[List[str]] = None,
params: Optional[dict] = None,
) -> List[dict] | dict:
"""
Generic HTTP request to SSDL API.
Supports both GET and POST methods.

Args:
access_token: Bearer token for authentication
endpoint: SSDL API endpoint (without base URL)
method: HTTP method ("GET" or "POST")
data: Request body data for POST requests (list of strings)
params: URL query parameters

Returns:
For GET requests: List[dict] - collection of items
For POST requests: dict - single result object

Note:
GET requests always return collections (List[dict]).
POST requests always return a single object (dict).

Raises:
AuthorizationError: For 401/403 responses
InvalidDataError: For 404 responses
InvalidParameterError: For 400 and other error responses
"""
urlstring = f"https://api.gateway.equinor.com/subsurfacedata/v3/api/v3.0/{endpoint}?"
params = params if params else {}
headers = {
"Content-Type": "application/json",
"authorization": f"Bearer {access_token}",
"Ocp-Apim-Subscription-Key": config.ENTERPRISE_SUBSCRIPTION_KEY,
}
timer = PerfTimer()

# Make the HTTP request based on method
if method.upper() == "POST":
post_data = data if data else []
response = await HTTPX_ASYNC_CLIENT_WRAPPER.client.post(
urlstring, json=post_data, params=params, headers=headers, timeout=60
)
elif method.upper() == "GET":
response = await HTTPX_ASYNC_CLIENT_WRAPPER.client.get(urlstring, params=params, headers=headers, timeout=60)
else:
raise InvalidParameterError(f"Unsupported HTTP method: {method}", Service.SSDL)

# Handle response
results = []
if response.status_code in [200, 201]:
results = response.json()
elif response.status_code == 401:
raise AuthorizationError("Unauthorized access to SSDL", Service.SSDL)
elif response.status_code == 403:
raise AuthorizationError("Forbidden access to SSDL", Service.SSDL)
elif response.status_code == 404:
raise InvalidDataError(f"No data found for endpoint {endpoint} with given parameters", Service.SSDL)
elif response.status_code == 400:
raise InvalidParameterError(f"Bad request to endpoint {endpoint}: {response.text}", Service.SSDL)
else:
# Capture other errors
raise InvalidParameterError(
f"Cannot {method.lower()} data from endpoint {endpoint}: {response.text}", Service.SSDL
)

LOGGER.debug(f"TIME SSDL {method.lower()} {endpoint} took {timer.lap_s():.2f} seconds")
return results
6 changes: 6 additions & 0 deletions backend_py/primary/primary/services/ssdl_access/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@


class WellboreCompletion(BaseModel):
wellbore_id: str
wellbore_uuid: str
md_top: float
md_bottom: float
tvd_top: float | None
Expand All @@ -13,6 +15,8 @@ class WellboreCompletion(BaseModel):


class WellboreCasing(BaseModel):
wellbore_id: str
wellbore_uuid: str
item_type: str # Casing type
diameter_numeric: float
diameter_inner: float
Expand All @@ -26,6 +30,8 @@ class WellboreCasing(BaseModel):


class WellborePerforation(BaseModel):
wellbore_id: str
wellbore_uuid: str
md_top: float
md_bottom: float
tvd_top: float
Expand Down
Loading
Loading