Source code for ophyd_async.core._providers

import os
import uuid
from abc import abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from datetime import date
from pathlib import Path
from typing import Protocol


[docs] @dataclass class PathInfo: """ Information about where and how to write a file. :param directory_path: Directory into which files should be written :param filename: Base filename to use generated by FilenameProvider, w/o extension :param create_dir_depth: Optional depth of directories to create if they do not exist """ directory_path: Path filename: str create_dir_depth: int = 0
class FilenameProvider(Protocol): @abstractmethod def __call__(self, device_name: str | None = None) -> str: """Get a filename to use for output data, w/o extension""" class PathProvider(Protocol): @abstractmethod def __call__(self, device_name: str | None = None) -> PathInfo: """Get the current directory to write files into""" class StaticFilenameProvider(FilenameProvider): def __init__(self, filename: str): self._static_filename = filename def __call__(self, device_name: str | None = None) -> str: return self._static_filename class UUIDFilenameProvider(FilenameProvider): def __init__( self, uuid_call_func: Callable = uuid.uuid4, uuid_call_args: list | None = None, ): self._uuid_call_func = uuid_call_func self._uuid_call_args = uuid_call_args or [] def __call__(self, device_name: str | None = None) -> str: if ( self._uuid_call_func in [uuid.uuid3, uuid.uuid5] and len(self._uuid_call_args) < 2 ): raise ValueError( f"To use {self._uuid_call_func} to generate UUID filenames," " UUID namespace and name must be passed as args!" ) uuid_str = self._uuid_call_func(*self._uuid_call_args) return f"{uuid_str}" class AutoIncrementFilenameProvider(FilenameProvider): def __init__( self, base_filename: str = "", max_digits: int = 5, starting_value: int = 0, increment: int = 1, inc_delimeter: str = "_", ): self._base_filename = base_filename self._max_digits = max_digits self._current_value = starting_value self._increment = increment self._inc_delimeter = inc_delimeter def __call__(self, device_name: str | None = None) -> str: if len(str(self._current_value)) > self._max_digits: raise ValueError( f"Auto incrementing filename counter \ exceeded maximum of {self._max_digits} digits!" ) padded_counter = f"{self._current_value:0{self._max_digits}}" filename = f"{self._base_filename}{self._inc_delimeter}{padded_counter}" self._current_value += self._increment return filename class StaticPathProvider(PathProvider): def __init__( self, filename_provider: FilenameProvider, directory_path: Path, create_dir_depth: int = 0, ) -> None: self._filename_provider = filename_provider self._directory_path = directory_path self._create_dir_depth = create_dir_depth def __call__(self, device_name: str | None = None) -> PathInfo: filename = self._filename_provider(device_name) return PathInfo( directory_path=self._directory_path, filename=filename, create_dir_depth=self._create_dir_depth, ) class AutoIncrementingPathProvider(PathProvider): def __init__( self, filename_provider: FilenameProvider, base_directory_path: Path, create_dir_depth: int = 0, max_digits: int = 5, starting_value: int = 0, num_calls_per_inc: int = 1, increment: int = 1, inc_delimeter: str = "_", base_name: str | None = None, ) -> None: self._filename_provider = filename_provider self._base_directory_path = base_directory_path self._create_dir_depth = create_dir_depth self._base_name = base_name self._starting_value = starting_value self._current_value = starting_value self._num_calls_per_inc = num_calls_per_inc self._inc_counter = 0 self._max_digits = max_digits self._increment = increment self._inc_delimeter = inc_delimeter def __call__(self, device_name: str | None = None) -> PathInfo: filename = self._filename_provider(device_name) padded_counter = f"{self._current_value:0{self._max_digits}}" auto_inc_dir_name = str(padded_counter) if self._base_name is not None: auto_inc_dir_name = ( f"{self._base_name}{self._inc_delimeter}{padded_counter}" ) elif device_name is not None: auto_inc_dir_name = f"{device_name}{self._inc_delimeter}{padded_counter}" self._inc_counter += 1 if self._inc_counter == self._num_calls_per_inc: self._inc_counter = 0 self._current_value += self._increment return PathInfo( directory_path=self._base_directory_path / auto_inc_dir_name, filename=filename, create_dir_depth=self._create_dir_depth, ) class YMDPathProvider(PathProvider): def __init__( self, filename_provider: FilenameProvider, base_directory_path: Path, create_dir_depth: int = -3, # Default to -3 to create YMD dirs device_name_as_base_dir: bool = False, ) -> None: self._filename_provider = filename_provider self._base_directory_path = Path(base_directory_path) self._create_dir_depth = create_dir_depth self._device_name_as_base_dir = device_name_as_base_dir def __call__(self, device_name: str | None = None) -> PathInfo: sep = os.path.sep current_date = date.today().strftime(f"%Y{sep}%m{sep}%d") if device_name is None: ymd_dir_path = current_date elif self._device_name_as_base_dir: ymd_dir_path = os.path.join( current_date, device_name, ) else: ymd_dir_path = os.path.join( device_name, current_date, ) filename = self._filename_provider(device_name) return PathInfo( directory_path=self._base_directory_path / ymd_dir_path, filename=filename, create_dir_depth=self._create_dir_depth, ) class NameProvider(Protocol): @abstractmethod def __call__(self) -> str: """Get the name to be used as a data_key in the descriptor document""" class DatasetDescriber(Protocol): @abstractmethod async def np_datatype(self) -> str: """Represents the numpy datatype""" @abstractmethod async def shape(self) -> tuple[int, ...]: """Get the shape of the data collection"""