doc-log
doc-log
offers the ability to parse docstrings and ultimately make decisions based on parsed rules. Everything that doc-log
can do is listed under the Features section. Follow the steps in Setup to install doc-log
. Examples and use-cases are listed under the Examples section.
Features
Parsing
- Parse docstrings based on any of the supported formats.
-
PEP257
-
Epytext
-
rEST
-
Google
-
numpydoc
-
Type Checking
- Enforce runtime type checking based on the
function
/method
docstring. Supports almost all of the types offered by the standard library typing.-
PEP257
-
Epytext
-
rEST
-
Google
-
numpydoc
-
Logging
-
Log
function
/method
calls based on docstring.-
PEP257
-
Epytext
-
rEST
-
Google
-
numpydoc
-
-
Logging Information
- Module that the
function
/method
was called from. - Time of
function
/method
call. - Passive type checking of passed parameters.
Warning
level or below.
- Active type checking of passed parameters and return types.
Error
level or above.
- Module that the
Examples
doc-log
Using # add_two.py
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True, _active_type_check=True)
def add_two(i):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
return i + 2
print(f"{add_two(i=2)} == 4")
$ python3 add_two.py
WARNING :: 2021-08-27 19:33:45,415 :: (doc-log :: <module>:14:25) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:33:45,415 :: (doc-log :: <module>:20:25) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
INFO :: 2021-08-27 19:33:45,415 :: (doc-log :: <module>:7:25) function: `add_two` called from `.../doc-log/add_two.py` at: `2021-08-27T19:33:45.415286`
4 == 4
Invalid Type Provided
# add_two.py
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True, _active_type_check=True)
def add_two(i):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
return i + 2
print(f"{add_two(i='2')} == 4")
$ python3 add_two.py
WARNING :: 2021-08-27 19:34:14,417 :: (doc-log :: <module>:14:25) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:34:14,417 :: (doc-log :: <module>:20:25) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
Traceback (most recent call last):
File ".../doc-log/add_two.py", line 25, in <module>
print(f"{add_two(i='2')} == 4")
File ".../doc-log/doc_log/__init__.py", line 48, in wrapper
raise TypeError(
TypeError: (doc-log :: <module>:14:25) parameter: `i` was not of expected type: `int` was actually `str`
Invalid Type Defined
# add_two.py
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True, _active_type_check=True)
def add_two(i):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- str
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
return i + 2
print(f"{add_two(i=2)} == 4")
$ python3 add_two.py
WARNING :: 2021-08-27 19:34:39,279 :: (doc-log :: <module>:14:25) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `str`
WARNING :: 2021-08-27 19:34:39,279 :: (doc-log :: <module>:20:25) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
Traceback (most recent call last):
File ".../doc-log/add_two.py", line 25, in <module>
print(f"{add_two(i=2)} == 4")
File ".../doc-log/doc_log/__init__.py", line 48, in wrapper
raise TypeError(
TypeError: (doc-log :: <module>:14:25) parameter: `i` was not of expected type: `str` was actually `int`
Logging With Type Check
# add_two.py
from logging import INFO, getLogger, basicConfig
basicConfig(format="%(levelname)s :: %(asctime)s :: %(message)s", level=INFO)
logger = getLogger(__name__)
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True)
def add_two(i, j=None):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
logger.info("parameter: `i` is {!s}".format(i))
return i + 2
print(f"{add_two(i=2, j=3)} == 4")
$ python3 add_two.py
WARNING :: 2021-08-27 19:35:03,278 :: (doc-log :: <module>:19:31) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:35:03,279 :: (doc-log :: <module>:25:31) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:35:03,292 :: (doc-log :: <module>:12:31) parameter: `j` was not of expected type: `_empty` was actually `int`
INFO :: 2021-08-27 19:35:03,293 :: (doc-log :: <module>:12:31) function: `add_two` called from `.../doc-log/add_two.py` at: `2021-08-27T19:35:03.292657`
INFO :: 2021-08-27 19:35:03,293 :: parameter: `i` is 2
4 == 4
Logging With Type Check (Error)
# add_two.py
from logging import ERROR, getLogger, basicConfig
basicConfig(format="%(levelname)s :: %(asctime)s :: %(message)s", level=ERROR)
logger = getLogger(__name__)
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True)
def add_two(i, j=None):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
logger.info("parameter: `i` is {!s}".format(i))
return i + 2
print(f"{add_two(i=2, j=3)} == 4")
$ python3 add_two.py
Traceback (most recent call last):
File ".../doc-log/add_two.py", line 31, in <module>
print(f"{add_two(i=2, j=3)} == 4")
File ".../doc-log/doc_log/__init__.py", line 48, in wrapper
raise TypeError(
TypeError: (doc-log :: <module>:12:31) parameter: `j` was not of expected type: `_empty` was actually `int`
Logging With Type Check (Debug)
# add_two.py
from logging import DEBUG, getLogger, basicConfig
basicConfig(format="%(levelname)s :: %(asctime)s :: %(message)s", level=DEBUG)
logger = getLogger(__name__)
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=True)
def add_two(i, j=None):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
logger.info("parameter: `i` is {!s}".format(i))
return i + 2
print(f"{add_two(i=2, j=3)} == 4")
$ python3 add_two.py
DEBUG :: 2021-08-27 19:35:56,182 :: (doc-log :: <module>:12:31) parsing docstring with dialect: `pep257` from function: `add_two` in `.../doc-log/add_two.py`
DEBUG :: 2021-08-27 19:35:56,183 :: (doc-log :: <module>:12:31) parsed sections: `['arguments', 'types', 'returns', 'rtypes']` from function: `add_two` in `.../doc-log/add_two.py`
WARNING :: 2021-08-27 19:35:56,184 :: (doc-log :: <module>:19:31) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:35:56,184 :: (doc-log :: <module>:25:31) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
DEBUG :: 2021-08-27 19:35:56,190 :: (doc-log :: <module>:19:31) item: `int` is not nested, type checking directly against value: `2`
DEBUG :: 2021-08-27 19:35:56,190 :: (doc-log :: <module>:12:31) item: `_empty` is not nested, type checking directly against value: `3`
DEBUG :: 2021-08-27 19:35:56,191 :: (doc-log :: <module>:18:31) type check arguments results was: `{'i': SectionItemTypeResult(item=SectionItem(value='int', _subitems=[], name='i', lineno=7), result=True, expected='int', actual='int', _subitems=[]), 'j': SectionItemTypeResult(item=SectionItem(value='_empty', _subitems=[], name='j', lineno=0), result=False, expected='_empty', actual='int', _subitems=[])}`
WARNING :: 2021-08-27 19:35:56,191 :: (doc-log :: <module>:12:31) parameter: `j` was not of expected type: `_empty` was actually `int`
INFO :: 2021-08-27 19:35:56,191 :: (doc-log :: <module>:12:31) function: `add_two` called from `.../doc-log/add_two.py` at: `2021-08-27T19:35:56.191936`
DEBUG :: 2021-08-27 19:35:56,191 :: (doc-log :: <module>:12:31) function: `add_two` was passed arguments: `()` and keyword arguments: `{'i': 2, 'j': 3}`
INFO :: 2021-08-27 19:35:56,191 :: parameter: `i` is 2
DEBUG :: 2021-08-27 19:35:56,196 :: (doc-log :: <module>:25:31) return type: `int` is not nested, type checking directly against value: `4`
DEBUG :: 2021-08-27 19:35:56,198 :: (doc-log :: <module>:24:31) type check return results was: `SectionItemTypeResult(item=SectionItem(value='int', _subitems=[], name=None, lineno=13), result=True, expected='int', actual='int', _subitems=[])`
4 == 4
Logging Without Type Check
# add_two.py
from logging import INFO, getLogger, basicConfig
basicConfig(format="%(levelname)s :: %(asctime)s :: %(message)s", level=INFO)
logger = getLogger(__name__)
from doc_log import doc_log
@doc_log(dialect="pep257", type_check=False)
def add_two(i, j=None):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
logger.info("parameter: `i` is {!s}".format(i))
return i + 2
print(f"{add_two(i=2, j=3)} == 4")
$ python3 add_two.py
WARNING :: 2021-08-27 19:36:16,881 :: (doc-log :: <module>:19:31) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:36:16,881 :: (doc-log :: <module>:25:31) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
INFO :: 2021-08-27 19:36:16,886 :: parameter: `i` is 2
4 == 4
PEP257
Docstring
Parsing # add_two.py
from doc_log.parser import parse_docstring
def add_two(i: int) -> int:
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Returns:
Integer `i` plus two (2)
"""
return i + 2
print(parse_docstring(add_two, dialect="pep257"))
$ python3 add_two.py
WARNING :: 2021-08-27 19:36:35,514 :: (doc-log :: <module>:7:18) parameter: `i` had different type hints in the docstring and in the signature, signature: `int` / docstring: `_empty`
WARNING :: 2021-08-27 19:36:35,514 :: (doc-log :: <module>:7:18) return type had different type hints in the docstring and in the signature, signature: `int` / docstring: `_empty`
{'arguments': Section(section='arguments', items=[SectionItem(value='the provided integer', _subitems=[], name='i', lineno=4)], _function=<code object add_two at 0x000002C883AD45B0, file ".../doc-log/add_two.py", line 6>, lineno=3), 'returns': Section(section='returns', items=[SectionItem(value='Integer `i` plus two (2)', _subitems=[], name=None, lineno=7)], _function=<code object add_two at 0x000002C883AD45B0, file ".../doc-log/add_two.py", line 6>, lineno=6), 'types': Section(section='types', items=[SectionItem(value='int', _subitems=[], name='i', lineno=0)], _function=<code object add_two at 0x000002C883AD45B0, file ".../doc-log/add_two.py", line 6>, lineno=0), 'rtypes': Section(section='rtypes', items=[SectionItem(value='int', _subitems=[], name=None, lineno=0)], _function=<code object add_two at 0x000002C883AD45B0, file ".../doc-log/add_two.py", line 6>, lineno=0)}
PEP257
Docstring
Parsing Type Hints in # add_two.py
from doc_log.parser import parse_docstring
def add_two(i):
"""Add two (2) to a provided integer and return.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
int
"""
return i + 2
print(parse_docstring(_function=add_two, dialect="pep257"))
$ python3 add_two.py
WARNING :: 2021-08-27 19:36:58,722 :: (doc-log :: <module>:14:24) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:36:58,722 :: (doc-log :: <module>:20:24) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
{'arguments': Section(section='arguments', items=[SectionItem(value='the provided integer', _subitems=[], name='i', lineno=4)], _function=<code object add_two at 0x000001A0E25D45B0, file ".../doc-log/add_two.py", line 6>, lineno=3), 'types': Section(section='types', items=[SectionItem(value='int', _subitems=[], name='i', lineno=7)], _function=<code object add_two at 0x000001A0E25D45B0, file ".../doc-log/add_two.py", line 6>, lineno=6), 'returns': Section(section='returns', items=[SectionItem(value='Integer `i` plus two (2)', _subitems=[], name=None, lineno=10)], _function=<code object add_two at 0x000001A0E25D45B0, file ".../doc-log/add_two.py", line 6>, lineno=9), 'rtypes': Section(section='rtypes', items=[SectionItem(value='int', _subitems=[], name=None, lineno=13)], _function=<code object add_two at 0x000001A0E25D45B0, file ".../doc-log/add_two.py", line 6>, lineno=12)}
PEP257
Docstring
Parse Type Check Results in # add_two.py
from doc_log.parser import parse_docstring
from doc_log.types import type_check_arguments, type_check_rtypes
def add_two(i):
"""Add two (2) to a provided integer and return the original and the result.
Arguments:
i -- the provided integer
Types:
i -- int
Returns:
Integer `i` plus two (2)
Return Type:
Tuple[int, int]
"""
return (i, i + 2)
parameters = {"i": 2}
result = add_two(**parameters)
docstring = parse_docstring(_function=add_two, dialect="pep257")
print("Arguments ==============")
print(type_check_arguments(docstring["types"], parameters=parameters))
print("Return ==============")
print(repr(type_check_rtypes(docstring["rtypes"], results=result)))
$ python3 add_two.py
WARNING :: 2021-08-27 19:37:24,673 :: (doc-log :: <module>:15:27) parameter: `i` had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `int`
WARNING :: 2021-08-27 19:37:24,673 :: (doc-log :: <module>:21:27) return type had different type hints in the docstring and in the signature, signature: `_empty` / docstring: `tuple[int, int]`
Arguments ==============
{'i': SectionItemTypeResult(item=SectionItem(value='int', _subitems=[], name='i', lineno=7), result=True, expected='int', actual='int', _subitems=[])}
Return ==============
SectionItemTypeResult(item=SectionItem(value='tuple', _subitems=[SectionItem(value='int', _subitems=[], name=None, lineno=13), SectionItem(value='int', _subitems=[], name=None, lineno=13)], name=None, lineno=13), result=True, expected='tuple', actual='tuple', _subitems=[SectionItemTypeResult(item=SectionItem(value='int', _subitems=[], name=None, lineno=13), result=True, expected='int', actual='int', _subitems=[]), SectionItemTypeResult(item=SectionItem(value='int', _subitems=[], name=None, lineno=13), result=True, expected='int', actual='int', _subitems=[])])
Setup
doc-log
does not use any external dependency (except for pytest and black when developing)
git clone https://github.com/wahl-sec/doc-log.git && cd doc-log && pip install .