Skip to content

Commit 0219425

Browse files
committed
refactor store test fixture
1 parent 3c3099a commit 0219425

File tree

7 files changed

+230
-132
lines changed

7 files changed

+230
-132
lines changed

tests/conftest.py

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,40 @@
3434
# ### amended from s3fs ### #
3535
test_bucket_name = "test"
3636
secure_bucket_name = "test-secure"
37-
port = 5555
38-
endpoint_url = f"http://127.0.0.1:{port}/"
3937

4038

4139
async def parse_store(
42-
store: Literal["local", "memory", "remote", "zip"], path: str
40+
store: str, path: str, s3_base: str
4341
) -> LocalStore | MemoryStore | RemoteStore | ZipStore:
44-
if store == "local":
45-
return await LocalStore.open(path, mode="a")
46-
if store == "memory":
47-
return await MemoryStore.open(mode="a")
48-
if store == "remote":
49-
return RemoteStore.from_url(
50-
f"s3://{test_bucket_name}/foo/spam/",
51-
mode="a",
52-
storage_options={"endpoint_url": endpoint_url, "anon": False},
53-
)
54-
if store == "zip":
55-
return await ZipStore.open(path + "/zarr.zip", mode="a")
42+
"""
43+
Take a string representation of a store + access mode, e.g. 'local_a', which would encode
44+
LocalStore + access mode `a`, and convert that string representation into the appropriate store object,
45+
which is then returned.
46+
"""
47+
store_parsed = store.split("_")
48+
49+
if len(store_parsed) == 1:
50+
store_type = store_parsed[0]
51+
# the default mode for testing is a
52+
mode = "a"
53+
elif len(store_parsed) == 2:
54+
store_type, mode = store_parsed
55+
else:
56+
raise ValueError(f"Invalid store specification: {store}")
57+
58+
match store_type:
59+
case "local":
60+
return await LocalStore.open(path, mode=mode)
61+
case "memory":
62+
return await MemoryStore.open(mode=mode)
63+
case "remote":
64+
return RemoteStore.from_url(
65+
f"s3://{test_bucket_name}/foo/spam/",
66+
mode=mode,
67+
storage_options={"endpoint_url": s3_base, "anon": False},
68+
)
69+
case "zip":
70+
return await ZipStore.open(path + "/zarr.zip", mode=mode)
5671
raise AssertionError
5772

5873

@@ -69,14 +84,14 @@ async def store_path(tmpdir: LEGACY_PATH) -> StorePath:
6984

7085

7186
@pytest.fixture
72-
async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> Store:
87+
async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3_base: str) -> Store:
7388
param = request.param
74-
return await parse_store(param, str(tmpdir))
89+
return await parse_store(param, str(tmpdir), s3_base)
7590

7691

7792
@pytest.fixture(params=["local", "memory", "zip"])
78-
def sync_store(request: pytest.FixtureRequest, tmp_path: LEGACY_PATH) -> Store:
79-
result = sync(parse_store(request.param, str(tmp_path)))
93+
def sync_store(request: pytest.FixtureRequest, tmp_path: LEGACY_PATH, s3_base: str) -> Store:
94+
result = sync(parse_store(request.param, str(tmp_path), s3_base))
8095
if not isinstance(result, Store):
8196
raise TypeError("Wrong store class returned by test fixture! got " + result + " instead")
8297
return result
@@ -90,10 +105,12 @@ class AsyncGroupRequest:
90105

91106

92107
@pytest.fixture
93-
async def async_group(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> AsyncGroup:
108+
async def async_group(
109+
request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3_base: str
110+
) -> AsyncGroup:
94111
param: AsyncGroupRequest = request.param
95112

96-
store = await parse_store(param.store, str(tmpdir))
113+
store = await parse_store(param.store, str(tmpdir), s3_base)
97114
return await AsyncGroup.from_store(
98115
store,
99116
attributes=param.attributes,
@@ -147,29 +164,31 @@ def zarr_format(request: pytest.FixtureRequest) -> ZarrFormat:
147164

148165

149166
@pytest.fixture(scope="module")
150-
def s3_base() -> Generator[None, None, None]:
167+
def s3_base() -> Generator[str, None, None]:
151168
# writable local S3 system
169+
from moto.server import ThreadedMotoServer
152170

153-
# This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests
154-
server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port)
155-
server.start()
156171
if "AWS_SECRET_ACCESS_KEY" not in os.environ:
157172
os.environ["AWS_SECRET_ACCESS_KEY"] = "foo"
158173
if "AWS_ACCESS_KEY_ID" not in os.environ:
159174
os.environ["AWS_ACCESS_KEY_ID"] = "foo"
175+
server = ThreadedMotoServer(ip_address="127.0.0.1", port=0)
176+
server.start()
177+
host, port = server._server.server_address
178+
endpoint_url = f"http://{host}:{port}"
160179

161-
yield
180+
yield endpoint_url
162181
server.stop()
163182

164183

165-
def get_boto3_client() -> botocore.client.BaseClient:
184+
def get_boto3_client(endpoint_url: str) -> botocore.client.BaseClient:
166185
# NB: we use the sync botocore client for setup
167186
session = Session()
168187
return session.create_client("s3", endpoint_url=endpoint_url, region_name="us-east-1")
169188

170189

171190
@pytest.fixture(autouse=True)
172-
def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignore[name-defined]
191+
def s3(s3_base: str) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignore[name-defined]
173192
"""
174193
Quoting Martin Durant:
175194
pytest-asyncio creates a new event loop for each async test.
@@ -182,14 +201,14 @@ def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: # type: igno
182201
183202
https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207
184203
"""
185-
client = get_boto3_client()
204+
client = get_boto3_client(s3_base)
186205
client.create_bucket(Bucket=test_bucket_name, ACL="public-read")
187206
s3fs.S3FileSystem.clear_instance_cache()
188-
s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url})
207+
s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": s3_base})
189208
session = sync(s3.set_session())
190209
s3.invalidate_cache()
191210
yield s3
192-
requests.post(f"{endpoint_url}/moto-api/reset")
211+
requests.post(f"{s3_base}/moto-api/reset")
193212
client.close()
194213
sync(session.close())
195214

0 commit comments

Comments
 (0)