orionis 0.406.0__py3-none-any.whl → 0.408.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 (52) hide show
  1. orionis/container/container.py +11 -9
  2. orionis/container/enums/lifetimes.py +2 -0
  3. orionis/container/validators/__init__.py +21 -0
  4. orionis/metadata/framework.py +1 -1
  5. orionis/services/asynchrony/contracts/coroutines.py +13 -5
  6. orionis/services/asynchrony/coroutines.py +33 -29
  7. orionis/services/asynchrony/exceptions/exception.py +9 -1
  8. orionis/services/environment/core/dot_env.py +46 -34
  9. orionis/services/environment/enums/__init__.py +0 -0
  10. orionis/services/environment/enums/cast_type.py +42 -0
  11. orionis/services/environment/serializer/__init__.py +0 -0
  12. orionis/services/environment/serializer/values.py +21 -0
  13. orionis/services/environment/validators/__init__.py +0 -0
  14. orionis/services/environment/validators/key_name.py +46 -0
  15. orionis/services/environment/validators/types.py +45 -0
  16. orionis/services/system/contracts/imports.py +38 -18
  17. orionis/services/system/contracts/workers.py +29 -12
  18. orionis/services/system/imports.py +65 -25
  19. orionis/services/system/runtime/imports.py +18 -9
  20. orionis/services/system/workers.py +49 -16
  21. orionis/test/output/dumper.py +1 -0
  22. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/METADATA +1 -1
  23. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/RECORD +52 -45
  24. tests/container/context/test_manager.py +15 -5
  25. tests/container/context/test_scope.py +12 -4
  26. tests/container/entities/test_binding.py +130 -21
  27. tests/container/enums/test_lifetimes.py +52 -18
  28. tests/container/facades/test_facade.py +29 -12
  29. tests/container/providers/test_providers.py +17 -10
  30. tests/container/resolver/test_resolver.py +14 -7
  31. tests/container/test_container.py +226 -71
  32. tests/container/test_singleton.py +43 -24
  33. tests/container/test_thread_safety.py +28 -156
  34. tests/container/validators/test_implements.py +59 -13
  35. tests/container/validators/test_is_abstract_class.py +73 -25
  36. tests/container/validators/test_is_callable.py +55 -26
  37. tests/container/validators/test_is_concrete_class.py +80 -17
  38. tests/container/validators/test_is_instance.py +67 -22
  39. tests/container/validators/test_is_not_subclass.py +28 -95
  40. tests/container/validators/test_is_subclass.py +84 -21
  41. tests/container/validators/test_is_valid_alias.py +46 -12
  42. tests/container/validators/test_lifetime.py +45 -14
  43. tests/example/test_example.py +2 -2
  44. tests/metadata/test_metadata_framework.py +71 -6
  45. tests/metadata/test_metadata_package.py +55 -10
  46. tests/services/asynchrony/test_services_asynchrony_coroutine.py +52 -7
  47. tests/services/system/test_services_system_imports.py +119 -16
  48. tests/services/system/test_services_system_workers.py +71 -30
  49. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/WHEEL +0 -0
  50. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/licenses/LICENCE +0 -0
  51. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/top_level.txt +0 -0
  52. {orionis-0.406.0.dist-info → orionis-0.408.0.dist-info}/zip-safe +0 -0
@@ -1,35 +1,52 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
3
  class IWorkers(ABC):
4
- """
5
- Interface for calculating the optimal number of workers a machine can handle based on CPU and memory resources.
6
-
7
- Notes
8
- -----
9
- Implementations should provide logic to determine the recommended number of worker processes
10
- according to the available CPU and memory resources of the current machine.
11
- """
12
4
 
13
5
  @abstractmethod
14
6
  def setRamPerWorker(self, ram_per_worker: float) -> None:
15
7
  """
16
- Set the amount of RAM allocated per worker.
8
+ Set the amount of RAM to allocate for each worker process.
17
9
 
18
10
  Parameters
19
11
  ----------
20
12
  ram_per_worker : float
21
- Amount of RAM (in GB) allocated per worker.
13
+ The amount of RAM, in gigabytes (GB), to allocate for each worker process.
14
+
15
+ Returns
16
+ -------
17
+ None
18
+ This method does not return any value.
19
+
20
+ Notes
21
+ -----
22
+ This method should be implemented by subclasses to configure the memory usage
23
+ per worker, which may affect the total number of workers that can be spawned
24
+ based on system resources.
22
25
  """
26
+
27
+ # Implementation should assign the specified RAM per worker.
23
28
  pass
24
29
 
25
30
  @abstractmethod
26
31
  def calculate(self) -> int:
27
32
  """
28
- Compute the maximum number of workers supported by the current machine.
33
+ Compute the recommended maximum number of worker processes for the current machine.
34
+
35
+ This method should consider both CPU and memory constraints to determine the optimal
36
+ number of worker processes that can be safely spawned without overloading system resources.
29
37
 
30
38
  Returns
31
39
  -------
32
40
  int
33
- Recommended number of worker processes based on CPU and memory limits.
41
+ The maximum number of worker processes that can be supported by the current machine,
42
+ based on available CPU cores and memory limits.
43
+
44
+ Notes
45
+ -----
46
+ Subclasses should implement this method to analyze system resources and return an integer
47
+ representing the recommended number of workers. The calculation should ensure that each
48
+ worker receives sufficient resources as configured (e.g., RAM per worker).
34
49
  """
50
+
51
+ # Implementation should analyze system resources and return the recommended worker count.
35
52
  pass
@@ -2,58 +2,68 @@ from typing import List, Dict, Any
2
2
  from orionis.services.system.contracts.imports import IImports
3
3
 
4
4
  class Imports(IImports):
5
- """
6
- Utility class to collect and display information about currently loaded Python modules.
7
-
8
- This class provides methods to gather details about user-defined Python modules
9
- currently loaded in `sys.modules`, excluding standard library and virtual environment modules.
10
- It can display the collected information in a formatted table using the Rich library.
11
- """
12
5
 
13
6
  def __init__(self):
14
7
  """
15
- Initialize the Imports object.
8
+ Initialize the Imports instance.
9
+
10
+ This constructor sets up the Imports object by initializing an empty list
11
+ to store information about user-defined Python modules. The list will
12
+ contain dictionaries, each representing a module with its name, file path,
13
+ and defined symbols.
16
14
 
17
- Initializes an empty list to store module information.
15
+ Returns
16
+ -------
17
+ None
18
+ This method does not return any value.
18
19
  """
20
+
21
+ # List to hold information about imported modules
19
22
  self.imports: List[Dict[str, Any]] = []
20
23
 
21
24
  def collect(self) -> 'Imports':
22
25
  """
23
26
  Collect information about user-defined Python modules currently loaded.
24
27
 
25
- For each qualifying module, gathers:
26
- - The module's name.
27
- - The relative file path to the module from the current working directory.
28
- - A list of symbols (functions, classes, or submodules) defined in the module.
28
+ Iterates through all modules in `sys.modules` and gathers details for each qualifying module:
29
+ - Module name.
30
+ - Relative file path from the current working directory.
31
+ - List of symbols (functions, classes, or submodules) defined in the module.
29
32
 
30
- Excludes:
31
- - Modules from the standard library.
32
- - Modules from the active virtual environment (if any).
33
- - Binary extension modules (.pyd, .dll, .so).
34
- - Special modules like "__main__", "__mp_main__", and modules starting with "_distutils".
33
+ Excludes modules that:
34
+ - Are part of the standard library.
35
+ - Reside in the active virtual environment (if any).
36
+ - Are binary extension modules (e.g., `.pyd`, `.dll`, `.so`).
37
+ - Are special modules such as `"__main__"`, `"__mp_main__"`, or those starting with `"_distutils"`.
35
38
 
36
- The collected information is stored in `self.imports` as a list of dictionaries.
39
+ The collected information is stored in `self.imports` as a list of dictionaries, each containing the module's name, file path, and symbols.
37
40
 
38
41
  Returns
39
42
  -------
40
43
  Imports
41
- The current instance with updated imports information.
44
+ The current instance of `Imports` with the `imports` attribute updated to include information about the collected modules.
42
45
  """
43
46
 
44
47
  import sys
45
48
  import os
46
49
  import types
47
50
 
51
+ # Clear any previously collected imports
48
52
  self.imports.clear()
53
+
54
+ # Get standard library paths to exclude standard modules
49
55
  stdlib_paths = [os.path.dirname(os.__file__)]
56
+
57
+ # Get virtual environment path if active
50
58
  venv_path = os.environ.get("VIRTUAL_ENV")
51
59
  if venv_path:
52
60
  venv_path = os.path.abspath(venv_path)
53
61
 
62
+ # Iterate over all loaded modules
54
63
  for name, module in list(sys.modules.items()):
55
- file:str = getattr(module, '__file__', None)
64
+ file: str = getattr(module, '__file__', None)
56
65
 
66
+ # Filter out unwanted modules based on path, type, and name
57
67
  if (
58
68
  file
59
69
  and not any(file.startswith(stdlib_path) for stdlib_path in stdlib_paths)
@@ -62,18 +72,25 @@ class Imports(IImports):
62
72
  and name not in ("__main__", "__mp_main__")
63
73
  and not name.startswith("_distutils")
64
74
  ):
75
+
76
+ # Get relative file path from current working directory
65
77
  rel_file = os.path.relpath(file, os.getcwd())
66
78
  symbols = []
67
79
 
80
+ # Collect symbols defined in the module (functions, classes, submodules)
68
81
  try:
69
82
  for attr in dir(module):
70
83
  value = getattr(module, attr)
71
84
  if isinstance(value, (types.FunctionType, type, types.ModuleType)):
85
+
86
+ # Ensure symbol is defined in this module
72
87
  if getattr(value, '__module__', None) == name:
73
88
  symbols.append(attr)
74
89
  except Exception:
90
+ # Ignore errors during symbol collection
75
91
  pass
76
92
 
93
+ # Only add modules that are not __init__.py and have symbols
77
94
  if not rel_file.endswith('__init__.py') and symbols:
78
95
  self.imports.append({
79
96
  "name": name,
@@ -81,32 +98,45 @@ class Imports(IImports):
81
98
  "symbols": symbols,
82
99
  })
83
100
 
101
+ # Return the current instance with updated imports
84
102
  return self
85
103
 
86
104
  def display(self) -> None:
87
105
  """
88
106
  Display a formatted table of collected import statements using the Rich library.
89
107
 
90
- If the imports have not been collected yet, it calls `self.collect()` to gather them.
91
- The table includes columns for the import name, file, and imported symbols, and is
92
- rendered inside a styled panel in the console.
108
+ This method presents a visual summary of all collected user-defined Python modules.
109
+ If the imports have not been collected yet, it automatically calls `self.collect()` to gather them.
110
+ The output is rendered as a table inside a styled panel in the console, showing each module's name,
111
+ relative file path, and its defined symbols.
112
+
113
+ Parameters
114
+ ----------
115
+ None
93
116
 
94
117
  Returns
95
118
  -------
96
119
  None
120
+ This method does not return any value. It outputs the formatted table to the console.
97
121
  """
98
122
 
123
+ # Collect imports if not already done
99
124
  if not self.imports:
100
125
  self.collect()
101
126
 
127
+ # Import Rich components for console output
102
128
  from rich.console import Console
103
129
  from rich.table import Table
104
130
  from rich.box import MINIMAL
105
131
  from rich.panel import Panel
106
132
 
133
+ # Create a console instance for output
107
134
  console = Console()
135
+
136
+ # Set table width to 75% of console width
108
137
  width = int(console.size.width * 0.75)
109
138
 
139
+ # Create a table with minimal box style and custom formatting
110
140
  table = Table(
111
141
  box=MINIMAL,
112
142
  show_header=True,
@@ -117,14 +147,17 @@ class Imports(IImports):
117
147
  collapse_padding=True,
118
148
  )
119
149
 
150
+ # Add columns for module name, file path, and symbols
120
151
  table.add_column("Name", style="cyan", no_wrap=True)
121
152
  table.add_column("File", style="white")
122
153
  table.add_column("Symbols", style="magenta")
123
154
 
155
+ # Populate the table with sorted import data
124
156
  for imp in sorted(self.imports, key=lambda x: x["name"].lower()):
125
157
  symbols_str = ", ".join(imp["symbols"])
126
158
  table.add_row(imp["name"], imp["file"], symbols_str)
127
159
 
160
+ # Render the table inside a styled panel in the console
128
161
  console.print(Panel(
129
162
  table,
130
163
  title="[bold blue]🔎 Loaded Python Modules (Orionis Imports Trace)[/bold blue]",
@@ -134,10 +167,17 @@ class Imports(IImports):
134
167
 
135
168
  def clear(self) -> None:
136
169
  """
137
- Clear the collected imports list.
170
+ Remove all entries from the collected imports list.
171
+
172
+ This method resets the `imports` attribute by removing all currently stored
173
+ module information. It is useful for discarding previously collected data
174
+ before performing a new collection or when a fresh state is required.
138
175
 
139
176
  Returns
140
177
  -------
141
178
  None
179
+ This method does not return any value. The `imports` list is emptied in place.
142
180
  """
181
+
182
+ # Remove all items from the imports list to reset its state
143
183
  self.imports.clear()
@@ -25,6 +25,7 @@ Notes
25
25
  - Thread safety is provided via a threading.Lock.
26
26
 
27
27
  """
28
+
28
29
  import builtins
29
30
  from collections import defaultdict
30
31
  from threading import Lock
@@ -40,40 +41,48 @@ _import_lock = Lock()
40
41
 
41
42
  def custom_import(name, globals=None, locals=None, fromlist=(), level=0):
42
43
  """
43
- Custom import function that tracks imports of 'orionis' modules.
44
+ Tracks and logs imports of modules whose names start with 'orionis'.
45
+
46
+ This function overrides Python's built-in import mechanism to monitor
47
+ how many times modules from the 'orionis' package are imported. It
48
+ increments an internal counter for each such import and prints a log
49
+ message with the module name, import count, and fromlist. Thread safety
50
+ is ensured using a lock.
44
51
 
45
52
  Parameters
46
53
  ----------
47
54
  name : str
48
55
  The name of the module to import.
49
56
  globals : dict, optional
50
- The global namespace.
57
+ The global namespace in which the import is performed.
51
58
  locals : dict, optional
52
- The local namespace.
59
+ The local namespace in which the import is performed.
53
60
  fromlist : tuple, optional
54
61
  Names to import from the module.
55
62
  level : int, optional
56
- Relative import level.
63
+ Relative import level (0 for absolute, >0 for relative).
57
64
 
58
65
  Returns
59
66
  -------
60
- module
61
- The imported module.
67
+ module : ModuleType
68
+ The imported module object as returned by the original import function.
62
69
  """
63
- # Check if the module name starts with 'orionis'
70
+ # Only track imports for modules starting with 'orionis'
64
71
  if str(name).startswith("orionis"):
65
72
  with _import_lock:
73
+
74
+ # Increment the import count for this module
66
75
  _import_count[name] += 1
67
76
  count = _import_count[name]
68
77
 
69
- # Print the import details
78
+ # Print import details to the console
70
79
  print(
71
80
  f"\033[1;37mModule\033[0m: \033[90m{name}\033[0m | "
72
81
  f"\033[1;37mImported\033[0m: \033[90m{count}\033[0m | "
73
82
  f"\033[1;37mFromList\033[0m: \033[90m{fromlist}\033[0m"
74
83
  )
75
84
 
76
- # Call the original import function
85
+ # Delegate the actual import to the original __import__ function
77
86
  return _original_import(name, globals, locals, fromlist, level)
78
87
 
79
88
  # Override the built-in __import__ function
@@ -4,21 +4,10 @@ import psutil
4
4
  from orionis.services.system.contracts.workers import IWorkers
5
5
 
6
6
  class Workers(IWorkers):
7
- """
8
- Estimate the optimal number of worker processes based on system CPU and memory resources.
9
-
10
- This class calculates the recommended number of Uvicorn (or similar) workers by considering:
11
- the number of available CPU cores, total system memory (RAM), and the estimated memory usage per worker.
12
-
13
- Parameters
14
- ----------
15
- ram_per_worker : float, optional
16
- Estimated amount of RAM in gigabytes (GB) that each worker will consume. Default is 0.5.
17
- """
18
7
 
19
8
  def __init__(self, ram_per_worker: float = 0.5):
20
9
  """
21
- Initialize the worker system with resource constraints.
10
+ Initialize the Workers system with resource constraints.
22
11
 
23
12
  Parameters
24
13
  ----------
@@ -33,31 +22,75 @@ class Workers(IWorkers):
33
22
  Total system RAM in gigabytes.
34
23
  _ram_per_worker : float
35
24
  RAM allocated per worker in gigabytes.
25
+
26
+ Returns
27
+ -------
28
+ None
29
+ This constructor does not return a value.
36
30
  """
31
+
32
+ # Get the number of CPU cores available
37
33
  self._cpu_count = multiprocessing.cpu_count()
34
+
35
+ # Get the total system RAM in gigabytes
38
36
  self._ram_total_gb = psutil.virtual_memory().total / (1024 ** 3)
37
+
38
+ # Set the RAM allocated per worker
39
39
  self._ram_per_worker = ram_per_worker
40
40
 
41
41
  def setRamPerWorker(self, ram_per_worker: float) -> None:
42
42
  """
43
- Set the amount of RAM allocated per worker.
43
+ Update the RAM allocation per worker.
44
44
 
45
45
  Parameters
46
46
  ----------
47
47
  ram_per_worker : float
48
- Amount of RAM (in GB) allocated per worker.
48
+ The new amount of RAM (in GB) to allocate for each worker.
49
+
50
+ Returns
51
+ -------
52
+ None
53
+ This method does not return a value. It updates the internal RAM allocation setting.
54
+
55
+ Notes
56
+ -----
57
+ Changing the RAM allocation per worker may affect the recommended number of workers
58
+ calculated by the system. This method only updates the internal configuration and does
59
+ not trigger any recalculation automatically.
49
60
  """
61
+
62
+ # Update the RAM allocated per worker
50
63
  self._ram_per_worker = ram_per_worker
51
64
 
52
65
  def calculate(self) -> int:
53
66
  """
54
- Compute the maximum number of workers supported by the current machine.
67
+ Compute the recommended maximum number of worker processes for the current machine,
68
+ considering both CPU and memory constraints.
69
+
70
+ Parameters
71
+ ----------
72
+ None
55
73
 
56
74
  Returns
57
75
  -------
58
76
  int
59
- The recommended number of worker processes based on CPU and memory constraints.
77
+ The maximum number of worker processes that can be safely run in parallel,
78
+ determined by the lesser of available CPU cores and memory capacity.
79
+
80
+ Notes
81
+ -----
82
+ The calculation is based on:
83
+ - The total number of CPU cores available.
84
+ - The total system RAM divided by the RAM allocated per worker.
85
+ The method ensures that neither CPU nor memory resources are overcommitted.
86
+
60
87
  """
88
+
89
+ # Calculate the maximum workers allowed by CPU core count
61
90
  max_workers_by_cpu = self._cpu_count
91
+
92
+ # Calculate the maximum workers allowed by available RAM
62
93
  max_workers_by_ram = math.floor(self._ram_total_gb / self._ram_per_worker)
94
+
95
+ # Return the minimum of the two to avoid overcommitting resources
63
96
  return min(max_workers_by_cpu, max_workers_by_ram)
@@ -65,6 +65,7 @@ class TestDumper(ITestDumper):
65
65
  AsyncTestCase,
66
66
  SyncTestCase,
67
67
  unittest.TestCase,
68
+ unittest.IsolatedAsyncioTestCase
68
69
  ),
69
70
  )
70
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.406.0
3
+ Version: 0.408.0
4
4
  Summary: Orionis Framework – Elegant, Fast, and Powerful.
5
5
  Home-page: https://github.com/orionis-framework/framework
6
6
  Author: Raul Mauricio Uñate Castro