Skip to content

Commit 166a2b1

Browse files
improves code quality (#2)
* improves code quality * adds missing dependencies * adds flake8 test to Azure Pipelines
1 parent 8dbbd45 commit 166a2b1

32 files changed

+662
-384
lines changed

.flake8

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[flake8]
2+
exclude = __pycache__,built,build,venv
3+
ignore = E203, E266, W503
4+
max-line-length = 88
5+
max-complexity = 18
6+
select = B,C,E,F,W,T4,B9

.github/workflows/build.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- ci
8+
pull_request:
9+
branches:
10+
- '*'
11+
12+
env:
13+
PROJECT_NAME: essentials
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-18.04
18+
strategy:
19+
matrix:
20+
python-version: [3.7, 3.8]
21+
22+
steps:
23+
- uses: actions/checkout@v1
24+
with:
25+
fetch-depth: 9
26+
submodules: false
27+
28+
- name: Use Python ${{ matrix.python-version }}
29+
uses: actions/setup-python@v1
30+
with:
31+
python-version: ${{ matrix.python-version }}
32+
33+
- uses: actions/cache@v1
34+
id: depcache
35+
with:
36+
path: deps
37+
key: requirements-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}
38+
39+
- name: Download dependencies
40+
if: steps.depcache.outputs.cache-hit != 'true'
41+
run: |
42+
pip download --dest=deps -r requirements.txt
43+
44+
- name: Install dependencies
45+
run: |
46+
pip install -U --no-index --find-links=deps deps/*
47+
48+
- name: Run tests
49+
run: |
50+
flake8 && pytest --doctest-modules --junitxml=junit/pytest-results-${{ matrix.python-version }}.xml --cov=$PROJECT_NAME --cov-report=xml tests/
51+
52+
- name: Upload pytest test results
53+
uses: actions/upload-artifact@master
54+
with:
55+
name: pytest-results-${{ matrix.python-version }}
56+
path: junit/pytest-results-${{ matrix.python-version }}.xml
57+
if: always()
58+
59+
- name: Install distribution dependencies
60+
run: pip install --upgrade twine setuptools wheel
61+
if: matrix.python-version == 3.8
62+
63+
- name: Create distribution package
64+
run: python setup.py sdist bdist_wheel
65+
if: matrix.python-version == 3.8
66+
67+
- name: Upload distribution package
68+
uses: actions/upload-artifact@master
69+
with:
70+
name: dist-package-${{ matrix.python-version }}
71+
path: dist
72+
if: matrix.python-version == 3.8

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,5 @@ venv.bak/
105105

106106
built/
107107

108-
.idea
108+
.idea
109+
.vscode

azure-pipelines.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ steps:
2626
pip install -r requirements.txt
2727
displayName: 'Install dependencies'
2828

29+
- script: flake8
30+
displayName: Flake8 test
31+
2932
- script: |
3033
pip install pytest pytest-asyncio pytest-cov
3134
pip install https://github.com/RobertoPrevato/pytest-azurepipelines/archive/css_styles.zip
3235
python -m pytest tests/ --cov essentials --cov-report html
33-
displayName: 'pytest'
36+
displayName: Run pytest tests
3437

3538
- script: |
3639
pip install --upgrade twine setuptools wheel

essentials.code-workspace

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
}
6+
],
7+
"settings": {
8+
"python.testing.pytestArgs": [
9+
"."
10+
],
11+
"files.trimTrailingWhitespace": true,
12+
"files.trimFinalNewlines": true,
13+
"python.testing.unittestEnabled": false,
14+
"python.testing.nosetestsEnabled": false,
15+
"python.testing.pytestEnabled": true,
16+
"python.linting.pylintEnabled": false,
17+
"python.linting.flake8Enabled": true,
18+
"python.linting.mypyEnabled": true,
19+
"python.linting.enabled": true,
20+
"[python]": {
21+
"editor.tabSize": 4,
22+
"editor.rulers": [
23+
88,
24+
100
25+
]
26+
}
27+
}
28+
}

essentials/decorators/__init__.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
from functools import wraps
22
from inspect import iscoroutinefunction
33
from typing import Type
4-
from .retry import retry, CatchException, OnException
4+
from .retry import retry, CatchException, OnException # noqa
55

66

7-
def exception_handle(exception_type: Type[Exception], catch_exceptions_types: CatchException = None):
7+
def exception_handle(
8+
exception_type: Type[Exception],
9+
catch_exceptions_types: CatchException = None
10+
):
811
"""
9-
Wraps a given function to catch all exceptions that might happen during its execution, and
10-
rethrow new exceptions of the given type.
12+
Wraps a given function to catch all exceptions that might happen during
13+
its execution, and rethrow new exceptions of the given type.
1114
12-
:param exception_type: the desired type of exception, that should throw the decorated function in case of exception.
13-
:param catch_exceptions_types: the types of exception to be catched (defaults to Exception)
14-
:return:
15+
:param exception_type: the desired type of exception,
16+
to be thrown in case of exception.
17+
:param catch_exceptions_types: the types of exception to be
18+
catched (defaults to Exception)
1519
"""
1620
if not catch_exceptions_types:
1721
catch_exceptions_types = Exception
1822

1923
def decorator(fn):
2024
if iscoroutinefunction(fn):
25+
2126
@wraps(fn)
2227
async def async_wrapper(*args, **kwargs):
2328
try:
@@ -33,5 +38,7 @@ def wrapper(*args, **kwargs):
3338
return fn(*args, **kwargs)
3439
except catch_exceptions_types as exp:
3540
raise exception_type(exp)
41+
3642
return wrapper
43+
3744
return decorator

essentials/decorators/logs.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import logging
2-
import reprlib
3-
from uuid import uuid4
4-
from logging import Logger
5-
from typing import Optional, Callable
62
from functools import wraps
7-
from essentials.diagnostics import StopWatch
83
from inspect import iscoroutinefunction
4+
from logging import Logger
5+
from typing import Callable, Optional
6+
from uuid import uuid4
97

8+
from essentials.diagnostics import StopWatch
109

1110
IdFactory = Callable[[], str]
1211

@@ -15,22 +14,24 @@ def _default_id_factory():
1514
return str(uuid4())
1615

1716

18-
def log(logger: Optional[Logger] = None,
19-
id_factory: Optional[IdFactory] = None,
20-
log_arguments: bool = False,
21-
log_return_value: bool = False,
22-
level=logging.INFO,
23-
call_msg='%s; called; call id: %s',
24-
call_msg_with_input='%s; called; call id: %s; args: %s; kwargs: %s',
25-
completed_msg='%s; completed; call id: %s; elapsed %s ms',
26-
completed_msg_with_output='%s; completed; call id: %s; elapsed %s ms; output: %s',
27-
exc_message='%s; unhandled exception; call id: %s; elapsed %s ms'):
17+
def log(
18+
logger: Optional[Logger] = None,
19+
id_factory: Optional[IdFactory] = None,
20+
log_arguments: bool = False,
21+
log_return_value: bool = False,
22+
level=logging.INFO,
23+
call_msg="%s; called; call id: %s",
24+
call_msg_with_input="%s; called; call id: %s; args: %s; kwargs: %s",
25+
completed_msg="%s; completed; call id: %s; elapsed %s ms",
26+
completed_msg_with_output="%s; completed; call id: %s; elapsed %s ms; output: %s",
27+
exc_message="%s; unhandled exception; call id: %s; elapsed %s ms",
28+
):
2829

2930
if not id_factory:
3031
id_factory = _default_id_factory
3132

3233
if logger is None:
33-
logger = logging.getLogger(f'fn_calls')
34+
logger = logging.getLogger(f"fn_calls")
3435

3536
def before(name, fn, args, kwargs):
3637
function_call_id = id_factory()
@@ -44,19 +45,31 @@ def before(name, fn, args, kwargs):
4445

4546
def on_exception(name, stop_watch, exc, function_call_id):
4647
stop_watch.stop()
47-
logger.exception(exc_message, name, function_call_id, stop_watch.elapsed_ms, exc_info=exc)
48+
logger.exception(
49+
exc_message, name, function_call_id, stop_watch.elapsed_ms, exc_info=exc
50+
)
4851

4952
def after(name, stop_watch, function_call_id, value):
5053
if log_return_value:
51-
logger.log(level, completed_msg_with_output, name, function_call_id, stop_watch.elapsed_ms, value)
54+
logger.log(
55+
level,
56+
completed_msg_with_output,
57+
name,
58+
function_call_id,
59+
stop_watch.elapsed_ms,
60+
value,
61+
)
5262
else:
53-
logger.log(level, completed_msg, name, function_call_id, stop_watch.elapsed_ms)
63+
logger.log(
64+
level, completed_msg, name, function_call_id, stop_watch.elapsed_ms
65+
)
5466

5567
def log_decorator(fn):
5668
nonlocal logger
57-
name = fn.__module__ + '.' + fn.__name__
69+
name = fn.__module__ + "." + fn.__name__
5870

5971
if iscoroutinefunction(fn):
72+
6073
@wraps(fn)
6174
async def async_wrapper(*args, **kwargs):
6275
function_call_id = before(name, fn, args, kwargs)
@@ -88,4 +101,5 @@ def wrapper(*args, **kwargs):
88101
return value
89102

90103
return wrapper
104+
91105
return log_decorator

essentials/decorators/retry.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
OnException = Optional[Callable[[Type[Exception], int], None]]
1010

1111

12-
def _get_retry_async_wrapper(fn,
13-
times: int,
14-
delay: float,
15-
catch_exceptions_types: CatchException,
16-
on_exception: OnException,
17-
loop):
12+
def _get_retry_async_wrapper(
13+
fn,
14+
times: int,
15+
delay: float,
16+
catch_exceptions_types: CatchException,
17+
on_exception: OnException,
18+
loop,
19+
):
1820
@wraps(fn)
1921
async def async_wrapper(*args, **kwargs):
2022
attempt = 0
@@ -39,22 +41,21 @@ async def async_wrapper(*args, **kwargs):
3941
return async_wrapper
4042

4143

42-
def retry(times: int = 3,
43-
delay: Optional[float] = 0.1,
44-
catch_exceptions_types: CatchException = None,
45-
on_exception: OnException = None,
46-
loop=None):
44+
def retry(
45+
times: int = 3,
46+
delay: Optional[float] = 0.1,
47+
catch_exceptions_types: CatchException = None,
48+
on_exception: OnException = None,
49+
loop=None,
50+
):
4751
if catch_exceptions_types is None:
4852
catch_exceptions_types = Exception
4953

5054
def retry_decorator(fn):
5155
if iscoroutinefunction(fn):
52-
return _get_retry_async_wrapper(fn,
53-
times,
54-
delay,
55-
catch_exceptions_types,
56-
on_exception,
57-
loop)
56+
return _get_retry_async_wrapper(
57+
fn, times, delay, catch_exceptions_types, on_exception, loop
58+
)
5859

5960
@wraps(fn)
6061
def wrapper(*args, **kwargs):
@@ -73,5 +74,7 @@ def wrapper(*args, **kwargs):
7374

7475
if delay is not None:
7576
time.sleep(delay)
77+
7678
return wrapper
79+
7780
return retry_decorator

essentials/diagnostics.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
"""Timer class, edited from: Python Cookbook, 3rd Edition by Brian K. Jones, David Beazley"""
1+
"""
2+
Timer class, edited from: Python Cookbook,
3+
3rd Edition by Brian K. Jones, David Beazley
4+
"""
25
import time
36

47

58
class StopWatch:
6-
79
def __init__(self, func=time.perf_counter):
810
self._elapsed_s = 0.0
911
self._func = func
1012
self._start = None
1113

1214
def __repr__(self):
13-
return f'<StopWatch elapsed s.: {self.elapsed_s}>'
15+
return f"<StopWatch elapsed s.: {self.elapsed_s}>"
1416

1517
def start(self):
1618
if self._start is not None:
17-
raise RuntimeError('StopWatch already running')
19+
raise RuntimeError("StopWatch already running")
1820

1921
self._start = self._func()
2022

2123
def stop(self):
2224
if self._start is None:
23-
raise RuntimeError('StopWatch not running')
25+
raise RuntimeError("StopWatch not running")
2426

2527
self._elapsed_s += self._func() - self._start
2628

0 commit comments

Comments
 (0)