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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
622 changes: 593 additions & 29 deletions README.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions changelog/@unreleased/pr-140.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type: feature
feature:
description: |-
Link error classes to operations. This allows developers to catch specific error conditions when making API requests. For example:

```python
from foundry.v2.datasets.errors import DatasetNotFound

try:
dataset = foundry_client.datasets.Dataset.get("ri.foundry.main.dataset.abc")
...
except DatasetNotFound as e:
print("Dataset not found", e.parameters["datasetRid"])
```
links:
- https://github.com/palantir/foundry-platform-python/pull/140
3 changes: 2 additions & 1 deletion docs/v1/Ontologies/models/DataValue.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Represents the value of data in the following format. Note that these values can
| CipherText | string | `"CIPHER::ri.bellaso.main.cipher-channel.e414ab9e-b606-499a-a0e1-844fa296ba7e::unzjs3VifsTxuIpf1fH1CJ7OaPBr2bzMMdozPaZJtCii8vVG60yXIEmzoOJaEl9mfFFe::CIPHER"` |
| Date | ISO 8601 extended local date string | `"2021-05-01"` |
| Decimal | string | `"2.718281828"` |
| Float | number | `3.14159265` |
| Double | number | `3.14159265` |
| EntrySet | array of JSON objects | `[{"key": "EMP1234", "value": "true"}, {"key": "EMP4444", "value": "false"}]` |
| Float | number | `3.14159265` |
| Integer | number | `238940` |
| Long | string | `"58319870951433"` |
| Marking | string | `"MU"` |
Expand Down
13 changes: 13 additions & 0 deletions docs/v1/Ontologies/models/EntrySetTypeDict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EntrySetTypeDict

EntrySetType

## Properties
| Name | Type | Required | Description |
| ------------ | ------------- | ------------- | ------------- |
**keyType** | QueryDataTypeDict | Yes | |
**valueType** | QueryDataTypeDict | Yes | |
**type** | Literal["entrySet"] | Yes | None |


[[Back to Model list]](../../../../README.md#models-v1-link) [[Back to API list]](../../../../README.md#apis-v1-link) [[Back to README]](../../../../README.md)
1 change: 1 addition & 0 deletions docs/v1/Ontologies/models/QueryDataTypeDict.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DateTypeDict | date
QueryStructTypeDict | struct
QuerySetTypeDict | set
StringTypeDict | string
EntrySetTypeDict | entrySet
DoubleTypeDict | double
IntegerTypeDict | integer
ThreeDimensionalAggregationDict | threeDimensionalAggregation
Expand Down
3 changes: 2 additions & 1 deletion docs/v2/Ontologies/models/DataValue.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Represents the value of data in the following format. Note that these values can
| CipherText | string | `"CIPHER::ri.bellaso.main.cipher-channel.e414ab9e-b606-499a-a0e1-844fa296ba7e::unzjs3VifsTxuIpf1fH1CJ7OaPBr2bzMMdozPaZJtCii8vVG60yXIEmzoOJaEl9mfFFe::CIPHER"` |
| Date | ISO 8601 extended local date string | `"2021-05-01"` |
| Decimal | string | `"2.718281828"` |
| Float | number | `3.14159265` |
| Double | number | `3.14159265` |
| EntrySet | array of JSON objects | `[{"key": "EMP1234", "value": "true"}, {"key": "EMP4444", "value": "false"}]` |
| Float | number | `3.14159265` |
| Integer | number | `238940` |
| Long | string | `"58319870951433"` |
| Marking | string | `"MU"` |
Expand Down
13 changes: 13 additions & 0 deletions docs/v2/Ontologies/models/EntrySetType.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EntrySetType

EntrySetType

## Properties
| Name | Type | Required | Description |
| ------------ | ------------- | ------------- | ------------- |
**key_type** | QueryDataType | Yes | |
**value_type** | QueryDataType | Yes | |
**type** | Literal["entrySet"] | Yes | None |


[[Back to Model list]](../../../../README.md#models-v2-link) [[Back to API list]](../../../../README.md#apis-v2-link) [[Back to README]](../../../../README.md)
13 changes: 13 additions & 0 deletions docs/v2/Ontologies/models/EntrySetTypeDict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EntrySetTypeDict

EntrySetType

## Properties
| Name | Type | Required | Description |
| ------------ | ------------- | ------------- | ------------- |
**keyType** | QueryDataTypeDict | Yes | |
**valueType** | QueryDataTypeDict | Yes | |
**type** | Literal["entrySet"] | Yes | None |


[[Back to Model list]](../../../../README.md#models-v2-link) [[Back to API list]](../../../../README.md#apis-v2-link) [[Back to README]](../../../../README.md)
1 change: 1 addition & 0 deletions docs/v2/Ontologies/models/QueryDataType.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DateType | date
QueryStructType | struct
QuerySetType | set
StringType | string
EntrySetType | entrySet
DoubleType | double
IntegerType | integer
ThreeDimensionalAggregation | threeDimensionalAggregation
Expand Down
1 change: 1 addition & 0 deletions docs/v2/Ontologies/models/QueryDataTypeDict.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DateTypeDict | date
QueryStructTypeDict | struct
QuerySetTypeDict | set
StringTypeDict | string
EntrySetTypeDict | entrySet
DoubleTypeDict | double
IntegerTypeDict | integer
ThreeDimensionalAggregationDict | threeDimensionalAggregation
Expand Down
4 changes: 4 additions & 0 deletions foundry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from foundry._core import StreamingContextManager
from foundry._core import UserTokenAuth
from foundry._errors import BadRequestError
from foundry._errors import ConflictError
from foundry._errors import ConnectionError
from foundry._errors import ConnectTimeout
from foundry._errors import EnvironmentNotConfigured
Expand All @@ -35,6 +36,7 @@
from foundry._errors import ProxyError
from foundry._errors import RateLimitError
from foundry._errors import ReadTimeout
from foundry._errors import RequestEntityTooLargeError
from foundry._errors import SDKInternalError
from foundry._errors import StreamConsumedError
from foundry._errors import TimeoutError
Expand Down Expand Up @@ -72,6 +74,8 @@
"NotFoundError",
"UnprocessableEntityError",
"RateLimitError",
"RequestEntityTooLargeError",
"ConflictError",
"InternalServerError",
"SDKInternalError",
"StreamConsumedError",
Expand Down
19 changes: 16 additions & 3 deletions foundry/_core/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from foundry._core.http_client import HttpClient
from foundry._core.resource_iterator import ResourceIterator
from foundry._errors import BadRequestError
from foundry._errors import ConflictError
from foundry._errors import ConnectionError
from foundry._errors import ConnectTimeout
from foundry._errors import InternalServerError
Expand All @@ -53,11 +54,13 @@
from foundry._errors import ProxyError
from foundry._errors import RateLimitError
from foundry._errors import ReadTimeout
from foundry._errors import RequestEntityTooLargeError
from foundry._errors import SDKInternalError
from foundry._errors import StreamConsumedError
from foundry._errors import UnauthorizedError
from foundry._errors import UnprocessableEntityError
from foundry._errors import WriteTimeout
from foundry._errors import deserialize_error
from foundry._versions import __version__

QueryParameters = Dict[str, Union[Any, List[Any]]]
Expand Down Expand Up @@ -92,6 +95,7 @@ class RequestInfo:
body: Any
body_type: Any
request_timeout: Optional[int]
throwable_errors: Dict[str, Type[PalantirRPCException]]

# DEPRECATED: Remove the streaming details
stream: bool = False
Expand All @@ -114,6 +118,7 @@ def update(
body_type=self.body_type,
request_timeout=self.request_timeout,
stream=stream if stream is not None else self.stream,
throwable_errors=self.throwable_errors,
)

@classmethod
Expand All @@ -130,6 +135,7 @@ def with_defaults(
request_timeout: Optional[int] = None,
stream: bool = False,
chunk_size: Optional[int] = None,
throwable_errors: Dict[str, Type[PalantirRPCException]] = {},
):
return cls(
method=method,
Expand All @@ -143,6 +149,7 @@ def with_defaults(
request_timeout=request_timeout,
stream=stream,
chunk_size=chunk_size,
throwable_errors=throwable_errors,
)


Expand Down Expand Up @@ -340,7 +347,7 @@ def make_request(token: Token):
except httpx.WriteTimeout as e:
raise WriteTimeout(str(e)) from e

self._check_for_errors(res)
self._check_for_errors(request_info, res)
return ApiResponse(request_info, res)

def stream_api(self, request_info: RequestInfo) -> StreamingContextManager[Any]:
Expand Down Expand Up @@ -387,7 +394,7 @@ def _create_headers(self, request_info: RequestInfo, token: Token) -> Dict[str,
},
}

def _check_for_errors(self, res: httpx.Response):
def _check_for_errors(self, req: RequestInfo, res: httpx.Response):
if 200 <= res.status_code <= 299:
return

Expand All @@ -396,14 +403,20 @@ def _check_for_errors(self, res: httpx.Response):
except json.JSONDecodeError:
raise SDKInternalError("Unable to decode JSON error response: " + res.text)

if res.status_code == 400:
if error_instance := deserialize_error(error_json, req.throwable_errors):
raise error_instance
elif res.status_code == 400:
raise BadRequestError(error_json)
elif res.status_code == 401:
raise UnauthorizedError(error_json)
elif res.status_code == 403:
raise PermissionDeniedError(error_json)
elif res.status_code == 404:
raise NotFoundError(error_json)
elif res.status_code == 409:
raise ConflictError(error_json)
elif res.status_code == 413:
raise RequestEntityTooLargeError(error_json)
elif res.status_code == 422:
raise UnprocessableEntityError(error_json)
elif res.status_code == 429:
Expand Down
3 changes: 3 additions & 0 deletions foundry/_errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
from foundry._errors.not_authenticated import NotAuthenticated
from foundry._errors.palantir_exception import PalantirException
from foundry._errors.palantir_rpc_exception import BadRequestError
from foundry._errors.palantir_rpc_exception import ConflictError
from foundry._errors.palantir_rpc_exception import InternalServerError
from foundry._errors.palantir_rpc_exception import NotFoundError
from foundry._errors.palantir_rpc_exception import PalantirRPCException
from foundry._errors.palantir_rpc_exception import PermissionDeniedError
from foundry._errors.palantir_rpc_exception import RateLimitError
from foundry._errors.palantir_rpc_exception import RequestEntityTooLargeError
from foundry._errors.palantir_rpc_exception import UnauthorizedError
from foundry._errors.palantir_rpc_exception import UnprocessableEntityError
from foundry._errors.sdk_internal_error import SDKInternalError
Expand All @@ -33,3 +35,4 @@
from foundry._errors.timeout_error import ReadTimeout
from foundry._errors.timeout_error import TimeoutError
from foundry._errors.timeout_error import WriteTimeout
from foundry._errors.utils import deserialize_error
11 changes: 11 additions & 0 deletions foundry/_errors/palantir_rpc_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class UnauthorizedError(PalantirRPCException):
"""


class RequestEntityTooLargeError(PalantirRPCException):
"""The request entity is too large. This error is thrown if a 413 status code is returned."""


class PermissionDeniedError(PalantirRPCException):
"""
You are missing the necessary permissions to complete your request. This error is thrown if a
Expand All @@ -69,5 +73,12 @@ class RateLimitError(PalantirRPCException):
"""


class ConflictError(PalantirRPCException):
"""
There was a conflict with another request. This error is thrown if a 409 status code is
returned.
"""


class InternalServerError(PalantirRPCException):
"""An error occurred within the service. This error is thrown if a 5XX status code is returned."""
57 changes: 31 additions & 26 deletions foundry/_errors/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import warnings
from typing import Any
from typing import Dict
from typing import Optional
from typing import Type
from typing import cast
from typing import get_type_hints
Expand All @@ -29,31 +30,35 @@
def deserialize_error(
error_metadata: Dict[str, Any],
exception_classes: Dict[str, type],
):
name = error_metadata["errorName"]
parameters = error_metadata["parameters"]
error_instance_id = error_metadata["errorInstanceId"]
) -> Optional[PalantirRPCException]:
try:
name = error_metadata["errorName"]
parameters = error_metadata["parameters"]
error_instance_id = error_metadata["errorInstanceId"]
except KeyError as e:
warnings.warn(str(SDKInternalError(f"Failed to find required error attributes: {e}")))

return None

exc_class = exception_classes.get(name)
if exc_class is not None:
annotations = get_type_hints(exc_class)
parameters_type = cast(Type[Dict[str, Any]], annotations["parameters"])
adapter = pydantic.TypeAdapter(parameters_type)

try:
parameters_instance = adapter.validate_python(parameters)
return exc_class(
name=name, parameters=parameters_instance, error_instance_id=error_instance_id
)
except pydantic.ValidationError as e:
# For whatever reason, if we can't properly deserialize the error parameters we will throw PalantirRPCException
# instead of failing
# To provide additional details to the user we will add a bunch of metadata to the warning
# using the "SDKInternalError" class but just emit a warning
warning_message = str(
SDKInternalError(f'Deserialization failed for error "{name}": {e}')
)
warnings.warn(warning_message)

# Fallback to PalantirRPCException if no match is found
return PalantirRPCException(error_metadata)
if exc_class is None:
return None

annotations = get_type_hints(exc_class)
parameters_type = cast(Type[Dict[str, Any]], annotations["parameters"])
adapter = pydantic.TypeAdapter(parameters_type)

try:
parameters_instance = adapter.validate_python(parameters)
return exc_class(
name=name, parameters=parameters_instance, error_instance_id=error_instance_id
)
except pydantic.ValidationError as e:
# For whatever reason, if we can't properly deserialize the error parameters we will throw PalantirRPCException
# instead of failing
# To provide additional details to the user we will add a bunch of metadata to the warning
# using the "SDKInternalError" class but just emit a warning
warning_message = str(SDKInternalError(f'Deserialization failed for error "{name}": {e}'))

warnings.warn(warning_message)
return None
2 changes: 1 addition & 1 deletion foundry/_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
# using the autorelease bot
__version__ = "0.0.0"

__openapi_document_version__ = "1.1077.0"
__openapi_document_version__ = "1.1080.0"
5 changes: 2 additions & 3 deletions foundry/v1/core/errors/_api_feature_preview_usage_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
from dataclasses import dataclass
from typing import Literal

import pydantic
from typing_extensions import TypedDict

from foundry._errors import PalantirRPCException
from foundry._errors import BadRequestError


class ApiFeaturePreviewUsageOnlyParameters(TypedDict):
Expand All @@ -34,7 +33,7 @@ class ApiFeaturePreviewUsageOnlyParameters(TypedDict):


@dataclass
class ApiFeaturePreviewUsageOnly(PalantirRPCException):
class ApiFeaturePreviewUsageOnly(BadRequestError):
name: Literal["ApiFeaturePreviewUsageOnly"]
parameters: ApiFeaturePreviewUsageOnlyParameters
error_instance_id: str
Expand Down
5 changes: 2 additions & 3 deletions foundry/v1/core/errors/_api_usage_denied.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@
from dataclasses import dataclass
from typing import Literal

import pydantic
from typing_extensions import NotRequired
from typing_extensions import TypedDict

from foundry._errors import PalantirRPCException
from foundry._errors import PermissionDeniedError
from foundry.v1.core.models._operation_scope import OperationScope


Expand All @@ -35,7 +34,7 @@ class ApiUsageDeniedParameters(TypedDict):


@dataclass
class ApiUsageDenied(PalantirRPCException):
class ApiUsageDenied(PermissionDeniedError):
name: Literal["ApiUsageDenied"]
parameters: ApiUsageDeniedParameters
error_instance_id: str
Expand Down
Loading