Merge remote-tracking branch 'spulec/master'
This commit is contained in:
commit
f541ff932c
@ -1,7 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
@ -198,7 +197,7 @@ class LambdaResponse(BaseResponse):
|
|||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _get_aws_region(self, full_url):
|
def _get_aws_region(self, full_url):
|
||||||
region = re.search(self.region_regex, full_url)
|
region = self.region_regex.search(full_url)
|
||||||
if region:
|
if region:
|
||||||
return region.group(1)
|
return region.group(1)
|
||||||
else:
|
else:
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
import boto.ec2.cloudwatch
|
import boto.ec2.cloudwatch
|
||||||
import datetime
|
from datetime import datetime, timedelta
|
||||||
|
from dateutil.tz import tzutc
|
||||||
from .utils import make_arn_for_dashboard
|
from .utils import make_arn_for_dashboard
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ACCOUNT_ID = 123456789012
|
DEFAULT_ACCOUNT_ID = 123456789012
|
||||||
|
|
||||||
|
|
||||||
@ -18,6 +18,34 @@ class Dimension(object):
|
|||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def daterange(start, stop, step=timedelta(days=1), inclusive=False):
|
||||||
|
"""
|
||||||
|
This method will iterate from `start` to `stop` datetimes with a timedelta step of `step`
|
||||||
|
(supports iteration forwards or backwards in time)
|
||||||
|
|
||||||
|
:param start: start datetime
|
||||||
|
:param stop: end datetime
|
||||||
|
:param step: step size as a timedelta
|
||||||
|
:param inclusive: if True, last item returned will be as step closest to `end` (or `end` if no remainder).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# inclusive=False to behave like range by default
|
||||||
|
total_step_secs = step.total_seconds()
|
||||||
|
assert total_step_secs != 0
|
||||||
|
|
||||||
|
if total_step_secs > 0:
|
||||||
|
while start < stop:
|
||||||
|
yield start
|
||||||
|
start = start + step
|
||||||
|
else:
|
||||||
|
while stop < start:
|
||||||
|
yield start
|
||||||
|
start = start + step
|
||||||
|
|
||||||
|
if inclusive and start == stop:
|
||||||
|
yield start
|
||||||
|
|
||||||
|
|
||||||
class FakeAlarm(BaseModel):
|
class FakeAlarm(BaseModel):
|
||||||
|
|
||||||
def __init__(self, name, namespace, metric_name, comparison_operator, evaluation_periods,
|
def __init__(self, name, namespace, metric_name, comparison_operator, evaluation_periods,
|
||||||
@ -38,14 +66,14 @@ class FakeAlarm(BaseModel):
|
|||||||
self.ok_actions = ok_actions
|
self.ok_actions = ok_actions
|
||||||
self.insufficient_data_actions = insufficient_data_actions
|
self.insufficient_data_actions = insufficient_data_actions
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.configuration_updated_timestamp = datetime.datetime.utcnow()
|
self.configuration_updated_timestamp = datetime.utcnow()
|
||||||
|
|
||||||
self.history = []
|
self.history = []
|
||||||
|
|
||||||
self.state_reason = ''
|
self.state_reason = ''
|
||||||
self.state_reason_data = '{}'
|
self.state_reason_data = '{}'
|
||||||
self.state = 'OK'
|
self.state = 'OK'
|
||||||
self.state_updated_timestamp = datetime.datetime.utcnow()
|
self.state_updated_timestamp = datetime.utcnow()
|
||||||
|
|
||||||
def update_state(self, reason, reason_data, state_value):
|
def update_state(self, reason, reason_data, state_value):
|
||||||
# History type, that then decides what the rest of the items are, can be one of ConfigurationUpdate | StateUpdate | Action
|
# History type, that then decides what the rest of the items are, can be one of ConfigurationUpdate | StateUpdate | Action
|
||||||
@ -56,17 +84,18 @@ class FakeAlarm(BaseModel):
|
|||||||
self.state_reason = reason
|
self.state_reason = reason
|
||||||
self.state_reason_data = reason_data
|
self.state_reason_data = reason_data
|
||||||
self.state = state_value
|
self.state = state_value
|
||||||
self.state_updated_timestamp = datetime.datetime.utcnow()
|
self.state_updated_timestamp = datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
class MetricDatum(BaseModel):
|
class MetricDatum(BaseModel):
|
||||||
|
|
||||||
def __init__(self, namespace, name, value, dimensions):
|
def __init__(self, namespace, name, value, dimensions, timestamp):
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
self.dimensions = [Dimension(dimension['name'], dimension[
|
self.timestamp = timestamp or datetime.utcnow().replace(tzinfo=tzutc())
|
||||||
'value']) for dimension in dimensions]
|
self.dimensions = [Dimension(dimension['Name'], dimension[
|
||||||
|
'Value']) for dimension in dimensions]
|
||||||
|
|
||||||
|
|
||||||
class Dashboard(BaseModel):
|
class Dashboard(BaseModel):
|
||||||
@ -75,7 +104,7 @@ class Dashboard(BaseModel):
|
|||||||
self.arn = make_arn_for_dashboard(DEFAULT_ACCOUNT_ID, name)
|
self.arn = make_arn_for_dashboard(DEFAULT_ACCOUNT_ID, name)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.body = body
|
self.body = body
|
||||||
self.last_modified = datetime.datetime.now()
|
self.last_modified = datetime.now()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_modified_iso(self):
|
def last_modified_iso(self):
|
||||||
@ -92,6 +121,53 @@ class Dashboard(BaseModel):
|
|||||||
return '<CloudWatchDashboard {0}>'.format(self.name)
|
return '<CloudWatchDashboard {0}>'.format(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class Statistics:
|
||||||
|
def __init__(self, stats, dt):
|
||||||
|
self.timestamp = iso_8601_datetime_with_milliseconds(dt)
|
||||||
|
self.values = []
|
||||||
|
self.stats = stats
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sample_count(self):
|
||||||
|
if 'SampleCount' not in self.stats:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return len(self.values)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sum(self):
|
||||||
|
if 'Sum' not in self.stats:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return sum(self.values)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min(self):
|
||||||
|
if 'Minimum' not in self.stats:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return min(self.values)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max(self):
|
||||||
|
if 'Maximum' not in self.stats:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return max(self.values)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def average(self):
|
||||||
|
if 'Average' not in self.stats:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# when moto is 3.4+ we can switch to the statistics module
|
||||||
|
return sum(self.values) / len(self.values)
|
||||||
|
|
||||||
|
|
||||||
class CloudWatchBackend(BaseBackend):
|
class CloudWatchBackend(BaseBackend):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -150,9 +226,34 @@ class CloudWatchBackend(BaseBackend):
|
|||||||
self.alarms.pop(alarm_name, None)
|
self.alarms.pop(alarm_name, None)
|
||||||
|
|
||||||
def put_metric_data(self, namespace, metric_data):
|
def put_metric_data(self, namespace, metric_data):
|
||||||
for name, value, dimensions in metric_data:
|
for metric_member in metric_data:
|
||||||
self.metric_data.append(MetricDatum(
|
self.metric_data.append(MetricDatum(
|
||||||
namespace, name, value, dimensions))
|
namespace, metric_member['MetricName'], float(metric_member['Value']), metric_member['Dimensions.member'], metric_member.get('Timestamp')))
|
||||||
|
|
||||||
|
def get_metric_statistics(self, namespace, metric_name, start_time, end_time, period, stats):
|
||||||
|
period_delta = timedelta(seconds=period)
|
||||||
|
filtered_data = [md for md in self.metric_data if
|
||||||
|
md.namespace == namespace and md.name == metric_name and start_time <= md.timestamp <= end_time]
|
||||||
|
|
||||||
|
# earliest to oldest
|
||||||
|
filtered_data = sorted(filtered_data, key=lambda x: x.timestamp)
|
||||||
|
if not filtered_data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
data = list()
|
||||||
|
for dt in daterange(filtered_data[0].timestamp, filtered_data[-1].timestamp + period_delta, period_delta):
|
||||||
|
s = Statistics(stats, dt)
|
||||||
|
while idx < len(filtered_data) and filtered_data[idx].timestamp < (dt + period_delta):
|
||||||
|
s.values.append(filtered_data[idx].value)
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
if not s.values:
|
||||||
|
continue
|
||||||
|
|
||||||
|
data.append(s)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def get_all_metrics(self):
|
def get_all_metrics(self):
|
||||||
return self.metric_data
|
return self.metric_data
|
||||||
|
@ -2,6 +2,7 @@ import json
|
|||||||
from moto.core.utils import amzn_request_id
|
from moto.core.utils import amzn_request_id
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import cloudwatch_backends
|
from .models import cloudwatch_backends
|
||||||
|
from dateutil.parser import parse as dtparse
|
||||||
|
|
||||||
|
|
||||||
class CloudWatchResponse(BaseResponse):
|
class CloudWatchResponse(BaseResponse):
|
||||||
@ -75,35 +76,36 @@ class CloudWatchResponse(BaseResponse):
|
|||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def put_metric_data(self):
|
def put_metric_data(self):
|
||||||
namespace = self._get_param('Namespace')
|
namespace = self._get_param('Namespace')
|
||||||
metric_data = []
|
metric_data = self._get_multi_param('MetricData.member')
|
||||||
metric_index = 1
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
metric_name = self.querystring[
|
|
||||||
'MetricData.member.{0}.MetricName'.format(metric_index)][0]
|
|
||||||
except KeyError:
|
|
||||||
break
|
|
||||||
value = self.querystring.get(
|
|
||||||
'MetricData.member.{0}.Value'.format(metric_index), [None])[0]
|
|
||||||
dimensions = []
|
|
||||||
dimension_index = 1
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
dimension_name = self.querystring[
|
|
||||||
'MetricData.member.{0}.Dimensions.member.{1}.Name'.format(metric_index, dimension_index)][0]
|
|
||||||
except KeyError:
|
|
||||||
break
|
|
||||||
dimension_value = self.querystring[
|
|
||||||
'MetricData.member.{0}.Dimensions.member.{1}.Value'.format(metric_index, dimension_index)][0]
|
|
||||||
dimensions.append(
|
|
||||||
{'name': dimension_name, 'value': dimension_value})
|
|
||||||
dimension_index += 1
|
|
||||||
metric_data.append([metric_name, value, dimensions])
|
|
||||||
metric_index += 1
|
|
||||||
self.cloudwatch_backend.put_metric_data(namespace, metric_data)
|
self.cloudwatch_backend.put_metric_data(namespace, metric_data)
|
||||||
template = self.response_template(PUT_METRIC_DATA_TEMPLATE)
|
template = self.response_template(PUT_METRIC_DATA_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def get_metric_statistics(self):
|
||||||
|
namespace = self._get_param('Namespace')
|
||||||
|
metric_name = self._get_param('MetricName')
|
||||||
|
start_time = dtparse(self._get_param('StartTime'))
|
||||||
|
end_time = dtparse(self._get_param('EndTime'))
|
||||||
|
period = int(self._get_param('Period'))
|
||||||
|
statistics = self._get_multi_param("Statistics.member")
|
||||||
|
|
||||||
|
# Unsupported Parameters (To Be Implemented)
|
||||||
|
unit = self._get_param('Unit')
|
||||||
|
extended_statistics = self._get_param('ExtendedStatistics')
|
||||||
|
dimensions = self._get_param('Dimensions')
|
||||||
|
if unit or extended_statistics or dimensions:
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
# TODO: this should instead throw InvalidParameterCombination
|
||||||
|
if not statistics:
|
||||||
|
raise NotImplemented("Must specify either Statistics or ExtendedStatistics")
|
||||||
|
|
||||||
|
datapoints = self.cloudwatch_backend.get_metric_statistics(namespace, metric_name, start_time, end_time, period, statistics)
|
||||||
|
template = self.response_template(GET_METRIC_STATISTICS_TEMPLATE)
|
||||||
|
return template.render(label=metric_name, datapoints=datapoints)
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def list_metrics(self):
|
def list_metrics(self):
|
||||||
metrics = self.cloudwatch_backend.get_all_metrics()
|
metrics = self.cloudwatch_backend.get_all_metrics()
|
||||||
@ -150,10 +152,6 @@ class CloudWatchResponse(BaseResponse):
|
|||||||
template = self.response_template(GET_DASHBOARD_TEMPLATE)
|
template = self.response_template(GET_DASHBOARD_TEMPLATE)
|
||||||
return template.render(dashboard=dashboard)
|
return template.render(dashboard=dashboard)
|
||||||
|
|
||||||
@amzn_request_id
|
|
||||||
def get_metric_statistics(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def list_dashboards(self):
|
def list_dashboards(self):
|
||||||
prefix = self._get_param('DashboardNamePrefix', '')
|
prefix = self._get_param('DashboardNamePrefix', '')
|
||||||
@ -266,6 +264,50 @@ PUT_METRIC_DATA_TEMPLATE = """<PutMetricDataResponse xmlns="http://monitoring.am
|
|||||||
</ResponseMetadata>
|
</ResponseMetadata>
|
||||||
</PutMetricDataResponse>"""
|
</PutMetricDataResponse>"""
|
||||||
|
|
||||||
|
GET_METRIC_STATISTICS_TEMPLATE = """<GetMetricStatisticsResponse xmlns="http://monitoring.amazonaws.com/doc/2010-08-01/">
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>
|
||||||
|
{{ request_id }}
|
||||||
|
</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
|
||||||
|
<GetMetricStatisticsResult>
|
||||||
|
<Label> {{ label }} </Label>
|
||||||
|
<Datapoints>
|
||||||
|
{% for datapoint in datapoints %}
|
||||||
|
<Datapoint>
|
||||||
|
{% if datapoint.sum %}
|
||||||
|
<Sum>{{ datapoint.sum }}</Sum>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if datapoint.average %}
|
||||||
|
<Average>{{ datapoint.average }}</Average>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if datapoint.maximum %}
|
||||||
|
<Maximum>{{ datapoint.maximum }}</Maximum>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if datapoint.minimum %}
|
||||||
|
<Minimum>{{ datapoint.minimum }}</Minimum>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if datapoint.sample_count %}
|
||||||
|
<SampleCount>{{ datapoint.sample_count }}</SampleCount>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if datapoint.extended_statistics %}
|
||||||
|
<ExtendedStatistics>{{ datapoint.extended_statistics }}</ExtendedStatistics>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<Timestamp>{{ datapoint.timestamp }}</Timestamp>
|
||||||
|
<Unit>{{ datapoint.unit }}</Unit>
|
||||||
|
</Datapoint>
|
||||||
|
{% endfor %}
|
||||||
|
</Datapoints>
|
||||||
|
</GetMetricStatisticsResult>
|
||||||
|
</GetMetricStatisticsResponse>"""
|
||||||
|
|
||||||
LIST_METRICS_TEMPLATE = """<ListMetricsResponse xmlns="http://monitoring.amazonaws.com/doc/2010-08-01/">
|
LIST_METRICS_TEMPLATE = """<ListMetricsResponse xmlns="http://monitoring.amazonaws.com/doc/2010-08-01/">
|
||||||
<ListMetricsResult>
|
<ListMetricsResult>
|
||||||
<Metrics>
|
<Metrics>
|
||||||
|
@ -106,7 +106,8 @@ class BaseResponse(_TemplateEnvironmentMixin):
|
|||||||
|
|
||||||
default_region = 'us-east-1'
|
default_region = 'us-east-1'
|
||||||
# to extract region, use [^.]
|
# to extract region, use [^.]
|
||||||
region_regex = r'\.(?P<region>[a-z]{2}-[a-z]+-\d{1})\.amazonaws\.com'
|
region_regex = re.compile(r'\.(?P<region>[a-z]{2}-[a-z]+-\d{1})\.amazonaws\.com')
|
||||||
|
param_list_regex = re.compile(r'(.*)\.(\d+)\.')
|
||||||
aws_service_spec = None
|
aws_service_spec = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -167,7 +168,7 @@ class BaseResponse(_TemplateEnvironmentMixin):
|
|||||||
self.response_headers = {"server": "amazon.com"}
|
self.response_headers = {"server": "amazon.com"}
|
||||||
|
|
||||||
def get_region_from_url(self, request, full_url):
|
def get_region_from_url(self, request, full_url):
|
||||||
match = re.search(self.region_regex, full_url)
|
match = self.region_regex.search(full_url)
|
||||||
if match:
|
if match:
|
||||||
region = match.group(1)
|
region = match.group(1)
|
||||||
elif 'Authorization' in request.headers and 'AWS4' in request.headers['Authorization']:
|
elif 'Authorization' in request.headers and 'AWS4' in request.headers['Authorization']:
|
||||||
@ -311,6 +312,41 @@ class BaseResponse(_TemplateEnvironmentMixin):
|
|||||||
return False
|
return False
|
||||||
return if_none
|
return if_none
|
||||||
|
|
||||||
|
def _get_multi_param_helper(self, param_prefix):
|
||||||
|
value_dict = dict()
|
||||||
|
tracked_prefixes = set() # prefixes which have already been processed
|
||||||
|
|
||||||
|
def is_tracked(name_param):
|
||||||
|
for prefix_loop in tracked_prefixes:
|
||||||
|
if name_param.startswith(prefix_loop):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
for name, value in self.querystring.items():
|
||||||
|
if is_tracked(name) or not name.startswith(param_prefix):
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = self.param_list_regex.search(name[len(param_prefix):]) if len(name) > len(param_prefix) else None
|
||||||
|
if match:
|
||||||
|
prefix = param_prefix + match.group(1)
|
||||||
|
value = self._get_multi_param(prefix)
|
||||||
|
tracked_prefixes.add(prefix)
|
||||||
|
name = prefix
|
||||||
|
value_dict[name] = value
|
||||||
|
else:
|
||||||
|
value_dict[name] = value[0]
|
||||||
|
|
||||||
|
if not value_dict:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(value_dict) > 1:
|
||||||
|
# strip off period prefix
|
||||||
|
value_dict = {name[len(param_prefix) + 1:]: value for name, value in value_dict.items()}
|
||||||
|
else:
|
||||||
|
value_dict = list(value_dict.values())[0]
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
def _get_multi_param(self, param_prefix):
|
def _get_multi_param(self, param_prefix):
|
||||||
"""
|
"""
|
||||||
Given a querystring of ?LaunchConfigurationNames.member.1=my-test-1&LaunchConfigurationNames.member.2=my-test-2
|
Given a querystring of ?LaunchConfigurationNames.member.1=my-test-1&LaunchConfigurationNames.member.2=my-test-2
|
||||||
@ -323,12 +359,13 @@ class BaseResponse(_TemplateEnvironmentMixin):
|
|||||||
values = []
|
values = []
|
||||||
index = 1
|
index = 1
|
||||||
while True:
|
while True:
|
||||||
try:
|
value_dict = self._get_multi_param_helper(prefix + str(index))
|
||||||
values.append(self.querystring[prefix + str(index)][0])
|
if not value_dict:
|
||||||
except KeyError:
|
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
index += 1
|
values.append(value_dict)
|
||||||
|
index += 1
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def _get_dict_param(self, param_prefix):
|
def _get_dict_param(self, param_prefix):
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
from six.moves.urllib.parse import urlparse
|
from six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
@ -15,12 +17,11 @@ from .exceptions import (
|
|||||||
MAXIMUM_VISIBILTY_TIMEOUT = 43200
|
MAXIMUM_VISIBILTY_TIMEOUT = 43200
|
||||||
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
|
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
|
||||||
DEFAULT_RECEIVED_MESSAGES = 1
|
DEFAULT_RECEIVED_MESSAGES = 1
|
||||||
SQS_REGION_REGEX = r'://(.+?)\.queue\.amazonaws\.com'
|
|
||||||
|
|
||||||
|
|
||||||
class SQSResponse(BaseResponse):
|
class SQSResponse(BaseResponse):
|
||||||
|
|
||||||
region_regex = SQS_REGION_REGEX
|
region_regex = re.compile(r'://(.+?)\.queue\.amazonaws\.com')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sqs_backend(self):
|
def sqs_backend(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user