Skip to content

Commit 026fc9d

Browse files
Merge pull request #1104 from roboflow/fix/handle-versionless-model-id-through-legacy-routes
handle versionless model id through legacy routes
2 parents e485086 + c4850b4 commit 026fc9d

File tree

1 file changed

+63
-37
lines changed

1 file changed

+63
-37
lines changed

inference/core/interfaces/http/http_api.py

+63-37
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@
33
import os
44
import traceback
55
from functools import partial, wraps
6-
from pathlib import Path as Pathlib
76
from time import sleep
87
from typing import Any, Dict, List, Optional, Union
98

109
import asgi_correlation_id
1110
import uvicorn
1211
from fastapi import BackgroundTasks, Depends, FastAPI, Path, Query, Request
13-
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse, Response
12+
from fastapi.responses import JSONResponse, RedirectResponse, Response
1413
from fastapi.staticfiles import StaticFiles
1514
from fastapi_cprofile.profiler import CProfileMiddleware
16-
from starlette.convertors import StringConvertor, register_url_convertor
1715
from starlette.middleware.base import BaseHTTPMiddleware
1816

1917
from inference.core import logger
@@ -267,35 +265,40 @@ def with_route_exceptions(route):
267265
async def wrapped_route(*args, **kwargs):
268266
try:
269267
return await route(*args, **kwargs)
270-
except ContentTypeInvalid:
268+
except ContentTypeInvalid as error:
269+
logger.error("%s: %s", type(error).__name__, error)
271270
resp = JSONResponse(
272271
status_code=400,
273272
content={
274273
"message": "Invalid Content-Type header provided with request."
275274
},
276275
)
277276
traceback.print_exc()
278-
except ContentTypeMissing:
277+
except ContentTypeMissing as error:
278+
logger.error("%s: %s", type(error).__name__, error)
279279
resp = JSONResponse(
280280
status_code=400,
281281
content={"message": "Content-Type header not provided with request."},
282282
)
283283
traceback.print_exc()
284-
except InputImageLoadError as e:
284+
except InputImageLoadError as error:
285+
logger.error("%s: %s", type(error).__name__, error)
285286
resp = JSONResponse(
286287
status_code=400,
287288
content={
288-
"message": f"Could not load input image. Cause: {e.get_public_error_details()}"
289+
"message": f"Could not load input image. Cause: {error.get_public_error_details()}"
289290
},
290291
)
291292
traceback.print_exc()
292-
except InvalidModelIDError:
293+
except InvalidModelIDError as error:
294+
logger.error("%s: %s", type(error).__name__, error)
293295
resp = JSONResponse(
294296
status_code=400,
295297
content={"message": "Invalid Model ID sent in request."},
296298
)
297299
traceback.print_exc()
298-
except InvalidMaskDecodeArgument:
300+
except InvalidMaskDecodeArgument as error:
301+
logger.error("%s: %s", type(error).__name__, error)
299302
resp = JSONResponse(
300303
status_code=400,
301304
content={
@@ -304,7 +307,8 @@ async def wrapped_route(*args, **kwargs):
304307
},
305308
)
306309
traceback.print_exc()
307-
except MissingApiKeyError:
310+
except MissingApiKeyError as error:
311+
logger.error("%s: %s", type(error).__name__, error)
308312
resp = JSONResponse(
309313
status_code=400,
310314
content={
@@ -319,6 +323,7 @@ async def wrapped_route(*args, **kwargs):
319323
ExecutionGraphStructureError,
320324
StepInputDimensionalityError,
321325
) as error:
326+
logger.error("%s: %s", type(error).__name__, error)
322327
content = WorkflowErrorResponse(
323328
message=str(error.public_message),
324329
error_type=error.__class__.__name__,
@@ -338,6 +343,7 @@ async def wrapped_route(*args, **kwargs):
338343
WorkflowExecutionEngineVersionError,
339344
NotSupportedExecutionEngineError,
340345
) as error:
346+
logger.error("%s: %s", type(error).__name__, error)
341347
resp = JSONResponse(
342348
status_code=400,
343349
content={
@@ -353,6 +359,7 @@ async def wrapped_route(*args, **kwargs):
353359
MalformedPayloadError,
354360
MessageToBigError,
355361
) as error:
362+
logger.error("%s: %s", type(error).__name__, error)
356363
resp = JSONResponse(
357364
status_code=400,
358365
content={
@@ -361,7 +368,11 @@ async def wrapped_route(*args, **kwargs):
361368
"inner_error_type": error.inner_error_type,
362369
},
363370
)
364-
except (RoboflowAPINotAuthorizedError, ProcessesManagerAuthorisationError):
371+
except (
372+
RoboflowAPINotAuthorizedError,
373+
ProcessesManagerAuthorisationError,
374+
) as error:
375+
logger.error("%s: %s", type(error).__name__, error)
365376
resp = JSONResponse(
366377
status_code=401,
367378
content={
@@ -371,7 +382,8 @@ async def wrapped_route(*args, **kwargs):
371382
},
372383
)
373384
traceback.print_exc()
374-
except (RoboflowAPINotNotFoundError, InferenceModelNotFound):
385+
except (RoboflowAPINotNotFoundError, InferenceModelNotFound) as error:
386+
logger.error("%s: %s", type(error).__name__, error)
375387
resp = JSONResponse(
376388
status_code=404,
377389
content={
@@ -381,6 +393,7 @@ async def wrapped_route(*args, **kwargs):
381393
)
382394
traceback.print_exc()
383395
except ProcessesManagerNotFoundError as error:
396+
logger.error("%s: %s", type(error).__name__, error)
384397
resp = JSONResponse(
385398
status_code=404,
386399
content={
@@ -394,28 +407,32 @@ async def wrapped_route(*args, **kwargs):
394407
InvalidEnvironmentVariableError,
395408
MissingServiceSecretError,
396409
ServiceConfigurationError,
397-
):
410+
) as error:
411+
logger.error("%s: %s", type(error).__name__, error)
398412
resp = JSONResponse(
399413
status_code=500, content={"message": "Service misconfiguration."}
400414
)
401415
traceback.print_exc()
402416
except (
403417
PreProcessingError,
404418
PostProcessingError,
405-
):
419+
) as error:
420+
logger.error("%s: %s", type(error).__name__, error)
406421
resp = JSONResponse(
407422
status_code=500,
408423
content={
409424
"message": "Model configuration related to pre- or post-processing is invalid."
410425
},
411426
)
412427
traceback.print_exc()
413-
except ModelArtefactError:
428+
except ModelArtefactError as error:
429+
logger.error("%s: %s", type(error).__name__, error)
414430
resp = JSONResponse(
415431
status_code=500, content={"message": "Model package is broken."}
416432
)
417433
traceback.print_exc()
418-
except OnnxProviderNotAvailable:
434+
except OnnxProviderNotAvailable as error:
435+
logger.error("%s: %s", type(error).__name__, error)
419436
resp = JSONResponse(
420437
status_code=501,
421438
content={
@@ -429,21 +446,24 @@ async def wrapped_route(*args, **kwargs):
429446
RoboflowAPIUnsuccessfulRequestError,
430447
WorkspaceLoadError,
431448
MalformedWorkflowResponseError,
432-
):
449+
) as error:
450+
logger.error("%s: %s", type(error).__name__, error)
433451
resp = JSONResponse(
434452
status_code=502,
435453
content={"message": "Internal error. Request to Roboflow API failed."},
436454
)
437455
traceback.print_exc()
438-
except RoboflowAPIConnectionError:
456+
except RoboflowAPIConnectionError as error:
457+
logger.error("%s: %s", type(error).__name__, error)
439458
resp = JSONResponse(
440459
status_code=503,
441460
content={
442461
"message": "Internal error. Could not connect to Roboflow API."
443462
},
444463
)
445464
traceback.print_exc()
446-
except RoboflowAPITimeoutError:
465+
except RoboflowAPITimeoutError as error:
466+
logger.error("%s: %s", type(error).__name__, error)
447467
resp = JSONResponse(
448468
status_code=504,
449469
content={
@@ -452,6 +472,7 @@ async def wrapped_route(*args, **kwargs):
452472
)
453473
traceback.print_exc()
454474
except StepExecutionError as error:
475+
logger.error("%s: %s", type(error).__name__, error)
455476
content = WorkflowErrorResponse(
456477
message=str(error.public_message),
457478
error_type=error.__class__.__name__,
@@ -471,6 +492,7 @@ async def wrapped_route(*args, **kwargs):
471492
)
472493
traceback.print_exc()
473494
except WorkflowError as error:
495+
logger.error("%s: %s", type(error).__name__, error)
474496
resp = JSONResponse(
475497
status_code=500,
476498
content={
@@ -486,6 +508,7 @@ async def wrapped_route(*args, **kwargs):
486508
ProcessesManagerClientError,
487509
CommunicationProtocolError,
488510
) as error:
511+
logger.error("%s: %s", type(error).__name__, error)
489512
resp = JSONResponse(
490513
status_code=500,
491514
content={
@@ -495,7 +518,8 @@ async def wrapped_route(*args, **kwargs):
495518
},
496519
)
497520
traceback.print_exc()
498-
except Exception:
521+
except Exception as error:
522+
logger.error("%s: %s", type(error).__name__, error)
499523
resp = JSONResponse(status_code=500, content={"message": "Internal error."})
500524
traceback.print_exc()
501525
return resp
@@ -557,6 +581,17 @@ def __init__(
557581
root_path=root_path,
558582
)
559583

584+
app.mount(
585+
"/static",
586+
StaticFiles(directory="./inference/landing/out/static", html=True),
587+
name="static",
588+
)
589+
app.mount(
590+
"/_next/static",
591+
StaticFiles(directory="./inference/landing/out/_next/static", html=True),
592+
name="_next_static",
593+
)
594+
560595
@app.on_event("shutdown")
561596
async def on_shutdown():
562597
logger.info("Shutting down %s", description)
@@ -2194,19 +2229,9 @@ async def notebook_start(browserless: bool = False):
21942229
app.include_router(builder_router, prefix="/build", tags=["builder"])
21952230

21962231
if LEGACY_ROUTE_ENABLED:
2197-
2198-
class IntStringConvertor(StringConvertor):
2199-
"""
2200-
Match digits but keep them as string.
2201-
"""
2202-
2203-
regex = "\d+"
2204-
2205-
register_url_convertor("int_string", IntStringConvertor())
2206-
22072232
# Legacy object detection inference path for backwards compatability
22082233
@app.get(
2209-
"/{dataset_id}/{version_id:int_string}",
2234+
"/{dataset_id}/{version_id:str}",
22102235
# Order matters in this response model Union. It will use the first matching model. For example, Object Detection Inference Response is a subset of Instance segmentation inference response, so instance segmentation must come first in order for the matching logic to work.
22112236
response_model=Union[
22122237
InstanceSegmentationInferenceResponse,
@@ -2220,7 +2245,7 @@ class IntStringConvertor(StringConvertor):
22202245
response_model_exclude_none=True,
22212246
)
22222247
@app.post(
2223-
"/{dataset_id}/{version_id:int_string}",
2248+
"/{dataset_id}/{version_id:str}",
22242249
# Order matters in this response model Union. It will use the first matching model. For example, Object Detection Inference Response is a subset of Instance segmentation inference response, so instance segmentation must come first in order for the matching logic to work.
22252250
response_model=Union[
22262251
InstanceSegmentationInferenceResponse,
@@ -2239,10 +2264,10 @@ async def legacy_infer_from_request(
22392264
background_tasks: BackgroundTasks,
22402265
request: Request,
22412266
dataset_id: str = Path(
2242-
description="ID of a Roboflow dataset corresponding to the model to use for inference"
2267+
description="ID of a Roboflow dataset corresponding to the model to use for inference OR workspace ID"
22432268
),
22442269
version_id: str = Path(
2245-
description="ID of a Roboflow dataset version corresponding to the model to use for inference"
2270+
description="ID of a Roboflow dataset version corresponding to the model to use for inference OR model ID"
22462271
),
22472272
api_key: Optional[str] = Query(
22482273
None,
@@ -2336,8 +2361,8 @@ async def legacy_infer_from_request(
23362361
23372362
Args:
23382363
background_tasks: (BackgroundTasks) pool of fastapi background tasks
2339-
dataset_id (str): ID of a Roboflow dataset corresponding to the model to use for inference.
2340-
version_id (str): ID of a Roboflow dataset version corresponding to the model to use for inference.
2364+
dataset_id (str): ID of a Roboflow dataset corresponding to the model to use for inference OR workspace ID
2365+
version_id (str): ID of a Roboflow dataset version corresponding to the model to use for inference OR model ID
23412366
api_key (Optional[str], default None): Roboflow API Key passed to the model during initialization for artifact retrieval.
23422367
# Other parameters described in the function signature...
23432368
@@ -2390,6 +2415,7 @@ async def legacy_infer_from_request(
23902415
"Service secret is required to disable inference usage tracking"
23912416
)
23922417
if LAMBDA:
2418+
logger.debug("request.scope: %s", request.scope)
23932419
request_model_id = (
23942420
request.scope["aws.event"]["requestContext"]["authorizer"][
23952421
"lambda"
@@ -2520,7 +2546,7 @@ async def model_add_legacy(
25202546
app.mount(
25212547
"/",
25222548
StaticFiles(directory="./inference/landing/out", html=True),
2523-
name="static",
2549+
name="root",
25242550
)
25252551

25262552
def run(self):

0 commit comments

Comments
 (0)