orionis 0.286.0__py3-none-any.whl → 0.288.0__py3-none-any.whl
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.
- orionis/metadata/framework.py +1 -1
- orionis/services/environment/contracts/env.py +45 -50
- orionis/services/environment/contracts/types.py +70 -0
- orionis/services/environment/dot_env.py +204 -182
- orionis/services/environment/env.py +68 -85
- orionis/services/{standard/exceptions/path_value_exceptions.py → environment/exceptions/environment_value_error.py} +2 -12
- orionis/services/environment/exceptions/environment_value_exception.py +23 -0
- orionis/services/environment/types.py +578 -0
- orionis/services/paths/exceptions/not_found_exceptions.py +15 -12
- orionis/services/paths/exceptions/path_value_exceptions.py +13 -10
- orionis/services/standard/contracts/std.py +1 -1
- orionis/services/standard/exceptions/std_value_exception.py +23 -0
- orionis/services/standard/std.py +2 -2
- orionis/services/system/contracts/imports.py +27 -7
- orionis/services/system/contracts/workers.py +8 -3
- orionis/services/system/imports.py +31 -12
- orionis/services/system/runtime_imports.py +33 -23
- orionis/services/system/workers.py +6 -8
- orionis/services/wrapper/dicts/dot_dict.py +82 -41
- orionis/test/logs/history.py +3 -5
- orionis/unittesting.py +12 -12
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/METADATA +1 -1
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/RECORD +76 -70
- tests/example/test_example.py +5 -2
- tests/foundation/config/app/{test_app.py → test_foundation_config_app.py} +14 -4
- tests/foundation/config/auth/{test_auth.py → test_foundation_config_auth.py} +10 -5
- tests/foundation/config/cache/{test_cache.py → test_foundation_config_cache.py} +61 -16
- tests/foundation/config/cache/test_foundation_config_cache_file.py +126 -0
- tests/foundation/config/cache/test_foundation_config_cache_stores.py +148 -0
- tests/foundation/config/cors/test_foundation_config_cors.py +190 -0
- tests/foundation/config/database/{test_database.py → test_foundation_config_database.py} +39 -15
- tests/foundation/config/database/{test_database_connections.py → test_foundation_config_database_connections.py} +80 -6
- tests/foundation/config/database/{test_database_mysql.py → test_foundation_config_database_mysql.py} +139 -16
- tests/foundation/config/database/{test_database_oracle.py → test_foundation_config_database_oracle.py} +111 -27
- tests/foundation/config/database/{test_database_pgsql.py → test_foundation_config_database_pgsql.py} +97 -27
- tests/foundation/config/database/{test_database_sqlite.py → test_foundation_config_database_sqlite.py} +57 -3
- tests/foundation/config/exceptions/{test_exceptions_integrity.py → test_foundation_config_exceptions.py} +45 -11
- tests/foundation/config/filesystems/{test_filesystems.py → test_foundation_config_filesystems.py} +65 -15
- tests/foundation/config/filesystems/{test_filesystems_aws.py → test_foundation_config_filesystems_aws.py} +46 -8
- tests/foundation/config/filesystems/{test_filesystems_disks.py → test_foundation_config_filesystems_disks.py} +79 -9
- tests/foundation/config/filesystems/{test_filesystems_local.py → test_foundation_config_filesystems_local.py} +67 -19
- tests/foundation/config/filesystems/{test_filesystems_public.py → test_foundation_config_filesystems_public.py} +38 -1
- tests/foundation/config/logging/test_foundation_config_logging.py +112 -0
- tests/foundation/config/logging/{test_logging_channels.py → test_foundation_config_logging_channels.py} +80 -3
- tests/foundation/config/logging/{test_logging_chunked.py → test_foundation_config_logging_chunked.py} +86 -13
- tests/foundation/config/logging/{test_logging_daily.py → test_foundation_config_logging_daily.py} +80 -13
- tests/foundation/config/logging/{test_logging_hourly.py → test_foundation_config_logging_hourly.py} +69 -3
- tests/foundation/config/logging/{test_logging_monthly.py → test_foundation_config_logging_monthly.py} +49 -3
- tests/foundation/config/logging/{test_logging_stack.py → test_foundation_config_logging_stack.py} +50 -15
- tests/foundation/config/logging/{test_logging_weekly.py → test_foundation_config_logging_weekly.py} +93 -3
- tests/foundation/config/mail/test_foundation_config_mail.py +145 -0
- tests/foundation/config/mail/{test_mail_file.py → test_foundation_config_mail_file.py} +41 -5
- tests/foundation/config/mail/test_foundation_config_mail_mailers.py +106 -0
- tests/foundation/config/mail/{test_mail_smtp.py → test_foundation_config_mail_smtp.py} +59 -15
- tests/foundation/config/queue/test_foundation_config_queue.py +111 -0
- tests/foundation/config/queue/{test_queue_brokers.py → test_foundation_config_queue_brokers.py} +28 -11
- tests/foundation/config/queue/{test_queue_database.py → test_foundation_config_queue_database.py} +54 -16
- tests/foundation/config/root/{test_root_paths.py → test_foundation_config_root_paths.py} +70 -3
- tests/foundation/config/session/{test_session.py → test_foundation_config_session.py} +31 -2
- tests/foundation/config/startup/{test_config_startup.py → test_foundation_config_startup.py} +91 -21
- tests/foundation/config/testing/{test_testing.py → test_foundation_config_testing.py} +69 -1
- tests/patterns/singleton/test_patterns_singleton.py +27 -0
- tests/services/asynchrony/{test_async_io.py → test_services_asynchrony_coroutine.py} +1 -1
- tests/services/environment/test_services_environment.py +93 -0
- tests/services/path/{test_resolver.py → test_services_resolver.py} +51 -12
- tests/services/standard/{test_std.py → test_services_std.py} +45 -22
- tests/services/system/__init__.py +0 -0
- tests/services/system/test_services_system_imports.py +101 -0
- tests/services/system/test_services_system_workers.py +89 -0
- tests/services/wrapper/{test_wrapper_doc_dict.py → test_services_wrapper_docdict.py} +28 -16
- tests/testing/test_testing_result.py +57 -20
- tests/testing/test_testing_unit.py +110 -41
- orionis/services/environment/exceptions/value_exception.py +0 -27
- tests/foundation/config/cache/test_cache_file.py +0 -78
- tests/foundation/config/cache/test_cache_stores.py +0 -88
- tests/foundation/config/cors/test_cors.py +0 -121
- tests/foundation/config/logging/test_logging.py +0 -48
- tests/foundation/config/mail/test_mail.py +0 -73
- tests/foundation/config/mail/test_mail_mailers.py +0 -58
- tests/foundation/config/queue/test_queue.py +0 -58
- tests/patterns/singleton/test_singleton.py +0 -18
- tests/services/environment/test_env.py +0 -155
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/WHEEL +0 -0
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/top_level.txt +0 -0
- {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/zip-safe +0 -0
orionis/metadata/framework.py
CHANGED
@@ -1,36 +1,47 @@
|
|
1
|
-
from typing import Any, Dict
|
1
|
+
from typing import Any, Dict
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
|
4
4
|
class IEnv(ABC):
|
5
|
-
"""Interface contract for environment management operations."""
|
6
5
|
|
7
6
|
@staticmethod
|
8
7
|
@abstractmethod
|
9
|
-
def get(key: str, default:
|
8
|
+
def get(key: str, default: Any = None) -> Any:
|
10
9
|
"""
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
The
|
10
|
+
Retrieve the value of an environment variable by key.
|
11
|
+
|
12
|
+
Parameters
|
13
|
+
----------
|
14
|
+
key : str
|
15
|
+
The name of the environment variable to retrieve.
|
16
|
+
default : Any, optional
|
17
|
+
The value to return if the key is not found. Default is None.
|
18
|
+
|
19
|
+
Returns
|
20
|
+
-------
|
21
|
+
Any
|
22
|
+
The value of the environment variable if found, otherwise the default value.
|
19
23
|
"""
|
20
24
|
pass
|
21
25
|
|
22
26
|
@staticmethod
|
23
27
|
@abstractmethod
|
24
|
-
def set(key: str, value: str) -> bool:
|
28
|
+
def set(key: str, value: str, type: str = None) -> bool:
|
25
29
|
"""
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
Set an environment variable in the .env file.
|
31
|
+
|
32
|
+
Parameters
|
33
|
+
----------
|
34
|
+
key : str
|
35
|
+
The name of the environment variable to set.
|
36
|
+
value : str
|
37
|
+
The value to assign to the environment variable.
|
38
|
+
type : str, optional
|
39
|
+
The type of the environment variable (e.g., 'str', 'int'). Default is None.
|
40
|
+
|
41
|
+
Returns
|
42
|
+
-------
|
43
|
+
bool
|
44
|
+
True if the variable was set successfully, False otherwise.
|
34
45
|
"""
|
35
46
|
pass
|
36
47
|
|
@@ -38,13 +49,17 @@ class IEnv(ABC):
|
|
38
49
|
@abstractmethod
|
39
50
|
def unset(key: str) -> bool:
|
40
51
|
"""
|
41
|
-
|
52
|
+
Remove the specified environment variable from the .env file.
|
42
53
|
|
43
|
-
|
44
|
-
|
54
|
+
Parameters
|
55
|
+
----------
|
56
|
+
key : str
|
57
|
+
The name of the environment variable to remove.
|
45
58
|
|
46
|
-
Returns
|
47
|
-
|
59
|
+
Returns
|
60
|
+
-------
|
61
|
+
bool
|
62
|
+
True if the variable was successfully removed, False otherwise.
|
48
63
|
"""
|
49
64
|
pass
|
50
65
|
|
@@ -52,31 +67,11 @@ class IEnv(ABC):
|
|
52
67
|
@abstractmethod
|
53
68
|
def all() -> Dict[str, Any]:
|
54
69
|
"""
|
55
|
-
|
56
|
-
|
57
|
-
Returns:
|
58
|
-
Dictionary of all key-value pairs
|
59
|
-
"""
|
60
|
-
pass
|
61
|
-
|
62
|
-
@staticmethod
|
63
|
-
@abstractmethod
|
64
|
-
def toJson() -> str:
|
65
|
-
"""
|
66
|
-
Serializes environment to JSON.
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
JSON string representation
|
70
|
-
"""
|
71
|
-
pass
|
72
|
-
|
73
|
-
@staticmethod
|
74
|
-
@abstractmethod
|
75
|
-
def toBase64() -> str:
|
76
|
-
"""
|
77
|
-
Encodes environment to Base64.
|
70
|
+
Retrieve all environment variables as a dictionary.
|
78
71
|
|
79
|
-
Returns
|
80
|
-
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
dict of str to Any
|
75
|
+
A dictionary containing all environment variables loaded by DotEnv.
|
81
76
|
"""
|
82
77
|
pass
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
class IEnvTypes(ABC):
|
4
|
+
|
5
|
+
@abstractmethod
|
6
|
+
def to(self, type_hint: str):
|
7
|
+
"""
|
8
|
+
Set the type hint for the Type instance.
|
9
|
+
|
10
|
+
Parameters
|
11
|
+
----------
|
12
|
+
type_hint : str
|
13
|
+
The type hint to set, which must be one of the valid options defined in OPTIONS.
|
14
|
+
|
15
|
+
Raises
|
16
|
+
------
|
17
|
+
OrionisEnvironmentValueError
|
18
|
+
If the provided type hint is not one of the valid options.
|
19
|
+
"""
|
20
|
+
pass
|
21
|
+
|
22
|
+
@abstractmethod
|
23
|
+
def hasValidTypeHint(self) -> bool:
|
24
|
+
"""
|
25
|
+
Check if the type hint is valid.
|
26
|
+
|
27
|
+
Returns
|
28
|
+
-------
|
29
|
+
bool
|
30
|
+
True if the type hint is valid (exists in the OPTIONS set), False otherwise.
|
31
|
+
"""
|
32
|
+
pass
|
33
|
+
|
34
|
+
@abstractmethod
|
35
|
+
def explode(self) -> tuple:
|
36
|
+
"""
|
37
|
+
Returns a tuple containing the type hint and value string.
|
38
|
+
|
39
|
+
Returns
|
40
|
+
-------
|
41
|
+
tuple
|
42
|
+
A tuple (type_hint, value_str) where:
|
43
|
+
type_hint : str or None
|
44
|
+
The extracted type hint in lowercase, or None if not provided.
|
45
|
+
value_str : str or None
|
46
|
+
The extracted value string, or None if not provided.
|
47
|
+
"""
|
48
|
+
pass
|
49
|
+
|
50
|
+
@abstractmethod
|
51
|
+
def get(self):
|
52
|
+
"""
|
53
|
+
Returns the value corresponding to the specified type hint.
|
54
|
+
|
55
|
+
Checks if the provided type hint is valid and then dispatches the call to the appropriate
|
56
|
+
method for handling the type.
|
57
|
+
|
58
|
+
Supported type hints include: 'path:', 'str:', 'int:', 'float:', 'bool:', 'list:', 'dict:', 'tuple:', and 'set:'.
|
59
|
+
|
60
|
+
Returns
|
61
|
+
-------
|
62
|
+
Any
|
63
|
+
The value converted or processed according to the specified type hint.
|
64
|
+
|
65
|
+
Raises
|
66
|
+
------
|
67
|
+
OrionisEnvironmentValueError
|
68
|
+
If the type hint is not one of the supported options.
|
69
|
+
"""
|
70
|
+
pass
|
@@ -1,247 +1,269 @@
|
|
1
1
|
import os
|
2
2
|
import ast
|
3
|
+
import re
|
3
4
|
import threading
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import Any, Optional, Union
|
6
7
|
from dotenv import dotenv_values, load_dotenv, set_key, unset_key
|
7
8
|
from orionis.patterns.singleton.meta_class import Singleton
|
8
|
-
from orionis.services.environment.exceptions.
|
9
|
+
from orionis.services.environment.exceptions.environment_value_exception import OrionisEnvironmentValueException
|
10
|
+
from orionis.services.environment.exceptions.environment_value_error import OrionisEnvironmentValueError
|
11
|
+
from orionis.services.environment.types import EnvTypes
|
9
12
|
|
10
13
|
class DotEnv(metaclass=Singleton):
|
11
|
-
"""
|
12
|
-
DotEnv is a singleton class for managing environment variables using a `.env` file.
|
13
|
-
This class provides methods to load, get, set, unset, and list environment variables,
|
14
|
-
with automatic serialization and deserialization of common Python data types.
|
15
|
-
It ensures that changes to the `.env` file are reflected in the current process's
|
16
|
-
environment variables and vice versa.
|
17
|
-
"""
|
18
14
|
|
15
|
+
# Thread-safe singleton instance lock
|
19
16
|
_lock = threading.RLock()
|
20
17
|
|
21
18
|
def __init__(self, path: str = None) -> None:
|
22
19
|
"""
|
23
|
-
|
20
|
+
Initialize the environment service by resolving the path to the `.env` file, ensuring its existence,
|
24
21
|
and loading environment variables from it.
|
25
|
-
Args:
|
26
|
-
path (str, optional): The path to the `.env` file. If not provided, defaults to a `.env` file
|
27
|
-
in the current working directory.
|
28
|
-
Raises:
|
29
|
-
OSError: If the `.env` file cannot be created when it does not exist.
|
30
|
-
"""
|
31
|
-
|
32
|
-
with self._lock:
|
33
|
-
if path:
|
34
|
-
self._resolved_path = Path(path).expanduser().resolve()
|
35
|
-
else:
|
36
|
-
self._resolved_path = Path(os.getcwd()) / ".env"
|
37
|
-
|
38
|
-
if not self._resolved_path.exists():
|
39
|
-
self._resolved_path.touch()
|
40
|
-
|
41
|
-
load_dotenv(self._resolved_path)
|
42
|
-
|
43
|
-
def get(self, key: str, default: Optional[Any] = None, is_path:bool=False) -> Any:
|
44
|
-
"""
|
45
|
-
Retrieve the value of an environment variable by key.
|
46
|
-
|
47
|
-
This method first attempts to fetch the value from the dotenv file specified by
|
48
|
-
`self._resolved_path`. If the key is not found in the dotenv file, it falls back
|
49
|
-
to the system environment variables. If the key is not found in either location,
|
50
|
-
the provided `default` value is returned.
|
51
|
-
|
52
|
-
The returned value is parsed using the internal `__parseValue` method if found.
|
53
|
-
|
54
|
-
Args:
|
55
|
-
key (str): The name of the environment variable to retrieve.
|
56
|
-
default (Optional[Any], optional): The value to return if the key is not found.
|
57
|
-
Defaults to None.
|
58
|
-
is_path (bool, optional): If True, the value is treated as a file path and backslashes are replaced with forward slashes.
|
59
|
-
This is useful for Windows paths. Defaults to False.
|
60
|
-
|
61
|
-
Returns:
|
62
|
-
Any: The parsed value of the environment variable, or the default if not found.
|
63
|
-
"""
|
64
|
-
with self._lock:
|
65
|
-
value = dotenv_values(self._resolved_path).get(key)
|
66
|
-
if value is None:
|
67
|
-
value = os.getenv(key)
|
68
|
-
return self.__parseValue(value, is_path) if value is not None else default
|
69
|
-
|
70
|
-
def set(self, key: str, value: Union[str, int, float, bool, list, dict], is_path:bool=False) -> bool:
|
71
|
-
"""
|
72
|
-
Sets an environment variable with the specified key and value.
|
73
|
-
|
74
|
-
This method serializes the given value and updates both the .env file and the current process's environment variables.
|
75
|
-
|
76
|
-
Args:
|
77
|
-
key (str): The name of the environment variable to set.
|
78
|
-
value (Union[str, int, float, bool, list, dict]): The value to assign to the environment variable. Supported types include string, integer, float, boolean, list, and dictionary.
|
79
|
-
is_path (bool, optional): If True, the value is treated as a file path and backslashes are replaced with forward slashes.
|
80
|
-
This is useful for Windows paths. Defaults to False.
|
81
22
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
serialized_value = self.__serializeValue(value, is_path)
|
88
|
-
set_key(str(self._resolved_path), key, serialized_value)
|
89
|
-
os.environ[key] = str(value)
|
90
|
-
return True
|
23
|
+
Parameters
|
24
|
+
----------
|
25
|
+
path : str, optional
|
26
|
+
Path to the `.env` file. If not provided, defaults to a `.env` file
|
27
|
+
in the current working directory.
|
91
28
|
|
92
|
-
|
29
|
+
Raises
|
30
|
+
------
|
31
|
+
OSError
|
32
|
+
If the `.env` file cannot be created when it does not exist.
|
93
33
|
"""
|
94
|
-
|
34
|
+
try:
|
35
|
+
with self._lock:
|
36
|
+
if path:
|
37
|
+
self._resolved_path = Path(path).expanduser().resolve()
|
38
|
+
else:
|
39
|
+
self._resolved_path = Path(os.getcwd()) / ".env"
|
95
40
|
|
96
|
-
|
97
|
-
|
41
|
+
if not self._resolved_path.exists():
|
42
|
+
self._resolved_path.touch()
|
98
43
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
with self._lock:
|
103
|
-
unset_key(str(self._resolved_path), key)
|
104
|
-
os.environ.pop(key, None)
|
105
|
-
return True
|
44
|
+
load_dotenv(self._resolved_path)
|
45
|
+
except OSError as e:
|
46
|
+
raise OSError(f"Failed to create or access the .env file at {self._resolved_path}: {e}")
|
106
47
|
|
107
|
-
def
|
48
|
+
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
108
49
|
"""
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
50
|
+
Get the value of an environment variable.
|
51
|
+
|
52
|
+
Parameters
|
53
|
+
----------
|
54
|
+
key : str
|
55
|
+
Name of the environment variable to retrieve.
|
56
|
+
default : Any, optional
|
57
|
+
Value to return if the key is not found. Default is None.
|
58
|
+
|
59
|
+
Returns
|
60
|
+
-------
|
61
|
+
Any
|
62
|
+
Parsed value of the environment variable, or `default` if not found.
|
63
|
+
|
64
|
+
Raises
|
65
|
+
------
|
66
|
+
OrionisEnvironmentValueError
|
67
|
+
If `key` is not a string.
|
114
68
|
"""
|
115
69
|
with self._lock:
|
116
|
-
raw_values = dotenv_values(self._resolved_path)
|
117
|
-
return {k: self.__parseValue(v) for k, v in raw_values.items()}
|
118
70
|
|
119
|
-
|
120
|
-
|
121
|
-
|
71
|
+
# Ensure the key is a string.
|
72
|
+
if not isinstance(key, str):
|
73
|
+
raise OrionisEnvironmentValueError(
|
74
|
+
f"Key must be a string, got {type(key).__name__}."
|
75
|
+
)
|
122
76
|
|
123
|
-
|
124
|
-
|
125
|
-
"""
|
126
|
-
import json
|
127
|
-
with self._lock:
|
128
|
-
return json.dumps(self.all(), indent=4)
|
77
|
+
# Get the value from the .env file or the current environment.
|
78
|
+
value = dotenv_values(self._resolved_path).get(key)
|
129
79
|
|
130
|
-
|
131
|
-
|
132
|
-
|
80
|
+
# If the value is not found in the .env file, check the current environment variables.
|
81
|
+
if value is None:
|
82
|
+
value = os.getenv(key)
|
133
83
|
|
134
|
-
|
135
|
-
|
136
|
-
"""
|
137
|
-
import base64
|
138
|
-
import json
|
139
|
-
with self._lock:
|
140
|
-
return base64.b64encode(json.dumps(self.all()).encode()).decode()
|
84
|
+
# Parse the value using the internal __parseValue method and return it
|
85
|
+
return self.__parseValue(value) if value is not None else default
|
141
86
|
|
142
|
-
def __parseValue(self, value: Any
|
87
|
+
def __parseValue(self, value: Any) -> Any:
|
143
88
|
"""
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
89
|
+
Parse and convert the input value to an appropriate Python data type with enhanced features.
|
90
|
+
|
91
|
+
Parameters
|
92
|
+
----------
|
93
|
+
value : Any
|
94
|
+
The value to parse and convert.
|
95
|
+
|
96
|
+
Returns
|
97
|
+
-------
|
98
|
+
Any
|
99
|
+
The parsed value, which may be of type None, bool, int, float, list, dict, or str.
|
100
|
+
- Returns None for None, empty strings, or strings like 'none', 'null', 'nan' (case-insensitive).
|
101
|
+
- Returns a boolean for 'true'/'false' strings (case-insensitive) or 1/0.
|
102
|
+
- Returns an int if the string represents an integer.
|
103
|
+
- Returns a float if the string represents a float.
|
104
|
+
- Attempts to evaluate the string as a Python literal (e.g., list, dict, tuple).
|
105
|
+
- Handles type hints via 'type:' prefix (e.g., 'int:42', 'bool:true').
|
106
|
+
- Returns the original string if no conversion is possible.
|
107
|
+
|
108
|
+
Raises
|
109
|
+
------
|
110
|
+
OrionisEnvironmentValueException
|
111
|
+
If type conversion fails for explicitly typed values (e.g., 'abc::int').
|
157
112
|
"""
|
113
|
+
# Early return for None
|
158
114
|
if value is None:
|
159
115
|
return None
|
160
116
|
|
161
|
-
if
|
117
|
+
# Return immediately if already a basic type
|
118
|
+
if isinstance(value, (bool, int, float, dict, list, tuple, set)):
|
162
119
|
return value
|
163
120
|
|
121
|
+
# Convert to string and clean
|
164
122
|
value_str = str(value).strip()
|
165
|
-
if not value_str or value_str.lower() in {'none', 'null', 'nan'}:
|
166
|
-
return None
|
167
123
|
|
168
|
-
|
169
|
-
|
124
|
+
# Handle empty strings and common null representations
|
125
|
+
# This includes 'none', 'null', 'nan', 'nil' (case-insensitive)
|
126
|
+
if not value_str or value_str.lower() in {'none', 'null', 'nan', 'nil'}:
|
127
|
+
return None
|
170
128
|
|
171
|
-
|
172
|
-
|
129
|
+
# Boolean detection (without type hint)
|
130
|
+
lower_val = value_str.lower()
|
131
|
+
if lower_val in ('true', 'false', 'yes', 'no', 'on', 'off', '1', '0'):
|
132
|
+
return lower_val in ('true', 'yes', 'on', '1')
|
173
133
|
|
174
|
-
|
175
|
-
|
134
|
+
# Handle type hints using the Type class
|
135
|
+
hints = EnvTypes(value_str)
|
136
|
+
if hints.hasValidTypeHint():
|
137
|
+
return hints.get()
|
176
138
|
|
139
|
+
# Try parseing to literal types, if failed, return the original value
|
177
140
|
try:
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
pass
|
141
|
+
return ast.literal_eval(value_str)
|
142
|
+
except (ValueError, SyntaxError):
|
143
|
+
return value_str
|
182
144
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
145
|
+
def set(self, key: str, value: Union[str, int, float, bool, list, dict, tuple, set], type_hint: str = None) -> bool:
|
146
|
+
"""
|
147
|
+
Set an environment variable with the specified key and value.
|
148
|
+
|
149
|
+
Serializes the given value and updates both the .env file and the current process's environment variables.
|
150
|
+
|
151
|
+
Parameters
|
152
|
+
----------
|
153
|
+
key : str
|
154
|
+
The name of the environment variable to set.
|
155
|
+
value : Union[str, int, float, bool, list, dict]
|
156
|
+
The value to assign to the environment variable. Supported types include string, integer, float, boolean, list, and dictionary.
|
157
|
+
type_hint : str, optional
|
158
|
+
The type of the value being set. If provided, it can be (path, str, int, float, bool, list, dict, tuple, set).
|
159
|
+
|
160
|
+
Returns
|
161
|
+
-------
|
162
|
+
bool
|
163
|
+
True if the environment variable was successfully set.
|
164
|
+
"""
|
165
|
+
with self._lock:
|
189
166
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
167
|
+
# Ensure the key is a string.
|
168
|
+
if not isinstance(key, str) or not re.match(r'^[A-Z][A-Z0-9_]*$', key):
|
169
|
+
raise OrionisEnvironmentValueError(
|
170
|
+
f"The environment variable name '{key}' is not valid. It must be an uppercase string, may contain numbers and underscores, and must always start with a letter. Example of a valid name: 'MY_ENV_VAR'."
|
171
|
+
)
|
194
172
|
|
195
|
-
|
173
|
+
# Ensure the value is a valid type.
|
174
|
+
if not isinstance(value, (str, int, float, bool, list, dict, tuple, set)):
|
175
|
+
raise OrionisEnvironmentValueError(
|
176
|
+
f"Unsupported value type: {type(value).__name__}. Allowed types are str, int, float, bool, list, dict, tuple, set."
|
177
|
+
)
|
196
178
|
|
197
|
-
|
198
|
-
|
199
|
-
|
179
|
+
# Dinamically determine the type hint if not provided.
|
180
|
+
if isinstance(value, (int, float, bool, list, dict, tuple, set)) and not type_hint:
|
181
|
+
type_hint = type(value).__name__.lower()
|
182
|
+
|
183
|
+
# Validate the type hint if provided.
|
184
|
+
options = EnvTypes.options()
|
185
|
+
if type_hint and type_hint not in options:
|
186
|
+
raise OrionisEnvironmentValueException(f"Invalid type hint: {type_hint}. Allowed types are {str(options)}.")
|
187
|
+
|
188
|
+
# Serialize the value based on its type.
|
189
|
+
serialized_value = self.__serializeValue(value, type_hint)
|
200
190
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
This is useful for Windows paths.
|
191
|
+
# Set the environment variable in the .env file and the current process environment.
|
192
|
+
set_key(str(self._resolved_path), key, serialized_value)
|
193
|
+
os.environ[key] = str(value)
|
205
194
|
|
206
|
-
|
207
|
-
|
195
|
+
# Return True to indicate success.
|
196
|
+
return True
|
208
197
|
|
209
|
-
|
210
|
-
|
198
|
+
def __serializeValue(self, value: Any, type_hint: str = None) -> str:
|
199
|
+
"""
|
200
|
+
Parameters
|
201
|
+
----------
|
202
|
+
value : Any
|
203
|
+
The value to serialize.
|
204
|
+
type_hint : str, optional
|
205
|
+
An optional type hint to guide serialization.
|
206
|
+
Returns
|
207
|
+
-------
|
208
|
+
str
|
209
|
+
The serialized string representation of the value.
|
210
|
+
Notes
|
211
|
+
-----
|
212
|
+
- If `value` is None, returns "null".
|
213
|
+
- If `type_hint` is provided, uses `EnvTypes` to serialize.
|
214
|
+
- Uses `repr()` for lists, dicts, tuples, and sets.
|
215
|
+
- Falls back to `str()` for other types.
|
211
216
|
"""
|
212
|
-
if is_path:
|
213
|
-
return str(value).replace("\\", "/")
|
214
217
|
|
215
218
|
if value is None:
|
216
|
-
return "
|
219
|
+
return "null"
|
220
|
+
|
221
|
+
if type_hint:
|
222
|
+
return EnvTypes(value).to(type_hint)
|
217
223
|
|
218
224
|
if isinstance(value, str):
|
219
|
-
return value
|
225
|
+
return value.strip()
|
220
226
|
|
221
227
|
if isinstance(value, bool):
|
222
228
|
return str(value).lower()
|
223
229
|
|
224
|
-
if isinstance(value, int):
|
230
|
+
if isinstance(value, int, float):
|
225
231
|
return str(value)
|
226
232
|
|
227
|
-
if isinstance(value,
|
228
|
-
value = str(value)
|
229
|
-
if 'e' in value or 'E' in value:
|
230
|
-
raise OrionisEnvironmentValueException('scientific notation is not supported, use a string instead')
|
231
|
-
return value
|
232
|
-
|
233
|
-
if isinstance(value, (list, dict)):
|
233
|
+
if isinstance(value, (list, dict, tuple, set)):
|
234
234
|
return repr(value)
|
235
235
|
|
236
|
-
|
237
|
-
|
236
|
+
return str(value)
|
237
|
+
|
238
|
+
def unset(self, key: str) -> bool:
|
239
|
+
"""
|
240
|
+
Remove the specified environment variable from both the .env file and the current process environment.
|
241
|
+
|
242
|
+
Parameters
|
243
|
+
----------
|
244
|
+
key : str
|
245
|
+
The name of the environment variable to unset.
|
238
246
|
|
239
|
-
|
240
|
-
|
247
|
+
Returns
|
248
|
+
-------
|
249
|
+
bool
|
250
|
+
True if the operation was successful.
|
251
|
+
"""
|
252
|
+
with self._lock:
|
253
|
+
unset_key(str(self._resolved_path), key)
|
254
|
+
os.environ.pop(key, None)
|
255
|
+
return True
|
241
256
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
return repr(value) if not isinstance(value, str) else value
|
257
|
+
def all(self) -> dict:
|
258
|
+
"""
|
259
|
+
Retrieve all environment variables from the resolved .env file.
|
246
260
|
|
247
|
-
|
261
|
+
Returns
|
262
|
+
-------
|
263
|
+
dict
|
264
|
+
Dictionary containing all environment variable key-value pairs,
|
265
|
+
with values parsed using the internal __parseValue method.
|
266
|
+
"""
|
267
|
+
with self._lock:
|
268
|
+
raw_values = dotenv_values(self._resolved_path)
|
269
|
+
return {k: self.__parseValue(v) for k, v in raw_values.items()}
|