11from __future__ import annotations
22
33import json
4- import os
54import secrets
65import subprocess # noqa: S404
7- from pathlib import Path
6+ from dataclasses import dataclass
87from typing import AsyncGenerator , Generator
98
109import pytest
1110from azure .storage .blob import ContainerClient
1211from 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
2026def _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" )
11384def 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" )
11889def 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" )
130101async 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
0 commit comments