l4py 0.0.1__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.
- l4py-0.0.1/LICENSE +21 -0
- l4py-0.0.1/PKG-INFO +35 -0
- l4py-0.0.1/README.md +20 -0
- l4py-0.0.1/l4py/__init__.py +2 -0
- l4py-0.0.1/l4py/builder.py +198 -0
- l4py-0.0.1/l4py/formatters.py +55 -0
- l4py-0.0.1/l4py/l4py.py +17 -0
- l4py-0.0.1/l4py/utils.py +49 -0
- l4py-0.0.1/l4py.egg-info/PKG-INFO +35 -0
- l4py-0.0.1/l4py.egg-info/SOURCES.txt +12 -0
- l4py-0.0.1/l4py.egg-info/dependency_links.txt +1 -0
- l4py-0.0.1/l4py.egg-info/top_level.txt +1 -0
- l4py-0.0.1/setup.cfg +4 -0
- l4py-0.0.1/setup.py +25 -0
l4py-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 roymanigley
|
|
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.
|
l4py-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: l4py
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Home-page: https://github.com/roymanigley/l4py
|
|
5
|
+
Author: Roy Manigley
|
|
6
|
+
Author-email: roy.manigley@gmail.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# l4py
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
```
|
|
22
|
+
pip install l4py
|
|
23
|
+
```
|
|
24
|
+
or from Github:
|
|
25
|
+
```
|
|
26
|
+
git clone https://github.com/roymanigley/l4py.git
|
|
27
|
+
cd l4py
|
|
28
|
+
pip install -r requirements.txt
|
|
29
|
+
python setup.py install
|
|
30
|
+
```
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from l4py import Builder
|
|
35
|
+
```
|
l4py-0.0.1/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# l4py
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```
|
|
7
|
+
pip install l4py
|
|
8
|
+
```
|
|
9
|
+
or from Github:
|
|
10
|
+
```
|
|
11
|
+
git clone https://github.com/roymanigley/l4py.git
|
|
12
|
+
cd l4py
|
|
13
|
+
pip install -r requirements.txt
|
|
14
|
+
python setup.py install
|
|
15
|
+
```
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from l4py import Builder
|
|
20
|
+
```
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from logging.handlers import RotatingFileHandler
|
|
4
|
+
|
|
5
|
+
from l4py import utils
|
|
6
|
+
from l4py.formatters import TextFormatter, JsonFormatter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LogBuilder:
|
|
10
|
+
__text_formatter = TextFormatter()
|
|
11
|
+
__json_formatter = JsonFormatter()
|
|
12
|
+
|
|
13
|
+
__console_format = None
|
|
14
|
+
__console_json = False
|
|
15
|
+
__console_level = None
|
|
16
|
+
|
|
17
|
+
__file = None
|
|
18
|
+
__file_format = None
|
|
19
|
+
__file_json = True
|
|
20
|
+
__file_level = None
|
|
21
|
+
__file_max_size = 10 * 1024 * 1024 # 10 MB (default)
|
|
22
|
+
__file_max_count = 5 # Default 5 backup files
|
|
23
|
+
|
|
24
|
+
def text_formatter(self, text_formatter: logging.Formatter) -> 'LogBuilder':
|
|
25
|
+
self.__text_formatter = text_formatter
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def json_formatter(self, json_formatter: logging.Formatter) -> 'LogBuilder':
|
|
29
|
+
self.__json_formatter = json_formatter
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def console_format(self, format_str: str) -> 'LogBuilder':
|
|
33
|
+
self.__console_format = format_str
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def console_json(self, value: bool) -> 'LogBuilder':
|
|
37
|
+
self.__console_json = value
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def console_level(self, level: int) -> 'LogBuilder':
|
|
41
|
+
self.__console_level = level
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def file(self, file_name: str) -> 'LogBuilder':
|
|
45
|
+
self.__file = file_name
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def file_format(self, format_str: str) -> 'LogBuilder':
|
|
49
|
+
self.__file_format = format_str
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def file_json(self, value: bool) -> 'LogBuilder':
|
|
53
|
+
self.__file_json = value
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def file_level(self, level: int) -> 'LogBuilder':
|
|
57
|
+
self.__file_level = level
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def file_max_size_mb(self, size_in_mb: int) -> 'LogBuilder':
|
|
61
|
+
self.__file_max_size = size_in_mb * 1024 * 1024
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def file_max_count(self, count: int) -> 'LogBuilder':
|
|
65
|
+
self.__file_max_count = count
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def build(self, logger_name: str = 'python-app') -> logging.Logger:
|
|
69
|
+
logger = logging.getLogger(logger_name)
|
|
70
|
+
logger.setLevel(logging.DEBUG)
|
|
71
|
+
|
|
72
|
+
console_handler = self.__build_console_handler(logger_name)
|
|
73
|
+
logger.addHandler(console_handler)
|
|
74
|
+
|
|
75
|
+
if self.__file:
|
|
76
|
+
file_handler = self.__build_file_handler(logger_name)
|
|
77
|
+
logger.addHandler(file_handler)
|
|
78
|
+
|
|
79
|
+
return logger
|
|
80
|
+
|
|
81
|
+
def __build_console_handler(self, logger_name: str) -> logging.Handler:
|
|
82
|
+
console_handler = logging.StreamHandler()
|
|
83
|
+
log_level = self.__console_level
|
|
84
|
+
if log_level is None:
|
|
85
|
+
log_level = utils.get_log_level(logger_name)
|
|
86
|
+
console_handler.setLevel(log_level)
|
|
87
|
+
if self.__console_format:
|
|
88
|
+
console_formatter = logging.Formatter(self.__console_format)
|
|
89
|
+
elif self.__console_json:
|
|
90
|
+
console_formatter = self.__json_formatter
|
|
91
|
+
else:
|
|
92
|
+
console_formatter = self.__text_formatter
|
|
93
|
+
console_handler.setFormatter(console_formatter)
|
|
94
|
+
return console_handler
|
|
95
|
+
|
|
96
|
+
def __build_file_handler(self, logger_name: str) -> logging.Handler:
|
|
97
|
+
file_handler = RotatingFileHandler(
|
|
98
|
+
self.__file,
|
|
99
|
+
maxBytes=self.__file_max_size,
|
|
100
|
+
backupCount=self.__file_max_count
|
|
101
|
+
)
|
|
102
|
+
log_level = self.__file_level
|
|
103
|
+
if log_level is None:
|
|
104
|
+
log_level = utils.get_log_level(logger_name)
|
|
105
|
+
file_handler.setLevel(log_level)
|
|
106
|
+
if self.__file_format:
|
|
107
|
+
file_formatter = logging.Formatter(self.__file_format)
|
|
108
|
+
elif self.__file_json:
|
|
109
|
+
file_formatter = self.__json_formatter
|
|
110
|
+
else:
|
|
111
|
+
file_formatter = self.text_formatter
|
|
112
|
+
file_handler.setFormatter(file_formatter)
|
|
113
|
+
return file_handler
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DjangoLogBuilder:
|
|
117
|
+
__text_formatter = TextFormatter()
|
|
118
|
+
__json_formatter = JsonFormatter()
|
|
119
|
+
|
|
120
|
+
__console_json = False
|
|
121
|
+
|
|
122
|
+
__file = None
|
|
123
|
+
__file_json = True
|
|
124
|
+
__file_max_size = 10 * 1024 * 1024 # 10 MB (default)
|
|
125
|
+
__file_max_count = 5 # Default 5 backup files
|
|
126
|
+
|
|
127
|
+
def console_json(self, value: bool) -> 'DjangoLogBuilder':
|
|
128
|
+
self.__console_json = value
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def file(self, file_name: str) -> 'DjangoLogBuilder':
|
|
132
|
+
self.__file = file_name
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def file_json(self, value: bool) -> 'DjangoLogBuilder':
|
|
136
|
+
self.__file_json = value
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def file_max_size_mb(self, size_in_mb: int) -> 'DjangoLogBuilder':
|
|
140
|
+
self.__file_max_size = size_in_mb * 1024 * 1024
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
def file_max_count(self, count: int) -> 'DjangoLogBuilder':
|
|
144
|
+
self.__file_max_count = count
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def build_config_4_django(self, django_log_level=None, show_sql=False) -> dict:
|
|
148
|
+
django_log_level = django_log_level if django_log_level else utils.get_log_level_root()
|
|
149
|
+
django_config = {
|
|
150
|
+
'version': 1,
|
|
151
|
+
'disable_existing_loggers': False,
|
|
152
|
+
'filters': {
|
|
153
|
+
'trace_id': {
|
|
154
|
+
'()': 'kydo.logging_helpers.TraceIDFilter',
|
|
155
|
+
},
|
|
156
|
+
'stack_trace': {
|
|
157
|
+
'()': 'kydo.logging_helpers.StackTraceFilter',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
'handlers': {
|
|
161
|
+
'console': {
|
|
162
|
+
'class': 'logging.StreamHandler',
|
|
163
|
+
'filters': ['trace_id', 'stack_trace'],
|
|
164
|
+
'formatter': 'default',
|
|
165
|
+
},
|
|
166
|
+
'file': {
|
|
167
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
|
168
|
+
'filename': self.__file,
|
|
169
|
+
'maxBytes': self.__file_max_size,
|
|
170
|
+
'backupCount': self.__file_max_count,
|
|
171
|
+
'filters': ['trace_id', 'stack_trace'],
|
|
172
|
+
'formatter': 'json',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
'root': {
|
|
176
|
+
'level': utils.get_log_level_root(),
|
|
177
|
+
},
|
|
178
|
+
'loggers': {
|
|
179
|
+
'django': {
|
|
180
|
+
'level': django_log_level,
|
|
181
|
+
'propagate': False,
|
|
182
|
+
},
|
|
183
|
+
'django.db.backends': {
|
|
184
|
+
'handlers': ['console', 'file'],
|
|
185
|
+
'level': 'DEBUG' if show_sql else django_log_level,
|
|
186
|
+
'propagate': False,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
'formatters': {
|
|
190
|
+
'json': {
|
|
191
|
+
'()': f'{JsonFormatter.__module__}.{JsonFormatter.__name__}',
|
|
192
|
+
},
|
|
193
|
+
'default': {
|
|
194
|
+
'()': f'{TextFormatter.__module__}.{TextFormatter.__name__}',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
return django_config
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from l4py import utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FormatTimeMixin():
|
|
9
|
+
def formatTime(self, record, datefmt=None):
|
|
10
|
+
ct = datetime.fromtimestamp(record.created)
|
|
11
|
+
if datefmt:
|
|
12
|
+
return ct.strftime(datefmt)
|
|
13
|
+
else:
|
|
14
|
+
return ct.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(record.msecs):03d}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JsonFormatter(FormatTimeMixin, logging.Formatter):
|
|
18
|
+
|
|
19
|
+
def __init__(self, app_name=utils.get_app_name()):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.app_name = app_name
|
|
22
|
+
|
|
23
|
+
def format(self, record):
|
|
24
|
+
log_record = {
|
|
25
|
+
"timestamp": self.formatTime(record),
|
|
26
|
+
"app_name": self.app_name,
|
|
27
|
+
"logger_name": record.name,
|
|
28
|
+
"level": record.levelname,
|
|
29
|
+
"file_name": record.filename,
|
|
30
|
+
"line_number": record.lineno,
|
|
31
|
+
"function_name": record.funcName,
|
|
32
|
+
"message": record.msg,
|
|
33
|
+
}
|
|
34
|
+
return json.dumps(log_record)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TextFormatter(FormatTimeMixin, logging.Formatter):
|
|
38
|
+
|
|
39
|
+
def __init__(self, app_name=utils.get_app_name()):
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.app_name = app_name
|
|
42
|
+
|
|
43
|
+
def format(self, record):
|
|
44
|
+
log_record = {
|
|
45
|
+
"timestamp": self.formatTime(record),
|
|
46
|
+
"app_name": self.app_name,
|
|
47
|
+
"logger_name": record.name,
|
|
48
|
+
"level": record.levelname,
|
|
49
|
+
"file_name": record.filename,
|
|
50
|
+
"line_number": record.lineno,
|
|
51
|
+
"function_name": record.funcName,
|
|
52
|
+
"message": record.msg,
|
|
53
|
+
}
|
|
54
|
+
return '{timestamp} [{level:<8}] {app_name} {file_name}:{line_number} {function_name}: {message}'.format(
|
|
55
|
+
**log_record)
|
l4py-0.0.1/l4py/l4py.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .builder import LogBuilder
|
|
2
|
+
|
|
3
|
+
if __name__ == '__main__':
|
|
4
|
+
class TestLogger:
|
|
5
|
+
|
|
6
|
+
logger = LogBuilder()\
|
|
7
|
+
.file('test.log')\
|
|
8
|
+
.build(__name__)
|
|
9
|
+
|
|
10
|
+
def test(self):
|
|
11
|
+
self.logger.debug("This is a debug message")
|
|
12
|
+
self.logger.info("This is an info message")
|
|
13
|
+
self.logger.warning("This is a warning message")
|
|
14
|
+
self.logger.error("This is an error message")
|
|
15
|
+
self.logger.critical("This is a critical message")
|
|
16
|
+
|
|
17
|
+
TestLogger().test()
|
l4py-0.0.1/l4py/utils.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
_LOG_LEVEL_PREFIX = 'PYTHON_LOG_LEVEL_'
|
|
5
|
+
_LOG_LEVEL_ROOT_KEY = f'{_LOG_LEVEL_PREFIX}ROOT'
|
|
6
|
+
_LOG_LEVEL_LOGGER_KEY_FORMAT = f'{_LOG_LEVEL_PREFIX}{{}}'
|
|
7
|
+
|
|
8
|
+
logging_env_vars = [
|
|
9
|
+
{'key': key, 'value': value}
|
|
10
|
+
for key, value in os.environ.items()
|
|
11
|
+
if key.startswith(_LOG_LEVEL_PREFIX)
|
|
12
|
+
]
|
|
13
|
+
logging_env_vars.sort(key=lambda item: item['key'])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_app_name() -> str:
|
|
17
|
+
return os.environ.get('PYTHON_APP_NAME', 'python-app')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_log_level_root() -> str:
|
|
21
|
+
return os.environ.get(_LOG_LEVEL_ROOT_KEY, logging.INFO)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_log_level(logger_name: str) -> str:
|
|
25
|
+
log_level = get_log_level_root()
|
|
26
|
+
for key, value in [(v['key'], v['value']) for v in logging_env_vars]:
|
|
27
|
+
if key.startswith(_LOG_LEVEL_PREFIX):
|
|
28
|
+
logger_name_for_level = _LOG_LEVEL_LOGGER_KEY_FORMAT.format(logger_name)
|
|
29
|
+
if key == logger_name_for_level:
|
|
30
|
+
return value
|
|
31
|
+
elif (logger_name_for_level).startswith(key + '.'):
|
|
32
|
+
log_level = value
|
|
33
|
+
return log_level
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == '__main__':
|
|
37
|
+
logger_name = 'logger.name'
|
|
38
|
+
os.environ.setdefault(f'{_LOG_LEVEL_PREFIX}logger', 'WARN')
|
|
39
|
+
os.environ.setdefault(f'{_LOG_LEVEL_PREFIX}logger.name', 'INFO')
|
|
40
|
+
os.environ.setdefault(f'{_LOG_LEVEL_PREFIX}logger.name_not', 'DEBUG')
|
|
41
|
+
os.environ.setdefault(f'{_LOG_LEVEL_PREFIX}logger.name_n', 'FATAL')
|
|
42
|
+
|
|
43
|
+
print(get_log_level('logger'))
|
|
44
|
+
print(get_log_level('logger.name'))
|
|
45
|
+
print(get_log_level('logger.name_not'))
|
|
46
|
+
print(get_log_level('logger.name_n'))
|
|
47
|
+
print(get_log_level('logger.name_naaaaa'))
|
|
48
|
+
|
|
49
|
+
print(logging.getLevelNamesMapping())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: l4py
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Home-page: https://github.com/roymanigley/l4py
|
|
5
|
+
Author: Roy Manigley
|
|
6
|
+
Author-email: roy.manigley@gmail.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# l4py
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
```
|
|
22
|
+
pip install l4py
|
|
23
|
+
```
|
|
24
|
+
or from Github:
|
|
25
|
+
```
|
|
26
|
+
git clone https://github.com/roymanigley/l4py.git
|
|
27
|
+
cd l4py
|
|
28
|
+
pip install -r requirements.txt
|
|
29
|
+
python setup.py install
|
|
30
|
+
```
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from l4py import Builder
|
|
35
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
l4py
|
l4py-0.0.1/setup.cfg
ADDED
l4py-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
long_description = open('README.md', "rt").read()
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name='l4py',
|
|
7
|
+
version='0.0.1',
|
|
8
|
+
description='',
|
|
9
|
+
long_description=long_description,
|
|
10
|
+
long_description_content_type='text/markdown',
|
|
11
|
+
url='https://github.com/roymanigley/l4py',
|
|
12
|
+
author='Roy Manigley',
|
|
13
|
+
author_email='roy.manigley@gmail.com',
|
|
14
|
+
license='MIT',
|
|
15
|
+
packages=['l4py'],
|
|
16
|
+
install_requires=[],
|
|
17
|
+
|
|
18
|
+
classifiers=[
|
|
19
|
+
'Development Status :: 5 - Production/Stable',
|
|
20
|
+
'Intended Audience :: Developers',
|
|
21
|
+
'License :: OSI Approved :: MIT License',
|
|
22
|
+
'Operating System :: POSIX :: Linux',
|
|
23
|
+
'Programming Language :: Python :: 3.11',
|
|
24
|
+
],
|
|
25
|
+
)
|