pyessential 0.1.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.
- pyessential/__init__.py +16 -0
- pyessential/decorators/__init__.py +3 -0
- pyessential/decorators/timer.py +13 -0
- pyessential/generators.py +31 -0
- pyessential/logger.py +265 -0
- pyessential-0.1.0.dist-info/METADATA +45 -0
- pyessential-0.1.0.dist-info/RECORD +10 -0
- pyessential-0.1.0.dist-info/WHEEL +5 -0
- pyessential-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyessential-0.1.0.dist-info/top_level.txt +1 -0
pyessential/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
from .decorators import timeit
|
|
3
|
+
from .generators import generate_random_int, generate_secret_key
|
|
4
|
+
from .logger import Logger
|
|
5
|
+
|
|
6
|
+
# Version and Author Info
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Abhishek Kumar"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"timeit",
|
|
13
|
+
"generate_random_int",
|
|
14
|
+
"generate_secret_key",
|
|
15
|
+
"Logger",
|
|
16
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
def timeit(func):
|
|
5
|
+
"""Decorator to measure and print function execution time."""
|
|
6
|
+
@wraps(func)
|
|
7
|
+
def wrapper(*args, **kwargs):
|
|
8
|
+
start_time = time.time()
|
|
9
|
+
result = func(*args, **kwargs)
|
|
10
|
+
execution_time = time.time() - start_time
|
|
11
|
+
print(f"[{func.__name__}] executed in {execution_time:.4f} seconds.")
|
|
12
|
+
return result
|
|
13
|
+
return wrapper
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Name: pyessential.generators
|
|
3
|
+
Description: A module providing utility functions for generating random data.
|
|
4
|
+
Author: Abhishek Kumar
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import secrets
|
|
8
|
+
|
|
9
|
+
def generate_random_int(min_value=0, max_value=100):
|
|
10
|
+
"""Generate a random integer within a specified range.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
min_value (int): The minimum value of the range. Default is 0.
|
|
14
|
+
max_value (int): The maximum value of the range. Default is 100.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
int: A randomly generated integer within the specified range.
|
|
18
|
+
"""
|
|
19
|
+
return secrets.randbelow(max_value - min_value + 1) + min_value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def generate_secret_key(length=32):
|
|
23
|
+
"""Generate a secure random secret key.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
length (int): The length of the secret key. Default is 32.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: A securely generated random secret key.
|
|
30
|
+
"""
|
|
31
|
+
return secrets.token_hex(length)
|
pyessential/logger.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logger utility for application logging.
|
|
3
|
+
|
|
4
|
+
This module provides the 'Logger' class, a robust logging utility that
|
|
5
|
+
integrates with Python's standard 'logging' library. It configures
|
|
6
|
+
three separate loggers/files for STDIN (ingress), STDOUT (general/info),
|
|
7
|
+
and STDERR (errors/critical) messages, with support for
|
|
8
|
+
timed log rotation and console output.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import traceback
|
|
15
|
+
import logging
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Logger:
|
|
21
|
+
"""
|
|
22
|
+
A logging utility class for managing application logs with separate streams.
|
|
23
|
+
|
|
24
|
+
It sets up three distinct, rotation-enabled log files:
|
|
25
|
+
1. STDIN (for input/ingress events)
|
|
26
|
+
2. STDOUT (for general debug/info/warning messages)
|
|
27
|
+
3. STDERR (for error/critical messages and exceptions)
|
|
28
|
+
|
|
29
|
+
The class is designed to be used as a context manager to ensure log closure.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> log = Logger(log_name="my_app", enable_console=True)
|
|
33
|
+
>>> log.info("Application started.")
|
|
34
|
+
>>> log.error("Something went wrong!")
|
|
35
|
+
|
|
36
|
+
or
|
|
37
|
+
|
|
38
|
+
>>> with Logger(log_name="my_app", enable_console=True) as log:
|
|
39
|
+
... log.info("Application started.")
|
|
40
|
+
... log.error("Something went wrong!")
|
|
41
|
+
...
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_LEVEL_MAP = {
|
|
45
|
+
"debug":logging.DEBUG,
|
|
46
|
+
"info": logging.INFO,
|
|
47
|
+
"warning": logging.WARNING,
|
|
48
|
+
"error": logging.ERROR,
|
|
49
|
+
"critical": logging.CRITICAL,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
log_dir="../logs",
|
|
55
|
+
log_name="project",
|
|
56
|
+
level="debug",
|
|
57
|
+
when="midnight",
|
|
58
|
+
interval=1,
|
|
59
|
+
backup_count=7,
|
|
60
|
+
enable_console=False,
|
|
61
|
+
fmt="%(asctime)s.%(msecs)03d - %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s",
|
|
62
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
63
|
+
encoding="utf-8"
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Args:
|
|
67
|
+
log_dir (str) : Directory to store log files. Default is '../logs'.
|
|
68
|
+
log_name (str) : Base name for the three log files (<log_name>_stdin.log, <log_name>_stdout.log, <log_name>_stderr.log).
|
|
69
|
+
level (str) : Logging level. Default is 'debug'.
|
|
70
|
+
when (str) : Time interval for log rotation. Default is 'midnight'.
|
|
71
|
+
interval (int) : Interval for log rotation. Default is 1.
|
|
72
|
+
backup_count (int) : Number of backup log files to keep. Default is 7.
|
|
73
|
+
enable_console (bool) : Enable console logging. Default is False.
|
|
74
|
+
fmt (str) : Log message format. Default is "%(asctime)s.%(msecs)03d - %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s".
|
|
75
|
+
datefmt (str) : Date format in logs. Default is "%Y-%m-%d %H:%M:%S".
|
|
76
|
+
encoding (str) : Encoding for log files. Default is "utf-8".
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
self.log_dir = os.path.abspath(log_dir)
|
|
80
|
+
self.log_name = log_name
|
|
81
|
+
|
|
82
|
+
# Filenames (with 'logs' extension as is common; can be changed if needed)
|
|
83
|
+
self.stdin_path = os.path.join(self.log_dir, f"{self.log_name}_stdin.log")
|
|
84
|
+
self.stdout_path = os.path.join(self.log_dir, f"{self.log_name}_stdout.log")
|
|
85
|
+
self.error_path = os.path.join(self.log_dir, f"{self.log_name}_error.log")
|
|
86
|
+
|
|
87
|
+
# Ensure Directory
|
|
88
|
+
os.makedirs(self.log_dir, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
# Common formatter
|
|
91
|
+
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
|
|
92
|
+
|
|
93
|
+
# Create three dedicated loggers so routing is explicit and simple
|
|
94
|
+
self._stdin_logger = self._build_logger(
|
|
95
|
+
name=f"{self.log_name}.stdin",
|
|
96
|
+
filepath=self.stdin_path,
|
|
97
|
+
level=level,
|
|
98
|
+
when=when,
|
|
99
|
+
interval=interval,
|
|
100
|
+
backup_count=backup_count,
|
|
101
|
+
formatter=formatter,
|
|
102
|
+
encoding=encoding,
|
|
103
|
+
enable_console=False, # stdin is typically not logged to console
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self._stdout_logger = self._build_logger(
|
|
107
|
+
name=f"{self.log_name}.stdout",
|
|
108
|
+
filepath=self.stdout_path,
|
|
109
|
+
level=level,
|
|
110
|
+
when=when,
|
|
111
|
+
interval=interval,
|
|
112
|
+
backup_count=backup_count,
|
|
113
|
+
formatter=formatter,
|
|
114
|
+
encoding=encoding,
|
|
115
|
+
enable_console=enable_console,
|
|
116
|
+
console_to_stderr=False # stdout -> sys.stdout
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self._error_logger = self._build_logger(
|
|
120
|
+
name=f"{self.log_name}.error",
|
|
121
|
+
filepath=self.error_path,
|
|
122
|
+
level=level,
|
|
123
|
+
when=when,
|
|
124
|
+
interval=interval,
|
|
125
|
+
backup_count=backup_count,
|
|
126
|
+
formatter=formatter,
|
|
127
|
+
encoding=encoding,
|
|
128
|
+
enable_console=enable_console,
|
|
129
|
+
console_to_stderr=True # stderr -> sys.stderr
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# --------------- Public Methods ---------------
|
|
133
|
+
def debug(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
134
|
+
"""Log a debug message to stdout logger."""
|
|
135
|
+
self._stdout_logger.debug(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
136
|
+
|
|
137
|
+
def info(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
138
|
+
"""Log an info message to stdout logger."""
|
|
139
|
+
self._stdout_logger.info(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
140
|
+
|
|
141
|
+
def warning(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
142
|
+
"""Log a warning message to stdout logger."""
|
|
143
|
+
self._stdout_logger.warning(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
144
|
+
|
|
145
|
+
def error(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
146
|
+
"""Log an error message to error logger."""
|
|
147
|
+
self._error_logger.error(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
148
|
+
|
|
149
|
+
def critical(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
150
|
+
"""Log a critical message to error logger."""
|
|
151
|
+
self._error_logger.critical(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
152
|
+
|
|
153
|
+
def exception(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
154
|
+
"""Log an exception message to error logger."""
|
|
155
|
+
self._error_logger.error(msg, *args, exc_info=True, stacklevel=stacklevel, **kwargs)
|
|
156
|
+
|
|
157
|
+
def stdin(self, msg, *args, exc_info=False, stacklevel=2, **kwargs):
|
|
158
|
+
"""Explicitly record 'input' or ingress events."""
|
|
159
|
+
self._stdin_logger.info(msg, *args, exc_info=exc_info, stacklevel=stacklevel, **kwargs)
|
|
160
|
+
|
|
161
|
+
def log_error(self, err: Exception):
|
|
162
|
+
"""Log an exception with traceback to the error logger.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
err (Exception): The exception instance to log.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
169
|
+
|
|
170
|
+
if exc_tb is None:
|
|
171
|
+
# It means getting called outputside except block.
|
|
172
|
+
filename = "<unknown>"
|
|
173
|
+
lineno = -1
|
|
174
|
+
func_name = "<unknown>"
|
|
175
|
+
else:
|
|
176
|
+
filename = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
|
|
177
|
+
lineno = exc_tb.tb_lineno
|
|
178
|
+
func_name = traceback.extract_tb(exc_tb, 1)[0][2]
|
|
179
|
+
|
|
180
|
+
e_type = err.__class__.__name__
|
|
181
|
+
|
|
182
|
+
data = {
|
|
183
|
+
"exception_type": e_type,
|
|
184
|
+
"exception_message": str(err),
|
|
185
|
+
"filename": filename,
|
|
186
|
+
"lineno": lineno,
|
|
187
|
+
"function": func_name,
|
|
188
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
189
|
+
"traceback": traceback.format_exc()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# log to stderr file
|
|
193
|
+
self.error(data, exc_info=True, stacklevel=2)
|
|
194
|
+
return data
|
|
195
|
+
|
|
196
|
+
def close(self):
|
|
197
|
+
"""
|
|
198
|
+
Flush and close all handlers explicitly.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
for lg in (self._stdin_logger, self._stdout_logger, self._error_logger):
|
|
202
|
+
for h in list(lg.handlers):
|
|
203
|
+
try:
|
|
204
|
+
h.flush()
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
h.close()
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
lg.removeHandler(h)
|
|
214
|
+
|
|
215
|
+
def __enter__(self):
|
|
216
|
+
return self
|
|
217
|
+
|
|
218
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
219
|
+
self.close()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# --------------- Internals ---------------
|
|
223
|
+
@classmethod
|
|
224
|
+
def _build_logger(
|
|
225
|
+
cls,
|
|
226
|
+
name,
|
|
227
|
+
filepath,
|
|
228
|
+
level,
|
|
229
|
+
when,
|
|
230
|
+
interval,
|
|
231
|
+
backup_count,
|
|
232
|
+
formatter,
|
|
233
|
+
encoding,
|
|
234
|
+
enable_console=False,
|
|
235
|
+
console_to_stderr=False
|
|
236
|
+
):
|
|
237
|
+
|
|
238
|
+
log_level = cls._LEVEL_MAP.get(level.lower(), logging.DEBUG)
|
|
239
|
+
logger = logging.getLogger(name)
|
|
240
|
+
logger.setLevel(log_level)
|
|
241
|
+
logger.handlers.clear() # Avoid duplicate handlers on re-initialization
|
|
242
|
+
logger.propagate = False # Prevent log messages from being propagated to the root logger
|
|
243
|
+
|
|
244
|
+
# Timed Rotating File Handler
|
|
245
|
+
fh = TimedRotatingFileHandler(
|
|
246
|
+
filename=filepath,
|
|
247
|
+
when=when,
|
|
248
|
+
interval=interval,
|
|
249
|
+
backupCount=backup_count,
|
|
250
|
+
encoding=encoding,
|
|
251
|
+
delay=True,
|
|
252
|
+
utc=False
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
fh.setFormatter(formatter)
|
|
256
|
+
logger.addHandler(fh)
|
|
257
|
+
|
|
258
|
+
if enable_console:
|
|
259
|
+
stream = sys.stderr if console_to_stderr else sys.stdout
|
|
260
|
+
sh = logging.StreamHandler(stream)
|
|
261
|
+
sh.setFormatter(formatter)
|
|
262
|
+
sh.setLevel(log_level)
|
|
263
|
+
logger.addHandler(sh)
|
|
264
|
+
|
|
265
|
+
return logger
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyessential
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A growing collection of Python utilities and decorators.
|
|
5
|
+
Home-page: https://github.com/Abhi39054/PyEssential.git
|
|
6
|
+
Author: Abhishek Kumar
|
|
7
|
+
Author-email: abhishekkumar39054@gmail.com
|
|
8
|
+
Maintainer: Abhishek Kumar
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# 🐍 PyEssential
|
|
22
|
+
|
|
23
|
+
**A growing collection of Python utilities and decorators to simplify common development tasks.**
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/pyessential/)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
|
|
30
|
+
## ✨ Features
|
|
31
|
+
|
|
32
|
+
`PyEssential` aims to be the go-to library for small, reusable tools that you often find yourself writing from scratch.
|
|
33
|
+
|
|
34
|
+
* **Performance:** Includes the ready-to-use `@timeit` decorator for simple function benchmarking.
|
|
35
|
+
* **Code Clarity:** Provides utility functions for common tasks like string manipulation (e.g., casing). (Coming soon!)
|
|
36
|
+
* **Scalability:** Structured for easy addition of new utility groups (e.g., `networking`, `file_io`, `validation`).
|
|
37
|
+
|
|
38
|
+
## 🚀 Installation
|
|
39
|
+
|
|
40
|
+
`PyEssential` is currenlty still in development stage. Once development is completed, It will be published in the Pypi for the community to use.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install pyessential
|
|
45
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pyessential/__init__.py,sha256=q0PBencuetHz8xKooZcLDyL256Wt6mhNJNRMbyNOYBM,316
|
|
2
|
+
pyessential/generators.py,sha256=4Ur_EJDvBZ5TWrqem7ZKGgNcsrHxDIXksqL92F6vi7o,895
|
|
3
|
+
pyessential/logger.py,sha256=4g7uO7Y1vBwlt2tkVFplcjdv7184i06kkji0HZ5ogLU,9674
|
|
4
|
+
pyessential/decorators/__init__.py,sha256=8c6P3y211dQcAKlYpcoHjgZNFHWPvkqv3cfzxq0gZM4,49
|
|
5
|
+
pyessential/decorators/timer.py,sha256=0zyAcwCVr9kHZ4JKzxx3OL4oZYrN7sGy0Us5AoR6VVw,428
|
|
6
|
+
pyessential-0.1.0.dist-info/licenses/LICENSE,sha256=zb0pNrxkWz5xapN3ADXzV_yIbY28-gHnSuIyFBe5URI,1092
|
|
7
|
+
pyessential-0.1.0.dist-info/METADATA,sha256=cFtLPmYfeWTmzjmpaWSg5B4LTyjiGoq9F8K2M1508Uo,1715
|
|
8
|
+
pyessential-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
pyessential-0.1.0.dist-info/top_level.txt,sha256=wCH4Sn4jN1o0cyc4-XMRNQCy15XVetATW5l-cZN2zO4,12
|
|
10
|
+
pyessential-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Abhishek Kumar
|
|
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 @@
|
|
|
1
|
+
pyessential
|