logging-module 0.2.0__tar.gz
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.
- logging_module-0.2.0/LICENSE +21 -0
- logging_module-0.2.0/PKG-INFO +151 -0
- logging_module-0.2.0/README.md +124 -0
- logging_module-0.2.0/logging_module/__init__.py +32 -0
- logging_module-0.2.0/logging_module/config.py +10 -0
- logging_module-0.2.0/logging_module/file_utils.py +108 -0
- logging_module-0.2.0/logging_module/my_logging.py +207 -0
- logging_module-0.2.0/logging_module.egg-info/PKG-INFO +151 -0
- logging_module-0.2.0/logging_module.egg-info/SOURCES.txt +15 -0
- logging_module-0.2.0/logging_module.egg-info/dependency_links.txt +1 -0
- logging_module-0.2.0/logging_module.egg-info/requires.txt +4 -0
- logging_module-0.2.0/logging_module.egg-info/top_level.txt +1 -0
- logging_module-0.2.0/pyproject.toml +46 -0
- logging_module-0.2.0/setup.cfg +4 -0
- logging_module-0.2.0/tests/test_config.py +14 -0
- logging_module-0.2.0/tests/test_file_utils.py +93 -0
- logging_module-0.2.0/tests/test_my_logging.py +152 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathan T Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: logging-module
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A comprehensive logging and file utility module for Python applications
|
|
5
|
+
Author-email: Nathan T Nguyen <nhatnguyen2807@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: CreatorGithub, https://github.com/NhatNguyen5
|
|
8
|
+
Project-URL: Homepage, https://test.pypi.org/project/logging-module/
|
|
9
|
+
Project-URL: Documentation, https://github.com/NhatNguyen5/logging_module/wiki/Instructions
|
|
10
|
+
Project-URL: Repository, https://github.com/NhatNguyen5/logging-module.git
|
|
11
|
+
Project-URL: Issues, https://github.com/NhatNguyen5/logging_module/issues
|
|
12
|
+
Keywords: logging,file-utilities,tracing,debugging
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: bump2version; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Logging Module
|
|
29
|
+
|
|
30
|
+
A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Flexible Logging**: Timestamp-based logging with caller information
|
|
35
|
+
- **File Utilities**: Safe file operations with error handling
|
|
36
|
+
- **Configuration**: Easy-to-configure logging and tracing options
|
|
37
|
+
- **Error Management**: Comprehensive error tracking and reporting
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### From PyPI (when published)
|
|
42
|
+
```bash
|
|
43
|
+
pip install logging-module
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### From Local Directory
|
|
47
|
+
```bash
|
|
48
|
+
pip install ./logging_module
|
|
49
|
+
# or in editable mode for development
|
|
50
|
+
pip install -e ./logging_module
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from logging_module import Logger
|
|
57
|
+
|
|
58
|
+
# Create a logger instance
|
|
59
|
+
app_logger = Logger()
|
|
60
|
+
|
|
61
|
+
# Basic logging
|
|
62
|
+
app_logger.log("Application started")
|
|
63
|
+
|
|
64
|
+
# Log with console output
|
|
65
|
+
app_logger.log("Important message", print_to_console=True)
|
|
66
|
+
|
|
67
|
+
# Log with custom console message
|
|
68
|
+
app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
|
|
69
|
+
|
|
70
|
+
# Use as a callable (shortcut for log())
|
|
71
|
+
app_logger("Quick log message")
|
|
72
|
+
|
|
73
|
+
# Control logging behavior
|
|
74
|
+
app_logger.enable_logging(False) # Disable this logger instance
|
|
75
|
+
app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
|
|
76
|
+
|
|
77
|
+
# Configure custom log directory
|
|
78
|
+
custom_logger = Logger(log_dir="my_logs")
|
|
79
|
+
|
|
80
|
+
# Configure project directory for better frame filtering
|
|
81
|
+
project_logger = Logger(project_dir="/path/to/project")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
### Logging Config (`config.py`)
|
|
87
|
+
- `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
|
|
88
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Modules
|
|
95
|
+
|
|
96
|
+
### Logger (`my_logging.py`)
|
|
97
|
+
Main logging class providing timestamped, structured logging with caller information.
|
|
98
|
+
|
|
99
|
+
**Key Features:**
|
|
100
|
+
- Automatic timestamping and file logging
|
|
101
|
+
- Caller information tracking with configurable frame filtering
|
|
102
|
+
- Configurable log directory and project directory
|
|
103
|
+
- Thread-safe file operations
|
|
104
|
+
- Configurable tracing depth
|
|
105
|
+
- Console output options
|
|
106
|
+
- Instance-level enable/disable controls
|
|
107
|
+
|
|
108
|
+
### File Utilities (`file_utils.py`)
|
|
109
|
+
Safe file operations with comprehensive error handling:
|
|
110
|
+
- Path verification with security checks
|
|
111
|
+
- File verification for different operations (read/write/execute)
|
|
112
|
+
- Directory operations with working directory constraints
|
|
113
|
+
|
|
114
|
+
### Configuration (`config.py`)
|
|
115
|
+
Global configuration settings for logging behavior:
|
|
116
|
+
- `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
|
|
117
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
### Prerequisites
|
|
122
|
+
- Python 3.10+
|
|
123
|
+
|
|
124
|
+
### Installing in Development Mode
|
|
125
|
+
```bash
|
|
126
|
+
cd logging_module
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Running Tests
|
|
131
|
+
```bash
|
|
132
|
+
# Install test dependencies
|
|
133
|
+
pip install -e ".[dev]"
|
|
134
|
+
|
|
135
|
+
# Run all tests
|
|
136
|
+
pytest
|
|
137
|
+
|
|
138
|
+
# Run with verbose output
|
|
139
|
+
pytest -v
|
|
140
|
+
|
|
141
|
+
# Run specific test file
|
|
142
|
+
pytest tests/test_my_logging.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
148
|
+
|
|
149
|
+
## Author
|
|
150
|
+
|
|
151
|
+
Nathan T Nguyen
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Logging Module
|
|
2
|
+
|
|
3
|
+
A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Flexible Logging**: Timestamp-based logging with caller information
|
|
8
|
+
- **File Utilities**: Safe file operations with error handling
|
|
9
|
+
- **Configuration**: Easy-to-configure logging and tracing options
|
|
10
|
+
- **Error Management**: Comprehensive error tracking and reporting
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### From PyPI (when published)
|
|
15
|
+
```bash
|
|
16
|
+
pip install logging-module
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### From Local Directory
|
|
20
|
+
```bash
|
|
21
|
+
pip install ./logging_module
|
|
22
|
+
# or in editable mode for development
|
|
23
|
+
pip install -e ./logging_module
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from logging_module import Logger
|
|
30
|
+
|
|
31
|
+
# Create a logger instance
|
|
32
|
+
app_logger = Logger()
|
|
33
|
+
|
|
34
|
+
# Basic logging
|
|
35
|
+
app_logger.log("Application started")
|
|
36
|
+
|
|
37
|
+
# Log with console output
|
|
38
|
+
app_logger.log("Important message", print_to_console=True)
|
|
39
|
+
|
|
40
|
+
# Log with custom console message
|
|
41
|
+
app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
|
|
42
|
+
|
|
43
|
+
# Use as a callable (shortcut for log())
|
|
44
|
+
app_logger("Quick log message")
|
|
45
|
+
|
|
46
|
+
# Control logging behavior
|
|
47
|
+
app_logger.enable_logging(False) # Disable this logger instance
|
|
48
|
+
app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
|
|
49
|
+
|
|
50
|
+
# Configure custom log directory
|
|
51
|
+
custom_logger = Logger(log_dir="my_logs")
|
|
52
|
+
|
|
53
|
+
# Configure project directory for better frame filtering
|
|
54
|
+
project_logger = Logger(project_dir="/path/to/project")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
### Logging Config (`config.py`)
|
|
60
|
+
- `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
|
|
61
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Modules
|
|
68
|
+
|
|
69
|
+
### Logger (`my_logging.py`)
|
|
70
|
+
Main logging class providing timestamped, structured logging with caller information.
|
|
71
|
+
|
|
72
|
+
**Key Features:**
|
|
73
|
+
- Automatic timestamping and file logging
|
|
74
|
+
- Caller information tracking with configurable frame filtering
|
|
75
|
+
- Configurable log directory and project directory
|
|
76
|
+
- Thread-safe file operations
|
|
77
|
+
- Configurable tracing depth
|
|
78
|
+
- Console output options
|
|
79
|
+
- Instance-level enable/disable controls
|
|
80
|
+
|
|
81
|
+
### File Utilities (`file_utils.py`)
|
|
82
|
+
Safe file operations with comprehensive error handling:
|
|
83
|
+
- Path verification with security checks
|
|
84
|
+
- File verification for different operations (read/write/execute)
|
|
85
|
+
- Directory operations with working directory constraints
|
|
86
|
+
|
|
87
|
+
### Configuration (`config.py`)
|
|
88
|
+
Global configuration settings for logging behavior:
|
|
89
|
+
- `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
|
|
90
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
### Prerequisites
|
|
95
|
+
- Python 3.10+
|
|
96
|
+
|
|
97
|
+
### Installing in Development Mode
|
|
98
|
+
```bash
|
|
99
|
+
cd logging_module
|
|
100
|
+
pip install -e .
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Running Tests
|
|
104
|
+
```bash
|
|
105
|
+
# Install test dependencies
|
|
106
|
+
pip install -e ".[dev]"
|
|
107
|
+
|
|
108
|
+
# Run all tests
|
|
109
|
+
pytest
|
|
110
|
+
|
|
111
|
+
# Run with verbose output
|
|
112
|
+
pytest -v
|
|
113
|
+
|
|
114
|
+
# Run specific test file
|
|
115
|
+
pytest tests/test_my_logging.py
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
121
|
+
|
|
122
|
+
## Author
|
|
123
|
+
|
|
124
|
+
Nathan T Nguyen
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging Module - A comprehensive logging and file utility package.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- Logging configuration and management
|
|
6
|
+
- File utilities for logging operations
|
|
7
|
+
- Tracing and debug capabilities
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .my_logging import Logger
|
|
11
|
+
from .config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
12
|
+
from .file_utils import (
|
|
13
|
+
FileUtilsError,
|
|
14
|
+
FileExecutionError,
|
|
15
|
+
FileOperator,
|
|
16
|
+
file_verify_path,
|
|
17
|
+
file_verify_file,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "0.2.0"
|
|
21
|
+
__author__ = "Nathan T Nguyen"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Logger",
|
|
25
|
+
"GLOBAL_LOGGING_ENABLED",
|
|
26
|
+
"GLOBAL_LOG_TRACING_ENABLED",
|
|
27
|
+
"FileUtilsError",
|
|
28
|
+
"FileExecutionError",
|
|
29
|
+
"FileOperator",
|
|
30
|
+
"file_verify_path",
|
|
31
|
+
"file_verify_file",
|
|
32
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
#
|
|
3
|
+
# Logging Module Configuration
|
|
4
|
+
#
|
|
5
|
+
# Configuration for logging and tracing
|
|
6
|
+
#
|
|
7
|
+
#######################################################################
|
|
8
|
+
|
|
9
|
+
GLOBAL_LOGGING_ENABLED = True
|
|
10
|
+
GLOBAL_LOG_TRACING_ENABLED = True
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
#
|
|
3
|
+
# File Utils
|
|
4
|
+
#
|
|
5
|
+
# Utility functions for file and directory operations
|
|
6
|
+
# Handles path verification and error management
|
|
7
|
+
#
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileUtilsError(str, Enum):
|
|
15
|
+
# Directory related errors
|
|
16
|
+
OUTSIDE_WORKING_DIR = "Error: Cannot list {directory} as it is outside the permitted working directory"
|
|
17
|
+
NOT_A_DIRECTORY = "Error: {directory} is not a directory"
|
|
18
|
+
# Read file errors
|
|
19
|
+
FILE_READ_OUTSIDE_WORKING_DIR = "Error: Cannot read {file_path} as it is outside the permitted working directory"
|
|
20
|
+
FILE_READ_NOT_FOUND_OR_NOT_REGULAR = 'Error: File not found or is not a regular file: "{file_path}"'
|
|
21
|
+
# Write file errors
|
|
22
|
+
FILE_WRITE_OUTSIDE_WORKING_DIR = 'Error: Cannot write to "{file_path}" as it is outside the permitted working directory'
|
|
23
|
+
FILE_WRITE_NOT_FOUND_OR_NOT_REGULAR = 'Error: File not found or is not a regular file: "{file_path}"\n Create the file before writing to it.'
|
|
24
|
+
# Executable file errors
|
|
25
|
+
FILE_EXECUTE_OUTSIDE_WORKING_DIR = 'Error: Cannot execute "{file_path}" as it is outside the permitted working directory'
|
|
26
|
+
FILE_EXECUTE_NOT_FOUND_OR_NOT_REGULAR = 'Error: File "{file_path}" not found.'
|
|
27
|
+
FILE_EXECUTE_NOT_PYTHON = 'Error: File "{file_path}" is not a Python file.'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FileExecutionError(str, Enum):
|
|
31
|
+
EXECUTION_FAILED = "Error: executing Python file: {error_message}"
|
|
32
|
+
EXECUTION_TIMEOUT = 'Error: Execution of file "{file_path}" timed out after {timeout} seconds.'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FileOperator(str, Enum):
|
|
36
|
+
READ_FILE = "read_file"
|
|
37
|
+
WRITE_FILE = "write_file"
|
|
38
|
+
EXECUTE_FILE = "execute_file"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def file_verify_path(working_directory: str, directory: str) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Verify that *directory* (relative to *working_directory*) exists and is
|
|
44
|
+
inside the working directory.
|
|
45
|
+
|
|
46
|
+
Returns the resolved path on success, or a ``FileUtilsError`` string on
|
|
47
|
+
failure.
|
|
48
|
+
"""
|
|
49
|
+
path_to_directory = os.path.join(working_directory, directory)
|
|
50
|
+
if (
|
|
51
|
+
not path_to_directory.startswith(working_directory)
|
|
52
|
+
or ".." in os.path.relpath(path_to_directory, working_directory)
|
|
53
|
+
):
|
|
54
|
+
return FileUtilsError.OUTSIDE_WORKING_DIR.value.format(directory=directory)
|
|
55
|
+
|
|
56
|
+
if not os.path.isdir(path_to_directory):
|
|
57
|
+
return FileUtilsError.NOT_A_DIRECTORY.value.format(directory=directory)
|
|
58
|
+
|
|
59
|
+
return path_to_directory
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def file_verify_file(working_directory: str, file: str, options: FileOperator | None = None) -> str:
|
|
63
|
+
"""
|
|
64
|
+
Verify that *file* (relative to *working_directory*) is valid for the
|
|
65
|
+
requested *options* operation.
|
|
66
|
+
|
|
67
|
+
Returns the resolved path on success, or a ``FileUtilsError`` string on
|
|
68
|
+
failure.
|
|
69
|
+
|
|
70
|
+
Raises
|
|
71
|
+
------
|
|
72
|
+
ValueError
|
|
73
|
+
If *options* is not a recognised ``FileOperator`` value.
|
|
74
|
+
"""
|
|
75
|
+
path_to_file = os.path.join(working_directory, file)
|
|
76
|
+
|
|
77
|
+
def _is_outside() -> bool:
|
|
78
|
+
return (
|
|
79
|
+
not path_to_file.startswith(working_directory)
|
|
80
|
+
or ".." in os.path.relpath(path_to_file, working_directory)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
match options:
|
|
84
|
+
case FileOperator.READ_FILE:
|
|
85
|
+
if _is_outside():
|
|
86
|
+
return FileUtilsError.FILE_READ_OUTSIDE_WORKING_DIR.value.format(file_path=path_to_file)
|
|
87
|
+
if not os.path.isfile(path_to_file):
|
|
88
|
+
return FileUtilsError.FILE_READ_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=path_to_file)
|
|
89
|
+
|
|
90
|
+
case FileOperator.WRITE_FILE:
|
|
91
|
+
if _is_outside():
|
|
92
|
+
return FileUtilsError.FILE_WRITE_OUTSIDE_WORKING_DIR.value.format(file_path=path_to_file)
|
|
93
|
+
# Write operations may target a not-yet-existing file; the caller is
|
|
94
|
+
# responsible for creating any missing parent directories. We only
|
|
95
|
+
# validate the path is inside the working directory and return it.
|
|
96
|
+
|
|
97
|
+
case FileOperator.EXECUTE_FILE:
|
|
98
|
+
if _is_outside():
|
|
99
|
+
return FileUtilsError.FILE_EXECUTE_OUTSIDE_WORKING_DIR.value.format(file_path=file)
|
|
100
|
+
if not os.path.isfile(path_to_file):
|
|
101
|
+
return FileUtilsError.FILE_EXECUTE_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=file)
|
|
102
|
+
if not path_to_file.endswith(".py"):
|
|
103
|
+
return FileUtilsError.FILE_EXECUTE_NOT_PYTHON.value.format(file_path=file)
|
|
104
|
+
|
|
105
|
+
case _:
|
|
106
|
+
raise ValueError(f"Invalid file operation option provided: {options!r}")
|
|
107
|
+
|
|
108
|
+
return path_to_file # reached by all arms that don't return early
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
#
|
|
3
|
+
# My Logging Module
|
|
4
|
+
#
|
|
5
|
+
# Provides logging functionality with timestamping and caller info
|
|
6
|
+
# Supports configurable logging and tracing options
|
|
7
|
+
#
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import threading
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from logging_module.file_utils import FileUtilsError, file_verify_path
|
|
14
|
+
from inspect import getframeinfo, stack
|
|
15
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Frame-filtering constants
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
# Prefixes of filenames/paths to always skip (logger internals, frozen frames)
|
|
23
|
+
_IGNORED_PREFIXES = (
|
|
24
|
+
"<frozen",
|
|
25
|
+
"<string>",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Basenames of framework/infra files to skip (blocklist fallback mode)
|
|
29
|
+
_IGNORED_FILES = {
|
|
30
|
+
"spawn.py",
|
|
31
|
+
"process.py",
|
|
32
|
+
"_subprocess.py",
|
|
33
|
+
"server.py",
|
|
34
|
+
"runners.py",
|
|
35
|
+
"config.py",
|
|
36
|
+
"importer.py",
|
|
37
|
+
"__init__.py",
|
|
38
|
+
"cli.py",
|
|
39
|
+
"discover.py",
|
|
40
|
+
"my_logging.py", # skip this file itself
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Logger
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
class Logger:
|
|
48
|
+
"""
|
|
49
|
+
Lightweight file logger with optional call-stack tracing.
|
|
50
|
+
|
|
51
|
+
Usage
|
|
52
|
+
-----
|
|
53
|
+
log = Logger()
|
|
54
|
+
log("Something happened")
|
|
55
|
+
log.log("Something happened", print_to_console=True, console_message="Hey!")
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
log_dir : str
|
|
60
|
+
Directory (relative or absolute) where log files are written.
|
|
61
|
+
Defaults to ``./logs``.
|
|
62
|
+
project_dir : str | None
|
|
63
|
+
Root of your project source tree. When supplied, only frames whose
|
|
64
|
+
absolute path starts with this directory (and are not inside
|
|
65
|
+
``site-packages``) are considered "your code". When ``None`` the
|
|
66
|
+
module falls back to the blocklist defined by ``_IGNORED_FILES`` /
|
|
67
|
+
``_IGNORED_PREFIXES``.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
DEFAULT_LOG_DIR = "logs"
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
log_dir: str = DEFAULT_LOG_DIR,
|
|
75
|
+
project_dir: str | None = None,
|
|
76
|
+
):
|
|
77
|
+
self._enable: bool = True
|
|
78
|
+
self._enable_log_tracing: bool = False
|
|
79
|
+
self._log_dir: str = log_dir
|
|
80
|
+
self._project_dir: str | None = (
|
|
81
|
+
os.path.abspath(project_dir) if project_dir is not None else None
|
|
82
|
+
)
|
|
83
|
+
self._lock = threading.Lock()
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
# Public interface
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def __call__(
|
|
90
|
+
self,
|
|
91
|
+
message: str,
|
|
92
|
+
print_to_console: bool = False,
|
|
93
|
+
console_message: str = "",
|
|
94
|
+
) -> None:
|
|
95
|
+
self.log(message, print_to_console=print_to_console, console_message=console_message)
|
|
96
|
+
|
|
97
|
+
def log(
|
|
98
|
+
self,
|
|
99
|
+
message: str,
|
|
100
|
+
print_to_console: bool = False,
|
|
101
|
+
console_message: str = "",
|
|
102
|
+
) -> None:
|
|
103
|
+
if not GLOBAL_LOGGING_ENABLED or not self._enable:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
now = datetime.now()
|
|
107
|
+
date = now.strftime("%Y-%m-%d")
|
|
108
|
+
time_str = now.strftime("%H:%M:%S")
|
|
109
|
+
timestamp = f"{date} {time_str}"
|
|
110
|
+
|
|
111
|
+
logistic_data = f"[{timestamp}][{self.get_caller_info()}]"
|
|
112
|
+
padded_space = "".rjust(len(logistic_data) + 1)
|
|
113
|
+
formatted_message = str(message).replace("\n", f"\n{padded_space}")
|
|
114
|
+
log_entry = f"{logistic_data} {formatted_message}\n"
|
|
115
|
+
|
|
116
|
+
self._write_to_file(log_entry, date, time_str)
|
|
117
|
+
|
|
118
|
+
if print_to_console:
|
|
119
|
+
print(message if console_message == "" else console_message)
|
|
120
|
+
|
|
121
|
+
def enable_logging(self, enable_logging: bool) -> None:
|
|
122
|
+
self._enable = enable_logging
|
|
123
|
+
|
|
124
|
+
def enable_log_tracing(self, enable_log_tracing: bool) -> None:
|
|
125
|
+
self._enable_log_tracing = enable_log_tracing
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Caller info
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def get_caller_info(self) -> str:
|
|
132
|
+
tracing_active = GLOBAL_LOG_TRACING_ENABLED and self._enable_log_tracing
|
|
133
|
+
frames = self._collect_project_frames()
|
|
134
|
+
|
|
135
|
+
if not frames:
|
|
136
|
+
return "unknown:0"
|
|
137
|
+
|
|
138
|
+
if not tracing_active:
|
|
139
|
+
return frames[0]
|
|
140
|
+
|
|
141
|
+
return ">".join(frames)
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
# Private helpers
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
def _is_project_frame(self, filename: str) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Return True when *filename* belongs to project code.
|
|
150
|
+
|
|
151
|
+
Allowlist mode (project_dir set):
|
|
152
|
+
Frame must live inside the project directory and not inside
|
|
153
|
+
a ``site-packages`` folder.
|
|
154
|
+
|
|
155
|
+
Blocklist mode (project_dir is None):
|
|
156
|
+
Frame must not match any entry in ``_IGNORED_FILES`` or
|
|
157
|
+
``_IGNORED_PREFIXES``.
|
|
158
|
+
"""
|
|
159
|
+
# Always skip frozen / string pseudo-files regardless of mode
|
|
160
|
+
if any(filename.startswith(prefix) for prefix in _IGNORED_PREFIXES):
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
if self._project_dir is not None:
|
|
164
|
+
abs_filename = os.path.abspath(filename)
|
|
165
|
+
return (
|
|
166
|
+
abs_filename.startswith(self._project_dir)
|
|
167
|
+
and "site-packages" not in abs_filename
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Blocklist fallback
|
|
171
|
+
return os.path.basename(filename) not in _IGNORED_FILES
|
|
172
|
+
|
|
173
|
+
def _collect_project_frames(self) -> list[str]:
|
|
174
|
+
"""Walk the call stack and return frame strings for project code only."""
|
|
175
|
+
frames: list[str] = []
|
|
176
|
+
for frame_record in stack():
|
|
177
|
+
try:
|
|
178
|
+
info = getframeinfo(frame_record[0])
|
|
179
|
+
if self._is_project_frame(info.filename):
|
|
180
|
+
frames.append(f"{os.path.basename(info.filename)}:{info.lineno}")
|
|
181
|
+
except (IndexError, TypeError):
|
|
182
|
+
# IndexError: stack shorter than expected
|
|
183
|
+
# TypeError: frame_record is not subscriptable (e.g. test mocks using None)
|
|
184
|
+
continue
|
|
185
|
+
return frames
|
|
186
|
+
|
|
187
|
+
def _write_to_file(self, log_entry: str, date: str, time_str: str) -> None:
|
|
188
|
+
"""Create the log directory/file if needed, then append *log_entry*."""
|
|
189
|
+
log_dir_path = os.path.join("./", self._log_dir)
|
|
190
|
+
|
|
191
|
+
# Ensure the log directory exists
|
|
192
|
+
file_path = file_verify_path("./", self._log_dir)
|
|
193
|
+
if file_path == FileUtilsError.NOT_A_DIRECTORY.value.format(directory=self._log_dir):
|
|
194
|
+
os.makedirs(log_dir_path, exist_ok=True)
|
|
195
|
+
file_path = file_verify_path("./", self._log_dir) # re-verify after creation
|
|
196
|
+
|
|
197
|
+
# Bail out if the path is still an error (e.g. outside working dir)
|
|
198
|
+
if not isinstance(file_path, str) or file_path.startswith("Error"):
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
file_name = os.path.join(file_path, f"{date}_logging.txt")
|
|
202
|
+
with self._lock:
|
|
203
|
+
with open(file_name, "a") as log_file:
|
|
204
|
+
# Write a header only on the very first entry (empty file)
|
|
205
|
+
if log_file.tell() == 0:
|
|
206
|
+
log_file.write(f"Log file created on {date} at {time_str}\n")
|
|
207
|
+
log_file.write(log_entry)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: logging-module
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A comprehensive logging and file utility module for Python applications
|
|
5
|
+
Author-email: Nathan T Nguyen <nhatnguyen2807@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: CreatorGithub, https://github.com/NhatNguyen5
|
|
8
|
+
Project-URL: Homepage, https://test.pypi.org/project/logging-module/
|
|
9
|
+
Project-URL: Documentation, https://github.com/NhatNguyen5/logging_module/wiki/Instructions
|
|
10
|
+
Project-URL: Repository, https://github.com/NhatNguyen5/logging-module.git
|
|
11
|
+
Project-URL: Issues, https://github.com/NhatNguyen5/logging_module/issues
|
|
12
|
+
Keywords: logging,file-utilities,tracing,debugging
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: bump2version; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Logging Module
|
|
29
|
+
|
|
30
|
+
A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Flexible Logging**: Timestamp-based logging with caller information
|
|
35
|
+
- **File Utilities**: Safe file operations with error handling
|
|
36
|
+
- **Configuration**: Easy-to-configure logging and tracing options
|
|
37
|
+
- **Error Management**: Comprehensive error tracking and reporting
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### From PyPI (when published)
|
|
42
|
+
```bash
|
|
43
|
+
pip install logging-module
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### From Local Directory
|
|
47
|
+
```bash
|
|
48
|
+
pip install ./logging_module
|
|
49
|
+
# or in editable mode for development
|
|
50
|
+
pip install -e ./logging_module
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from logging_module import Logger
|
|
57
|
+
|
|
58
|
+
# Create a logger instance
|
|
59
|
+
app_logger = Logger()
|
|
60
|
+
|
|
61
|
+
# Basic logging
|
|
62
|
+
app_logger.log("Application started")
|
|
63
|
+
|
|
64
|
+
# Log with console output
|
|
65
|
+
app_logger.log("Important message", print_to_console=True)
|
|
66
|
+
|
|
67
|
+
# Log with custom console message
|
|
68
|
+
app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
|
|
69
|
+
|
|
70
|
+
# Use as a callable (shortcut for log())
|
|
71
|
+
app_logger("Quick log message")
|
|
72
|
+
|
|
73
|
+
# Control logging behavior
|
|
74
|
+
app_logger.enable_logging(False) # Disable this logger instance
|
|
75
|
+
app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
|
|
76
|
+
|
|
77
|
+
# Configure custom log directory
|
|
78
|
+
custom_logger = Logger(log_dir="my_logs")
|
|
79
|
+
|
|
80
|
+
# Configure project directory for better frame filtering
|
|
81
|
+
project_logger = Logger(project_dir="/path/to/project")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
### Logging Config (`config.py`)
|
|
87
|
+
- `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
|
|
88
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Modules
|
|
95
|
+
|
|
96
|
+
### Logger (`my_logging.py`)
|
|
97
|
+
Main logging class providing timestamped, structured logging with caller information.
|
|
98
|
+
|
|
99
|
+
**Key Features:**
|
|
100
|
+
- Automatic timestamping and file logging
|
|
101
|
+
- Caller information tracking with configurable frame filtering
|
|
102
|
+
- Configurable log directory and project directory
|
|
103
|
+
- Thread-safe file operations
|
|
104
|
+
- Configurable tracing depth
|
|
105
|
+
- Console output options
|
|
106
|
+
- Instance-level enable/disable controls
|
|
107
|
+
|
|
108
|
+
### File Utilities (`file_utils.py`)
|
|
109
|
+
Safe file operations with comprehensive error handling:
|
|
110
|
+
- Path verification with security checks
|
|
111
|
+
- File verification for different operations (read/write/execute)
|
|
112
|
+
- Directory operations with working directory constraints
|
|
113
|
+
|
|
114
|
+
### Configuration (`config.py`)
|
|
115
|
+
Global configuration settings for logging behavior:
|
|
116
|
+
- `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
|
|
117
|
+
- `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
### Prerequisites
|
|
122
|
+
- Python 3.10+
|
|
123
|
+
|
|
124
|
+
### Installing in Development Mode
|
|
125
|
+
```bash
|
|
126
|
+
cd logging_module
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Running Tests
|
|
131
|
+
```bash
|
|
132
|
+
# Install test dependencies
|
|
133
|
+
pip install -e ".[dev]"
|
|
134
|
+
|
|
135
|
+
# Run all tests
|
|
136
|
+
pytest
|
|
137
|
+
|
|
138
|
+
# Run with verbose output
|
|
139
|
+
pytest -v
|
|
140
|
+
|
|
141
|
+
# Run specific test file
|
|
142
|
+
pytest tests/test_my_logging.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
148
|
+
|
|
149
|
+
## Author
|
|
150
|
+
|
|
151
|
+
Nathan T Nguyen
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
logging_module/__init__.py
|
|
5
|
+
logging_module/config.py
|
|
6
|
+
logging_module/file_utils.py
|
|
7
|
+
logging_module/my_logging.py
|
|
8
|
+
logging_module.egg-info/PKG-INFO
|
|
9
|
+
logging_module.egg-info/SOURCES.txt
|
|
10
|
+
logging_module.egg-info/dependency_links.txt
|
|
11
|
+
logging_module.egg-info/requires.txt
|
|
12
|
+
logging_module.egg-info/top_level.txt
|
|
13
|
+
tests/test_config.py
|
|
14
|
+
tests/test_file_utils.py
|
|
15
|
+
tests/test_my_logging.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
logging_module
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools.packages.find]
|
|
6
|
+
# This tells build exactly which folders to treat as packages
|
|
7
|
+
include = ["logging_module*"]
|
|
8
|
+
# This ensures the 'logs' folder is ignored
|
|
9
|
+
exclude = ["logs*"]
|
|
10
|
+
|
|
11
|
+
[project]
|
|
12
|
+
name = "logging-module"
|
|
13
|
+
version = "0.2.0"
|
|
14
|
+
description = "A comprehensive logging and file utility module for Python applications"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
requires-python = ">=3.10"
|
|
17
|
+
license = "MIT"
|
|
18
|
+
authors = [
|
|
19
|
+
{name = "Nathan T Nguyen", email = "nhatnguyen2807@gmail.com"}
|
|
20
|
+
]
|
|
21
|
+
keywords = ["logging", "file-utilities", "tracing", "debugging"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest", "bump2version"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
python_files = ["test_*.py"]
|
|
38
|
+
python_classes = ["Test*"]
|
|
39
|
+
python_functions = ["test_*"]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
CreatorGithub = "https://github.com/NhatNguyen5"
|
|
43
|
+
Homepage = "https://test.pypi.org/project/logging-module/"
|
|
44
|
+
Documentation = "https://github.com/NhatNguyen5/logging_module/wiki/Instructions"
|
|
45
|
+
Repository = "https://github.com/NhatNguyen5/logging-module.git"
|
|
46
|
+
Issues = "https://github.com/NhatNguyen5/logging_module/issues"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_global_logging_enabled():
|
|
6
|
+
"""Test that GLOBAL_LOGGING_ENABLED is a boolean."""
|
|
7
|
+
assert isinstance(GLOBAL_LOGGING_ENABLED, bool)
|
|
8
|
+
assert GLOBAL_LOGGING_ENABLED is True # Default value
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_global_log_tracing_enabled():
|
|
12
|
+
"""Test that GLOBAL_LOG_TRACING_ENABLED is a boolean."""
|
|
13
|
+
assert isinstance(GLOBAL_LOG_TRACING_ENABLED, bool)
|
|
14
|
+
assert GLOBAL_LOG_TRACING_ENABLED is True # Default value
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import pytest
|
|
4
|
+
from logging_module.file_utils import (
|
|
5
|
+
FileUtilsError,
|
|
6
|
+
FileExecutionError,
|
|
7
|
+
FileOperator,
|
|
8
|
+
file_verify_path,
|
|
9
|
+
file_verify_file,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestFileVerifyPath:
|
|
14
|
+
def test_valid_directory(self):
|
|
15
|
+
"""Test verifying a valid directory."""
|
|
16
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
17
|
+
sub_dir = os.path.join(temp_dir, "test_dir")
|
|
18
|
+
os.makedirs(sub_dir)
|
|
19
|
+
result = file_verify_path(temp_dir, "test_dir")
|
|
20
|
+
assert result == sub_dir
|
|
21
|
+
|
|
22
|
+
def test_invalid_directory_not_exists(self):
|
|
23
|
+
"""Test verifying a non-existent directory."""
|
|
24
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
25
|
+
result = file_verify_path(temp_dir, "nonexistent")
|
|
26
|
+
assert result == FileUtilsError.NOT_A_DIRECTORY.value.format(directory="nonexistent")
|
|
27
|
+
|
|
28
|
+
def test_outside_working_directory(self):
|
|
29
|
+
"""Test verifying a directory outside working directory."""
|
|
30
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
31
|
+
result = file_verify_path(temp_dir, "../outside")
|
|
32
|
+
assert result == FileUtilsError.OUTSIDE_WORKING_DIR.value.format(directory="../outside")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestFileVerifyFile:
|
|
36
|
+
def test_valid_read_file(self):
|
|
37
|
+
"""Test verifying a valid file for reading."""
|
|
38
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
39
|
+
file_path = os.path.join(temp_dir, "test.txt")
|
|
40
|
+
with open(file_path, "w") as f:
|
|
41
|
+
f.write("test")
|
|
42
|
+
result = file_verify_file(temp_dir, "test.txt", FileOperator.READ_FILE)
|
|
43
|
+
assert result == file_path
|
|
44
|
+
|
|
45
|
+
def test_invalid_read_file_not_exists(self):
|
|
46
|
+
"""Test verifying a non-existent file for reading."""
|
|
47
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
48
|
+
result = file_verify_file(temp_dir, "nonexistent.txt", FileOperator.READ_FILE)
|
|
49
|
+
assert result == FileUtilsError.FILE_READ_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=os.path.join(temp_dir, "nonexistent.txt"))
|
|
50
|
+
|
|
51
|
+
def test_outside_working_directory_read(self):
|
|
52
|
+
"""Test verifying a file outside working directory for reading."""
|
|
53
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
54
|
+
result = file_verify_file(temp_dir, "../outside.txt", FileOperator.READ_FILE)
|
|
55
|
+
expected = FileUtilsError.FILE_READ_OUTSIDE_WORKING_DIR.value.format(file_path=os.path.join(temp_dir, "../outside.txt"))
|
|
56
|
+
assert result == expected
|
|
57
|
+
|
|
58
|
+
def test_valid_write_file_new(self):
|
|
59
|
+
"""Test verifying a new file for writing."""
|
|
60
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
61
|
+
result = file_verify_file(temp_dir, "new.txt", FileOperator.WRITE_FILE)
|
|
62
|
+
assert result == os.path.join(temp_dir, "new.txt")
|
|
63
|
+
|
|
64
|
+
def test_outside_working_directory_write(self):
|
|
65
|
+
"""Test verifying a file outside working directory for writing."""
|
|
66
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
67
|
+
result = file_verify_file(temp_dir, "../outside.txt", FileOperator.WRITE_FILE)
|
|
68
|
+
expected = FileUtilsError.FILE_WRITE_OUTSIDE_WORKING_DIR.value.format(file_path=os.path.join(temp_dir, "../outside.txt"))
|
|
69
|
+
assert result == expected
|
|
70
|
+
|
|
71
|
+
def test_valid_execute_file(self):
|
|
72
|
+
"""Test verifying a valid Python file for execution."""
|
|
73
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
74
|
+
file_path = os.path.join(temp_dir, "test.py")
|
|
75
|
+
with open(file_path, "w") as f:
|
|
76
|
+
f.write("print('test')")
|
|
77
|
+
result = file_verify_file(temp_dir, "test.py", FileOperator.EXECUTE_FILE)
|
|
78
|
+
assert result == file_path
|
|
79
|
+
|
|
80
|
+
def test_invalid_execute_file_not_python(self):
|
|
81
|
+
"""Test verifying a non-Python file for execution."""
|
|
82
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
83
|
+
file_path = os.path.join(temp_dir, "test.txt")
|
|
84
|
+
with open(file_path, "w") as f:
|
|
85
|
+
f.write("test")
|
|
86
|
+
result = file_verify_file(temp_dir, "test.txt", FileOperator.EXECUTE_FILE)
|
|
87
|
+
assert result == FileUtilsError.FILE_EXECUTE_NOT_PYTHON.value.format(file_path="test.txt")
|
|
88
|
+
|
|
89
|
+
def test_invalid_option(self):
|
|
90
|
+
"""Test invalid file operation option."""
|
|
91
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
92
|
+
with pytest.raises(ValueError, match="Invalid file operation option provided"):
|
|
93
|
+
file_verify_file(temp_dir, "test.txt", "invalid")
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from logging_module.my_logging import Logger
|
|
7
|
+
from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestLogger:
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
"""Setup before each test."""
|
|
13
|
+
self.Logger = Logger()
|
|
14
|
+
|
|
15
|
+
def test_init(self):
|
|
16
|
+
"""Test Logger initialization."""
|
|
17
|
+
assert self.Logger._enable is True
|
|
18
|
+
assert self.Logger._enable_log_tracing is False
|
|
19
|
+
|
|
20
|
+
@patch('logging_module.my_logging.datetime')
|
|
21
|
+
@patch('logging_module.my_logging.file_verify_path')
|
|
22
|
+
@patch('builtins.open', new_callable=MagicMock)
|
|
23
|
+
def test_log_basic(self, mock_open, mock_verify_path, mock_datetime):
|
|
24
|
+
"""Test basic logging functionality."""
|
|
25
|
+
# Setup mocks
|
|
26
|
+
mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
|
|
27
|
+
mock_verify_path.return_value = "/tmp/logs"
|
|
28
|
+
|
|
29
|
+
# Create a temporary directory for logs
|
|
30
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
31
|
+
with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
|
|
32
|
+
with patch('os.path.isfile', return_value=False):
|
|
33
|
+
with patch('os.makedirs'):
|
|
34
|
+
self.Logger.log("Test message")
|
|
35
|
+
|
|
36
|
+
# Check that open was called for writing
|
|
37
|
+
assert mock_open.call_count >= 1 # Single open in append mode
|
|
38
|
+
|
|
39
|
+
@patch('logging_module.my_logging.datetime')
|
|
40
|
+
@patch('logging_module.my_logging.file_verify_path')
|
|
41
|
+
@patch('builtins.open', new_callable=MagicMock)
|
|
42
|
+
@patch('builtins.print')
|
|
43
|
+
def test_log_with_console_output(self, mock_print, mock_open, mock_verify_path, mock_datetime):
|
|
44
|
+
"""Test logging with console output."""
|
|
45
|
+
mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
|
|
46
|
+
mock_verify_path.return_value = "/tmp/logs"
|
|
47
|
+
|
|
48
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
49
|
+
with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
|
|
50
|
+
with patch('os.path.isfile', return_value=True):
|
|
51
|
+
self.Logger.log("Test message", print_to_console=True)
|
|
52
|
+
|
|
53
|
+
# Check that print was called
|
|
54
|
+
mock_print.assert_called_once_with("Test message")
|
|
55
|
+
|
|
56
|
+
@patch('logging_module.my_logging.datetime')
|
|
57
|
+
@patch('logging_module.my_logging.file_verify_path')
|
|
58
|
+
@patch('builtins.open', new_callable=MagicMock)
|
|
59
|
+
@patch('builtins.print')
|
|
60
|
+
def test_log_with_custom_console_message(self, mock_print, mock_open, mock_verify_path, mock_datetime):
|
|
61
|
+
"""Test logging with custom console message."""
|
|
62
|
+
mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
|
|
63
|
+
mock_verify_path.return_value = "/tmp/logs"
|
|
64
|
+
|
|
65
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
66
|
+
with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
|
|
67
|
+
with patch('os.path.isfile', return_value=True):
|
|
68
|
+
self.Logger.log("Test message", print_to_console=True, console_message="Custom message")
|
|
69
|
+
|
|
70
|
+
# Check that print was called with custom message
|
|
71
|
+
mock_print.assert_called_once_with("Custom message")
|
|
72
|
+
|
|
73
|
+
def test_log_disabled_globally(self):
|
|
74
|
+
"""Test logging when globally disabled."""
|
|
75
|
+
with patch('logging_module.my_logging.GLOBAL_LOGGING_ENABLED', False):
|
|
76
|
+
with patch('builtins.open') as mock_open:
|
|
77
|
+
self.Logger.log("Test message")
|
|
78
|
+
mock_open.assert_not_called()
|
|
79
|
+
|
|
80
|
+
def test_log_disabled_locally(self):
|
|
81
|
+
"""Test logging when locally disabled."""
|
|
82
|
+
self.Logger.enable_logging(False)
|
|
83
|
+
with patch('builtins.open') as mock_open:
|
|
84
|
+
self.Logger.log("Test message")
|
|
85
|
+
mock_open.assert_not_called()
|
|
86
|
+
|
|
87
|
+
@patch('logging_module.my_logging.getframeinfo')
|
|
88
|
+
@patch('logging_module.my_logging.stack')
|
|
89
|
+
def test_get_caller_info_basic(self, mock_stack, mock_getframeinfo):
|
|
90
|
+
"""Test getting basic caller info."""
|
|
91
|
+
mock_frame = MagicMock()
|
|
92
|
+
mock_frame.filename = "/path/to/file.py"
|
|
93
|
+
mock_frame.lineno = 42
|
|
94
|
+
mock_getframeinfo.return_value = mock_frame
|
|
95
|
+
mock_stack.return_value = [None, None, None, mock_frame] # calldepth = 3
|
|
96
|
+
|
|
97
|
+
result = self.Logger.get_caller_info()
|
|
98
|
+
assert result == "file.py:42"
|
|
99
|
+
|
|
100
|
+
@patch('logging_module.my_logging.getframeinfo')
|
|
101
|
+
@patch('logging_module.my_logging.stack')
|
|
102
|
+
def test_get_caller_info_tracing_disabled(self, mock_stack, mock_getframeinfo):
|
|
103
|
+
"""Test caller info when tracing is disabled."""
|
|
104
|
+
self.Logger.enable_log_tracing(False)
|
|
105
|
+
mock_frame = MagicMock()
|
|
106
|
+
mock_frame.filename = "/path/to/file.py"
|
|
107
|
+
mock_frame.lineno = 42
|
|
108
|
+
mock_getframeinfo.return_value = mock_frame
|
|
109
|
+
mock_stack.return_value = [None, None, None, mock_frame]
|
|
110
|
+
|
|
111
|
+
result = self.Logger.get_caller_info()
|
|
112
|
+
assert result == "file.py:42"
|
|
113
|
+
|
|
114
|
+
@patch('logging_module.my_logging.getframeinfo')
|
|
115
|
+
@patch('logging_module.my_logging.stack')
|
|
116
|
+
def test_get_caller_info_tracing_enabled(self, mock_stack, mock_getframeinfo):
|
|
117
|
+
"""Test caller info when tracing is enabled."""
|
|
118
|
+
self.Logger.enable_log_tracing(True)
|
|
119
|
+
with patch('logging_module.my_logging.GLOBAL_LOG_TRACING_ENABLED', True):
|
|
120
|
+
mock_frame1 = MagicMock()
|
|
121
|
+
mock_frame1.filename = "/path/to/file1.py"
|
|
122
|
+
mock_frame1.lineno = 10
|
|
123
|
+
mock_frame2 = MagicMock()
|
|
124
|
+
mock_frame2.filename = "/path/to/file2.py"
|
|
125
|
+
mock_frame2.lineno = 20
|
|
126
|
+
mock_getframeinfo.side_effect = [mock_frame1, mock_frame2]
|
|
127
|
+
mock_stack.return_value = [None, None, None, mock_frame1, mock_frame2]
|
|
128
|
+
|
|
129
|
+
result = self.Logger.get_caller_info()
|
|
130
|
+
assert "file1.py:10>file2.py:20" in result
|
|
131
|
+
|
|
132
|
+
def test_enable_logging(self):
|
|
133
|
+
"""Test enabling/disabling logging."""
|
|
134
|
+
self.Logger.enable_logging(False)
|
|
135
|
+
assert self.Logger._enable is False
|
|
136
|
+
|
|
137
|
+
self.Logger.enable_logging(True)
|
|
138
|
+
assert self.Logger._enable is True
|
|
139
|
+
|
|
140
|
+
def test_enable_log_tracing(self):
|
|
141
|
+
"""Test enabling/disabling log tracing."""
|
|
142
|
+
self.Logger.enable_log_tracing(True)
|
|
143
|
+
assert self.Logger._enable_log_tracing is True
|
|
144
|
+
|
|
145
|
+
self.Logger.enable_log_tracing(False)
|
|
146
|
+
assert self.Logger._enable_log_tracing is False
|
|
147
|
+
|
|
148
|
+
def test_call_method(self):
|
|
149
|
+
"""Test using Logger as callable."""
|
|
150
|
+
with patch.object(self.Logger, 'log') as mock_log:
|
|
151
|
+
self.Logger("Test message", print_to_console=True)
|
|
152
|
+
mock_log.assert_called_once_with("Test message", print_to_console=True, console_message="")
|