Skip to content

Commit a40b1fc

Browse files
author
PPB InfoSec Engineering
committed
initial version
0 parents  commit a40b1fc

25 files changed

Lines changed: 612 additions & 0 deletions

.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# file set with Dockerfile.tests (for run_tests script) in mind - review if used for anything else
2+
.jenkins
3+
**/.coverage
4+
**/__pycache__
5+
**/*.pyc
6+
dist
7+
*.egg-info
8+
.git
9+
.reports
10+
.dockerignore
11+
**/.pytest_cache
12+
**/.vscode

.github/workflows/publish.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This workflows will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: publish
5+
6+
on:
7+
release:
8+
types: [created]
9+
10+
jobs:
11+
deploy:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Set up Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: '3.x'
19+
- name: Install dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
pip install setuptools wheel twine
23+
- name: Build and publish
24+
env:
25+
TWINE_USERNAME: __token__
26+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
27+
run: |
28+
python setup.py sdist bdist_wheel
29+
twine upload dist/*

.github/workflows/publish_test.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This workflows will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: publish test
5+
6+
on:
7+
push:
8+
branches: testpypi
9+
10+
jobs:
11+
deploy:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Set up Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: '3.x'
19+
- name: Install dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
pip install setuptools wheel twine
23+
- name: Build and publish
24+
env:
25+
TWINE_USERNAME: __token__
26+
TWINE_PASSWORD: ${{ secrets.PYPI_TEST_TOKEN }}
27+
run: |
28+
python setup.py sdist bdist_wheel
29+
twine upload --repository testpypi dist/*

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.pyc
2+
__pycache__
3+
/dist
4+
*.egg-info
5+
.coverage
6+
.reports
7+
.pytest_cache
8+
.vscode

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2021 PaddyPowerBetfair
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.
22+

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include README.md
2+
include LICENSE

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.PHONY: style
2+
style:
3+
black --target-version=py36 \
4+
--line-length=120 \
5+
--skip-string-normalization \
6+
logbasecommand testapp setup.py
7+
8+
.PHONY: style_check
9+
style_check:
10+
black --target-version=py36 \
11+
--line-length=120 \
12+
--skip-string-normalization \
13+
--check \
14+
logbasecommand testapp setup.py
15+
16+
test:
17+
testapp/manage.py test $${TEST_ARGS:-tests}
18+
19+
coverage:
20+
PYTHONPATH="testapp" \
21+
python -b -W always -m coverage run testapp/manage.py test $${TEST_ARGS:-tests}
22+
coverage report

README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# django-logbasecommand
2+
3+
Minimal package to add logging helpers to Django management commands
4+
5+
## Usage
6+
7+
Replace
8+
9+
```
10+
from django.core.management.base import BaseCommand
11+
12+
class YourCommand(BaseCommand):
13+
```
14+
15+
with
16+
17+
```
18+
from logbasecommand.base import LogBaseCommand
19+
20+
class LogBaseCommand(BaseCommand):
21+
```
22+
23+
and now you can use the drop-in methods to replace `self.stdout`/`self.stderr`:
24+
* `log`
25+
* `log_debug`
26+
* `log_warning`
27+
* `log_error`
28+
* `log_exception`
29+
30+
Or access `self.logger` directly.
31+
32+
33+
All command logger names are derived from the command module name and prefixed with `logbasecommand.base` (by default, use `LOGBASECOMMAND_PREFIX` setting to change it), so logging can be configured from your project settings.py, with `LOGGING`, ie (full example, check the `loggers` part):
34+
35+
```python
36+
LOGGING = {
37+
'version': 1,
38+
'disable_existing_loggers': False,
39+
'filters': {
40+
'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'},
41+
'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue'},
42+
},
43+
'formatters': {
44+
'verbose': {'format': '[%(asctime)s] [%(process)s] [%(levelname)s] [%(name)s] %(message)s'},
45+
'minimal': {'format': '[%(levelname)s] [%(name)s] %(message)s'},
46+
},
47+
'handlers': {
48+
'console': {
49+
'level': 'INFO',
50+
'filters': ['require_debug_false'],
51+
'class': 'logging.StreamHandler',
52+
'formatter': 'verbose',
53+
},
54+
'console_debug': {
55+
'level': 'DEBUG',
56+
'filters': ['require_debug_true'],
57+
'class': 'logging.StreamHandler',
58+
'formatter': 'verbose',
59+
},
60+
'console_minimal': {
61+
'level': 'INFO',
62+
'filters': ['require_debug_false'],
63+
'class': 'logging.StreamHandler',
64+
'formatter': 'minimal',
65+
},
66+
'console_debug_minimal': {
67+
'level': 'DEBUG',
68+
'filters': ['require_debug_true'],
69+
'class': 'logging.StreamHandler',
70+
'formatter': 'minimal',
71+
},
72+
'mail_admins': {
73+
'level': 'ERROR',
74+
'filters': ['require_debug_false'],
75+
'class': 'django.utils.log.AdminEmailHandler',
76+
},
77+
},
78+
'loggers': {
79+
'': {'handlers': ['console', 'console_debug'], 'level': 'INFO', 'propagate': True},
80+
'logbasecommand.base': {
81+
'handlers': ['console_minimal', 'console_debug_minimal'],
82+
'level': 'DEBUG',
83+
'propagate': False,
84+
},
85+
},
86+
}
87+
```

logbasecommand/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '0.0.1'

logbasecommand/base.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import logging
2+
3+
from django.core.management.base import (
4+
BaseCommand,
5+
CommandError, # noqa - to make it on subclasses to import from only one place
6+
)
7+
from django.conf import settings
8+
9+
10+
class LogBaseCommand(BaseCommand):
11+
verbosity = 1
12+
custom_stdout = False
13+
custom_stderr = False
14+
15+
def __init__(self, *args, **kwargs):
16+
super().__init__(*args, **kwargs)
17+
prefix = getattr(settings, 'LOGBASECOMMAND_PREFIX', None) or __name__
18+
self.logger = logging.getLogger(prefix + '.' + self.__module__.split('.')[-1])
19+
20+
def __handle_custom_std(self, ifstd, std, msg, *args):
21+
if ifstd:
22+
std.write(str(msg) if not args else msg % args)
23+
24+
def __custom_stderr(self, msg, *args):
25+
self.__handle_custom_std(self.custom_stderr, self.stderr, msg, *args)
26+
27+
def __custom_stdout(self, msg, *args):
28+
self.__handle_custom_std(self.custom_stdout, self.stdout, msg, *args)
29+
30+
def log_debug(self, msg, *args, **kwargs):
31+
if self.verbosity >= 2:
32+
self.__custom_stdout(msg, *args)
33+
return self.logger.debug(msg, *args, **kwargs)
34+
35+
def log(self, msg, *args, **kwargs):
36+
self.__custom_stdout(msg, *args)
37+
return self.logger.info(msg, *args, **kwargs)
38+
39+
def log_warning(self, msg, *args, **kwargs):
40+
self.__custom_stderr(msg, *args)
41+
return self.logger.warning(msg, *args, **kwargs)
42+
43+
def log_error(self, msg, *args, **kwargs):
44+
self.__custom_stderr(msg, *args)
45+
return self.logger.error(msg, *args, **kwargs)
46+
47+
def log_exception(self, msg, *args, **kwargs):
48+
self.__custom_stderr(msg, *args)
49+
return self.logger.exception(msg, *args, **kwargs)
50+
51+
def execute(self, *args, **options):
52+
self.verbosity = options['verbosity']
53+
self.logger.setLevel(
54+
[logging.ERROR, max(self.logger.getEffectiveLevel(), logging.INFO), logging.DEBUG, logging.DEBUG][
55+
self.verbosity
56+
]
57+
)
58+
59+
if options.get('stdout') is not None:
60+
self.custom_stdout = True
61+
if options.get('stderr') is not None:
62+
self.custom_stderr = True
63+
64+
super().execute(*args, **options)

0 commit comments

Comments
 (0)