Add Rekognition support. (#4957)
This commit is contained in:
parent
a271057df2
commit
4ed5a837de
@ -131,6 +131,9 @@ mock_redshift = lazy_load(".redshift", "mock_redshift")
|
|||||||
mock_redshiftdata = lazy_load(
|
mock_redshiftdata = lazy_load(
|
||||||
".redshiftdata", "mock_redshiftdata", boto3_name="redshift-data"
|
".redshiftdata", "mock_redshiftdata", boto3_name="redshift-data"
|
||||||
)
|
)
|
||||||
|
mock_rekognition = lazy_load(
|
||||||
|
".rekognition", "mock_rekognition", boto3_name="rekognition"
|
||||||
|
)
|
||||||
mock_resourcegroups = lazy_load(
|
mock_resourcegroups = lazy_load(
|
||||||
".resourcegroups", "mock_resourcegroups", boto3_name="resource-groups"
|
".resourcegroups", "mock_resourcegroups", boto3_name="resource-groups"
|
||||||
)
|
)
|
||||||
|
@ -102,6 +102,7 @@ backend_url_patterns = [
|
|||||||
("rds", re.compile("https?://rds\\.amazonaws\\.com")),
|
("rds", re.compile("https?://rds\\.amazonaws\\.com")),
|
||||||
("redshift", re.compile("https?://redshift\\.(.+)\\.amazonaws\\.com")),
|
("redshift", re.compile("https?://redshift\\.(.+)\\.amazonaws\\.com")),
|
||||||
("redshift-data", re.compile("https?://redshift-data\\.(.+)\\.amazonaws\\.com")),
|
("redshift-data", re.compile("https?://redshift-data\\.(.+)\\.amazonaws\\.com")),
|
||||||
|
("rekognition", re.compile("https?://rekognition\\.(.+)\\.amazonaws\\.com")),
|
||||||
(
|
(
|
||||||
"resource-groups",
|
"resource-groups",
|
||||||
re.compile("https?://resource-groups(-fips)?\\.(.+)\\.amazonaws.com"),
|
re.compile("https?://resource-groups(-fips)?\\.(.+)\\.amazonaws.com"),
|
||||||
|
5
moto/rekognition/__init__.py
Normal file
5
moto/rekognition/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""rekognition module initialization; sets value for base decorator."""
|
||||||
|
from .models import rekognition_backends
|
||||||
|
from ..core.models import base_decorator
|
||||||
|
|
||||||
|
mock_rekognition = base_decorator(rekognition_backends)
|
1
moto/rekognition/exceptions.py
Normal file
1
moto/rekognition/exceptions.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Exceptions raised by the rekognition service."""
|
215
moto/rekognition/models.py
Normal file
215
moto/rekognition/models.py
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
"""RekognitionBackend class with methods for supported APIs."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
from moto.core import BaseBackend
|
||||||
|
from moto.core.utils import BackendDict
|
||||||
|
|
||||||
|
|
||||||
|
class RekognitionBackend(BaseBackend):
|
||||||
|
"""Implementation of Rekognition APIs."""
|
||||||
|
|
||||||
|
def __init__(self, region_name=None):
|
||||||
|
self.region_name = region_name
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Re-initialize all attributes for this instance."""
|
||||||
|
region_name = self.region_name
|
||||||
|
self.__dict__ = {}
|
||||||
|
self.__init__(region_name)
|
||||||
|
|
||||||
|
def start_text_detection(self):
|
||||||
|
return self._job_id()
|
||||||
|
|
||||||
|
def get_text_detection(self):
|
||||||
|
"""
|
||||||
|
This returns hardcoded values and none of the parameters are taken into account.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
self._job_status(),
|
||||||
|
self._status_message(),
|
||||||
|
self._video_metadata(),
|
||||||
|
self._text_detections(),
|
||||||
|
self._next_token(),
|
||||||
|
self._text_model_version(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# private
|
||||||
|
|
||||||
|
def _job_id(self):
|
||||||
|
return "".join(
|
||||||
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(64)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _job_status(self):
|
||||||
|
return "SUCCEEDED"
|
||||||
|
|
||||||
|
def _next_token(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _status_message(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _text_model_version(self):
|
||||||
|
return "3.1"
|
||||||
|
|
||||||
|
def _video_metadata(self):
|
||||||
|
return {
|
||||||
|
"Codec": "h264",
|
||||||
|
"DurationMillis": 15020,
|
||||||
|
"Format": "QuickTime / MOV",
|
||||||
|
"FrameRate": 24.0,
|
||||||
|
"FrameHeight": 720,
|
||||||
|
"FrameWidth": 1280,
|
||||||
|
"ColorRange": "LIMITED",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _text_detections(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"Timestamp": 0,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "Hello world",
|
||||||
|
"Type": "LINE",
|
||||||
|
"Id": 0,
|
||||||
|
"Confidence": 97.89398956298828,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.1364741027355194,
|
||||||
|
"Height": 0.0318513885140419,
|
||||||
|
"Left": 0.4310702085494995,
|
||||||
|
"Top": 0.876121461391449,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.4310702085494995, "Y": 0.8769540190696716},
|
||||||
|
{"X": 0.5673548579216003, "Y": 0.876121461391449},
|
||||||
|
{"X": 0.5675443410873413, "Y": 0.90714031457901},
|
||||||
|
{"X": 0.4312596917152405, "Y": 0.9079728722572327},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": 0,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "Hello",
|
||||||
|
"Type": "WORD",
|
||||||
|
"Id": 1,
|
||||||
|
"ParentId": 0,
|
||||||
|
"Confidence": 99.1568832397461,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.0648193359375,
|
||||||
|
"Height": 0.0234375,
|
||||||
|
"Left": 0.43121337890625,
|
||||||
|
"Top": 0.876953125,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.43121337890625, "Y": 0.876953125},
|
||||||
|
{"X": 0.49603271484375, "Y": 0.876953125},
|
||||||
|
{"X": 0.49603271484375, "Y": 0.900390625},
|
||||||
|
{"X": 0.43121337890625, "Y": 0.900390625},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": 0,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "world",
|
||||||
|
"Type": "WORD",
|
||||||
|
"Id": 2,
|
||||||
|
"ParentId": 0,
|
||||||
|
"Confidence": 96.63108825683594,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.07103776931762695,
|
||||||
|
"Height": 0.02804870530962944,
|
||||||
|
"Left": 0.4965003430843353,
|
||||||
|
"Top": 0.8795245885848999,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.4965003430843353, "Y": 0.8809727430343628},
|
||||||
|
{"X": 0.5673661231994629, "Y": 0.8795245885848999},
|
||||||
|
{"X": 0.5675381422042847, "Y": 0.9061251282691956},
|
||||||
|
{"X": 0.4966723322868347, "Y": 0.9075732827186584},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": 1000,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "Goodbye world",
|
||||||
|
"Type": "LINE",
|
||||||
|
"Id": 0,
|
||||||
|
"Confidence": 98.9729995727539,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.13677978515625,
|
||||||
|
"Height": 0.0302734375,
|
||||||
|
"Left": 0.43121337890625,
|
||||||
|
"Top": 0.876953125,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.43121337890625, "Y": 0.876953125},
|
||||||
|
{"X": 0.5679931640625, "Y": 0.876953125},
|
||||||
|
{"X": 0.5679931640625, "Y": 0.9072265625},
|
||||||
|
{"X": 0.43121337890625, "Y": 0.9072265625},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": 1000,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "Goodbye",
|
||||||
|
"Type": "WORD",
|
||||||
|
"Id": 1,
|
||||||
|
"ParentId": 0,
|
||||||
|
"Confidence": 99.7258529663086,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.0648193359375,
|
||||||
|
"Height": 0.0234375,
|
||||||
|
"Left": 0.43121337890625,
|
||||||
|
"Top": 0.876953125,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.43121337890625, "Y": 0.876953125},
|
||||||
|
{"X": 0.49603271484375, "Y": 0.876953125},
|
||||||
|
{"X": 0.49603271484375, "Y": 0.900390625},
|
||||||
|
{"X": 0.43121337890625, "Y": 0.900390625},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": 1000,
|
||||||
|
"TextDetection": {
|
||||||
|
"DetectedText": "world",
|
||||||
|
"Type": "WORD",
|
||||||
|
"Id": 2,
|
||||||
|
"ParentId": 0,
|
||||||
|
"Confidence": 98.22015380859375,
|
||||||
|
"Geometry": {
|
||||||
|
"BoundingBox": {
|
||||||
|
"Width": 0.0703125,
|
||||||
|
"Height": 0.0263671875,
|
||||||
|
"Left": 0.4976806640625,
|
||||||
|
"Top": 0.880859375,
|
||||||
|
},
|
||||||
|
"Polygon": [
|
||||||
|
{"X": 0.4976806640625, "Y": 0.880859375},
|
||||||
|
{"X": 0.5679931640625, "Y": 0.880859375},
|
||||||
|
{"X": 0.5679931640625, "Y": 0.9072265625},
|
||||||
|
{"X": 0.4976806640625, "Y": 0.9072265625},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
rekognition_backends = BackendDict(RekognitionBackend, "rekognition")
|
45
moto/rekognition/responses.py
Normal file
45
moto/rekognition/responses.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""Handles incoming rekognition requests, invokes methods, returns responses."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
from .models import rekognition_backends
|
||||||
|
|
||||||
|
|
||||||
|
class RekognitionResponse(BaseResponse):
|
||||||
|
"""Handler for Rekognition requests and responses."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rekognition_backend(self):
|
||||||
|
"""Return backend instance specific for this region."""
|
||||||
|
return rekognition_backends[self.region]
|
||||||
|
|
||||||
|
def get_text_detection(self):
|
||||||
|
(
|
||||||
|
job_status,
|
||||||
|
status_message,
|
||||||
|
video_metadata,
|
||||||
|
text_detections,
|
||||||
|
next_token,
|
||||||
|
text_model_version,
|
||||||
|
) = self.rekognition_backend.get_text_detection()
|
||||||
|
|
||||||
|
return json.dumps(
|
||||||
|
dict(
|
||||||
|
JobStatus=job_status,
|
||||||
|
StatusMessage=status_message,
|
||||||
|
VideoMetadata=video_metadata,
|
||||||
|
TextDetections=text_detections,
|
||||||
|
NextToken=next_token,
|
||||||
|
TextModelVersion=text_model_version,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_text_detection(self):
|
||||||
|
headers = {"Content-Type": "application/x-amz-json-1.1"}
|
||||||
|
job_id = self.rekognition_backend.start_text_detection()
|
||||||
|
response = ('{"JobId":"' + job_id + '"}').encode()
|
||||||
|
|
||||||
|
return 200, headers, response
|
||||||
|
|
||||||
|
|
||||||
|
# add templates from here
|
10
moto/rekognition/urls.py
Normal file
10
moto/rekognition/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""rekognition base URL and path."""
|
||||||
|
from .responses import RekognitionResponse
|
||||||
|
|
||||||
|
url_bases = [
|
||||||
|
r"https?://rekognition\.(.+)\.amazonaws\.com",
|
||||||
|
]
|
||||||
|
|
||||||
|
url_paths = {
|
||||||
|
"{0}/$": RekognitionResponse.dispatch,
|
||||||
|
}
|
0
tests/test_rekognition/__init__.py
Normal file
0
tests/test_rekognition/__init__.py
Normal file
45
tests/test_rekognition/test_rekognition.py
Normal file
45
tests/test_rekognition/test_rekognition.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""Unit tests for rekognition-supported APIs."""
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
|
||||||
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
from moto import mock_rekognition
|
||||||
|
|
||||||
|
# See our Development Tips on writing tests for hints on how to write good tests:
|
||||||
|
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rekognition
|
||||||
|
def test_start_text_detection():
|
||||||
|
client = boto3.client("rekognition", region_name="ap-southeast-1")
|
||||||
|
video = {
|
||||||
|
"S3Object": {
|
||||||
|
"Bucket": "bucket",
|
||||||
|
"Name": "key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.start_text_detection(Video=video)
|
||||||
|
|
||||||
|
resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
||||||
|
|
||||||
|
resp.should.have.key("JobId")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rekognition
|
||||||
|
def test_get_text_detection():
|
||||||
|
client = boto3.client("rekognition", region_name="us-east-2")
|
||||||
|
job_id = "".join(
|
||||||
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(64)
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = client.get_text_detection(JobId=job_id)
|
||||||
|
|
||||||
|
resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
|
||||||
|
|
||||||
|
resp["JobStatus"].should.equal("SUCCEEDED")
|
||||||
|
resp["TextDetections"][0]["TextDetection"]["DetectedText"].should.equal(
|
||||||
|
"Hello world"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user