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.
Files changed (86) hide show
  1. orionis/metadata/framework.py +1 -1
  2. orionis/services/environment/contracts/env.py +45 -50
  3. orionis/services/environment/contracts/types.py +70 -0
  4. orionis/services/environment/dot_env.py +204 -182
  5. orionis/services/environment/env.py +68 -85
  6. orionis/services/{standard/exceptions/path_value_exceptions.py → environment/exceptions/environment_value_error.py} +2 -12
  7. orionis/services/environment/exceptions/environment_value_exception.py +23 -0
  8. orionis/services/environment/types.py +578 -0
  9. orionis/services/paths/exceptions/not_found_exceptions.py +15 -12
  10. orionis/services/paths/exceptions/path_value_exceptions.py +13 -10
  11. orionis/services/standard/contracts/std.py +1 -1
  12. orionis/services/standard/exceptions/std_value_exception.py +23 -0
  13. orionis/services/standard/std.py +2 -2
  14. orionis/services/system/contracts/imports.py +27 -7
  15. orionis/services/system/contracts/workers.py +8 -3
  16. orionis/services/system/imports.py +31 -12
  17. orionis/services/system/runtime_imports.py +33 -23
  18. orionis/services/system/workers.py +6 -8
  19. orionis/services/wrapper/dicts/dot_dict.py +82 -41
  20. orionis/test/logs/history.py +3 -5
  21. orionis/unittesting.py +12 -12
  22. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/METADATA +1 -1
  23. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/RECORD +76 -70
  24. tests/example/test_example.py +5 -2
  25. tests/foundation/config/app/{test_app.py → test_foundation_config_app.py} +14 -4
  26. tests/foundation/config/auth/{test_auth.py → test_foundation_config_auth.py} +10 -5
  27. tests/foundation/config/cache/{test_cache.py → test_foundation_config_cache.py} +61 -16
  28. tests/foundation/config/cache/test_foundation_config_cache_file.py +126 -0
  29. tests/foundation/config/cache/test_foundation_config_cache_stores.py +148 -0
  30. tests/foundation/config/cors/test_foundation_config_cors.py +190 -0
  31. tests/foundation/config/database/{test_database.py → test_foundation_config_database.py} +39 -15
  32. tests/foundation/config/database/{test_database_connections.py → test_foundation_config_database_connections.py} +80 -6
  33. tests/foundation/config/database/{test_database_mysql.py → test_foundation_config_database_mysql.py} +139 -16
  34. tests/foundation/config/database/{test_database_oracle.py → test_foundation_config_database_oracle.py} +111 -27
  35. tests/foundation/config/database/{test_database_pgsql.py → test_foundation_config_database_pgsql.py} +97 -27
  36. tests/foundation/config/database/{test_database_sqlite.py → test_foundation_config_database_sqlite.py} +57 -3
  37. tests/foundation/config/exceptions/{test_exceptions_integrity.py → test_foundation_config_exceptions.py} +45 -11
  38. tests/foundation/config/filesystems/{test_filesystems.py → test_foundation_config_filesystems.py} +65 -15
  39. tests/foundation/config/filesystems/{test_filesystems_aws.py → test_foundation_config_filesystems_aws.py} +46 -8
  40. tests/foundation/config/filesystems/{test_filesystems_disks.py → test_foundation_config_filesystems_disks.py} +79 -9
  41. tests/foundation/config/filesystems/{test_filesystems_local.py → test_foundation_config_filesystems_local.py} +67 -19
  42. tests/foundation/config/filesystems/{test_filesystems_public.py → test_foundation_config_filesystems_public.py} +38 -1
  43. tests/foundation/config/logging/test_foundation_config_logging.py +112 -0
  44. tests/foundation/config/logging/{test_logging_channels.py → test_foundation_config_logging_channels.py} +80 -3
  45. tests/foundation/config/logging/{test_logging_chunked.py → test_foundation_config_logging_chunked.py} +86 -13
  46. tests/foundation/config/logging/{test_logging_daily.py → test_foundation_config_logging_daily.py} +80 -13
  47. tests/foundation/config/logging/{test_logging_hourly.py → test_foundation_config_logging_hourly.py} +69 -3
  48. tests/foundation/config/logging/{test_logging_monthly.py → test_foundation_config_logging_monthly.py} +49 -3
  49. tests/foundation/config/logging/{test_logging_stack.py → test_foundation_config_logging_stack.py} +50 -15
  50. tests/foundation/config/logging/{test_logging_weekly.py → test_foundation_config_logging_weekly.py} +93 -3
  51. tests/foundation/config/mail/test_foundation_config_mail.py +145 -0
  52. tests/foundation/config/mail/{test_mail_file.py → test_foundation_config_mail_file.py} +41 -5
  53. tests/foundation/config/mail/test_foundation_config_mail_mailers.py +106 -0
  54. tests/foundation/config/mail/{test_mail_smtp.py → test_foundation_config_mail_smtp.py} +59 -15
  55. tests/foundation/config/queue/test_foundation_config_queue.py +111 -0
  56. tests/foundation/config/queue/{test_queue_brokers.py → test_foundation_config_queue_brokers.py} +28 -11
  57. tests/foundation/config/queue/{test_queue_database.py → test_foundation_config_queue_database.py} +54 -16
  58. tests/foundation/config/root/{test_root_paths.py → test_foundation_config_root_paths.py} +70 -3
  59. tests/foundation/config/session/{test_session.py → test_foundation_config_session.py} +31 -2
  60. tests/foundation/config/startup/{test_config_startup.py → test_foundation_config_startup.py} +91 -21
  61. tests/foundation/config/testing/{test_testing.py → test_foundation_config_testing.py} +69 -1
  62. tests/patterns/singleton/test_patterns_singleton.py +27 -0
  63. tests/services/asynchrony/{test_async_io.py → test_services_asynchrony_coroutine.py} +1 -1
  64. tests/services/environment/test_services_environment.py +93 -0
  65. tests/services/path/{test_resolver.py → test_services_resolver.py} +51 -12
  66. tests/services/standard/{test_std.py → test_services_std.py} +45 -22
  67. tests/services/system/__init__.py +0 -0
  68. tests/services/system/test_services_system_imports.py +101 -0
  69. tests/services/system/test_services_system_workers.py +89 -0
  70. tests/services/wrapper/{test_wrapper_doc_dict.py → test_services_wrapper_docdict.py} +28 -16
  71. tests/testing/test_testing_result.py +57 -20
  72. tests/testing/test_testing_unit.py +110 -41
  73. orionis/services/environment/exceptions/value_exception.py +0 -27
  74. tests/foundation/config/cache/test_cache_file.py +0 -78
  75. tests/foundation/config/cache/test_cache_stores.py +0 -88
  76. tests/foundation/config/cors/test_cors.py +0 -121
  77. tests/foundation/config/logging/test_logging.py +0 -48
  78. tests/foundation/config/mail/test_mail.py +0 -73
  79. tests/foundation/config/mail/test_mail_mailers.py +0 -58
  80. tests/foundation/config/queue/test_queue.py +0 -58
  81. tests/patterns/singleton/test_singleton.py +0 -18
  82. tests/services/environment/test_env.py +0 -155
  83. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/WHEEL +0 -0
  84. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/licenses/LICENCE +0 -0
  85. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/top_level.txt +0 -0
  86. {orionis-0.286.0.dist-info → orionis-0.288.0.dist-info}/zip-safe +0 -0
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.286.0"
8
+ VERSION = "0.288.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -1,36 +1,47 @@
1
- from typing import Any, Dict, Optional
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: Optional[Any] = None) -> Any:
8
+ def get(key: str, default: Any = None) -> Any:
10
9
  """
11
- Retrieves an environment variable's value.
12
-
13
- Args:
14
- key: Environment variable name
15
- default: Default value if key doesn't exist
16
-
17
- Returns:
18
- The parsed value or default if not found
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
- Sets an environment variable.
27
-
28
- Args:
29
- key: Environment variable name
30
- value: Value to set
31
-
32
- Returns:
33
- True if successful, False otherwise
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
- Removes an environment variable.
52
+ Remove the specified environment variable from the .env file.
42
53
 
43
- Args:
44
- key: Environment variable name
54
+ Parameters
55
+ ----------
56
+ key : str
57
+ The name of the environment variable to remove.
45
58
 
46
- Returns:
47
- True if successful, False otherwise
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
- Retrieves all environment variables.
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
- Base64 encoded string
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.value_exception import OrionisEnvironmentValueException
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
- Initializes the environment service by resolving the path to the `.env` file, ensuring its existence,
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
- Returns:
83
- bool: True if the environment variable was successfully set.
84
- """
85
-
86
- with self._lock:
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
- def unset(self, key: str) -> bool:
29
+ Raises
30
+ ------
31
+ OSError
32
+ If the `.env` file cannot be created when it does not exist.
93
33
  """
94
- Removes the specified environment variable from both the .env file and the current process environment.
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
- Args:
97
- key (str): The name of the environment variable to unset.
41
+ if not self._resolved_path.exists():
42
+ self._resolved_path.touch()
98
43
 
99
- Returns:
100
- bool: True if the operation was successful.
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 all(self) -> dict:
48
+ def get(self, key: str, default: Optional[Any] = None) -> Any:
108
49
  """
109
- Retrieves all environment variables from the resolved .env file.
110
-
111
- Returns:
112
- dict: A dictionary containing all environment variable key-value pairs,
113
- with values parsed using the internal __parseValue method.
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
- def toJson(self) -> str:
120
- """
121
- Serializes all environment variables managed by this instance to a JSON-formatted string.
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
- Returns:
124
- str: A JSON string representation of all environment variables, formatted with indentation for readability.
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
- def toBase64(self) -> str:
131
- """
132
- Serializes all environment variables to a JSON string and encodes it in Base64.
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
- Returns:
135
- str: A Base64-encoded string representation of all environment variables.
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, is_path:bool=False) -> Any:
87
+ def __parseValue(self, value: Any) -> Any:
143
88
  """
144
- Parses and converts the input value to an appropriate Python data type.
145
-
146
- Args:
147
- value (Any): The value to parse and convert.
148
-
149
- Returns:
150
- Any: The parsed value, which may be of type None, bool, int, float, or the original string.
151
- - Returns None for None, empty strings, or strings like 'none', 'null', 'nan' (case-insensitive).
152
- - Returns a boolean for 'true'/'false' strings (case-insensitive).
153
- - Returns an int if the string represents an integer.
154
- - Returns a float if the string represents a float.
155
- - Attempts to evaluate the string as a Python literal (e.g., list, dict, tuple).
156
- - Returns the original string if no conversion is possible.
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 isinstance(value, (bool, int, float)):
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
- if value_str.lower() == 'true':
169
- return True
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
- if value_str.lower() == 'false':
172
- return False
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
- if is_path:
175
- return value_str.replace("\\", "/")
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
- if value_str.isdigit() or (value_str.startswith('-') and value_str[1:].isdigit()):
179
- return int(value_str)
180
- except Exception:
181
- pass
141
+ return ast.literal_eval(value_str)
142
+ except (ValueError, SyntaxError):
143
+ return value_str
182
144
 
183
- try:
184
- float_val = float(value_str)
185
- if '.' in value_str or 'e' in value_str.lower():
186
- return float_val
187
- except Exception:
188
- pass
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
- try:
191
- return ast.literal_eval(value_str)
192
- except Exception:
193
- pass
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
- return value_str
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
- def __serializeValue(self, value: Any, is_path:bool=False) -> str:
198
- """
199
- Serializes a given value to a string suitable for storage in a .env file.
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
- Parameters:
202
- value (Any): The value to serialize. Supported types are None, str, bool, int, float, list, and dict.
203
- is_path (bool): If True, the value is treated as a file path and backslashes are replaced with forward slashes.
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
- Returns:
207
- str: The serialized string representation of the value.
195
+ # Return True to indicate success.
196
+ return True
208
197
 
209
- Raises:
210
- OrionisEnvironmentValueException: If a float value is in scientific notation or If the value's type is not serializable for .env files.
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 "None"
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, float):
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
- if hasattr(value, '__dict__'):
237
- raise OrionisEnvironmentValueException(f"Type {type(value).__name__} is not serializable for .env")
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
- if not isinstance(value, (list, dict, bool, int, float, str)):
240
- raise OrionisEnvironmentValueException(f"Type {type(value).__name__} is not serializable for .env")
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
- if isinstance(value, (list, dict, bool, int, float, str)):
243
- if type(value).__module__ != "builtins" and not isinstance(value, str):
244
- raise OrionisEnvironmentValueException(f"Type {type(value).__name__} is not serializable for .env")
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
- raise OrionisEnvironmentValueException(f"Type {type(value).__name__} is not serializable for .env")
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()}