Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 18 additions & 31 deletions docs/supported-databases/azure_blob_storage.rst
Original file line number Diff line number Diff line change
@@ -1,56 +1,43 @@
Azure Blob Storage
==================

Integration with `Azure Blob Storage <https://azure.microsoft.com/en-us/products/storage/blobs>`_, a cloud-based object storage service.

This integration uses the official `Azure Storage Blobs Python Client <https://learn.microsoft.com/en-us/python/api/overview/azure/storage-blob-readme>`_ to interact with Azure Blob Storage, which provides scalable object storage for testing and development.
Integration with `Azure Blob Storage <https://azure.microsoft.com/en-us/products/storage/blobs>`_.

Installation
------------

.. code-block:: bash

pip install pytest-databases[azure]

Configuration
-------------

* ``AZURE_STORAGE_CONNECTION_STRING``: Connection string for Azure Blob Storage
* ``AZURE_STORAGE_ACCOUNT_NAME``: Account name for Azure Blob Storage
* ``AZURE_STORAGE_ACCOUNT_KEY``: Account key for Azure Blob Storage
* ``AZURE_STORAGE_CONTAINER_NAME``: Container name for Azure Blob Storage (default: "pytest-databases")
pip install pytest-databases[azure-storage]

Usage Example
-------------

.. code-block:: python

import pytest
from azure.storage.blob import BlobServiceClient
from pytest_databases.docker.azure_blob import AzureBlobStorageService
pytest_plugins = ["pytest_databases.docker.azure_blob"]
from azure.storage.blob import ContainerClient
from pytest_databases.docker.azure_blob import AzureBlobService

def test(azure_blob_storage_service: AzureBlobStorageService) -> None:
client = BlobServiceClient.from_connection_string(
azure_blob_storage_service.connection_string
)
container = client.get_container_client(azure_blob_storage_service.container_name)
container.create_container()
assert container.exists()
pytest_plugins = ["pytest_databases.docker.azure_blob"]

def test(azure_blob_storage_client: BlobServiceClient) -> None:
container = azure_blob_storage_client.get_container_client("test-container")
container.create_container()
assert container.exists()
def test(
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
with ContainerClient.from_connection_string(
azure_blob_service.connection_string,
container_name=azure_blob_default_container_name,
) as container:
assert container.exists()

Available Fixtures
------------------

* ``azurite_in_memory``: Whether to use in-memory storage for Azurite (default: ``True``)
* ``azure_blob_service``: A fixture that provides an Azure Blob Storage service.
* ``azure_blob_default_container_name``: The default container name for Azure Blob Storage (default: ``pytest-databases``)
* ``azure_blob_container_client``: A fixture that provides an Azure Blob Storage container client.
* ``azure_blob_async_container_client``: A fixture that provides an Azure Blob Storage container client for async operations.
* ``azurite_in_memory``: Whether to use in-memory storage for Azurite (default: ``True``).
* ``azure_blob_service``: A fixture that provides an Azure Blob Storage service with the default container pre-created.
* ``azure_blob_default_container_name``: The default container name for Azure Blob Storage (default: ``pytest-databases``).
* ``azure_blob_xdist_isolation_level``: Xdist isolation level for the service (default: ``database``).

Service API
-----------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Issues = "https://github.com/litestar-org/pytest-databases/issues"
Source = "https://github.com/litestar-org/pytest-databases"

[project.optional-dependencies]
azure-storage = ["azure-storage-blob"]
azure-storage = []
bigquery = ["google-cloud-bigquery"]
cockroachdb = ["psycopg"]
dragonfly = ["redis"]
Expand Down
59 changes: 32 additions & 27 deletions src/pytest_databases/docker/azure_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,42 @@
from typing import TYPE_CHECKING

import pytest
from azure.storage.blob import ContainerClient
from azure.storage.blob.aio import ContainerClient as AsyncContainerClient

from pytest_databases.helpers import get_xdist_worker_count, get_xdist_worker_num
from pytest_databases.types import ServiceContainer, XdistIsolationLevel

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Generator
from collections.abc import Generator

from pytest_databases._service import DockerService


DEFAULT_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
DEFAULT_ACCOUNT_NAME = "devstoreaccount1"
AZURE_CLI_IMAGE = "mcr.microsoft.com/azure-cli:latest"


def _bootstrap_azure_blob_container(
docker_service: DockerService,
*,
connection_string: str,
container_name: str,
) -> None:
docker_service._client.containers.run(
AZURE_CLI_IMAGE,
[
"az",
"storage",
"container",
"create",
"--name",
container_name,
"--connection-string",
connection_string,
],
network_mode="host",
remove=True,
)


@dataclass
Expand Down Expand Up @@ -47,6 +69,7 @@ def azure_blob_service(
docker_service: DockerService,
azurite_in_memory: bool,
azure_blob_xdist_isolation_level: XdistIsolationLevel,
azure_blob_default_container_name: str,
) -> Generator[ServiceContainer, None, None]:
command = "azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --skipApiVersionCheck"
if azurite_in_memory:
Expand Down Expand Up @@ -83,6 +106,12 @@ def azure_blob_service(
f"BlobEndpoint={account_url};"
)

_bootstrap_azure_blob_container(
docker_service,
connection_string=connection_string,
container_name=azure_blob_default_container_name,
)

yield AzureBlobService(
host=service.host,
port=service.port,
Expand All @@ -97,27 +126,3 @@ def azure_blob_service(
@pytest.fixture(scope="session")
def azure_blob_default_container_name() -> str:
return "pytest-databases"


@pytest.fixture(scope="session")
def azure_blob_container_client(
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> Generator[ContainerClient, None, None]:
with ContainerClient.from_connection_string(
azure_blob_service.connection_string,
container_name=azure_blob_default_container_name,
) as container_client:
yield container_client


@pytest.fixture(scope="session")
async def azure_blob_async_container_client(
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> AsyncGenerator[AsyncContainerClient, None]:
async with AsyncContainerClient.from_connection_string(
azure_blob_service.connection_string,
container_name=azure_blob_default_container_name,
) as container_client:
yield container_client
132 changes: 104 additions & 28 deletions tests/test_azure_blob.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,91 @@
import pytest
from __future__ import annotations

from typing import TYPE_CHECKING

def test_default_no_xdist(pytester: pytest.Pytester) -> None:
if TYPE_CHECKING:
import pytest


AZURE_BLOB_TEST_HELPERS = """
import json


def run_az(docker_service, service, *args):
return docker_service._client.containers.run(
"mcr.microsoft.com/azure-cli:latest",
["az", "storage", *args, "--connection-string", service.connection_string],
network_mode="host",
remove=True,
)


def az_container_exists(docker_service, service, container_name):
raw = run_az(docker_service, service, "container", "exists", "--name", container_name)
if isinstance(raw, bytes):
raw = raw.decode()
return json.loads(raw)["exists"]
"""


def test_plugin_imports_without_azure_storage_blob(pytester: pytest.Pytester) -> None:
pytester.makepyfile("""
import pytest
from azure.storage.blob import ContainerClient
import builtins

def test_import() -> None:
original_import = builtins.__import__

def blocked_import(name, globals=None, locals=None, fromlist=(), level=0):
if name == "azure.storage.blob" or name.startswith("azure.storage.blob."):
raise ModuleNotFoundError(name)
return original_import(name, globals, locals, fromlist, level)

builtins.__import__ = blocked_import
try:
import pytest_databases.docker.azure_blob # noqa: F401
finally:
builtins.__import__ = original_import
""")

result = pytester.runpytest_subprocess("-p", "pytest_databases", "-vv")
result.assert_outcomes(passed=1)


def test_default_no_xdist(pytester: pytest.Pytester) -> None:
pytester.makepyfile(f"""
from pytest_databases._service import DockerService
from pytest_databases.docker.azure_blob import AzureBlobService

pytest_plugins = [
"pytest_databases.docker.azure_blob",
]

{AZURE_BLOB_TEST_HELPERS}

def test_one(azure_blob_container_client: ContainerClient) -> None:
azure_blob_container_client.create_container()

def test_one(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)

def test_two(azure_blob_container_client: ContainerClient) -> None:
assert azure_blob_container_client.exists()

def test_two(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases")
result.assert_outcomes(passed=2)


def test_xdist_isolate_server(pytester: pytest.Pytester) -> None:
pytester.makepyfile("""
pytester.makepyfile(f"""
import pytest
from azure.storage.blob import ContainerClient
from pytest_databases._service import DockerService
from pytest_databases.docker.azure_blob import AzureBlobService

pytest_plugins = [
"pytest_databases.docker.azure_blob",
Expand All @@ -37,42 +97,58 @@ def azure_blob_xdist_isolation_level():
return "server"


def test_one(azure_blob_container_client: ContainerClient) -> None:
assert not azure_blob_container_client.exists()
azure_blob_container_client.create_container()
{AZURE_BLOB_TEST_HELPERS}


def test_one(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)

def test_two(azure_blob_container_client: ContainerClient) -> None:
assert not azure_blob_container_client.exists()
azure_blob_container_client.create_container()

def test_two(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2")
result.assert_outcomes(passed=2)


def test_xdist_isolate_database(pytester: pytest.Pytester) -> None:
pytester.makepyfile("""
from azure.storage.blob import ContainerClient
pytester.makepyfile(f"""
from pytest_databases._service import DockerService
from pytest_databases.docker.azure_blob import AzureBlobService
from pytest_databases.helpers import get_xdist_worker_num

pytest_plugins = [
"pytest_databases.docker.azure_blob",
]


def test_one(azure_blob_container_client: ContainerClient, azure_blob_default_container_name: str) -> None:
assert not azure_blob_container_client.exists()
azure_blob_container_client.create_container()
assert azure_blob_container_client.container_name == azure_blob_default_container_name
assert azure_blob_container_client.account_name == f"test_account_{get_xdist_worker_num()}"
{AZURE_BLOB_TEST_HELPERS}


def test_one(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)
assert azure_blob_service.account_name == f"test_account_{{get_xdist_worker_num()}}"

def test_two(azure_blob_container_client: ContainerClient, azure_blob_default_container_name: str) -> None:
assert not azure_blob_container_client.exists()
azure_blob_container_client.create_container()
assert azure_blob_container_client.container_name == azure_blob_default_container_name
assert azure_blob_container_client.account_name == f"test_account_{get_xdist_worker_num()}"

def test_two(
docker_service: DockerService,
azure_blob_service: AzureBlobService,
azure_blob_default_container_name: str,
) -> None:
assert az_container_exists(docker_service, azure_blob_service, azure_blob_default_container_name)
assert azure_blob_service.account_name == f"test_account_{{get_xdist_worker_num()}}"
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2")
result.assert_outcomes(passed=2)
Loading