Coverage for src/models/variables.py: 100.00%
121 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-18 15:02 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-18 15:02 +0000
1#!/usr/bin/env python3
3# Standard libraries
4from argparse import Namespace
5from enum import Enum
6from os import environ
7from pathlib import Path
8from typing import Dict, List
10# Components
11from ..types.environment import Environment
13# VariableName type
14VariableName = str
16# VariableValue type
17VariableValue = str
19# Variables type
20Variables = Dict[VariableName, VariableValue]
22# VariablesParser class
23class VariablesParser:
25 # Enumerations
26 Types = Enum('Types', [
27 'PARAMETERS',
28 'LOCALS',
29 'GLOBALS',
30 'ENV_FILES',
31 ])
33 # Members
34 __cache: Variables
35 __env_files: Variables
36 __globals: Variables
37 __locals: Variables
38 __options: Namespace
39 __parameters: Variables
41 # Constructor
42 def __init__(
43 self,
44 options: Namespace,
45 ) -> None:
47 # Prepare options
48 self.__options = options
50 # Prepare members
51 self.__env_files = {}
52 self.__globals = {}
53 self.__locals = {}
54 self.__parameters = {}
56 # Cache environment
57 self.environment_cache()
59 # Parse env options
60 self.options_parse_env()
62 # Cache environment
63 def environment_cache(self) -> None:
64 self.__cache = environ.copy()
66 # Restore environment
67 def environment_restore(self) -> None:
68 environ.clear()
69 environ.update(self.__cache)
71 # Update environment
72 @staticmethod
73 def environment_update(values: Dict[str, str]) -> None:
75 # Apply environment values
76 environ.update(values)
78 # Apply variables
79 def apply(
80 self,
81 types: List[Types],
82 ) -> None:
84 # Prepare parameters variables
85 if VariablesParser.Types.PARAMETERS in types:
86 for variable, value in self.__parameters.items():
87 environ[variable] = value
89 # Prepare locals variables
90 if VariablesParser.Types.LOCALS in types:
91 for variable, value in self.__locals.items():
92 if variable not in environ:
93 environ[variable] = value
95 # Prepare globals variables
96 if VariablesParser.Types.GLOBALS in types:
97 for variable, value in self.__globals.items():
98 if variable not in environ:
99 environ[variable] = value
101 # Prepare env_files variables
102 if VariablesParser.Types.ENV_FILES in types:
103 for variable, value in self.__env_files.items():
104 if variable not in environ:
105 environ[variable] = value
107 # Evaluate job variables
108 def evaluate_job(
109 self,
110 job_variables: Dict[str, str],
111 ) -> Dict[str, str]:
113 # Variables
114 variables: Variables = {}
116 # Register parameters variables
117 for variable, value in self.__parameters.items():
118 variables[variable] = value
120 # Register locals variables
121 for variable, value in self.__locals.items():
122 if variable not in variables:
123 variables[variable] = value
125 # Register job variables
126 for variable, value in job_variables.items():
127 if variable not in variables:
128 variables[variable] = str(value)
130 # Register globals variables
131 for variable, value in self.__globals.items():
132 if variable not in variables:
133 variables[variable] = value
135 # Register env_files variables
136 for variable, value in self.__env_files.items():
137 if variable not in variables:
138 variables[variable] = value
140 # Result
141 return variables
143 # Expand value
144 def expand(
145 self,
146 value: str,
147 types: List[Types],
148 ) -> str:
150 # Cache environment
151 cache = environ.copy()
153 # Apply variables
154 self.apply(types)
156 # Expand environment
157 result = Environment.expand(value)
159 # Restore environment
160 environ.clear()
161 environ.update(cache)
163 # Result
164 return result
166 # Parse options
167 def options_parse_env(self) -> None:
169 # Variables
170 files: List[Path] = []
172 # Register .env file
173 files += [
174 Path(self.__options.path) / '.env',
175 ]
177 # Parse env options
178 if self.__options.env:
179 for env in self.__options.env:
180 env_parsed = env.split('=', 1)
182 # Parse VARIABLE=value
183 if len(env_parsed) == 2:
184 variable = env_parsed[0]
185 value = env_parsed[1]
186 self.__parameters[variable] = value
188 # Parse ENVIRONMENT_FILE
189 elif (Path(self.__options.path) / env).is_file():
190 files += [Path(self.__options.path) / env]
192 # Parse VARIABLE
193 else:
194 variable = env
195 if variable in environ:
196 self.__parameters[variable] = environ[variable]
198 # Parse environment files
199 if files:
201 # Modules libraries, pylint: disable=import-outside-toplevel
202 from dotenv import dotenv_values
204 # Iterate through environment files
205 for env_file in files:
206 try:
207 if not env_file.is_file():
208 continue
209 except PermissionError: # pragma: no cover
210 continue
212 # Parse environment files
213 env_values = dotenv_values(dotenv_path=env_file)
214 for variable in env_values:
216 # Define default environment variable
217 if isinstance(env_values[variable], str):
218 self.__env_files[variable] = str(env_values[variable])
220 # Parse local_env
221 def local_parse_env(
222 self,
223 data_node: List[str],
224 ) -> None:
226 # Parse data node
227 for env in data_node:
228 env_parsed = env.split('=', 1)
230 # Parse VARIABLE=value
231 if len(env_parsed) == 2:
232 variable = env_parsed[0]
233 value = env_parsed[1]
234 if variable not in self.__locals:
235 self.__locals[variable] = value
237 # Parse ENVIRONMENT_FILE
238 elif (Path(self.__options.path) / env).is_file():
240 # Modules libraries, pylint: disable=import-outside-toplevel
241 from dotenv import dotenv_values
243 # Parse environment file
244 env_file = Path(self.__options.path) / env
245 env_values = dotenv_values(dotenv_path=env_file)
246 for variable in env_values:
248 # Define default environment variable
249 if variable not in self.__locals and isinstance(
250 env_values[variable], str):
251 self.__locals[variable] = str(env_values[variable])
253 # Parse VARIABLE
254 else:
255 variable = env
256 if variable not in self.__locals and variable in environ:
257 self.__locals[variable] = environ[variable]
259 # Parse local_variables
260 def local_parse_variables(
261 self,
262 data_node: Dict[str, str],
263 ) -> None:
265 # Parse data node
266 for variable in data_node:
267 value = data_node[variable]
268 if variable not in self.__locals:
269 self.__locals[variable] = str(value)
271 # Globals
272 @property
273 def globals(self) -> Dict[str, str]:
274 return self.__globals
276 # Parameters
277 @property
278 def parameters(self) -> Dict[str, str]:
279 return self.__parameters