refactor: update storages with dynaconf logic, add validators, repair test

This commit is contained in:
Julius Unverfehrt 2024-01-16 15:34:56 +01:00
parent 27917863c9
commit e5c8a6e9f1
5 changed files with 58 additions and 34 deletions

View File

@ -1,32 +1,30 @@
from functools import lru_cache, partial from functools import lru_cache, partial
from typing import Callable from typing import Callable
from dynaconf import Dynaconf
from funcy import compose from funcy import compose
from pyinfra.config import Config
from pyinfra.storage.storage_info import get_storage_info_from_config, get_storage_from_storage_info
from pyinfra.storage.storages.interface import Storage from pyinfra.storage.storages.interface import Storage
from pyinfra.storage.storages.s3 import get_s3_storage_from_settings
from pyinfra.utils.compressing import get_decompressor, get_compressor from pyinfra.utils.compressing import get_decompressor, get_compressor
from pyinfra.utils.config_validation import validate_settings, storage_validators
from pyinfra.utils.encoding import get_decoder, get_encoder from pyinfra.utils.encoding import get_decoder, get_encoder
def get_storage_from_config(config: Config) -> Storage: def get_storage_from_settings(settings: Dynaconf) -> Storage:
validate_settings(settings, storage_validators)
storage_info = get_storage_info_from_config(config) return storage_dispatcher[settings.storage.backend](settings)
storage = get_storage_from_storage_info(storage_info)
return storage
def verify_existence(storage: Storage, bucket: str, file_name: str) -> str: storage_dispatcher = {
if not storage.exists(bucket, file_name): "azure": get_s3_storage_from_settings,
raise FileNotFoundError(f"{file_name=} name not found on storage in {bucket=}.") "s3": get_s3_storage_from_settings,
return file_name }
@lru_cache(maxsize=10) @lru_cache(maxsize=10)
def make_downloader(storage: Storage, bucket: str, file_type: str, compression_type: str) -> Callable: def make_downloader(storage: Storage, bucket: str, file_type: str, compression_type: str) -> Callable:
verify = partial(verify_existence, storage, bucket) verify = partial(verify_existence, storage, bucket)
download = partial(storage.get_object, bucket) download = partial(storage.get_object, bucket)
decompress = get_decompressor(compression_type) decompress = get_decompressor(compression_type)
@ -37,7 +35,6 @@ def make_downloader(storage: Storage, bucket: str, file_type: str, compression_t
@lru_cache(maxsize=10) @lru_cache(maxsize=10)
def make_uploader(storage: Storage, bucket: str, file_type: str, compression_type: str) -> Callable: def make_uploader(storage: Storage, bucket: str, file_type: str, compression_type: str) -> Callable:
upload = partial(storage.put_object, bucket) upload = partial(storage.put_object, bucket)
compress = get_compressor(compression_type) compress = get_compressor(compression_type)
encode = get_encoder(file_type) encode = get_encoder(file_type)
@ -46,3 +43,9 @@ def make_uploader(storage: Storage, bucket: str, file_type: str, compression_typ
upload(file_name, compose(compress, encode)(file_bytes)) upload(file_name, compose(compress, encode)(file_bytes))
return inner return inner
def verify_existence(storage: Storage, bucket: str, file_name: str) -> str:
if not storage.exists(bucket, file_name):
raise FileNotFoundError(f"{file_name=} name not found on storage in {bucket=}.")
return file_name

View File

@ -1,12 +1,14 @@
import logging import logging
from azure.storage.blob import BlobServiceClient, ContainerClient
from itertools import repeat from itertools import repeat
from kn_utils.logging import logger
from operator import attrgetter from operator import attrgetter
from azure.storage.blob import BlobServiceClient, ContainerClient
from dynaconf import Dynaconf
from kn_utils.logging import logger
from retry import retry from retry import retry
from pyinfra.config import Config
from pyinfra.storage.storages.interface import Storage from pyinfra.storage.storages.interface import Storage
from pyinfra.utils.config_validation import azure_storage_validators, validate_settings
logging.getLogger("azure").setLevel(logging.WARNING) logging.getLogger("azure").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING)
@ -74,5 +76,6 @@ class AzureStorage(Storage):
return zip(repeat(bucket_name), map(attrgetter("name"), blobs)) return zip(repeat(bucket_name), map(attrgetter("name"), blobs))
def get_azure_storage_from_config(config: Config): def get_azure_storage_from_settings(settings: Dynaconf):
return AzureStorage(BlobServiceClient.from_connection_string(conn_str=config.storage_azureconnectionstring)) validate_settings(settings, azure_storage_validators)
return AzureStorage(BlobServiceClient.from_connection_string(conn_str=settings.storage.azure.connection_string))

View File

@ -1,12 +1,15 @@
import io import io
from itertools import repeat from itertools import repeat
from operator import attrgetter
from dynaconf import Dynaconf
from kn_utils.logging import logger from kn_utils.logging import logger
from minio import Minio from minio import Minio
from operator import attrgetter
from retry import retry from retry import retry
from pyinfra.config import Config
from pyinfra.storage.storages.interface import Storage from pyinfra.storage.storages.interface import Storage
from pyinfra.utils.config_validation import validate_settings, s3_storage_validators
from pyinfra.utils.url_parsing import validate_and_parse_s3_endpoint
class S3Storage(Storage): class S3Storage(Storage):
@ -63,13 +66,17 @@ class S3Storage(Storage):
return zip(repeat(bucket_name), map(attrgetter("object_name"), objs)) return zip(repeat(bucket_name), map(attrgetter("object_name"), objs))
def get_s3_storage_from_config(config: Config): def get_s3_storage_from_settings(settings: Dynaconf):
validate_settings(settings, s3_storage_validators)
secure, endpoint = validate_and_parse_s3_endpoint(settings.storage.s3.endpoint)
return S3Storage( return S3Storage(
Minio( Minio(
secure=config.storage_secure_connection, secure=secure,
endpoint=config.storage_endpoint, endpoint=endpoint,
access_key=config.storage_key, access_key=settings.storage.s3.key,
secret_key=config.storage_secret, secret_key=settings.storage.s3.secret,
region=config.storage_region, region=settings.storage.s3.region,
) )
) )

View File

@ -13,6 +13,21 @@ queue_manager_validators = [
Validator("rabbitmq.dead_letter_queue", must_exist=True), Validator("rabbitmq.dead_letter_queue", must_exist=True),
] ]
azure_storage_validators = [
Validator("storage.azure.connection_string", must_exist=True),
]
s3_storage_validators = [
Validator("storage.s3.endpoint", must_exist=True),
Validator("storage.s3.key", must_exist=True),
Validator("storage.s3.secret", must_exist=True),
Validator("storage.s3.region", must_exist=True),
]
storage_validators = [
Validator("storage.backend", must_exist=True),
]
def validate_settings(settings: Dynaconf, validators): def validate_settings(settings: Dynaconf, validators):
settings_valid = True settings_valid = True

View File

@ -1,24 +1,20 @@
import pytest import pytest
from pyinfra.storage.storage import get_storage_from_config from pyinfra.storage.storage import get_storage_from_settings
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def storage(storage_backend, bucket_name, settings): def storage(storage_backend, bucket_name, settings):
settings.storage_backend = storage_backend settings.storage.backend = storage_backend
settings.storage_bucket = bucket_name
storage = get_storage_from_config(settings) storage = get_storage_from_settings(settings)
storage.make_bucket(bucket_name) storage.make_bucket(bucket_name)
yield storage yield storage
storage.clear_bucket(bucket_name) storage.clear_bucket(bucket_name)
# @pytest.mark.parametrize("storage_backend", ["azure", "s3"], scope="session") @pytest.mark.parametrize("storage_backend", ["azure", "s3"], scope="session")
# FIXME: Azure storage test needs the secret azure connection string
# when the config is refactored as file, add this and provide file via bitwarden
@pytest.mark.parametrize("storage_backend", ["s3"], scope="session")
@pytest.mark.parametrize("bucket_name", ["bucket"], scope="session") @pytest.mark.parametrize("bucket_name", ["bucket"], scope="session")
class TestStorage: class TestStorage:
def test_clearing_bucket_yields_empty_bucket(self, storage, bucket_name): def test_clearing_bucket_yields_empty_bucket(self, storage, bucket_name):