Skip to content

Commit 12a74ba

Browse files
committed
migrate azurite
1 parent 3e91374 commit 12a74ba

File tree

3 files changed

+76
-122
lines changed

3 files changed

+76
-122
lines changed

src/pytest_databases/_service.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,19 @@ def _stop_all_containers(self) -> None:
102102
def run(
103103
self,
104104
image: str,
105-
check: Callable[[ServiceContainer], bool],
106105
container_port: int,
107106
name: str,
108107
command: str | None = None,
109108
env: dict[str, Any] | None = None,
110109
exec_after_start: str | list[str] | None = None,
110+
check: Callable[[ServiceContainer], bool] | None = None,
111+
wait_for_log: str | None = None,
111112
timeout: int = 10,
112113
pause: int = 0.1,
113114
) -> Generator[ServiceContainer, None, None]:
115+
if check is None and wait_for_log is None:
116+
raise ValueError(f"Must set at least check or wait_for_log")
117+
114118
name = f"pytest_databases_{name}"
115119
lock = filelock.FileLock(self._tmp_path / name) if self._is_xdist else contextlib.nullcontext()
116120
with lock:
@@ -136,15 +140,28 @@ def run(
136140
host_port = int(
137141
container.ports[next(k for k in container.ports if k.startswith(str(container_port)))][0]["HostPort"]
138142
)
139-
service = ServiceContainer(host="127.0.0.1", port=host_port)
143+
service = ServiceContainer(
144+
host="127.0.0.1",
145+
port=host_port,
146+
)
147+
140148
started = time.time()
141-
while time.time() - started < timeout:
142-
result = check(service)
143-
if result is True:
144-
break
145-
time.sleep(pause)
146-
else:
147-
raise ValueError(f"Service {name!r} failed to come online")
149+
if wait_for_log:
150+
wait_for_log = wait_for_log.encode()
151+
while time.time() - started < timeout:
152+
if wait_for_log in container.logs():
153+
break
154+
time.sleep(pause)
155+
else:
156+
raise ValueError(f"Service {name!r} failed to come online")
157+
158+
if check:
159+
while time.time() - started < timeout:
160+
if check(service) is True:
161+
break
162+
time.sleep(pause)
163+
else:
164+
raise ValueError(f"Service {name!r} failed to come online")
148165

149166
if exec_after_start:
150167
container.exec_run(exec_after_start)
Lines changed: 50 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
from __future__ import annotations
22

33
import json
4-
import os
54
import secrets
65
import subprocess # noqa: S404
7-
from pathlib import Path
6+
from dataclasses import dataclass
87
from typing import AsyncGenerator, Generator
98

109
import pytest
1110
from azure.storage.blob import ContainerClient
1211
from azure.storage.blob.aio import ContainerClient as AsyncContainerClient
1312

14-
from pytest_databases.docker import DockerServiceRegistry
15-
from pytest_databases.helpers import simple_string_hash
13+
from pytest_databases._service import DockerService
14+
from pytest_databases.helpers import get_xdist_worker_num
15+
from pytest_databases.types import ServiceContainer
1616

17-
COMPOSE_PROJECT_NAME: str = f"pytest-databases-azure-blob-{simple_string_hash(__file__)}"
17+
18+
@dataclass
19+
class AzureBlobService(ServiceContainer):
20+
connection_string: str
21+
account_url: str
22+
account_key: str = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
23+
account_name: str = "devstoreaccount1"
1824

1925

2026
def _get_container_ids(compose_file_name: str) -> list[str]:
@@ -33,22 +39,13 @@ def _get_container_ids(compose_file_name: str) -> list[str]:
3339
return [json.loads(line)["ID"] for line in proc.stdout.splitlines()]
3440

3541

36-
def _get_container_logs(compose_file_name: str) -> str:
37-
logs = ""
38-
for container_id in _get_container_ids(compose_file_name):
39-
stdout = subprocess.run(
40-
["docker", "logs", container_id], # noqa: S607
41-
capture_output=True,
42-
text=True,
43-
check=True,
44-
).stdout
45-
logs += stdout
46-
return logs
47-
48-
49-
@pytest.fixture(scope="session")
50-
def azure_blob_compose_project_name() -> str:
51-
return os.environ.get("COMPOSE_PROJECT_NAME", COMPOSE_PROJECT_NAME)
42+
def _get_container_logs(container_id: str) -> str:
43+
return subprocess.run(
44+
["docker", "logs", container_id], # noqa: S607
45+
capture_output=True,
46+
text=True,
47+
check=True,
48+
).stdout
5249

5350

5451
@pytest.fixture(scope="session")
@@ -57,107 +54,56 @@ def azure_blob_service_startup_delay() -> int:
5754

5855

5956
@pytest.fixture(scope="session")
60-
def azure_blob_docker_services(
61-
azure_blob_compose_project_name: str,
62-
azure_blob_service_startup_delay: int,
63-
worker_id: str = "main",
64-
) -> Generator[DockerServiceRegistry, None, None]:
65-
with DockerServiceRegistry(
66-
worker_id,
67-
compose_project_name=azure_blob_compose_project_name,
68-
) as registry:
69-
yield registry
70-
71-
72-
@pytest.fixture(scope="session")
73-
def azure_blob_connection_string() -> str:
74-
return "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
75-
76-
77-
@pytest.fixture(scope="session")
78-
def azure_blob_account_name() -> str:
79-
return "devstoreaccount1"
80-
81-
82-
@pytest.fixture(scope="session")
83-
def azure_blob_account_key() -> str:
84-
return "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
85-
86-
87-
@pytest.fixture(scope="session")
88-
def azure_blob_account_url() -> str:
89-
return "http://127.0.0.1:10000/devstoreaccount1"
90-
91-
92-
@pytest.fixture(scope="session")
93-
def azure_blob_port() -> int:
94-
return 10000
95-
96-
97-
@pytest.fixture(scope="session")
98-
def azure_blob_docker_compose_files() -> list[Path]:
99-
return [Path(Path(__file__).parent / "docker-compose.azurite.yml")]
100-
101-
102-
@pytest.fixture(scope="session")
103-
def default_azure_blob_redis_service_name() -> str:
104-
return "azurite"
105-
106-
107-
@pytest.fixture(scope="session")
108-
def azure_blob_docker_ip(azure_blob_docker_services: DockerServiceRegistry) -> str:
109-
return azure_blob_docker_services.docker_ip
57+
def azure_blob_service(
58+
docker_service: DockerService,
59+
) -> Generator[ServiceContainer, None, None]:
60+
with docker_service.run(
61+
image="mcr.microsoft.com/azure-storage/azurite",
62+
name="azurite-blob",
63+
command="azurite-blob --blobHost 0.0.0.0 --blobPort 10000",
64+
wait_for_log="Azurite Blob service successfully listens on",
65+
container_port=10000,
66+
) as service:
67+
account_url = f"http://127.0.0.1:{service.port}/devstoreaccount1"
68+
connection_string = (
69+
"DefaultEndpointsProtocol=http;"
70+
"AccountName=devstoreaccount1;"
71+
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
72+
f"BlobEndpoint={account_url};"
73+
)
74+
75+
yield AzureBlobService(
76+
host=service.host,
77+
port=service.port,
78+
connection_string=connection_string,
79+
account_url=account_url,
80+
)
11081

11182

11283
@pytest.fixture(scope="session")
11384
def azure_blob_default_container_name() -> str:
114-
return secrets.token_hex(4)
85+
return f"pytest{get_xdist_worker_num()}"
11586

11687

11788
@pytest.fixture(scope="session")
11889
def azure_blob_container_client(
119-
azure_blob_connection_string: str,
90+
azure_blob_service: AzureBlobService,
12091
azure_blob_default_container_name: str,
121-
azure_blob_service: None,
12292
) -> Generator[ContainerClient, None, None]:
12393
with ContainerClient.from_connection_string(
124-
azure_blob_connection_string, container_name=azure_blob_default_container_name
94+
azure_blob_service.connection_string,
95+
container_name=azure_blob_default_container_name,
12596
) as container_client:
12697
yield container_client
12798

12899

129100
@pytest.fixture(scope="session")
130101
async def azure_blob_async_container_client(
131-
azure_blob_connection_string: str, azure_blob_default_container_name: str
102+
azure_blob_service: AzureBlobService,
103+
azure_blob_default_container_name: str,
132104
) -> AsyncGenerator[AsyncContainerClient, None]:
133105
async with AsyncContainerClient.from_connection_string(
134-
azure_blob_connection_string, container_name=azure_blob_default_container_name
106+
azure_blob_service.connection_string,
107+
container_name=azure_blob_default_container_name,
135108
) as container_client:
136109
yield container_client
137-
138-
139-
@pytest.fixture(autouse=False, scope="session")
140-
def azure_blob_service(
141-
azure_blob_docker_services: DockerServiceRegistry,
142-
default_azure_blob_redis_service_name: str,
143-
azure_blob_docker_compose_files: list[Path],
144-
azure_blob_connection_string: str,
145-
azure_blob_port: int,
146-
azure_blob_default_container_name: str,
147-
) -> Generator[None, None, None]:
148-
os.environ["AZURE_BLOB_PORT"] = str(azure_blob_port)
149-
150-
def azurite_responsive(host: str, port: int) -> bool:
151-
# because azurite has a bug where it will hang for a long time if you make a
152-
# request against it before it has completed startup we can't ping it, so we're
153-
# inspecting the container logs instead
154-
logs = _get_container_logs(str(azure_blob_docker_compose_files[0].absolute()))
155-
return "Azurite Blob service successfully listens on" in logs
156-
157-
azure_blob_docker_services.start(
158-
name=default_azure_blob_redis_service_name,
159-
docker_compose_files=azure_blob_docker_compose_files,
160-
check=azurite_responsive,
161-
port=azure_blob_port,
162-
)
163-
yield

src/pytest_databases/docker/docker-compose.azurite.yml

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)