Skip to content

Commit 1c24619

Browse files
author
Darren Weber
committed
aiomoto
1 parent 904332b commit 1c24619

15 files changed

+830
-3
lines changed

aiobotocore/aiomoto/__init__.py

Whitespace-only changes.
+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
"""
2+
AWS asyncio test fixtures
3+
"""
4+
5+
import aiobotocore.client
6+
import aiobotocore.config
7+
import pytest
8+
9+
from aiobotocore.aiomoto.aiomoto_services import MotoService
10+
from aiobotocore.aiomoto.utils import AWS_ACCESS_KEY_ID
11+
from aiobotocore.aiomoto.utils import AWS_SECRET_ACCESS_KEY
12+
13+
14+
#
15+
# Asyncio AWS Services
16+
#
17+
18+
19+
@pytest.fixture
20+
async def aio_aws_batch_server():
21+
async with MotoService("batch") as svc:
22+
svc.reset()
23+
yield svc.endpoint_url
24+
25+
26+
@pytest.fixture
27+
async def aio_aws_cloudformation_server():
28+
async with MotoService("cloudformation") as svc:
29+
svc.reset()
30+
yield svc.endpoint_url
31+
32+
33+
@pytest.fixture
34+
async def aio_aws_ec2_server():
35+
async with MotoService("ec2") as svc:
36+
svc.reset()
37+
yield svc.endpoint_url
38+
39+
40+
@pytest.fixture
41+
async def aio_aws_ecs_server():
42+
async with MotoService("ecs") as svc:
43+
svc.reset()
44+
yield svc.endpoint_url
45+
46+
47+
@pytest.fixture
48+
async def aio_aws_iam_server():
49+
async with MotoService("iam") as svc:
50+
yield svc.endpoint_url
51+
52+
53+
@pytest.fixture
54+
async def aio_aws_dynamodb2_server():
55+
async with MotoService("dynamodb2") as svc:
56+
svc.reset()
57+
yield svc.endpoint_url
58+
59+
60+
@pytest.fixture
61+
async def aio_aws_logs_server():
62+
# cloud watch logs
63+
async with MotoService("logs") as svc:
64+
svc.reset()
65+
yield svc.endpoint_url
66+
67+
68+
@pytest.fixture
69+
async def aio_aws_s3_server():
70+
async with MotoService("s3") as svc:
71+
svc.reset()
72+
yield svc.endpoint_url
73+
74+
75+
@pytest.fixture
76+
async def aio_aws_sns_server():
77+
async with MotoService("sns") as svc:
78+
svc.reset()
79+
yield svc.endpoint_url
80+
81+
82+
@pytest.fixture
83+
async def aio_aws_sqs_server():
84+
async with MotoService("sqs") as svc:
85+
svc.reset()
86+
yield svc.endpoint_url
87+
88+
89+
#
90+
# Asyncio AWS Clients
91+
#
92+
93+
94+
@pytest.fixture
95+
def aio_aws_session(aws_credentials, aws_region, event_loop):
96+
# pytest-asyncio provides and manages the `event_loop`
97+
98+
session = aiobotocore.get_session(loop=event_loop)
99+
session.user_agent_name = "aiomoto"
100+
101+
assert session.get_default_client_config() is None
102+
aioconfig = aiobotocore.config.AioConfig(
103+
max_pool_connections=1, region_name=aws_region
104+
)
105+
106+
# Note: tried to use proxies for the aiobotocore.endpoint, to replace
107+
# 'https://batch.us-west-2.amazonaws.com/v1/describejobqueues', but
108+
# the moto.server does not behave as a proxy server. Leaving this
109+
# here for the record to avoid trying to do it again sometime later.
110+
# proxies = {
111+
# 'http': os.getenv("HTTP_PROXY", "http://127.0.0.1:5000/moto-api/"),
112+
# 'https': os.getenv("HTTPS_PROXY", "http://127.0.0.1:5000/moto-api/"),
113+
# }
114+
# assert aioconfig.proxies is None
115+
# aioconfig.proxies = proxies
116+
117+
session.set_default_client_config(aioconfig)
118+
assert session.get_default_client_config() == aioconfig
119+
120+
session.set_credentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
121+
session.set_debug_logger(logger_name="aiomoto")
122+
123+
yield session
124+
125+
126+
@pytest.fixture
127+
async def aio_aws_client(aio_aws_session):
128+
async def _get_client(service_name):
129+
async with MotoService(service_name) as srv:
130+
async with aio_aws_session.create_client(
131+
service_name, endpoint_url=srv.endpoint_url
132+
) as client:
133+
yield client
134+
135+
return _get_client
136+
137+
138+
@pytest.fixture
139+
async def aio_aws_batch_client(aio_aws_session, aio_aws_batch_server):
140+
async with aio_aws_session.create_client(
141+
"batch", endpoint_url=aio_aws_batch_server
142+
) as client:
143+
yield client
144+
145+
146+
@pytest.fixture
147+
async def aio_aws_ec2_client(aio_aws_session, aio_aws_ec2_server):
148+
async with aio_aws_session.create_client(
149+
"ec2", endpoint_url=aio_aws_ec2_server
150+
) as client:
151+
yield client
152+
153+
154+
@pytest.fixture
155+
async def aio_aws_ecs_client(aio_aws_session, aio_aws_ecs_server):
156+
async with aio_aws_session.create_client(
157+
"ecs", endpoint_url=aio_aws_ecs_server
158+
) as client:
159+
yield client
160+
161+
162+
@pytest.fixture
163+
async def aio_aws_iam_client(aio_aws_session, aio_aws_iam_server):
164+
async with aio_aws_session.create_client(
165+
"iam", endpoint_url=aio_aws_iam_server
166+
) as client:
167+
client.meta.config.region_name = "aws-global" # not AWS_REGION
168+
yield client
169+
170+
171+
@pytest.fixture
172+
async def aio_aws_logs_client(aio_aws_session, aio_aws_logs_server):
173+
async with aio_aws_session.create_client(
174+
"logs", endpoint_url=aio_aws_logs_server
175+
) as client:
176+
yield client
177+
178+
179+
@pytest.fixture
180+
async def aio_aws_s3_client(aio_aws_session, aio_aws_s3_server):
181+
async with aio_aws_session.create_client(
182+
"s3", endpoint_url=aio_aws_s3_server
183+
) as client:
184+
yield client
+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import asyncio
2+
import functools
3+
import logging
4+
import socket
5+
import threading
6+
import time
7+
import os
8+
9+
# Third Party
10+
import aiohttp
11+
import moto.backends
12+
import moto.server
13+
import werkzeug.serving
14+
15+
16+
HOST = "127.0.0.1"
17+
18+
_PYCHARM_HOSTED = os.environ.get("PYCHARM_HOSTED") == "1"
19+
CONNECT_TIMEOUT = 90 if _PYCHARM_HOSTED else 10
20+
21+
22+
def get_free_tcp_port(release_socket: bool = False):
23+
sckt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24+
sckt.bind(("", 0))
25+
addr, port = sckt.getsockname()
26+
if release_socket:
27+
sckt.close()
28+
return port
29+
30+
return sckt, port
31+
32+
33+
class MotoService:
34+
""" Will Create MotoService.
35+
Service is ref-counted so there will only be one per process. Real Service will
36+
be returned by `__aenter__`."""
37+
38+
_services = dict() # {name: instance}
39+
40+
def __init__(self, service_name: str, port: int = None):
41+
self._service_name = service_name
42+
43+
if port:
44+
self._socket = None
45+
self._port = port
46+
else:
47+
self._socket, self._port = get_free_tcp_port()
48+
49+
self._thread = None
50+
self._logger = logging.getLogger("MotoService")
51+
self._refcount = None
52+
self._ip_address = HOST
53+
self._server = None
54+
55+
@property
56+
def endpoint_url(self):
57+
return "http://{}:{}".format(self._ip_address, self._port)
58+
59+
def reset(self):
60+
# each service can have multiple regional backends
61+
service_backends = moto.backends.BACKENDS[self._service_name]
62+
for region_name, backend in service_backends.items():
63+
backend.reset()
64+
65+
def __call__(self, func):
66+
async def wrapper(*args, **kwargs):
67+
await self._start()
68+
try:
69+
result = await func(*args, **kwargs)
70+
finally:
71+
await self._stop()
72+
return result
73+
74+
functools.update_wrapper(wrapper, func)
75+
wrapper.__wrapped__ = func
76+
return wrapper
77+
78+
async def __aenter__(self):
79+
svc = self._services.get(self._service_name)
80+
if svc is None:
81+
self._services[self._service_name] = self
82+
self._refcount = 1
83+
await self._start()
84+
return self
85+
else:
86+
svc._refcount += 1
87+
return svc
88+
89+
async def __aexit__(self, exc_type, exc_val, exc_tb):
90+
self._refcount -= 1
91+
92+
if self._socket:
93+
self._socket.close()
94+
self._socket = None
95+
96+
if self._refcount == 0:
97+
del self._services[self._service_name]
98+
await self._stop()
99+
100+
def _server_entry(self):
101+
self._main_app = moto.server.DomainDispatcherApplication(
102+
moto.server.create_backend_app, service=self._service_name
103+
)
104+
self._main_app.debug = True
105+
106+
if self._socket:
107+
self._socket.close() # release right before we use it
108+
self._socket = None
109+
110+
self._server = werkzeug.serving.make_server(
111+
self._ip_address, self._port, self._main_app, True
112+
)
113+
self._server.serve_forever()
114+
115+
async def _start(self):
116+
self._thread = threading.Thread(target=self._server_entry, daemon=True)
117+
self._thread.start()
118+
119+
async with aiohttp.ClientSession() as session:
120+
start = time.time()
121+
122+
while time.time() - start < 10:
123+
if not self._thread.is_alive():
124+
break
125+
126+
try:
127+
# we need to bypass the proxies due to monkeypatches
128+
async with session.get(
129+
self.endpoint_url + "/static", timeout=CONNECT_TIMEOUT
130+
):
131+
pass
132+
break
133+
except (asyncio.TimeoutError, aiohttp.ClientConnectionError):
134+
await asyncio.sleep(0.5)
135+
else:
136+
await self._stop() # pytest.fail doesn't call stop_process
137+
raise Exception(
138+
"Cannot start MotoService: {}".format(self._service_name)
139+
)
140+
141+
async def _stop(self):
142+
if self._server:
143+
self._server.shutdown()
144+
145+
self._thread.join()

0 commit comments

Comments
 (0)