logwrap


Namelogwrap JSON
Version 11.0.0 PyPI version JSON
download
home_pagehttps://github.com/python-useful-helpers/logwrap
SummaryDecorator for logging function arguments and return value by human-readable way
upload_time2023-06-20 07:44:08
maintainer
docs_urlNone
author
requires_python>=3.8.0
licenseApache-2.0
keywords logging debugging development
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            logwrap
=======

.. image:: https://github.com/python-useful-helpers/logwrap/workflows/Python%20package/badge.svg
    :target: https://github.com/python-useful-helpers/logwrap/actions
.. image:: https://coveralls.io/repos/github/python-useful-helpers/logwrap/badge.svg?branch=master
    :target: https://coveralls.io/github/python-useful-helpers/logwrap?branch=master
.. image:: https://readthedocs.org/projects/logwrap/badge/?version=latest
    :target: http://logwrap.readthedocs.io/
    :alt: Documentation Status
.. image:: https://img.shields.io/pypi/v/logwrap.svg
    :target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/pypi/pyversions/logwrap.svg
    :target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/pypi/status/logwrap.svg
    :target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/github/license/python-useful-helpers/logwrap.svg
    :target: https://raw.githubusercontent.com/python-useful-helpers/logwrap/master/LICENSE
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/ambv/black


logwrap is a helper for logging in human-readable format function arguments and call result on function call.
Why? Because logging of `*args, **kwargs` become useless with project grow and you need more details in call log.

Cons:

* Log records are not single line.

Pros:

* Log records are not single 100500 symbols length line.
  (Especially actual for testing/development environments and for Kibana users).
* Service free: job is done by this library and it's dependencies. It works at virtualenv
* Free software: Apache license
* Open Source: https://github.com/python-useful-helpers/logwrap
* PyPI packaged: https://pypi.python.org/pypi/logwrap
* Self-documented code: docstrings with types in comments
* Tested: see bages on top
* Support multiple Python versions:

::

    Python 3.8
    Python 3.9
    Python 3.10

.. note::

    Support python versions:

    * 2.7: < 5.0.0
    * 3.5: < 6.0.0
    * 3.7: < 10.0.0

This package includes helpers:

* `logwrap` - main helper. The same is `LogWrap`.

* `LogWrap` - class with `logwrap` implementation. May be used directly.

* `pretty_repr`

* `pretty_str`

* `PrettyFormat`

* `LogOnAccess` - property with logging on successful get/set/delete or failure.

Usage
=====

logwrap
-------
The main decorator. Could be used as not argumented (`@logwrap.logwrap`) and argumented (`@logwrap.logwrap()`).
Not argumented usage simple calls with default values for all positions.

.. note:: Argumens should be set via keywords only.

Argumented usage with arguments from signature:

.. code-block:: python

    @logwrap.logwrap(
        log=None,  # if not set: try to find LOGGER, LOG, logger or log object in target module and use it if it logger instance. Fallback: logger named logwrap
        log_level=logging.DEBUG,
        exc_level=logging.ERROR,
        max_indent=20,  # forwarded to the pretty_repr
        blacklisted_names=None,  # list argument names, which should be dropped from log
        blacklisted_exceptions=None,  # Exceptions to skip details in log (no traceback, no exception details - just class name)
        log_call_args=True,  # Log call arguments before call
        log_call_args_on_exc=True,  # Log call arguments if exception happens
        log_traceback = True,  # Log traceback if exception happens
        log_result_obj=True,  # Log result object
    )

Usage examples:

.. code-block:: python

    @logwrap.logwrap()
    def foo():
        pass

is equal to:

.. code-block:: python

    @logwrap.logwrap
    def foo():
        pass

Get decorator for use without parameters:

.. code-block:: python

    get_logs = logwrap.logwrap()  # set required parameters via arguments

    type(get_logs) == LogWrap  # All logic is implemented in LogWrap class starting from version 2.2.0

    @get_logs
    def foo():
        pass

Call example (python 3.8):

.. code-block:: python

   import logwrap

   @logwrap.logwrap
   def example_function1(
           arg0: str,
           /,
           arg1: str,
           arg2: str='arg2',
           *args,
           kwarg1: str,
           kwarg2: str='kwarg2',
           **kwargs
   ) -> tuple():
       return (arg0, arg1, arg2, args, kwarg1, kwarg2, kwargs)

   example_function1('arg0', 'arg1', kwarg1='kwarg1', kwarg3='kwarg3')

This code during execution will produce log records:

::

    Calling:
    'example_function1'(
        # POSITIONAL_ONLY:
        arg0='arg0',  # type: str
        # POSITIONAL_OR_KEYWORD:
        arg1='arg1',  # type: str
        arg2='arg2',  # type: str
        # VAR_POSITIONAL:
        args=(),
        # KEYWORD_ONLY:
        kwarg1='kwarg1',  # type: str
        kwarg2='kwarg2',  # type: str
        # VAR_KEYWORD:
        kwargs={
            'kwarg3': 'kwarg3',
        },
    )
    Done: 'example_function1' with result:

     (
        'arg0',
        'arg1',
        'arg2',
        (),
        'kwarg1',
        'kwarg2',
        {
            'kwarg3': 'kwarg3',
        },
     )

LogWrap
-------
Example construction and read from test:

.. code-block:: python

    log_call = logwrap.LogWrap()
    log_call.log_level == logging.DEBUG
    log_call.exc_level == logging.ERROR
    log_call.max_indent == 20
    log_call.blacklisted_names == []
    log_call.blacklisted_exceptions == []
    log_call.log_call_args == True
    log_call.log_call_args_on_exc == True
    log_call.log_traceback == True
    log_call.log_result_obj == True

On object change, variable types is validated.

In special cases, when special processing required for parameters logging (hide or change parameters in log),
it can be done by override `pre_process_param` and `post_process_param`.

See API documentation for details.


pretty_repr
-----------
This is specified helper for making human-readable repr on complex objects.
Signature is self-documenting:

.. code-block:: python

    def pretty_repr(
        src,  # object for repr
        indent=0,  # start indent
        no_indent_start=False,  # do not indent the first level
        max_indent=20,  # maximum allowed indent level
        indent_step=4,  # step between indents
    )


pretty_str
----------
This is specified helper for making human-readable str on complex objects.
Signature is self-documenting:

.. code-block:: python

    def pretty_str(
        src,  # object for __str__
        indent=0,  # start indent
        no_indent_start=False,  # do not indent the first level
        max_indent=20,  # maximum allowed indent level
        indent_step=4,  # step between indents
    )

Limitations:
    Dict like objects is always marked inside `{}` for readability, even if it is `collections.OrderedDict` (standard repr as list of tuples).

    Iterable types is not declared, only brackets is used.

    String and bytes looks the same (its __str__, not __repr__).

PrettyFormat
------------
PrettyFormat is the main formatting implementation class.
`pretty_repr` and `pretty_str` uses instances of subclasses `PrettyRepr` and `PrettyStr` from this class.
This class is mostly exposed for typing reasons.
Object signature:

.. code-block:: python

    def __init__(
        self,
        max_indent=20,  # maximum allowed indent level
        indent_step=4,  # step between indents
    )

Callable object (`PrettyFormat` instance) signature:

.. code-block:: python

    def __call__(
        self,
        src,  # object for repr
        indent=0,  # start indent
        no_indent_start=False  # do not indent the first level
    )

Adopting your code
------------------
pretty_repr behavior could be overridden for your classes by implementing specific magic method:

.. code-block:: python

    def __pretty_repr__(
        self,
        parser  # PrettyFormat class instance,
        indent  # start indent,
        no_indent_start  # do not indent the first level
    ):
        return ...

This method will be executed instead of __repr__ on your object.

.. code-block:: python

    def __pretty_str__(
        self,
        parser  # PrettyFormat class instance,
        indent  # start indent,
        no_indent_start  # do not indent the first level
    ):
        return ...

This method will be executed instead of __str__ on your object.

LogOnAccess
-----------

This special case of property is useful in cases, where a lot of properties should be logged by similar way without writing a lot of code.

Basic API is conform with `property`, but in addition it is possible to customize logger, log levels and log conditions.

Usage examples:

1. Simple usage. All by default.
   logger is re-used:

    * from instance if available with names `logger` or `log`,
    * from instance module if available with names `LOGGER`, `log`,
    * else used internal `logwrap.log_on_access` logger.

  .. code-block:: python

    import logging

    class Target(object):

        def init(self, val='ok')
            self.val = val
            self.logger = logging.get_logger(self.__class__.__name__)  # Single for class, follow subclassing

        def __repr__(self):
            return "{cls}(val={self.val})".format(cls=self.__class__.__name__, self=self)

        @logwrap.LogOnAccess
        def ok(self):
            return self.val

        @ok.setter
        def ok(self, val):
            self.val = val

        @ok.deleter
        def ok(self):
            self.val = ""

2. Use with global logger for class:

  .. code-block:: python

    class Target(object):

      def init(self, val='ok')
          self.val = val

      def __repr__(self):
          return "{cls}(val={self.val})".format(cls=self.__class__.__name__, self=self)

      @logwrap.LogOnAccess
      def ok(self):
          return self.val

      @ok.setter
      def ok(self, val):
          self.val = val

      @ok.deleter
      def ok(self):
          self.val = ""

      ok.logger = 'test_logger'
      ok.log_level = logging.INFO
      ok.exc_level = logging.ERROR
      ok.log_object_repr = True  # As by default
      ok.log_before = True  # As by default
      ok.log_success = True  # As by default
      ok.log_failure = True  # As by default
      ok.log_traceback = True  # As by default
      ok.override_name = None  # As by default: use original name

Testing
=======
The main test mechanism for the package `logwrap` is using `tox`.
Available environments can be collected via `tox -l`

CI/CD systems
=============

`GitHub: <https://github.com/python-useful-helpers/logwrap/actions>`_ is used for functional tests.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/python-useful-helpers/logwrap",
    "name": "logwrap",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8.0",
    "maintainer_email": "Aleksei Stepanov <penguinolog@gmail.com>, Antonio Esposito <esposito.cloud@gmail.com>, Dennis Dmitriev <dis-xcom@gmail.com>",
    "keywords": "logging,debugging,development",
    "author": "",
    "author_email": "Aleksei Stepanov <penguinolog@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/86/c3/045cf85927e336b7ac487db8ab3367551034f5ff413a844dd126baa72279/logwrap-11.0.0.tar.gz",
    "platform": null,
    "description": "logwrap\n=======\n\n.. image:: https://github.com/python-useful-helpers/logwrap/workflows/Python%20package/badge.svg\n    :target: https://github.com/python-useful-helpers/logwrap/actions\n.. image:: https://coveralls.io/repos/github/python-useful-helpers/logwrap/badge.svg?branch=master\n    :target: https://coveralls.io/github/python-useful-helpers/logwrap?branch=master\n.. image:: https://readthedocs.org/projects/logwrap/badge/?version=latest\n    :target: http://logwrap.readthedocs.io/\n    :alt: Documentation Status\n.. image:: https://img.shields.io/pypi/v/logwrap.svg\n    :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/pypi/pyversions/logwrap.svg\n    :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/pypi/status/logwrap.svg\n    :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/github/license/python-useful-helpers/logwrap.svg\n    :target: https://raw.githubusercontent.com/python-useful-helpers/logwrap/master/LICENSE\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n    :target: https://github.com/ambv/black\n\n\nlogwrap is a helper for logging in human-readable format function arguments and call result on function call.\nWhy? Because logging of `*args, **kwargs` become useless with project grow and you need more details in call log.\n\nCons:\n\n* Log records are not single line.\n\nPros:\n\n* Log records are not single 100500 symbols length line.\n  (Especially actual for testing/development environments and for Kibana users).\n* Service free: job is done by this library and it's dependencies. It works at virtualenv\n* Free software: Apache license\n* Open Source: https://github.com/python-useful-helpers/logwrap\n* PyPI packaged: https://pypi.python.org/pypi/logwrap\n* Self-documented code: docstrings with types in comments\n* Tested: see bages on top\n* Support multiple Python versions:\n\n::\n\n    Python 3.8\n    Python 3.9\n    Python 3.10\n\n.. note::\n\n    Support python versions:\n\n    * 2.7: < 5.0.0\n    * 3.5: < 6.0.0\n    * 3.7: < 10.0.0\n\nThis package includes helpers:\n\n* `logwrap` - main helper. The same is `LogWrap`.\n\n* `LogWrap` - class with `logwrap` implementation. May be used directly.\n\n* `pretty_repr`\n\n* `pretty_str`\n\n* `PrettyFormat`\n\n* `LogOnAccess` - property with logging on successful get/set/delete or failure.\n\nUsage\n=====\n\nlogwrap\n-------\nThe main decorator. Could be used as not argumented (`@logwrap.logwrap`) and argumented (`@logwrap.logwrap()`).\nNot argumented usage simple calls with default values for all positions.\n\n.. note:: Argumens should be set via keywords only.\n\nArgumented usage with arguments from signature:\n\n.. code-block:: python\n\n    @logwrap.logwrap(\n        log=None,  # if not set: try to find LOGGER, LOG, logger or log object in target module and use it if it logger instance. Fallback: logger named logwrap\n        log_level=logging.DEBUG,\n        exc_level=logging.ERROR,\n        max_indent=20,  # forwarded to the pretty_repr\n        blacklisted_names=None,  # list argument names, which should be dropped from log\n        blacklisted_exceptions=None,  # Exceptions to skip details in log (no traceback, no exception details - just class name)\n        log_call_args=True,  # Log call arguments before call\n        log_call_args_on_exc=True,  # Log call arguments if exception happens\n        log_traceback = True,  # Log traceback if exception happens\n        log_result_obj=True,  # Log result object\n    )\n\nUsage examples:\n\n.. code-block:: python\n\n    @logwrap.logwrap()\n    def foo():\n        pass\n\nis equal to:\n\n.. code-block:: python\n\n    @logwrap.logwrap\n    def foo():\n        pass\n\nGet decorator for use without parameters:\n\n.. code-block:: python\n\n    get_logs = logwrap.logwrap()  # set required parameters via arguments\n\n    type(get_logs) == LogWrap  # All logic is implemented in LogWrap class starting from version 2.2.0\n\n    @get_logs\n    def foo():\n        pass\n\nCall example (python 3.8):\n\n.. code-block:: python\n\n   import logwrap\n\n   @logwrap.logwrap\n   def example_function1(\n           arg0: str,\n           /,\n           arg1: str,\n           arg2: str='arg2',\n           *args,\n           kwarg1: str,\n           kwarg2: str='kwarg2',\n           **kwargs\n   ) -> tuple():\n       return (arg0, arg1, arg2, args, kwarg1, kwarg2, kwargs)\n\n   example_function1('arg0', 'arg1', kwarg1='kwarg1', kwarg3='kwarg3')\n\nThis code during execution will produce log records:\n\n::\n\n    Calling:\n    'example_function1'(\n        # POSITIONAL_ONLY:\n        arg0='arg0',  # type: str\n        # POSITIONAL_OR_KEYWORD:\n        arg1='arg1',  # type: str\n        arg2='arg2',  # type: str\n        # VAR_POSITIONAL:\n        args=(),\n        # KEYWORD_ONLY:\n        kwarg1='kwarg1',  # type: str\n        kwarg2='kwarg2',  # type: str\n        # VAR_KEYWORD:\n        kwargs={\n            'kwarg3': 'kwarg3',\n        },\n    )\n    Done: 'example_function1' with result:\n\n     (\n        'arg0',\n        'arg1',\n        'arg2',\n        (),\n        'kwarg1',\n        'kwarg2',\n        {\n            'kwarg3': 'kwarg3',\n        },\n     )\n\nLogWrap\n-------\nExample construction and read from test:\n\n.. code-block:: python\n\n    log_call = logwrap.LogWrap()\n    log_call.log_level == logging.DEBUG\n    log_call.exc_level == logging.ERROR\n    log_call.max_indent == 20\n    log_call.blacklisted_names == []\n    log_call.blacklisted_exceptions == []\n    log_call.log_call_args == True\n    log_call.log_call_args_on_exc == True\n    log_call.log_traceback == True\n    log_call.log_result_obj == True\n\nOn object change, variable types is validated.\n\nIn special cases, when special processing required for parameters logging (hide or change parameters in log),\nit can be done by override `pre_process_param` and `post_process_param`.\n\nSee API documentation for details.\n\n\npretty_repr\n-----------\nThis is specified helper for making human-readable repr on complex objects.\nSignature is self-documenting:\n\n.. code-block:: python\n\n    def pretty_repr(\n        src,  # object for repr\n        indent=0,  # start indent\n        no_indent_start=False,  # do not indent the first level\n        max_indent=20,  # maximum allowed indent level\n        indent_step=4,  # step between indents\n    )\n\n\npretty_str\n----------\nThis is specified helper for making human-readable str on complex objects.\nSignature is self-documenting:\n\n.. code-block:: python\n\n    def pretty_str(\n        src,  # object for __str__\n        indent=0,  # start indent\n        no_indent_start=False,  # do not indent the first level\n        max_indent=20,  # maximum allowed indent level\n        indent_step=4,  # step between indents\n    )\n\nLimitations:\n    Dict like objects is always marked inside `{}` for readability, even if it is `collections.OrderedDict` (standard repr as list of tuples).\n\n    Iterable types is not declared, only brackets is used.\n\n    String and bytes looks the same (its __str__, not __repr__).\n\nPrettyFormat\n------------\nPrettyFormat is the main formatting implementation class.\n`pretty_repr` and `pretty_str` uses instances of subclasses `PrettyRepr` and `PrettyStr` from this class.\nThis class is mostly exposed for typing reasons.\nObject signature:\n\n.. code-block:: python\n\n    def __init__(\n        self,\n        max_indent=20,  # maximum allowed indent level\n        indent_step=4,  # step between indents\n    )\n\nCallable object (`PrettyFormat` instance) signature:\n\n.. code-block:: python\n\n    def __call__(\n        self,\n        src,  # object for repr\n        indent=0,  # start indent\n        no_indent_start=False  # do not indent the first level\n    )\n\nAdopting your code\n------------------\npretty_repr behavior could be overridden for your classes by implementing specific magic method:\n\n.. code-block:: python\n\n    def __pretty_repr__(\n        self,\n        parser  # PrettyFormat class instance,\n        indent  # start indent,\n        no_indent_start  # do not indent the first level\n    ):\n        return ...\n\nThis method will be executed instead of __repr__ on your object.\n\n.. code-block:: python\n\n    def __pretty_str__(\n        self,\n        parser  # PrettyFormat class instance,\n        indent  # start indent,\n        no_indent_start  # do not indent the first level\n    ):\n        return ...\n\nThis method will be executed instead of __str__ on your object.\n\nLogOnAccess\n-----------\n\nThis special case of property is useful in cases, where a lot of properties should be logged by similar way without writing a lot of code.\n\nBasic API is conform with `property`, but in addition it is possible to customize logger, log levels and log conditions.\n\nUsage examples:\n\n1. Simple usage. All by default.\n   logger is re-used:\n\n    * from instance if available with names `logger` or `log`,\n    * from instance module if available with names `LOGGER`, `log`,\n    * else used internal `logwrap.log_on_access` logger.\n\n  .. code-block:: python\n\n    import logging\n\n    class Target(object):\n\n        def init(self, val='ok')\n            self.val = val\n            self.logger = logging.get_logger(self.__class__.__name__)  # Single for class, follow subclassing\n\n        def __repr__(self):\n            return \"{cls}(val={self.val})\".format(cls=self.__class__.__name__, self=self)\n\n        @logwrap.LogOnAccess\n        def ok(self):\n            return self.val\n\n        @ok.setter\n        def ok(self, val):\n            self.val = val\n\n        @ok.deleter\n        def ok(self):\n            self.val = \"\"\n\n2. Use with global logger for class:\n\n  .. code-block:: python\n\n    class Target(object):\n\n      def init(self, val='ok')\n          self.val = val\n\n      def __repr__(self):\n          return \"{cls}(val={self.val})\".format(cls=self.__class__.__name__, self=self)\n\n      @logwrap.LogOnAccess\n      def ok(self):\n          return self.val\n\n      @ok.setter\n      def ok(self, val):\n          self.val = val\n\n      @ok.deleter\n      def ok(self):\n          self.val = \"\"\n\n      ok.logger = 'test_logger'\n      ok.log_level = logging.INFO\n      ok.exc_level = logging.ERROR\n      ok.log_object_repr = True  # As by default\n      ok.log_before = True  # As by default\n      ok.log_success = True  # As by default\n      ok.log_failure = True  # As by default\n      ok.log_traceback = True  # As by default\n      ok.override_name = None  # As by default: use original name\n\nTesting\n=======\nThe main test mechanism for the package `logwrap` is using `tox`.\nAvailable environments can be collected via `tox -l`\n\nCI/CD systems\n=============\n\n`GitHub: <https://github.com/python-useful-helpers/logwrap/actions>`_ is used for functional tests.\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "Decorator for logging function arguments and return value by human-readable way",
    "version": "11.0.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/python-useful-helpers/logwrap/issues",
        "Documentation": "https://logwrap.readthedocs.io/",
        "Homepage": "https://github.com/python-useful-helpers/logwrap",
        "Repository": "https://github.com/python-useful-helpers/logwrap"
    },
    "split_keywords": [
        "logging",
        "debugging",
        "development"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "50762c03ba65ed67ad8a1e795a5f24df1ce6c0900a04434cb1d0adcc3e63c0c6",
                "md5": "27912b8597c6f2044e7dde3a73082d54",
                "sha256": "0c72afce5e9be529165bb615313461e9b8fd104e3c3f62d9d255caf7e0b21ecd"
            },
            "downloads": -1,
            "filename": "logwrap-11.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "27912b8597c6f2044e7dde3a73082d54",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8.0",
            "size": 27117,
            "upload_time": "2023-06-20T07:44:06",
            "upload_time_iso_8601": "2023-06-20T07:44:06.413725Z",
            "url": "https://files.pythonhosted.org/packages/50/76/2c03ba65ed67ad8a1e795a5f24df1ce6c0900a04434cb1d0adcc3e63c0c6/logwrap-11.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "86c3045cf85927e336b7ac487db8ab3367551034f5ff413a844dd126baa72279",
                "md5": "7908c40afb37f06970d9f758f0ca9220",
                "sha256": "cbf682e6d29ca3bc626047cf4a1262adbbcb1bbc53a8479d7d23dee32eb1b9df"
            },
            "downloads": -1,
            "filename": "logwrap-11.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7908c40afb37f06970d9f758f0ca9220",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8.0",
            "size": 41266,
            "upload_time": "2023-06-20T07:44:08",
            "upload_time_iso_8601": "2023-06-20T07:44:08.596165Z",
            "url": "https://files.pythonhosted.org/packages/86/c3/045cf85927e336b7ac487db8ab3367551034f5ff413a844dd126baa72279/logwrap-11.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-20 07:44:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "python-useful-helpers",
    "github_project": "logwrap",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "tox": true,
    "lcname": "logwrap"
}
        
Elapsed time: 0.07825s