mmar-envops 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 evjava
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: mmar-envops
3
+ Version: 1.0.1
4
+ Summary: Environment and configuration operations for Python applications
5
+ Keywords: environment,configuration,config,env,settings,dotenv
6
+ Author: Tagin
7
+ Author-email: Tagin <evjava@yandex.ru>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Documentation
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Topic :: Utilities
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: pyyaml~=6.0 ; extra == 'all'
23
+ Requires-Dist: gitpython~=3.1 ; extra == 'all'
24
+ Requires-Python: >=3.12
25
+ Provides-Extra: all
26
+ Description-Content-Type: text/markdown
27
+
28
+ # mmar-envops
29
+
30
+ Environment and configuration operations for Python applications.
31
+
32
+ A facade for managing environment variables, configuration files, and system operations in Python applications.
33
+
34
+ ## Features
35
+
36
+ - **Environment file handling**: Read and parse `.env` files
37
+ - **Configuration management**: YAML-based configuration with environment-aware loading
38
+ - **Settings integration**: Pydantic settings support
39
+ - **System operations**: File system, git, and execution context utilities
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install mmar-envops
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ from mmar_envops import EnvFileAccessor, Config
51
+
52
+ # Load environment variables from .env file
53
+ env = EnvFileAccessor.from_file(".env")
54
+ api_key = env.get("API_KEY")
55
+
56
+ # Load configuration from YAML
57
+ config = Config.from_yaml("config.yaml")
58
+ ```
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,35 @@
1
+ # mmar-envops
2
+
3
+ Environment and configuration operations for Python applications.
4
+
5
+ A facade for managing environment variables, configuration files, and system operations in Python applications.
6
+
7
+ ## Features
8
+
9
+ - **Environment file handling**: Read and parse `.env` files
10
+ - **Configuration management**: YAML-based configuration with environment-aware loading
11
+ - **Settings integration**: Pydantic settings support
12
+ - **System operations**: File system, git, and execution context utilities
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install mmar-envops
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ from mmar_envops import EnvFileAccessor, Config
24
+
25
+ # Load environment variables from .env file
26
+ env = EnvFileAccessor.from_file(".env")
27
+ api_key = env.get("API_KEY")
28
+
29
+ # Load configuration from YAML
30
+ config = Config.from_yaml("config.yaml")
31
+ ```
32
+
33
+ ## License
34
+
35
+ MIT
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "mmar-envops"
3
+ # dynamic version is not supported yet on uv_build
4
+ version = "1.0.1"
5
+ description = "Environment and configuration operations for Python applications"
6
+ authors = [{name = "Tagin", email = "evjava@yandex.ru"}]
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ keywords = ["environment", "configuration", "config", "env", "settings", "dotenv"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Documentation",
22
+ "Topic :: Software Development",
23
+ "Topic :: Utilities",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.optional-dependencies]
29
+ all = [
30
+ "pyyaml~=6.0",
31
+ "gitpython~=3.1",
32
+ ]
33
+
34
+ [build-system]
35
+ requires = ["uv_build>=0.8.14,<0.9.0"]
36
+ build-backend = "uv_build"
37
+
38
+ [tool.uv.build-backend]
39
+ module-name = "mmar_envops"
40
+ source-exclude = [".ruff_cache"]
41
+
42
+ [dependency-groups]
43
+ ci = [
44
+ "ruff>=0.4",
45
+ "pytest>=8.2",
46
+ "pytest-asyncio>=1.0.0",
47
+ ]
48
+
49
+ [tool.uv]
50
+ default-groups = ["ci"]
51
+
52
+ [tool.ruff]
53
+ line-length = 120
54
+
55
+ [tool.pytest.ini_options]
56
+ asyncio_mode = "auto"
57
+
58
+ [pytest]
59
+ python_files = [ "test_*.py" ]
60
+ testpaths = [ "tests" ]
61
+
62
+ # action:message_regex:warning_class:module_regex:line
63
+ filterwarnings = [ "error" ]
64
+
65
+ [mypy]
66
+ ignore_missing_imports = true
67
+ exclude = "tests/fixtures/"
68
+ warn_unused_ignores = true
69
+ show_error_codes = true
@@ -0,0 +1,13 @@
1
+ """
2
+ System operations package.
3
+
4
+ This package provides interfaces to system-level operations including:
5
+ - Shell command execution
6
+ - File system operations
7
+ - SSH tunnel management
8
+ - Environment file operations
9
+ - Environment variable parsing
10
+ - Interactive REPL utilities
11
+ - Output formatting and colors
12
+ - Execution context and configuration
13
+ """
@@ -0,0 +1,176 @@
1
+ """Auxiliary very lightweight pydantic-settings alternative"""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import Any, ClassVar, Literal, TypeVar, get_args, get_origin
6
+
7
+ T = TypeVar("T")
8
+
9
+ SupportedTypes = str | int | bool | float
10
+
11
+
12
+ def _str_to_bool(value: str) -> bool:
13
+ """Convert string to boolean."""
14
+ normalized = value.lower()
15
+ if normalized in {"1", "true", "yes", "on"}:
16
+ return True
17
+ if normalized in {"0", "false", "no", "off"}:
18
+ return False
19
+ raise ValueError(f"Invalid bool value: '{value}'")
20
+
21
+
22
+ def _str_to_int(value: str) -> int:
23
+ """Convert string to int."""
24
+ try:
25
+ return int(value)
26
+ except ValueError:
27
+ raise ValueError(f"Invalid int value: '{value}'") from None
28
+
29
+
30
+ def _str_to_float(value: str) -> float:
31
+ """Convert string to float."""
32
+ try:
33
+ return float(value)
34
+ except ValueError:
35
+ return 0.0
36
+
37
+
38
+ def _str_to_literal(value: str, literal_values: tuple) -> Any:
39
+ """Convert string to one of the allowed literal values."""
40
+ for lit_val in literal_values:
41
+ if isinstance(lit_val, str) and value == lit_val:
42
+ return lit_val
43
+ if isinstance(lit_val, bool) and _str_to_bool(value) == lit_val:
44
+ return lit_val
45
+ if isinstance(lit_val, int) and value.isdigit():
46
+ if int(value) == lit_val:
47
+ return lit_val
48
+ if isinstance(lit_val, float):
49
+ try:
50
+ if float(value) == lit_val:
51
+ return lit_val
52
+ except ValueError:
53
+ pass
54
+ raise ValueError(f"Invalid value '{value}' for Literal, expected one of {literal_values}")
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class AkaField:
59
+ """Field descriptor with default value and alias support."""
60
+
61
+ default: Any = None
62
+ alias: str | None = None
63
+
64
+
65
+ class _FieldInfo:
66
+ """Internal field metadata storage."""
67
+
68
+ __slots__ = ("name", "type", "default", "alias", "has_alias", "is_literal", "literal_values")
69
+
70
+ def __init__(self, name: str, type_: type, default: Any, alias: str | None = None):
71
+ self.name = name
72
+ self.type = type_
73
+ self.default = default
74
+ self.alias = alias
75
+ self.has_alias = alias is not None
76
+ self.is_literal = get_origin(type_) is Literal
77
+ self.literal_values = get_args(type_) if self.is_literal else ()
78
+
79
+
80
+ class AkaBaseSettings:
81
+ """Base settings class that reads from environment variables.
82
+
83
+ Features:
84
+ - Field aliases: e.g., debug with alias="d" can be set via DEBUG or D env var
85
+ - Type coercion for str/int/bool/float/Literal
86
+ - Default values
87
+ - Ignores extra environment variables
88
+ """
89
+
90
+ _field_info: ClassVar[dict[str, _FieldInfo]] = {}
91
+
92
+ def __init_subclass__(cls, **kwargs: Any):
93
+ """Parse class annotations and build field metadata."""
94
+ super().__init_subclass__(**kwargs)
95
+
96
+ fields = {}
97
+
98
+ for name, type_ in cls.__annotations__.items():
99
+ default_value = getattr(cls, name, None)
100
+ alias = None
101
+
102
+ if isinstance(default_value, AkaField):
103
+ alias = default_value.alias
104
+ default_value = default_value.default
105
+ elif default_value is None:
106
+ # For None defaults, check if it's Optional or required
107
+ pass
108
+
109
+ fields[name] = _FieldInfo(name, type_, default_value, alias)
110
+
111
+ cls._field_info = fields
112
+
113
+ def __init__(self, **kwargs: Any):
114
+ """Initialize settings from environment variables and keyword arguments.
115
+
116
+ Priority: environment variables > passed kwargs > default values.
117
+ Extra kwargs that don't match any field are ignored.
118
+ """
119
+ self._set_values(**kwargs)
120
+
121
+ def _set_values(self, **kwargs: Any) -> None:
122
+ """Set field values from environment, kwargs, or defaults."""
123
+ for info in self._field_info.values():
124
+ value = self._get_value(info, **kwargs)
125
+ object.__setattr__(self, info.name, value)
126
+
127
+ def _get_value(self, info: _FieldInfo, **kwargs: Any) -> Any:
128
+ """Get value for a field from env, kwargs, or default.
129
+
130
+ Priority:
131
+ 1. Environment variable (alias first, then field name)
132
+ 2. Passed kwargs
133
+ 3. Default value
134
+ """
135
+ # Check environment by alias first, then by field name
136
+ env_value = None
137
+
138
+ if info.has_alias:
139
+ env_value = os.environ.get(info.alias.upper())
140
+
141
+ if env_value is None:
142
+ env_value = os.environ.get(info.name.upper())
143
+
144
+ if env_value is not None:
145
+ return self._convert_type(env_value, info.type, info.is_literal, info.literal_values)
146
+
147
+ # Check kwargs by alias first, then by field name
148
+ if info.has_alias and info.alias in kwargs:
149
+ kwarg_value = kwargs[info.alias]
150
+ return self._convert_type(str(kwarg_value), info.type, info.is_literal, info.literal_values)
151
+
152
+ if info.name in kwargs:
153
+ kwarg_value = kwargs[info.name]
154
+ return self._convert_type(str(kwarg_value), info.type, info.is_literal, info.literal_values)
155
+
156
+ return info.default
157
+
158
+ def _convert_type(self, value: str, target_type: type, is_literal: bool, literal_values: tuple) -> Any:
159
+ """Convert string value to target type."""
160
+
161
+ if is_literal:
162
+ return _str_to_literal(value, literal_values)
163
+
164
+ if target_type is str:
165
+ return value
166
+
167
+ if target_type is int:
168
+ return _str_to_int(value)
169
+
170
+ if target_type is float:
171
+ return _str_to_float(value)
172
+
173
+ if target_type is bool:
174
+ return _str_to_bool(value)
175
+
176
+ return value
@@ -0,0 +1,39 @@
1
+ import os
2
+ from typing import Literal
3
+
4
+ from mmar_envops.aka_pydantic_settings import AkaBaseSettings, AkaField
5
+
6
+
7
+ class ExecConfig(AkaBaseSettings):
8
+ debug: bool = AkaField(default=False, alias="d")
9
+ time: bool = AkaField(default=False, alias="t")
10
+ autoyes: bool = AkaField(default=False)
11
+ dry: bool = False
12
+ color: bool = True
13
+ ripgrep_max_columns: int = 500
14
+ prefer_env_keys: Literal["upper", "lower", "no"] = "no"
15
+
16
+ def fix_key_case(self, key: str):
17
+ pref = self.prefer_env_keys
18
+ match pref:
19
+ case "upper":
20
+ return key.upper()
21
+ case "lower":
22
+ return key.lower()
23
+ case _:
24
+ return key
25
+
26
+
27
+ def is_debug():
28
+ env = os.environ
29
+ return (env.get("debug") or env.get("d")) in {"1", "true", "yes"} or False
30
+
31
+
32
+ def is_force():
33
+ env = os.environ
34
+ return env.get("force") in {"1", "true", "yes"} or False
35
+
36
+
37
+ def is_autoyes():
38
+ env = os.environ
39
+ return (env.get("autoyes")) in {"1", "true", "yes"} or False