Skip to content

Commit

Permalink
Merge pull request #317 from roboflow/release/v0.9.16
Browse files Browse the repository at this point in the history
Release/v0.9.16
  • Loading branch information
PawelPeczek-Roboflow authored Mar 11, 2024
2 parents 5bef3d3 + a1cc461 commit 14fe2a9
Show file tree
Hide file tree
Showing 20 changed files with 302 additions and 115 deletions.
2 changes: 1 addition & 1 deletion examples/clip-search-engine/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ def send_image(path):


if __name__ == "__main__":
app.run(debug=True)
app.run()
8 changes: 7 additions & 1 deletion inference/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ class WorkspaceLoadError(Exception):


class InputImageLoadError(Exception):
pass

def __init__(self, message: str, public_message: str):
super().__init__(message)
self._public_message = public_message

def get_public_error_details(self) -> str:
return self._public_message


class InvalidNumpyInput(InputImageLoadError):
Expand Down
138 changes: 113 additions & 25 deletions inference/core/interfaces/http/http_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,48 +161,136 @@ def with_route_exceptions(route):
async def wrapped_route(*args, **kwargs):
try:
return await route(*args, **kwargs)
except (
ContentTypeInvalid,
ContentTypeMissing,
InputImageLoadError,
InvalidModelIDError,
InvalidMaskDecodeArgument,
MissingApiKeyError,
RuntimePayloadError,
) as e:
resp = JSONResponse(status_code=400, content={"message": str(e)})
except ContentTypeInvalid:
resp = JSONResponse(
status_code=400,
content={
"message": "Invalid Content-Type header provided with request."
},
)
traceback.print_exc()
except ContentTypeMissing:
resp = JSONResponse(
status_code=400,
content={"message": "Content-Type header not provided with request."},
)
traceback.print_exc()
except InputImageLoadError as e:
resp = JSONResponse(
status_code=400,
content={
"message": f"Could not load input image. Cause: {e.get_public_error_details()}"
},
)
traceback.print_exc()
except InvalidModelIDError:
resp = JSONResponse(
status_code=400,
content={"message": "Invalid Model ID sent in request."},
)
traceback.print_exc()
except RoboflowAPINotAuthorizedError as e:
resp = JSONResponse(status_code=401, content={"message": str(e)})
except InvalidMaskDecodeArgument:
resp = JSONResponse(
status_code=400,
content={
"message": "Invalid mask decode argument sent. tradeoff_factor must be in [0.0, 1.0], "
"mask_decode_mode: must be one of ['accurate', 'fast', 'tradeoff']"
},
)
traceback.print_exc()
except (RoboflowAPINotNotFoundError, InferenceModelNotFound) as e:
resp = JSONResponse(status_code=404, content={"message": str(e)})
except MissingApiKeyError:
resp = JSONResponse(
status_code=400,
content={
"message": "Required Roboflow API key is missing. Visit https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key "
"to learn how to retrieve one."
},
)
traceback.print_exc()
except RuntimePayloadError as e:
resp = JSONResponse(
status_code=400, content={"message": e.get_public_message()}
)
traceback.print_exc()
except RoboflowAPINotAuthorizedError:
resp = JSONResponse(
status_code=401,
content={
"message": "Unauthorized access to roboflow API - check API key and make sure the key is valid for "
"workspace you use. Visit https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key "
"to learn how to retrieve one."
},
)
traceback.print_exc()
except (RoboflowAPINotNotFoundError, InferenceModelNotFound):
resp = JSONResponse(
status_code=404,
content={
"message": "Requested Roboflow resource not found. Make sure that workspace, project or model "
"you referred in request exists."
},
)
traceback.print_exc()
except (
InvalidEnvironmentVariableError,
MissingServiceSecretError,
WorkspaceLoadError,
ServiceConfigurationError,
):
resp = JSONResponse(
status_code=500, content={"message": "Service misconfiguration."}
)
traceback.print_exc()
except (
PreProcessingError,
PostProcessingError,
ServiceConfigurationError,
ModelArtefactError,
MalformedWorkflowResponseError,
):
resp = JSONResponse(
status_code=500,
content={
"message": "Model configuration related to pre- or post-processing is invalid."
},
)
traceback.print_exc()
except ModelArtefactError:
resp = JSONResponse(
status_code=500, content={"message": "Model package is broken."}
)
traceback.print_exc()
except (
WorkflowsCompilerError,
ExecutionEngineError,
) as e:
resp = JSONResponse(status_code=500, content={"message": str(e)})
resp = JSONResponse(
status_code=500, content={"message": e.get_public_message()}
)
traceback.print_exc()
except OnnxProviderNotAvailable as e:
resp = JSONResponse(status_code=501, content={"message": str(e)})
except OnnxProviderNotAvailable:
resp = JSONResponse(
status_code=501,
content={
"message": "Could not find requested ONNX Runtime Provider. Check that you are using "
"the correct docker image on a supported device."
},
)
traceback.print_exc()
except (
MalformedRoboflowAPIResponseError,
RoboflowAPIUnsuccessfulRequestError,
) as e:
resp = JSONResponse(status_code=502, content={"message": str(e)})
WorkspaceLoadError,
MalformedWorkflowResponseError,
):
resp = JSONResponse(
status_code=502,
content={"message": "Internal error. Request to Roboflow API failed."},
)
traceback.print_exc()
except RoboflowAPIConnectionError as e:
resp = JSONResponse(status_code=503, content={"message": str(e)})
except RoboflowAPIConnectionError:
resp = JSONResponse(
status_code=503,
content={
"message": "Internal error. Could not connect to Roboflow API."
},
)
traceback.print_exc()
except Exception:
resp = JSONResponse(status_code=500, content={"message": "Internal error."})
Expand Down
37 changes: 26 additions & 11 deletions inference/core/utils/image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def extract_image_payload_and_type(value: Any) -> Tuple[Any, Optional[ImageType]
return value, image_type
if image_type.lower() not in allowed_payload_types:
raise InvalidImageTypeDeclared(
f"Declared image type: {image_type.lower()} which is not in allowed types: {allowed_payload_types}."
message=f"Declared image type: {image_type.lower()} which is not in allowed types: {allowed_payload_types}.",
public_message="Image declaration contains not recognised image type.",
)
return value, ImageType(image_type.lower())

Expand All @@ -146,7 +147,8 @@ def load_image_with_known_type(
"""
if image_type is ImageType.NUMPY and not ALLOW_NUMPY_INPUT:
raise InvalidImageTypeDeclared(
f"NumPy image type is not supported in this configuration of `inference`."
message=f"NumPy image type is not supported in this configuration of `inference`.",
public_message=f"NumPy image type is not supported in this configuration of `inference`.",
)
loader = IMAGE_LOADERS[image_type]
is_bgr = True if image_type is not ImageType.PILLOW else False
Expand Down Expand Up @@ -221,7 +223,8 @@ def attempt_loading_image_from_string(
return load_image_from_numpy_str(value=value), True
except InvalidNumpyInput as error:
raise InputFormatInferenceFailed(
"Input image format could not be inferred from string."
message="Input image format could not be inferred from string.",
public_message="Input image format could not be inferred from string.",
) from error


Expand All @@ -244,7 +247,10 @@ def load_image_base64(
image_np = np.frombuffer(value, np.uint8)
result = cv2.imdecode(image_np, cv_imread_flags)
if result is None:
raise InputImageLoadError("Could not load valid image from base64 string.")
raise InputImageLoadError(
message="Could not load valid image from base64 string.",
public_message="Malformed base64 input image.",
)
return result


Expand All @@ -264,7 +270,10 @@ def load_image_from_buffer(
image_np = np.frombuffer(value.read(), np.uint8)
result = cv2.imdecode(image_np, cv_imread_flags)
if result is None:
raise InputImageLoadError("Could not load valid image from buffer.")
raise InputImageLoadError(
message="Could not load valid image from buffer.",
public_message="Could not decode bytes into image.",
)
return result


Expand All @@ -286,7 +295,8 @@ def load_image_from_numpy_str(value: Union[bytes, str]) -> np.ndarray:
data = pickle.loads(value)
except (EOFError, TypeError, pickle.UnpicklingError, binascii.Error) as error:
raise InvalidNumpyInput(
f"Could not unpickle image data. Cause: {error}"
message=f"Could not unpickle image data. Cause: {error}",
public_message="Could not deserialize pickle payload.",
) from error
validate_numpy_image(data=data)
return data
Expand All @@ -309,15 +319,18 @@ def validate_numpy_image(data: np.ndarray) -> None:
"""
if not issubclass(type(data), np.ndarray):
raise InvalidNumpyInput(
f"Data provided as input could not be decoded into np.ndarray object."
message=f"Data provided as input could not be decoded into np.ndarray object.",
public_message=f"Data provided as input could not be decoded into np.ndarray object.",
)
if len(data.shape) != 3 and len(data.shape) != 2:
raise InvalidNumpyInput(
f"For image given as np.ndarray expected 2 or 3 dimensions, got {len(data.shape)} dimensions."
message=f"For image given as np.ndarray expected 2 or 3 dimensions, got {len(data.shape)} dimensions.",
public_message=f"For image given as np.ndarray expected 2 or 3 dimensions.",
)
if data.shape[-1] != 3 and data.shape[-1] != 1:
raise InvalidNumpyInput(
f"For image given as np.ndarray expected 1 or 3 channels, got {data.shape[-1]} channels."
message=f"For image given as np.ndarray expected 1 or 3 channels, got {data.shape[-1]} channels.",
public_message="For image given as np.ndarray expected 1 or 3 channels.",
)


Expand All @@ -340,7 +353,8 @@ def load_image_from_url(
)
except (RequestException, ConnectionError) as error:
raise InputImageLoadError(
f"Error while loading image from url: {value}. Details: {error}"
message=f"Could not load image from url: {value}. Details: {error}",
public_message="Data pointed by URL could not be decoded into image.",
)


Expand All @@ -361,7 +375,8 @@ def load_image_from_encoded_bytes(
image = cv2.imdecode(image_np, cv_imread_flags)
if image is None:
raise InputImageLoadError(
f"Could not parse response content from url {value} into image."
message=f"Could not decode bytes as image.",
public_message="Data is not image.",
)
return image

Expand Down
2 changes: 1 addition & 1 deletion inference/core/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.9.16rc3"
__version__ = "0.9.16"


if __name__ == "__main__":
Expand Down
5 changes: 2 additions & 3 deletions inference/enterprise/workflows/complier/execution_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,9 @@ async def safe_execute_step(
background_tasks=background_tasks,
)
except Exception as error:
logger.exception(f"Execution of step {step} encountered error.")
raise ExecutionEngineError(
f"Error during execution of step: {step}. "
f"Type of error: {type(error).__name__}. "
f"Cause: {error}"
f"Error during execution of step: {step}."
) from error


Expand Down
5 changes: 4 additions & 1 deletion inference/enterprise/workflows/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
class WorkflowsCompilerError(Exception):
pass
# Message of error must be prepared to be revealed in any API response.

def get_public_message(self) -> str:
return str(self)


class ValidationError(WorkflowsCompilerError):
Expand Down
Loading

0 comments on commit 14fe2a9

Please sign in to comment.