orionis 0.401.0__py3-none-any.whl → 0.403.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 (37) hide show
  1. orionis/container/providers/service_provider.py +4 -4
  2. orionis/foundation/application.py +4 -2
  3. orionis/foundation/config/base.py +112 -0
  4. orionis/foundation/config/logging/entities/channels.py +87 -69
  5. orionis/foundation/config/logging/entities/chunked.py +37 -92
  6. orionis/foundation/config/logging/entities/daily.py +43 -81
  7. orionis/foundation/config/logging/entities/hourly.py +16 -71
  8. orionis/foundation/config/logging/entities/logging.py +37 -38
  9. orionis/foundation/config/logging/entities/monthly.py +19 -75
  10. orionis/foundation/config/logging/entities/stack.py +13 -69
  11. orionis/foundation/config/logging/entities/weekly.py +19 -75
  12. orionis/foundation/config/logging/enums/levels.py +6 -7
  13. orionis/foundation/config/logging/validators/__init__.py +7 -0
  14. orionis/foundation/config/logging/validators/level.py +54 -0
  15. orionis/foundation/config/logging/validators/path.py +34 -0
  16. orionis/foundation/providers/console_provider.py +0 -4
  17. orionis/foundation/providers/dumper_provider.py +0 -4
  18. orionis/foundation/providers/logger_provider.py +17 -0
  19. orionis/foundation/providers/path_resolver_provider.py +0 -4
  20. orionis/foundation/providers/progress_bar_provider.py +0 -4
  21. orionis/foundation/providers/workers_provider.py +0 -4
  22. orionis/metadata/framework.py +1 -1
  23. orionis/services/log/contracts/__init__.py +0 -0
  24. orionis/services/log/contracts/log_service.py +23 -0
  25. orionis/services/log/exceptions/__init__.py +5 -0
  26. orionis/services/log/exceptions/runtime.py +19 -0
  27. orionis/services/log/handlers/__init__.py +0 -0
  28. orionis/services/log/handlers/size_rotating.py +52 -0
  29. orionis/services/log/handlers/timed_rotating.py +53 -0
  30. orionis/services/log/log_service.py +188 -143
  31. orionis/support/facades/logger.py +15 -0
  32. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/METADATA +1 -1
  33. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/RECORD +37 -24
  34. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/WHEEL +0 -0
  35. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/licenses/LICENCE +0 -0
  36. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/top_level.txt +0 -0
  37. {orionis-0.401.0.dist-info → orionis-0.403.0.dist-info}/zip-safe +0 -0
@@ -1,9 +1,11 @@
1
- from dataclasses import asdict, dataclass, field, fields
1
+ from dataclasses import dataclass, field
2
+ from orionis.foundation.config.base import BaseConfigEntity
3
+ from orionis.foundation.config.logging.validators import IsValidPath, IsValidLevel
2
4
  from orionis.foundation.exceptions import OrionisIntegrityException
3
5
  from orionis.foundation.config.logging.enums import Level
4
6
 
5
7
  @dataclass(unsafe_hash=True, kw_only=True)
6
- class Weekly:
8
+ class Weekly(BaseConfigEntity):
7
9
  """
8
10
  Configuration entity for weekly log file management.
9
11
 
@@ -14,24 +16,24 @@ class Weekly:
14
16
  """
15
17
 
16
18
  path: str = field(
17
- default='storage/log/application.log',
18
- metadata={
19
+ default = 'storage/log/application.log',
20
+ metadata = {
19
21
  "description": "The file path where the log is stored.",
20
22
  "default": "storage/log/application.log",
21
23
  },
22
24
  )
23
25
 
24
26
  level: int | str | Level = field(
25
- default=Level.INFO,
26
- metadata={
27
- "description": "The logging level (e.g., 'info', 'error', 'debug').",
27
+ default = Level.INFO,
28
+ metadata = {
29
+ "description": "The logging level (e.g., DEBUG, INFO, WARNING, ERROR, CRITICAL).",
28
30
  "default": Level.INFO,
29
31
  },
30
32
  )
31
33
 
32
34
  retention_weeks: int = field(
33
- default=4,
34
- metadata={
35
+ default = 4,
36
+ metadata = {
35
37
  "description": "The number of weeks to retain log files before deletion.",
36
38
  "default": 4,
37
39
  },
@@ -49,76 +51,18 @@ class Weekly:
49
51
  Raises:
50
52
  OrionisIntegrityException: If any attribute is invalid.
51
53
  """
52
- # Validate 'path'
53
- if not isinstance(self.path, str) or not self.path.strip():
54
- raise OrionisIntegrityException(
55
- f"File cache configuration error: 'path' must be a non-empty string, got {repr(self.path)}."
56
- )
54
+ # Validate 'path' using the IsValidPath validator
55
+ IsValidPath(self.path)
57
56
 
58
- # Validate 'level'
59
- valid_level_types = (int, str, Level)
60
- if not isinstance(self.level, valid_level_types):
61
- raise OrionisIntegrityException(
62
- f"File cache configuration error: 'level' must be int, str, or Level enum, got {type(self.level).__name__}."
63
- )
64
-
65
- if isinstance(self.level, str):
66
- _value = self.level.strip().upper()
67
- if not _value:
68
- raise OrionisIntegrityException(
69
- "File cache configuration error: 'level' string cannot be empty."
70
- )
71
- if _value not in Level.__members__:
72
- raise OrionisIntegrityException(
73
- f"File cache configuration error: 'level' must be one of {list(Level.__members__.keys())}, got '{self.level}'."
74
- )
75
- self.level = Level[_value].value
76
- elif isinstance(self.level, int):
77
- valid_values = [level.value for level in Level]
78
- if self.level not in valid_values:
79
- raise OrionisIntegrityException(
80
- f"File cache configuration error: 'level' must be one of {valid_values}, got '{self.level}'."
81
- )
82
- elif isinstance(self.level, Level):
83
- self.level = self.level.value
57
+ # Validate 'level' using the IsValidLevel validator
58
+ IsValidLevel(self.level)
84
59
 
85
60
  # Validate 'retention_weeks'
86
- if not isinstance(self.retention_weeks, int) or self.retention_weeks < 0:
61
+ if not isinstance(self.retention_weeks, int):
87
62
  raise OrionisIntegrityException(
88
- f"File cache configuration error: 'retention_weeks' must be a non-negative integer, got {self.retention_weeks}."
63
+ f"Invalid type for 'retention_weeks': expected int, got {type(self.retention_weeks).__name__}."
89
64
  )
90
65
  if self.retention_weeks < 1 or self.retention_weeks > 12:
91
66
  raise OrionisIntegrityException(
92
- f"File cache configuration error: 'retention_weeks' must be between 1 and 12, got {self.retention_weeks}."
93
- )
94
-
95
- def toDict(self) -> dict:
96
- """
97
- Converts the Weekly configuration object to a dictionary.
98
-
99
- Returns:
100
- dict: Dictionary representation of the Weekly configuration.
101
- """
102
- return asdict(self)
103
-
104
- def getFields(self):
105
- """
106
- Retrieves a list of field information for the current dataclass instance.
107
-
108
- Returns:
109
- list: A list of dictionaries, each containing details about a field:
110
- - name (str): The name of the field.
111
- - type (type): The type of the field.
112
- - default: The default value of the field, if specified; otherwise, the value from metadata or None.
113
- - metadata (mapping): The metadata associated with the field.
114
- """
115
- __fields = []
116
- for field in fields(self):
117
- __metadata = dict(field.metadata) or {}
118
- __fields.append({
119
- "name": field.name,
120
- "type": field.type.__name__ if hasattr(field.type, '__name__') else str(field.type),
121
- "default": field.default if (field.default is not None and '_MISSING_TYPE' not in str(field.default)) else __metadata.get('default', None),
122
- "metadata": __metadata
123
- })
124
- return __fields
67
+ f"'retention_weeks' must be an integer between 1 and 12 (inclusive), but got {self.retention_weeks}."
68
+ )
@@ -1,11 +1,11 @@
1
1
  from enum import Enum
2
+ import logging
2
3
 
3
4
  class Level(Enum):
4
5
  """
5
6
  Enumeration of standard logging levels.
6
7
 
7
8
  Attributes:
8
- NOTSET (int): No specific logging level set. Value is 0.
9
9
  DEBUG (int): Detailed information, typically of interest only when diagnosing problems. Value is 10.
10
10
  INFO (int): Confirmation that things are working as expected. Value is 20.
11
11
  WARNING (int): An indication that something unexpected happened, or indicative of some problem in the near future. Value is 30.
@@ -13,9 +13,8 @@ class Level(Enum):
13
13
  CRITICAL (int): A very serious error, indicating that the program itself may be unable to continue running. Value is 50.
14
14
  """
15
15
 
16
- NOTSET = 0
17
- DEBUG = 10
18
- INFO = 20
19
- WARNING = 30
20
- ERROR = 40
21
- CRITICAL = 50
16
+ DEBUG = logging.DEBUG
17
+ INFO = logging.INFO
18
+ WARNING = logging.WARNING
19
+ ERROR = logging.ERROR
20
+ CRITICAL = logging.CRITICAL
@@ -0,0 +1,7 @@
1
+ from .level import IsValidLevel
2
+ from .path import IsValidPath
3
+
4
+ __all__ = [
5
+ "IsValidLevel",
6
+ "IsValidPath",
7
+ ]
@@ -0,0 +1,54 @@
1
+ from typing import Any
2
+ from orionis.foundation.config.logging.enums import Level
3
+ from orionis.foundation.exceptions import OrionisIntegrityException
4
+
5
+ class _IsValidLevel:
6
+ """
7
+ Validator that checks if a value is a valid logging level.
8
+ Accepts int, str, or Level enum.
9
+ """
10
+
11
+ _level_names = {level.name for level in Level}
12
+ _level_values = {level.value for level in Level}
13
+
14
+ def __call__(self, value: Any) -> None:
15
+ """
16
+ Validate the provided logging level value.
17
+ Parameters
18
+ ----------
19
+ value : Any
20
+ The value to validate as a logging level. Can be an integer, string, or Level enum instance.
21
+ Raises
22
+ ------
23
+ OrionisIntegrityException
24
+ If the value is not a valid logging level or not of an accepted type (int, str, or Level).
25
+ Notes
26
+ -----
27
+ - If `value` is an integer, it must be present in `self._level_values`.
28
+ - If `value` is a string, it is stripped, uppercased, and must be present in `self._level_names`.
29
+ - If `value` is a Level enum instance, it is accepted as valid.
30
+ """
31
+ if isinstance(value, Level):
32
+ return
33
+
34
+ if isinstance(value, int):
35
+ if value not in self._level_values:
36
+ raise OrionisIntegrityException(
37
+ f"'level' must be one of {sorted(self._level_values)}, got {value}."
38
+ )
39
+ return
40
+
41
+ if isinstance(value, str):
42
+ name = value.strip().upper()
43
+ if name not in self._level_names:
44
+ raise OrionisIntegrityException(
45
+ f"'level' must be one of {sorted(self._level_names)}, got '{value}'."
46
+ )
47
+ return
48
+
49
+ raise OrionisIntegrityException(
50
+ f"'level' must be int, str, or Level enum, got {type(value).__name__}."
51
+ )
52
+
53
+ # Exported singleton instance
54
+ IsValidLevel = _IsValidLevel()
@@ -0,0 +1,34 @@
1
+ from typing import Any
2
+ from orionis.foundation.exceptions import OrionisIntegrityException
3
+
4
+ class __IsValidPath:
5
+ """
6
+ __IsValidPath is a callable class used to validate that a given value is a non-empty string representing a file path.
7
+
8
+ Methods
9
+ -------
10
+ __call__(value: Any) -> None
11
+
12
+ Raises
13
+ ------
14
+ OrionisIntegrityException
15
+ If the provided value is not a non-empty string representing a file path.
16
+ """
17
+
18
+ def __call__(self, value: Any) -> None:
19
+ """
20
+ Validates that the provided value is a non-empty string representing a file path.
21
+
22
+ Args:
23
+ value (Any): The value to validate as a file path.
24
+
25
+ Raises:
26
+ OrionisIntegrityException: If the value is not a non-empty string.
27
+ """
28
+ if not isinstance(value, str) or not value.strip():
29
+ raise OrionisIntegrityException(
30
+ f"File cache configuration error: 'path' must be a non-empty string, got {repr(value)}."
31
+ )
32
+
33
+ # Exported singleton instance
34
+ IsValidPath = __IsValidPath()
@@ -3,10 +3,6 @@ from orionis.console.output.contracts.console import IConsole
3
3
  from orionis.container.providers.service_provider import ServiceProvider
4
4
 
5
5
  class ConsoleProvider(ServiceProvider):
6
- """
7
- Debug provider for the Orionis framework.
8
- This provider is responsible for debugging functionalities.
9
- """
10
6
 
11
7
  def register(self) -> None:
12
8
  """
@@ -3,10 +3,6 @@ from orionis.console.dumper.contracts.dump import IDebug
3
3
  from orionis.container.providers.service_provider import ServiceProvider
4
4
 
5
5
  class DumperProvider(ServiceProvider):
6
- """
7
- Debug provider for the Orionis framework.
8
- This provider is responsible for debugging functionalities.
9
- """
10
6
 
11
7
  def register(self) -> None:
12
8
  """
@@ -0,0 +1,17 @@
1
+ from orionis.container.providers.service_provider import ServiceProvider
2
+ from orionis.services.log.contracts.log_service import ILoggerService
3
+ from orionis.services.log.log_service import LoggerService
4
+
5
+ class LoggerProvider(ServiceProvider):
6
+
7
+ def register(self) -> None:
8
+ """
9
+ Register services into the application container.
10
+ """
11
+ self.app.instance(ILoggerService, LoggerService(self.app.config('logging')), alias="core.orionis.logger")
12
+
13
+ def boot(self) -> None:
14
+ """
15
+ Perform any post-registration bootstrapping or initialization.
16
+ """
17
+ pass
@@ -3,10 +3,6 @@ from orionis.services.paths.contracts.resolver import IResolver
3
3
  from orionis.services.paths.resolver import Resolver
4
4
 
5
5
  class PathResolverProvider(ServiceProvider):
6
- """
7
- Debug provider for the Orionis framework.
8
- This provider is responsible for debugging functionalities.
9
- """
10
6
 
11
7
  def register(self) -> None:
12
8
  """
@@ -3,10 +3,6 @@ from orionis.console.dynamic.progress_bar import ProgressBar
3
3
  from orionis.container.providers.service_provider import ServiceProvider
4
4
 
5
5
  class ProgressBarProvider(ServiceProvider):
6
- """
7
- Debug provider for the Orionis framework.
8
- This provider is responsible for debugging functionalities.
9
- """
10
6
 
11
7
  def register(self) -> None:
12
8
  """
@@ -3,10 +3,6 @@ from orionis.services.system.contracts.workers import IWorkers
3
3
  from orionis.services.system.workers import Workers
4
4
 
5
5
  class WorkersProvider(ServiceProvider):
6
- """
7
- Debug provider for the Orionis framework.
8
- This provider is responsible for debugging functionalities.
9
- """
10
6
 
11
7
  def register(self) -> None:
12
8
  """
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.401.0"
8
+ VERSION = "0.403.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
File without changes
@@ -0,0 +1,23 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class ILoggerService(ABC):
4
+
5
+ @abstractmethod
6
+ def info(self, message: str) -> None:
7
+ """Log an informational message."""
8
+ pass
9
+
10
+ @abstractmethod
11
+ def error(self, message: str) -> None:
12
+ """Log an error message."""
13
+ pass
14
+
15
+ @abstractmethod
16
+ def warning(self, message: str) -> None:
17
+ """Log a warning message."""
18
+ pass
19
+
20
+ @abstractmethod
21
+ def debug(self, message: str) -> None:
22
+ """Log a debug message."""
23
+ pass
@@ -0,0 +1,5 @@
1
+ from .runtime import LoggerRuntimeError
2
+
3
+ __all__ = [
4
+ "LoggerRuntimeError",
5
+ ]
@@ -0,0 +1,19 @@
1
+ class LoggerRuntimeError(RuntimeError):
2
+
3
+ def __init__(self, msg: str):
4
+ """
5
+ Parameters
6
+ ----------
7
+ msg : str
8
+ Descriptive error message explaining the cause of the exception.
9
+ """
10
+ super().__init__(msg)
11
+
12
+ def __str__(self) -> str:
13
+ """
14
+ Returns
15
+ -------
16
+ str
17
+ Formatted string describing the exception.
18
+ """
19
+ return str(self.args[0])
File without changes
@@ -0,0 +1,52 @@
1
+ from datetime import datetime
2
+ from logging.handlers import RotatingFileHandler
3
+ from pathlib import Path
4
+ import os
5
+
6
+ class PrefixedSizeRotatingFileHandler(RotatingFileHandler):
7
+
8
+ def rotation_filename(self, default_name):
9
+ """
10
+ Generates a rotated log filename by prefixing the original filename with a timestamp.
11
+ This method takes an original file path, extracts its directory, base name, and extension,
12
+ and returns a new file path where the base name is prefixed with the current timestamp
13
+ in the format 'YYYYMMDD_HHMMSS'. If the target directory does not exist, it is created.
14
+ The original file path to be rotated.
15
+ The new file path with a timestamp prefix added to the base name.
16
+ Notes
17
+ -----
18
+ - The timestamp is based on the current local time.
19
+ - The method ensures that the parent directory for the new file exists.
20
+
21
+ Returns
22
+ -------
23
+ str
24
+ The new filename with a timestamp prefix in the format 'YYYYMMDD_HHMMSS'.
25
+ """
26
+ # Split the original path to extract the base name and extension
27
+ if '/' in default_name:
28
+ parts = default_name.split('/')
29
+ elif '\\' in default_name:
30
+ parts = default_name.split('\\')
31
+ else:
32
+ parts = default_name.split(os.sep)
33
+
34
+ # Get the base name and extension
35
+ filename, ext = os.path.splitext(parts[-1])
36
+
37
+ # Create the path without the last part
38
+ path = os.path.join(*parts[:-1]) if len(parts) > 1 else ''
39
+
40
+ # Prefix the base name with a timestamp
41
+ prefix = datetime.now().strftime("%Y%m%d_%H%M%S")
42
+
43
+ # Join the path, prefix, and filename to create the full path
44
+ full_path = os.path.join(path, f"{prefix}_{filename}{ext}")
45
+
46
+ # Ensure the log directory exists
47
+ log_dir = Path(full_path).parent
48
+ if not log_dir.exists():
49
+ log_dir.mkdir(parents=True, exist_ok=True)
50
+
51
+ # Return the full path as a string
52
+ return full_path
@@ -0,0 +1,53 @@
1
+ from datetime import datetime
2
+ from logging.handlers import TimedRotatingFileHandler
3
+ from pathlib import Path
4
+ import os
5
+
6
+ class PrefixedTimedRotatingFileHandler(TimedRotatingFileHandler):
7
+
8
+ def rotation_filename(self, default_name):
9
+ """
10
+ Generates a rotated log filename by prefixing the original filename with a timestamp.
11
+ This method takes an original file path, extracts its directory, base name, and extension,
12
+ and returns a new file path where the base name is prefixed with the current timestamp
13
+ in the format 'YYYYMMDD_HHMMSS'. If the target directory does not exist, it is created.
14
+ The original file path to be rotated.
15
+ The new file path with a timestamp prefix added to the base name.
16
+ Notes
17
+ -----
18
+ - The timestamp is based on the current local time.
19
+ - The method ensures that the parent directory for the new file exists.
20
+
21
+ Returns
22
+ -------
23
+ str
24
+ The new filename with a timestamp prefix in the format 'YYYYMMDD_HHMMSS'.
25
+ """
26
+
27
+ # Split the original path to extract the base name and extension
28
+ if '/' in default_name:
29
+ parts = default_name.split('/')
30
+ elif '\\' in default_name:
31
+ parts = default_name.split('\\')
32
+ else:
33
+ parts = default_name.split(os.sep)
34
+
35
+ # Get the base name and extension
36
+ filename, ext = os.path.splitext(parts[-1])
37
+
38
+ # Create the path without the last part
39
+ path = os.path.join(*parts[:-1]) if len(parts) > 1 else ''
40
+
41
+ # Prefix the base name with a timestamp
42
+ prefix = datetime.now().strftime("%Y%m%d_%H%M%S")
43
+
44
+ # Join the path, prefix, and filename to create the full path
45
+ full_path = os.path.join(path, f"{prefix}_{filename}{ext}")
46
+
47
+ # Ensure the log directory exists
48
+ log_dir = Path(full_path).parent
49
+ if not log_dir.exists():
50
+ log_dir.mkdir(parents=True, exist_ok=True)
51
+
52
+ # Return the full path as a string
53
+ return full_path