| 
1 | 1 | """Environment variable management for MDIO operations."""  | 
2 | 2 | 
 
  | 
3 | 3 | from os import getenv  | 
4 |  | -from typing import Final  | 
5 | 4 | 
 
  | 
6 | 5 | from psutil import cpu_count  | 
7 | 6 | 
 
  | 
8 | 7 | from mdio.converters.exceptions import EnvironmentFormatError  | 
9 | 8 | 
 
  | 
 | 9 | +# Environment variable keys  | 
 | 10 | +_EXPORT_CPUS_KEY = "MDIO__EXPORT__CPU_COUNT"  | 
 | 11 | +_IMPORT_CPUS_KEY = "MDIO__IMPORT__CPU_COUNT"  | 
 | 12 | +_GRID_SPARSITY_RATIO_WARN_KEY = "MDIO__GRID__SPARSITY_RATIO_WARN"  | 
 | 13 | +_GRID_SPARSITY_RATIO_LIMIT_KEY = "MDIO__GRID__SPARSITY_RATIO_LIMIT"  | 
 | 14 | +_SAVE_SEGY_FILE_HEADER_KEY = "MDIO__IMPORT__SAVE_SEGY_FILE_HEADER"  | 
 | 15 | +_MDIO_SEGY_SPEC_KEY = "MDIO__SEGY__SPEC"  | 
 | 16 | +_RAW_HEADERS_KEY = "MDIO__IMPORT__RAW_HEADERS"  | 
 | 17 | +_IGNORE_CHECKS_KEY = "MDIO_IGNORE_CHECKS"  | 
 | 18 | +_CLOUD_NATIVE_KEY = "MDIO__IMPORT__CLOUD_NATIVE"  | 
10 | 19 | 
 
  | 
11 |  | -class Environment:  | 
12 |  | -    """Unified API for accessing and validating MDIO environment variables."""  | 
13 |  | - | 
14 |  | -    # Environment variable keys and defaults  | 
15 |  | -    _EXPORT_CPUS_KEY: Final[str] = "MDIO__EXPORT__CPU_COUNT"  | 
16 |  | -    _IMPORT_CPUS_KEY: Final[str] = "MDIO__IMPORT__CPU_COUNT"  | 
17 |  | -    _GRID_SPARSITY_RATIO_WARN_KEY: Final[str] = "MDIO__GRID__SPARSITY_RATIO_WARN"  | 
18 |  | -    _GRID_SPARSITY_RATIO_LIMIT_KEY: Final[str] = "MDIO__GRID__SPARSITY_RATIO_LIMIT"  | 
19 |  | -    _SAVE_SEGY_FILE_HEADER_KEY: Final[str] = "MDIO__IMPORT__SAVE_SEGY_FILE_HEADER"  | 
20 |  | -    _MDIO_SEGY_SPEC_KEY: Final[str] = "MDIO__SEGY__SPEC"  | 
21 |  | -    _RAW_HEADERS_KEY: Final[str] = "MDIO__IMPORT__RAW_HEADERS"  | 
22 |  | -    _IGNORE_CHECKS_KEY: Final[str] = "MDIO_IGNORE_CHECKS"  | 
23 |  | -    _CLOUD_NATIVE_KEY: Final[str] = "MDIO__IMPORT__CLOUD_NATIVE"  | 
24 |  | - | 
25 |  | -    # Default values  | 
26 |  | -    _EXPORT_CPUS_DEFAULT: Final[int] = cpu_count(logical=True)  | 
27 |  | -    _IMPORT_CPUS_DEFAULT: Final[int] = cpu_count(logical=True)  | 
28 |  | -    _GRID_SPARSITY_RATIO_WARN_DEFAULT: Final[str] = "2"  | 
29 |  | -    _GRID_SPARSITY_RATIO_LIMIT_DEFAULT: Final[str] = "10"  | 
30 |  | -    _SAVE_SEGY_FILE_HEADER_DEFAULT: Final[str] = "false"  | 
31 |  | -    _MDIO_SEGY_SPEC_DEFAULT: Final[None] = None  | 
32 |  | -    _RAW_HEADERS_DEFAULT: Final[str] = "false"  | 
33 |  | -    _IGNORE_CHECKS_DEFAULT: Final[str] = "false"  | 
34 |  | -    _CLOUD_NATIVE_DEFAULT: Final[str] = "false"  | 
35 |  | - | 
36 |  | -    @classmethod  | 
37 |  | -    def _get_env_value(cls, key: str, default: str | int | None) -> str | None:  | 
38 |  | -        """Get environment variable value with fallback to default."""  | 
39 |  | -        if isinstance(default, int):  | 
40 |  | -            default = str(default)  | 
41 |  | -        return getenv(key, default)  | 
42 |  | - | 
43 |  | -    @staticmethod  | 
44 |  | -    def _parse_bool(value: str | None) -> bool:  | 
45 |  | -        """Parse string value to boolean."""  | 
46 |  | -        if value is None:  | 
47 |  | -            return False  | 
48 |  | -        return value.lower() in ("1", "true", "yes", "on")  | 
49 |  | - | 
50 |  | -    @staticmethod  | 
51 |  | -    def _parse_int(value: str | None, key: str) -> int:  | 
52 |  | -        """Parse string value to integer with validation."""  | 
53 |  | -        if value is None:  | 
54 |  | -            raise EnvironmentFormatError(key, "int")  | 
55 |  | -        try:  | 
56 |  | -            return int(value)  | 
57 |  | -        except ValueError as e:  | 
58 |  | -            raise EnvironmentFormatError(key, "int") from e  | 
59 |  | - | 
60 |  | -    @staticmethod  | 
61 |  | -    def _parse_float(value: str | None, key: str) -> float:  | 
62 |  | -        """Parse string value to float with validation."""  | 
63 |  | -        if value is None:  | 
64 |  | -            raise EnvironmentFormatError(key, "float")  | 
65 |  | -        try:  | 
66 |  | -            return float(value)  | 
67 |  | -        except ValueError as e:  | 
68 |  | -            raise EnvironmentFormatError(key, "float") from e  | 
69 |  | - | 
70 |  | -    @classmethod  | 
71 |  | -    def export_cpus(cls) -> int:  | 
72 |  | -        """Number of CPUs to use for export operations."""  | 
73 |  | -        value = cls._get_env_value(cls._EXPORT_CPUS_KEY, cls._EXPORT_CPUS_DEFAULT)  | 
74 |  | -        return cls._parse_int(value, cls._EXPORT_CPUS_KEY)  | 
75 |  | - | 
76 |  | -    @classmethod  | 
77 |  | -    def import_cpus(cls) -> int:  | 
78 |  | -        """Number of CPUs to use for import operations."""  | 
79 |  | -        value = cls._get_env_value(cls._IMPORT_CPUS_KEY, cls._IMPORT_CPUS_DEFAULT)  | 
80 |  | -        return cls._parse_int(value, cls._IMPORT_CPUS_KEY)  | 
81 |  | - | 
82 |  | -    @classmethod  | 
83 |  | -    def grid_sparsity_ratio_warn(cls) -> float:  | 
84 |  | -        """Sparsity ratio threshold for warnings."""  | 
85 |  | -        value = cls._get_env_value(cls._GRID_SPARSITY_RATIO_WARN_KEY, cls._GRID_SPARSITY_RATIO_WARN_DEFAULT)  | 
86 |  | -        return cls._parse_float(value, cls._GRID_SPARSITY_RATIO_WARN_KEY)  | 
87 |  | - | 
88 |  | -    @classmethod  | 
89 |  | -    def grid_sparsity_ratio_limit(cls) -> float:  | 
90 |  | -        """Sparsity ratio threshold for errors."""  | 
91 |  | -        value = cls._get_env_value(cls._GRID_SPARSITY_RATIO_LIMIT_KEY, cls._GRID_SPARSITY_RATIO_LIMIT_DEFAULT)  | 
92 |  | -        return cls._parse_float(value, cls._GRID_SPARSITY_RATIO_LIMIT_KEY)  | 
93 |  | - | 
94 |  | -    @classmethod  | 
95 |  | -    def save_segy_file_header(cls) -> bool:  | 
96 |  | -        """Whether to save SEG-Y file headers."""  | 
97 |  | -        value = cls._get_env_value(cls._SAVE_SEGY_FILE_HEADER_KEY, cls._SAVE_SEGY_FILE_HEADER_DEFAULT)  | 
98 |  | -        return cls._parse_bool(value)  | 
99 |  | - | 
100 |  | -    @classmethod  | 
101 |  | -    def mdio_segy_spec(cls) -> str | None:  | 
102 |  | -        """Path to MDIO SEG-Y specification file."""  | 
103 |  | -        return cls._get_env_value(cls._MDIO_SEGY_SPEC_KEY, cls._MDIO_SEGY_SPEC_DEFAULT)  | 
104 |  | - | 
105 |  | -    @classmethod  | 
106 |  | -    def raw_headers(cls) -> bool:  | 
107 |  | -        """Whether to preserve raw headers."""  | 
108 |  | -        value = cls._get_env_value(cls._RAW_HEADERS_KEY, cls._RAW_HEADERS_DEFAULT)  | 
109 |  | -        return cls._parse_bool(value)  | 
110 |  | - | 
111 |  | -    @classmethod  | 
112 |  | -    def ignore_checks(cls) -> bool:  | 
113 |  | -        """Whether to ignore validation checks."""  | 
114 |  | -        value = cls._get_env_value(cls._IGNORE_CHECKS_KEY, cls._IGNORE_CHECKS_DEFAULT)  | 
115 |  | -        return cls._parse_bool(value)  | 
116 |  | - | 
117 |  | -    @classmethod  | 
118 |  | -    def cloud_native(cls) -> bool:  | 
119 |  | -        """Whether to use cloud-native mode for SEG-Y processing."""  | 
120 |  | -        value = cls._get_env_value(cls._CLOUD_NATIVE_KEY, cls._CLOUD_NATIVE_DEFAULT)  | 
121 |  | -        return cls._parse_bool(value)  | 
 | 20 | +# Default values  | 
 | 21 | +_EXPORT_CPUS_DEFAULT = cpu_count(logical=True)  | 
 | 22 | +_IMPORT_CPUS_DEFAULT = cpu_count(logical=True)  | 
 | 23 | +_GRID_SPARSITY_RATIO_WARN_DEFAULT = "2"  | 
 | 24 | +_GRID_SPARSITY_RATIO_LIMIT_DEFAULT = "10"  | 
 | 25 | +_SAVE_SEGY_FILE_HEADER_DEFAULT = "false"  | 
 | 26 | +_MDIO_SEGY_SPEC_DEFAULT = None  | 
 | 27 | +_RAW_HEADERS_DEFAULT = "false"  | 
 | 28 | +_IGNORE_CHECKS_DEFAULT = "false"  | 
 | 29 | +_CLOUD_NATIVE_DEFAULT = "false"  | 
 | 30 | + | 
 | 31 | + | 
 | 32 | +def _get_env_value(key: str, default: str | int | None) -> str | None:  | 
 | 33 | +    """Get environment variable value with fallback to default."""  | 
 | 34 | +    if isinstance(default, int):  | 
 | 35 | +        default = str(default)  | 
 | 36 | +    return getenv(key, default)  | 
 | 37 | + | 
 | 38 | + | 
 | 39 | +def _parse_bool(value: str | None) -> bool:  | 
 | 40 | +    """Parse string value to boolean."""  | 
 | 41 | +    if value is None:  | 
 | 42 | +        return False  | 
 | 43 | +    return value.lower() in ("1", "true", "yes", "on")  | 
 | 44 | + | 
 | 45 | + | 
 | 46 | +def _parse_int(value: str | None, key: str) -> int:  | 
 | 47 | +    """Parse string value to integer with validation."""  | 
 | 48 | +    if value is None:  | 
 | 49 | +        raise EnvironmentFormatError(key, "int")  | 
 | 50 | +    try:  | 
 | 51 | +        return int(value)  | 
 | 52 | +    except ValueError as e:  | 
 | 53 | +        raise EnvironmentFormatError(key, "int") from e  | 
 | 54 | + | 
 | 55 | + | 
 | 56 | +def _parse_float(value: str | None, key: str) -> float:  | 
 | 57 | +    """Parse string value to float with validation."""  | 
 | 58 | +    if value is None:  | 
 | 59 | +        raise EnvironmentFormatError(key, "float")  | 
 | 60 | +    try:  | 
 | 61 | +        return float(value)  | 
 | 62 | +    except ValueError as e:  | 
 | 63 | +        raise EnvironmentFormatError(key, "float") from e  | 
 | 64 | + | 
 | 65 | + | 
 | 66 | +def export_cpus() -> int:  | 
 | 67 | +    """Number of CPUs to use for export operations."""  | 
 | 68 | +    value = _get_env_value(_EXPORT_CPUS_KEY, _EXPORT_CPUS_DEFAULT)  | 
 | 69 | +    return _parse_int(value, _EXPORT_CPUS_KEY)  | 
 | 70 | + | 
 | 71 | + | 
 | 72 | +def import_cpus() -> int:  | 
 | 73 | +    """Number of CPUs to use for import operations."""  | 
 | 74 | +    value = _get_env_value(_IMPORT_CPUS_KEY, _IMPORT_CPUS_DEFAULT)  | 
 | 75 | +    return _parse_int(value, _IMPORT_CPUS_KEY)  | 
 | 76 | + | 
 | 77 | + | 
 | 78 | +def grid_sparsity_ratio_warn() -> float:  | 
 | 79 | +    """Sparsity ratio threshold for warnings."""  | 
 | 80 | +    value = _get_env_value(_GRID_SPARSITY_RATIO_WARN_KEY, _GRID_SPARSITY_RATIO_WARN_DEFAULT)  | 
 | 81 | +    return _parse_float(value, _GRID_SPARSITY_RATIO_WARN_KEY)  | 
 | 82 | + | 
 | 83 | + | 
 | 84 | +def grid_sparsity_ratio_limit() -> float:  | 
 | 85 | +    """Sparsity ratio threshold for errors."""  | 
 | 86 | +    value = _get_env_value(_GRID_SPARSITY_RATIO_LIMIT_KEY, _GRID_SPARSITY_RATIO_LIMIT_DEFAULT)  | 
 | 87 | +    return _parse_float(value, _GRID_SPARSITY_RATIO_LIMIT_KEY)  | 
 | 88 | + | 
 | 89 | + | 
 | 90 | +def save_segy_file_header() -> bool:  | 
 | 91 | +    """Whether to save SEG-Y file headers."""  | 
 | 92 | +    value = _get_env_value(_SAVE_SEGY_FILE_HEADER_KEY, _SAVE_SEGY_FILE_HEADER_DEFAULT)  | 
 | 93 | +    return _parse_bool(value)  | 
 | 94 | + | 
 | 95 | + | 
 | 96 | +def mdio_segy_spec() -> str | None:  | 
 | 97 | +    """Path to MDIO SEG-Y specification file."""  | 
 | 98 | +    return _get_env_value(_MDIO_SEGY_SPEC_KEY, _MDIO_SEGY_SPEC_DEFAULT)  | 
 | 99 | + | 
 | 100 | + | 
 | 101 | +def raw_headers() -> bool:  | 
 | 102 | +    """Whether to preserve raw headers."""  | 
 | 103 | +    value = _get_env_value(_RAW_HEADERS_KEY, _RAW_HEADERS_DEFAULT)  | 
 | 104 | +    return _parse_bool(value)  | 
 | 105 | + | 
 | 106 | + | 
 | 107 | +def ignore_checks() -> bool:  | 
 | 108 | +    """Whether to ignore validation checks."""  | 
 | 109 | +    value = _get_env_value(_IGNORE_CHECKS_KEY, _IGNORE_CHECKS_DEFAULT)  | 
 | 110 | +    return _parse_bool(value)  | 
 | 111 | + | 
 | 112 | + | 
 | 113 | +def cloud_native() -> bool:  | 
 | 114 | +    """Whether to use cloud-native mode for SEG-Y processing."""  | 
 | 115 | +    value = _get_env_value(_CLOUD_NATIVE_KEY, _CLOUD_NATIVE_DEFAULT)  | 
 | 116 | +    return _parse_bool(value)  | 
0 commit comments