[
  {
    "path": ".codecov.yml",
    "content": "comment: false\n\ncoverage:\n  status:\n    patch: no\n    changes: no\n    project:\n      default:\n        target: 100%\n        threshold:\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nAs an open source product, only  the latest major version will be patched for security vulnerabilities. Previous versions of Loguru will likely not be retroactively patched.\n\n## Reporting a Vulnerability\n\nTo report a security issue, please email delgan.py@gmail.com with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue.\n\nOnce the vulnerability has been confirmed, it will be fixed as soon as possible if feasible. Alternatively, mitigating solutions will be detailed in the documentation: [Security considerations when using Loguru](https://loguru.readthedocs.io/en/stable/resources/recipes.html#security-considerations-when-using-loguru).\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: /\n  schedule:\n    interval: quarterly\n  labels: []\n- package-ecosystem: pip\n  directory: /\n  schedule:\n    interval: quarterly\n  allow:\n  - dependency-type: direct\n  labels: []\n  ignore:\n  - dependency-name: '*'\n    update-types: [version-update:semver-patch]\n  - dependency-name: pytest-cov  # Latest version needs a new \"subprocess\" configuration.\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: CodeQL\n\non:\n  push:\n  pull_request:\n  schedule:\n  - cron: 0 0 * * 0\n\njobs:\n  analyze:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository\n    runs-on: ubuntu-22.04\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [python]\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v3\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Docs\n\non: [push, pull_request]\n\njobs:\n  docs:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository\n    runs-on: ubuntu-22.04\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.11'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install tox\n    - name: Build documentation\n      run: |\n        tox -e docs\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non: [push, pull_request]\n\njobs:\n  lint:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository\n    runs-on: ubuntu-22.04\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.11'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install tox\n    - name: Run linters\n      run: |\n        tox -e lint\n"
  },
  {
    "path": ".github/workflows/packaging.yml",
    "content": "name: Packaging\n\non: [push, pull_request]\n\njobs:\n  build:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository\n    runs-on: ubuntu-22.04\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.11'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install tox\n    - name: Build package\n      run: |\n        tox -e build\n    - name: Upload package\n      uses: actions/upload-artifact@v4\n      with:\n        name: python-package-distributions\n        path: dist/\n  publish:\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-22.04\n    needs: build\n    environment:\n      name: pypi\n      url: https://pypi.org/project/loguru/\n    permissions:\n      id-token: write\n    steps:\n    - name: Download package\n      uses: actions/download-artifact@v5\n      with:\n        name: python-package-distributions\n        path: dist/\n    - name: Publish package\n      uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n  schedule:\n  - cron: 0 0 * * 0\n\njobs:\n  tests:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n        - ubuntu-22.04\n        python-version:\n        - '3.7'\n        - '3.8'\n        - '3.9'\n        - '3.10'\n        - '3.11'\n        - '3.12'\n        - '3.13'\n        - 3.14-dev\n        - pypy-3.11\n        allow-failure:\n        - false\n        include:\n        - os: ubuntu-22.04\n          python-version: '3.5'\n          allow-failure: false\n          container: python:3.5-slim\n        - os: ubuntu-22.04\n          python-version: '3.6'\n          allow-failure: false\n          container: python:3.6-slim\n        - os: windows-2022\n          python-version: '3.12'\n          allow-failure: false\n        - os: macos-15\n          python-version: '3.12'\n          allow-failure: false\n    runs-on: ${{ matrix.os }}\n    # Some versions of Python are not longer supported by GitHub Actions.\n    # For those, we use a container image and skip the setup-python step.\n    # It will be empty and ignored for the other matrix entries.\n    container: ${{ matrix.container }}\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n    - name: Set up Python\n      if: ${{ matrix.container == null }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install tox\n    - name: Run tests\n      run: |\n        tox -e tests\n      continue-on-error: ${{ matrix.allow-failure }}\n    - name: Upload coverage data\n      uses: actions/upload-artifact@v4\n      with:\n        name: coverage-${{ matrix.python-version }}-${{ matrix.os }}\n        path: coverage.xml\n      continue-on-error: ${{ matrix.allow-failure }}\n  push-coverage-to-codecov:\n    runs-on: ubuntu-22.04\n    needs: tests\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v5\n    - name: Download artifacts\n      uses: actions/download-artifact@v5\n    - name: Upload to Codecov\n      uses: codecov/codecov-action@v5\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n        verbose: true\n        fail_ci_if_error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# dotenv\n.env\n\n# virtualenv\n.venv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# Idea IDE\n.idea/\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: v6.0.0\n  hooks:\n  - id: end-of-file-fixer\n  - id: fix-byte-order-marker\n  - id: trailing-whitespace\n  - id: check-added-large-files\n  - id: mixed-line-ending\n    args: [--fix=lf]\n- repo: https://github.com/crate-ci/typos\n  rev: v1.35.3\n  hooks:\n  - id: typos\n- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks\n  rev: v2.15.0\n  hooks:\n  - id: pretty-format-ini\n    args: [--autofix]\n  - id: pretty-format-yaml\n    args: [--autofix, --indent, '2']\n- repo: https://github.com/ComPWA/taplo-pre-commit\n  rev: v0.9.3\n  hooks:\n  - id: taplo-format\n- repo: https://github.com/ambv/black\n  rev: 25.1.0\n  hooks:\n  - id: black\n- repo: https://github.com/astral-sh/ruff-pre-commit\n  rev: v0.12.8\n  hooks:\n  - id: ruff-check\n    args: [--fix, --exit-non-zero-on-fix]\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "version: 2\n\nbuild:\n  os: ubuntu-20.04\n  tools:\n    python: '3.11'\n\npython:\n  install:\n  - method: pip\n    path: .\n    extra_requirements:\n    - dev\n\nsphinx:\n  builder: html\n  configuration: docs/conf.py\n\nformats:\n- htmlzip\n- pdf\n- epub\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"python.testing.pytestArgs\": [\n        \"tests\"\n    ],\n    \"python.testing.unittestEnabled\": false,\n    \"python.testing.pytestEnabled\": true,\n    \"[python]\": {\n        \"editor.defaultFormatter\": \"ms-python.black-formatter\"\n    }\n}\n"
  },
  {
    "path": "CHANGELOG.rst",
    "content": "`Unreleased`_\n=============\n\n- Add new ``logger.reinstall()`` method to automatically set up the ``logger`` in spawned child processes (`#818 <https://github.com/Delgan/loguru/issues/818>`_, thanks `@monchin <https://github.com/monchin>`_).\n- Fix incorrect microsecond value when formatting the log timestamp using ``{time:x}`` (`#1440 <https://github.com/Delgan/loguru/issues/1440>`_).\n- Fix hex color short code expansion (`#1426 <https://github.com/Delgan/loguru/issues/1426>`_, thanks `@turkoid <https://github.com/turkoid>`_).\n- Fix possible internal error when dealing with (rare) frame objects missing a ``f_lineno`` value (`#1435 <https://github.com/Delgan/loguru/issues/1435>`).\n- Fix a regression preventing formatting of ``record[\"time\"]`` when using ``zoneinfo.ZoneInfo`` timezones (`#1260 <https://github.com/Delgan/loguru/pull/1260>`_, thanks `@bijlpieter <https://github.com/bijlpieter>`_).\n- Fix possible ``ValueError`` raised on Windows when system clock was set far ahead in the future (`#1291 <https://github.com/Delgan/loguru/issues/1291>`_).\n- Respect the ``.level`` attribute of standard logging ``Handler`` used as sink, which was previously ignored (`#1409 <https://github.com/Delgan/loguru/issues/1409>`_).\n- Allow the ``rotation`` argument of file sinks to accept a list of rotation conditions, any of which can trigger the rotation (`#1174 <https://github.com/Delgan/loguru/issues/1174>`_, thanks `@CollinHeist <https://github.com/CollinHeist>`_).\n- Update the default log format to include the timezone offset since it produces less ambiguous logs (`#856 <https://github.com/Delgan/loguru/pull/856>`_, thanks `@tim-x-y-z <https://github.com/tim-x-y-z>`_).\n- Honor the ``NO_COLOR`` environment variable which disables color output by default if ``colorize`` is not provided (`#1178 <https://github.com/Delgan/loguru/issues/1178>`_).\n- Honor the ``FORCE_COLOR`` environment variable which enables color output by default if ``colorize`` is not provided (`#1305 <https://github.com/Delgan/loguru/issues/1305>`_, thanks `@nhurden <https://github.com/nhurden>`_).\n- Add requirement for ``TERM`` environment variable not to be ``\"dumb\"`` to enable colorization (`#1287 <https://github.com/Delgan/loguru/pull/1287>`_, thanks `@snosov1 <https://github.com/snosov1>`_).\n- Make ``logger.catch()`` usable as an asynchronous context manager (`#1084 <https://github.com/Delgan/loguru/issues/1084>`_).\n- Make ``logger.catch()`` compatible with asynchronous generators (`#1302 <https://github.com/Delgan/loguru/issues/1302>`_).\n\n`0.7.3`_ (2024-12-06)\n=====================\n\n- Fix Cython incompatibility caused by the absence of underlying stack frames, which resulted in a ``ValueError`` during logging (`#88 <https://github.com/Delgan/loguru/issues/88>`_).\n- Fix possible ``RuntimeError`` when removing all handlers with ``logger.remove()`` due to thread-safety issue (`#1183 <https://github.com/Delgan/loguru/issues/1183>`_, thanks `@jeremyk <https://github.com/jeremyk>`_).\n- Fix ``diagnose=True`` option of exception formatting not working as expected with Python 3.13 (`#1235 <https://github.com/Delgan/loguru/issues/1235>`_, thanks `@etianen <https://github.com/etianen>`_).\n- Fix non-standard level names not fully compatible with ``logging.Formatter()`` (`#1231 <https://github.com/Delgan/loguru/issues/1231>`_, thanks `@yechielb2000 <https://github.com/yechielb2000>`_).\n- Fix inability to display a literal ``\"\\\"`` immediately before color markups (`#988 <https://github.com/Delgan/loguru/issues/988>`_).\n- Fix possible infinite recursion when an exception is raised from a ``__repr__``  method decorated with ``logger.catch()`` (`#1044 <https://github.com/Delgan/loguru/issues/1044>`_).\n- Improve performance of ``datetime`` formatting while logging messages (`#1201 <https://github.com/Delgan/loguru/issues/1201>`_, thanks `@trim21 <https://github.com/trim21>`_).\n- Reduce startup time in the presence of installed but unused ``IPython`` third-party library (`#1001 <https://github.com/Delgan/loguru/issues/1001>`_, thanks `@zakstucke <https://github.com/zakstucke>`_).\n\n\n`0.7.2`_ (2023-09-11)\n=====================\n\n- Add support for formatting of ``ExceptionGroup`` errors (`#805 <https://github.com/Delgan/loguru/issues/805>`_).\n- Fix possible ``RuntimeError`` when using ``multiprocessing.set_start_method()`` after importing the ``logger`` (`#974 <https://github.com/Delgan/loguru/issues/974>`_).\n- Fix formatting of possible ``__notes__`` attached to an ``Exception`` (`#980 <https://github.com/Delgan/loguru/issues/980>`_).\n\n\n`0.7.1`_ (2023-09-04)\n=====================\n\n- Add a new ``context`` optional argument to ``logger.add()`` specifying ``multiprocessing`` context (like ``\"spawn\"`` or ``\"fork\"``) to be used internally instead of the default one (`#851 <https://github.com/Delgan/loguru/issues/851>`_).\n- Add support for true colors on Windows using ANSI/VT console when available (`#934 <https://github.com/Delgan/loguru/issues/934>`_, thanks `@tunaflsh <https://github.com/tunaflsh>`_).\n- Fix possible deadlock when calling ``logger.complete()`` with concurrent logging of an asynchronous sink (`#906 <https://github.com/Delgan/loguru/issues/906>`_).\n- Fix file possibly rotating too early or too late when re-starting an application around midnight (`#894 <https://github.com/Delgan/loguru/issues/894>`_).\n- Fix inverted ``\"<hide>\"`` and ``\"<strike>\"`` color tags (`#943 <https://github.com/Delgan/loguru/pull/943>`_, thanks `@tunaflsh <https://github.com/tunaflsh>`_).\n- Fix possible untraceable errors raised when logging non-unpicklable ``Exception`` instances while using ``enqueue=True`` (`#329 <https://github.com/Delgan/loguru/issues/329>`_).\n- Fix possible errors raised when logging non-picklable ``Exception`` instances while using ``enqueue=True`` (`#342 <https://github.com/Delgan/loguru/issues/342>`_, thanks `@ncoudene <https://github.com/ncoudene>`_).\n- Fix missing seconds and microseconds when formatting timezone offset that requires such accuracy (`#961 <https://github.com/Delgan/loguru/issues/961>`_).\n- Raise ``ValueError`` if an attempt to use nanosecond precision for time formatting is detected (`#855 <https://github.com/Delgan/loguru/issues/855>`_).\n\n\n`0.7.0`_ (2023-04-10)\n=====================\n\n- Update ``InterceptHandler`` recipe to make it compatible with Python 3.11 (`#654 <https://github.com/Delgan/loguru/issues/654>`_).\n- Add a new ``watch`` optional argument to file sinks in order to automatically re-create possibly deleted or changed file (`#471 <https://github.com/Delgan/loguru/issues/471>`_).\n- Make ``patch()`` calls cumulative instead of overriding the possibly existing patching function (`#462 <https://github.com/Delgan/loguru/issues/462>`_).\n- Make sinks added with ``enqueue=True`` and ``catch=False`` still process logged messages in case of internal exception (`#833 <https://github.com/Delgan/loguru/issues/833>`_).\n- Avoid possible deadlocks caused by re-using the logger inside a sink, a signal handler or a ``__del__`` method. Since the logger is not re-entrant, such misuse will be detected and will now generate a ``RuntimeError`` (`#712 <https://github.com/Delgan/loguru/issues/712>`_, thanks `@jacksmith15 <https://github.com/jacksmith15>`_).\n- Fix file sink rotation using an aware ``datetime.time`` for which the timezone was ignored (`#697 <https://github.com/Delgan/loguru/issues/697>`_).\n- Fix logs colorization not automatically enabled for Jupyter Notebook and Google Colab (`#494 <https://github.com/Delgan/loguru/issues/494>`_).\n- Fix logs colorization not automatically enabled for Github Actions and others CI platforms (`#604 <https://github.com/Delgan/loguru/issues/604>`_).\n- Fix ``logger.complete()`` possibly hanging forever when ``enqueue=True`` and ``catch=False`` if internal thread killed due to ``Exception`` raised by sink (`#647 <https://github.com/Delgan/loguru/issues/647>`_).\n- Fix incompatibility with ``freezegun`` library used to simulate time (`#600 <https://github.com/Delgan/loguru/issues/600>`_).\n- Raise exception if ``logger.catch()`` is used to wrap a class instead of a function to avoid unexpected behavior (`#623 <https://github.com/Delgan/loguru/issues/623>`_).\n\n\n`0.6.0`_ (2022-01-29)\n=====================\n\n- Remove internal use of ``pickle.loads()`` to fix the (finally rejected) security vulnerability referenced as `CVE-2022-0329 <https://nvd.nist.gov/vuln/detail/CVE-2022-0329>`_ (`#563 <https://github.com/Delgan/loguru/issues/563>`_).\n- Modify coroutine sink to make it discard log messages when ``loop=None`` and no event loop is running (due to internally using ``asyncio.get_running_loop()`` in place of ``asyncio.get_event_loop()``).\n- Remove the possibility to add a coroutine sink with ``enqueue=True`` if ``loop=None`` and no event loop is running.\n- Change default encoding of file sink to be ``utf8`` instead of ``locale.getpreferredencoding()`` (`#339 <https://github.com/Delgan/loguru/issues/339>`_).\n- Prevent non-ascii characters to be escaped while logging JSON message with ``serialize=True`` (`#575 <https://github.com/Delgan/loguru/pull/575>`_, thanks `@ponponon <https://github.com/ponponon>`_).\n- Fix ``flake8`` errors and improve code readability (`#353 <https://github.com/Delgan/loguru/issues/353>`_, thanks `@AndrewYakimets <https://github.com/AndrewYakimets>`_).\n\n\n`0.5.3`_ (2020-09-20)\n=====================\n\n- Fix child process possibly hanging at exit while combining ``enqueue=True`` with third party library like ``uwsgi`` (`#309 <https://github.com/Delgan/loguru/issues/309>`_, thanks `@dstlmrk <https://github.com/dstlmrk>`_).\n- Fix possible exception during formatting of non-string messages (`#331 <https://github.com/Delgan/loguru/issues/331>`_).\n\n\n`0.5.2`_ (2020-09-06)\n=====================\n\n- Fix ``AttributeError`` within handlers using ``serialize=True`` when calling ``logger.exception()`` outside of the context of an exception (`#296 <https://github.com/Delgan/loguru/issues/296>`_).\n- Fix error while logging an exception containing a non-picklable ``value`` to a handler with ``enqueue=True`` (`#298 <https://github.com/Delgan/loguru/issues/298>`_).\n- Add support for async callable classes (with ``__call__`` method) used as sinks (`#294 <https://github.com/Delgan/loguru/pull/294>`_, thanks `@jessekrubin <https://github.com/jessekrubin>`_).\n\n\n`0.5.1`_ (2020-06-12)\n=====================\n\n- Modify the way the ``extra`` dict is used by ``LogRecord`` in order to prevent possible ``KeyError`` with standard ``logging`` handlers (`#271 <https://github.com/Delgan/loguru/issues/271>`_).\n- Add a new ``default`` optional argument to ``logger.catch()``, it should be the returned value by the decorated function in case an error occurred (`#272 <https://github.com/Delgan/loguru/issues/272>`_).\n- Fix ``ValueError`` when using ``serialize=True`` in combination with ``logger.catch()`` or ``logger.opt(record=True)`` due to circular reference of the ``record`` dict (`#286 <https://github.com/Delgan/loguru/issues/286>`_).\n\n\n`0.5.0`_ (2020-05-17)\n=====================\n\n- Remove the possibility to modify the severity ``no`` of levels once they have been added in order to prevent surprising behavior (`#209 <https://github.com/Delgan/loguru/issues/209>`_).\n- Add better support for \"structured logging\" by automatically adding ``**kwargs`` to the ``extra`` dict besides using these arguments to format the message. This behavior can be disabled by setting the new ``.opt(capture=False)`` parameter (`#2 <https://github.com/Delgan/loguru/issues/2>`_).\n- Add a new ``onerror`` optional argument to ``logger.catch()``, it should be a function which will be called when an exception occurs in order to customize error handling (`#224 <https://github.com/Delgan/loguru/issues/224>`_).\n- Add a new ``exclude`` optional argument to ``logger.catch()``, is should be a type of exception to be purposefully ignored and propagated to the caller without being logged (`#248 <https://github.com/Delgan/loguru/issues/248>`_).\n- Modify ``complete()`` to make it callable from non-asynchronous functions, it can thus be used if ``enqueue=True`` to make sure all messages have been processed (`#231 <https://github.com/Delgan/loguru/issues/231>`_).\n- Fix possible deadlocks on Linux when ``multiprocessing.Process()`` collides with ``enqueue=True`` or ``threading`` (`#231 <https://github.com/Delgan/loguru/issues/231>`_).\n- Fix ``compression`` function not executable concurrently due to file renaming (to resolve conflicts) being performed after and not before it (`#243 <https://github.com/Delgan/loguru/issues/243>`_).\n- Fix the filter function listing files for ``retention`` being too restrictive, it now matches files based on the pattern ``\"root(.*).ext(.*)\"`` (`#229 <https://github.com/Delgan/loguru/issues/229>`_).\n- Fix the impossibility to ``remove()`` a handler if an exception is raised while the sink' ``stop()`` function is called (`#237 <https://github.com/Delgan/loguru/issues/237>`_).\n- Fix file sink left in an unstable state if an exception occurred during ``retention`` or ``compression`` process (`#238 <https://github.com/Delgan/loguru/issues/238>`_).\n- Fix situation where changes made to ``record[\"message\"]`` were unexpectedly ignored when ``opt(colors=True)``, causing \"out-of-date\" ``message`` to be logged due to implementation details (`#221 <https://github.com/Delgan/loguru/issues/221>`_).\n- Fix possible exception if a stream having an ``isatty()`` method returning ``True`` but not being compatible with ``colorama`` is used on Windows (`#249 <https://github.com/Delgan/loguru/issues/249>`_).\n- Fix exceptions occurring in coroutine sinks never retrieved and hence causing warnings (`#227 <https://github.com/Delgan/loguru/issues/227>`_).\n\n\n`0.4.1`_ (2020-01-19)\n=====================\n\n- Deprecate the ``ansi`` parameter of ``.opt()`` in favor of ``colors`` which is a name more appropriate.\n- Prevent unrelated files and directories to be incorrectly collected thus causing errors during the ``retention`` process (`#195 <https://github.com/Delgan/loguru/issues/195>`_, thanks `@gazpachoking <https://github.com/gazpachoking>`_).\n- Strip color markups contained in ``record[\"message\"]`` when logging with ``.opt(ansi=True)`` instead of leaving them as is (`#198 <https://github.com/Delgan/loguru/issues/198>`_).\n- Ignore color markups contained in ``*args`` and ``**kwargs`` when logging with ``.opt(ansi=True)``, leave them as is instead of trying to use them to colorize the message which could cause undesirable errors (`#197 <https://github.com/Delgan/loguru/issues/197>`_).\n\n\n`0.4.0`_ (2019-12-02)\n=====================\n\n- Add support for coroutine functions used as sinks and add the new ``logger.complete()`` asynchronous method to ``await`` them (`#171 <https://github.com/Delgan/loguru/issues/171>`_).\n- Add a way to filter logs using one level per module in the form of a ``dict`` passed to the ``filter`` argument (`#148 <https://github.com/Delgan/loguru/issues/148>`_).\n- Add type hints to annotate the public methods using a ``.pyi`` stub file (`#162 <https://github.com/Delgan/loguru/issues/162>`_).\n- Add support for ``copy.deepcopy()`` of the ``logger`` allowing multiple independent loggers with separate set of handlers (`#72 <https://github.com/Delgan/loguru/issues/72>`_).\n- Add the possibility to convert ``datetime`` to UTC before formatting (in logs and filenames) by adding ``\"!UTC\"`` at the end of the time format specifier (`#128 <https://github.com/Delgan/loguru/issues/128>`_).\n- Add the level ``name`` as the first argument of namedtuple returned by the ``.level()`` method.\n- Remove ``class`` objects from the list of supported sinks and restrict usage of ``**kwargs`` in ``.add()`` to file sink only. User is in charge of instantiating sink and wrapping additional keyword arguments if needed, before passing it to the ``.add()`` method.\n- Rename the ``logger.configure()`` keyword argument ``patch`` to ``patcher`` so it better matches the signature of ``logger.patch()``.\n- Fix incompatibility with ``multiprocessing`` on Windows by entirely refactoring the internal structure of the ``logger`` so it can be inherited by child processes along with added handlers (`#108 <https://github.com/Delgan/loguru/issues/108>`_).\n- Fix ``AttributeError`` while using a file sink on some distributions (like Alpine Linux) missing the ``os.getxattr`` and ``os.setxattr`` functions (`#158 <https://github.com/Delgan/loguru/pull/158>`_, thanks `@joshgordon <https://github.com/joshgordon>`_).\n- Fix values wrongly displayed for keyword arguments during exception formatting with ``diagnose=True`` (`#144 <https://github.com/Delgan/loguru/issues/144>`_).\n- Fix logging messages wrongly chopped off at the end while using standard ``logging.Handler`` sinks with ``.opt(raw=True)`` (`#136 <https://github.com/Delgan/loguru/issues/136>`_).\n- Fix potential errors during rotation if destination file exists due to large resolution clock on Windows (`#179 <https://github.com/Delgan/loguru/issues/179>`_).\n- Fix an error using a ``filter`` function \"by name\" while receiving a log with ``record[\"name\"]`` equals to ``None``.\n- Fix incorrect record displayed while handling errors (if ``catch=True``) occurring because of non-picklable objects (if ``enqueue=True``).\n- Prevent hypothetical ``ImportError`` if a Python installation is missing the built-in ``distutils`` module (`#118 <https://github.com/Delgan/loguru/issues/118>`_).\n- Raise ``TypeError`` instead of ``ValueError`` when a ``logger`` method is called with argument of invalid type.\n- Raise ``ValueError`` if the built-in ``format()`` and ``filter()`` functions are respectively used as ``format`` and ``filter`` arguments of the ``add()`` method. This helps the user to understand the problem, as such a mistake can quite easily occur (`#177 <https://github.com/Delgan/loguru/issues/177>`_).\n- Remove inheritance of some record dict attributes to ``str`` (for ``\"level\"``, ``\"file\"``, ``\"thread\"`` and ``\"process\"``).\n- Give a name to the worker thread used when ``enqueue=True`` (`#174 <https://github.com/Delgan/loguru/pull/174>`_, thanks `@t-mart <https://github.com/t-mart>`_).\n\n\n`0.3.2`_ (2019-07-21)\n=====================\n\n- Fix exception during import when executing Python with ``-s`` and ``-S`` flags causing ``site.USER_SITE`` to be missing (`#114 <https://github.com/Delgan/loguru/issues/114>`_).\n\n\n`0.3.1`_ (2019-07-13)\n=====================\n\n- Fix ``retention`` and ``rotation`` issues when file sink initialized with ``delay=True`` (`#113 <https://github.com/Delgan/loguru/issues/113>`_).\n- Fix ``\"sec\"`` no longer recognized as a valid duration unit for file ``rotation`` and ``retention`` arguments.\n- Ensure stack from the caller is displayed while formatting exception of a function decorated with ``@logger.catch`` when ``backtrace=False``.\n- Modify datetime used to automatically rename conflicting file when rotating (it happens if file already exists because ``\"{time}\"`` not presents in filename) so it's based on the file creation time rather than the current time.\n\n\n`0.3.0`_ (2019-06-29)\n=====================\n\n- Remove all dependencies previously needed by ``loguru`` (on Windows platform, it solely remains ``colorama`` and ``win32-setctime``).\n- Add a new ``logger.patch()`` method which can be used to modify the record dict on-the-fly before it's being sent to the handlers.\n- Modify behavior of sink option ``backtrace`` so it only extends the stacktrace upward, the display of variables values is now controlled with the new ``diagnose`` argument (`#49 <https://github.com/Delgan/loguru/issues/49>`_).\n- Change behavior of ``rotation`` option in file sinks: it is now based on the file creation time rather than the current time, note that proper support may differ depending on your platform (`#58 <https://github.com/Delgan/loguru/issues/58>`_).\n- Raise errors on unknowns color tags rather than silently ignoring them (`#57 <https://github.com/Delgan/loguru/issues/57>`_).\n- Add the possibility to auto-close color tags by using ``</>`` (e.g. ``<yellow>message</>``).\n- Add coloration of exception traceback even if ``diagnose`` and ``backtrace`` options are ``False``.\n- Add a way to limit the depth of formatted exceptions traceback by setting the conventional ``sys.tracebacklimit`` variable (`#77 <https://github.com/Delgan/loguru/issues/77>`_).\n- Add ``__repr__`` value to the ``logger`` for convenient debugging (`#84 <https://github.com/Delgan/loguru/issues/84>`_).\n- Remove colors tags mixing directives (e.g. ``<red,blue>``) for simplification.\n- Make the ``record[\"exception\"]`` attribute unpackable as a ``(type, value, traceback)`` tuple.\n- Fix error happening in some rare circumstances because ``frame.f_globals`` dict did not contain ``\"__name__\"`` key and hence prevented Loguru to retrieve the module's name. From now, ``record[\"name\"]`` will be equal to ``None`` in such case (`#62 <https://github.com/Delgan/loguru/issues/62>`_).\n- Fix logging methods not being serializable with ``pickle`` and hence raising exception while being passed to some ``multiprocessing`` functions (`#102 <https://github.com/Delgan/loguru/issues/102>`_).\n- Fix exception stack trace not colorizing source code lines on Windows.\n- Fix possible ``AttributeError`` while formatting exceptions within a ``celery`` task (`#52 <https://github.com/Delgan/loguru/issues/52>`_).\n- Fix ``logger.catch`` decorator not working with generator and coroutine functions (`#75 <https://github.com/Delgan/loguru/issues/75>`_).\n- Fix ``record[\"path\"]`` case being normalized for no necessary reason (`#85 <https://github.com/Delgan/loguru/issues/85>`_).\n- Fix some Windows terminal emulators (mintty) not correctly detected as supporting colors, causing ansi codes to be automatically stripped (`#104 <https://github.com/Delgan/loguru/issues/104>`_).\n- Fix handler added with ``enqueue=True`` stopping working if exception was raised in sink although ``catch=True``.\n- Fix thread-safety of ``enable()`` and ``disable()`` being called during logging.\n- Use Tox to run tests (`#41 <https://github.com/Delgan/loguru/issues/41>`_).\n\n\n`0.2.5`_ (2019-01-20)\n=====================\n\n- Modify behavior of sink option ``backtrace=False`` so it doesn't extend traceback upward automatically (`#30 <https://github.com/Delgan/loguru/issues/30>`_).\n- Fix import error on some platforms using Python 3.5 with limited ``localtime()`` support (`#33 <https://github.com/Delgan/loguru/issues/33>`_).\n- Fix incorrect time formatting of locale month using ``MMM`` and ``MMMM`` tokens (`#34 <https://github.com/Delgan/loguru/pull/34>`_, thanks `@nasyxx <https://github.com/nasyxx>`_).\n- Fix race condition permitting writing on a stopped handler.\n\n\n`0.2.4`_ (2018-12-26)\n=====================\n\n- Fix adding handler while logging which was not thread-safe (`#22 <https://github.com/Delgan/loguru/issues/22>`_).\n\n\n`0.2.3`_ (2018-12-16)\n=====================\n\n- Add support for PyPy.\n- Add support for Python 3.5.\n- Fix incompatibility with ``awscli`` by downgrading required ``colorama`` dependency version (`#12 <https://github.com/Delgan/loguru/issues/12>`_).\n\n\n`0.2.2`_ (2018-12-12)\n=====================\n\n- Deprecate ``logger.start()`` and ``logger.stop()`` methods in favor of ``logger.add()`` and ``logger.remove()`` (`#3 <https://github.com/Delgan/loguru/issues/3>`_).\n- Fix ignored formatting while using ``logging.Handler`` sinks (`#4 <https://github.com/Delgan/loguru/issues/4>`_).\n- Fix impossibility to set empty environment variable color on Windows (`#7 <https://github.com/Delgan/loguru/issues/7>`_).\n\n\n`0.2.1`_ (2018-12-08)\n=====================\n\n- Fix typo preventing README to be correctly displayed on PyPI.\n\n\n`0.2.0`_ (2018-12-08)\n=====================\n\n- Remove the ``parser`` and refactor it into the ``logger.parse()`` method.\n- Remove the ``notifier`` and its dependencies (``pip install notifiers`` should be used instead).\n\n\n`0.1.0`_ (2018-12-07)\n=====================\n\n- Add logger.\n- Add notifier.\n- Add parser.\n\n\n`0.0.1`_ (2017-09-04)\n=====================\n\nInitial release.\n\n\n.. _Unreleased: https://github.com/delgan/loguru/compare/0.7.3...master\n.. _0.7.3: https://github.com/delgan/loguru/releases/tag/0.7.3\n.. _0.7.2: https://github.com/delgan/loguru/releases/tag/0.7.2\n.. _0.7.1: https://github.com/delgan/loguru/releases/tag/0.7.1\n.. _0.7.0: https://github.com/delgan/loguru/releases/tag/0.7.0\n.. _0.6.0: https://github.com/delgan/loguru/releases/tag/0.6.0\n.. _0.5.3: https://github.com/delgan/loguru/releases/tag/0.5.3\n.. _0.5.2: https://github.com/delgan/loguru/releases/tag/0.5.2\n.. _0.5.1: https://github.com/delgan/loguru/releases/tag/0.5.1\n.. _0.5.0: https://github.com/delgan/loguru/releases/tag/0.5.0\n.. _0.4.1: https://github.com/delgan/loguru/releases/tag/0.4.1\n.. _0.4.0: https://github.com/delgan/loguru/releases/tag/0.4.0\n.. _0.3.2: https://github.com/delgan/loguru/releases/tag/0.3.2\n.. _0.3.1: https://github.com/delgan/loguru/releases/tag/0.3.1\n.. _0.3.0: https://github.com/delgan/loguru/releases/tag/0.3.0\n.. _0.2.5: https://github.com/delgan/loguru/releases/tag/0.2.5\n.. _0.2.4: https://github.com/delgan/loguru/releases/tag/0.2.4\n.. _0.2.3: https://github.com/delgan/loguru/releases/tag/0.2.3\n.. _0.2.2: https://github.com/delgan/loguru/releases/tag/0.2.2\n.. _0.2.1: https://github.com/delgan/loguru/releases/tag/0.2.1\n.. _0.2.0: https://github.com/delgan/loguru/releases/tag/0.2.0\n.. _0.1.0: https://github.com/delgan/loguru/releases/tag/0.1.0\n.. _0.0.1: https://github.com/delgan/loguru/releases/tag/0.0.1\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "Thank you for considering improving `Loguru`, any contribution is much welcome!\n\n.. _minimal reproducible example: https://stackoverflow.com/help/mcve\n.. _open a new issue: https://github.com/Delgan/loguru/issues/new\n.. _open a pull request: https://github.com/Delgan/loguru/compare\n.. _PEP 8: https://www.python.org/dev/peps/pep-0008/\n.. _Loguru: https://github.com/Delgan/loguru\n\n\nAutomated Contributions Policy\n------------------------------\n\nRecent advances in artificial intelligence (AI) offer powerful tools to identify bugs and accelerate development, but their usage must remain carefully controlled.\n\nThe use of AI tools to interact with this project is welcome under the following principles:\n\n- Any content generated or significantly refined by AI must be disclosed as such at the time of submission.\n- While AI can help with drafting your PRs or issues, describing problems or questions in your own words is strongly preferred.\n- Always review and test the output generated by AI tools. Do not submit AI output that you do not understand or cannot verify.\n- Fully-automated contributions without human oversight, such as autonomous agents, are strictly prohibited.\n\nPRs and issues may be closed without proper review if they do not adhere to these guidelines.\n\n\nAsking questions\n----------------\n\nIf you have any question about `Loguru`, if you are seeking for help, or if you would like to suggest a new feature, you are encouraged to `open a new issue`_ so we can discuss it. Bringing new ideas and pointing out elements needing clarification allows to make this library always better!\n\n\nReporting a bug\n---------------\n\nIf you encountered an unexpected behavior using `Loguru`, please `open a new issue`_ and describe the problem you have spotted. Be as specific as possible in the description of the trouble so we can easily analyse it and quickly fix it.\n\nAn ideal bug report includes:\n\n* The Python version you are using\n* The `Loguru` version you are using (you can find it with ``print(loguru.__version__)``)\n* Your operating system name and version (Linux, MacOS, Windows)\n* Your development environment and local setup (IDE, Terminal, project context, any relevant information that could be useful)\n* Some `minimal reproducible example`_\n\n\nImplementing changes\n--------------------\n\nIf you are willing to enhance `Loguru` by implementing non-trivial changes, please `open a new issue`_ first to keep a reference about why such modifications are made (and potentially avoid unneeded work).\n\nPrefer using a relatively recent Python version as some dependencies required for development may have dropped support for oldest Python versions. Then, the workflow would look as follows:\n\n1. Fork the `Loguru`_ project from GitHub.\n2. Clone the repository locally::\n\n    $ git clone git@github.com:your_name_here/loguru.git\n    $ cd loguru\n\n3. Activate your virtual environment::\n\n    $ python -m venv env\n    $ source env/bin/activate\n\n4. Install `Loguru` in development mode::\n\n    $ pip install -e \".[dev]\"\n\n5. Install pre-commit hooks that will check your commits::\n\n    $ pre-commit install --install-hooks\n\n6. Create a new branch from ``master``::\n\n    $ git checkout master\n    $ git branch fix_bug\n    $ git checkout fix_bug\n\n7. Implement the modifications wished. During the process of development, honor `PEP 8`_ as much as possible.\n8. Add unit tests (don't hesitate to be exhaustive!) and ensure none are failing using::\n\n    $ tox -e tests\n\n9. Remember to update documentation if required.\n10. If your development modifies `Loguru` behavior, update the ``CHANGELOG.rst`` file with what you improved.\n11. ``add`` and ``commit`` your changes, then ``push`` your local project::\n\n    $ git add .\n    $ git commit -m 'Add succinct explanation of what changed'\n    $ git push origin fix_bug\n\n12. If previous step failed due to the pre-commit hooks, fix reported errors and try again.\n13. Finally, `open a pull request`_ before getting it merged!\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <a href=\"#readme\">\n        <img alt=\"Loguru logo\" src=\"https://raw.githubusercontent.com/Delgan/loguru/master/docs/_static/img/logo.png\">\n        <!-- Logo credits: Sambeet from Pixabay -->\n        <!-- Logo fonts: Comfortaa + Raleway -->\n    </a>\n</p>\n<p align=\"center\">\n    <a href=\"https://pypi.python.org/pypi/loguru\"><img alt=\"Pypi version\" src=\"https://img.shields.io/pypi/v/loguru.svg\"></a>\n    <a href=\"https://pypi.python.org/pypi/loguru\"><img alt=\"Python versions\" src=\"https://img.shields.io/badge/python-3.5%2B%20%7C%20PyPy-blue.svg\"></a>\n    <a href=\"https://loguru.readthedocs.io/en/stable/index.html\"><img alt=\"Documentation\" src=\"https://img.shields.io/readthedocs/loguru.svg\"></a>\n    <a href=\"https://github.com/Delgan/loguru/actions/workflows/tests.yml?query=branch:master\"><img alt=\"Build status\" src=\"https://img.shields.io/github/actions/workflow/status/Delgan/loguru/tests.yml?branch=master\"></a>\n    <a href=\"https://codecov.io/gh/delgan/loguru/branch/master\"><img alt=\"Coverage\" src=\"https://img.shields.io/codecov/c/github/delgan/loguru/master.svg\"></a>\n    <a href=\"https://app.codacy.com/gh/Delgan/loguru/dashboard\"><img alt=\"Code quality\" src=\"https://img.shields.io/codacy/grade/be7337df3c0d40d1929eb7f79b1671a6.svg\"></a>\n    <a href=\"https://github.com/Delgan/loguru/blob/master/LICENSE\"><img alt=\"License\" src=\"https://img.shields.io/github/license/delgan/loguru.svg\"></a>\n</p>\n<p align=\"center\">\n    <a href=\"#readme\">\n        <img alt=\"Loguru logo\" src=\"https://raw.githubusercontent.com/Delgan/loguru/master/docs/_static/img/demo.gif\">\n    </a>\n</p>\n\n______________________________________________________________________\n\n**Loguru** is a library which aims to bring enjoyable logging in Python.\n\nDid you ever feel lazy about configuring a logger and used `print()` instead?... I did, yet logging is fundamental to every application and eases the process of debugging. Using **Loguru** you have no excuse not to use logging from the start, this is as simple as `from loguru import logger`.\n\nAlso, this library is intended to make Python logging less painful by adding a bunch of useful functionalities that solve caveats of the standard loggers. Using logs in your application should be an automatism, **Loguru** tries to make it both pleasant and powerful.\n\n<!-- end-of-readme-intro -->\n\n## Installation\n\n```\npip install loguru\n```\n\n## Features\n\n- [Ready to use out of the box without boilerplate](#ready-to-use-out-of-the-box-without-boilerplate)\n- [No Handler, no Formatter, no Filter: one function to rule them all](#no-handler-no-formatter-no-filter-one-function-to-rule-them-all)\n- [Easier file logging with rotation / retention / compression](#easier-file-logging-with-rotation--retention--compression)\n- [Modern string formatting using braces style](#modern-string-formatting-using-braces-style)\n- [Exceptions catching within threads or main](#exceptions-catching-within-threads-or-main)\n- [Pretty logging with colors](#pretty-logging-with-colors)\n- [Asynchronous, Thread-safe, Multiprocess-safe](#asynchronous-thread-safe-multiprocess-safe)\n- [Fully descriptive exceptions](#fully-descriptive-exceptions)\n- [Structured logging as needed](#structured-logging-as-needed)\n- [Lazy evaluation of expensive functions](#lazy-evaluation-of-expensive-functions)\n- [Customizable levels](#customizable-levels)\n- [Better datetime handling](#better-datetime-handling)\n- [Suitable for scripts and libraries](#suitable-for-scripts-and-libraries)\n- [Entirely compatible with standard logging](#entirely-compatible-with-standard-logging)\n- [Personalizable defaults through environment variables](#personalizable-defaults-through-environment-variables)\n- [Convenient parser](#convenient-parser)\n- [Exhaustive notifier](#exhaustive-notifier)\n- <s>[10x faster than built-in logging](#10x-faster-than-built-in-logging)</s>\n\n## Take the tour\n\n### Ready to use out of the box without boilerplate\n\nThe main concept of Loguru is that **there is one and only one** [`logger`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger).\n\nFor convenience, it is pre-configured and outputs to `stderr` to begin with (but that's entirely configurable).\n\n```python\nfrom loguru import logger\n\nlogger.debug(\"That's it, beautiful and simple logging!\")\n```\n\nThe [`logger`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger) is just an interface which dispatches log messages to configured handlers. Simple, right?\n\n### No Handler, no Formatter, no Filter: one function to rule them all\n\nHow to add a handler? How to set up logs formatting? How to filter messages? How to set level?\n\nOne answer: the [`add()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add) function.\n\n```python\nlogger.add(sys.stderr, format=\"{time} {level} {message}\", filter=\"my_module\", level=\"INFO\")\n```\n\nThis function should be used to register [sinks](https://loguru.readthedocs.io/en/stable/api/logger.html#sink) which are responsible for managing [log messages](https://loguru.readthedocs.io/en/stable/api/logger.html#message) contextualized with a [record dict](https://loguru.readthedocs.io/en/stable/api/logger.html#record). A sink can take many forms: a simple function, a string path, a file-like object, a coroutine function or a built-in Handler.\n\nNote that you may also [`remove()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.remove) a previously added handler by using the identifier returned while adding it. This is particularly useful if you want to supersede the default `stderr` handler: just call `logger.remove()` to make a fresh start.\n\n### Easier file logging with rotation / retention / compression\n\nIf you want to send logged messages to a file, you just have to use a string path as the sink. It can be automatically timed too for convenience:\n\n```python\nlogger.add(\"file_{time}.log\")\n```\n\nIt is also [easily configurable](https://loguru.readthedocs.io/en/stable/api/logger.html#file) if you need rotating logger, if you want to remove older logs, or if you wish to compress your files at closure.\n\n```python\nlogger.add(\"file_1.log\", rotation=\"500 MB\")    # Automatically rotate too big file\nlogger.add(\"file_2.log\", rotation=\"12:00\")     # New file is created each day at noon\nlogger.add(\"file_3.log\", rotation=\"1 week\")    # Once the file is too old, it's rotated\n\nlogger.add(\"file_X.log\", retention=\"10 days\")  # Cleanup after some time\n\nlogger.add(\"file_Y.log\", compression=\"zip\")    # Save some loved space\n```\n\n### Modern string formatting using braces style\n\nLoguru favors the much more elegant and powerful `{}` formatting over `%`, logging functions are actually equivalent to `str.format()`.\n\n```python\nlogger.info(\"If you're using Python {}, prefer {feature} of course!\", 3.6, feature=\"f-strings\")\n```\n\n### Exceptions catching within threads or main\n\nHave you ever seen your program crashing unexpectedly without seeing anything in the log file? Did you ever notice that exceptions occurring in threads were not logged? This can be solved using the [`catch()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.catch) decorator / context manager which ensures that any error is correctly propagated to the [`logger`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger).\n\n```python\n@logger.catch\ndef my_function(x, y, z):\n    # An error? It's caught anyway!\n    return 1 / (x + y + z)\n```\n\n### Pretty logging with colors\n\nLoguru automatically adds colors to your logs if your terminal is compatible. You can define your favorite style by using [markup tags](https://loguru.readthedocs.io/en/stable/api/logger.html#color) in the sink format.\n\n```python\nlogger.add(sys.stdout, colorize=True, format=\"<green>{time}</green> <level>{message}</level>\")\n```\n\n### Asynchronous, Thread-safe, Multiprocess-safe\n\nAll sinks added to the [`logger`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger) are thread-safe by default. They are not multiprocess-safe, but you can `enqueue` the messages to ensure logs integrity. This same argument can also be used if you want async logging.\n\n```python\nlogger.add(\"somefile.log\", enqueue=True)\n```\n\nCoroutine functions used as sinks are also supported and should be awaited with [`complete()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.complete).\n\n### Fully descriptive exceptions\n\nLogging exceptions that occur in your code is important to track bugs, but it's quite useless if you don't know why it failed. Loguru helps you identify problems by allowing the entire stack trace to be displayed, including values of variables (thanks [`better_exceptions`](https://github.com/Qix-/better-exceptions) for this!).\n\nThe code:\n\n```python\n# Caution, \"diagnose=True\" is the default and may leak sensitive data in prod\nlogger.add(\"out.log\", backtrace=True, diagnose=True)\n\ndef func(a, b):\n    return a / b\n\ndef nested(c):\n    try:\n        func(5, c)\n    except ZeroDivisionError:\n        logger.exception(\"What?!\")\n\nnested(0)\n```\n\nWould result in:\n\n```none\n2018-07-17 01:38:43.975 | ERROR    | __main__:nested:10 - What?!\nTraceback (most recent call last):\n\n  File \"test.py\", line 12, in <module>\n    nested(0)\n    └ <function nested at 0x7f5c755322f0>\n\n> File \"test.py\", line 8, in nested\n    func(5, c)\n    │       └ 0\n    └ <function func at 0x7f5c79fc2e18>\n\n  File \"test.py\", line 4, in func\n    return a / b\n           │   └ 0\n           └ 5\n\nZeroDivisionError: division by zero\n```\n\nNote that this feature won't work on default Python REPL due to unavailable frame data.\n\nSee also: [Security considerations when using Loguru](https://loguru.readthedocs.io/en/stable/resources/recipes.html#security-considerations-when-using-loguru).\n\n### Structured logging as needed\n\nWant your logs to be serialized for easier parsing or to pass them around? Using the `serialize` argument, each log message will be converted to a JSON string before being sent to the configured sink.\n\n```python\nlogger.add(custom_sink_function, serialize=True)\n```\n\nUsing [`bind()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.bind) you can contextualize your logger messages by modifying the `extra` record attribute.\n\n```python\nlogger.add(\"file.log\", format=\"{extra[ip]} {extra[user]} {message}\")\ncontext_logger = logger.bind(ip=\"192.168.0.1\", user=\"someone\")\ncontext_logger.info(\"Contextualize your logger easily\")\ncontext_logger.bind(user=\"someone_else\").info(\"Inline binding of extra attribute\")\ncontext_logger.info(\"Use kwargs to add context during formatting: {user}\", user=\"anybody\")\n```\n\nIt is possible to modify a context-local state temporarily with [`contextualize()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.contextualize):\n\n```python\nwith logger.contextualize(task=task_id):\n    do_something()\n    logger.info(\"End of task\")\n```\n\nYou can also have more fine-grained control over your logs by combining [`bind()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.bind) and `filter`:\n\n```python\nlogger.add(\"special.log\", filter=lambda record: \"special\" in record[\"extra\"])\nlogger.debug(\"This message is not logged to the file\")\nlogger.bind(special=True).info(\"This message, though, is logged to the file!\")\n```\n\nFinally, the [`patch()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.patch) method allows dynamic values to be attached to the record dict of each new message:\n\n```python\nlogger.add(sys.stderr, format=\"{extra[utc]} {message}\")\nlogger = logger.patch(lambda record: record[\"extra\"].update(utc=datetime.utcnow()))\n```\n\n### Lazy evaluation of expensive functions\n\nSometime you would like to log verbose information without performance penalty in production, you can use the [`opt()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.opt) method to achieve this.\n\n```python\nlogger.opt(lazy=True).debug(\"If sink level <= DEBUG: {x}\", x=lambda: expensive_function(2**64))\n\n# By the way, \"opt()\" serves many usages\nlogger.opt(exception=True).info(\"Error stacktrace added to the log message (tuple accepted too)\")\nlogger.opt(colors=True).info(\"Per message <blue>colors</blue>\")\nlogger.opt(record=True).info(\"Display values from the record (eg. {record[thread]})\")\nlogger.opt(raw=True).info(\"Bypass sink formatting\\n\")\nlogger.opt(depth=1).info(\"Use parent stack context (useful within wrapped functions)\")\nlogger.opt(capture=False).info(\"Keyword arguments not added to {dest} dict\", dest=\"extra\")\n```\n\n### Customizable levels\n\nLoguru comes with all standard [logging levels](https://loguru.readthedocs.io/en/stable/api/logger.html#levels) to which [`trace()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.trace) and [`success()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.success) are added. Do you need more? Then, just create it by using the [`level()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.level) function.\n\n```python\nnew_level = logger.level(\"SNAKY\", no=38, color=\"<yellow>\", icon=\"🐍\")\n\nlogger.log(\"SNAKY\", \"Here we go!\")\n```\n\n### Better datetime handling\n\nThe standard logging is bloated with arguments like `datefmt` or `msecs`, `%(asctime)s` and `%(created)s`, naive datetimes without timezone information, not intuitive formatting, etc. Loguru [fixes it](https://loguru.readthedocs.io/en/stable/api/logger.html#time):\n\n```python\nlogger.add(\"file.log\", format=\"{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}\")\n```\n\n### Suitable for scripts and libraries\n\nUsing the logger in your scripts is easy, and you can [`configure()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.configure) it at start. To use Loguru from inside a library, remember to never call [`add()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add) but use [`disable()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.disable) instead so logging functions become no-op. If a developer wishes to see your library's logs, they can [`enable()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.enable) it again.\n\n```python\n# For scripts\nconfig = {\n    \"handlers\": [\n        {\"sink\": sys.stdout, \"format\": \"{time} - {message}\"},\n        {\"sink\": \"file.log\", \"serialize\": True},\n    ],\n    \"extra\": {\"user\": \"someone\"}\n}\nlogger.configure(**config)\n\n# For libraries, should be your library's `__name__`\nlogger.disable(\"my_library\")\nlogger.info(\"No matter added sinks, this message is not displayed\")\n\n# In your application, enable the logger in the library\nlogger.enable(\"my_library\")\nlogger.info(\"This message however is propagated to the sinks\")\n```\n\nFor additional convenience, you can also use the [`loguru-config`](https://github.com/erezinman/loguru-config) library to setup the `logger` directly from a configuration file.\n\n### Entirely compatible with standard logging\n\nWish to use built-in logging `Handler` as a Loguru sink?\n\n```python\nhandler = logging.handlers.SysLogHandler(address=('localhost', 514))\nlogger.add(handler)\n```\n\nNeed to propagate Loguru messages to standard `logging`?\n\n```python\nclass PropagateHandler(logging.Handler):\n    def emit(self, record: logging.LogRecord) -> None:\n        logging.getLogger(record.name).handle(record)\n\nlogger.add(PropagateHandler(), format=\"{message}\")\n```\n\nWant to intercept standard `logging` messages toward your Loguru sinks?\n\n```python\nclass InterceptHandler(logging.Handler):\n    def emit(self, record: logging.LogRecord) -> None:\n        # Get corresponding Loguru level if it exists.\n        try:\n            level: str | int = logger.level(record.levelname).name\n        except ValueError:\n            level = record.levelno\n\n        # Find caller from where originated the logged message.\n        frame, depth = inspect.currentframe(), 0\n        while frame:\n            filename = frame.f_code.co_filename\n            is_logging = filename == logging.__file__\n            is_frozen = \"importlib\" in filename and \"_bootstrap\" in filename\n            if depth > 0 and not (is_logging or is_frozen):\n                break\n            frame = frame.f_back\n            depth += 1\n\n        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())\n\nlogging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)\n```\n\n### Personalizable defaults through environment variables\n\nDon't like the default logger formatting? Would prefer another `DEBUG` color? [No problem](https://loguru.readthedocs.io/en/stable/api/logger.html#env):\n\n```bash\n# Linux / OSX\nexport LOGURU_FORMAT=\"{time} | <lvl>{message}</lvl>\"\n\n# Windows\nsetx LOGURU_DEBUG_COLOR \"<green>\"\n```\n\nEnvironment variables match the arguments of [`logger.add()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add), with `LOGURU_LEVEL` notably letting you set the default level of handlers. Common flags like `NO_COLOR` and `FORCE_COLOR` are also supported.\n\n### Convenient parser\n\nIt is often useful to extract specific information from generated logs, this is why Loguru provides a [`parse()`](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.parse) method which helps to deal with logs and regexes.\n\n```python\npattern = r\"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)\"  # Regex with named groups\ncaster_dict = dict(time=dateutil.parser.parse, level=int)        # Transform matching groups\n\nfor groups in logger.parse(\"file.log\", pattern, cast=caster_dict):\n    print(\"Parsed:\", groups)\n    # {\"level\": 30, \"message\": \"Log example\", \"time\": datetime(2018, 12, 09, 11, 23, 55)}\n```\n\n### Exhaustive notifier\n\nLoguru can easily be combined with the great [`apprise`](https://github.com/caronc/apprise) library (must be installed separately) to receive an e-mail when your program fail unexpectedly or to send many other kind of notifications.\n\n```python\nimport apprise\n\n# Define the configuration constants.\nWEBHOOK_ID = \"123456790\"\nWEBHOOK_TOKEN = \"abc123def456\"\n\n# Prepare the object to send Discord notifications.\nnotifier = apprise.Apprise()\nnotifier.add(f\"discord://{WEBHOOK_ID}/{WEBHOOK_TOKEN}\")\n\n# Install a handler to be alerted on each error.\n# You can filter out logs from \"apprise\" itself to avoid recursive calls.\nlogger.add(notifier.notify, level=\"ERROR\", filter={\"apprise\": False})\n```\n\nThis can be seamlessly integrated using the [`logprise`](https://github.com/svaningelgem/logprise) library.\n\n<s>\n\n### 10x faster than built-in logging\n\n</s>\n\nAlthough logging impact on performances is in most cases negligible, a zero-cost logger would allow to use it anywhere without much concern. In an upcoming release, Loguru's critical functions will be implemented in C for maximum speed.\n\n<!-- end-of-readme-usage -->\n\n## Documentation\n\n- [API Reference](https://loguru.readthedocs.io/en/stable/api/logger.html)\n- [Help & Guides](https://loguru.readthedocs.io/en/stable/resources.html)\n- [Type hints](https://loguru.readthedocs.io/en/stable/api/type_hints.html)\n- [Contributing](https://loguru.readthedocs.io/en/stable/project/contributing.html)\n- [License](https://loguru.readthedocs.io/en/stable/project/license.html)\n- [Changelog](https://loguru.readthedocs.io/en/stable/project/changelog.html)\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = loguru\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/_static/css/loguru.css",
    "content": ".wy-nav-content {\n    /* By default it's hardcoded to 800px.\n    We increase it a bit so code source fits without horizontal scrolling. */\n    max-width: 850px;\n}\n"
  },
  {
    "path": "docs/_static/js/copybutton.js",
    "content": "$(document).ready(function() {\n    /* Add a [>>>] button on the top-right corner of code samples to hide\n     * the >>> and ... prompts and the output and thus make the code\n     * copyable. */\n    var hig = $('.highlight-default')\n    var div = hig.find('.highlight')\n    var pre = div.find('pre');\n\n    // get the styles from the current theme\n    pre.parent().parent().css('position', 'relative');\n    var hide_text = 'Hide the prompts and output';\n    var show_text = 'Show the prompts and output';\n    var border_width = hig.css('border-top-width');\n    var border_style = hig.css('border-top-style');\n    var border_color = hig.css('border-top-color');\n    var button_styles = {\n        'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',\n        'border-color': border_color, 'border-style': border_style,\n        'border-width': border_width, 'color': border_color, 'text-size': '75%',\n        'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',\n        'border-radius': '0', 'border-width': '0px 0px 1px 1px',\n        'line-height': '14px', 'font-size': '12px'\n    }\n\n    // create and add the button to all the code blocks that contain >>>\n    div.each(function(index) {\n        var jthis = $(this);\n        if (jthis.find('.gp').length > 0) {\n            var button = $('<span class=\"copybutton\">&gt;&gt;&gt;</span>');\n            button.css(button_styles)\n            button.attr('title', hide_text);\n            button.data('hidden', 'false');\n            jthis.prepend(button);\n        }\n        // tracebacks (.gt) contain bare text elements that need to be\n        // wrapped in a span to work with .nextUntil() (see later)\n        jthis.find('pre:has(.gt)').contents().filter(function() {\n            return ((this.nodeType == 3) && (this.data.trim().length > 0));\n        }).wrap('<span>');\n    });\n\n    // define the behavior of the button when it's clicked\n    $('.copybutton').click(function(e){\n        e.preventDefault();\n        var button = $(this);\n        if (button.data('hidden') === 'false') {\n            // hide the code output\n            button.parent().find('.go, .gp, .gt').hide();\n            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden');\n            button.css('text-decoration', 'line-through');\n            button.attr('title', show_text);\n            button.data('hidden', 'true');\n        } else {\n            // show the code output\n            button.parent().find('.go, .gp, .gt').show();\n            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible');\n            button.css('text-decoration', 'none');\n            button.attr('title', hide_text);\n            button.data('hidden', 'false');\n        }\n    });\n});\n"
  },
  {
    "path": "docs/_templates/breadcrumbs.html",
    "content": "{%- extends \"sphinx_rtd_theme/breadcrumbs.html\" %}\n\n{% block breadcrumbs_aside %}\n{% endblock %}\n"
  },
  {
    "path": "docs/_templates/layout.html",
    "content": "{% extends '!layout.html' %}\n{% block document %}\n{{super()}}\n\n<div class=\"github-corner\">\n<svg width=\"120\" height=\"120\" viewBox=\"0 0 250 250\">\n  <a fill=\"transparent\" href=\"https://github.com/{{github_user}}/{{github_repo}}\" style=\"pointer-events:auto\">\n    <path d=\"M0,0 L250,250 L250,0 Z\"></path>\n  </a>\n  <g class=\"octocat\">\n    <path fill=\"#343131\" d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\" ></path>\n    <path d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\"\n      fill=\"#ffffff\" style=\"transform-origin: 130px 106px;\" class=\"octo-arm\"></path>\n    <path d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\" fill=\"#ffffff\" class=\"octo-body\"></path>\n    <text x=\"240px\" y=\"-10px\" class=\"octo-click-text\" stroke=\"#ffffff\" fill=\"#ffffff\">CLICK</text>\n    <g class=\"octo-glasses\" visibility=\"hidden\">\n      <svg fill=\"#343131\" width=\"640\" height=\"480\">\n       <defs>\n        <symbol id=\"glasses\" viewBox=\"0 0 512 512\">\n         <path d=\"m465.4,247c-2.2,-22 -12.4,-43 -28.9,-58.4c-17.1,-15.9 -39.3,-24.7 -62.7,-24.7c-41.5,0 -77.3,27.4 -88.5,67c-7,-7 -18.5,-11.7 -29.3,-11.7c-10.8,0 -22.3,4.7 -29.3,11.7c-11.2,-39.6 -47,-67 -88.5,-67c-23.3,0 -45.6,8.7 -62.7,24.6c-16.5,15.5 -26.7,36.5 -28.9,58.5l-14.6,0l0,18l14.6,0c2.2,22 12.4,43 28.9,58.4c17.1,15.9 39.3,24.7 62.7,24.7c50.8,0 92.1,-41.2 92.1,-92c0,-0.1 0,-0.1 0,-0.1l0,0c0,-9.9 11.5,-21.6 25.7,-21.6s25.7,11.7 25.7,21.6l0,0c0,0 0,0 0,0.1c0,50.8 41.3,92 92.1,92c23.3,0 45.6,-8.7 62.7,-24.7c16.5,-15.4 26.7,-36.5 28.9,-58.5l14.6,0l0,-18l-14.6,0l0,0.1z\" />\n        </symbol>\n       </defs>\n       <g>\n          <use x=\"530\" y=\"-70\" xlink:href=\"#glasses\" transform=\"rotate(45 100,75) matrix(0.185,0,0,0.185,0,0) \" />\n       </g>\n      </svg>\n    </g>\n  </g>\n</svg>\n</div>\n\n<style>\n.github-corner {\n  pointer-events: none;\n  position: absolute;\n  top: 0;\n  right: 0;\n  border: 0;\n  mix-blend-mode: darken;\n}\n.github-corner:hover .octocat{\n  transform-origin: center;\n  animation: octocat-grow 5s ease-in-out forwards;\n}\n@keyframes octocat-grow{\n  16%, 66%{\n    transform: scale(1.2);\n  }\n  33%{\n    transform: scale(1.1);\n  }\n  50%, 100%{\n    transform: scale(1.3);\n  }\n  83%{\n    transform: scale(1.4);\n  }\n}\n.octo-click-text {\n  font-weight: normal;\n  text-anchor: middle;\n  font-family: 'Avenir', Helvetica, Arial, sans-serif;\n  font-size: 20px;\n  stroke-width: 2px;\n  transform: rotate(45deg);\n  opacity: 0;\n}\n.github-corner:hover .octo-click-text {\n  animation: octocat-text 5s linear forwards\n}\n@keyframes octocat-text {\n  99% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n.github-corner:hover .octo-arm{\n  animation: octocat-wave 400ms linear infinite;\n}\n@keyframes octocat-wave {\n  0%,50%,100% {\n    transform: rotate(0)\n  }\n  25% {\n    transform: rotate(-25deg)\n  }\n  75% {\n    transform: rotate(10deg)\n  }\n}\n.github-corner .octo-glasses{\n  visibility: visible;\n  opacity: 0;\n  transition: opacity 5s;\n}\n.github-corner:hover .octo-glasses{\n  opacity: 1;\n  animation: octocat-glass-wiggle 400ms linear forwards;\n  animation-delay: 4s;\n}\n@keyframes octocat-glass-wiggle {\n  0%, 50%, 100% {\n    transform: translateX(0px) translateY(0px);\n  }\n  25%, 75% {\n    transform: translateX(3px) translateY(-3px);\n  }\n}\n</style>\n{% endblock %}\n"
  },
  {
    "path": "docs/api/logger.rst",
    "content": "``loguru.logger``\n=================\n\n.. automodule:: loguru._logger\n\n.. autoclass:: loguru._logger.Logger()\n   :members:\n"
  },
  {
    "path": "docs/api/type_hints.rst",
    "content": ".. _type-hints:\n\nType Hints\n==========\n\n.. |str| replace:: :class:`str`\n.. |namedtuple| replace:: :func:`namedtuple<collections.namedtuple>`\n.. |dict| replace:: :class:`dict`\n\n.. |Logger| replace:: :class:`~loguru._logger.Logger`\n.. |catch| replace:: :meth:`~loguru._logger.Logger.catch()`\n.. |contextualize| replace:: :meth:`~loguru._logger.Logger.contextualize()`\n.. |complete| replace:: :meth:`~loguru._logger.Logger.complete()`\n.. |bind| replace:: :meth:`~loguru._logger.Logger.bind()`\n.. |patch| replace:: :meth:`~loguru._logger.Logger.patch()`\n.. |opt| replace:: :meth:`~loguru._logger.Logger.opt()`\n.. |level| replace:: :meth:`~loguru._logger.Logger.level()`\n\n.. _stub file: https://www.python.org/dev/peps/pep-0484/#stub-files\n.. _string literals: https://www.python.org/dev/peps/pep-0484/#forward-references\n.. _postponed evaluation of annotations: https://www.python.org/dev/peps/pep-0563/\n.. |future| replace:: ``__future__``\n.. _future: https://www.python.org/dev/peps/pep-0563/#enabling-the-future-behavior-in-python-3-7\n.. |loguru-mypy| replace:: ``loguru-mypy``\n.. _loguru-mypy: https://github.com/kornicameister/loguru-mypy\n.. |documentation of loguru-mypy| replace:: documentation of ``loguru-mypy``\n.. _documentation of loguru-mypy:\n    https://github.com/kornicameister/loguru-mypy/blob/master/README.md\n.. _@kornicameister: https://github.com/kornicameister\n\nLoguru relies on a `stub file`_ to document its types. This implies that these types are not\naccessible during execution of your program, however they can be used by type checkers and IDE.\nAlso, this means that your Python interpreter has to support `postponed evaluation of annotations`_\nto prevent error at runtime. This is achieved with a |future|_ import in Python 3.7+ or by using\n`string literals`_ for earlier versions.\n\nA basic usage example could look like this:\n\n.. code-block:: python\n\n    from __future__ import annotations\n\n    import loguru\n    from loguru import logger\n\n    def good_sink(message: loguru.Message):\n        print(\"My name is\", message.record[\"name\"])\n\n    def bad_filter(record: loguru.Record):\n        return record[\"invalid\"]\n\n    logger.add(good_sink, filter=bad_filter)\n\n\n.. code-block::\n\n    $ mypy test.py\n    test.py:8: error: TypedDict \"Record\" has no key 'invalid'\n    Found 1 error in 1 file (checked 1 source file)\n\nThere are several internal types to which you can be exposed using Loguru's public API, they are\nlisted here and might be useful to type hint your code:\n\n- ``Logger``: the usual |logger| object (also returned by |opt|, |bind| and |patch|).\n- ``Message``: the formatted logging message sent to the sinks (a |str| with ``record``\n  attribute).\n- ``Record``: the |dict| containing all contextual information of the logged message.\n- ``Level``: the |namedtuple| returned by |level| (with ``name``, ``no``, ``color`` and ``icon``\n  attributes).\n- ``Catcher``: the context decorator returned by |catch|.\n- ``Contextualizer``: the context decorator returned by |contextualize|.\n- ``AwaitableCompleter``: the awaitable object returned by |complete|.\n- ``RecordFile``: the ``record[\"file\"]`` with ``name`` and ``path`` attributes.\n- ``RecordLevel``: the ``record[\"level\"]`` with ``name``, ``no`` and ``icon`` attributes.\n- ``RecordThread``: the ``record[\"thread\"]`` with ``id`` and ``name`` attributes.\n- ``RecordProcess``: the ``record[\"process\"]`` with ``id`` and ``name`` attributes.\n- ``RecordException``: the ``record[\"exception\"]`` with ``type``, ``value`` and ``traceback``\n  attributes.\n\nIf that is not enough, one can also use the |loguru-mypy|_ library developed by `@kornicameister`_.\nPlugin can be installed separately using::\n\n    pip install loguru-mypy\n\nIt helps to catch several possible runtime errors by performing additional checks like:\n\n- ``opt(lazy=True)`` loggers accepting only ``typing.Callable[[], typing.Any]`` arguments\n- ``opt(record=True)`` loggers wrongly calling log handler like so ``logger.info(..., record={})``\n- and even more...\n\nFor more details, go to official |documentation of loguru-mypy|_.\n\n\nSee also: :ref:`type-hints-source`.\n"
  },
  {
    "path": "docs/api/type_hints_source.rst",
    "content": ":orphan:\n\n.. _type-hints-source:\n\nSource Code of Type Hints\n=========================\n\n.. include:: ../../loguru/__init__.pyi\n   :literal:\n"
  },
  {
    "path": "docs/api.rst",
    "content": "API Reference\n=============\n\n.. automodule:: loguru\n\n.. toctree::\n   :hidden:\n   :includehidden:\n\n   api/logger.rst\n   api/type_hints.rst\n\n\n* :class:`~loguru._logger.Logger`\n\n    * :meth:`~loguru._logger.Logger.add`\n\n        * :ref:`sink`\n        * :ref:`message`\n        * :ref:`levels`\n        * :ref:`record`\n        * :ref:`time`\n        * :ref:`file`\n        * :ref:`color`\n        * :ref:`env`\n\n    * :meth:`~loguru._logger.Logger.remove`\n    * :meth:`~loguru._logger.Logger.complete`\n    * :meth:`~loguru._logger.Logger.catch`\n    * :meth:`~loguru._logger.Logger.opt`\n    * :meth:`~loguru._logger.Logger.bind`\n    * :meth:`~loguru._logger.Logger.contextualize`\n    * :meth:`~loguru._logger.Logger.patch`\n    * :meth:`~loguru._logger.Logger.level`\n    * :meth:`~loguru._logger.Logger.disable`\n    * :meth:`~loguru._logger.Logger.enable`\n    * :meth:`~loguru._logger.Logger.configure`\n    * :meth:`~loguru._logger.Logger.reinstall`\n    * :meth:`~loguru._logger.Logger.parse`\n    * :meth:`~loguru._logger.Logger.trace`\n    * :meth:`~loguru._logger.Logger.debug`\n    * :meth:`~loguru._logger.Logger.info`\n    * :meth:`~loguru._logger.Logger.success`\n    * :meth:`~loguru._logger.Logger.warning`\n    * :meth:`~loguru._logger.Logger.error`\n    * :meth:`~loguru._logger.Logger.critical`\n    * :meth:`~loguru._logger.Logger.log`\n    * :meth:`~loguru._logger.Logger.exception`\n\n* :ref:`type-hints`\n"
  },
  {
    "path": "docs/conf.py",
    "content": "\"\"\"Configuration file for the Sphinx documentation builder.\n\nThis file does only contain a selection of the most common options. For a\nfull list see the documentation: http://www.sphinx-doc.org/en/master/config\n\n-- Path setup --------------------------------------------------------------\n\nIf extensions (or modules to document with autodoc) are in another directory,\nadd these directories to sys.path here. If the directory is relative to the\ndocumentation root, use os.path.abspath to make it absolute, like shown here.\n\"\"\"\n\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath(\"..\"))\n\n\n# -- Project information -----------------------------------------------------\n\nproject = \"loguru\"\ncopyright = \"2018, Delgan\"\nauthor = \"Delgan\"\n\n# The short X.Y version\nversion = \"\"\n# The full version, including alpha/beta/rc tags\nrelease = \"\"\n\n\n# -- General configuration ---------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"myst_parser\",\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.intersphinx\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = [\".rst\", \".md\"]\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = \"English\"\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path .\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# Warn about all references where the target cannot be found.\nnitpicky = True\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\nhtml_theme_options = {}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\n#\n# The default sidebars (for documents that don't match any pattern) are\n# defined by theme itself.  Builtin themes are using these templates by\n# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',\n# 'searchbox.html']``.\n#\n# html_sidebars = {}\n\n\n# -- Options for HTMLHelp output ---------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"logurudoc\"\n\n\n# -- Options for LaTeX output ------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [(master_doc, \"loguru.tex\", \"loguru Documentation\", \"Delgan\", \"manual\")]\n\n\n# -- Options for manual page output ------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"loguru\", \"loguru Documentation\", [author], 1)]\n\n\n# -- Options for Texinfo output ----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"loguru\",\n        \"loguru Documentation\",\n        author,\n        \"loguru\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    )\n]\n\n\n# -- Extension configuration -------------------------------------------------\n\nhtml_context = {\"github_user\": \"delgan\", \"github_repo\": \"loguru\"}\n\nadd_module_names = False\nautodoc_member_order = \"bysource\"\nintersphinx_mapping = {\"python\": (\"https://docs.python.org/3\", None)}\nhtml_show_sourcelink = False\nhtml_show_copyright = False\nnapoleon_use_rtype = False\nnapoleon_use_ivar = True\nmyst_heading_anchors = 3\n\n# MyST parser complains that headers where the README start at H2 and not H1.\n# We may be able to get rid of this warning if we convert the reST files to MyST.\nsuppress_warnings = [\"myst.header\"]\n\n\ndef setup(app):\n    \"\"\"Configure the generation of docs.\"\"\"\n    app.add_css_file(\"css/loguru.css\")\n    app.add_js_file(\"js/copybutton.js\")\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. include:: ../README.md\n   :parser: myst_parser.sphinx_\n   :end-before: <!-- end-of-readme-intro -->\n\nTable of Contents\n=================\n\n.. toctree::\n   :includehidden:\n   :maxdepth: 2\n\n   overview.rst\n   api.rst\n   resources.rst\n   project.rst\n"
  },
  {
    "path": "docs/overview.rst",
    "content": "Overview\n========\n\n.. include:: ../README.md\n   :parser: myst_parser.sphinx_\n   :start-after: <!-- end-of-readme-intro -->\n   :end-before: <!-- end-of-readme-usage -->\n"
  },
  {
    "path": "docs/project/changelog.rst",
    "content": "Changelog\n#########\n\n.. include:: ../../CHANGELOG.rst\n"
  },
  {
    "path": "docs/project/contributing.rst",
    "content": "Contributing\n############\n\n.. include:: ../../CONTRIBUTING.rst\n"
  },
  {
    "path": "docs/project/license.rst",
    "content": "License\n#######\n\n.. literalinclude:: ../../LICENSE\n    :language: none\n"
  },
  {
    "path": "docs/project.rst",
    "content": "Project Information\n===================\n\n.. toctree::\n\n   project/contributing.rst\n   project/license.rst\n   project/changelog.rst\n"
  },
  {
    "path": "docs/resources/migration.rst",
    "content": "Switching from Standard Logging to Loguru\n=========================================\n\n.. highlight:: python3\n\n.. |getLogger| replace:: :func:`~logging.getLogger`\n.. |addLevelName| replace:: :func:`~logging.addLevelName`\n.. |getLevelName| replace:: :func:`~logging.getLevelName`\n.. |Handler| replace:: :class:`~logging.Handler`\n.. |Logger| replace:: :class:`~logging.Logger`\n.. |Filter| replace:: :class:`~logging.Filter`\n.. |Formatter| replace:: :class:`~logging.Formatter`\n.. |LoggerAdapter| replace:: :class:`~logging.LoggerAdapter`\n.. |LogRecord| replace:: :class:`~logging.LogRecord`\n.. |logger.setLevel| replace:: :meth:`~logging.Logger.setLevel`\n.. |logger.addFilter| replace:: :meth:`~logging.Logger.addFilter`\n.. |makeRecord| replace:: :meth:`~logging.Logger.makeRecord`\n.. |disable| replace:: :func:`~logging.disable`\n.. |setLogRecordFactory| replace:: :func:`~logging.setLogRecordFactory`\n.. |propagate| replace:: :attr:`~logging.Logger.propagate`\n.. |addHandler| replace:: :meth:`~logging.Logger.addHandler`\n.. |removeHandler| replace:: :meth:`~logging.Logger.removeHandler`\n.. |handle| replace:: :meth:`~logging.Handler.handle`\n.. |emit| replace:: :meth:`~logging.Handler.emit`\n.. |handler.setLevel| replace:: :meth:`~logging.Handler.setLevel`\n.. |handler.addFilter| replace:: :meth:`~logging.Handler.addFilter`\n.. |setFormatter| replace:: :meth:`~logging.Handler.setFormatter`\n.. |createLock| replace:: :meth:`~logging.Handler.createLock`\n.. |acquire| replace:: :meth:`~logging.Handler.acquire`\n.. |release| replace:: :meth:`~logging.Handler.release`\n.. |isEnabledFor| replace:: :meth:`~logging.Logger.isEnabledFor`\n.. |dictConfig| replace:: :func:`~logging.config.dictConfig`\n.. |basicConfig| replace:: :func:`~logging.basicConfig`\n.. |captureWarnings| replace:: :func:`~logging.captureWarnings`\n.. |assertLogs| replace:: :meth:`~unittest.TestCase.assertLogs`\n.. |unittest| replace:: :mod:`unittest`\n.. |warnings| replace:: :mod:`warnings`\n.. |warnings.showwarning| replace:: :func:`warnings.showwarning`\n\n.. |add| replace:: :meth:`~loguru._logger.Logger.add()`\n.. |remove| replace:: :meth:`~loguru._logger.Logger.remove()`\n.. |bind| replace:: :meth:`~loguru._logger.Logger.bind`\n.. |patch| replace:: :meth:`~loguru._logger.Logger.patch`\n.. |opt| replace:: :meth:`~loguru._logger.Logger.opt()`\n.. |level| replace:: :meth:`~loguru._logger.Logger.level()`\n.. |configure| replace:: :meth:`~loguru._logger.Logger.configure()`\n\n.. |pytest| replace:: ``pytest``\n.. _pytest: https://docs.pytest.org/en/latest/\n.. |conftest.py| replace:: ``conftest.py``\n.. _conftest.py: https://docs.pytest.org/en/latest/reference/fixtures.html#conftest-py-sharing-fixtures-across-multiple-files\n.. |caplog| replace:: ``caplog``\n.. _caplog: https://docs.pytest.org/en/latest/logging.html?highlight=caplog#caplog-fixture\n.. |pytest-loguru| replace:: ``pytest-loguru``\n.. _pytest-loguru: https://github.com/mcarans/pytest-loguru\n\n.. _@mcarans: https://github.com/mcarans\n\n\n\nIntroduction to logging in Python\n---------------------------------\n\nFirst and foremost, it is important to understand some basic concepts about logging in Python.\n\nLogging is an essential part of any application, as it allows you to track the behavior of your code and diagnose issues. It associates messages with severity levels which are collected and dispatched to readable outputs called handlers.\n\nFor newcomers, take a look at the tutorial in the Python documentation: `Logging HOWTO <https://docs.python.org/3/howto/logging.html>`_.\n\n\nFundamental differences between ``logging`` and ``loguru``\n----------------------------------------------------------\n\nAlthough ``loguru`` is written \"from scratch\" and does not rely on standard ``logging`` internally, both libraries serve the same purpose: provide functionalities to implement a flexible event logging system. The main difference is that standard ``logging`` requires the user to explicitly instantiate named ``Logger`` and configure them with ``Handler``, ``Formatter`` and ``Filter``, while ``loguru`` tries to narrow down the amount of configuration steps.\n\nApart from that, usage is globally the same, once the ``logger`` object is created or imported you can start using it to log messages with the appropriate severity (``logger.debug(\"Dev message\")``, ``logger.warning(\"Danger!\")``, etc.), messages which are then sent to the configured handlers.\n\nAs for standard logging, default logs are sent to ``sys.stderr`` rather than ``sys.stdout``. The POSIX standard specifies that  ``stderr`` is the correct stream for \"diagnostic output\". The main compelling case in favor or logging to ``stderr`` is that it avoids mixing the actual output of the application with debug information. Consider for example pipe-redirection like ``python my_app.py | other_app`` which would not be possible if logs were emitted to ``stdout``. Another major benefit is that Python resolves encoding issues on ``sys.stderr`` by escaping faulty characters (``\"backslashreplace\"`` policy) while it raises an ``UnicodeEncodeError`` (``\"strict\"`` policy) on ``sys.stdout``.\n\n\nReplacing ``getLogger()`` function\n----------------------------------\n\nIt is usual to call |getLogger| at the beginning of each file to retrieve and use a logger across your module, like this: ``logger = logging.getLogger(__name__)``.\n\nUsing Loguru, there is no need to explicitly get and name a logger, ``from loguru import logger`` suffices. Each time this imported logger is used, a :ref:`record <record>` is created and will automatically contain the contextual ``__name__`` value.\n\nAs for standard logging, the ``name`` attribute can then be used to format and filter your logs.\n\n\nReplacing ``Logger`` objects\n----------------------------\n\nLoguru replaces the standard |Logger| configuration by a proper :ref:`sink <sink>` definition. Instead of configuring a logger, you should |add| and parametrize your handlers. The |logger.setLevel| and |logger.addFilter| are suppressed by the configured sink ``level`` and ``filter`` parameters. The |propagate| attribute and |disable| function can be replaced by the ``filter`` option too. The |makeRecord| method can be replaced using the ``record[\"extra\"]`` dict.\n\nSometimes, more fine-grained control is required over a particular logger. In such case, Loguru provides the |bind| method which can be in particular used to generate a specifically named logger.\n\nFor example, by calling ``other_logger = logger.bind(name=\"other\")``, each :ref:`message <message>` logged using ``other_logger`` will populate the ``record[\"extra\"]`` dict with the ``name`` value, while using ``logger`` won't. This permits differentiating logs from ``logger`` or ``other_logger`` from within your sink or filter function.\n\nLet suppose you want a sink to log only some very specific messages::\n\n    def specific_only(record):\n        return \"specific\" in record[\"extra\"]\n\n    logger.add(\"specific.log\", filter=specific_only)\n\n    specific_logger = logger.bind(specific=True)\n\n    logger.info(\"General message\")          # This is filtered-out by the specific sink\n    specific_logger.info(\"Module message\")  # This is accepted by the specific sink (and others)\n\nAnother example, if you want to attach one sink to one named logger::\n\n    # Only write messages from \"a\" logger\n    logger.add(\"a.log\", filter=lambda record: record[\"extra\"].get(\"name\") == \"a\")\n    # Only write messages from \"b\" logger\n    logger.add(\"b.log\", filter=lambda record: record[\"extra\"].get(\"name\") == \"b\")\n\n    logger_a = logger.bind(name=\"a\")\n    logger_b = logger.bind(name=\"b\")\n\n    logger_a.info(\"Message A\")\n    logger_b.info(\"Message B\")\n\n\nReplacing ``Handler``, ``Filter`` and ``Formatter`` objects\n-----------------------------------------------------------\n\nStandard ``logging`` requires you to create an |Handler| object and then call |addHandler|. Using Loguru, the handlers are started using |add|. The sink defines how the handler should manage incoming logging messages, as would do |handle| or |emit|. To log from multiple modules, you just have to import the logger, all messages will be dispatched to the added handlers.\n\nWhile calling |add|, the ``level`` parameter replaces |handler.setLevel|, the ``format`` parameter replaces |setFormatter|, the ``filter`` parameter replaces |handler.addFilter|. The thread-safety is managed automatically by Loguru, so there is no need for |createLock|, |acquire| nor |release|. The equivalent method of |removeHandler| is |remove| which should be used with the identifier returned by |add|.\n\nNote that you don't necessarily need to replace your |Handler| objects because |add| accepts them as valid sinks.\n\nIn short, you can replace::\n\n    logger.setLevel(logging.DEBUG)\n\n    fh = logging.FileHandler(\"spam.log\")\n    fh.setLevel(logging.DEBUG)\n\n    ch = logging.StreamHandler()\n    ch.setLevel(logging.ERROR)\n\n    formatter = logging.Formatter(\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\")\n    fh.setFormatter(formatter)\n    ch.setFormatter(formatter)\n\n    logger.addHandler(fh)\n    logger.addHandler(ch)\n\nWith::\n\n    fmt = \"{time} - {name} - {level} - {message}\"\n    logger.add(\"spam.log\", level=\"DEBUG\", format=fmt)\n    logger.add(sys.stderr, level=\"ERROR\", format=fmt)\n\n\nReplacing ``LogRecord`` objects\n-------------------------------\n\nIn Loguru, the equivalence of a |LogRecord| instance is a simple ``dict`` which stores the details of a logged message. To find the correspondence with |LogRecord| attributes, please refer to :ref:`the \"record dict\" documentation <record>` which lists all available keys.\n\nThis ``dict`` is attached to each :ref:`logged message <message>` through a special ``record`` attribute of the ``str``-like object received by sinks. For example::\n\n    def simple_sink(message):\n        # A simple sink can use \"message\" as a basic string and ignore the \"record\" attribute.\n        print(message, end=\"\")\n\n    def advanced_sink(message):\n        # An advanced sink can use the \"record\" attribute to access contextual information.\n        record = message.record\n\n        if record[\"level\"].no >= 50:\n            file_path = record[\"file\"].path\n            print(f\"Critical error in {file_path}\", end=\"\", file=sys.stderr)\n        else:\n            print(message, end=\"\")\n\n    logger.add(simple_sink)\n    logger.add(advanced_sink)\n\n\nAs explained in the previous sections, the record dict is also available during invocation of filtering and formatting functions.\n\nIf you need to extend the record dict with custom information similarly to what was possible with |setLogRecordFactory|, you can simply use the |patch| method to add the desired keys to the ``record[\"extra\"]`` dict.\n\n\nReplacing ``%`` style formatting of messages\n--------------------------------------------\n\nLoguru only supports ``{}``-style formatting.\n\nYou have to replace ``logger.debug(\"Some variable: %s\", var)`` with ``logger.debug(\"Some variable: {}\", var)``. All ``*args`` and ``**kwargs`` passed to a logging function are used to call ``message.format(*args, **kwargs)``. Arguments which do not appear in the message string are simply ignored. Note that passing arguments to logging functions like this may be useful to (slightly) improve performances: it avoids formatting the message if the level is too low to pass any configured handler.\n\nFor converting the general format used by |Formatter|, refer to :ref:`list of available record tokens <record>`.\n\nFor converting the date format used by ``datefmt``, refer to :ref:`list of available date tokens<time>`.\n\n\nReplacing ``exc_info`` argument\n-------------------------------\n\nWhile calling standard logging function, you can pass ``exc_info`` as an argument to add stacktrace to the message. Instead of that, you should use the |opt| method with ``exception`` parameter, replacing ``logger.debug(\"Debug error:\", exc_info=True)`` with ``logger.opt(exception=True).debug(\"Debug error:\")``.\n\nThe formatted exception will include the whole stacktrace and variables. To prevent that, make sure to use ``backtrace=False`` and ``diagnose=False`` while adding your sink.\n\n\nReplacing ``extra`` argument and ``LoggerAdapter`` objects\n----------------------------------------------------------\n\nTo pass contextual information to log messages, replace ``extra`` by inlining |bind| method::\n\n    context = {\"clientip\": \"192.168.0.1\", \"user\": \"fbloggs\"}\n\n    logger.info(\"Protocol problem\", extra=context)   # Standard logging\n    logger.bind(**context).info(\"Protocol problem\")  # Loguru\n\nThis will add context information to the ``record[\"extra\"]`` dict of your logged message, so make sure to configure your handler format adequately::\n\n    fmt = \"%(asctime)s %(clientip)s %(user)s %(message)s\"     # Standard logging\n    fmt = \"{time} {extra[clientip]} {extra[user]} {message}\"  # Loguru\n\nYou can also replace |LoggerAdapter| by calling ``logger = logger.bind(clientip=\"192.168.0.1\")`` before using it, or by assigning the bound logger to a class instance::\n\n    class MyClass:\n\n        def __init__(self, clientip):\n            self.logger = logger.bind(clientip=clientip)\n\n        def func(self):\n            self.logger.debug(\"Running func\")\n\n\nReplacing ``isEnabledFor()`` method\n-----------------------------------\n\nIf you wish to log useful information for your debug logs, but don't want to pay the performance penalty in release mode while no debug handler is configured, standard logging provides the |isEnabledFor| method::\n\n    if logger.isEnabledFor(logging.DEBUG):\n        logger.debug(\"Message data: %s\", expensive_func())\n\nYou can replace this with the |opt| method and ``lazy`` option::\n\n    # Arguments should be functions which will be called if needed\n    logger.opt(lazy=True).debug(\"Message data: {}\", expensive_func)\n\n\nReplacing ``addLevelName()`` and ``getLevelName()`` functions\n-------------------------------------------------------------\n\nTo add a new custom level, you can replace |addLevelName| with the |level| function::\n\n    logging.addLevelName(33, \"CUSTOM\")                       # Standard logging\n    logger.level(\"CUSTOM\", no=45, color=\"<red>\", icon=\"🚨\")  # Loguru\n\nThe same function can be used to replace |getLevelName|::\n\n    logger.getLevelName(33)  # => \"CUSTOM\"\n    logger.level(\"CUSTOM\")   # => (name='CUSTOM', no=33, color=\"<red>\", icon=\"🚨\")\n\nNote that contrary to standard logging, Loguru doesn't associate severity number to any level, levels are only identified by their name.\n\n\nReplacing ``basicConfig()`` and ``dictConfig()`` functions\n----------------------------------------------------------\n\nThe |basicConfig| and |dictConfig| functions are replaced by the |configure| method.\n\nThis does not accept ``config.ini`` files, though, so you have to handle that yourself using your favorite format.\n\n\nReplacing ``captureWarnings()`` function\n----------------------------------------\n\nThe |captureWarnings| function which redirects alerts from the |warnings| module to the logging system can be implemented by simply replacing |warnings.showwarning| function as follow::\n\n    import warnings\n    from loguru import logger\n\n    showwarning_ = warnings.showwarning\n\n    def showwarning(message, *args, **kwargs):\n        logger.warning(message)\n        showwarning_(message, *args, **kwargs)\n\n    warnings.showwarning = showwarning\n\n\n.. _migration-assert-logs:\n\nReplacing ``assertLogs()`` method from ``unittest`` library\n-----------------------------------------------------------\n\nThe |assertLogs| method defined in the |unittest| from standard library is used to capture and test logged messages. However, it can't be made compatible with Loguru. It needs to be replaced with a custom context manager possibly implemented as follows::\n\n    from contextlib import contextmanager\n\n    @contextmanager\n    def capture_logs(level=\"INFO\", format=\"{level}:{name}:{message}\"):\n        \"\"\"Capture loguru-based logs.\"\"\"\n        output = []\n        handler_id = logger.add(output.append, level=level, format=format)\n        yield output\n        logger.remove(handler_id)\n\nIt provides the list of :ref:`logged messages <message>` for each of which you can access :ref:`the record attribute<record>`. Here is a usage example::\n\n    def do_something(val):\n        if val < 0:\n            logger.error(\"Invalid value\")\n            return 0\n        return val * 2\n\n\n    class TestDoSomething(unittest.TestCase):\n        def test_do_something_good(self):\n            with capture_logs() as output:\n                do_something(1)\n            self.assertEqual(output, [])\n\n        def test_do_something_bad(self):\n            with capture_logs() as output:\n                do_something(-1)\n            self.assertEqual(len(output), 1)\n            message = output[0]\n            self.assertIn(\"Invalid value\", message)\n            self.assertEqual(message.record[\"level\"].name, \"ERROR\")\n\n.. seealso::\n\n   See :ref:`testing logging <recipes-testing>` for alternative approaches.\n\n\n.. _migration-caplog:\n\nReplacing ``caplog`` fixture from ``pytest`` library\n----------------------------------------------------\n\n|pytest|_ is a very common testing framework. The |caplog|_ fixture captures logging output so that it can be tested against. For example::\n\n    from loguru import logger\n\n    def some_func(a, b):\n        if a < 0:\n            logger.warning(\"Oh no!\")\n        return a + b\n\n    def test_some_func(caplog):\n        assert some_func(-1, 3) == 2\n        assert \"Oh no!\" in caplog.text\n\nIf you've followed all the migration guidelines thus far, you'll notice that this test will fail. This is because |pytest|_ links to the standard library's ``logging`` module.\n\nTo ensure compatibility between Loguru and |pytest|_, we need to propagate logs to the standard logging handlers used by |pytest|_. We simply need to |add| these handlers to Loguru's logger using a custom fixture:\n\n* The ``caplog_handler`` handler which captures logs for inspection in tests using the |caplog|_ fixture.\n* The ``log_cli_handler`` which outputs logs in live during tests when the ``--log-cli-level`` flag is used.\n* The ``report_handler`` which displays logs in the terminal summary at the end of tests in case of failures.\n\nIn your |conftest.py|_ file, add the following::\n\n    import pytest\n    from loguru import logger\n\n    @pytest.fixture(autouse=True)\n    def propagate_logs(request):\n        \"\"\"Propagate Loguru logs to standard logging handlers used by Pytest.\"\"\"\n        plugin = request.config.pluginmanager.getplugin(\"logging-plugin\")\n\n        # Remove all existing Loguru handlers, including the default one.\n        logger.remove()\n\n        handler_ids = []\n\n        for handler in [plugin.caplog_handler, plugin.log_cli_handler, plugin.report_handler]:\n\n            # Note that, by default, all log levels are propagated to standard handlers.\n            # You can adjust the `level` here, modify the handler's level, or use `caplog.set_level()`.\n            handler_id = logger.add(handler, format=\"{message}\", level=0)\n            handler_ids.append(handler_id)\n\n        yield\n\n        for handler_id in handler_ids:\n            logger.remove(handler_id)\n\n\nRun your tests and things should all be working as expected. See also `How to manage logging <https://docs.pytest.org/en/stable/how-to/logging.html>`_ from the official Pytest documentation.\n\nYou can also install and use the |pytest-loguru|_ package created by `@mcarans`_.\n\n.. seealso::\n\n    See :ref:`testing logging <recipes-testing>` for alternative approaches.\n"
  },
  {
    "path": "docs/resources/recipes.rst",
    "content": "Code Snippets and Recipes for Loguru\n====================================\n\n.. highlight:: python3\n\n.. |print| replace:: :func:`print()`\n.. |open| replace:: :func:`open()`\n.. |sys.__stdout__| replace:: :data:`sys.__stdout__`\n.. |sys.stdout| replace:: :data:`sys.stdout`\n.. |sys.stderr| replace:: :data:`sys.stderr`\n.. |warnings| replace:: :mod:`warnings`\n.. |warnings.showwarning| replace:: :func:`warnings.showwarning`\n.. |warnings.warn| replace:: :func:`warnings.warn`\n.. |contextlib.redirect_stdout| replace:: :func:`contextlib.redirect_stdout`\n.. |copy.deepcopy| replace:: :func:`copy.deepcopy`\n.. |os.fork| replace:: :func:`os.fork`\n.. |os.umask| replace:: :func:`os.umask`\n.. |multiprocessing| replace:: :mod:`multiprocessing`\n.. |pickle| replace:: :mod:`pickle`\n.. |traceback| replace:: :mod:`traceback`\n.. |Thread| replace:: :class:`~threading.Thread`\n.. |Process| replace:: :class:`~multiprocessing.Process`\n.. |Pool| replace:: :class:`~multiprocessing.pool.Pool`\n.. |Pool.map| replace:: :meth:`~multiprocessing.pool.Pool.map`\n.. |Pool.apply| replace:: :meth:`~multiprocessing.pool.Pool.apply`\n.. |sys.stdout.reconfigure| replace:: :meth:`sys.stdout.reconfigure() <io.TextIOWrapper.reconfigure>`\n.. |UnicodeEncodeError| replace:: :exc:`UnicodeEncodeError`\n\n.. |add| replace:: :meth:`~loguru._logger.Logger.add()`\n.. |remove| replace:: :meth:`~loguru._logger.Logger.remove()`\n.. |enable| replace:: :meth:`~loguru._logger.Logger.enable()`\n.. |disable| replace:: :meth:`~loguru._logger.Logger.disable()`\n.. |bind| replace:: :meth:`~loguru._logger.Logger.bind()`\n.. |patch| replace:: :meth:`~loguru._logger.Logger.patch()`\n.. |opt| replace:: :meth:`~loguru._logger.Logger.opt()`\n.. |log| replace:: :meth:`~loguru._logger.Logger.log()`\n.. |level| replace:: :meth:`~loguru._logger.Logger.level()`\n.. |configure| replace:: :meth:`~loguru._logger.Logger.configure()`\n.. |complete| replace:: :meth:`~loguru._logger.Logger.complete()`\n\n.. _`unicode`: https://docs.python.org/3/howto/unicode.html\n\n.. |if-name-equals-main| replace:: ``if __name__ == \"__main__\":``\n.. _if-name-equals-main: https://docs.python.org/3/library/__main__.html#idiomatic-usage\n\n.. |logot| replace:: ``logot``\n.. _logot: https://logot.readthedocs.io/\n\n.. |pytest| replace:: ``pytest``\n.. _pytest: https://docs.pytest.org/en/latest/\n\n.. |stackprinter| replace:: ``stackprinter``\n.. _stackprinter: https://github.com/cknd/stackprinter\n\n.. |zmq| replace:: ``zmq``\n.. _zmq: https://github.com/zeromq/pyzmq\n\n.. _`GH#132`: https://github.com/Delgan/loguru/issues/132\n\n\nSecurity considerations when using Loguru\n-----------------------------------------\n\nFirstly, if you use |pickle| to load log messages (e.g. from the network), make sure the source is trustable or sign the data to verify its authenticity before deserializing it. If you do not take these precautions, malicious code could be executed by an attacker. You can read more details in this article: `What’s so dangerous about pickles? <https://intoli.com/blog/dangerous-pickles/>`_\n\n.. code::\n\n    import hashlib\n    import hmac\n    import pickle\n\n    def client(connection):\n        data = pickle.dumps(\"Log message\")\n        digest =  hmac.digest(b\"secret-shared-key\", data, hashlib.sha1)\n        connection.send(digest + b\" \" + data)\n\n    def server(connection):\n        expected_digest, data = connection.read().split(b\" \", 1)\n        data_digest = hmac.digest(b\"secret-shared-key\", data, hashlib.sha1)\n        if not hmac.compare_digest(data_digest, expected_digest):\n            print(\"Integrity error\")\n        else:\n            message = pickle.loads(data)\n            logger.info(message)\n\n\nYou should also avoid logging a message that could be maliciously hand-crafted by an attacker. Calling ``logger.debug(message, value)`` is roughly equivalent to calling ``print(message.format(value))`` and the same safety rules apply. In particular, an attacker could force printing of assumed hidden variables of your application. Here is an article explaining the possible vulnerability: `Be Careful with Python's New-Style String Format <https://lucumr.pocoo.org/2016/12/29/careful-with-str-format/>`_.\n\n.. code::\n\n    SECRET_KEY = 'Y0UC4NTS33Th1S!'\n\n    class SomeValue:\n        def __init__(self, value):\n            self.value = value\n\n    # If user types \"{value.__init__.__globals__[SECRET_KEY]}\" then the secret key is displayed.\n    message = \"[Custom message] \" + input()\n    logger.info(message, value=SomeValue(10))\n\n\nAnother danger due to external input is the possibility of a log injection attack. Consider that you may need to escape user values before logging them: `Is your Python code vulnerable to log injection? <https://dev.arie.bovenberg.net/blog/is-your-python-code-vulnerable-to-log-injection/>`_\n\n.. code::\n\n    logger.add(\"file.log\", format=\"{level} {message}\")\n\n    # If value is \"Josh logged in.\\nINFO User James\" then there will appear to be two log entries.\n    username = external_data()\n    logger.info(\"User \" + username + \" logged in.\")\n\n\nNote that by default, Loguru will display the value of existing variables when an ``Exception`` is logged. This is very useful for debugging but could lead to credentials appearing in log files. Make sure to turn it off in production (or set the ``LOGURU_DIAGNOSE=NO`` environment variable).\n\n.. code::\n\n    logger.add(\"out.log\", diagnose=False)\n\n\nAnother thing you should consider is to change the access permissions of your log file. Loguru creates files using the built-in |open| function, which means by default they might be read by a different user than the owner. If this is not desirable, be sure to modify the default access rights.\n\n.. code::\n\n    def opener(file, flags):\n        return os.open(file, flags, 0o600)\n\n    logger.add(\"combined.log\", opener=opener)\n\n\nAvoiding logs to be printed twice on the terminal\n-------------------------------------------------\n\nThe logger is pre-configured for convenience with a default handler which writes messages to |sys.stderr|. You should |remove| it first if you plan to |add| another handler logging messages to the console, otherwise you may end up with duplicated logs.\n\n.. code::\n\n    logger.remove()  # Remove all handlers added so far, including the default one.\n    logger.add(sys.stderr, level=\"WARNING\")\n\n\n.. _changing-level-of-existing-handler:\n\nChanging the level of an existing handler\n-----------------------------------------\n\nOnce a handler has been added, it is actually not possible to update it. This is a deliberate choice in order to keep the Loguru's API minimal. Several solutions are possible, though, if you need to change the configured ``level`` of a handler. Chose the one that best fits your use case.\n\nThe most straightforward workaround is to |remove| your handler and then re-|add| it with the updated ``level`` parameter. To do so, you have to keep a reference to the identifier number returned while adding a handler::\n\n    handler_id = logger.add(sys.stderr, level=\"WARNING\")\n\n    logger.info(\"Logging 'WARNING' or higher messages only\")\n\n    ...\n\n    logger.remove(handler_id)  # For the default handler, it's actually '0'.\n    logger.add(sys.stderr, level=\"DEBUG\")\n\n    logger.debug(\"Logging 'DEBUG' messages too\")\n\n\nAlternatively, you can combine the |bind| method with the ``filter`` argument to provide a function dynamically filtering logs based on their level::\n\n    def my_filter(record):\n        if record[\"extra\"].get(\"warn_only\"):  # \"warn_only\" is bound to the logger and set to 'True'\n            return record[\"level\"].no >= logger.level(\"WARNING\").no\n        return True  # Fallback to default 'level' configured while adding the handler\n\n\n    logger.add(sys.stderr, filter=my_filter, level=\"DEBUG\")\n\n    # Use this logger first, debug messages are filtered out\n    logger = logger.bind(warn_only=True)\n    logger.warn(\"Initialization in progress\")\n\n    # Then you can use this one to log all messages\n    logger = logger.bind(warn_only=False)\n    logger.debug(\"Back to debug messages\")\n\n\nFinally, more advanced control over handler's level can be achieved by using a callable object as the ``filter``::\n\n    min_level = logger.level(\"DEBUG\").no\n\n    def filter_by_level(record):\n        return record[\"level\"].no >= min_level\n\n\n    logger.remove()\n    logger.add(sys.stderr, filter=filter_by_level, level=0)\n\n    logger.debug(\"Logged\")\n\n    min_level = logger.level(\"WARNING\").no\n\n    logger.debug(\"Not logged\")\n\n\nAlso, note that if the default handler configuration, such as the default level, does not suit your needs, it can be adjusted :ref:`through environment variables <env>`.\n\n.. _configuring-loguru-as-lib-or-app:\n\nConfiguring Loguru to be used by a library or an application\n------------------------------------------------------------\n\nA clear distinction must be made between the use of Loguru within a library or an application.\n\nIn case of an application, you can add handlers from anywhere in your code. It's advised though to configure the logger from within a |if-name-equals-main|_ block inside the entry file of your script.\n\nHowever, if your work is intended to be used as a library, you usually should not add any handler. This is user responsibility to configure logging according to its preferences, and it's better not to interfere with that. Indeed, since Loguru is based on a single common logger, handlers added by a library will also receive user logs, which is generally not desirable.\n\nBy default, a third-library should not emit logs except if specifically requested. For this reason, there exist the |disable| and |enable| methods. Make sure to first call ``logger.disable(\"mylib\")``. This avoids library logs to be mixed with those of the user. The user can always call ``logger.enable(\"mylib\")`` if he wants to access the logs of your library.\n\nIf you would like to ease logging configuration for your library users, it is advised to provide a function like ``configure_logger()`` in charge of adding the desired handlers. This will allow the user to activate the logging only if he needs to.\n\nTo summarize, let's look at this hypothetical package (none of the listed files are required, it all depends on how you plan your project to be used):\n\n.. code:: text\n\n    mypackage\n    ├── __init__.py\n    ├── __main__.py\n    ├── main.py\n    └── mymodule.py\n\nFiles relate to Loguru as follows:\n\n* File ``__init__.py``:\n\n    * It is the entry point when your project is used as a library (``import mypackage``).\n    * It should contain ``logger.disable(\"mypackage\")`` unconditionally at the top level.\n    * It should not call ``logger.add()`` as it modifies handlers configuration.\n\n* File ``__main__.py``:\n\n    * It is the entry point when your project is used as an application (``python -m mypackage``).\n    * It can contain logging configuration unconditionally at the top level.\n\n* File ``main.py``:\n\n    * It is the entry point when your project is used as a script (``python mypackage/main.py``).\n    * It can contain logging configuration inside an ``if __name__ == \"__main__\":`` block.\n\n* File ``mymodule.py``:\n\n    * It is an internal module used by your project.\n    * It can use the ``logger`` simply by importing it.\n    * It does not need to configure anything.\n\n\n.. _inter-process-communication:\n\nTransmitting log messages across network, processes or Gunicorn workers\n-----------------------------------------------------------------------\n\nIt is possible to send and receive logs between different processes and even between different computers if needed. Once the connection is established between the two Python programs, this requires serializing the logging record in one side while re-constructing the message on the other hand. Keep in mind though that `pickling is unsafe <https://intoli.com/blog/dangerous-pickles/>`_, you should use this with care.\n\nThe first thing you will need is to run a server responsible for receiving log messages emitted by other processes::\n\n    # server.py\n    import socketserver\n    import pickle\n    import struct\n    import sys\n    from loguru import logger\n\n\n    class LoggingRequestHandler(socketserver.StreamRequestHandler):\n\n        def handle(self):\n            while True:\n                chunk = self.connection.recv(4)\n                if len(chunk) < 4:\n                    break\n                slen = struct.unpack(\">L\", chunk)[0]\n                chunk = self.connection.recv(slen)\n                while len(chunk) < slen:\n                    chunk = chunk + self.connection.recv(slen - len(chunk))\n                record = pickle.loads(chunk)\n                level, message = record[\"level\"].name, record[\"message\"]\n                logger.patch(lambda r, record=record: r.update(record)).log(level, message)\n\n\n    if __name__ == \"__main__\":\n        # Configure the logger with desired handlers.\n        logger.configure(handlers=[{\"sink\": \"server.log\"}, {\"sink\": sys.stderr}])\n\n        # Setup the server to receive log messages from other processes.\n        with socketserver.TCPServer((\"localhost\", 9999), LoggingRequestHandler) as server:\n            server.serve_forever()\n\n\nThen, you need your clients to send messages using a specific handler::\n\n    # client.py\n    import socket\n    import struct\n    import time\n    import pickle\n    from loguru import logger\n\n\n    class SocketHandler:\n\n        def __init__(self, host, port):\n            self._host = host\n            self._port = port\n\n        def write(self, message):\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock.connect((self._host, self._port))\n            record = message.record\n            data = pickle.dumps(record)\n            slen = struct.pack(\">L\", len(data))\n            sock.send(slen + data)\n\n\n    if __name__ == \"__main__\":\n        # Setup the handler sending log messages to the server.\n        logger.configure(handlers=[{\"sink\": SocketHandler('localhost', 9999)}])\n\n        # Proceed with standard logger usage.\n        logger.info(\"Sending message from the client\")\n\n\nMake sure that the server is running while the clients are logging messages, and note that they must communicate on the same port.\n\nAnother example, when using Gunicorn and FastAPI you should add the previously defined ``SocketHandler`` to each of the running workers, possibly like so::\n\n    from contextlib import asynccontextmanager\n    from fastapi import FastAPI\n    from loguru import logger\n\n    @asynccontextmanager\n    async def lifespan(app: FastAPI):\n        \"\"\"Setup the server instance (executed once for each worker).\"\"\"\n        logger.configure(handlers=[{\"sink\": SocketHandler(\"localhost\", 9999)}])\n        logger.debug(\"Worker is initializing\")\n        yield\n\n    app = FastAPI(lifespan=lifespan)\n\nWhen sharing the logger between processes is not technically possible, using a server handling TCP requests is the most reliable way of guaranteeing the integrity of logged messages.\n\n\nUsing ZMQ to send and receive log messages\n------------------------------------------\n\nThird-party libraries like |zmq|_ can be leveraged to exchange messages between multiple processes. Here is an example of a basic server and client:\n\n.. code::\n\n    # client.py\n    import zmq\n    from zmq.log.handlers import PUBHandler\n    from logging import Formatter\n    from loguru import logger\n\n    socket = zmq.Context().socket(zmq.PUB)\n    socket.connect(\"tcp://127.0.0.1:12345\")\n    handler = PUBHandler(socket)\n    handler.setFormatter(Formatter(\"%(message)s\"))\n    logger.add(handler)\n\n    logger.info(\"Logging from client\")\n\n\n.. code::\n\n    # server.py\n    import sys\n    import zmq\n    from loguru import logger\n\n    socket = zmq.Context().socket(zmq.SUB)\n    socket.bind(\"tcp://127.0.0.1:12345\")\n    socket.subscribe(\"\")\n\n    logger.configure(handlers=[{\"sink\": sys.stderr, \"format\": \"{message}\"}])\n\n    while True:\n        _, message = socket.recv_multipart()\n        logger.info(message.decode(\"utf8\").strip())\n\n\n\nResolving ``UnicodeEncodeError`` and other encoding issues\n----------------------------------------------------------\n\nWhen you write a log message, the handler may need to encode the received `unicode`_ string to a specific sequence of bytes. The ``encoding`` used to perform this operation varies depending on the sink type and your environment. Problem may occur if you try to write a character which is not supported by the handler ``encoding``. In such case, it's likely that Python will raise an |UnicodeEncodeError|.\n\nFor example, this may happen while printing to the terminal::\n\n    print(\"天\")\n    # UnicodeEncodeError: 'charmap' codec can't encode character '\\u5929' in position 0: character maps to <undefined>\n\nA similar error may occur while writing to a file which has not been opened using an appropriate encoding. Most common problem happen while logging to standard output or to a file on Windows. So, how to avoid such error? Simply by properly configuring your handler so that it can process any kind of unicode string.\n\nIf you are encountering this error while logging to ``stdout``, you have several options:\n\n* Use |sys.stderr| instead of |sys.stdout| (the former will escape faulty characters rather than raising exception)\n* Set the :envvar:`PYTHONIOENCODING` environment variable to ``utf-8``\n* Call |sys.stdout.reconfigure| with ``encoding='utf-8'`` and / or ``errors='backslashreplace'``\n\nIf you are using a file sink, you can configure the ``errors`` or ``encoding`` parameter while adding the handler like ``logger.add(\"file.log\", encoding=\"utf8\")`` for example.  All additional ``**kwargs`` argument are passed to the built-in |open| function.\n\nFor other types of handlers, you have to check if there is a way to parametrize encoding or fallback policy.\n\n\nLogging entry and exit of functions with a decorator\n----------------------------------------------------\n\nIn some cases, it might be useful to log entry and exit values of a function. Although Loguru doesn't provide such feature out of the box, it can be easily implemented by using Python decorators::\n\n    import functools\n    from loguru import logger\n\n\n    def logger_wraps(*, entry=True, exit=True, level=\"DEBUG\"):\n\n        def wrapper(func):\n            name = func.__name__\n\n            @functools.wraps(func)\n            def wrapped(*args, **kwargs):\n                logger_ = logger.opt(depth=1)\n                if entry:\n                    logger_.log(level, \"Entering '{}' (args={}, kwargs={})\", name, args, kwargs)\n                result = func(*args, **kwargs)\n                if exit:\n                    logger_.log(level, \"Exiting '{}' (result={})\", name, result)\n                return result\n\n            return wrapped\n\n        return wrapper\n\nYou could then use it like this::\n\n    @logger_wraps()\n    def foo(a, b, c):\n        logger.info(\"Inside the function\")\n        return a * b * c\n\n    def bar():\n        foo(2, 4, c=8)\n\n    bar()\n\n\nWhich would result in:\n\n.. code-block:: none\n\n    2019-04-07 11:08:44.198 | DEBUG    | __main__:bar:30 - Entering 'foo' (args=(2, 4), kwargs={'c': 8})\n    2019-04-07 11:08:44.198 | INFO     | __main__:foo:26 - Inside the function\n    2019-04-07 11:08:44.198 | DEBUG    | __main__:bar:30 - Exiting 'foo' (result=64)\n\n\nHere is another simple example to record timing of a function::\n\n    def timeit(func):\n\n        def wrapped(*args, **kwargs):\n            start = time.time()\n            result = func(*args, **kwargs)\n            end = time.time()\n            logger.debug(\"Function '{}' executed in {:f} s\", func.__name__, end - start)\n            return result\n\n        return wrapped\n\nFinally, here is an example of a generic wrapper that combines a success message with error handling during function execution::\n\n    def with_logging(func):\n        @functools.wraps(func)\n        @logger.catch(message=f\"Error in {func.__name__}\")\n        def wrapper(*args, **kwargs):\n            result = func(*args, **kwargs)\n            logger.success(f\"Successfully completed {func.__name__}\")\n            return result\n\n        return wrapper\n\n    @with_logging\n    def may_fail(x):\n        if x < 0:\n            raise ValueError(\"Negative value!\")\n        return x * 2\n\n    may_fail(10)  # Should log success\n    may_fail(-5)  # Should log an error\n\n\nUsing logging function based on custom added levels\n---------------------------------------------------\n\nAfter adding a new level, it's habitually used with the |log| function::\n\n    logger.level(\"foobar\", no=33, icon=\"🤖\", color=\"<blue>\")\n\n    logger.log(\"foobar\", \"A message\")\n\n\nFor convenience, one can assign a new logging function which automatically uses the custom added level::\n\n    from functools import partialmethod\n\n    logger.__class__.foobar = partialmethod(logger.__class__.log, \"foobar\")\n\n    logger.foobar(\"A message\")\n\n\nThe new method need to be added only once and will be usable across all your files importing the ``logger``. Assigning the method to ``logger.__class__`` rather than ``logger`` directly ensures that it stays available even after calling ``logger.bind()``, ``logger.patch()`` and ``logger.opt()`` (because these functions return a new ``logger`` instance).\n\n\nSetting permissions on created log files\n----------------------------------------\n\nTo set desired permissions on created log files, use the ``opener`` argument to pass in a custom opener with permissions octal::\n\n    def opener(file, flags):\n        return os.open(file, flags, 0o600)  # read/write by owner only\n\n    logger.add(\"foo.log\", rotation=\"100 kB\", opener=opener)\n\nWhen using an opener argument, all created log files including ones created during rotation will use the initially provided opener.\n\nNote that the provided mode will be masked out by the OS `umask <https://en.wikipedia.org/wiki/Umask>`_ value (describing which bits are *not* to be set when creating a file or directory). This value is conventionally equals to ``0o022``, which means specifying a ``0o666`` mode will result in a ``0o666 - 0o022 = 0o644`` file permission in this case (which is actually the default). It is possible to change the umask value by first calling |os.umask|, but this needs to be done with careful consideration, as it changes the value globally and can cause security issues.\n\n\nPreserving an ``opt()`` parameter for the whole module\n------------------------------------------------------\n\nSupposing you wish to color each of your log messages without having to call ``logger.opt(colors=True)`` every time, you can add this at the very beginning of your module::\n\n    logger = logger.opt(colors=True)\n\n    logger.info(\"It <green>works</>!\")\n\nHowever, it should be noted that it's not possible to chain |opt| calls, using this method again will reset the ``colors`` option to its default value (which is ``False``). For this reason, it is also necessary to patch the |opt| method so that all subsequent calls continue to use the desired value::\n\n    from functools import partial\n\n    logger = logger.opt(colors=True)\n    logger.opt = partial(logger.opt, colors=True)\n\n    logger.opt(raw=True).info(\"It <green>still</> works!\\n\")\n\n\nSerializing log messages using a custom function\n------------------------------------------------\n\nEach handler added with ``serialize=True`` will create messages by converting the logging record to a valid JSON string. Depending on the sink for which the messages are intended, it may be useful to make changes to the generated string. Instead of using the ``serialize`` parameter, you can implement your own serialization function and use it directly in your sink::\n\n    def serialize(record):\n        subset = {\"timestamp\": record[\"time\"].timestamp(), \"message\": record[\"message\"]}\n        return json.dumps(subset)\n\n    def sink(message):\n        serialized = serialize(message.record)\n        print(serialized)\n\n    logger.add(sink)\n\n\nIf you need to send structured logs to a file (or any kind of sink in general), a similar result can be obtained by using a custom ``format`` function::\n\n    def formatter(record):\n        # Note this function returns the string to be formatted, not the actual message to be logged\n        record[\"extra\"][\"serialized\"] = serialize(record)\n        return \"{extra[serialized]}\\n\"\n\n    logger.add(\"file.log\", format=formatter)\n\n\nYou can also use |patch| for this, so the serialization function will be called only once in case you want to use it in multiple sinks::\n\n    def patching(record):\n        record[\"extra\"][\"serialized\"] = serialize(record)\n\n    logger = logger.patch(patching)\n\n    # Note that if \"format\" is not a function, possible exception will be appended to the message\n    logger.add(sys.stderr, format=\"{extra[serialized]}\")\n    logger.add(\"file.log\", format=\"{extra[serialized]}\")\n\n\nAdapting colors and format of logged messages dynamically\n---------------------------------------------------------\n\nIt is possible to customize the colors of your logs thanks to several :ref:`markup tags <color>`. Those are used to configure the ``format`` of your handler. By creating a appropriate formatting function, you can easily define colors depending on the logged message.\n\nFor example, if you want to associate each module with a unique color::\n\n    from collections import defaultdict\n    from random import choice\n\n    colors = [\"blue\", \"cyan\", \"green\", \"magenta\", \"red\", \"yellow\"]\n    color_per_module = defaultdict(lambda: choice(colors))\n\n    def formatter(record):\n        color_tag = color_per_module[record[\"name\"]]\n        return \"<\" + color_tag + \">[{name}]</> <bold>{message}</>\\n{exception}\"\n\n    logger.add(sys.stderr, format=formatter)\n\n\nIf you need to dynamically colorize the ``record[\"message\"]``, make sure that the color tags appear in the returned format instead of modifying the message::\n\n    def rainbow(text):\n        colors = [\"red\", \"yellow\", \"green\", \"cyan\", \"blue\", \"magenta\"]\n        chars = (\"<{}>{}</>\".format(colors[i % len(colors)], c) for i, c in enumerate(text))\n        return \"\".join(chars)\n\n    def formatter(record):\n        rainbow_message = rainbow(record[\"message\"])\n        # Prevent '{}' in message (if any) to be incorrectly parsed during formatting\n        escaped = rainbow_message.replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n        return \"<b>{time}</> \" + escaped + \"\\n{exception}\"\n\n    logger.add(sys.stderr, format=formatter)\n\n\nDynamically formatting messages to properly align values with padding\n---------------------------------------------------------------------\n\nThe default formatter is unable to vertically align log messages because the length of ``{name}``, ``{function}`` and ``{line}`` are not fixed.\n\nOne workaround consists of using padding with some maximum value that should suffice most of the time. For this purpose, you can use Python's string formatting directives, like in this example::\n\n    fmt = \"{time} | {level: <8} | {name: ^15} | {function: ^15} | {line: >3} | {message}\"\n    logger.add(sys.stderr, format=fmt)\n\nHere, ``<``, ``^`` and ``>`` will left, center, and right-align the respective keys, and pad them to a maximum length.\n\nOther solutions are possible by using a formatting function or class. For example, it is possible to dynamically adjust the padding length based on previously encountered values::\n\n    class Formatter:\n\n        def __init__(self):\n            self.padding = 0\n            self.fmt = \"{time} | {level: <8} | {name}:{function}:{line}{extra[padding]} | {message}\\n{exception}\"\n\n        def format(self, record):\n            length = len(\"{name}:{function}:{line}\".format(**record))\n            self.padding = max(self.padding, length)\n            record[\"extra\"][\"padding\"] = \" \" * (self.padding - length)\n            return self.fmt\n\n    formatter = Formatter()\n\n    logger.remove()\n    logger.add(sys.stderr, format=formatter.format)\n\n\nCustomizing the formatting of exceptions\n----------------------------------------\n\nLoguru will automatically add the traceback of occurring exception while using ``logger.exception()`` or ``logger.opt(exception=True)``::\n\n    def inverse(x):\n        try:\n            1 / x\n        except ZeroDivisionError:\n            logger.exception(\"Oups...\")\n\n    if __name__ == \"__main__\":\n        inverse(0)\n\n.. code-block:: none\n\n    2019-11-15 10:01:13.703 | ERROR    | __main__:inverse:8 - Oups...\n    Traceback (most recent call last):\n    File \"foo.py\", line 6, in inverse\n        1 / x\n    ZeroDivisionError: division by zero\n\nIf the handler is added with ``backtrace=True``, the traceback is extended to see where the exception came from:\n\n.. code-block:: none\n\n    2019-11-15 10:11:32.829 | ERROR    | __main__:inverse:8 - Oups...\n    Traceback (most recent call last):\n      File \"foo.py\", line 16, in <module>\n        inverse(0)\n    > File \"foo.py\", line 6, in inverse\n        1 / x\n    ZeroDivisionError: division by zero\n\nIf the handler is added with ``diagnose=True``, then the traceback is annotated to see what caused the problem:\n\n.. code-block:: none\n\n    Traceback (most recent call last):\n\n    File \"foo.py\", line 6, in inverse\n        1 / x\n            └ 0\n\n    ZeroDivisionError: division by zero\n\nIt is possible to further personalize the formatting of exception by adding an handler with a custom ``format`` function. For example, supposing you want to format errors using the |stackprinter|_ library::\n\n    import stackprinter\n\n    def format(record):\n        format_ = \"{time} {message}\\n\"\n\n        if record[\"exception\"] is not None:\n            record[\"extra\"][\"stack\"] = stackprinter.format(record[\"exception\"])\n            format_ += \"{extra[stack]}\\n\"\n\n        return format_\n\n    logger.add(sys.stderr, format=format)\n\n.. code-block:: none\n\n    2019-11-15T10:46:18.059964+0100 Oups...\n    File foo.py, line 17, in inverse\n        15   def inverse(x):\n        16       try:\n    --> 17           1 / x\n        18       except ZeroDivisionError:\n        ..................................................\n        x = 0\n        ..................................................\n\n    ZeroDivisionError: division by zero\n\n\nDisplaying a stacktrace without using the error context\n-------------------------------------------------------\n\nIt may be useful in some cases to display the traceback at the time your message is logged, while no exceptions have been raised. Although this feature is not built-in into Loguru as it is more related to debugging than logging, it is possible to |patch| your logger and then display the stacktrace as needed (using the |traceback| module)::\n\n    import traceback\n\n    def add_traceback(record):\n        extra = record[\"extra\"]\n        if extra.get(\"with_traceback\", False):\n            extra[\"traceback\"] = \"\\n\" + \"\".join(traceback.format_stack())\n        else:\n            extra[\"traceback\"] = \"\"\n\n    logger = logger.patch(add_traceback)\n    logger.add(sys.stderr, format=\"{time} - {message}{extra[traceback]}\")\n\n    logger.info(\"No traceback\")\n    logger.bind(with_traceback=True).info(\"With traceback\")\n\nHere is another example that demonstrates how to prefix the logged message with the full call stack::\n\n    import traceback\n    from itertools import takewhile\n\n    def tracing_formatter(record):\n        # Filter out frames coming from Loguru internals\n        frames = takewhile(lambda f: \"/loguru/\" not in f.filename, traceback.extract_stack())\n        stack = \" > \".join(\"{}:{}:{}\".format(f.filename, f.name, f.lineno) for f in frames)\n        record[\"extra\"][\"stack\"] = stack\n        return \"{level} | {extra[stack]} - {message}\\n{exception}\"\n\n    def foo():\n        logger.info(\"Deep call\")\n\n    def bar():\n        foo()\n\n    logger.remove()\n    logger.add(sys.stderr, format=tracing_formatter)\n\n    bar()\n    # Output: \"INFO | script.py:<module>:23 > script.py:bar:18 > script.py:foo:15 - Deep call\"\n\n\nManipulating newline terminator to write multiple logs on the same line\n-----------------------------------------------------------------------\n\nYou can temporarily log a message on a continuous line by combining the use of |bind|, |opt| and a custom ``format`` function. This is especially useful if you want to illustrate a step-by-step process in progress, for example::\n\n    def formatter(record):\n        end = record[\"extra\"].get(\"end\", \"\\n\")\n        return \"[{time}] {message}\" + end + \"{exception}\"\n\n    logger.add(sys.stderr, format=formatter)\n    logger.add(\"foo.log\", mode=\"w\")\n\n    logger.bind(end=\"\").debug(\"Progress: \")\n\n    for _ in range(5):\n        logger.opt(raw=True).debug(\".\")\n\n    logger.opt(raw=True).debug(\"\\n\")\n\n    logger.info(\"Done\")\n\n.. code-block:: none\n\n    [2020-03-26T22:47:01.708016+0100] Progress: .....\n    [2020-03-26T22:47:01.709031+0100] Done\n\nNote, however, that you may encounter difficulties depending on the sinks you use. Logging is not always appropriate for this type of end-user message.\n\n\nCapturing standard ``stdout``, ``stderr`` and ``warnings``\n----------------------------------------------------------\n\nThe use of logging should be privileged over |print|, yet, it may happen that you don't have plain control over code executed in your application. If you wish to capture standard output, you can suppress |sys.stdout| (and |sys.stderr|) with a custom stream object using |contextlib.redirect_stdout|. You have to take care of first removing the default handler, and not adding a new stdout sink once redirected or that would cause dead lock (you may use |sys.__stdout__| instead)::\n\n    import contextlib\n    import sys\n    from loguru import logger\n\n    class StreamToLogger:\n\n        def __init__(self, level=\"INFO\"):\n            self._level = level\n\n        def write(self, buffer):\n            for line in buffer.rstrip().splitlines():\n                logger.opt(depth=1).log(self._level, line.rstrip())\n\n        def flush(self):\n            pass\n\n    logger.remove()\n    logger.add(sys.__stdout__)\n\n    stream = StreamToLogger()\n    with contextlib.redirect_stdout(stream):\n        print(\"Standard output is sent to added handlers.\")\n\n\nYou may also capture warnings emitted by your application by replacing |warnings.showwarning|::\n\n    import warnings\n    from loguru import logger\n\n    showwarning_ = warnings.showwarning\n\n    def showwarning(message, *args, **kwargs):\n        logger.opt(depth=2).warning(message)\n        showwarning_(message, *args, **kwargs)\n\n    warnings.showwarning = showwarning\n\n\nAlternatively, if you want to emit warnings based on logged messages, you can simply use |warnings.warn| as a sink::\n\n\n    logger.add(warnings.warn, format=\"{message}\", filter=lambda record: record[\"level\"].name == \"WARNING\")\n\n\nCircumventing modules whose ``__name__`` value is absent\n--------------------------------------------------------\n\nLoguru makes use of the global variable ``__name__`` to determine from where the logged message is coming from. However, it may happen in very specific situation (like some Dask distributed environment) that this value is not set. In such case, Loguru will use ``None`` to make up for the lack of the value. This implies that if you want to |disable| messages coming from such special module, you have to explicitly call ``logger.disable(None)``.\n\nSimilar considerations should be taken into account while dealing with the ``filter`` attribute. As ``__name__`` is missing, Loguru will assign the ``None`` value to the ``record[\"name\"]`` entry. It also means that once formatted in your log messages, the ``{name}`` token will be equals to ``\"None\"``. This can be worked around by manually overriding the ``record[\"name\"]`` value using |patch| from inside the faulty module::\n\n    # If Loguru fails to retrieve the proper \"name\" value, assign it manually\n    logger = logger.patch(lambda record: record.update(name=\"my_module\"))\n\nYou probably should not worry about all of this except if you noticed that your code is subject to this behavior.\n\n\nInteroperability with ``tqdm`` iterations\n-----------------------------------------\n\nTrying to use the Loguru's ``logger`` during an iteration wrapped by the ``tqdm`` library may disturb the displayed progress bar. As a workaround, one can use the ``tqdm.write()`` function instead of writings logs directly to ``sys.stderr``::\n\n    import time\n\n    from loguru import logger\n    from tqdm import tqdm\n\n    logger.remove()\n    logger.add(lambda msg: tqdm.write(msg, end=\"\"), colorize=True)\n\n    logger.info(\"Initializing\")\n\n    for x in tqdm(range(100)):\n        logger.info(\"Iterating #{}\", x)\n        time.sleep(0.1)\n\n\nYou may encounter problems with colorization of your logs after importing ``tqdm`` using Spyder on Windows. This issue is discussed in `GH#132`_. You can easily circumvent the problem by calling ``colorama.deinit()`` right after your import.\n\n\nUsing Loguru's ``logger`` within a Cython module\n------------------------------------------------\n\nLoguru and Cython do not interoperate very well. This is because Loguru (and logging generally) heavily relies on Python stack frames while Cython, being an alternative Python implementation, try to get rid of these frames for optimization reasons.\n\nCalling the ``logger`` from code compiled with Cython may result in \"incomplete\" logs (missing call context):\n\n.. code-block:: none\n\n    2024-11-26 15:58:48.985 | INFO     | None:<unknown>:0 - Message from Cython!\n\nThis happens when Loguru tries to access a stack frame which has been suppressed by Cython. In such a case, there is no way for Loguru to retrieve contextual information of the logged message.\n\nYou can update the default ``format`` of your handlers and omit the uninteresting fields. You can also tries to |patch| the ``logger`` to manually add information you may know about the caller, for example::\n\n    logger = logger.patch(lambda record: record.update(name=\"my_cython_module\"))\n\nNote that the ``\"name\"`` attribute of the log record is set to ``None`` when the frame is unavailable.\n\n\n.. _creating-independent-loggers:\n\nCreating independent loggers with separate set of handlers\n----------------------------------------------------------\n\nLoguru is fundamentally designed to be usable with exactly one global ``logger`` object dispatching logging messages to the configured handlers. In some circumstances, it may be useful to have specific messages logged to specific handlers.\n\nFor example, supposing you want to split your logs in two files based on an arbitrary identifier, you can achieve that by combining |bind| and ``filter``::\n\n    from loguru import logger\n\n    def task_A():\n        logger_a = logger.bind(task=\"A\")\n        logger_a.info(\"Starting task A\")\n        do_something()\n        logger_a.success(\"End of task A\")\n\n    def task_B():\n        logger_b = logger.bind(task=\"B\")\n        logger_b.info(\"Starting task B\")\n        do_something_else()\n        logger_b.success(\"End of task B\")\n\n    logger.add(\"file_A.log\", filter=lambda record: record[\"extra\"][\"task\"] == \"A\")\n    logger.add(\"file_B.log\", filter=lambda record: record[\"extra\"][\"task\"] == \"B\")\n\n    task_A()\n    task_B()\n\nThat way, ``\"file_A.log\"`` and ``\"file_B.log\"`` will only contains logs from respectively the ``task_A()`` and ``task_B()`` function.\n\nNow, supposing that you have a lot of these tasks. It may be a bit cumbersome to configure every handlers like this. Most importantly, it may unnecessarily slow down your application as each log will need to be checked by the ``filter`` function of each handler. In such case, it is recommended to rely on the |copy.deepcopy| built-in method that will create an independent ``logger`` object. If you add a handler to a deep copied ``logger``, it will not be shared with others functions using the original ``logger``::\n\n    import copy\n    from loguru import logger\n\n    def task(task_id, logger):\n        logger.info(\"Starting task {}\", task_id)\n        do_something(task_id)\n        logger.success(\"End of task {}\", task_id)\n\n    logger.remove()\n\n    for task_id in [\"A\", \"B\", \"C\", \"D\", \"E\"]:\n        logger_ = copy.deepcopy(logger)\n        logger_.add(\"file_%s.log\" % task_id)\n        task(task_id, logger_)\n\nNote that you may encounter errors if you try to copy a ``logger`` to which non-picklable handlers have been added. For this reason, it is generally advised to remove all handlers before calling ``copy.deepcopy(logger)``.\n\n\n.. _multiprocessing-compatibility:\n\nCompatibility with ``multiprocessing`` using ``enqueue`` argument\n-----------------------------------------------------------------\n\nOn Linux, thanks to |os.fork| there is no pitfall while using the ``logger`` inside another process started by the |multiprocessing| module. The child process will automatically inherit added handlers, the ``enqueue=True`` parameter is optional but is recommended as it would avoid concurrent access of your sink::\n\n    # Linux implementation\n    import multiprocessing\n    from loguru import logger\n\n    def my_process():\n        logger.info(\"Executing function in child process\")\n        logger.complete()\n\n    if __name__ == \"__main__\":\n        logger.add(\"file.log\", enqueue=True)\n\n        process = multiprocessing.Process(target=my_process)\n        process.start()\n        process.join()\n\n        logger.info(\"Done\")\n\nThings get a little more complicated on Windows. Indeed, this operating system does not support forking, so Python has to use an alternative method to create sub-processes called \"spawning\". This procedure requires the whole file where the child process is created to be reloaded from scratch. This does not interoperate very well with Loguru, causing handlers to be added twice without any synchronization or, on the contrary, not being added at all (depending on ``add()`` and ``remove()`` being called inside or outside the ``__main__`` branch). For this reason, the ``logger`` object need to be explicitly passed as an initializer argument of your child process::\n\n    # Windows implementation\n    import multiprocessing\n    from loguru import logger\n\n    def my_process(logger_):\n        logger_.info(\"Executing function in child process\")\n        logger_.complete()\n\n    if __name__ == \"__main__\":\n        logger.remove()  # Default \"sys.stderr\" sink is not picklable\n        logger.add(\"file.log\", enqueue=True)\n\n        process = multiprocessing.Process(target=my_process, args=(logger, ))\n        process.start()\n        process.join()\n\n        logger.info(\"Done\")\n\nWindows requires the added sinks to be picklable or otherwise will raise an error while creating the child process. Many stream objects like standard output and file descriptors are not picklable. In such case, the ``enqueue=True`` argument is required as it will allow the child process to only inherit the queue object where logs are sent.\n\nThe |multiprocessing| library is also commonly used to start a pool of workers using for example |Pool.map| or |Pool.apply|. Again, it will work flawlessly on Linux, but it will require some tinkering on Windows. You will probably not be able to pass the ``logger`` as an argument for your worker functions because it needs to be picklable, but although handlers added using ``enqueue=True`` are \"inheritable\", they are not \"picklable\". Instead, you will need to make use of the ``initializer`` and ``initargs`` parameters while creating the |Pool| object in a way allowing your workers to access the shared ``logger``. You can either assign it to a class attribute or override the global logger of your child processes:\n\n.. code::\n\n    # workers_a.py\n    class Worker:\n\n        _logger = None\n\n        @staticmethod\n        def set_logger(logger_):\n            Worker._logger = logger_\n\n        def work(self, x):\n            self._logger.info(\"Square rooting {}\", x)\n            return x**0.5\n\n\n.. code::\n\n    # workers_b.py\n    from loguru import logger\n\n    def set_logger(logger_):\n        global logger\n        logger = logger_\n\n    def work(x):\n        logger.info(\"Square rooting {}\", x)\n        return x**0.5\n\n\n.. code::\n\n    # main.py\n    from multiprocessing import Pool\n    from loguru import logger\n    import workers_a\n    import workers_b\n\n    if __name__ == \"__main__\":\n        logger.remove()\n        logger.add(\"file.log\", enqueue=True)\n\n        worker = workers_a.Worker()\n        with Pool(4, initializer=worker.set_logger, initargs=(logger, )) as pool:\n            results = pool.map(worker.work, [1, 10, 100])\n\n        with Pool(4, initializer=workers_b.set_logger, initargs=(logger, )) as pool:\n            results = pool.map(workers_b.work, [1, 10, 100])\n\n        logger.info(\"Done\")\n\nIndependently of the operating system, note that the process in which a handler is added with ``enqueue=True`` is in charge of the queue internally used. This means that you should avoid to ``.remove()`` such handler from the parent process is any child is likely to continue using it. More importantly, note that a |Thread| is started internally to consume the queue. Therefore, it is recommended to call |complete| before leaving |Process| to make sure the queue is left in a stable state.\n\nAnother thing to keep in mind when dealing with multiprocessing is the fact that handlers created with ``enqueue=True`` create a queue internally in the default multiprocessing context. If they are passed through to a subprocesses instantiated within a different context (e.g. with ``multiprocessing.get_context(\"spawn\")`` on linux, where the default context is ``\"fork\"``) it will most likely result in crashing the subprocess. This is also noted in the `python multiprocessing docs <https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods>`_. To prevent any problems, you should specify the context to be used by Loguru while adding the handler. This can be done by passing the ``context`` argument to the ``add()`` method::\n\n    import multiprocessing\n    from loguru import logger\n    import workers_a\n\n    if __name__ == \"__main__\":\n        context = multiprocessing.get_context(\"spawn\")\n\n        logger.remove()\n        logger.add(\"file.log\", enqueue=True, context=context)\n\n        worker = workers_a.Worker()\n        with context.Pool(4, initializer=worker.set_logger, initargs=(logger, )) as pool:\n            results = pool.map(worker.work, [1, 10, 100])\n\n\n.. _recipes-testing:\n\nUnit testing logs emitted by Loguru\n-----------------------------------\n\nWhen migrating an existing project from standard :mod:`logging`, it can be useful to migrate your existing test cases too. In particular, you may want to:\n\n- Migrate ``assertLogs()`` from :mod:`unittest` to Loguru: :ref:`migration-assert-logs`\n- Migrate ``caplog`` from |pytest|_ to Loguru: :ref:`migration-caplog`\n\nAlternatively, logging calls can be tested using |logot|_, a high-level log testing library with built-in support for Loguru::\n\n    from logot import Logot, logged\n\n    def test_something(logot: Logot) -> None:\n        do_something()\n        logot.assert_logged(logged.info(\"Something was done\"))\n\nEnable Loguru log capture in your |pytest|_ configuration:\n\n.. code:: toml\n\n   [tool.pytest.ini_options]\n   logot_capturer = \"logot.loguru.LoguruCapturer\"\n\n.. seealso::\n\n    See `using logot with Loguru <https://logot.readthedocs.io/latest/integrations/loguru.html>`_ for more information\n    about `configuring pytest <https://logot.readthedocs.io/latest/integrations/loguru.html#enabling-for-pytest>`_\n    and `configuring unittest <https://logot.readthedocs.io/latest/integrations/loguru.html#enabling-for-unittest>`_.\n\n\n.. _add_opentelemetry_traces_id:\n\nAdd OpenTelemetry ``trace_id`` and ``span_id`` to logs\n------------------------------------------------------\n\nThere is no official implementation of OpenTelemetry for Loguru as of now, but one frequent task when integrating Loguru and OpenTelemetry\nis adding the trace context to the logs to create a correlation between them. The following example uses |patch| to automatically inject OpenTelemetry trace context into every log record::\n\n    from loguru import logger\n    import sys\n\n    from opentelemetry.trace import (\n        INVALID_SPAN,\n        INVALID_SPAN_CONTEXT,\n        get_current_span,\n        get_tracer_provider,\n    )\n\n\n    def instrument_loguru():\n        provider = get_tracer_provider()\n        service_name = None\n\n        def add_trace_context(record):\n            record[\"extra\"][\"otelSpanID\"] = \"0\"\n            record[\"extra\"][\"otelTraceID\"] = \"0\"\n            record[\"extra\"][\"otelTraceSampled\"] = False\n\n            nonlocal service_name\n            if service_name is None:\n                resource = getattr(provider, \"resource\", None)\n                if resource:\n                    service_name = resource.attributes.get(\"service.name\") or \"\"\n                else:\n                    service_name = \"\"\n\n            record[\"extra\"][\"otelServiceName\"] = service_name\n\n            span = get_current_span()\n            if span != INVALID_SPAN:\n                ctx = span.get_span_context()\n                if ctx != INVALID_SPAN_CONTEXT:\n                    record[\"extra\"][\"otelSpanID\"] = format(ctx.span_id, \"016x\")\n                    record[\"extra\"][\"otelTraceID\"] = format(ctx.trace_id, \"032x\")\n                    record[\"extra\"][\"otelTraceSampled\"] = ctx.trace_flags.sampled\n\n        logger.configure(patcher=add_trace_context)\n\n\n    instrument_loguru()\n\n    logger.remove()\n    format_ = \"{time:YYYY-MM-DD HH:MM:SS.sss} {level} [{name}] [{file}:{line} [trace_id={extra[otelTraceID]} span_id={extra[otelSpanID]} resource.service.name={extra[otelServiceName]} trace_sampled={extra[otelTraceSampled]}] - {message}\"\n    logger.add(sys.stderr, format=format_)\n\n    logger.info(\"This is an info message\")\n\nAlternatively, a custom formatter can be added to be able to save ``otelSpanID`` and ``otelTraceID`` in the root of the JSON log.\n"
  },
  {
    "path": "docs/resources/troubleshooting.rst",
    "content": "Frequently Asked Questions and Troubleshooting Tips for Loguru\n==============================================================\n\n.. highlight:: python3\n\n.. |sys.stdout| replace:: :data:`sys.stdout`\n.. |sys.stderr| replace:: :data:`sys.stderr`\n.. |str.format| replace:: :meth:`str.format()`\n.. |isatty| replace:: :meth:`~io.IOBase.isatty`\n.. |IOBase.close| replace:: :meth:`~io.IOBase.close`\n\n.. |Logger| replace:: :class:`~loguru._logger.Logger`\n.. |add| replace:: :meth:`~loguru._logger.Logger.add()`\n.. |remove| replace:: :meth:`~loguru._logger.Logger.remove()`\n.. |bind| replace:: :meth:`~loguru._logger.Logger.bind()`\n.. |opt| replace:: :meth:`~loguru._logger.Logger.opt()`\n.. |patch| replace:: :meth:`~loguru._logger.Logger.patch()`\n.. |log| replace:: :meth:`~loguru._logger.Logger.log()`\n\n.. |colorama| replace:: ``colorama``\n.. _colorama: https://github.com/tartley/colorama\n\n.. |if-name-equals-main| replace:: ``if __name__ == \"__main__\":``\n.. _if-name-equals-main: https://docs.python.org/3/library/__main__.html#idiomatic-usage\n\n.. |the-no-color-environment-variable| replace:: the ``NO_COLOR`` environment variable\n.. _the-no-color-environment-variable: https://no-color.org/\n\n.. _ANSI escape sequences: https://en.wikipedia.org/wiki/ANSI_escape_code\n\n\nHow do I create and configure a logger?\n---------------------------------------\n\nLoguru differs from standard logging as you don't need to create a logger. It is directly provided by Loguru, and you should just import it::\n\n    from loguru import logger\n\n    logger.info(\"Hello, World!\")\n\nThis |Logger| object is unique and shared across all modules of your application. Import it into every file where you need to use it. It acts as a basic facade interface around a list of handlers. These handlers are responsible for receiving log messages, formatting them, and logging them to one or more desired destinations (file, console, etc.).\n\nWhen you first import Loguru's logger, it comes pre-configured with a default handler that displays your logs on the standard error output (|sys.stderr|). However, you can easily change the logger's configuration to suit your needs. First, use |remove| to discard the default handler. Then, use |add| to register one or more handlers that will log messages to the desired destinations. For example::\n\n    logger.remove()  # Remove the default handler.\n    logger.add(sys.stderr, format=\"{time} - {level} - {message}\")  # Log to console with custom format.\n    logger.add(\"file.log\", level=\"INFO\", rotation=\"500 MB\")  # Also log to a file, rotating every 500 MB.\n\nThe logger should be configured only once, at the entry point of your application (typically within a |if-name-equals-main|_ block). Other modules in your application will automatically inherit this configuration by simply importing Loguru's global ``logger``.\n\n.. seealso::\n\n   :ref:`Configuring Loguru to be used by a library or an application <configuring-loguru-as-lib-or-app>`\n\n\nWhy are my logs duplicated in the output?\n-----------------------------------------\n\nRemember that the initial ``logger`` has a default handler for convenience. If you plan to change the logging configuration, make sure to |remove| this default handler before to |add| a new one. Otherwise, messages will be duplicated because they will be sent to both the default handler and your new handler::\n\n    # Replace the default handler with a new one.\n    logger.remove()\n    logger.add(sys.stderr, format=\"{time} - {level} - {message}\")\n\nAdditionally, since there is a single ``logger`` shared across all modules in your application, you should configure it in one place only. Handlers will be added as many times as ``logger.add()`` is called, so be careful not to reconfigure it multiple times.\n\nIn particular when using ``multiprocessing`` (either directly or indirectly through a web framework, for instance), ensure that the ``logger`` configuration is guarded by an if |if-name-equals-main|_ block. Otherwise, each spawned child process will re-execute the configuration code. This can result in duplicated logs or unexpected configurations. See :ref:`this section of the documentation <multiprocessing-compatibility>` for details.\n\nFinally, don't forget that the ``level`` argument of |add| defines a minimum threshold, not an exact filtering mechanism. It is generally a mistake to add two handlers with the same sink, as it will cause duplication unless they are configured with mutually exclusive ``filter`` functions. For example::\n\n    def is_debug(record):\n        return record[\"level\"].no <= 10\n\n    logger.add(sys.stderr, level=\"DEBUG\", format=\"{time} - {name} - {message}\", filter=is_debug)\n    logger.add(sys.stderr, level=\"INFO\", format=\"{message}\", filter=lambda r: not is_debug(r))\n\n\nHow do I set the logging level?\n-------------------------------\n\nThe :ref:`logging levels <levels>` allow filtering messages based on their importance. It is a minimum threshold above which messages are logged (or ignored otherwise). This makes it possible, for example, to adjust the verbosity of logs depending on the execution environment (development or production).\n\nThe |Logger| itself is not associated with any specific level. Instead, it is the level of each handler that individually determines whether a message is logged or not. This level is defined when configuring the handler and adding it to the logger using the ``level`` argument of the |add| method::\n\n    logger.add(sys.stdout, level=\"WARNING\")  # Log only messages with level \"WARNING\" or higher.\n    logger.debug(\"Some debug message\")  # Will be ignored.\n    logger.error(\"Some error message\")  # Will be displayed.\n\nIt is not possible to change the level of an existing handler. If you need to modify the logging level, you can |remove| the existing handler and |add| a new one with the desired level::\n\n    logger.remove()  # Remove the default handler.\n    logger.add(sys.stderr, level=\"INFO\")\n\nBy default, the level of each handler is ``\"DEBUG\"``. You can adjust this value :ref:`using environment variables <env>`:\n\n.. code:: text\n\n    export LOGURU_LEVEL=\"INFO\"  # On Linux or macOS.\n    setx LOGURU_LEVEL \"INFO\"    # On Windows.\n\nNote that this only affects handlers that do not explicitly configure their level.\n\n.. seealso::\n\n   :ref:`Changing the level of an existing handler <changing-level-of-existing-handler>`\n\n\n.. _anonymous_levels:\n\nWhy isn't the level name shown when using an integer or a built-in level?\n-------------------------------------------------------------------------\n\nYou may have noticed that when a numeric value is passed to the |log| function, or equivalently when a level from the standard ``logging`` library is used, it appears in the logs as \"Level *NUM*\" rather than the expected level name::\n\n    import logging\n    from loguru import logger\n\n    logger.log(logging.INFO, \"This is an info message.\")\n    # Output: 2025-11-02 12:40:37.266 +01:00 | Level 20 | __main__:<module>:4 - This is an info message.\n\nThis behavior is normal and stems from the fact that, unlike the standard logging library, Loguru levels are identified exclusively by their name (and not by their severity).\n\nIndeed, it is entirely possible to define different logging levels that share the same severity number. In such cases, however, it becomes impossible to identify a level based on severity alone. To avoid any ambiguity, Loguru maps each level name with its severity number, but not the opposite.\n\nConsequently, if an integer is provided to |log|, the level is interpreted as an anonymous one and displayed as such.\n\n\nHow do I customize the log format and re-use the default one?\n-------------------------------------------------------------\n\nThe log format must be defined using the ``format`` argument of the |add| method::\n\n    logger.add(sys.stderr, format=\"{time} - {level} - {message}\")\n\nRefer to :ref:`this section of the documentation <record>` to learn about the different formatting variables available. You can also use :ref:`color tags <color>`::\n\n    logger.add(sys.stderr, format=\"<green>{time}</> - {level} - <lvl>{message}</>\")\n\nFor advanced configuration, the ``format`` argument also accepts a function, allowing you to dynamically generate the desired format. Be aware that in this case, you have to explicitly include the line ending and exception field (since you gain full control over the formatting, while ``\"\\n{exception}\"`` is added automatically when the ``format`` is a string). For example, to include the thread identifier but only for error messages and above::\n\n        def custom_formatter(record):\n            if record[\"level\"].no >= 40:\n                return \"<green>{time}</> - {level} - <red>{thread}</> - <lvl>{message}</>\\n{exception}\"\n            else:\n                return \"<green>{time}</> - {level} - <lvl>{message}</lvl>\\n{exception}\"\n\n        logger.add(sys.stderr, format=custom_formatter)\n\nFinally, note that accessing the default log format is not directly possible, as it would only be useful in a very limited number of cases. Instead, you need to explicitly redefine your desired format. To quickly copy-paste the default logging format, check out the ``LOGURU_FORMAT`` variable `in the source code <https://github.com/Delgan/loguru/blob/master/loguru/_defaults.py>`_.\n\n\nWhy are my logs not colored?\n----------------------------\n\nLog colors are configured using :ref:`special tags <color>` in the ``format`` of the handlers. If you use a custom ``format``, make sure that these tags are included, for example::\n\n    logger.add(sys.stderr, format=\"<green>{time}</green> | <level>{message}</level>\")\n\nWhen adding a handler with ``colorize=None`` (the default), Loguru tries to automatically detect whether the added sink (such as ``sys.stderr`` in the above example) supports colors. If it's not the case, color tags will be stripped. Otherwise, they'll be converted to `ANSI escape sequences`_.\n\nThese sequences are generally only supported within a terminal. Therefore, it is normal that you don't see colors when logs are saved to a text file. Sinks that support colors are usually |sys.stderr| and |sys.stdout|::\n\n    logger.add(sys.stderr)  # Can be colored.\n    logger.add(\"file.log\")  # Cannot be colored.\n\nWhen such stream object is used for logging, Loguru will also call |isatty| to determine whether colors should be used. This method notably returns ``False`` if the stream is not connected to a terminal, which would make colorization pointless. For example, redirecting the output of your script to a file will disable colors:\n\n.. code-block:: bash\n\n    python my_script.py > output.log  # Colors will be disabled.\n\nAdditionally, it is not uncommon in some virtual environments for the standard output not to be considered as connected to a terminal, even though you can view the logs' output live without redirection. This is the case, for instance, in some cloud services. Check the value of ``sys.stderr.isatty()`` if you encounter any issues.\n\nVarious heuristics assist in determining whether colors should be enabled by default. Specifically, Loguru honors |the-no-color-environment-variable|_ and disables coloring if it is set to any value. Additionally, terminals identified by ``TERM=dumb`` are considered to lack color support.\n\nIn any case, you can always control log coloring by explicitly specifying the ``colorize`` argument of the |add| method::\n\n    logger.add(sys.stderr, colorize=True)  # Force ANSI sequences in output.\n\nConversely, if raw ANSI sequences such as ``\\x1b[31m`` or ``\\x1b[0m`` appear in your logs, it certainly means the sink does not support colors, and you should disable them.\n\nNote that on Windows, log coloring is handled using the |colorama|_ library.\n\n\nWhy are my logs not showing up?\n-------------------------------\n\nEnsure that you've added at least one sink using |add|. You can get an overview of the configured handlers by simply printing the logger object::\n\n    print(logger)\n    # Output: <loguru.logger handlers=[(id=0, level=10, sink=<stderr>)]>\n\n\nCheck also the logging level: messages below the set level won't appear::\n\n    logger.add(sys.stderr, level=\"INFO\")\n    logger.debug(\"Some debug message\")  # Won't be displayed since \"DEBUG\" is below \"INFO\".\n\n\nWhy is the captured exception missing from the formatted message?\n-----------------------------------------------------------------\n\nWhen ``logger.exception()`` or ``logger.opt(exception=True)`` is used within an ``except`` clause, Loguru automatically captures the exception information and includes it in :ref:`the logged message <message>`.\n\nThe position of the exception in the message is controlled by the ``\"{exception}\"`` field of the configured log format. By default, when the ``format`` argument of |add| is a string, the ``\"{exception}\"`` field is automatically appended to the format::\n\n    # The \"{exception}\" placeholder is implicit here (at the end of the format).\n    log_format = \"{time} - {level} - {message}\"\n    logger.add(sys.stderr, format=log_format)\n\n\nHowever, when using a custom function to define the format of logs, the user gets complete control over the desired format. This means the ``\"{exception}\"`` field must be explicitly included::\n\n    def custom_formatter(record):\n        return \"{time} - {level} - {message}\\n{exception}\"\n\n    logger.add(sys.stderr, format=custom_formatter)\n\nIf the field is missing, the formatted error will not appear in the log message. Always ensure the ``\"{exception}\"`` placeholder is present in your log format if you want exception details to appear in your logs.\n\n\nHow can I use different loggers in different modules of my application?\n-----------------------------------------------------------------------\n\nSince Loguru is designed on the use of a single ``logger``, it is fundamentally not possible to create different loggers for multiple modules. The idea is that modules should simply import the global ``logger`` from ``loguru``, and log differentiation should be handled through handlers (which should only be configured once, at the application's entry point).\n\nNote that is generally possible to identify the origin of a log message via the ``record[\"name\"]`` field in the record dict. This field contains the name of the module that emitted the message. For example, you can use this information to redirect messages based on their origin::\n\n    logger.add(\"my_app.log\")  # All messages.\n    logger.add(\"module_1.log\", filter=\"module_1\")  # Messages from \"module_1\" only.\n    logger.add(\"module_2.log\", filter=\"module_2\")  # Messages from \"module_2\" only.\n\nFor more advanced use cases, it is recommended to use the |bind| method, which returns a new instance of the ``logger`` tied to the given value. This allows you to identify logs more precisely::\n\n    def is_specific_log(record):\n        return record[\"extra\"].get(\"is_specific\") is True\n\n    logger.add(\"specific.log\", filter=is_specific_log)\n    logger.add(\"other.log\", filter=lambda r: not is_specific_log(r))\n\n    specific_logger = logger.bind(is_specific=True)\n    specific_logger.info(\"This message will go to 'specific.log' only.\")\n\n    logger.info(\"This message will go to 'other.log' only.\")\n\n.. seealso::\n\n   :ref:`Creating independent loggers with separate set of handlers <creating-independent-loggers>`\n\n\nWhy are my log files sometimes duplicated or the content trimmed?\n-----------------------------------------------------------------\n\nProblem with logging files duplicated or trimmed is generally symptomatic of a configuration issue. More precisely, this can happen if |add| is inadvertently called multiple times with the same file path.\n\nWhen this happens, the file is opened again by the newly created handler. Consequently, multiple handlers manage and write to the same file concurrently. This is an incorrect situation that inevitably leads to conflicts. If the problem isn't detected, handlers risk overwriting logs over each other, otherwise it can also result in duplicated files at the moment of the rotation.\n\nIf you observe such weird behavior, you should review your code carefully to ensure that the same file sink is not being added multiple times. This can occur if ``multiprocessing`` is used incorrectly (see :ref:`this section of the documentation <multiprocessing-compatibility>` for more details). You have to make sure that the logger is not configured repeatedly by different processes, and you should use a |if-name-equals-main|_ guard.\n\nIt is also a common issue with web frameworks like Gunicorn and Uvicorn, as they start multiple workers in parallel. In such cases, you need to set up a log server, and configure workers to send messages to it using a socket. Refer to :ref:`Transmitting log messages across network, processes or Gunicorn workers <inter-process-communication>` for details.\n\n\nWhy am I facing errors when I use a custom formatting function?\n---------------------------------------------------------------\n\nWhen using a custom function to define the log format (by passing a callable to the ``format`` argument of |add|), you must ensure that the function returns a string containing the unformatted template and not an already formatted message.\n\nThis string should include placeholders for the fields you want to display (such as ``\"{time}\"``, ``\"{level}\"``, ``\"{message}\"``, etc). Later, Loguru will call an equivalent of |str.format| on this string, passing the log record as keyword arguments to replace the placeholders with actual values. If the returned string is already formatted (i.e., if it contains actual values instead of placeholders), some of the values might be interpreted as placeholders and lead to a ``ValueError`` or ``KeyError`` because they don't correspond to any key in the log record.\n\nFor example, the following code is incorrect because the function returns a pre-formatted string::\n\n    def incorrect_dynamic_format(record):\n        # This is wrong because it formats the message immediately.\n        return f\"{record['time']} - {record['level']} - {record['message']}\"\n\n    logger.add(sys.stderr, format=incorrect_dynamic_format)\n\nInstead, the function should return a string with placeholders, like this::\n\n    def correct_dynamic_format(record):\n        # This is correct because it returns a template string.\n        return \"{time} - {level} - {message}\\n{exception}\"\n\n    logger.add(sys.stderr, format=correct_dynamic_format)\n\nThink about this function as a way to dynamically generate the format template, not to format the message itself. Loguru will handle the actual formatting of the log message later, using the template you provide.\n\nIf for some reason you need to format the message yourself, you should save the result in a new field of the record's ``\"extra\"`` dictionary, and then use this new field in the log format. For example::\n\n    def custom_format_with_extra(record):\n        record[\"extra\"][\"custom_msg\"] = f\"{record['time']} - {record['level']} - {record['message']}\"\n        return \"{extra[custom_msg]}\\n{exception}\"\n\n    logger.add(sys.stderr, format=custom_format_with_extra)\n\nFinally, note that if your format contains curly braces that are not meant to be placeholders, you must escape them by doubling them::\n\n    def custom_json_format(record):\n        return '{{ \"@timestamp\": {time:X}, \"levelno\": \"{level.no}\", \"msg\": \"{message}\" }}'\n\n    logger.add(sys.stderr, format=custom_json_format)\n\n\nWhy logging a message with f-string sometimes raises an exception?\n------------------------------------------------------------------\n\nWhen positional or keyword arguments are passed to the logging function, Loguru will integrate them to the message. For example::\n\n    logger.info(\"My name is {name}\", name=\"John\")\n    # Output: [INFO] My name is John\n\nThis is actually equivalent to using the |str.format| built-in Python method::\n\n    message = \"My name is {name}\".format(name=\"John\")\n    logger.info(message)\n\nHowever, the behavior described above can cause an error if the arguments passed were not intended to be formatted with the message (but rather just captured in the \"extra\" dict of the log record). This is particularly true if the message contains curly braces. The formatting function will then interpret them as placeholders and attempt to replace them with the passed arguments.\n\nHere are some examples that result in various exceptions:\n\n.. code-block::\n\n    # KeyError: 'key1, key2'\n    logger.warning(\"Config file missing keys: {key1, key2}\", filename=\"app.cfg\")\n\n.. code-block::\n\n    # ValueError: Single '{' encountered in format string\n    logger.info(\"This is a curly bracket: {\", foo=\"bar\")\n\n.. code-block::\n\n    # AttributeError: 'dict' object has no attribute 'format'\n    logger.debug({\"key\": \"value\"}, identifier=42)\n\n.. code-block::\n\n    # IndexError: Replacement index 0 out of range for positional args tuple\n    logger.error(\"Use 'set()' not '{}' for empty set\", strictness=9)\n\n\nIt is common to encounter these errors when using f-strings, as this can leads to the creation of a message that already contains curly braces. For example::\n\n    data = {\"foo\": 42}\n\n    # Will raise \"KeyError\" because it's equivalent to:\n    #   logger.info(\"Processing '{'foo': 42}'\", data=data)\n    logger.info(f\"Processing '{data}'\", data=data)\n\nTherefore, you must be careful not to inadvertently introduce curly braces into the message. Instead of using an f-string, you can let Loguru handle the formatting::\n\n    logger.info(\"Processing '{data}'\", data=data)\n\nYou can also use |bind| to add extra information to a message without formatting it::\n\n    logger.bind(data=data).info(f\"Processing '{data}'\")\n\nFinally, you can possibly disable formatting by doubling the curly braces::\n\n    logger.info(\"Curly brackets are {{ and }}\", data=data)\n\n\nHow do I fix \"ValueError: I/O operation error on closed file\"?\n--------------------------------------------------------------\n\nThis error occurs because the logger is trying to write to a stream object (like ``sys.stderr`` or ``sys.stdout``) that has been closed, which is invalid (see |IOBase.close|).\n\nWhen stream objects are used as logging sink, Loguru will not close them. This would be very inconvenient and incorrect (as the stream is global, it must remain usable after the sink has been removed). Since Loguru does not close such a stream by itself, this means something else closed the stream while it was still in use by the ``logger``.\n\nThis is generally due to some tools or specific environments that take the liberty of replacing ``sys.stdout`` and ``sys.stderr`` with their own stream object. In this way, they can capture what is written to the standard output. This is the case with some libraries, IDEs and cloud platforms.\nThe problem is that the ``logger`` will use this wrapped stream as well. If the third-party tool happens to clean up and close the stream, then the ``logger`` is left with an unusable sink.\n\nHere is a simplified example to illustrate the issue::\n\n    from contextlib import contextmanager\n    import sys\n    import io\n    from loguru import logger\n\n\n    @contextmanager\n    def redirect_stdout(new_target):\n        old_target, sys.stdout = sys.stdout, new_target\n        try:\n            yield new_target\n        finally:\n            sys.stdout = old_target\n            new_target.close()\n\n\n    if __name__ == \"__main__\":\n        logger.remove()\n        f = io.StringIO()\n\n        with redirect_stdout(f):\n            logger.add(sys.stdout)  # Logger is inadvertently configured with wrapped stream.\n            logger.info(\"Hello\")\n            output = f.getvalue()\n\n        print(f\"Captured output: {output}\")\n\n        # ValueError: I/O operation on closed file.\n        logger.info(\"World\")\n\n\nAnd here is another example causing the same error with Pytest::\n\n    import sys\n    from loguru import logger\n\n    logger.remove()\n\n    def test_1(capsys):\n        # Here, \"sys.stderr\" is actually a mock object due to usage of \"capsys\" fixture.\n        logger.add(sys.stderr, catch=False)\n        logger.info(\"Test 1\")\n\n\n    def test_2():\n        # After execution of the previous test, the mocked \"sys.stderr\" was closed by Pytest.\n        # However, the handler was not removed from the Loguru logger. It'll raise a \"ValueError\" here.\n        logger.info(\"Test 2\", catch=False)\n\n\nWhat you can possibly do in such a situation:\n\n- identify any tool that could be manipulating ``sys.stdout``, try to call ``print(sys.stdout)`` to see if it's a wrapper object;\n- make sure the ``logger`` is always fully re-initialized whenever your code is susceptible to clean up the wrapped ``sys.stdout``;\n- configure the ``logger`` with ``logger.add(lambda m: sys.stdout.write(m))`` instead of ``logger.add(sys.stdout)``, so that the stream is dynamically retrieved and therefore not affected by changes.\n\n\nHow do I prevent \"RuntimeError\" due to \"deadlock avoided\"?\n----------------------------------------------------------\n\nThe logging functions are not reentrant. This means you must not use the logger when it's already in use in the same thread. This situation can occur notably if you use the logger inside a sink (which itself is called by the logger). Logically, this would result in an infinite recursive loop. In practice, it would more likely cause your application to hang because logging is protected by an internal lock.\n\nTo prevent such problems, there is a mechanism that detects and prevents the logger from being called recursively. This is what might lead to a ``RuntimeError``. When faced with such an error, you need to ensure that the handlers you configure do not internally call the logger. This also applies to the logger from the standard ``logging`` library.\n\nIf you cannot prevent the use of the logger inside a handler, you should implement a ``filter`` to avoid recursive calls. For example::\n\n    import sys\n    from loguru import logger\n\n\n    def my_sink(message):\n        logger.debug(\"Within my sink\")\n        print(message, end=\"\")\n\n\n    def avoid_recursion(record):\n        return record[\"function\"] != \"my_sink\"\n\n\n    if __name__ == \"__main__\":\n        logger.remove()\n        logger.add(\"file.log\")\n        logger.add(my_sink, filter=avoid_recursion)\n\n        logger.info(\"First message\")\n        logger.debug(\"Another message\")\n\n\nWhy is the source (name, file, function, line) of the log message incorrect or missing?\n---------------------------------------------------------------------------------------\n\nIn some very specific circumstances, the module name might be ``None`` and the filename and function name might be ``\"<unknown>\"``.\n\n.. code-block:: none\n\n    2024-12-01 16:23:21.769 | INFO     | None:<unknown>:0 - Message from unknown source.\n\nSuch a situation indicates that the ``logger`` was unable to retrieve the caller's context. In particular, this can happen when Loguru is used with Dask or Cython. In such cases, this behavior is normal, and there is nothing to do unless you wish to implement a custom |patch| function::\n\n    logger = logger.patch(lambda record: record.update(name=\"my_module\"))\n\nThis issue may also result from improper use of the ``depth`` argument of the |opt| method. Make sure that the value of this argument is correct.\n\n\nWhy can't I access the ``Logger`` class and other types at runtime?\n-------------------------------------------------------------------\n\nThe ``logger`` object imported from the ``loguru`` library is an instance of the |Logger| class. However, you should not attempt to instantiate a logger yourself. The |Logger| class is not public and will be unusable by your Python application. It is therefore expected that the following code will raise an error::\n\n    from loguru import Logger\n    # Output: ImportError: cannot import name 'Logger' from 'loguru'\n\nIt is only possible to use the |Logger| class in the context of type hints. In such cases, no error will be raised. Said otherwise, that means only type checkers can access the |Logger| class. Below is an example of how to use ``Logger`` for typing purposes, but without runtime access::\n\n    from __future__ import annotations\n\n    import typing\n\n    from loguru import logger\n\n    if typing.TYPE_CHECKING:\n        from loguru import Logger\n\n    def my_function(logger: Logger):\n        logger.info(\"Hello, World!\")\n\nIf for some reason you need to perform type checking at runtime, you can make a comparison with the type on the ``logger`` instance::\n\n    import loguru\n    import logging\n\n    def my_function(logger: loguru.Logger | logging.Logger):\n        if isinstance(logger, type(loguru.logger)):\n            logger.info(\"Hello, {}!\", \"World\")\n        else:\n            logger.info(\"Hello, %s!\", \"World\")\n\n.. seealso::\n\n   :ref:`Type hints <type-hints>`\n"
  },
  {
    "path": "docs/resources.rst",
    "content": "Help & Guides\n=============\n\n.. toctree::\n\n   resources/migration.rst\n   resources/troubleshooting.rst\n   resources/recipes.rst\n"
  },
  {
    "path": "loguru/__init__.py",
    "content": "\"\"\"\nThe Loguru library provides a pre-instanced logger to facilitate dealing with logging in Python.\n\nJust ``from loguru import logger``.\n\"\"\"\n\nimport atexit as _atexit\nimport sys as _sys\n\nfrom . import _defaults\nfrom ._logger import Core as _Core\nfrom ._logger import Logger as _Logger\n\n__version__ = \"0.7.3\"\n\n__all__ = [\"logger\"]\n\nlogger = _Logger(\n    core=_Core(),\n    exception=None,\n    depth=0,\n    record=False,\n    lazy=False,\n    colors=False,\n    raw=False,\n    capture=True,\n    patchers=[],\n    extra={},\n)\n\nif _defaults.LOGURU_AUTOINIT and _sys.stderr:\n    logger.add(_sys.stderr)\n\n_atexit.register(logger.remove)\n"
  },
  {
    "path": "loguru/__init__.pyi",
    "content": "import sys\nfrom asyncio import AbstractEventLoop\nfrom datetime import datetime, time, timedelta\nfrom logging import Handler\nfrom multiprocessing.context import BaseContext\nfrom types import TracebackType\nfrom typing import (\n    Any,\n    BinaryIO,\n    Callable,\n    Dict,\n    Generator,\n    Generic,\n    List,\n    NamedTuple,\n    NewType,\n    Optional,\n    Pattern,\n    Sequence,\n    TextIO,\n    Tuple,\n    Type,\n    TypeVar,\n    Union,\n    overload,\n)\n\nif sys.version_info >= (3, 6):\n    from typing import Awaitable\nelse:\n    from typing_extensions import Awaitable\n\nif sys.version_info >= (3, 6):\n    from os import PathLike\n    from typing import ContextManager\n\n    PathLikeStr = PathLike[str]\nelse:\n    from pathlib import PurePath as PathLikeStr\n\n    from typing_extensions import ContextManager\n\nif sys.version_info >= (3, 8):\n    from typing import Protocol, TypedDict\nelse:\n    from typing_extensions import Protocol, TypedDict\n\n_T = TypeVar(\"_T\")\n_F = TypeVar(\"_F\", bound=Callable[..., Any])\nExcInfo = Tuple[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]]\n\nclass _GeneratorContextManager(ContextManager[_T], Generic[_T]):\n    def __call__(self, func: _F) -> _F: ...\n    def __exit__(\n        self,\n        type: Optional[Type[BaseException]],\n        value: Optional[BaseException],\n        traceback: Optional[TracebackType],\n    ) -> Optional[bool]: ...\n\nCatcher = NewType(\"Catcher\", _GeneratorContextManager[None])\nContextualizer = NewType(\"Contextualizer\", _GeneratorContextManager[None])\nAwaitableCompleter = Awaitable[None]\n\nclass Level(NamedTuple):\n    name: str\n    no: int\n    color: str\n    icon: str\n\nclass _RecordAttribute:\n    def __format__(self, spec: str) -> str: ...\n\nclass RecordFile(_RecordAttribute):\n    name: str\n    path: str\n\nclass RecordLevel(_RecordAttribute):\n    name: str\n    no: int\n    icon: str\n\nclass RecordThread(_RecordAttribute):\n    id: int\n    name: str\n\nclass RecordProcess(_RecordAttribute):\n    id: int\n    name: str\n\nclass RecordException(NamedTuple):\n    type: Optional[Type[BaseException]]\n    value: Optional[BaseException]\n    traceback: Optional[TracebackType]\n\nclass Record(TypedDict):\n    elapsed: timedelta\n    exception: Optional[RecordException]\n    extra: Dict[Any, Any]\n    file: RecordFile\n    function: str\n    level: RecordLevel\n    line: int\n    message: str\n    module: str\n    name: Optional[str]\n    process: RecordProcess\n    thread: RecordThread\n    time: datetime\n\nclass Message(str):\n    record: Record\n\nclass Writable(Protocol):\n    def write(self, message: Message) -> Any: ...\n\nFilterDict = Dict[Optional[str], Union[str, int, bool]]\nFilterFunction = Callable[[Record], bool]\nFormatFunction = Callable[[Record], str]\nPatcherFunction = Callable[[Record], None]\nRotationFunction = Callable[[Message, TextIO], bool]\nRetentionFunction = Callable[[List[str]], None]\nCompressionFunction = Callable[[str], None]\n\nStandardOpener = Callable[[str, int], int]\n\nclass BasicHandlerConfig(TypedDict, total=False):\n    sink: Union[TextIO, Writable, Callable[[Message], None], Handler]\n    level: Union[str, int]\n    format: Union[str, FormatFunction]\n    filter: Optional[Union[str, FilterFunction, FilterDict]]\n    colorize: Optional[bool]\n    serialize: bool\n    backtrace: bool\n    diagnose: bool\n    enqueue: bool\n    catch: bool\n\nclass FileHandlerConfig(TypedDict, total=False):\n    sink: Union[str, PathLikeStr]\n    level: Union[str, int]\n    format: Union[str, FormatFunction]\n    filter: Optional[Union[str, FilterFunction, FilterDict]]\n    colorize: Optional[bool]\n    serialize: bool\n    backtrace: bool\n    diagnose: bool\n    enqueue: bool\n    catch: bool\n    rotation: Optional[\n        Union[\n            str,\n            int,\n            time,\n            timedelta,\n            RotationFunction,\n            list[Union[str, int, time, timedelta, RotationFunction]],\n        ]\n    ]\n    retention: Optional[Union[str, int, timedelta, RetentionFunction]]\n    compression: Optional[Union[str, CompressionFunction]]\n    delay: bool\n    watch: bool\n    mode: str\n    buffering: int\n    encoding: str\n    errors: Optional[str]\n    newline: Optional[str]\n    closefd: bool\n    opener: Optional[StandardOpener]\n\nclass AsyncHandlerConfig(TypedDict, total=False):\n    sink: Callable[[Message], Awaitable[None]]\n    level: Union[str, int]\n    format: Union[str, FormatFunction]\n    filter: Optional[Union[str, FilterFunction, FilterDict]]\n    colorize: Optional[bool]\n    serialize: bool\n    backtrace: bool\n    diagnose: bool\n    enqueue: bool\n    catch: bool\n    context: Optional[Union[str, BaseContext]]\n    loop: Optional[AbstractEventLoop]\n\nHandlerConfig = Union[BasicHandlerConfig, FileHandlerConfig, AsyncHandlerConfig]\n\nclass LevelConfig(TypedDict, total=False):\n    name: str\n    no: int\n    color: str\n    icon: str\n\nActivationConfig = Tuple[Optional[str], bool]\n\nclass Logger:\n    @overload\n    def add(\n        self,\n        sink: Union[TextIO, Writable, Callable[[Message], None], Handler],\n        *,\n        level: Union[str, int] = ...,\n        format: Union[str, FormatFunction] = ...,\n        filter: Optional[Union[str, FilterFunction, FilterDict]] = ...,\n        colorize: Optional[bool] = ...,\n        serialize: bool = ...,\n        backtrace: bool = ...,\n        diagnose: bool = ...,\n        enqueue: bool = ...,\n        context: Optional[Union[str, BaseContext]] = ...,\n        catch: bool = ...\n    ) -> int: ...\n    @overload\n    def add(\n        self,\n        sink: Callable[[Message], Awaitable[None]],\n        *,\n        level: Union[str, int] = ...,\n        format: Union[str, FormatFunction] = ...,\n        filter: Optional[Union[str, FilterFunction, FilterDict]] = ...,\n        colorize: Optional[bool] = ...,\n        serialize: bool = ...,\n        backtrace: bool = ...,\n        diagnose: bool = ...,\n        enqueue: bool = ...,\n        catch: bool = ...,\n        context: Optional[Union[str, BaseContext]] = ...,\n        loop: Optional[AbstractEventLoop] = ...\n    ) -> int: ...\n    @overload\n    def add(\n        self,\n        sink: Union[str, PathLikeStr],\n        *,\n        level: Union[str, int] = ...,\n        format: Union[str, FormatFunction] = ...,\n        filter: Optional[Union[str, FilterFunction, FilterDict]] = ...,\n        colorize: Optional[bool] = ...,\n        serialize: bool = ...,\n        backtrace: bool = ...,\n        diagnose: bool = ...,\n        enqueue: bool = ...,\n        context: Optional[Union[str, BaseContext]] = ...,\n        catch: bool = ...,\n        rotation: Optional[\n            Union[\n                str,\n                int,\n                time,\n                timedelta,\n                RotationFunction,\n                list[Union[str, int, time, timedelta, RotationFunction]],\n            ]\n        ] = ...,\n        retention: Optional[Union[str, int, timedelta, RetentionFunction]] = ...,\n        compression: Optional[Union[str, CompressionFunction]] = ...,\n        delay: bool = ...,\n        watch: bool = ...,\n        mode: str = ...,\n        buffering: int = ...,\n        encoding: str = ...,\n        errors: Optional[str] = ...,\n        newline: Optional[str] = ...,\n        closefd: bool = ...,\n        opener: Optional[StandardOpener] = ...,\n    ) -> int: ...\n    def remove(self, handler_id: Optional[int] = ...) -> None: ...\n    def complete(self) -> AwaitableCompleter: ...\n    @overload\n    def catch(\n        self,\n        exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ...,\n        *,\n        level: Union[str, int] = ...,\n        reraise: bool = ...,\n        onerror: Optional[Callable[[BaseException], None]] = ...,\n        exclude: Optional[Union[Type[BaseException], Tuple[Type[BaseException], ...]]] = ...,\n        default: Any = ...,\n        message: str = ...\n    ) -> Catcher: ...\n    @overload\n    def catch(self, function: _F) -> _F: ...\n    def opt(\n        self,\n        *,\n        exception: Optional[Union[bool, ExcInfo, BaseException]] = ...,\n        record: bool = ...,\n        lazy: bool = ...,\n        colors: bool = ...,\n        raw: bool = ...,\n        capture: bool = ...,\n        depth: int = ...,\n        ansi: bool = ...\n    ) -> Logger: ...\n    def bind(__self, **kwargs: Any) -> Logger: ...  # noqa: N805\n    def contextualize(__self, **kwargs: Any) -> Contextualizer: ...  # noqa: N805\n    def patch(self, patcher: PatcherFunction) -> Logger: ...\n    @overload\n    def level(self, name: str) -> Level: ...\n    @overload\n    def level(\n        self, name: str, no: int = ..., color: Optional[str] = ..., icon: Optional[str] = ...\n    ) -> Level: ...\n    @overload\n    def level(\n        self,\n        name: str,\n        no: Optional[int] = ...,\n        color: Optional[str] = ...,\n        icon: Optional[str] = ...,\n    ) -> Level: ...\n    def disable(self, name: Optional[str]) -> None: ...\n    def enable(self, name: Optional[str]) -> None: ...\n    def configure(\n        self,\n        *,\n        handlers: Optional[Sequence[HandlerConfig]] = ...,\n        levels: Optional[Sequence[LevelConfig]] = ...,\n        extra: Optional[Dict[Any, Any]] = ...,\n        patcher: Optional[PatcherFunction] = ...,\n        activation: Optional[Sequence[ActivationConfig]] = ...\n    ) -> List[int]: ...\n    def reinstall(self) -> None: ...\n    # @staticmethod cannot be used with @overload in mypy (python/mypy#7781).\n    # However Logger is not exposed and logger is an instance of Logger\n    # so for type checkers it is all the same whether it is defined here\n    # as a static method or an instance method.\n    @overload\n    def parse(\n        self,\n        file: Union[str, PathLikeStr, TextIO],\n        pattern: Union[str, Pattern[str]],\n        *,\n        cast: Union[Dict[str, Callable[[str], Any]], Callable[[Dict[str, str]], None]] = ...,\n        chunk: int = ...\n    ) -> Generator[Dict[str, Any], None, None]: ...\n    @overload\n    def parse(\n        self,\n        file: BinaryIO,\n        pattern: Union[bytes, Pattern[bytes]],\n        *,\n        cast: Union[Dict[str, Callable[[bytes], Any]], Callable[[Dict[str, bytes]], None]] = ...,\n        chunk: int = ...\n    ) -> Generator[Dict[str, Any], None, None]: ...\n    @overload\n    def trace(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def trace(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def debug(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def debug(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def info(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def info(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def success(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def success(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def warning(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def warning(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def error(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def error(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def critical(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def critical(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def exception(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...  # noqa: N805\n    @overload\n    def exception(__self, __message: Any) -> None: ...  # noqa: N805\n    @overload\n    def log(\n        __self, __level: Union[int, str], __message: str, *args: Any, **kwargs: Any  # noqa: N805\n    ) -> None: ...\n    @overload\n    def log(__self, __level: Union[int, str], __message: Any) -> None: ...  # noqa: N805\n    def start(self, *args: Any, **kwargs: Any) -> int: ...\n    def stop(self, *args: Any, **kwargs: Any) -> None: ...\n\nlogger: Logger\n"
  },
  {
    "path": "loguru/_asyncio_loop.py",
    "content": "import asyncio\nimport sys\n\n\ndef load_loop_functions():\n    if sys.version_info >= (3, 7):\n\n        def get_task_loop(task):\n            return task.get_loop()\n\n        get_running_loop = asyncio.get_running_loop\n\n    else:\n\n        def get_task_loop(task):\n            return task._loop\n\n        def get_running_loop():\n            loop = asyncio.get_event_loop()\n            if not loop.is_running():\n                raise RuntimeError(\"There is no running event loop\")\n            return loop\n\n    return get_task_loop, get_running_loop\n\n\nget_task_loop, get_running_loop = load_loop_functions()\n"
  },
  {
    "path": "loguru/_better_exceptions.py",
    "content": "import builtins\nimport inspect\nimport io\nimport keyword\nimport linecache\nimport os\nimport re\nimport sys\nimport sysconfig\nimport tokenize\nimport traceback\n\nif sys.version_info >= (3, 11):\n\n    def is_exception_group(exc):\n        return isinstance(exc, ExceptionGroup)\n\nelse:\n    try:\n        from exceptiongroup import ExceptionGroup\n    except ImportError:\n\n        def is_exception_group(exc):\n            return False\n\n    else:\n\n        def is_exception_group(exc):\n            return isinstance(exc, ExceptionGroup)\n\n\nclass SyntaxHighlighter:\n    _default_style = frozenset(\n        {\n            \"comment\": \"\\x1b[30m\\x1b[1m{}\\x1b[0m\",\n            \"keyword\": \"\\x1b[35m\\x1b[1m{}\\x1b[0m\",\n            \"builtin\": \"\\x1b[1m{}\\x1b[0m\",\n            \"string\": \"\\x1b[36m{}\\x1b[0m\",\n            \"number\": \"\\x1b[34m\\x1b[1m{}\\x1b[0m\",\n            \"operator\": \"\\x1b[35m\\x1b[1m{}\\x1b[0m\",\n            \"punctuation\": \"\\x1b[1m{}\\x1b[0m\",\n            \"constant\": \"\\x1b[36m\\x1b[1m{}\\x1b[0m\",\n            \"identifier\": \"\\x1b[1m{}\\x1b[0m\",\n            \"other\": \"{}\",\n        }.items()\n    )\n\n    _builtins = frozenset(dir(builtins))\n    _constants = frozenset({\"True\", \"False\", \"None\"})\n    _punctuation = frozenset({\"(\", \")\", \"[\", \"]\", \"{\", \"}\", \":\", \",\", \";\"})\n\n    if sys.version_info >= (3, 12):\n        _strings = frozenset(\n            {tokenize.STRING, tokenize.FSTRING_START, tokenize.FSTRING_MIDDLE, tokenize.FSTRING_END}\n        )\n        _fstring_middle = tokenize.FSTRING_MIDDLE\n    else:\n        _strings = frozenset({tokenize.STRING})\n        _fstring_middle = None\n\n    def __init__(self, style=None):\n        self._style = style or dict(self._default_style)\n\n    def highlight(self, source):\n        style = self._style\n        row, column = 0, 0\n        output = \"\"\n\n        for token in self.tokenize(source):\n            type_, string, (start_row, start_column), (_, end_column), line = token\n\n            if type_ == self._fstring_middle:\n                # When an f-string contains \"{{\" or \"}}\", they appear as \"{\" or \"}\" in the \"string\"\n                # attribute of the token. However, they do not count in the column position.\n                end_column += string.count(\"{\") + string.count(\"}\")\n\n            if type_ == tokenize.NAME:\n                if string in self._constants:\n                    color = style[\"constant\"]\n                elif keyword.iskeyword(string):\n                    color = style[\"keyword\"]\n                elif string in self._builtins:\n                    color = style[\"builtin\"]\n                else:\n                    color = style[\"identifier\"]\n            elif type_ == tokenize.OP:\n                if string in self._punctuation:\n                    color = style[\"punctuation\"]\n                else:\n                    color = style[\"operator\"]\n            elif type_ == tokenize.NUMBER:\n                color = style[\"number\"]\n            elif type_ in self._strings:\n                color = style[\"string\"]\n            elif type_ == tokenize.COMMENT:\n                color = style[\"comment\"]\n            else:\n                color = style[\"other\"]\n\n            if start_row != row:\n                source = source[column:]\n                row, column = start_row, 0\n\n            if type_ != tokenize.ENCODING:\n                output += line[column:start_column]\n                output += color.format(line[start_column:end_column])\n\n            column = end_column\n\n        output += source[column:]\n\n        return output\n\n    @staticmethod\n    def tokenize(source):\n        # Worth reading: https://www.asmeurer.com/brown-water-python/\n        source = source.encode(\"utf-8\")\n        source = io.BytesIO(source)\n\n        try:\n            yield from tokenize.tokenize(source.readline)\n        except tokenize.TokenError:\n            return\n\n\nclass ExceptionFormatter:\n    _default_theme = frozenset(\n        {\n            \"introduction\": \"\\x1b[33m\\x1b[1m{}\\x1b[0m\",\n            \"cause\": \"\\x1b[1m{}\\x1b[0m\",\n            \"context\": \"\\x1b[1m{}\\x1b[0m\",\n            \"dirname\": \"\\x1b[32m{}\\x1b[0m\",\n            \"basename\": \"\\x1b[32m\\x1b[1m{}\\x1b[0m\",\n            \"line\": \"\\x1b[33m{}\\x1b[0m\",\n            \"function\": \"\\x1b[35m{}\\x1b[0m\",\n            \"exception_type\": \"\\x1b[31m\\x1b[1m{}\\x1b[0m\",\n            \"exception_value\": \"\\x1b[1m{}\\x1b[0m\",\n            \"arrows\": \"\\x1b[36m{}\\x1b[0m\",\n            \"value\": \"\\x1b[36m\\x1b[1m{}\\x1b[0m\",\n        }.items()\n    )\n\n    def __init__(\n        self,\n        colorize=False,\n        backtrace=False,\n        diagnose=True,\n        theme=None,\n        style=None,\n        max_length=128,\n        encoding=\"ascii\",\n        hidden_frames_filename=None,\n        prefix=\"\",\n    ):\n        self._colorize = colorize\n        self._diagnose = diagnose\n        self._theme = theme or dict(self._default_theme)\n        self._backtrace = backtrace\n        self._syntax_highlighter = SyntaxHighlighter(style)\n        self._max_length = max_length\n        self._encoding = encoding\n        self._hidden_frames_filename = hidden_frames_filename\n        self._prefix = prefix\n        self._lib_dirs = self._get_lib_dirs()\n        self._pipe_char = self._get_char(\"\\u2502\", \"|\")\n        self._cap_char = self._get_char(\"\\u2514\", \"->\")\n        self._catch_point_identifier = \" <Loguru catch point here>\"\n\n    @staticmethod\n    def _get_lib_dirs():\n        schemes = sysconfig.get_scheme_names()\n        names = [\"stdlib\", \"platstdlib\", \"platlib\", \"purelib\"]\n        paths = {sysconfig.get_path(name, scheme) for scheme in schemes for name in names}\n        return [os.path.abspath(path).lower() + os.sep for path in paths if path in sys.path]\n\n    @staticmethod\n    def _indent(text, count, *, prefix=\"| \"):\n        if count == 0:\n            yield text\n            return\n        for line in text.splitlines(True):\n            indented = \"  \" * count + prefix + line\n            yield indented.rstrip() + \"\\n\"\n\n    def _get_char(self, char, default):\n        try:\n            char.encode(self._encoding)\n        except (UnicodeEncodeError, LookupError):\n            return default\n        else:\n            return char\n\n    def _is_file_mine(self, file):\n        filepath = os.path.abspath(file).lower()\n        if not filepath.endswith(\".py\"):\n            return False\n        return not any(filepath.startswith(d) for d in self._lib_dirs)\n\n    def _should_include_frame(self, frame):\n        return frame.f_code.co_filename != self._hidden_frames_filename\n\n    def _extract_frames(self, tb, is_first, *, limit=None, from_decorator=False):\n        frames, final_source = [], None\n\n        if tb is None or (limit is not None and limit <= 0):\n            return frames, final_source\n\n        def get_info(frame, lineno):\n            filename = frame.f_code.co_filename\n            function = frame.f_code.co_name\n            if lineno is None:  # Upstream bug, see python/cpython#89726.\n                source = \"\"\n                lineno = 0\n            else:\n                source = linecache.getline(filename, lineno).strip()\n            return filename, lineno, function, source\n\n        infos = []\n\n        if self._should_include_frame(tb.tb_frame):\n            infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))\n\n        get_parent_only = from_decorator and not self._backtrace\n\n        if (self._backtrace and is_first) or get_parent_only:\n            frame = tb.tb_frame.f_back\n            while frame:\n                if self._should_include_frame(frame):\n                    infos.insert(0, (get_info(frame, frame.f_lineno), frame))\n                    if get_parent_only:\n                        break\n                frame = frame.f_back\n\n            if infos and not get_parent_only:\n                (filename, lineno, function, source), frame = infos[-1]\n                function += self._catch_point_identifier\n                infos[-1] = ((filename, lineno, function, source), frame)\n\n        tb = tb.tb_next\n\n        while tb:\n            if self._should_include_frame(tb.tb_frame):\n                infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))\n            tb = tb.tb_next\n\n        if limit is not None:\n            infos = infos[-limit:]\n\n        for (filename, lineno, function, source), frame in infos:\n            final_source = source\n            if source:\n                colorize = self._colorize and self._is_file_mine(filename)\n                lines = []\n                if colorize:\n                    lines.append(self._syntax_highlighter.highlight(source))\n                else:\n                    lines.append(source)\n                if self._diagnose:\n                    relevant_values = self._get_relevant_values(source, frame)\n                    values = self._format_relevant_values(list(relevant_values), colorize)\n                    lines += list(values)\n                source = \"\\n    \".join(lines)\n            frames.append((filename, lineno, function, source))\n\n        return frames, final_source\n\n    def _get_relevant_values(self, source, frame):\n        value = None\n        pending = None\n        is_attribute = False\n        is_valid_value = False\n        is_assignment = True\n\n        for token in self._syntax_highlighter.tokenize(source):\n            type_, string, (_, col), *_ = token\n\n            if pending is not None:\n                # Keyword arguments are ignored\n                if type_ != tokenize.OP or string != \"=\" or is_assignment:\n                    yield pending\n                pending = None\n\n            if type_ == tokenize.NAME and not keyword.iskeyword(string):\n                if not is_attribute:\n                    for variables in (frame.f_locals, frame.f_globals):\n                        try:\n                            value = variables[string]\n                        except KeyError:\n                            continue\n                        else:\n                            is_valid_value = True\n                            pending = (col, self._format_value(value))\n                            break\n                elif is_valid_value:\n                    try:\n                        value = inspect.getattr_static(value, string)\n                    except AttributeError:\n                        is_valid_value = False\n                    else:\n                        yield (col, self._format_value(value))\n            elif type_ == tokenize.OP and string == \".\":\n                is_attribute = True\n                is_assignment = False\n            elif type_ == tokenize.OP and string == \";\":\n                is_assignment = True\n                is_attribute = False\n                is_valid_value = False\n            else:\n                is_attribute = False\n                is_valid_value = False\n                is_assignment = False\n\n        if pending is not None:\n            yield pending\n\n    def _format_relevant_values(self, relevant_values, colorize):\n        for i in reversed(range(len(relevant_values))):\n            col, value = relevant_values[i]\n            pipe_cols = [pcol for pcol, _ in relevant_values[:i]]\n            pre_line = \"\"\n            index = 0\n\n            for pc in pipe_cols:\n                pre_line += (\" \" * (pc - index)) + self._pipe_char\n                index = pc + 1\n\n            pre_line += \" \" * (col - index)\n            value_lines = value.split(\"\\n\")\n\n            for n, value_line in enumerate(value_lines):\n                if n == 0:\n                    arrows = pre_line + self._cap_char + \" \"\n                else:\n                    arrows = pre_line + \" \" * (len(self._cap_char) + 1)\n\n                if colorize:\n                    arrows = self._theme[\"arrows\"].format(arrows)\n                    value_line = self._theme[\"value\"].format(value_line)\n\n                yield arrows + value_line\n\n    def _format_value(self, v):\n        try:\n            v = repr(v)\n        except Exception:\n            v = \"<unprintable %s object>\" % type(v).__name__\n\n        max_length = self._max_length\n        if max_length is not None and len(v) > max_length:\n            v = v[: max_length - 3] + \"...\"\n        return v\n\n    def _format_locations(self, frames_lines, *, has_introduction):\n        prepend_with_new_line = has_introduction\n        regex = r'^  File \"(?P<file>.*?)\", line (?P<line>[^,]+)(?:, in (?P<function>.*))?\\n'\n\n        for frame in frames_lines:\n            match = re.match(regex, frame)\n\n            if match:\n                file, line, function = match.group(\"file\", \"line\", \"function\")\n\n                is_mine = self._is_file_mine(file)\n\n                if function is not None:\n                    pattern = '  File \"{}\", line {}, in {}\\n'\n                else:\n                    pattern = '  File \"{}\", line {}\\n'\n\n                if self._backtrace and function and function.endswith(self._catch_point_identifier):\n                    function = function[: -len(self._catch_point_identifier)]\n                    pattern = \">\" + pattern[1:]\n\n                if self._colorize and is_mine:\n                    dirname, basename = os.path.split(file)\n                    if dirname:\n                        dirname += os.sep\n                    dirname = self._theme[\"dirname\"].format(dirname)\n                    basename = self._theme[\"basename\"].format(basename)\n                    file = dirname + basename\n                    line = self._theme[\"line\"].format(line)\n                    function = self._theme[\"function\"].format(function)\n\n                if self._diagnose and (is_mine or prepend_with_new_line):\n                    pattern = \"\\n\" + pattern\n\n                location = pattern.format(file, line, function)\n                frame = location + frame[match.end() :]\n                prepend_with_new_line = is_mine\n\n            yield frame\n\n    def _format_exception(\n        self, value, tb, *, seen=None, is_first=False, from_decorator=False, group_nesting=0\n    ):\n        # Implemented from built-in traceback module:\n        # https://github.com/python/cpython/blob/a5b76167/Lib/traceback.py#L468\n        exc_type, exc_value, exc_traceback = type(value), value, tb\n\n        if seen is None:\n            seen = set()\n\n        seen.add(id(exc_value))\n\n        if exc_value:\n            if exc_value.__cause__ is not None and id(exc_value.__cause__) not in seen:\n                yield from self._format_exception(\n                    exc_value.__cause__,\n                    exc_value.__cause__.__traceback__,\n                    seen=seen,\n                    group_nesting=group_nesting,\n                )\n                cause = \"The above exception was the direct cause of the following exception:\"\n                if self._colorize:\n                    cause = self._theme[\"cause\"].format(cause)\n                if self._diagnose:\n                    yield from self._indent(\"\\n\\n\" + cause + \"\\n\\n\\n\", group_nesting)\n                else:\n                    yield from self._indent(\"\\n\" + cause + \"\\n\\n\", group_nesting)\n\n            elif (\n                exc_value.__context__ is not None\n                and id(exc_value.__context__) not in seen\n                and not exc_value.__suppress_context__\n            ):\n                yield from self._format_exception(\n                    exc_value.__context__,\n                    exc_value.__context__.__traceback__,\n                    seen=seen,\n                    group_nesting=group_nesting,\n                )\n                context = \"During handling of the above exception, another exception occurred:\"\n                if self._colorize:\n                    context = self._theme[\"context\"].format(context)\n                if self._diagnose:\n                    yield from self._indent(\"\\n\\n\" + context + \"\\n\\n\\n\", group_nesting)\n                else:\n                    yield from self._indent(\"\\n\" + context + \"\\n\\n\", group_nesting)\n\n        is_grouped = is_exception_group(value)\n\n        if is_grouped and group_nesting == 0:\n            yield from self._format_exception(\n                value,\n                tb,\n                seen=seen,\n                group_nesting=1,\n                is_first=is_first,\n                from_decorator=from_decorator,\n            )\n            return\n\n        try:\n            traceback_limit = sys.tracebacklimit\n        except AttributeError:\n            traceback_limit = None\n\n        frames, final_source = self._extract_frames(\n            exc_traceback, is_first, limit=traceback_limit, from_decorator=from_decorator\n        )\n        exception_only = traceback.format_exception_only(exc_type, exc_value)\n\n        # Determining the correct index for the \"Exception: message\" part in the formatted exception\n        # is challenging. This is because it might be preceded by multiple lines specific to\n        # \"SyntaxError\" or followed by various notes. However, we can make an educated guess based\n        # on the indentation; the preliminary context for \"SyntaxError\" is always indented, while\n        # the Exception itself is not. This allows us to identify the correct index for the\n        # exception message.\n        no_indented_indexes = (i for i, p in enumerate(exception_only) if not p.startswith(\" \"))\n        error_message_index = next(no_indented_indexes, None)\n\n        if error_message_index is not None:\n            # Remove final new line temporarily.\n            error_message = exception_only[error_message_index][:-1]\n\n            if self._colorize:\n                if \":\" in error_message:\n                    exception_type, exception_value = error_message.split(\":\", 1)\n                    exception_type = self._theme[\"exception_type\"].format(exception_type)\n                    exception_value = self._theme[\"exception_value\"].format(exception_value)\n                    error_message = exception_type + \":\" + exception_value\n                else:\n                    error_message = self._theme[\"exception_type\"].format(error_message)\n\n            if self._diagnose and frames:\n                if issubclass(exc_type, AssertionError) and not str(exc_value) and final_source:\n                    if self._colorize:\n                        final_source = self._syntax_highlighter.highlight(final_source)\n                    error_message += \": \" + final_source\n\n                error_message = \"\\n\" + error_message\n\n            exception_only[error_message_index] = error_message + \"\\n\"\n\n        if is_first:\n            yield self._prefix\n\n        has_introduction = bool(frames)\n\n        if has_introduction:\n            if is_grouped:\n                introduction = \"Exception Group Traceback (most recent call last):\"\n            else:\n                introduction = \"Traceback (most recent call last):\"\n            if self._colorize:\n                introduction = self._theme[\"introduction\"].format(introduction)\n            if group_nesting == 1:  # Implies we're processing the root ExceptionGroup.\n                yield from self._indent(introduction + \"\\n\", group_nesting, prefix=\"+ \")\n            else:\n                yield from self._indent(introduction + \"\\n\", group_nesting)\n\n        frames_lines = self._format_list(frames) + exception_only\n        if self._colorize or self._backtrace or self._diagnose:\n            frames_lines = self._format_locations(frames_lines, has_introduction=has_introduction)\n\n        yield from self._indent(\"\".join(frames_lines), group_nesting)\n\n        if is_grouped:\n            exc = None\n            for n, exc in enumerate(value.exceptions, start=1):\n                ruler = \"+\" + (\" %s \" % (\"...\" if n > 15 else n)).center(35, \"-\")\n                yield from self._indent(ruler, group_nesting, prefix=\"+-\" if n == 1 else \"  \")\n                if n > 15:\n                    message = \"and %d more exceptions\\n\" % (len(value.exceptions) - 15)\n                    yield from self._indent(message, group_nesting + 1)\n                    break\n                elif group_nesting == 10 and is_exception_group(exc):\n                    message = \"... (max_group_depth is 10)\\n\"\n                    yield from self._indent(message, group_nesting + 1)\n                else:\n                    yield from self._format_exception(\n                        exc,\n                        exc.__traceback__,\n                        seen=seen,\n                        group_nesting=group_nesting + 1,\n                    )\n            if not is_exception_group(exc) or group_nesting == 10:\n                yield from self._indent(\"-\" * 35, group_nesting + 1, prefix=\"+-\")\n\n    def _format_list(self, frames):\n\n        def source_message(filename, lineno, name, line):\n            message = '  File \"%s\", line %d, in %s\\n' % (filename, lineno, name)\n            if line:\n                message += \"    %s\\n\" % line.strip()\n            return message\n\n        def skip_message(count):\n            plural = \"s\" if count > 1 else \"\"\n            return \"  [Previous line repeated %d more time%s]\\n\" % (count, plural)\n\n        result = []\n        count = 0\n        last_source = None\n\n        for *source, line in frames:\n            if source != last_source and count > 3:\n                result.append(skip_message(count - 3))\n\n            if source == last_source:\n                count += 1\n                if count > 3:\n                    continue\n            else:\n                count = 1\n\n            result.append(source_message(*source, line))\n            last_source = source\n\n        # Add a final skip message if the iteration of frames ended mid-repetition.\n        if count > 3:\n            result.append(skip_message(count - 3))\n\n        return result\n\n    def format_exception(self, type_, value, tb, *, from_decorator=False):\n        yield from self._format_exception(value, tb, is_first=True, from_decorator=from_decorator)\n"
  },
  {
    "path": "loguru/_colorama.py",
    "content": "import builtins\nimport os\nimport sys\n\n\ndef should_colorize(stream):\n    if stream is None:\n        return False\n\n    is_standard_stream = stream is sys.stdout or stream is sys.stderr\n    is_original_standard_stream = stream is sys.__stdout__ or stream is sys.__stderr__\n\n    if is_standard_stream or is_original_standard_stream:\n        # Per the spec (https://no-color.org/), this needs to check for a\n        # non-empty string, not just presence of the variable:\n        if os.getenv(\"NO_COLOR\"):\n            return False\n\n        # Per the spec (https://force-color.org/), this needs to check for a\n        # non-empty string, not just presence of the variable:\n        if os.getenv(\"FORCE_COLOR\"):\n            return True\n\n    if getattr(builtins, \"__IPYTHON__\", False) and is_standard_stream:\n        try:\n            import ipykernel\n            import IPython\n\n            ipython = IPython.get_ipython()\n            is_jupyter_stream = isinstance(stream, ipykernel.iostream.OutStream)\n            is_jupyter_shell = isinstance(ipython, ipykernel.zmqshell.ZMQInteractiveShell)\n        except Exception:\n            pass\n        else:\n            if is_jupyter_stream and is_jupyter_shell:\n                return True\n\n    if is_original_standard_stream:\n        if \"CI\" in os.environ and any(\n            ci in os.environ\n            for ci in [\"TRAVIS\", \"CIRCLECI\", \"APPVEYOR\", \"GITLAB_CI\", \"GITHUB_ACTIONS\"]\n        ):\n            return True\n        if \"PYCHARM_HOSTED\" in os.environ:\n            return True\n        if os.environ.get(\"TERM\", \"\") in (\"dumb\",):\n            return False\n        if os.name == \"nt\" and \"TERM\" in os.environ:\n            return True\n\n    try:\n        return stream.isatty()\n    except Exception:\n        return False\n\n\ndef should_wrap(stream):\n    if os.name != \"nt\":\n        return False\n\n    if stream is not sys.__stdout__ and stream is not sys.__stderr__:\n        return False\n\n    from colorama.win32 import winapi_test\n\n    if not winapi_test():\n        return False\n\n    try:\n        from colorama.winterm import enable_vt_processing\n    except ImportError:\n        return True\n\n    try:\n        return not enable_vt_processing(stream.fileno())\n    except Exception:\n        return True\n\n\ndef wrap(stream):\n    from colorama import AnsiToWin32\n\n    return AnsiToWin32(stream, convert=True, strip=True, autoreset=False).stream\n"
  },
  {
    "path": "loguru/_colorizer.py",
    "content": "import re\nfrom contextlib import contextmanager\nfrom string import Formatter\n\n\n@contextmanager\ndef try_formatting(*exceptions):\n    try:\n        yield\n    except exceptions as e:\n        raise ValueError(\n            \"The logging message could not be formatted with the provided arguments.\\n\"\n            \"Common causes include:\\n\"\n            \"  - The message contains unmatched or malformed curly braces.\\n\"\n            \"  - Positional or keyword arguments are missing for the placeholders.\\n\"\n            \"  - The message is not a string.\\n\"\n            \"  - f-strings were used causing double interpolation.\\n\"\n            \"  - Contextual values were passed via kwargs but not meant for formatting.\\n\"\n            \"To avoid this, consider:\\n\"\n            \"  - Escaping non-formatting braces by doubling them.\\n\"\n            \"  - Avoiding f-string in the logged message.\\n\"\n            \"  - Using `logger.bind()` for structured context instead of kwargs.\\n\"\n        ) from e\n\n\nclass Style:\n    RESET_ALL = 0\n    BOLD = 1\n    DIM = 2\n    ITALIC = 3\n    UNDERLINE = 4\n    BLINK = 5\n    REVERSE = 7\n    HIDE = 8\n    STRIKE = 9\n    NORMAL = 22\n\n\nclass Fore:\n    BLACK = 30\n    RED = 31\n    GREEN = 32\n    YELLOW = 33\n    BLUE = 34\n    MAGENTA = 35\n    CYAN = 36\n    WHITE = 37\n    RESET = 39\n\n    LIGHTBLACK_EX = 90\n    LIGHTRED_EX = 91\n    LIGHTGREEN_EX = 92\n    LIGHTYELLOW_EX = 93\n    LIGHTBLUE_EX = 94\n    LIGHTMAGENTA_EX = 95\n    LIGHTCYAN_EX = 96\n    LIGHTWHITE_EX = 97\n\n\nclass Back:\n    BLACK = 40\n    RED = 41\n    GREEN = 42\n    YELLOW = 43\n    BLUE = 44\n    MAGENTA = 45\n    CYAN = 46\n    WHITE = 47\n    RESET = 49\n\n    LIGHTBLACK_EX = 100\n    LIGHTRED_EX = 101\n    LIGHTGREEN_EX = 102\n    LIGHTYELLOW_EX = 103\n    LIGHTBLUE_EX = 104\n    LIGHTMAGENTA_EX = 105\n    LIGHTCYAN_EX = 106\n    LIGHTWHITE_EX = 107\n\n\ndef ansi_escape(codes):\n    return {name: \"\\033[%dm\" % code for name, code in codes.items()}\n\n\nclass TokenType:\n    TEXT = 1\n    ANSI = 2\n    LEVEL = 3\n    CLOSING = 4\n\n\nclass AnsiParser:\n    _style = ansi_escape(\n        {\n            \"b\": Style.BOLD,\n            \"d\": Style.DIM,\n            \"n\": Style.NORMAL,\n            \"h\": Style.HIDE,\n            \"i\": Style.ITALIC,\n            \"l\": Style.BLINK,\n            \"s\": Style.STRIKE,\n            \"u\": Style.UNDERLINE,\n            \"v\": Style.REVERSE,\n            \"bold\": Style.BOLD,\n            \"dim\": Style.DIM,\n            \"normal\": Style.NORMAL,\n            \"hide\": Style.HIDE,\n            \"italic\": Style.ITALIC,\n            \"blink\": Style.BLINK,\n            \"strike\": Style.STRIKE,\n            \"underline\": Style.UNDERLINE,\n            \"reverse\": Style.REVERSE,\n        }\n    )\n\n    _foreground = ansi_escape(\n        {\n            \"k\": Fore.BLACK,\n            \"r\": Fore.RED,\n            \"g\": Fore.GREEN,\n            \"y\": Fore.YELLOW,\n            \"e\": Fore.BLUE,\n            \"m\": Fore.MAGENTA,\n            \"c\": Fore.CYAN,\n            \"w\": Fore.WHITE,\n            \"lk\": Fore.LIGHTBLACK_EX,\n            \"lr\": Fore.LIGHTRED_EX,\n            \"lg\": Fore.LIGHTGREEN_EX,\n            \"ly\": Fore.LIGHTYELLOW_EX,\n            \"le\": Fore.LIGHTBLUE_EX,\n            \"lm\": Fore.LIGHTMAGENTA_EX,\n            \"lc\": Fore.LIGHTCYAN_EX,\n            \"lw\": Fore.LIGHTWHITE_EX,\n            \"black\": Fore.BLACK,\n            \"red\": Fore.RED,\n            \"green\": Fore.GREEN,\n            \"yellow\": Fore.YELLOW,\n            \"blue\": Fore.BLUE,\n            \"magenta\": Fore.MAGENTA,\n            \"cyan\": Fore.CYAN,\n            \"white\": Fore.WHITE,\n            \"light-black\": Fore.LIGHTBLACK_EX,\n            \"light-red\": Fore.LIGHTRED_EX,\n            \"light-green\": Fore.LIGHTGREEN_EX,\n            \"light-yellow\": Fore.LIGHTYELLOW_EX,\n            \"light-blue\": Fore.LIGHTBLUE_EX,\n            \"light-magenta\": Fore.LIGHTMAGENTA_EX,\n            \"light-cyan\": Fore.LIGHTCYAN_EX,\n            \"light-white\": Fore.LIGHTWHITE_EX,\n        }\n    )\n\n    _background = ansi_escape(\n        {\n            \"K\": Back.BLACK,\n            \"R\": Back.RED,\n            \"G\": Back.GREEN,\n            \"Y\": Back.YELLOW,\n            \"E\": Back.BLUE,\n            \"M\": Back.MAGENTA,\n            \"C\": Back.CYAN,\n            \"W\": Back.WHITE,\n            \"LK\": Back.LIGHTBLACK_EX,\n            \"LR\": Back.LIGHTRED_EX,\n            \"LG\": Back.LIGHTGREEN_EX,\n            \"LY\": Back.LIGHTYELLOW_EX,\n            \"LE\": Back.LIGHTBLUE_EX,\n            \"LM\": Back.LIGHTMAGENTA_EX,\n            \"LC\": Back.LIGHTCYAN_EX,\n            \"LW\": Back.LIGHTWHITE_EX,\n            \"BLACK\": Back.BLACK,\n            \"RED\": Back.RED,\n            \"GREEN\": Back.GREEN,\n            \"YELLOW\": Back.YELLOW,\n            \"BLUE\": Back.BLUE,\n            \"MAGENTA\": Back.MAGENTA,\n            \"CYAN\": Back.CYAN,\n            \"WHITE\": Back.WHITE,\n            \"LIGHT-BLACK\": Back.LIGHTBLACK_EX,\n            \"LIGHT-RED\": Back.LIGHTRED_EX,\n            \"LIGHT-GREEN\": Back.LIGHTGREEN_EX,\n            \"LIGHT-YELLOW\": Back.LIGHTYELLOW_EX,\n            \"LIGHT-BLUE\": Back.LIGHTBLUE_EX,\n            \"LIGHT-MAGENTA\": Back.LIGHTMAGENTA_EX,\n            \"LIGHT-CYAN\": Back.LIGHTCYAN_EX,\n            \"LIGHT-WHITE\": Back.LIGHTWHITE_EX,\n        }\n    )\n\n    _regex_tag = re.compile(r\"(\\\\*)(</?(?:[fb]g\\s)?[^<>\\s]*>)\")\n\n    def __init__(self):\n        self._tokens = []\n        self._tags = []\n        self._color_tokens = []\n\n    @staticmethod\n    def strip(tokens):\n        output = \"\"\n        for type_, value in tokens:\n            if type_ == TokenType.TEXT:\n                output += value\n        return output\n\n    @staticmethod\n    def colorize(tokens, ansi_level):\n        output = \"\"\n\n        for type_, value in tokens:\n            if type_ == TokenType.LEVEL:\n                if ansi_level is None:\n                    raise ValueError(\n                        \"The '<level>' color tag is not allowed in this context, \"\n                        \"it has not yet been associated to any color value.\"\n                    )\n                value = ansi_level\n            output += value\n\n        return output\n\n    @staticmethod\n    def wrap(tokens, *, ansi_level, color_tokens):\n        output = \"\"\n\n        for type_, value in tokens:\n            if type_ == TokenType.LEVEL:\n                value = ansi_level\n            output += value\n            if type_ == TokenType.CLOSING:\n                for subtype, subvalue in color_tokens:\n                    if subtype == TokenType.LEVEL:\n                        subvalue = ansi_level\n                    output += subvalue\n\n        return output\n\n    def feed(self, text, *, raw=False):\n        if raw:\n            self._tokens.append((TokenType.TEXT, text))\n            return\n\n        position = 0\n\n        for match in self._regex_tag.finditer(text):\n            escaping, markup = match.group(1), match.group(2)\n\n            self._tokens.append((TokenType.TEXT, text[position : match.start()]))\n\n            position = match.end()\n\n            escaping_count = len(escaping)\n            backslashes = \"\\\\\" * (escaping_count // 2)\n\n            if escaping_count % 2 == 1:\n                self._tokens.append((TokenType.TEXT, backslashes + markup))\n                continue\n\n            if escaping_count > 0:\n                self._tokens.append((TokenType.TEXT, backslashes))\n\n            is_closing = markup[1] == \"/\"\n            tag = markup[2:-1] if is_closing else markup[1:-1]\n\n            if is_closing:\n                if self._tags and (tag == \"\" or tag == self._tags[-1]):\n                    self._tags.pop()\n                    self._color_tokens.pop()\n                    self._tokens.append((TokenType.CLOSING, \"\\033[0m\"))\n                    self._tokens.extend(self._color_tokens)\n                    continue\n                if tag in self._tags:\n                    raise ValueError('Closing tag \"%s\" violates nesting rules' % markup)\n                raise ValueError('Closing tag \"%s\" has no corresponding opening tag' % markup)\n\n            if tag in {\"lvl\", \"level\"}:\n                token = (TokenType.LEVEL, None)\n            else:\n                ansi = self._get_ansicode(tag)\n\n                if ansi is None:\n                    raise ValueError(\n                        'Tag \"%s\" does not correspond to any known color directive, '\n                        \"make sure you have not misspelled it (or prepend '\\\\' to escape it)\"\n                        % markup\n                    )\n\n                token = (TokenType.ANSI, ansi)\n\n            self._tags.append(tag)\n            self._color_tokens.append(token)\n            self._tokens.append(token)\n\n        self._tokens.append((TokenType.TEXT, text[position:]))\n\n    def done(self, *, strict=True):\n        if strict and self._tags:\n            faulty_tag = self._tags.pop(0)\n            raise ValueError('Opening tag \"<%s>\" has no corresponding closing tag' % faulty_tag)\n        return self._tokens\n\n    def current_color_tokens(self):\n        return list(self._color_tokens)\n\n    def _get_ansicode(self, tag):\n        style = self._style\n        foreground = self._foreground\n        background = self._background\n\n        # Substitute on a direct match.\n        if tag in style:\n            return style[tag]\n        if tag in foreground:\n            return foreground[tag]\n        if tag in background:\n            return background[tag]\n\n        # An alternative syntax for setting the color (e.g. <fg red>, <bg red>).\n        if tag.startswith(\"fg \") or tag.startswith(\"bg \"):\n            st, color = tag[:2], tag[3:].strip(\" \")\n            code = \"38\" if st == \"fg\" else \"48\"\n\n            if st == \"fg\" and color.lower() in foreground:\n                return foreground[color.lower()]\n            if st == \"bg\" and color.upper() in background:\n                return background[color.upper()]\n            if color.isdigit() and int(color) <= 255:\n                return \"\\033[%s;5;%sm\" % (code, color)\n            if (\n                color.startswith(\"#\")\n                and all(s in \"0123456789abcdef\" for s in color[1:].lower())\n                and len(color[1:]) in [3, 6]\n            ):\n                hex_color = color[1:]\n                if len(hex_color) == 3:\n                    r, g, b = list(hex_color)\n                    hex_color = (r * 2) + (g * 2) + (b * 2)\n                rgb = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))\n                return \"\\033[%s;2;%s;%s;%sm\" % ((code, *rgb))\n            if color.count(\",\") == 2:\n                colors = tuple(color.split(\",\"))\n                if all(x.isdigit() and int(x) <= 255 for x in colors):\n                    return \"\\033[%s;2;%s;%s;%sm\" % ((code, *colors))\n\n        return None\n\n\nclass ColoringMessage(str):\n    __fields__ = (\"_messages\",)\n\n    def __format__(self, spec):\n        return next(self._messages).__format__(spec)\n\n\nclass ColoredMessage:\n    def __init__(self, tokens):\n        self.tokens = tokens\n        self.stripped = AnsiParser.strip(tokens)\n\n    def colorize(self, ansi_level):\n        return AnsiParser.colorize(self.tokens, ansi_level)\n\n\nclass ColoredFormat:\n    def __init__(self, tokens, messages_color_tokens):\n        self._tokens = tokens\n        self._messages_color_tokens = messages_color_tokens\n\n    def strip(self):\n        return AnsiParser.strip(self._tokens)\n\n    def colorize(self, ansi_level):\n        return AnsiParser.colorize(self._tokens, ansi_level)\n\n    def make_coloring_message(self, message, *, ansi_level, colored_message):\n        messages = [\n            (\n                message\n                if color_tokens is None\n                else AnsiParser.wrap(\n                    colored_message.tokens, ansi_level=ansi_level, color_tokens=color_tokens\n                )\n            )\n            for color_tokens in self._messages_color_tokens\n        ]\n        coloring = ColoringMessage(message)\n        coloring._messages = iter(messages)\n        return coloring\n\n\nclass Colorizer:\n    @staticmethod\n    def prepare_format(string):\n        tokens, messages_color_tokens = Colorizer._parse_without_formatting(string)\n        return ColoredFormat(tokens, messages_color_tokens)\n\n    @staticmethod\n    def prepare_message(string, args=(), kwargs={}):  # noqa: B006\n        tokens = Colorizer._parse_with_formatting(string, args, kwargs)\n        return ColoredMessage(tokens)\n\n    @staticmethod\n    def prepare_simple_message(string):\n        parser = AnsiParser()\n        parser.feed(string)\n        tokens = parser.done()\n        return ColoredMessage(tokens)\n\n    @staticmethod\n    def ansify(text):\n        parser = AnsiParser()\n        parser.feed(text.strip())\n        tokens = parser.done(strict=False)\n        return AnsiParser.colorize(tokens, None)\n\n    @staticmethod\n    def _parse_with_formatting(\n        string, args, kwargs, *, recursion_depth=2, auto_arg_index=0, recursive=False\n    ):\n        # This function re-implements Formatter._vformat()\n\n        if recursion_depth < 0:\n            raise ValueError(\"Max string recursion exceeded\")\n\n        formatter = Formatter()\n        parser = AnsiParser()\n\n        with try_formatting(TypeError, ValueError):\n            parsing_output = list(formatter.parse(string))\n\n        for literal_text, field_name, format_spec, conversion in parsing_output:\n            parser.feed(literal_text, raw=recursive)\n\n            if field_name is not None:\n                if field_name == \"\":\n                    if auto_arg_index is False:\n                        raise ValueError(\n                            \"cannot switch from manual field \"\n                            \"specification to automatic field \"\n                            \"numbering\"\n                        )\n                    field_name = str(auto_arg_index)\n                    auto_arg_index += 1\n                elif field_name.isdigit():\n                    if auto_arg_index:\n                        raise ValueError(\n                            \"cannot switch from manual field \"\n                            \"specification to automatic field \"\n                            \"numbering\"\n                        )\n                    auto_arg_index = False\n\n                with try_formatting(KeyError, IndexError, AttributeError):\n                    obj, _ = formatter.get_field(field_name, args, kwargs)\n\n                obj = formatter.convert_field(obj, conversion)\n\n                format_spec, auto_arg_index = Colorizer._parse_with_formatting(\n                    format_spec,\n                    args,\n                    kwargs,\n                    recursion_depth=recursion_depth - 1,\n                    auto_arg_index=auto_arg_index,\n                    recursive=True,\n                )\n\n                formatted = formatter.format_field(obj, format_spec)\n                parser.feed(formatted, raw=True)\n\n        tokens = parser.done()\n\n        if recursive:\n            return AnsiParser.strip(tokens), auto_arg_index\n\n        return tokens\n\n    @staticmethod\n    def _parse_without_formatting(string, *, recursion_depth=2, recursive=False):\n        if recursion_depth < 0:\n            raise ValueError(\"Max string recursion exceeded\")\n\n        formatter = Formatter()\n        parser = AnsiParser()\n\n        messages_color_tokens = []\n\n        for literal_text, field_name, format_spec, conversion in formatter.parse(string):\n            if literal_text and literal_text[-1] in \"{}\":\n                literal_text += literal_text[-1]\n\n            parser.feed(literal_text, raw=recursive)\n\n            if field_name is not None:\n                if field_name == \"message\":\n                    if recursive:\n                        messages_color_tokens.append(None)\n                    else:\n                        color_tokens = parser.current_color_tokens()\n                        messages_color_tokens.append(color_tokens)\n                field = \"{%s\" % field_name\n                if conversion:\n                    field += \"!%s\" % conversion\n                if format_spec:\n                    field += \":%s\" % format_spec\n                field += \"}\"\n                parser.feed(field, raw=True)\n\n                _, color_tokens = Colorizer._parse_without_formatting(\n                    format_spec, recursion_depth=recursion_depth - 1, recursive=True\n                )\n                messages_color_tokens.extend(color_tokens)\n\n        return parser.done(), messages_color_tokens\n"
  },
  {
    "path": "loguru/_contextvars.py",
    "content": "import sys\n\n\ndef load_contextvar_class():\n    if sys.version_info >= (3, 7):\n        from contextvars import ContextVar\n    elif sys.version_info >= (3, 5, 3):\n        from aiocontextvars import ContextVar\n    else:\n        from contextvars import ContextVar\n\n    return ContextVar\n\n\nContextVar = load_contextvar_class()\n"
  },
  {
    "path": "loguru/_ctime_functions.py",
    "content": "import os\n\n\ndef load_ctime_functions():\n    if os.name == \"nt\":\n        import win32_setctime\n\n        def get_ctime_windows(filepath):\n            return os.stat(filepath).st_ctime\n\n        def set_ctime_windows(filepath, timestamp):\n            if not win32_setctime.SUPPORTED:\n                return\n\n            try:\n                win32_setctime.setctime(filepath, timestamp)\n            except (OSError, ValueError):\n                pass\n\n        return get_ctime_windows, set_ctime_windows\n\n    if hasattr(os.stat_result, \"st_birthtime\"):\n\n        def get_ctime_macos(filepath):\n            return os.stat(filepath).st_birthtime\n\n        def set_ctime_macos(filepath, timestamp):\n            pass\n\n        return get_ctime_macos, set_ctime_macos\n\n    if hasattr(os, \"getxattr\") and hasattr(os, \"setxattr\"):\n\n        def get_ctime_linux(filepath):\n            try:\n                return float(os.getxattr(filepath, b\"user.loguru_crtime\"))\n            except OSError:\n                return os.stat(filepath).st_mtime\n\n        def set_ctime_linux(filepath, timestamp):\n            try:\n                os.setxattr(filepath, b\"user.loguru_crtime\", str(timestamp).encode(\"ascii\"))\n            except OSError:\n                pass\n\n        return get_ctime_linux, set_ctime_linux\n\n    def get_ctime_fallback(filepath):\n        return os.stat(filepath).st_mtime\n\n    def set_ctime_fallback(filepath, timestamp):\n        pass\n\n    return get_ctime_fallback, set_ctime_fallback\n\n\nget_ctime, set_ctime = load_ctime_functions()\n"
  },
  {
    "path": "loguru/_datetime.py",
    "content": "import re\nfrom calendar import day_abbr, day_name, month_abbr, month_name\nfrom datetime import datetime as datetime_\nfrom datetime import timedelta, timezone\nfrom functools import lru_cache, partial\nfrom time import localtime, strftime\n\ntokens = r\"H{1,2}|h{1,2}|m{1,2}|s{1,2}|S+|YYYY|YY|M{1,4}|D{1,4}|Z{1,2}|zz|A|X|x|E|Q|dddd|ddd|d\"\n\npattern = re.compile(r\"(?:{0})|\\[(?:{0}|!UTC|)\\]\".format(tokens))\n\n\ndef _builtin_datetime_formatter(is_utc, format_string, dt):\n    if is_utc:\n        dt = dt.astimezone(timezone.utc)\n    return dt.strftime(format_string)\n\n\ndef _loguru_datetime_formatter(is_utc, format_string, formatters, dt):\n    if is_utc:\n        dt = dt.astimezone(timezone.utc)\n    t = dt.timetuple()\n    args = tuple(f(t, dt) for f in formatters)\n    return format_string % args\n\n\ndef _default_datetime_formatter(dt):\n    return \"%04d-%02d-%02d %02d:%02d:%02d.%03d %s\" % (\n        dt.year,\n        dt.month,\n        dt.day,\n        dt.hour,\n        dt.minute,\n        dt.second,\n        dt.microsecond // 1000,\n        _format_timezone(dt, sep=\":\"),\n    )\n\n\ndef _format_timezone(dt, *, sep):\n    tzinfo = dt.tzinfo or timezone.utc\n    offset = tzinfo.utcoffset(dt).total_seconds()\n    sign = \"+\" if offset >= 0 else \"-\"\n    (h, m), s = divmod(abs(offset // 60), 60), abs(offset) % 60\n    z = \"%s%02d%s%02d\" % (sign, h, sep, m)\n    if s > 0:\n        if s.is_integer():\n            z += \"%s%02d\" % (sep, s)\n        else:\n            z += \"%s%09.06f\" % (sep, s)\n    return z\n\n\n@lru_cache(maxsize=32)\ndef _compile_format(spec):\n    if spec == \"YYYY-MM-DD HH:mm:ss.SSS Z\":\n        return _default_datetime_formatter\n\n    is_utc = spec.endswith(\"!UTC\")\n\n    if is_utc:\n        spec = spec[:-4]\n\n    if not spec:\n        spec = \"%Y-%m-%dT%H:%M:%S.%f%z\"\n\n    if \"%\" in spec:\n        return partial(_builtin_datetime_formatter, is_utc, spec)\n\n    if \"SSSSSSS\" in spec:\n        raise ValueError(\n            \"Invalid time format: the provided format string contains more than six successive \"\n            \"'S' characters. This may be due to an attempt to use nanosecond precision, which \"\n            \"is not supported.\"\n        )\n\n    rep = {\n        \"YYYY\": (\"%04d\", lambda t, dt: t.tm_year),\n        \"YY\": (\"%02d\", lambda t, dt: t.tm_year % 100),\n        \"Q\": (\"%d\", lambda t, dt: (t.tm_mon - 1) // 3 + 1),\n        \"MMMM\": (\"%s\", lambda t, dt: month_name[t.tm_mon]),\n        \"MMM\": (\"%s\", lambda t, dt: month_abbr[t.tm_mon]),\n        \"MM\": (\"%02d\", lambda t, dt: t.tm_mon),\n        \"M\": (\"%d\", lambda t, dt: t.tm_mon),\n        \"DDDD\": (\"%03d\", lambda t, dt: t.tm_yday),\n        \"DDD\": (\"%d\", lambda t, dt: t.tm_yday),\n        \"DD\": (\"%02d\", lambda t, dt: t.tm_mday),\n        \"D\": (\"%d\", lambda t, dt: t.tm_mday),\n        \"dddd\": (\"%s\", lambda t, dt: day_name[t.tm_wday]),\n        \"ddd\": (\"%s\", lambda t, dt: day_abbr[t.tm_wday]),\n        \"d\": (\"%d\", lambda t, dt: t.tm_wday),\n        \"E\": (\"%d\", lambda t, dt: t.tm_wday + 1),\n        \"HH\": (\"%02d\", lambda t, dt: t.tm_hour),\n        \"H\": (\"%d\", lambda t, dt: t.tm_hour),\n        \"hh\": (\"%02d\", lambda t, dt: (t.tm_hour - 1) % 12 + 1),\n        \"h\": (\"%d\", lambda t, dt: (t.tm_hour - 1) % 12 + 1),\n        \"mm\": (\"%02d\", lambda t, dt: t.tm_min),\n        \"m\": (\"%d\", lambda t, dt: t.tm_min),\n        \"ss\": (\"%02d\", lambda t, dt: t.tm_sec),\n        \"s\": (\"%d\", lambda t, dt: t.tm_sec),\n        \"S\": (\"%d\", lambda t, dt: dt.microsecond // 100000),\n        \"SS\": (\"%02d\", lambda t, dt: dt.microsecond // 10000),\n        \"SSS\": (\"%03d\", lambda t, dt: dt.microsecond // 1000),\n        \"SSSS\": (\"%04d\", lambda t, dt: dt.microsecond // 100),\n        \"SSSSS\": (\"%05d\", lambda t, dt: dt.microsecond // 10),\n        \"SSSSSS\": (\"%06d\", lambda t, dt: dt.microsecond),\n        \"A\": (\"%s\", lambda t, dt: \"AM\" if t.tm_hour < 12 else \"PM\"),\n        \"Z\": (\"%s\", lambda t, dt: _format_timezone(dt, sep=\":\")),\n        \"ZZ\": (\"%s\", lambda t, dt: _format_timezone(dt, sep=\"\")),\n        \"zz\": (\"%s\", lambda t, dt: (dt.tzinfo or timezone.utc).tzname(dt) or \"\"),\n        \"X\": (\"%d\", lambda t, dt: dt.timestamp()),\n        \"x\": (\"%d\", lambda t, dt: int(dt.timestamp()) * 1000000 + dt.microsecond),\n    }\n\n    format_string = \"\"\n    formatters = []\n    pos = 0\n\n    for match in pattern.finditer(spec):\n        start, end = match.span()\n        format_string += spec[pos:start]\n        pos = end\n\n        token = match.group(0)\n\n        try:\n            specifier, formatter = rep[token]\n        except KeyError:\n            format_string += token[1:-1]\n        else:\n            format_string += specifier\n            formatters.append(formatter)\n\n    format_string += spec[pos:]\n\n    return partial(_loguru_datetime_formatter, is_utc, format_string, formatters)\n\n\nclass datetime(datetime_):  # noqa: N801\n    def __format__(self, fmt):\n        return _compile_format(fmt)(self)\n\n\ndef _fallback_tzinfo(timestamp):\n    utc_naive = datetime_.fromtimestamp(timestamp, tz=timezone.utc).replace(tzinfo=None)\n    offset = datetime_.fromtimestamp(timestamp) - utc_naive\n    seconds = offset.total_seconds()\n    zone = strftime(\"%Z\")\n    return timezone(timedelta(seconds=seconds), zone)\n\n\ndef _get_tzinfo(timestamp):\n    try:\n        local = localtime(timestamp)\n    except (OSError, OverflowError):\n        # The \"localtime()\" can overflow on some platforms when the timestamp is too large.\n        # Not sure the fallback won't also overflow, though.\n        return _fallback_tzinfo(timestamp)\n\n    try:\n        seconds = local.tm_gmtoff\n        zone = local.tm_zone\n    except AttributeError:\n        # The attributes were not availanble on all platforms before Python 3.6.\n        return _fallback_tzinfo(timestamp)\n\n    try:\n        return timezone(timedelta(seconds=seconds), zone)\n    except ValueError:\n        # The number of seconds returned by \"tm_gmtoff\" might be invalid on Windows (year 2038+).\n        # Curiously, the fallback workaround does not exhibit the same problem.\n        return _fallback_tzinfo(timestamp)\n\n\ndef aware_now():\n    now = datetime_.now()\n    timestamp = now.timestamp()\n    tzinfo = _get_tzinfo(timestamp)\n    return datetime.combine(now.date(), now.time().replace(tzinfo=tzinfo))\n"
  },
  {
    "path": "loguru/_defaults.py",
    "content": "from os import environ\n\n\ndef env(key, type_, default=None):\n    if key not in environ:\n        return default\n\n    val = environ[key]\n\n    if type_ is str:\n        return val\n    if type_ is bool:\n        if val.lower() in [\"1\", \"true\", \"yes\", \"y\", \"ok\", \"on\"]:\n            return True\n        if val.lower() in [\"0\", \"false\", \"no\", \"n\", \"nok\", \"off\"]:\n            return False\n        raise ValueError(\n            \"Invalid environment variable '%s' (expected a boolean): '%s'\" % (key, val)\n        )\n    if type_ is int:\n        try:\n            return int(val)\n        except ValueError:\n            raise ValueError(\n                \"Invalid environment variable '%s' (expected an integer): '%s'\" % (key, val)\n            ) from None\n    raise ValueError(\"The requested type '%s' is not supported\" % type_.__name__)\n\n\nLOGURU_AUTOINIT = env(\"LOGURU_AUTOINIT\", bool, True)\n\nLOGURU_FORMAT = env(\n    \"LOGURU_FORMAT\",\n    str,\n    \"<green>{time:YYYY-MM-DD HH:mm:ss.SSS Z}</green> | \"\n    \"<level>{level: <8}</level> | \"\n    \"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>\",\n)\nLOGURU_FILTER = env(\"LOGURU_FILTER\", str, None)\nLOGURU_LEVEL = env(\"LOGURU_LEVEL\", str, \"DEBUG\")\nLOGURU_COLORIZE = env(\"LOGURU_COLORIZE\", bool, None)\nLOGURU_SERIALIZE = env(\"LOGURU_SERIALIZE\", bool, False)\nLOGURU_BACKTRACE = env(\"LOGURU_BACKTRACE\", bool, True)\nLOGURU_DIAGNOSE = env(\"LOGURU_DIAGNOSE\", bool, True)\nLOGURU_ENQUEUE = env(\"LOGURU_ENQUEUE\", bool, False)\nLOGURU_CONTEXT = env(\"LOGURU_CONTEXT\", str, None)\nLOGURU_CATCH = env(\"LOGURU_CATCH\", bool, True)\n\nLOGURU_TRACE_NO = env(\"LOGURU_TRACE_NO\", int, 5)\nLOGURU_TRACE_COLOR = env(\"LOGURU_TRACE_COLOR\", str, \"<cyan><bold>\")\nLOGURU_TRACE_ICON = env(\"LOGURU_TRACE_ICON\", str, \"\\u270f\\ufe0f\")  # Pencil\n\nLOGURU_DEBUG_NO = env(\"LOGURU_DEBUG_NO\", int, 10)\nLOGURU_DEBUG_COLOR = env(\"LOGURU_DEBUG_COLOR\", str, \"<blue><bold>\")\nLOGURU_DEBUG_ICON = env(\"LOGURU_DEBUG_ICON\", str, \"\\U0001f41e\")  # Lady Beetle\n\nLOGURU_INFO_NO = env(\"LOGURU_INFO_NO\", int, 20)\nLOGURU_INFO_COLOR = env(\"LOGURU_INFO_COLOR\", str, \"<bold>\")\nLOGURU_INFO_ICON = env(\"LOGURU_INFO_ICON\", str, \"\\u2139\\ufe0f\")  # Information\n\nLOGURU_SUCCESS_NO = env(\"LOGURU_SUCCESS_NO\", int, 25)\nLOGURU_SUCCESS_COLOR = env(\"LOGURU_SUCCESS_COLOR\", str, \"<green><bold>\")\nLOGURU_SUCCESS_ICON = env(\"LOGURU_SUCCESS_ICON\", str, \"\\u2705\")  # White Heavy Check Mark\n\nLOGURU_WARNING_NO = env(\"LOGURU_WARNING_NO\", int, 30)\nLOGURU_WARNING_COLOR = env(\"LOGURU_WARNING_COLOR\", str, \"<yellow><bold>\")\nLOGURU_WARNING_ICON = env(\"LOGURU_WARNING_ICON\", str, \"\\u26a0\\ufe0f\")  # Warning\n\nLOGURU_ERROR_NO = env(\"LOGURU_ERROR_NO\", int, 40)\nLOGURU_ERROR_COLOR = env(\"LOGURU_ERROR_COLOR\", str, \"<red><bold>\")\nLOGURU_ERROR_ICON = env(\"LOGURU_ERROR_ICON\", str, \"\\u274c\")  # Cross Mark\n\nLOGURU_CRITICAL_NO = env(\"LOGURU_CRITICAL_NO\", int, 50)\nLOGURU_CRITICAL_COLOR = env(\"LOGURU_CRITICAL_COLOR\", str, \"<RED><bold>\")\nLOGURU_CRITICAL_ICON = env(\"LOGURU_CRITICAL_ICON\", str, \"\\u2620\\ufe0f\")  # Skull and Crossbones\n"
  },
  {
    "path": "loguru/_error_interceptor.py",
    "content": "import sys\nimport traceback\n\n\nclass ErrorInterceptor:\n    def __init__(self, should_catch, handler_id):\n        self._should_catch = should_catch\n        self._handler_id = handler_id\n\n    def should_catch(self):\n        return self._should_catch\n\n    def print(self, record=None, *, exception=None):\n        if not sys.stderr:\n            return\n\n        if exception is None:\n            type_, value, traceback_ = sys.exc_info()\n        else:\n            type_, value, traceback_ = (type(exception), exception, exception.__traceback__)\n\n        try:\n            sys.stderr.write(\"--- Logging error in Loguru Handler #%d ---\\n\" % self._handler_id)\n            try:\n                record_repr = str(record)\n            except Exception:\n                record_repr = \"/!\\\\ Unprintable record /!\\\\\"\n            sys.stderr.write(\"Record was: %s\\n\" % record_repr)\n            traceback.print_exception(type_, value, traceback_, None, sys.stderr)\n            sys.stderr.write(\"--- End of logging error ---\\n\")\n        except OSError:\n            pass\n        finally:\n            del type_, value, traceback_\n"
  },
  {
    "path": "loguru/_file_sink.py",
    "content": "import datetime\nimport decimal\nimport glob\nimport numbers\nimport os\nimport shutil\nimport string\nfrom functools import partial\nfrom stat import ST_DEV, ST_INO\n\nfrom . import _string_parsers as string_parsers\nfrom ._ctime_functions import get_ctime, set_ctime\nfrom ._datetime import aware_now\n\n\ndef generate_rename_path(root, ext, creation_time):\n    creation_datetime = datetime.datetime.fromtimestamp(creation_time)\n    date = FileDateFormatter(creation_datetime)\n\n    renamed_path = \"{}.{}{}\".format(root, date, ext)\n    counter = 1\n\n    while os.path.exists(renamed_path):\n        counter += 1\n        renamed_path = \"{}.{}.{}{}\".format(root, date, counter, ext)\n\n    return renamed_path\n\n\nclass FileDateFormatter:\n    def __init__(self, datetime=None):\n        self.datetime = datetime or aware_now()\n\n    def __format__(self, spec):\n        if not spec:\n            spec = \"%Y-%m-%d_%H-%M-%S_%f\"\n        return self.datetime.__format__(spec)\n\n\nclass Compression:\n    @staticmethod\n    def add_compress(path_in, path_out, opener, **kwargs):\n        with opener(path_out, **kwargs) as f_comp:\n            f_comp.add(path_in, os.path.basename(path_in))\n\n    @staticmethod\n    def write_compress(path_in, path_out, opener, **kwargs):\n        with opener(path_out, **kwargs) as f_comp:\n            f_comp.write(path_in, os.path.basename(path_in))\n\n    @staticmethod\n    def copy_compress(path_in, path_out, opener, **kwargs):\n        with open(path_in, \"rb\") as f_in:\n            with opener(path_out, **kwargs) as f_out:\n                shutil.copyfileobj(f_in, f_out)\n\n    @staticmethod\n    def compression(path_in, ext, compress_function):\n        path_out = \"{}{}\".format(path_in, ext)\n\n        if os.path.exists(path_out):\n            creation_time = get_ctime(path_out)\n            root, ext_before = os.path.splitext(path_in)\n            renamed_path = generate_rename_path(root, ext_before + ext, creation_time)\n            os.rename(path_out, renamed_path)\n        compress_function(path_in, path_out)\n        os.remove(path_in)\n\n\nclass Retention:\n    @staticmethod\n    def retention_count(logs, number):\n        def key_log(log):\n            return (-os.stat(log).st_mtime, log)\n\n        for log in sorted(logs, key=key_log)[number:]:\n            os.remove(log)\n\n    @staticmethod\n    def retention_age(logs, seconds):\n        t = datetime.datetime.now().timestamp()\n        for log in logs:\n            if os.stat(log).st_mtime <= t - seconds:\n                os.remove(log)\n\n\nclass Rotation:\n    @staticmethod\n    def forward_day(t):\n        return t + datetime.timedelta(days=1)\n\n    @staticmethod\n    def forward_weekday(t, weekday):\n        while True:\n            t += datetime.timedelta(days=1)\n            if t.weekday() == weekday:\n                return t\n\n    @staticmethod\n    def forward_interval(t, interval):\n        return t + interval\n\n    @staticmethod\n    def rotation_size(message, file, size_limit):\n        file.seek(0, 2)\n        return file.tell() + len(message) > size_limit\n\n    class RotationTime:\n        def __init__(self, step_forward, time_init=None):\n            self._step_forward = step_forward\n            self._time_init = time_init\n            self._limit = None\n\n        def __call__(self, message, file):\n            record_time = message.record[\"time\"]\n\n            if self._limit is None:\n                filepath = os.path.realpath(file.name)\n                creation_time = get_ctime(filepath)\n                set_ctime(filepath, creation_time)\n                start_time = datetime.datetime.fromtimestamp(\n                    creation_time, tz=datetime.timezone.utc\n                )\n\n                time_init = self._time_init\n\n                if time_init is None:\n                    limit = start_time.astimezone(record_time.tzinfo).replace(tzinfo=None)\n                    limit = self._step_forward(limit)\n                else:\n                    tzinfo = record_time.tzinfo if time_init.tzinfo is None else time_init.tzinfo\n                    limit = start_time.astimezone(tzinfo).replace(\n                        hour=time_init.hour,\n                        minute=time_init.minute,\n                        second=time_init.second,\n                        microsecond=time_init.microsecond,\n                    )\n\n                    if limit <= start_time:\n                        limit = self._step_forward(limit)\n\n                    if time_init.tzinfo is None:\n                        limit = limit.replace(tzinfo=None)\n\n                self._limit = limit\n\n            if self._limit.tzinfo is None:\n                record_time = record_time.replace(tzinfo=None)\n\n            if record_time >= self._limit:\n                while self._limit <= record_time:\n                    self._limit = self._step_forward(self._limit)\n                return True\n            return False\n\n    class RotationGroup:\n        def __init__(self, rotations) -> None:\n            self._rotations = rotations\n\n        def __call__(self, message, file) -> bool:\n            return any(rotation(message, file) for rotation in self._rotations)\n\n\nclass FileSink:\n    def __init__(\n        self,\n        path,\n        *,\n        rotation=None,\n        retention=None,\n        compression=None,\n        delay=False,\n        watch=False,\n        mode=\"a\",\n        buffering=1,\n        encoding=\"utf8\",\n        **kwargs\n    ):\n        self.encoding = encoding\n\n        self._kwargs = {**kwargs, \"mode\": mode, \"buffering\": buffering, \"encoding\": self.encoding}\n        self._path = str(path)\n\n        self._glob_patterns = self._make_glob_patterns(self._path)\n        self._rotation_function = self._make_rotation_function(rotation)\n        self._retention_function = self._make_retention_function(retention)\n        self._compression_function = self._make_compression_function(compression)\n\n        self._file = None\n        self._file_path = None\n\n        self._watch = watch\n        self._file_dev = -1\n        self._file_ino = -1\n\n        if not delay:\n            path = self._create_path()\n            self._create_dirs(path)\n            self._create_file(path)\n\n    def write(self, message):\n        if self._file is None:\n            path = self._create_path()\n            self._create_dirs(path)\n            self._create_file(path)\n\n        if self._watch:\n            self._reopen_if_needed()\n\n        if self._rotation_function is not None and self._rotation_function(message, self._file):\n            self._terminate_file(is_rotating=True)\n\n        self._file.write(message)\n\n    def stop(self):\n        if self._watch:\n            self._reopen_if_needed()\n\n        self._terminate_file(is_rotating=False)\n\n    def tasks_to_complete(self):\n        return []\n\n    def _create_path(self):\n        path = self._path.format_map({\"time\": FileDateFormatter()})\n        return os.path.abspath(path)\n\n    def _create_dirs(self, path):\n        dirname = os.path.dirname(path)\n        os.makedirs(dirname, exist_ok=True)\n\n    def _create_file(self, path):\n        self._file = open(path, **self._kwargs)\n        self._file_path = path\n\n        if self._watch:\n            fileno = self._file.fileno()\n            result = os.fstat(fileno)\n            self._file_dev = result[ST_DEV]\n            self._file_ino = result[ST_INO]\n\n    def _close_file(self):\n        self._file.flush()\n        self._file.close()\n\n        self._file = None\n        self._file_path = None\n        self._file_dev = -1\n        self._file_ino = -1\n\n    def _reopen_if_needed(self):\n        # Implemented based on standard library:\n        # https://github.com/python/cpython/blob/cb589d1b/Lib/logging/handlers.py#L486\n        if not self._file:\n            return\n\n        filepath = self._file_path\n\n        try:\n            result = os.stat(filepath)\n        except FileNotFoundError:\n            result = None\n\n        if not result or result[ST_DEV] != self._file_dev or result[ST_INO] != self._file_ino:\n            self._close_file()\n            self._create_dirs(filepath)\n            self._create_file(filepath)\n\n    def _terminate_file(self, *, is_rotating=False):\n        old_path = self._file_path\n\n        if self._file is not None:\n            self._close_file()\n\n        if is_rotating:\n            new_path = self._create_path()\n            self._create_dirs(new_path)\n\n            if new_path == old_path:\n                creation_time = get_ctime(old_path)\n                root, ext = os.path.splitext(old_path)\n                renamed_path = generate_rename_path(root, ext, creation_time)\n                os.rename(old_path, renamed_path)\n                old_path = renamed_path\n\n        if is_rotating or self._rotation_function is None:\n            if self._compression_function is not None and old_path is not None:\n                self._compression_function(old_path)\n\n            if self._retention_function is not None:\n                logs = {\n                    file\n                    for pattern in self._glob_patterns\n                    for file in glob.glob(pattern)\n                    if os.path.isfile(file)\n                }\n                self._retention_function(list(logs))\n\n        if is_rotating:\n            self._create_file(new_path)\n            set_ctime(new_path, datetime.datetime.now().timestamp())\n\n    @staticmethod\n    def _make_glob_patterns(path):\n        formatter = string.Formatter()\n        tokens = formatter.parse(path)\n        escaped = \"\".join(glob.escape(text) + \"*\" * (name is not None) for text, name, *_ in tokens)\n\n        root, ext = os.path.splitext(escaped)\n\n        if not ext:\n            return [escaped, escaped + \".*\"]\n\n        return [escaped, escaped + \".*\", root + \".*\" + ext, root + \".*\" + ext + \".*\"]\n\n    @staticmethod\n    def _make_rotation_function(rotation):\n        if rotation is None:\n            return None\n        if isinstance(rotation, (list, tuple, set)):\n            if len(rotation) == 0:\n                raise ValueError(\"Must provide at least one rotation condition\")\n            return Rotation.RotationGroup(\n                [FileSink._make_rotation_function(rot) for rot in rotation]\n            )\n        if isinstance(rotation, str):\n            size = string_parsers.parse_size(rotation)\n            if size is not None:\n                return FileSink._make_rotation_function(size)\n            interval = string_parsers.parse_duration(rotation)\n            if interval is not None:\n                return FileSink._make_rotation_function(interval)\n            frequency = string_parsers.parse_frequency(rotation)\n            if frequency is not None:\n                return Rotation.RotationTime(frequency)\n            daytime = string_parsers.parse_daytime(rotation)\n            if daytime is not None:\n                day, time = daytime\n                if day is None:\n                    return FileSink._make_rotation_function(time)\n                if time is None:\n                    time = datetime.time(0, 0, 0)\n                step_forward = partial(Rotation.forward_weekday, weekday=day)\n                return Rotation.RotationTime(step_forward, time)\n            raise ValueError(\"Cannot parse rotation from: '%s'\" % rotation)\n        if isinstance(rotation, (numbers.Real, decimal.Decimal)):\n            return partial(Rotation.rotation_size, size_limit=rotation)\n        if isinstance(rotation, datetime.time):\n            return Rotation.RotationTime(Rotation.forward_day, rotation)\n        if isinstance(rotation, datetime.timedelta):\n            step_forward = partial(Rotation.forward_interval, interval=rotation)\n            return Rotation.RotationTime(step_forward)\n        if callable(rotation):\n            return rotation\n        raise TypeError(\"Cannot infer rotation for objects of type: '%s'\" % type(rotation).__name__)\n\n    @staticmethod\n    def _make_retention_function(retention):\n        if retention is None:\n            return None\n        if isinstance(retention, str):\n            interval = string_parsers.parse_duration(retention)\n            if interval is None:\n                raise ValueError(\"Cannot parse retention from: '%s'\" % retention)\n            return FileSink._make_retention_function(interval)\n        if isinstance(retention, int):\n            return partial(Retention.retention_count, number=retention)\n        if isinstance(retention, datetime.timedelta):\n            return partial(Retention.retention_age, seconds=retention.total_seconds())\n        if callable(retention):\n            return retention\n        raise TypeError(\n            \"Cannot infer retention for objects of type: '%s'\" % type(retention).__name__\n        )\n\n    @staticmethod\n    def _make_compression_function(compression):\n        if compression is None:\n            return None\n        if isinstance(compression, str):\n            ext = compression.strip().lstrip(\".\")\n\n            if ext == \"gz\":\n                import gzip\n\n                compress = partial(Compression.copy_compress, opener=gzip.open, mode=\"wb\")\n            elif ext == \"bz2\":\n                import bz2\n\n                compress = partial(Compression.copy_compress, opener=bz2.open, mode=\"wb\")\n\n            elif ext == \"xz\":\n                import lzma\n\n                compress = partial(\n                    Compression.copy_compress, opener=lzma.open, mode=\"wb\", format=lzma.FORMAT_XZ\n                )\n\n            elif ext == \"lzma\":\n                import lzma\n\n                compress = partial(\n                    Compression.copy_compress, opener=lzma.open, mode=\"wb\", format=lzma.FORMAT_ALONE\n                )\n            elif ext == \"tar\":\n                import tarfile\n\n                compress = partial(Compression.add_compress, opener=tarfile.open, mode=\"w:\")\n            elif ext == \"tar.gz\":\n                import gzip\n                import tarfile\n\n                compress = partial(Compression.add_compress, opener=tarfile.open, mode=\"w:gz\")\n            elif ext == \"tar.bz2\":\n                import bz2\n                import tarfile\n\n                compress = partial(Compression.add_compress, opener=tarfile.open, mode=\"w:bz2\")\n\n            elif ext == \"tar.xz\":\n                import lzma\n                import tarfile\n\n                compress = partial(Compression.add_compress, opener=tarfile.open, mode=\"w:xz\")\n            elif ext == \"zip\":\n                import zipfile\n\n                compress = partial(\n                    Compression.write_compress,\n                    opener=zipfile.ZipFile,\n                    mode=\"w\",\n                    compression=zipfile.ZIP_DEFLATED,\n                )\n            else:\n                raise ValueError(\"Invalid compression format: '%s'\" % ext)\n\n            return partial(Compression.compression, ext=\".\" + ext, compress_function=compress)\n        if callable(compression):\n            return compression\n        raise TypeError(\n            \"Cannot infer compression for objects of type: '%s'\" % type(compression).__name__\n        )\n"
  },
  {
    "path": "loguru/_filters.py",
    "content": "def filter_none(record):\n    return record[\"name\"] is not None\n\n\ndef filter_by_name(record, parent, length):\n    name = record[\"name\"]\n    if name is None:\n        return False\n    return (name + \".\")[:length] == parent\n\n\ndef filter_by_level(record, level_per_module):\n    name = record[\"name\"]\n\n    while True:\n        level = level_per_module.get(name, None)\n        if level is False:\n            return False\n        if level is not None:\n            return record[\"level\"].no >= level\n        if not name:\n            return True\n        index = name.rfind(\".\")\n        name = name[:index] if index != -1 else \"\"\n"
  },
  {
    "path": "loguru/_get_frame.py",
    "content": "import sys\nfrom sys import exc_info\n\n\ndef get_frame_fallback(n):\n    try:\n        raise Exception\n    except Exception:\n        frame = exc_info()[2].tb_frame.f_back\n        for _ in range(n):\n            frame = frame.f_back\n        return frame\n\n\ndef load_get_frame_function():\n    if hasattr(sys, \"_getframe\"):\n        get_frame = sys._getframe\n    else:\n        get_frame = get_frame_fallback\n    return get_frame\n\n\nget_frame = load_get_frame_function()\n"
  },
  {
    "path": "loguru/_handler.py",
    "content": "import functools\nimport json\nimport multiprocessing\nimport os\nimport threading\nfrom contextlib import contextmanager\nfrom threading import Thread\n\nfrom ._colorizer import Colorizer\nfrom ._locks_machinery import create_handler_lock\n\n\ndef prepare_colored_format(format_, ansi_level):\n    colored = Colorizer.prepare_format(format_)\n    return colored, colored.colorize(ansi_level)\n\n\ndef prepare_stripped_format(format_):\n    colored = Colorizer.prepare_format(format_)\n    return colored.strip()\n\n\ndef memoize(function):\n    return functools.lru_cache(maxsize=64)(function)\n\n\nclass Message(str):\n    __slots__ = (\"record\",)\n\n\nclass Handler:\n    def __init__(\n        self,\n        *,\n        sink,\n        name,\n        levelno,\n        formatter,\n        is_formatter_dynamic,\n        filter_,\n        colorize,\n        serialize,\n        enqueue,\n        multiprocessing_context,\n        error_interceptor,\n        exception_formatter,\n        id_,\n        levels_ansi_codes\n    ):\n        self._name = name\n        self._sink = sink\n        self._levelno = levelno\n        self._formatter = formatter\n        self._is_formatter_dynamic = is_formatter_dynamic\n        self._filter = filter_\n        self._colorize = colorize\n        self._serialize = serialize\n        self._enqueue = enqueue\n        self._multiprocessing_context = multiprocessing_context\n        self._error_interceptor = error_interceptor\n        self._exception_formatter = exception_formatter\n        self._id = id_\n        self._levels_ansi_codes = levels_ansi_codes  # Warning, reference shared among handlers\n\n        self._decolorized_format = None\n        self._precolorized_formats = {}\n        self._memoize_dynamic_format = None\n\n        self._stopped = False\n        self._lock = create_handler_lock()\n        self._lock_acquired = threading.local()\n        self._queue = None\n        self._queue_lock = None\n        self._confirmation_event = None\n        self._confirmation_lock = None\n        self._owner_process_pid = None\n        self._thread = None\n\n        if self._is_formatter_dynamic:\n            if self._colorize:\n                self._memoize_dynamic_format = memoize(prepare_colored_format)\n            else:\n                self._memoize_dynamic_format = memoize(prepare_stripped_format)\n        else:\n            if self._colorize:\n                for level_name in self._levels_ansi_codes:\n                    self.update_format(level_name)\n            else:\n                self._decolorized_format = self._formatter.strip()\n\n        if self._enqueue:\n            if self._multiprocessing_context is None:\n                self._queue = multiprocessing.SimpleQueue()\n                self._confirmation_event = multiprocessing.Event()\n                self._confirmation_lock = multiprocessing.Lock()\n            else:\n                self._queue = self._multiprocessing_context.SimpleQueue()\n                self._confirmation_event = self._multiprocessing_context.Event()\n                self._confirmation_lock = self._multiprocessing_context.Lock()\n            self._queue_lock = create_handler_lock()\n            self._owner_process_pid = os.getpid()\n            self._thread = Thread(\n                target=self._queued_writer, daemon=True, name=\"loguru-writer-%d\" % self._id\n            )\n            self._thread.start()\n\n    def __repr__(self):\n        return \"(id=%d, level=%d, sink=%s)\" % (self._id, self._levelno, self._name)\n\n    @contextmanager\n    def _protected_lock(self):\n        \"\"\"Acquire the lock, but fail fast if its already acquired by the current thread.\"\"\"\n        if getattr(self._lock_acquired, \"acquired\", False):\n            raise RuntimeError(\n                \"Could not acquire internal lock because it was already in use (deadlock avoided). \"\n                \"This likely happened because the logger was re-used inside a sink, a signal \"\n                \"handler or a '__del__' method. This is not permitted because the logger and its \"\n                \"handlers are not re-entrant.\"\n            )\n        self._lock_acquired.acquired = True\n        try:\n            with self._lock:\n                yield\n        finally:\n            self._lock_acquired.acquired = False\n\n    def emit(self, record, level_id, from_decorator, is_raw, colored_message):\n        try:\n            if self._levelno > record[\"level\"].no:\n                return\n\n            if self._filter is not None:\n                if not self._filter(record):\n                    return\n\n            if self._is_formatter_dynamic:\n                dynamic_format = self._formatter(record)\n\n            formatter_record = record.copy()\n\n            if not record[\"exception\"]:\n                formatter_record[\"exception\"] = \"\"\n            else:\n                type_, value, tb = record[\"exception\"]\n                formatter = self._exception_formatter\n                lines = formatter.format_exception(type_, value, tb, from_decorator=from_decorator)\n                formatter_record[\"exception\"] = \"\".join(lines)\n\n            if colored_message is not None and colored_message.stripped != record[\"message\"]:\n                colored_message = None\n\n            if is_raw:\n                if colored_message is None or not self._colorize:\n                    formatted = record[\"message\"]\n                else:\n                    ansi_level = self._levels_ansi_codes[level_id]\n                    formatted = colored_message.colorize(ansi_level)\n            elif self._is_formatter_dynamic:\n                if not self._colorize:\n                    precomputed_format = self._memoize_dynamic_format(dynamic_format)\n                    formatted = precomputed_format.format_map(formatter_record)\n                elif colored_message is None:\n                    ansi_level = self._levels_ansi_codes[level_id]\n                    _, precomputed_format = self._memoize_dynamic_format(dynamic_format, ansi_level)\n                    formatted = precomputed_format.format_map(formatter_record)\n                else:\n                    ansi_level = self._levels_ansi_codes[level_id]\n                    formatter, precomputed_format = self._memoize_dynamic_format(\n                        dynamic_format, ansi_level\n                    )\n                    coloring_message = formatter.make_coloring_message(\n                        record[\"message\"], ansi_level=ansi_level, colored_message=colored_message\n                    )\n                    formatter_record[\"message\"] = coloring_message\n                    formatted = precomputed_format.format_map(formatter_record)\n\n            else:\n                if not self._colorize:\n                    precomputed_format = self._decolorized_format\n                    formatted = precomputed_format.format_map(formatter_record)\n                elif colored_message is None:\n                    ansi_level = self._levels_ansi_codes[level_id]\n                    precomputed_format = self._precolorized_formats[level_id]\n                    formatted = precomputed_format.format_map(formatter_record)\n                else:\n                    ansi_level = self._levels_ansi_codes[level_id]\n                    precomputed_format = self._precolorized_formats[level_id]\n                    coloring_message = self._formatter.make_coloring_message(\n                        record[\"message\"], ansi_level=ansi_level, colored_message=colored_message\n                    )\n                    formatter_record[\"message\"] = coloring_message\n                    formatted = precomputed_format.format_map(formatter_record)\n\n            if self._serialize:\n                formatted = self._serialize_record(formatted, record)\n\n            str_record = Message(formatted)\n            str_record.record = record\n\n            with self._protected_lock():\n                if self._stopped:\n                    return\n                if self._enqueue:\n                    self._queue.put(str_record)\n                else:\n                    self._sink.write(str_record)\n        except Exception:\n            if not self._error_interceptor.should_catch():\n                raise\n            self._error_interceptor.print(record)\n\n    def stop(self):\n        with self._protected_lock():\n            self._stopped = True\n            if self._enqueue:\n                if self._owner_process_pid != os.getpid():\n                    return\n                self._queue.put(None)\n                self._thread.join()\n                if hasattr(self._queue, \"close\"):\n                    self._queue.close()\n\n            self._sink.stop()\n\n    def complete_queue(self):\n        if not self._enqueue:\n            return\n\n        with self._confirmation_lock:\n            self._queue.put(True)\n            self._confirmation_event.wait()\n            self._confirmation_event.clear()\n\n    def tasks_to_complete(self):\n        if self._enqueue and self._owner_process_pid != os.getpid():\n            return []\n        lock = self._queue_lock if self._enqueue else self._protected_lock()\n        with lock:\n            return self._sink.tasks_to_complete()\n\n    def update_format(self, level_id):\n        if not self._colorize or self._is_formatter_dynamic:\n            return\n        ansi_code = self._levels_ansi_codes[level_id]\n        self._precolorized_formats[level_id] = self._formatter.colorize(ansi_code)\n\n    @property\n    def levelno(self):\n        return self._levelno\n\n    @staticmethod\n    def _serialize_record(text, record):\n        exception = record[\"exception\"]\n\n        if exception is not None:\n            exception = {\n                \"type\": None if exception.type is None else exception.type.__name__,\n                \"value\": exception.value,\n                \"traceback\": bool(exception.traceback),\n            }\n\n        serializable = {\n            \"text\": text,\n            \"record\": {\n                \"elapsed\": {\n                    \"repr\": record[\"elapsed\"],\n                    \"seconds\": record[\"elapsed\"].total_seconds(),\n                },\n                \"exception\": exception,\n                \"extra\": record[\"extra\"],\n                \"file\": {\"name\": record[\"file\"].name, \"path\": record[\"file\"].path},\n                \"function\": record[\"function\"],\n                \"level\": {\n                    \"icon\": record[\"level\"].icon,\n                    \"name\": record[\"level\"].name,\n                    \"no\": record[\"level\"].no,\n                },\n                \"line\": record[\"line\"],\n                \"message\": record[\"message\"],\n                \"module\": record[\"module\"],\n                \"name\": record[\"name\"],\n                \"process\": {\"id\": record[\"process\"].id, \"name\": record[\"process\"].name},\n                \"thread\": {\"id\": record[\"thread\"].id, \"name\": record[\"thread\"].name},\n                \"time\": {\"repr\": record[\"time\"], \"timestamp\": record[\"time\"].timestamp()},\n            },\n        }\n\n        return json.dumps(serializable, default=str, ensure_ascii=False) + \"\\n\"\n\n    def _queued_writer(self):\n        message = None\n        queue = self._queue\n\n        # We need to use a lock to protect sink during fork.\n        # Particularly, writing to stderr may lead to deadlock in child process.\n        lock = self._queue_lock\n\n        while True:\n            try:\n                message = queue.get()\n            except Exception:\n                with lock:\n                    self._error_interceptor.print(None)\n                continue\n\n            if message is None:\n                break\n\n            if message is True:\n                self._confirmation_event.set()\n                continue\n\n            with lock:\n                try:\n                    self._sink.write(message)\n                except Exception:\n                    self._error_interceptor.print(message.record)\n\n    def __getstate__(self):\n        state = self.__dict__.copy()\n        state[\"_lock\"] = None\n        state[\"_lock_acquired\"] = None\n        state[\"_memoize_dynamic_format\"] = None\n        if self._enqueue:\n            state[\"_sink\"] = None\n            state[\"_thread\"] = None\n            state[\"_queue_lock\"] = None\n        return state\n\n    def __setstate__(self, state):\n        self.__dict__.update(state)\n        self._lock = create_handler_lock()\n        self._lock_acquired = threading.local()\n        if self._enqueue:\n            self._queue_lock = create_handler_lock()\n        if self._is_formatter_dynamic:\n            if self._colorize:\n                self._memoize_dynamic_format = memoize(prepare_colored_format)\n            else:\n                self._memoize_dynamic_format = memoize(prepare_stripped_format)\n"
  },
  {
    "path": "loguru/_locks_machinery.py",
    "content": "import os\nimport threading\nimport weakref\n\nif not hasattr(os, \"register_at_fork\"):\n\n    def create_logger_lock():\n        return threading.Lock()\n\n    def create_handler_lock():\n        return threading.Lock()\n\nelse:\n    # While forking, we need to sanitize all locks to make sure the child process doesn't run into\n    # a deadlock (if a lock already acquired is inherited) and to protect sink from corrupted state.\n    # It's very important to acquire logger locks before handlers one to prevent possible deadlock\n    # while 'remove()' is called for example.\n\n    logger_locks = weakref.WeakSet()\n    handler_locks = weakref.WeakSet()\n\n    def acquire_locks():\n        for lock in logger_locks:\n            lock.acquire()\n\n        for lock in handler_locks:\n            lock.acquire()\n\n    def release_locks():\n        for lock in logger_locks:\n            lock.release()\n\n        for lock in handler_locks:\n            lock.release()\n\n    os.register_at_fork(\n        before=acquire_locks,\n        after_in_parent=release_locks,\n        after_in_child=release_locks,\n    )\n\n    def create_logger_lock():\n        lock = threading.Lock()\n        logger_locks.add(lock)\n        return lock\n\n    def create_handler_lock():\n        lock = threading.Lock()\n        handler_locks.add(lock)\n        return lock\n"
  },
  {
    "path": "loguru/_logger.py",
    "content": "\"\"\"Core logging functionalities of the `Loguru` library.\n\n.. References and links rendered by Sphinx are kept here as \"module documentation\" so that they can\n   be used in the ``Logger`` docstrings but do not pollute ``help(logger)`` output.\n\n.. |Logger| replace:: :class:`~Logger`\n.. |add| replace:: :meth:`~Logger.add()`\n.. |remove| replace:: :meth:`~Logger.remove()`\n.. |complete| replace:: :meth:`~Logger.complete()`\n.. |catch| replace:: :meth:`~Logger.catch()`\n.. |bind| replace:: :meth:`~Logger.bind()`\n.. |contextualize| replace:: :meth:`~Logger.contextualize()`\n.. |patch| replace:: :meth:`~Logger.patch()`\n.. |opt| replace:: :meth:`~Logger.opt()`\n.. |log| replace:: :meth:`~Logger.log()`\n.. |error| replace:: :meth:`~Logger.error()`\n.. |level| replace:: :meth:`~Logger.level()`\n.. |enable| replace:: :meth:`~Logger.enable()`\n.. |disable| replace:: :meth:`~Logger.disable()`\n\n.. |Any| replace:: :obj:`~typing.Any`\n.. |str| replace:: :class:`str`\n.. |int| replace:: :class:`int`\n.. |bool| replace:: :class:`bool`\n.. |tuple| replace:: :class:`tuple`\n.. |namedtuple| replace:: :func:`namedtuple<collections.namedtuple>`\n.. |list| replace:: :class:`list`\n.. |dict| replace:: :class:`dict`\n.. |str.format| replace:: :meth:`str.format()`\n.. |Path| replace:: :class:`pathlib.Path`\n.. |match.groupdict| replace:: :meth:`re.Match.groupdict()`\n.. |Handler| replace:: :class:`logging.Handler`\n.. |sys.stderr| replace:: :data:`sys.stderr`\n.. |sys.exc_info| replace:: :func:`sys.exc_info()`\n.. |time| replace:: :class:`datetime.time`\n.. |datetime| replace:: :class:`datetime.datetime`\n.. |timedelta| replace:: :class:`datetime.timedelta`\n.. |open| replace:: :func:`open()`\n.. |logging| replace:: :mod:`logging`\n.. |signal| replace:: :mod:`signal`\n.. |contextvars| replace:: :mod:`contextvars`\n.. |multiprocessing| replace:: :mod:`multiprocessing`\n.. |Thread.run| replace:: :meth:`Thread.run()<threading.Thread.run()>`\n.. |Exception| replace:: :class:`Exception`\n.. |AbstractEventLoop| replace:: :class:`AbstractEventLoop<asyncio.AbstractEventLoop>`\n.. |asyncio.get_running_loop| replace:: :func:`asyncio.get_running_loop()`\n.. |asyncio.run| replace:: :func:`asyncio.run()`\n.. |loop.run_until_complete| replace::\n    :meth:`loop.run_until_complete()<asyncio.loop.run_until_complete()>`\n.. |loop.create_task| replace:: :meth:`loop.create_task()<asyncio.loop.create_task()>`\n\n.. |logger.trace| replace:: :meth:`logger.trace()<Logger.trace()>`\n.. |logger.debug| replace:: :meth:`logger.debug()<Logger.debug()>`\n.. |logger.info| replace:: :meth:`logger.info()<Logger.info()>`\n.. |logger.success| replace:: :meth:`logger.success()<Logger.success()>`\n.. |logger.warning| replace:: :meth:`logger.warning()<Logger.warning()>`\n.. |logger.error| replace:: :meth:`logger.error()<Logger.error()>`\n.. |logger.critical| replace:: :meth:`logger.critical()<Logger.critical()>`\n\n.. |file-like object| replace:: ``file-like object``\n.. _file-like object: https://docs.python.org/3/glossary.html#term-file-object\n.. |callable| replace:: ``callable``\n.. _callable: https://docs.python.org/3/library/functions.html#callable\n.. |coroutine function| replace:: ``coroutine function``\n.. _coroutine function: https://docs.python.org/3/glossary.html#term-coroutine-function\n.. |re.Pattern| replace:: ``re.Pattern``\n.. _re.Pattern: https://docs.python.org/3/library/re.html#re-objects\n.. |multiprocessing.Context| replace:: ``multiprocessing.Context``\n.. _multiprocessing.Context:\n   https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods\n\n.. |FAQ anonymous levels| replace:: :ref:`FAQ anonymous levels <anonymous_levels>`\n\n.. |better_exceptions| replace:: ``better_exceptions``\n.. _better_exceptions: https://github.com/Qix-/better-exceptions\n\n.. |loguru-config| replace:: ``loguru-config``\n.. _loguru-config: https://github.com/erezinman/loguru-config\n\n.. _Pendulum: https://pendulum.eustace.io/docs/#tokens\n\n.. _@Qix-: https://github.com/Qix-\n.. _@erezinman: https://github.com/erezinman\n.. _@sdispater: https://github.com/sdispater\n\n.. _formatting directives: https://docs.python.org/3/library/string.html#format-string-syntax\n.. _reentrant: https://en.wikipedia.org/wiki/Reentrancy_(computing)\n\n.. _ANSI codes: https://en.wikipedia.org/wiki/ANSI_escape_code\n\n.. |NO_COLOR| replace:: ``NO_COLOR``\n.. _NO_COLOR: https://no-color.org/\n\n.. |FORCE_COLOR| replace:: ``FORCE_COLOR``\n.. _FORCE_COLOR: https://force-color.org/\n\n\"\"\"\n\nimport builtins\nimport contextlib\nimport functools\nimport logging\nimport re\nimport sys\nimport threading\nimport warnings\nfrom collections import namedtuple\nfrom inspect import isclass, iscoroutinefunction, isgeneratorfunction\nfrom multiprocessing import current_process, get_context\nfrom multiprocessing.context import BaseContext\nfrom os.path import basename, splitext\nfrom threading import current_thread\n\nfrom . import _asyncio_loop, _colorama, _defaults, _filters\nfrom ._better_exceptions import ExceptionFormatter\nfrom ._colorizer import Colorizer, try_formatting\nfrom ._contextvars import ContextVar\nfrom ._datetime import aware_now\nfrom ._error_interceptor import ErrorInterceptor\nfrom ._file_sink import FileSink\nfrom ._get_frame import get_frame\nfrom ._handler import Handler\nfrom ._locks_machinery import create_logger_lock\nfrom ._recattrs import RecordException, RecordFile, RecordLevel, RecordProcess, RecordThread\nfrom ._simple_sinks import AsyncSink, CallableSink, StandardSink, StreamSink\n\nif sys.version_info >= (3, 6):\n    from collections.abc import AsyncGenerator\n    from inspect import isasyncgenfunction\n    from os import PathLike\n\nelse:\n    from pathlib import PurePath as PathLike\n\n    def isasyncgenfunction(func):\n        return False\n\n\nLevel = namedtuple(\"Level\", [\"name\", \"no\", \"color\", \"icon\"])  # noqa: PYI024\n\nstart_time = aware_now()\n\ncontext = ContextVar(\"loguru_context\", default={})\n\n\nclass Core:\n    def __init__(self):\n        levels = [\n            Level(\n                \"TRACE\",\n                _defaults.LOGURU_TRACE_NO,\n                _defaults.LOGURU_TRACE_COLOR,\n                _defaults.LOGURU_TRACE_ICON,\n            ),\n            Level(\n                \"DEBUG\",\n                _defaults.LOGURU_DEBUG_NO,\n                _defaults.LOGURU_DEBUG_COLOR,\n                _defaults.LOGURU_DEBUG_ICON,\n            ),\n            Level(\n                \"INFO\",\n                _defaults.LOGURU_INFO_NO,\n                _defaults.LOGURU_INFO_COLOR,\n                _defaults.LOGURU_INFO_ICON,\n            ),\n            Level(\n                \"SUCCESS\",\n                _defaults.LOGURU_SUCCESS_NO,\n                _defaults.LOGURU_SUCCESS_COLOR,\n                _defaults.LOGURU_SUCCESS_ICON,\n            ),\n            Level(\n                \"WARNING\",\n                _defaults.LOGURU_WARNING_NO,\n                _defaults.LOGURU_WARNING_COLOR,\n                _defaults.LOGURU_WARNING_ICON,\n            ),\n            Level(\n                \"ERROR\",\n                _defaults.LOGURU_ERROR_NO,\n                _defaults.LOGURU_ERROR_COLOR,\n                _defaults.LOGURU_ERROR_ICON,\n            ),\n            Level(\n                \"CRITICAL\",\n                _defaults.LOGURU_CRITICAL_NO,\n                _defaults.LOGURU_CRITICAL_COLOR,\n                _defaults.LOGURU_CRITICAL_ICON,\n            ),\n        ]\n        self.levels = {level.name: level for level in levels}\n        self.levels_ansi_codes = {\n            **{name: Colorizer.ansify(level.color) for name, level in self.levels.items()},\n            None: \"\",\n        }\n\n        # Cache used internally to quickly access level attributes based on their name or severity.\n        # It can also contain integers as keys, it serves to avoid calling \"isinstance()\" repeatedly\n        # when \"logger.log()\" is used.\n        self.levels_lookup = {\n            name: (name, name, level.no, level.icon) for name, level in self.levels.items()\n        }\n\n        self.handlers_count = 0\n        self.handlers = {}\n\n        self.extra = {}\n        self.patcher = None\n\n        self.min_level = float(\"inf\")\n        self.enabled = {}\n        self.activation_list = []\n        self.activation_none = True\n\n        self.thread_locals = threading.local()\n        self.lock = create_logger_lock()\n\n    def __getstate__(self):\n        state = self.__dict__.copy()\n        state[\"thread_locals\"] = None\n        state[\"lock\"] = None\n        return state\n\n    def __setstate__(self, state):\n        self.__dict__.update(state)\n        self.thread_locals = threading.local()\n        self.lock = create_logger_lock()\n\n\nclass Logger:\n    \"\"\"An object to dispatch logging messages to configured handlers.\n\n    The |Logger| is the core object of ``loguru``, every logging configuration and usage pass\n    through a call to one of its methods. There is only one logger, so there is no need to retrieve\n    one before usage.\n\n    Once the ``logger`` is imported, it can be used to write messages about events happening in your\n    code. By reading the output logs of your application, you gain a better understanding of the\n    flow of your program and you more easily track and debug unexpected behaviors.\n\n    Handlers to which the logger sends log messages are added using the |add| method. Note that you\n    can use the |Logger| right after import as it comes pre-configured (logs are emitted to\n    |sys.stderr| by default). Messages can be logged with different severity levels and they can be\n    formatted using curly braces (it uses |str.format| under the hood).\n\n    When a message is logged, a \"record\" is associated with it. This record is a dict which contains\n    information about the logging context: time, function, file, line, thread, level... It also\n    contains the ``__name__`` of the module, this is why you don't need named loggers.\n\n    You should not instantiate a |Logger| by yourself, use ``from loguru import logger`` instead.\n    \"\"\"\n\n    def __init__(self, core, exception, depth, record, lazy, colors, raw, capture, patchers, extra):\n        self._core = core\n        self._options = (exception, depth, record, lazy, colors, raw, capture, patchers, extra)\n\n    def __repr__(self):\n        return \"<loguru.logger handlers=%r>\" % list(self._core.handlers.values())\n\n    def add(\n        self,\n        sink,\n        *,\n        level=_defaults.LOGURU_LEVEL,\n        format=_defaults.LOGURU_FORMAT,\n        filter=_defaults.LOGURU_FILTER,\n        colorize=_defaults.LOGURU_COLORIZE,\n        serialize=_defaults.LOGURU_SERIALIZE,\n        backtrace=_defaults.LOGURU_BACKTRACE,\n        diagnose=_defaults.LOGURU_DIAGNOSE,\n        enqueue=_defaults.LOGURU_ENQUEUE,\n        context=_defaults.LOGURU_CONTEXT,\n        catch=_defaults.LOGURU_CATCH,\n        **kwargs\n    ):\n        r\"\"\"Add a handler sending log messages to a sink adequately configured.\n\n        Parameters\n        ----------\n        sink : |file-like object|_, |str|, |Path|, |callable|_, |coroutine function|_ or |Handler|\n            An object in charge of receiving formatted logging messages and propagating them to an\n            appropriate endpoint.\n        level : |int| or |str|, optional\n            The minimum severity level from which logged messages should be sent to the sink.\n        format : |str| or |callable|_, optional\n            The template used to format logged messages before being sent to the sink. Such template\n            can be dynamically generated by a function taking the log record as parameter.\n        filter : |callable|_, |str| or |dict|, optional\n            A directive optionally used to decide for each logged message whether it should be sent\n            to the sink or not.\n        colorize : |bool|, optional\n            Whether the color markups contained in the formatted message should be converted to ansi\n            codes for terminal coloration, or stripped otherwise. If ``None``, the choice is\n            automatically made based on the sink being a tty or not.\n        serialize : |bool|, optional\n            Whether the logged message and its records should be first converted to a JSON string\n            before being sent to the sink.\n        backtrace : |bool|, optional\n            Whether the exception trace formatted should be extended upward, beyond the catching\n            point, to show the full stacktrace which generated the error.\n        diagnose : |bool|, optional\n            Whether the exception trace should display the variables values to ease the debugging.\n            This should be set to ``False`` in production to avoid leaking sensitive data.\n        enqueue : |bool|, optional\n            Whether the messages to be logged should first pass through a multiprocessing-safe queue\n            before reaching the sink. This is useful while logging to a file through multiple\n            processes. This also has the advantage of making logging calls non-blocking.\n        context : |multiprocessing.Context| or |str|, optional\n            A context object or name that will be used for all tasks involving internally the\n            |multiprocessing| module, in particular when ``enqueue=True``. If ``None``, the default\n            context is used.\n        catch : |bool|, optional\n            Whether errors occurring while sink handles logs messages should be automatically\n            caught. If ``True``, an exception message is displayed on |sys.stderr| but the exception\n            is not propagated to the caller, preventing your app to crash.\n        **kwargs\n            Additional parameters that are only valid to configure a coroutine or file sink (see\n            below).\n\n\n        If and only if the sink is a coroutine function, the following parameter applies:\n\n        Parameters\n        ----------\n        loop : |AbstractEventLoop|, optional\n            The event loop in which the asynchronous logging task will be scheduled and executed. If\n            ``None``, the loop used is the one returned by |asyncio.get_running_loop| at the time of\n            the logging call (task is discarded if there is no loop currently running).\n\n\n        If and only if the sink is a file path, the following parameters apply:\n\n        Parameters\n        ----------\n        rotation : |str|, |int|, |time|, |timedelta|, |callable|_, or |list| of any of those\n            types, optional\n            Condition(s) indicating whenever the current logged file should be closed and a\n            new one started. If a list of conditions are provided, the current file is rotated\n            if any condition is true.\n        retention : |str|, |int|, |timedelta| or |callable|_, optional\n            A directive filtering old files that should be removed during rotation or end of\n            program.\n        compression : |str| or |callable|_, optional\n            A compression or archive format to which log files should be converted at closure.\n        delay : |bool|, optional\n            Whether the file should be created as soon as the sink is configured, or delayed until\n            first logged message. It defaults to ``False``.\n        watch : |bool|, optional\n            Whether or not the file should be watched and re-opened when deleted or changed (based\n            on its device and inode properties) by an external program. It defaults to ``False``.\n        mode : |str|, optional\n            The opening mode as for built-in |open| function. It defaults to ``\"a\"`` (open the\n            file in appending mode).\n        buffering : |int|, optional\n            The buffering policy as for built-in |open| function. It defaults to ``1`` (line\n            buffered file).\n        encoding : |str|, optional\n            The file encoding as for built-in |open| function. It defaults to ``\"utf8\"``.\n        **kwargs\n            Others parameters are passed to the built-in |open| function.\n\n        Returns\n        -------\n        :class:`int`\n            An identifier associated with the added sink and which should be used to\n            |remove| it.\n\n        Raises\n        ------\n        ValueError\n            If any of the arguments passed to configure the sink is invalid.\n\n        Notes\n        -----\n        Extended summary follows.\n\n        .. _sink:\n\n        .. rubric:: The sink parameter\n\n        The ``sink`` handles incoming log messages and proceed to their writing somewhere and\n        somehow. A sink can take many forms:\n\n        - A |file-like object|_ like ``sys.stderr`` or ``open(\"file.log\", \"w\")``. Anything with\n          a ``.write()`` method is considered as a file-like object. Custom handlers may also\n          implement ``flush()`` (called after each logged message), ``stop()`` (called at sink\n          termination) and ``complete()`` (awaited by the eponymous method).\n        - A file path as |str| or |Path|. It can be parametrized with some additional parameters,\n          see below.\n        - A |callable|_ (such as a simple function) like ``lambda msg: print(msg)``. This\n          allows for logging procedure entirely defined by user preferences and needs.\n        - A asynchronous |coroutine function|_ defined with the ``async def`` statement. The\n          coroutine object returned by such function will be added to the event loop using\n          |loop.create_task|. The tasks should be awaited before ending the loop by using\n          |complete|.\n        - A built-in |Handler| like ``logging.StreamHandler``. In such a case, the `Loguru` records\n          are automatically converted to the structure expected by the |logging| module.\n\n        Note that the logging functions are not `reentrant`_. This means you should avoid using\n        the ``logger`` inside any of your sinks or from within |signal| handlers. Otherwise, you\n        may face deadlock if the module's sink was not explicitly disabled.\n\n        .. _message:\n\n        .. rubric:: The logged message\n\n        The logged message passed to all added sinks is nothing more than a string of the\n        formatted log, to which a special attribute is associated: the ``.record`` which is a dict\n        containing all contextual information possibly needed (see below).\n\n        Logged messages are formatted according to the ``format`` of the added sink. This format\n        is usually a string containing braces fields to display attributes from the record dict.\n\n        If fine-grained control is needed, the ``format`` can also be a function which takes the\n        record as parameter and must return the unformatted template string. It is critical that\n        this string has not been pre-formatted, as Loguru will handle the actual formatting later.\n        Returning an already formatted log message would break internal mechanisms and lead to\n        runtime errors. Note that when using a function, you are also responsible for appending the\n        line ending and the exception field, whereas ``\"\\n{exception}\"`` is automatically appended\n        for convenience when the ``format`` is a string.\n\n        The ``filter`` attribute can be used to control which messages are effectively passed to the\n        sink and which one are ignored. A function can be used, accepting the record as an\n        argument, and returning ``True`` if the message should be logged, ``False`` otherwise. If\n        a string is used, only the records with the same ``name`` and its children will be allowed.\n        One can also pass a ``dict`` mapping module names to minimum required level. In such case,\n        each log record will search for it's closest parent in the ``dict`` and use the associated\n        level as the filter. The ``dict`` values can be ``int`` severity, ``str`` level name or\n        ``True`` and ``False`` to respectively authorize and discard all module logs\n        unconditionally. In order to set a default level, the ``\"\"`` module name should be used as\n        it is the parent of all modules (it does not suppress global ``level`` threshold, though).\n\n        Note that while calling a logging method, the keyword arguments (if any) are automatically\n        added to the ``extra`` dict for convenient contextualization (in addition to being used for\n        formatting).\n\n        .. _levels:\n\n        .. rubric:: The severity levels\n\n        Each logged message is associated with a severity level. These levels make it possible to\n        prioritize messages and to choose the verbosity of the logs according to usages. For\n        example, it allows to display some debugging information to a developer, while hiding it to\n        the end user running the application.\n\n        The ``level`` attribute of every added sink controls the minimum threshold from which log\n        messages are allowed to be emitted. While using the ``logger``, you are in charge of\n        configuring the appropriate granularity of your logs. It is possible to add even more custom\n        levels by using the |level| method.\n\n        Here are the standard levels with their default severity value, each one is associated with\n        a logging method of the same name:\n\n        +----------------------+------------------------+------------------------+\n        | Level name           | Severity value         | Logger method          |\n        +======================+========================+========================+\n        | ``TRACE``            | 5                      | |logger.trace|         |\n        +----------------------+------------------------+------------------------+\n        | ``DEBUG``            | 10                     | |logger.debug|         |\n        +----------------------+------------------------+------------------------+\n        | ``INFO``             | 20                     | |logger.info|          |\n        +----------------------+------------------------+------------------------+\n        | ``SUCCESS``          | 25                     | |logger.success|       |\n        +----------------------+------------------------+------------------------+\n        | ``WARNING``          | 30                     | |logger.warning|       |\n        +----------------------+------------------------+------------------------+\n        | ``ERROR``            | 40                     | |logger.error|         |\n        +----------------------+------------------------+------------------------+\n        | ``CRITICAL``         | 50                     | |logger.critical|      |\n        +----------------------+------------------------+------------------------+\n\n        .. _record:\n\n        .. rubric:: The record dict\n\n        The record is just a Python dict, accessible from sinks by ``message.record``. It contains\n        all contextual information of the logging call (time, function, file, line, level, etc.).\n\n        Each of the record keys can be used in the handler's ``format`` so the corresponding value\n        is properly displayed in the logged message (e.g. ``\"{level}\"`` will return ``\"INFO\"``).\n        Some records' values are objects with two or more attributes. These can be formatted with\n        ``\"{key.attr}\"`` (``\"{key}\"`` would display one by default).\n\n        Note that you can use any `formatting directives`_ available in Python's ``str.format()``\n        method (e.g. ``\"{key: >3}\"`` will right-align and pad to a width of 3 characters). This is\n        particularly useful for time formatting (see below).\n\n        +------------+---------------------------------+----------------------------+\n        | Key        | Description                     | Attributes                 |\n        +============+=================================+============================+\n        | elapsed    | The time elapsed since the      | See |timedelta|            |\n        |            | start of the program            |                            |\n        +------------+---------------------------------+----------------------------+\n        | exception  | The formatted exception if any, | ``type``, ``value``,       |\n        |            | ``None`` otherwise              | ``traceback``              |\n        +------------+---------------------------------+----------------------------+\n        | extra      | The dict of attributes          | None                       |\n        |            | bound by the user (see |bind|)  |                            |\n        +------------+---------------------------------+----------------------------+\n        | file       | The file where the logging call | ``name`` (default),        |\n        |            | was made                        | ``path``                   |\n        +------------+---------------------------------+----------------------------+\n        | function   | The function from which the     | None                       |\n        |            | logging call was made           |                            |\n        +------------+---------------------------------+----------------------------+\n        | level      | The severity used to log the    | ``name`` (default),        |\n        |            | message                         | ``no``, ``icon``           |\n        +------------+---------------------------------+----------------------------+\n        | line       | The line number in the source   | None                       |\n        |            | code                            |                            |\n        +------------+---------------------------------+----------------------------+\n        | message    | The logged message (not yet     | None                       |\n        |            | formatted)                      |                            |\n        +------------+---------------------------------+----------------------------+\n        | module     | The module where the logging    | None                       |\n        |            | call was made                   |                            |\n        +------------+---------------------------------+----------------------------+\n        | name       | The ``__name__`` where the      | None                       |\n        |            | logging call was made           |                            |\n        +------------+---------------------------------+----------------------------+\n        | process    | The process in which the        | ``name``, ``id`` (default) |\n        |            | logging call was made           |                            |\n        +------------+---------------------------------+----------------------------+\n        | thread     | The thread in which the         | ``name``, ``id`` (default) |\n        |            | logging call was made           |                            |\n        +------------+---------------------------------+----------------------------+\n        | time       | The aware local time when the   | See |datetime|             |\n        |            | logging call was made           |                            |\n        +------------+---------------------------------+----------------------------+\n\n        .. _time:\n\n        .. rubric:: The time formatting\n\n        To use your favorite time representation, you can set it directly in the time formatter\n        specifier of your handler format, like for example ``format=\"{time:HH:mm:ss} {message}\"``.\n        Note that this datetime represents your local time, and it is also made timezone-aware,\n        so you can display the UTC offset to avoid ambiguities.\n\n        The time field can be formatted using more human-friendly tokens. These constitute a subset\n        of the one used by the `Pendulum`_ library of `@sdispater`_. To escape a token, just add\n        square brackets around it, for example ``\"[YY]\"`` would display literally ``\"YY\"``.\n\n        If you prefer to display UTC rather than local time, you can add ``\"!UTC\"`` at the very end\n        of the time format, like ``{time:HH:mm:ss!UTC}``. Doing so will convert the ``datetime``\n        to UTC before formatting.\n\n        If no time formatter specifier is used, like for example if ``format=\"{time} {message}\"``,\n        the default one will use ISO 8601.\n\n        +------------------------+---------+----------------------------------------+\n        |                        | Token   | Output                                 |\n        +========================+=========+========================================+\n        | Year                   | YYYY    | 2000, 2001, 2002 ... 2012, 2013        |\n        |                        +---------+----------------------------------------+\n        |                        | YY      | 00, 01, 02 ... 12, 13                  |\n        +------------------------+---------+----------------------------------------+\n        | Quarter                | Q       | 1 2 3 4                                |\n        +------------------------+---------+----------------------------------------+\n        | Month                  | MMMM    | January, February, March ...           |\n        |                        +---------+----------------------------------------+\n        |                        | MMM     | Jan, Feb, Mar ...                      |\n        |                        +---------+----------------------------------------+\n        |                        | MM      | 01, 02, 03 ... 11, 12                  |\n        |                        +---------+----------------------------------------+\n        |                        | M       | 1, 2, 3 ... 11, 12                     |\n        +------------------------+---------+----------------------------------------+\n        | Day of Year            | DDDD    | 001, 002, 003 ... 364, 365             |\n        |                        +---------+----------------------------------------+\n        |                        | DDD     | 1, 2, 3 ... 364, 365                   |\n        +------------------------+---------+----------------------------------------+\n        | Day of Month           | DD      | 01, 02, 03 ... 30, 31                  |\n        |                        +---------+----------------------------------------+\n        |                        | D       | 1, 2, 3 ... 30, 31                     |\n        +------------------------+---------+----------------------------------------+\n        | Day of Week            | dddd    | Monday, Tuesday, Wednesday ...         |\n        |                        +---------+----------------------------------------+\n        |                        | ddd     | Mon, Tue, Wed ...                      |\n        |                        +---------+----------------------------------------+\n        |                        | d       | 0, 1, 2 ... 6                          |\n        +------------------------+---------+----------------------------------------+\n        | Days of ISO Week       | E       | 1, 2, 3 ... 7                          |\n        +------------------------+---------+----------------------------------------+\n        | Hour                   | HH      | 00, 01, 02 ... 23, 24                  |\n        |                        +---------+----------------------------------------+\n        |                        | H       | 0, 1, 2 ... 23, 24                     |\n        |                        +---------+----------------------------------------+\n        |                        | hh      | 01, 02, 03 ... 11, 12                  |\n        |                        +---------+----------------------------------------+\n        |                        | h       | 1, 2, 3 ... 11, 12                     |\n        +------------------------+---------+----------------------------------------+\n        | Minute                 | mm      | 00, 01, 02 ... 58, 59                  |\n        |                        +---------+----------------------------------------+\n        |                        | m       | 0, 1, 2 ... 58, 59                     |\n        +------------------------+---------+----------------------------------------+\n        | Second                 | ss      | 00, 01, 02 ... 58, 59                  |\n        |                        +---------+----------------------------------------+\n        |                        | s       | 0, 1, 2 ... 58, 59                     |\n        +------------------------+---------+----------------------------------------+\n        | Fractional Second      | S       | 0 1 ... 8 9                            |\n        |                        +---------+----------------------------------------+\n        |                        | SS      | 00, 01, 02 ... 98, 99                  |\n        |                        +---------+----------------------------------------+\n        |                        | SSS     | 000 001 ... 998 999                    |\n        |                        +---------+----------------------------------------+\n        |                        | SSSS... | 000[0..] 001[0..] ... 998[0..] 999[0..]|\n        |                        +---------+----------------------------------------+\n        |                        | SSSSSS  | 000000 000001 ... 999998 999999        |\n        +------------------------+---------+----------------------------------------+\n        | AM / PM                | A       | AM, PM                                 |\n        +------------------------+---------+----------------------------------------+\n        | Timezone               | Z       | -07:00, -06:00 ... +06:00, +07:00      |\n        |                        +---------+----------------------------------------+\n        |                        | ZZ      | -0700, -0600 ... +0600, +0700          |\n        |                        +---------+----------------------------------------+\n        |                        | zz      | EST CST ... MST PST                    |\n        +------------------------+---------+----------------------------------------+\n        | Seconds timestamp      | X       | 1381685817                             |\n        +------------------------+---------+----------------------------------------+\n        | Microseconds timestamp | x       | 1234567890123                          |\n        +------------------------+---------+----------------------------------------+\n\n        .. _file:\n\n        .. rubric:: The file sinks\n\n        If the sink is a |str| or a |Path|, the corresponding file will be opened for writing logs.\n        The path can also contain a special ``\"{time}\"`` field that will be formatted with the\n        current date at file creation. The file is closed at sink stop, i.e. when the application\n        ends or the handler is removed.\n\n        The ``rotation`` check is made before logging each message. If there is already an existing\n        file with the same name that the file to be created, then the existing file is renamed by\n        appending the date to its basename to prevent file overwriting. This parameter accepts:\n\n        - an |int| which corresponds to the maximum file size in bytes before that the current\n          logged file is closed and a new one started over.\n        - a |timedelta| which indicates the frequency of each new rotation.\n        - a |time| which specifies the hour when the daily rotation should occur.\n        - a |str| for human-friendly parametrization of one of the previously enumerated types.\n          Examples: ``\"100 MB\"``, ``\"0.5 GB\"``, ``\"1 month 2 weeks\"``, ``\"4 days\"``, ``\"10h\"``,\n          ``\"monthly\"``, ``\"18:00\"``, ``\"sunday\"``, ``\"w0\"``, ``\"monday at 12:00\"``, ...\n        - a |callable|_ which will be invoked before logging. It should accept two arguments: the\n          logged message and the file object, and it should return ``True`` if the rotation should\n          happen now, ``False`` otherwise.\n\n        The ``retention`` occurs at rotation or at sink stop if rotation is ``None``. Files\n        resulting from previous sessions or rotations are automatically collected from disk. A file\n        is selected if it matches the pattern ``\"root(.*).ext(.*)\"``, where ``root`` and ``ext`` are\n        derived from ``os.path.splitext()`` applied to the configured sink path (possible time\n        fields are beforehand replaced with ``.*``). Afterwards, the list is processed to determine\n        files to be retained. This parameter accepts:\n\n        - an |int| which indicates the number of log files to keep, while older files are deleted.\n        - a |timedelta| which specifies the maximum age of files to keep.\n        - a |str| for human-friendly parametrization of the maximum age of files to keep.\n          Examples: ``\"1 week, 3 days\"``, ``\"2 months\"``, ...\n        - a |callable|_ which will be invoked before the retention process. It should accept the\n          list of log files as argument and process to whatever it wants (moving files, removing\n          them, etc.).\n\n        The ``compression`` happens at rotation or at sink stop if rotation is ``None``. This\n        parameter accepts:\n\n        - a |str| which corresponds to the compressed or archived file extension. This can be one\n          of: ``\"gz\"``, ``\"bz2\"``, ``\"xz\"``, ``\"lzma\"``, ``\"tar\"``, ``\"tar.gz\"``, ``\"tar.bz2\"``,\n          ``\"tar.xz\"``, ``\"zip\"``.\n        - a |callable|_ which will be invoked after closing (and possibly renaming) the currently\n          logged file. It should accept the path of the log file as argument and process to whatever\n          it wants (custom compression, network sending, renaming it, removing it, etc.).\n\n        Either way, if you use a custom function designed according to your preferences, you must be\n        very careful not to use the ``logger`` within your function. Otherwise, there is a risk that\n        your program hang because of a deadlock.\n\n        .. _color:\n\n        .. rubric:: The color markups\n\n        When the sink supports it, the logs can be colored by using markups in the format string.\n        By default (when ``colorize`` is not specified), these are automatically converted or\n        removed depending on the sink's support for `ANSI codes`_. Loguru also honors the\n        |NO_COLOR|_ and |FORCE_COLOR|_ environment variables (the former taking precedence over the\n        latter).\n\n        To add colors, you just have to enclose your format string with the appropriate tags\n        (e.g. ``<red>some message</red>``). For convenience, you can use ``</>`` to close the last\n        opening tag without repeating its name (e.g. ``<red>another message</>``). The special tag\n        ``<level>`` (abbreviated with ``<lvl>``) is transformed according to the configured color\n        of the logged message level.\n\n        Tags which are not recognized will raise an exception during parsing, to inform you about\n        possible misuse. If you wish to display a markup tag literally, you can escape it by\n        prepending a ``\\`` like for example ``\\<blue>``. To prevent the escaping to occur, you can\n        simply double the ``\\`` (e.g. ``\\\\<blue>`` will print a literal ``\\`` before colored text).\n        If, for some reason, you need to escape a string programmatically, note that the regex used\n        internally to parse markup tags is ``r\"(\\\\*)(</?(?:[fb]g\\s)?[^<>\\s]*>)\"``.\n\n        Note that when logging a message with ``opt(colors=True)``, color tags present in the\n        formatting arguments (``args`` and ``kwargs``) are completely ignored. This is important if\n        you need to log strings containing markups that might interfere with the color tags (in this\n        case, do not use f-string).\n\n        Here are the available tags (note that compatibility may vary depending on terminal):\n\n        +------------------------------------+--------------------------------------+\n        | Color (abbr)                       | Styles (abbr)                        |\n        +====================================+======================================+\n        | Black (k)                          | Bold (b)                             |\n        +------------------------------------+--------------------------------------+\n        | Blue (e)                           | Dim (d)                              |\n        +------------------------------------+--------------------------------------+\n        | Cyan (c)                           | Normal (n)                           |\n        +------------------------------------+--------------------------------------+\n        | Green (g)                          | Italic (i)                           |\n        +------------------------------------+--------------------------------------+\n        | Magenta (m)                        | Underline (u)                        |\n        +------------------------------------+--------------------------------------+\n        | Red (r)                            | Strike (s)                           |\n        +------------------------------------+--------------------------------------+\n        | White (w)                          | Reverse (v)                          |\n        +------------------------------------+--------------------------------------+\n        | Yellow (y)                         | Blink (l)                            |\n        +------------------------------------+--------------------------------------+\n        |                                    | Hide (h)                             |\n        +------------------------------------+--------------------------------------+\n\n        Usage:\n\n        +-----------------+-------------------------------------------------------------------+\n        | Description     | Examples                                                          |\n        |                 +---------------------------------+---------------------------------+\n        |                 | Foreground                      | Background                      |\n        +=================+=================================+=================================+\n        | Basic colors    | ``<red>``, ``<r>``              | ``<GREEN>``, ``<G>``            |\n        +-----------------+---------------------------------+---------------------------------+\n        | Light colors    | ``<light-blue>``, ``<le>``      | ``<LIGHT-CYAN>``, ``<LC>``      |\n        +-----------------+---------------------------------+---------------------------------+\n        | 8-bit colors    | ``<fg 86>``, ``<fg 255>``       | ``<bg 42>``, ``<bg 9>``         |\n        +-----------------+---------------------------------+---------------------------------+\n        | Hex colors      | ``<fg #00005f>``, ``<fg #EE1>`` | ``<bg #AF5FD7>``, ``<bg #fff>`` |\n        +-----------------+---------------------------------+---------------------------------+\n        | RGB colors      | ``<fg 0,95,0>``                 | ``<bg 72,119,65>``              |\n        +-----------------+---------------------------------+---------------------------------+\n        | Stylizing       | ``<bold>``, ``<b>``,  ``<underline>``, ``<u>``                    |\n        +-----------------+-------------------------------------------------------------------+\n\n        .. _env:\n\n        .. rubric:: The environment variables\n\n        The default values of sink parameters can be entirely customized. This is particularly\n        useful if you don't like the log format of the pre-configured sink.\n\n        Each of the |add| default parameter can be modified by setting the ``LOGURU_[PARAM]``\n        environment variable. For example on Linux: ``export LOGURU_LEVEL=\"WARNING\"``\n        or ``export LOGURU_FORMAT=\"{time} - {message}\"``.\n\n        The default levels' attributes can also be modified by setting the ``LOGURU_[LEVEL]_[ATTR]``\n        environment variable. For example, on Windows: ``setx LOGURU_DEBUG_COLOR \"<blue>\"``\n        or ``setx LOGURU_TRACE_ICON \"🚀\"``. If you use the ``set`` command, do not include quotes\n        but escape special symbol as needed, e.g. ``set LOGURU_DEBUG_COLOR=^<blue^>``.\n\n        If you want to disable the pre-configured sink, you can set the ``LOGURU_AUTOINIT``\n        variable to ``False``.\n\n        On Linux, you will probably need to edit the ``~/.profile`` file to make this persistent. On\n        Windows, don't forget to restart your terminal for the change to be taken into account.\n\n        Examples\n        --------\n        >>> logger.add(sys.stdout, format=\"{time} - {level} - {message}\", filter=\"sub.module\")\n\n        >>> logger.add(\"file_{time}.log\", level=\"TRACE\", rotation=\"100 MB\")\n\n        >>> def debug_only(record):\n        ...     return record[\"level\"].name == \"DEBUG\"\n        ...\n        >>> logger.add(\"debug.log\", filter=debug_only)  # Other levels are filtered out\n\n        >>> def my_sink(message):\n        ...     record = message.record\n        ...     update_db(message, time=record[\"time\"], level=record[\"level\"])\n        ...\n        >>> logger.add(my_sink)\n\n        >>> def dynamic_format(record):\n        ...     # Caution: the template must be returned as-is (with placeholders left unformatted).\n        ...     if record[\"level\"].no >= 40:\n        ...         return \"<red>{time} {level} {message}</red>\\n{exception}\"\n        ...     return \"{time} {level} {message}\\n{exception}\"\n        ...\n        >>> logger.add(sys.stderr, format=dynamic_format)\n\n        >>> level_per_module = {\n        ...     \"\": \"DEBUG\",\n        ...     \"third.lib\": \"WARNING\",\n        ...     \"anotherlib\": False\n        ... }\n        >>> logger.add(lambda m: print(m, end=\"\"), filter=level_per_module, level=0)\n\n        >>> async def publish(message):\n        ...     await api.post(message)\n        ...\n        >>> logger.add(publish, serialize=True)\n\n        >>> from logging import StreamHandler\n        >>> logger.add(StreamHandler(sys.stderr), format=\"{message}\")\n\n        >>> class RandomStream:\n        ...     def __init__(self, seed, threshold):\n        ...         self.threshold = threshold\n        ...         random.seed(seed)\n        ...     def write(self, message):\n        ...         if random.random() > self.threshold:\n        ...             print(message)\n        ...\n        >>> stream_object = RandomStream(seed=12345, threshold=0.25)\n        >>> logger.add(stream_object, level=\"INFO\")\n        \"\"\"\n        with self._core.lock:\n            handler_id = self._core.handlers_count\n            self._core.handlers_count += 1\n\n        error_interceptor = ErrorInterceptor(catch, handler_id)\n\n        if colorize is None and serialize:\n            colorize = False\n\n        if isinstance(sink, (str, PathLike)):\n            path = sink\n            name = \"'%s'\" % path\n\n            if colorize is None:\n                colorize = False\n\n            wrapped_sink = FileSink(path, **kwargs)\n            kwargs = {}\n            encoding = wrapped_sink.encoding\n            terminator = \"\\n\"\n            exception_prefix = \"\"\n        elif hasattr(sink, \"write\") and callable(sink.write):\n            name = getattr(sink, \"name\", None) or repr(sink)\n\n            if colorize is None:\n                colorize = _colorama.should_colorize(sink)\n\n            if colorize is True and _colorama.should_wrap(sink):\n                stream = _colorama.wrap(sink)\n            else:\n                stream = sink\n\n            wrapped_sink = StreamSink(stream)\n            encoding = getattr(sink, \"encoding\", None)\n            terminator = \"\\n\"\n            exception_prefix = \"\"\n        elif isinstance(sink, logging.Handler):\n            name = repr(sink)\n\n            if colorize is None:\n                colorize = False\n\n            wrapped_sink = StandardSink(sink)\n            encoding = getattr(sink, \"encoding\", None)\n            terminator = \"\"\n            exception_prefix = \"\\n\"\n        elif iscoroutinefunction(sink) or iscoroutinefunction(\n            getattr(sink, \"__call__\", None)  # noqa: B004\n        ):\n            name = getattr(sink, \"__name__\", None) or repr(sink)\n\n            if colorize is None:\n                colorize = False\n\n            loop = kwargs.pop(\"loop\", None)\n\n            # The worker thread needs an event loop, it can't create a new one internally because it\n            # has to be accessible by the user while calling \"complete()\", instead we use the global\n            # one when the sink is added. If \"enqueue=False\" the event loop is dynamically retrieved\n            # at each logging call, which is much more convenient. However, coroutine can't access\n            # running loop in Python 3.5.2 and earlier versions, see python/asyncio#452.\n            if enqueue and loop is None:\n                try:\n                    loop = _asyncio_loop.get_running_loop()\n                except RuntimeError as e:\n                    raise ValueError(\n                        \"An event loop is required to add a coroutine sink with `enqueue=True`, \"\n                        \"but none has been passed as argument and none is currently running.\"\n                    ) from e\n\n            coro = sink if iscoroutinefunction(sink) else sink.__call__\n            wrapped_sink = AsyncSink(coro, loop, error_interceptor)\n            encoding = \"utf8\"\n            terminator = \"\\n\"\n            exception_prefix = \"\"\n        elif callable(sink):\n            name = getattr(sink, \"__name__\", None) or repr(sink)\n\n            if colorize is None:\n                colorize = False\n\n            wrapped_sink = CallableSink(sink)\n            encoding = \"utf8\"\n            terminator = \"\\n\"\n            exception_prefix = \"\"\n        else:\n            raise TypeError(\"Cannot log to objects of type '%s'\" % type(sink).__name__)\n\n        if kwargs:\n            raise TypeError(\"add() got an unexpected keyword argument '%s'\" % next(iter(kwargs)))\n\n        if filter is None:\n            filter_func = None\n        elif filter == \"\":\n            filter_func = _filters.filter_none\n        elif isinstance(filter, str):\n            parent = filter + \".\"\n            length = len(parent)\n            filter_func = functools.partial(_filters.filter_by_name, parent=parent, length=length)\n        elif isinstance(filter, dict):\n            level_per_module = {}\n            for module, level_ in filter.items():\n                if module is not None and not isinstance(module, str):\n                    raise TypeError(\n                        \"The filter dict contains an invalid module, \"\n                        \"it should be a string (or None), not: '%s'\" % type(module).__name__\n                    )\n                if level_ is False:\n                    levelno_ = False\n                elif level_ is True:\n                    levelno_ = 0\n                elif isinstance(level_, str):\n                    try:\n                        levelno_ = self.level(level_).no\n                    except ValueError:\n                        raise ValueError(\n                            \"The filter dict contains a module '%s' associated to a level name \"\n                            \"which does not exist: '%s'\" % (module, level_)\n                        ) from None\n                elif isinstance(level_, int):\n                    levelno_ = level_\n                else:\n                    raise TypeError(\n                        \"The filter dict contains a module '%s' associated to an invalid level, \"\n                        \"it should be an integer, a string or a boolean, not: '%s'\"\n                        % (module, type(level_).__name__)\n                    )\n                if levelno_ < 0:\n                    raise ValueError(\n                        \"The filter dict contains a module '%s' associated to an invalid level, \"\n                        \"it should be a positive integer, not: '%d'\" % (module, levelno_)\n                    )\n                level_per_module[module] = levelno_\n            filter_func = functools.partial(\n                _filters.filter_by_level, level_per_module=level_per_module\n            )\n        elif callable(filter):\n            if filter == builtins.filter:\n                raise ValueError(\n                    \"The built-in 'filter()' function cannot be used as a 'filter' parameter, \"\n                    \"this is most likely a mistake (please double-check the arguments passed \"\n                    \"to 'logger.add()').\"\n                )\n            filter_func = filter\n        else:\n            raise TypeError(\n                \"Invalid filter, it should be a function, a string or a dict, not: '%s'\"\n                % type(filter).__name__\n            )\n\n        if isinstance(level, str):\n            levelno = self.level(level).no\n        elif isinstance(level, int):\n            levelno = level\n        else:\n            raise TypeError(\n                \"Invalid level, it should be an integer or a string, not: '%s'\"\n                % type(level).__name__\n            )\n\n        if levelno < 0:\n            raise ValueError(\n                \"Invalid level value, it should be a positive integer, not: %d\" % levelno\n            )\n\n        if isinstance(format, str):\n            try:\n                formatter = Colorizer.prepare_format(format + terminator + \"{exception}\")\n            except ValueError as e:\n                raise ValueError(\n                    \"Invalid format, color markups could not be parsed correctly\"\n                ) from e\n            is_formatter_dynamic = False\n        elif callable(format):\n            if format == builtins.format:\n                raise ValueError(\n                    \"The built-in 'format()' function cannot be used as a 'format' parameter, \"\n                    \"this is most likely a mistake (please double-check the arguments passed \"\n                    \"to 'logger.add()').\"\n                )\n            formatter = format\n            is_formatter_dynamic = True\n        else:\n            raise TypeError(\n                \"Invalid format, it should be a string or a function, not: '%s'\"\n                % type(format).__name__\n            )\n\n        if not isinstance(encoding, str):\n            encoding = \"ascii\"\n\n        if isinstance(context, str):\n            context = get_context(context)\n        elif context is not None and not isinstance(context, BaseContext):\n            raise TypeError(\n                \"Invalid context, it should be a string or a multiprocessing context, \"\n                \"not: '%s'\" % type(context).__name__\n            )\n\n        with self._core.lock:\n            exception_formatter = ExceptionFormatter(\n                colorize=colorize,\n                encoding=encoding,\n                diagnose=diagnose,\n                backtrace=backtrace,\n                hidden_frames_filename=self.catch.__code__.co_filename,\n                prefix=exception_prefix,\n            )\n\n            handler = Handler(\n                name=name,\n                sink=wrapped_sink,\n                levelno=levelno,\n                formatter=formatter,\n                is_formatter_dynamic=is_formatter_dynamic,\n                filter_=filter_func,\n                colorize=colorize,\n                serialize=serialize,\n                enqueue=enqueue,\n                multiprocessing_context=context,\n                id_=handler_id,\n                error_interceptor=error_interceptor,\n                exception_formatter=exception_formatter,\n                levels_ansi_codes=self._core.levels_ansi_codes,\n            )\n\n            handlers = self._core.handlers.copy()\n            handlers[handler_id] = handler\n\n            self._core.min_level = min(self._core.min_level, levelno)\n            self._core.handlers = handlers\n\n        return handler_id\n\n    def remove(self, handler_id=None):\n        \"\"\"Remove a previously added handler and stop sending logs to its sink.\n\n        Parameters\n        ----------\n        handler_id : |int| or ``None``\n            The id of the sink to remove, as it was returned by the |add| method. If ``None``, all\n            handlers are removed. The pre-configured handler is guaranteed to have the index ``0``.\n\n        Raises\n        ------\n        ValueError\n            If ``handler_id`` is not ``None`` but there is no active handler with such id.\n\n        Examples\n        --------\n        >>> i = logger.add(sys.stderr, format=\"{message}\")\n        >>> logger.info(\"Logging\")\n        Logging\n        >>> logger.remove(i)\n        >>> logger.info(\"No longer logging\")\n        \"\"\"\n        if not (handler_id is None or isinstance(handler_id, int)):\n            raise TypeError(\n                \"Invalid handler id, it should be an integer as returned \"\n                \"by the 'add()' method (or None), not: '%s'\" % type(handler_id).__name__\n            )\n\n        with self._core.lock:\n            if handler_id is not None and handler_id not in self._core.handlers:\n                raise ValueError(\"There is no existing handler with id %d\" % handler_id) from None\n\n            if handler_id is None:\n                handler_ids = list(self._core.handlers)\n            else:\n                handler_ids = [handler_id]\n\n            for handler_id in handler_ids:\n                handlers = self._core.handlers.copy()\n                handler = handlers.pop(handler_id)\n\n                # This needs to be done first in case \"stop()\" raises an exception\n                levelnos = (h.levelno for h in handlers.values())\n                self._core.min_level = min(levelnos, default=float(\"inf\"))\n                self._core.handlers = handlers\n\n                handler.stop()\n\n    def complete(self):\n        \"\"\"Wait for the end of enqueued messages and asynchronous tasks scheduled by handlers.\n\n        This method proceeds in two steps: first it waits for all logging messages added to handlers\n        with ``enqueue=True`` to be processed, then it returns an object that can be awaited to\n        finalize all logging tasks added to the event loop by coroutine sinks.\n\n        It can be called from non-asynchronous code. This is especially recommended when the\n        ``logger`` is utilized with ``multiprocessing`` to ensure messages put to the internal\n        queue have been properly transmitted before leaving a child process.\n\n        The returned object should be awaited before the end of a coroutine executed by\n        |asyncio.run| or |loop.run_until_complete| to ensure all asynchronous logging messages are\n        processed. The function |asyncio.get_running_loop| is called beforehand, only tasks\n        scheduled in the same loop that the current one will be awaited by the method.\n\n        Returns\n        -------\n        :term:`awaitable`\n            An awaitable object which ensures all asynchronous logging calls are completed when\n            awaited.\n\n        Examples\n        --------\n        >>> async def sink(message):\n        ...     await asyncio.sleep(0.1)  # IO processing...\n        ...     print(message, end=\"\")\n        ...\n        >>> async def work():\n        ...     logger.info(\"Start\")\n        ...     logger.info(\"End\")\n        ...     await logger.complete()\n        ...\n        >>> logger.add(sink)\n        1\n        >>> asyncio.run(work())\n        Start\n        End\n\n        >>> def process():\n        ...     logger.info(\"Message sent from the child\")\n        ...     logger.complete()\n        ...\n        >>> logger.add(sys.stderr, enqueue=True)\n        1\n        >>> process = multiprocessing.Process(target=process)\n        >>> process.start()\n        >>> process.join()\n        Message sent from the child\n        \"\"\"\n        tasks = []\n\n        with self._core.lock:\n            handlers = self._core.handlers.copy()\n            for handler in handlers.values():\n                handler.complete_queue()\n                tasks.extend(handler.tasks_to_complete())\n\n        class AwaitableCompleter:\n            def __await__(self):\n                for task in tasks:\n                    yield from task.__await__()\n\n        return AwaitableCompleter()\n\n    def catch(\n        self,\n        exception=Exception,\n        *,\n        level=\"ERROR\",\n        reraise=False,\n        onerror=None,\n        exclude=None,\n        default=None,\n        message=\"An error has been caught in function '{record[function]}', \"\n        \"process '{record[process].name}' ({record[process].id}), \"\n        \"thread '{record[thread].name}' ({record[thread].id}):\"\n    ):\n        \"\"\"Return a decorator to automatically log possibly caught error in wrapped function.\n\n        This is useful to ensure unexpected exceptions are logged, the entire program can be\n        wrapped by this method. This is also very useful to decorate |Thread.run| methods while\n        using threads to propagate errors to the main logger thread.\n\n        Note that the visibility of variables values (which uses the great |better_exceptions|_\n        library from `@Qix-`_) depends on the ``diagnose`` option of each configured sink.\n\n        The returned object can also be used as a context manager.\n\n        Parameters\n        ----------\n        exception : |Exception|, optional\n            The type of exception to intercept. If several types should be caught, a tuple of\n            exceptions can be used too.\n        level : |str| or |int|, optional\n            The level name or severity with which the message should be logged.\n        reraise : |bool|, optional\n            Whether the exception should be raised again and hence propagated to the caller.\n        onerror : |callable|_, optional\n            A function that will be called if an error occurs, once the message has been logged.\n            It should accept the exception instance as it sole argument.\n        exclude : |Exception|, optional\n            A type of exception (or a tuple of types) that will be purposely ignored and hence\n            propagated to the caller without being logged.\n        default : |Any|, optional\n            The value to be returned by the decorated function if an error occurred without being\n            re-raised.\n        message : |str|, optional\n            The message that will be automatically logged if an exception occurs. Note that it will\n            be formatted with the ``record`` attribute.\n\n        Returns\n        -------\n        :term:`decorator` / :term:`context manager`\n            An object that can be used to decorate a function or as a context manager to log\n            exceptions possibly caught.\n\n        Examples\n        --------\n        >>> @logger.catch\n        ... def f(x):\n        ...     100 / x\n        ...\n        >>> def g():\n        ...     f(10)\n        ...     f(0)\n        ...\n        >>> g()\n        ERROR - An error has been caught in function 'g', process 'Main' (367), thread 'ch1' (1398):\n        Traceback (most recent call last):\n          File \"program.py\", line 12, in <module>\n            g()\n            └ <function g at 0x7f225fe2bc80>\n        > File \"program.py\", line 10, in g\n            f(0)\n            └ <function f at 0x7f225fe2b9d8>\n          File \"program.py\", line 6, in f\n            100 / x\n                  └ 0\n        ZeroDivisionError: division by zero\n\n        >>> with logger.catch(message=\"Because we never know...\"):\n        ...    main()  # No exception, no logs\n\n        >>> # Use 'onerror' to prevent the program exit code to be 0 (if 'reraise=False') while\n        >>> # also avoiding the stacktrace to be duplicated on stderr (if 'reraise=True').\n        >>> @logger.catch(onerror=lambda _: sys.exit(1))\n        ... def main():\n        ...     1 / 0\n        \"\"\"\n        if callable(exception) and (\n            not isclass(exception) or not issubclass(exception, BaseException)\n        ):\n            return self.catch()(exception)\n\n        logger = self\n\n        class Catcher:\n            def __init__(self, from_decorator):\n                self._from_decorator = from_decorator\n\n            def __enter__(self):\n                return None\n\n            def __exit__(self, type_, value, traceback_):\n                if type_ is None:\n                    return None\n\n                # We must prevent infinite recursion in case \"logger.catch()\" handles an exception\n                # that occurs while logging another exception. This can happen for example when\n                # the exception formatter calls \"repr(obj)\" while the \"__repr__\" method is broken\n                # but decorated with \"logger.catch()\". In such a case, we ignore the catching\n                # mechanism and just let the exception be thrown (that way, the formatter will\n                # rightly assume the object is unprintable).\n                if getattr(logger._core.thread_locals, \"already_logging_exception\", False):\n                    return False\n\n                if not issubclass(type_, exception):\n                    return False\n\n                if exclude is not None and issubclass(type_, exclude):\n                    return False\n\n                from_decorator = self._from_decorator\n                _, depth, _, *options = logger._options\n\n                if from_decorator:\n                    depth += 1\n\n                catch_options = [(type_, value, traceback_), depth, True, *options]\n\n                logger._core.thread_locals.already_logging_exception = True\n                try:\n                    logger._log(level, from_decorator, catch_options, message, (), {})\n                finally:\n                    logger._core.thread_locals.already_logging_exception = False\n\n                if onerror is not None:\n                    onerror(value)\n\n                return not reraise\n\n            def __call__(self, function):\n                if isclass(function):\n                    raise TypeError(\n                        \"Invalid object decorated with 'catch()', it must be a function, \"\n                        \"not a class (tried to wrap '%s')\" % function.__name__\n                    )\n\n                catcher = Catcher(True)\n\n                if iscoroutinefunction(function):\n\n                    async def catch_wrapper(*args, **kwargs):\n                        with catcher:\n                            return await function(*args, **kwargs)\n                        return default\n\n                elif isgeneratorfunction(function):\n\n                    def catch_wrapper(*args, **kwargs):\n                        with catcher:\n                            return (yield from function(*args, **kwargs))\n                        return default\n\n                elif isasyncgenfunction(function):\n\n                    class AsyncGenCatchWrapper(AsyncGenerator):\n\n                        def __init__(self, gen):\n                            self._gen = gen\n\n                        async def asend(self, value):\n                            with catcher:\n                                try:\n                                    return await self._gen.asend(value)\n                                except StopAsyncIteration:\n                                    pass\n                                except:\n                                    raise\n                            raise StopAsyncIteration\n\n                        async def athrow(self, *args, **kwargs):\n                            return await self._gen.athrow(*args, **kwargs)\n\n                    def catch_wrapper(*args, **kwargs):\n                        gen = function(*args, **kwargs)\n                        return AsyncGenCatchWrapper(gen)\n\n                else:\n\n                    def catch_wrapper(*args, **kwargs):\n                        with catcher:\n                            return function(*args, **kwargs)\n                        return default\n\n                functools.update_wrapper(catch_wrapper, function)\n                return catch_wrapper\n\n            async def __aenter__(self):\n                return self.__enter__()\n\n            async def __aexit__(self, type_, value, traceback_):\n                return self.__exit__(type_, value, traceback_)\n\n        return Catcher(False)\n\n    def opt(\n        self,\n        *,\n        exception=None,\n        record=False,\n        lazy=False,\n        colors=False,\n        raw=False,\n        capture=True,\n        depth=0,\n        ansi=False\n    ):\n        r\"\"\"Parametrize a logging call to slightly change generated log message.\n\n        Note that it's not possible to chain |opt| calls, the last one takes precedence over the\n        others as it will \"reset\" the options to their default values.\n\n        Parameters\n        ----------\n        exception : |bool|, |tuple| or |Exception|, optional\n            If it does not evaluate as ``False``, the passed exception is formatted and added to the\n            log message. It could be an |Exception| object or a ``(type, value, traceback)`` tuple,\n            otherwise the exception information is retrieved from |sys.exc_info|.\n        record : |bool|, optional\n            If ``True``, the record dict contextualizing the logging call can be used to format the\n            message by using ``{record[key]}`` in the log message.\n        lazy : |bool|, optional\n            If ``True``, the logging call attribute to format the message should be functions which\n            will be called only if the level is high enough. This can be used to avoid expensive\n            functions if not necessary.\n        colors : |bool|, optional\n            If ``True``, logged message will be colorized according to the markups it possibly\n            contains.\n        raw : |bool|, optional\n            If ``True``, the formatting of each sink will be bypassed and the message will be sent\n            as is.\n        capture : |bool|, optional\n            If ``False``, the ``**kwargs`` of logged message will not automatically populate\n            the ``extra`` dict (although they are still used for formatting).\n        depth : |int|, optional\n            Specify which stacktrace should be used to contextualize the logged message. This is\n            useful while using the logger from inside a wrapped function to retrieve worthwhile\n            information.\n        ansi : |bool|, optional\n            Deprecated since version 0.4.1: the ``ansi`` parameter will be removed in Loguru 1.0.0,\n            it is replaced by ``colors`` which is a more appropriate name.\n\n        Returns\n        -------\n        :class:`~Logger`\n            A logger wrapping the core logger, but transforming logged message adequately before\n            sending.\n\n        Examples\n        --------\n        >>> try:\n        ...     1 / 0\n        ... except ZeroDivisionError:\n        ...    logger.opt(exception=True).debug(\"Exception logged with debug level:\")\n        ...\n        [18:10:02] DEBUG in '<module>' - Exception logged with debug level:\n        Traceback (most recent call last, catch point marked):\n        > File \"<stdin>\", line 2, in <module>\n        ZeroDivisionError: division by zero\n\n        >>> logger.opt(record=True).info(\"Current line is: {record[line]}\")\n        [18:10:33] INFO in '<module>' - Current line is: 1\n\n        >>> logger.opt(lazy=True).debug(\"If sink <= DEBUG: {x}\", x=lambda: math.factorial(2**5))\n        [18:11:19] DEBUG in '<module>' - If sink <= DEBUG: 263130836933693530167218012160000000\n\n        >>> logger.opt(colors=True).warning(\"We got a <red>BIG</red> problem\")\n        [18:11:30] WARNING in '<module>' - We got a BIG problem\n\n        >>> logger.opt(raw=True).debug(\"No formatting\\n\")\n        No formatting\n\n        >>> logger.opt(capture=False).info(\"Displayed but not captured: {value}\", value=123)\n        [18:11:41] Displayed but not captured: 123\n\n        >>> def wrapped():\n        ...     logger.opt(depth=1).info(\"Get parent context\")\n        ...\n        >>> def func():\n        ...     wrapped()\n        ...\n        >>> func()\n        [18:11:54] DEBUG in 'func' - Get parent context\n        \"\"\"\n        if ansi:\n            colors = True\n            warnings.warn(\n                \"The 'ansi' parameter is deprecated, please use 'colors' instead\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n\n        args = self._options[-2:]\n        return Logger(self._core, exception, depth, record, lazy, colors, raw, capture, *args)\n\n    def bind(__self, **kwargs):  # noqa: N805\n        \"\"\"Bind attributes to the ``extra`` dict of each logged message record.\n\n        This is used to add custom context to each logging call.\n\n        Parameters\n        ----------\n        **kwargs\n            Mapping between keys and values that will be added to the ``extra`` dict.\n\n        Returns\n        -------\n        :class:`~Logger`\n            A logger wrapping the core logger, but which sends record with the customized ``extra``\n            dict.\n\n        Examples\n        --------\n        >>> logger.add(sys.stderr, format=\"{extra[ip]} - {message}\")\n        >>> class Server:\n        ...     def __init__(self, ip):\n        ...         self.ip = ip\n        ...         self.logger = logger.bind(ip=ip)\n        ...     def call(self, message):\n        ...         self.logger.info(message)\n        ...\n        >>> instance_1 = Server(\"192.168.0.200\")\n        >>> instance_2 = Server(\"127.0.0.1\")\n        >>> instance_1.call(\"First instance\")\n        192.168.0.200 - First instance\n        >>> instance_2.call(\"Second instance\")\n        127.0.0.1 - Second instance\n        \"\"\"\n        *options, extra = __self._options\n        return Logger(__self._core, *options, {**extra, **kwargs})\n\n    @contextlib.contextmanager\n    def contextualize(__self, **kwargs):  # noqa: N805\n        \"\"\"Bind attributes to the context-local ``extra`` dict while inside the ``with`` block.\n\n        Contrary to |bind| there is no ``logger`` returned, the ``extra`` dict is modified in-place\n        and updated globally. Most importantly, it uses |contextvars| which means that\n        contextualized values are unique to each threads and asynchronous tasks.\n\n        The ``extra`` dict will retrieve its initial state once the context manager is exited.\n\n        Parameters\n        ----------\n        **kwargs\n            Mapping between keys and values that will be added to the context-local ``extra`` dict.\n\n        Returns\n        -------\n        :term:`context manager` / :term:`decorator`\n            A context manager (usable as a decorator too) that will bind the attributes once entered\n            and restore the initial state of the ``extra`` dict while exited.\n\n        Examples\n        --------\n        >>> logger.add(sys.stderr, format=\"{message} | {extra}\")\n        1\n        >>> def task():\n        ...     logger.info(\"Processing!\")\n        ...\n        >>> with logger.contextualize(task_id=123):\n        ...     task()\n        ...\n        Processing! | {'task_id': 123}\n        >>> logger.info(\"Done.\")\n        Done. | {}\n        \"\"\"\n        with __self._core.lock:\n            new_context = {**context.get(), **kwargs}\n            token = context.set(new_context)\n\n        try:\n            yield\n        finally:\n            with __self._core.lock:\n                context.reset(token)\n\n    def patch(self, patcher):\n        \"\"\"Attach a function to modify the record dict created by each logging call.\n\n        The ``patcher`` may be used to update the record on-the-fly before it's propagated to the\n        handlers. This allows the \"extra\" dict to be populated with dynamic values and also permits\n        advanced modifications of the record emitted while logging a message. The function is called\n        once before sending the log message to the different handlers.\n\n        It is recommended to apply modification on the ``record[\"extra\"]`` dict rather than on the\n        ``record`` dict itself, as some values are used internally by `Loguru`, and modify them may\n        produce unexpected results.\n\n        The logger can be patched multiple times. In this case, the functions are called in the\n        same order as they are added.\n\n        Parameters\n        ----------\n        patcher: |callable|_\n            The function to which the record dict will be passed as the sole argument. This function\n            is in charge of updating the record in-place, the function does not need to return any\n            value, the modified record object will be re-used.\n\n        Returns\n        -------\n        :class:`~Logger`\n            A logger wrapping the core logger, but which records are passed through the ``patcher``\n            function before being sent to the added handlers.\n\n        Examples\n        --------\n        >>> logger.add(sys.stderr, format=\"{extra[utc]} {message}\")\n        >>> logger = logger.patch(lambda record: record[\"extra\"].update(utc=datetime.utcnow())\n        >>> logger.info(\"That's way, you can log messages with time displayed in UTC\")\n\n        >>> def wrapper(func):\n        ...     @functools.wraps(func)\n        ...     def wrapped(*args, **kwargs):\n        ...         logger.patch(lambda r: r.update(function=func.__name__)).info(\"Wrapped!\")\n        ...         return func(*args, **kwargs)\n        ...     return wrapped\n\n        >>> def recv_record_from_network(pipe):\n        ...     record = pickle.loads(pipe.read())\n        ...     level, message = record[\"level\"], record[\"message\"]\n        ...     logger.patch(lambda r: r.update(record)).log(level, message)\n        \"\"\"\n        *options, patchers, extra = self._options\n        return Logger(self._core, *options, [*patchers, patcher], extra)\n\n    def level(self, name, no=None, color=None, icon=None):\n        r\"\"\"Add, update or retrieve a logging level.\n\n        Logging levels are defined by their ``name`` to which a severity ``no``, an ansi ``color``\n        tag and an ``icon`` are associated and possibly modified at run-time. To |log| to a custom\n        level, you should necessarily use its name, the severity number is not linked back to levels\n        name (this implies that several levels can share the same severity).\n\n        To add a new level, its ``name`` and its ``no`` are required. A ``color`` and an ``icon``\n        can also be specified or will be empty by default.\n\n        To update an existing level, pass its ``name`` with the parameters to be changed. It is not\n        possible to modify the ``no`` of a level once it has been added.\n\n        To retrieve level information, the ``name`` solely suffices.\n\n        Parameters\n        ----------\n        name : |str|\n            The name of the logging level.\n        no : |int|\n            The severity of the level to be added or updated.\n        color : |str|\n            The color markup of the level to be added or updated.\n        icon : |str|\n            The icon of the level to be added or updated.\n\n        Returns\n        -------\n        ``Level``\n            A |namedtuple| containing information about the level.\n\n        Raises\n        ------\n        ValueError\n            If attempting to access a level with a ``name`` that is not registered, or if trying to\n            change the severity ``no`` of an existing level.\n\n        Examples\n        --------\n        >>> level = logger.level(\"ERROR\")\n        >>> print(level)\n        Level(name='ERROR', no=40, color='<red><bold>', icon='❌')\n        >>> logger.add(sys.stderr, format=\"{level.no} {level.icon} {message}\")\n        1\n        >>> logger.level(\"CUSTOM\", no=15, color=\"<blue>\", icon=\"@\")\n        Level(name='CUSTOM', no=15, color='<blue>', icon='@')\n        >>> logger.log(\"CUSTOM\", \"Logging...\")\n        15 @ Logging...\n        >>> logger.level(\"WARNING\", icon=r\"/!\\\\\")\n        Level(name='WARNING', no=30, color='<yellow><bold>', icon='/!\\\\\\\\')\n        >>> logger.warning(\"Updated!\")\n        30 /!\\\\ Updated!\n        \"\"\"\n        if not isinstance(name, str):\n            raise TypeError(\n                \"Invalid level name, it should be a string, not: '%s'\" % type(name).__name__\n            )\n\n        if no is color is icon is None:\n            try:\n                return self._core.levels[name]\n            except KeyError:\n                raise ValueError(\"Level '%s' does not exist\" % name) from None\n\n        if name not in self._core.levels:\n            if no is None:\n                raise ValueError(\n                    \"Level '%s' does not exist, you have to create it by specifying a level no\"\n                    % name\n                )\n            old_color, old_icon = \"\", \" \"\n        elif no is not None:\n            raise ValueError(\"Level '%s' already exists, you can't update its severity no\" % name)\n        else:\n            _, no, old_color, old_icon = self.level(name)\n\n        if color is None:\n            color = old_color\n\n        if icon is None:\n            icon = old_icon\n\n        if not isinstance(no, int):\n            raise TypeError(\n                \"Invalid level no, it should be an integer, not: '%s'\" % type(no).__name__\n            )\n\n        if no < 0:\n            raise ValueError(\"Invalid level no, it should be a positive integer, not: %d\" % no)\n\n        ansi = Colorizer.ansify(color)\n        level = Level(name, no, color, icon)\n\n        with self._core.lock:\n            self._core.levels[name] = level\n            self._core.levels_ansi_codes[name] = ansi\n            self._core.levels_lookup[name] = (name, name, no, icon)\n            for handler in self._core.handlers.values():\n                handler.update_format(name)\n\n        return level\n\n    def disable(self, name):\n        \"\"\"Disable logging of messages coming from ``name`` module and its children.\n\n        Developers of library using `Loguru` should absolutely disable it to avoid disrupting\n        users with unrelated logs messages.\n\n        Note that in some rare circumstances, it is not possible for `Loguru` to\n        determine the module's ``__name__`` value. In such situation, ``record[\"name\"]`` will be\n        equal to ``None``, this is why ``None`` is also a valid argument.\n\n        Parameters\n        ----------\n        name : |str| or ``None``\n            The name of the parent module to disable.\n\n        Examples\n        --------\n        >>> logger.info(\"Allowed message by default\")\n        [22:21:55] Allowed message by default\n        >>> logger.disable(\"my_library\")\n        >>> logger.info(\"While publishing a library, don't forget to disable logging\")\n        \"\"\"\n        self._change_activation(name, False)\n\n    def enable(self, name):\n        \"\"\"Enable logging of messages coming from ``name`` module and its children.\n\n        Logging is generally disabled by imported library using `Loguru`, hence this function\n        allows users to receive these messages anyway.\n\n        To enable all logs regardless of the module they are coming from, an empty string ``\"\"`` can\n        be passed.\n\n        Parameters\n        ----------\n        name : |str| or ``None``\n            The name of the parent module to re-allow.\n\n        Examples\n        --------\n        >>> logger.disable(\"__main__\")\n        >>> logger.info(\"Disabled, so nothing is logged.\")\n        >>> logger.enable(\"__main__\")\n        >>> logger.info(\"Re-enabled, messages are logged.\")\n        [22:46:12] Re-enabled, messages are logged.\n        \"\"\"\n        self._change_activation(name, True)\n\n    def configure(self, *, handlers=None, levels=None, extra=None, patcher=None, activation=None):\n        \"\"\"Configure the core logger.\n\n        It should be noted that ``extra`` values set using this function are available across all\n        modules, so this is the best way to set overall default values.\n\n        To load the configuration directly from a file, such as JSON or YAML, it is also possible to\n        use the |loguru-config|_ library developed by `@erezinman`_.\n\n        Parameters\n        ----------\n        handlers : |list| of |dict|, optional\n            A list of each handler to be added. The list should contain dicts of params passed to\n            the |add| function as keyword arguments. If not ``None``, all previously added\n            handlers are first removed.\n        levels : |list| of |dict|, optional\n            A list of each level to be added or updated. The list should contain dicts of params\n            passed to the |level| function as keyword arguments. This will never remove previously\n            created levels.\n        extra : |dict|, optional\n            A dict containing additional parameters bound to the core logger, useful to share\n            common properties if you call |bind| in several of your files modules. If not ``None``,\n            this will remove previously configured ``extra`` dict.\n        patcher : |callable|_, optional\n            A function that will be applied to the record dict of each logged messages across all\n            modules using the logger. It should modify the dict in-place without returning anything.\n            The function is executed prior to the one possibly added by the |patch| method. If not\n            ``None``, this will replace previously configured ``patcher`` function.\n        activation : |list| of |tuple|, optional\n            A list of ``(name, state)`` tuples which denotes which loggers should be enabled (if\n            ``state`` is ``True``) or disabled (if ``state`` is ``False``). The calls to |enable|\n            and |disable| are made accordingly to the list order. This will not modify previously\n            activated loggers, so if you need a fresh start prepend your list with ``(\"\", False)``\n            or ``(\"\", True)``.\n\n        Returns\n        -------\n        :class:`list` of :class:`int`\n            A list containing the identifiers of added sinks (if any).\n\n        Examples\n        --------\n        >>> logger.configure(\n        ...     handlers=[\n        ...         dict(sink=sys.stderr, format=\"[{time}] {message}\"),\n        ...         dict(sink=\"file.log\", enqueue=True, serialize=True),\n        ...     ],\n        ...     levels=[dict(name=\"NEW\", no=13, icon=\"¤\", color=\"\")],\n        ...     extra={\"common_to_all\": \"default\"},\n        ...     patcher=lambda record: record[\"extra\"].update(some_value=42),\n        ...     activation=[(\"my_module.secret\", False), (\"another_library.module\", True)],\n        ... )\n        [1, 2]\n\n        >>> # Set a default \"extra\" dict to logger across all modules, without \"bind()\"\n        >>> extra = {\"context\": \"foo\"}\n        >>> logger.configure(extra=extra)\n        >>> logger.add(sys.stderr, format=\"{extra[context]} - {message}\")\n        >>> logger.info(\"Context without bind\")\n        >>> # => \"foo - Context without bind\"\n        >>> logger.bind(context=\"bar\").info(\"Suppress global context\")\n        >>> # => \"bar - Suppress global context\"\n        \"\"\"\n        if handlers is not None:\n            self.remove()\n        else:\n            handlers = []\n\n        if levels is not None:\n            for params in levels:\n                self.level(**params)\n\n        if patcher is not None:\n            with self._core.lock:\n                self._core.patcher = patcher\n\n        if extra is not None:\n            with self._core.lock:\n                self._core.extra.clear()\n                self._core.extra.update(extra)\n\n        if activation is not None:\n            for name, state in activation:\n                if state:\n                    self.enable(name)\n                else:\n                    self.disable(name)\n\n        return [self.add(**params) for params in handlers]\n\n    def reinstall(self):\n        \"\"\"Reinstall the core of logger.\n\n        When using multiprocessing, you can pass logger as a parameter to the target of\n        ``multiprocessing.Process``, and run this method once, thus you don't need to pass\n        logger to every function you called in the same process with spawn multiprocessing.\n\n        Examples\n        --------\n        >>> def subworker(logger):\n        ...     logger.reinstall()\n        ...     logger.info(\"Child\")\n        ...     deeper_subworker()\n\n        >>> def deeper_subworker():\n        ...     logger.info(\"Grandchild\")\n\n        >>> def test_process_spawn():\n        ...     spawn_context = multiprocessing.get_context(\"spawn\")\n        ...     logger.add(\"file.log\", context=spawn_context, enqueue=True, catch=False)\n        ...     process = spawn_context.Process(target=subworker, args=(logger,))\n        ...     process.start()\n        ...     process.join()\n        ...     assert process.exitcode == 0\n        ...     logger.info(\"Main\")\n        ...     logger.remove()\n        \"\"\"\n        from . import logger\n\n        logger._core = self._core\n\n    def _change_activation(self, name, status):\n        if not (name is None or isinstance(name, str)):\n            raise TypeError(\n                \"Invalid name, it should be a string (or None), not: '%s'\" % type(name).__name__\n            )\n\n        with self._core.lock:\n            enabled = self._core.enabled.copy()\n\n            if name is None:\n                for n in enabled:\n                    if n is None:\n                        enabled[n] = status\n                self._core.activation_none = status\n                self._core.enabled = enabled\n                return\n\n            if name != \"\":\n                name += \".\"\n\n            activation_list = [\n                (n, s) for n, s in self._core.activation_list if n[: len(name)] != name\n            ]\n\n            parent_status = next((s for n, s in activation_list if name[: len(n)] == n), None)\n            if parent_status != status and not (name == \"\" and status is True):\n                activation_list.append((name, status))\n\n                def modules_depth(x):\n                    return x[0].count(\".\")\n\n                activation_list.sort(key=modules_depth, reverse=True)\n\n            for n in enabled:\n                if n is not None and (n + \".\")[: len(name)] == name:\n                    enabled[n] = status\n\n            self._core.activation_list = activation_list\n            self._core.enabled = enabled\n\n    @staticmethod\n    def parse(file, pattern, *, cast={}, chunk=2**16):  # noqa: B006\n        \"\"\"Parse raw logs and extract each entry as a |dict|.\n\n        The logging format has to be specified as the regex ``pattern``, it will then be\n        used to parse the ``file`` and retrieve each entry based on the named groups present\n        in the regex.\n\n        Parameters\n        ----------\n        file : |str|, |Path| or |file-like object|_\n            The path of the log file to be parsed, or an already opened file object.\n        pattern : |str| or |re.Pattern|_\n            The regex to use for logs parsing, it should contain named groups which will be included\n            in the returned dict.\n        cast : |callable|_ or |dict|, optional\n            A function that should convert in-place the regex groups parsed (a dict of string\n            values) to more appropriate types. If a dict is passed, it should be a mapping between\n            keys of parsed log dict and the function that should be used to convert the associated\n            value.\n        chunk : |int|, optional\n            The number of bytes read while iterating through the logs, this avoids having to load\n            the whole file in memory.\n\n        Yields\n        ------\n        :class:`dict`\n            The dict mapping regex named groups to matched values, as returned by |match.groupdict|\n            and optionally converted according to ``cast`` argument.\n\n        Examples\n        --------\n        >>> reg = r\"(?P<lvl>[0-9]+): (?P<msg>.*)\"    # If log format is \"{level.no} - {message}\"\n        >>> for e in logger.parse(\"file.log\", reg):  # A file line could be \"10 - A debug message\"\n        ...     print(e)                             # => {'lvl': '10', 'msg': 'A debug message'}\n\n        >>> caster = dict(lvl=int)                   # Parse 'lvl' key as an integer\n        >>> for e in logger.parse(\"file.log\", reg, cast=caster):\n        ...     print(e)                             # => {'lvl': 10, 'msg': 'A debug message'}\n\n        >>> def cast(groups):\n        ...     if \"date\" in groups:\n        ...         groups[\"date\"] = datetime.strptime(groups[\"date\"], \"%Y-%m-%d %H:%M:%S\")\n        ...\n        >>> with open(\"file.log\") as file:\n        ...     for log in logger.parse(file, reg, cast=cast):\n        ...         print(log[\"date\"], log[\"something_else\"])\n        \"\"\"\n        if isinstance(file, (str, PathLike)):\n\n            @contextlib.contextmanager\n            def opener():\n                with open(str(file)) as fileobj:\n                    yield fileobj\n\n        elif hasattr(file, \"read\") and callable(file.read):\n\n            @contextlib.contextmanager\n            def opener():\n                yield file\n\n        else:\n            raise TypeError(\n                \"Invalid file, it should be a string path or a file object, not: '%s'\"\n                % type(file).__name__\n            )\n\n        if isinstance(cast, dict):\n\n            def cast_function(groups):\n                for key, converter in cast.items():\n                    if key in groups:\n                        groups[key] = converter(groups[key])\n\n        elif callable(cast):\n            cast_function = cast\n        else:\n            raise TypeError(\n                \"Invalid cast, it should be a function or a dict, not: '%s'\" % type(cast).__name__\n            )\n\n        try:\n            regex = re.compile(pattern)\n        except TypeError:\n            raise TypeError(\n                \"Invalid pattern, it should be a string or a compiled regex, not: '%s'\"\n                % type(pattern).__name__\n            ) from None\n\n        with opener() as fileobj:\n            matches = Logger._find_iter(fileobj, regex, chunk)\n\n            for match in matches:\n                groups = match.groupdict()\n                cast_function(groups)\n                yield groups\n\n    @staticmethod\n    def _find_iter(fileobj, regex, chunk):\n        buffer = fileobj.read(0)\n\n        while True:\n            text = fileobj.read(chunk)\n            buffer += text\n            matches = list(regex.finditer(buffer))\n\n            if not text:\n                yield from matches\n                break\n\n            if len(matches) > 1:\n                end = matches[-2].end()\n                buffer = buffer[end:]\n                yield from matches[:-1]\n\n    def _log(self, level, from_decorator, options, message, args, kwargs):\n        core = self._core\n\n        if not core.handlers:\n            return\n\n        try:\n            level_id, level_name, level_no, level_icon = core.levels_lookup[level]\n        except (KeyError, TypeError):\n            if isinstance(level, str):\n                raise ValueError(\"Level '%s' does not exist\" % level) from None\n            if not isinstance(level, int):\n                raise TypeError(\n                    \"Invalid level, it should be an integer or a string, not: '%s'\"\n                    % type(level).__name__\n                ) from None\n            if level < 0:\n                raise ValueError(\n                    \"Invalid level value, it should be a positive integer, not: %d\" % level\n                ) from None\n            cache = (None, \"Level %d\" % level, level, \" \")\n            level_id, level_name, level_no, level_icon = cache\n            core.levels_lookup[level] = cache\n\n        if level_no < core.min_level:\n            return\n\n        (exception, depth, record, lazy, colors, raw, capture, patchers, extra) = options\n\n        try:\n            frame = get_frame(depth + 2)\n        except ValueError:\n            f_globals = {}\n            f_lineno = 0\n            co_name = \"<unknown>\"\n            co_filename = \"<unknown>\"\n        else:\n            f_globals = frame.f_globals\n            f_lineno = frame.f_lineno or 0  # Can be None, see python/cpython#89726.\n            co_name = frame.f_code.co_name\n            co_filename = frame.f_code.co_filename\n\n        try:\n            name = f_globals[\"__name__\"]\n        except KeyError:\n            name = None\n\n        try:\n            if not core.enabled[name]:\n                return\n        except KeyError:\n            enabled = core.enabled\n            if name is None:\n                status = core.activation_none\n                enabled[name] = status\n                if not status:\n                    return\n            else:\n                dotted_name = name + \".\"\n                for dotted_module_name, status in core.activation_list:\n                    if dotted_name[: len(dotted_module_name)] == dotted_module_name:\n                        if status:\n                            break\n                        enabled[name] = False\n                        return\n                enabled[name] = True\n\n        current_datetime = aware_now()\n\n        file_name = basename(co_filename)\n        thread = current_thread()\n        process = current_process()\n        elapsed = current_datetime - start_time\n\n        if exception:\n            if isinstance(exception, BaseException):\n                type_, value, traceback = (type(exception), exception, exception.__traceback__)\n            elif isinstance(exception, tuple):\n                type_, value, traceback = exception\n            else:\n                type_, value, traceback = sys.exc_info()\n            exception = RecordException(type_, value, traceback)\n        else:\n            exception = None\n\n        log_record = {\n            \"elapsed\": elapsed,\n            \"exception\": exception,\n            \"extra\": {**core.extra, **context.get(), **extra},\n            \"file\": RecordFile(file_name, co_filename),\n            \"function\": co_name,\n            \"level\": RecordLevel(level_name, level_no, level_icon),\n            \"line\": f_lineno,\n            \"message\": str(message),\n            \"module\": splitext(file_name)[0],\n            \"name\": name,\n            \"process\": RecordProcess(process.ident, process.name),\n            \"thread\": RecordThread(thread.ident, thread.name),\n            \"time\": current_datetime,\n        }\n\n        if lazy:\n            args = [arg() for arg in args]\n            kwargs = {key: value() for key, value in kwargs.items()}\n\n        if capture and kwargs:\n            log_record[\"extra\"].update(kwargs)\n\n        if record:\n            if \"record\" in kwargs:\n                raise TypeError(\n                    \"The message can't be formatted: 'record' shall not be used as a keyword \"\n                    \"argument while logger has been configured with '.opt(record=True)'\"\n                )\n            kwargs.update(record=log_record)\n\n        if colors:\n            if args or kwargs:\n                colored_message = Colorizer.prepare_message(message, args, kwargs)\n            else:\n                colored_message = Colorizer.prepare_simple_message(str(message))\n            log_record[\"message\"] = colored_message.stripped\n        elif args or kwargs:\n            colored_message = None\n            with try_formatting(KeyError, IndexError, AttributeError, ValueError):\n                log_record[\"message\"] = message.format(*args, **kwargs)\n        else:\n            colored_message = None\n\n        if core.patcher:\n            core.patcher(log_record)\n\n        for patcher in patchers:\n            patcher(log_record)\n\n        for handler in core.handlers.values():\n            handler.emit(log_record, level_id, from_decorator, raw, colored_message)\n\n    def trace(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'TRACE'``.\"\"\"\n        __self._log(\"TRACE\", False, __self._options, __message, args, kwargs)\n\n    def debug(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'DEBUG'``.\"\"\"\n        __self._log(\"DEBUG\", False, __self._options, __message, args, kwargs)\n\n    def info(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'INFO'``.\"\"\"\n        __self._log(\"INFO\", False, __self._options, __message, args, kwargs)\n\n    def success(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'SUCCESS'``.\"\"\"\n        __self._log(\"SUCCESS\", False, __self._options, __message, args, kwargs)\n\n    def warning(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'WARNING'``.\"\"\"\n        __self._log(\"WARNING\", False, __self._options, __message, args, kwargs)\n\n    def error(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'ERROR'``.\"\"\"\n        __self._log(\"ERROR\", False, __self._options, __message, args, kwargs)\n\n    def critical(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``'CRITICAL'``.\"\"\"\n        __self._log(\"CRITICAL\", False, __self._options, __message, args, kwargs)\n\n    def exception(__self, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log an ``'ERROR'`` message while also capturing the currently handled exception.\n\n        This method internally uses |sys.exc_info|, therefore it should only be called within\n        an ``except`` block. To log an exception that has already been caught, use the ``exception``\n        argument of |opt| along with a call to the |error| method (for example).\n        \"\"\"\n        options = (True, *__self._options[1:])\n        __self._log(\"ERROR\", False, options, __message, args, kwargs)\n\n    def log(__self, __level, __message, *args, **kwargs):  # noqa: N805\n        r\"\"\"Log ``message.format(*args, **kwargs)`` with severity ``level``.\n\n        Note that if an |int| is provided as ``level``, the level is interpreted as an anonymous\n        one and displayed as such (see also |FAQ anonymous levels| for more details).\n        \"\"\"\n        __self._log(__level, False, __self._options, __message, args, kwargs)\n\n    def start(self, *args, **kwargs):\n        \"\"\"Add a handler sending log messages to a sink adequately configured.\n\n        Deprecated function, use |add| instead.\n\n        Warnings\n        --------\n        .. deprecated:: 0.2.2\n          ``start()`` will be removed in Loguru 1.0.0, it is replaced by ``add()`` which is a less\n          confusing name.\n        \"\"\"\n        warnings.warn(\n            \"The 'start()' method is deprecated, please use 'add()' instead\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        return self.add(*args, **kwargs)\n\n    def stop(self, *args, **kwargs):\n        \"\"\"Remove a previously added handler and stop sending logs to its sink.\n\n        Deprecated function, use |remove| instead.\n\n        Warnings\n        --------\n        .. deprecated:: 0.2.2\n          ``stop()`` will be removed in Loguru 1.0.0, it is replaced by ``remove()`` which is a less\n          confusing name.\n        \"\"\"\n        warnings.warn(\n            \"The 'stop()' method is deprecated, please use 'remove()' instead\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        return self.remove(*args, **kwargs)\n"
  },
  {
    "path": "loguru/_recattrs.py",
    "content": "import pickle\nfrom collections import namedtuple\n\n\nclass RecordLevel:\n    \"\"\"A class representing the logging level record with name, number and icon.\n\n    Attributes\n    ----------\n    icon : str\n        The icon representing the log level\n    name : str\n        The name of the log level\n    no : int\n        The numeric value of the log level\n    \"\"\"\n\n    __slots__ = (\"icon\", \"name\", \"no\")\n\n    def __init__(self, name, no, icon):\n        \"\"\"Initialize a RecordLevel instance.\n\n        Parameters\n        ----------\n        name : str\n            The name of the log level\n        no : int\n            The numeric value of the log level\n        icon : str\n            The icon representing the log level\n        \"\"\"\n        self.name = name\n        self.no = no\n        self.icon = icon\n\n    def __repr__(self):\n        \"\"\"Return string representation of RecordLevel.\n\n        Returns\n        -------\n        str\n            Formatted string with name, number and icon\n        \"\"\"\n        return \"(name=%r, no=%r, icon=%r)\" % (self.name, self.no, self.icon)\n\n    def __format__(self, spec):\n        \"\"\"Format the RecordLevel instance.\n\n        Parameters\n        ----------\n        spec : str\n            Format specification\n\n        Returns\n        -------\n        str\n            Formatted name according to specification\n        \"\"\"\n        return self.name.__format__(spec)\n\n\nclass RecordFile:\n    \"\"\"A class representing a file record with name and path.\n\n    Attributes\n    ----------\n    name : str\n        The name of the file\n    path : str\n        The path to the file\n    \"\"\"\n\n    __slots__ = (\"name\", \"path\")\n\n    def __init__(self, name, path):\n        \"\"\"Initialize a RecordFile instance.\n\n        Parameters\n        ----------\n        name : str\n            The name of the file\n        path : str\n            The path to the file\n        \"\"\"\n        self.name = name\n        self.path = path\n\n    def __repr__(self):\n        \"\"\"Return string representation of RecordFile.\n\n        Returns\n        -------\n        str\n            Formatted string with name and path\n        \"\"\"\n        return \"(name=%r, path=%r)\" % (self.name, self.path)\n\n    def __format__(self, spec):\n        \"\"\"Format the RecordFile instance.\n\n        Parameters\n        ----------\n        spec : str\n            Format specification\n\n        Returns\n        -------\n        str\n            Formatted name according to specification\n        \"\"\"\n        return self.name.__format__(spec)\n\n\nclass RecordThread:\n    \"\"\"A class representing a thread record with ID and name.\n\n    Attributes\n    ----------\n    id : int\n        The thread ID\n    name : str\n        The thread name\n    \"\"\"\n\n    __slots__ = (\"id\", \"name\")\n\n    def __init__(self, id_, name):\n        \"\"\"Initialize a RecordThread instance.\n\n        Parameters\n        ----------\n        id_ : int\n            The thread ID\n        name : str\n            The thread name\n        \"\"\"\n        self.id = id_\n        self.name = name\n\n    def __repr__(self):\n        \"\"\"Return string representation of RecordThread.\n\n        Returns\n        -------\n        str\n            Formatted string with id and name\n        \"\"\"\n        return \"(id=%r, name=%r)\" % (self.id, self.name)\n\n    def __format__(self, spec):\n        \"\"\"Format the RecordThread instance.\n\n        Parameters\n        ----------\n        spec : str\n            Format specification\n\n        Returns\n        -------\n        str\n            Formatted ID according to specification\n        \"\"\"\n        return self.id.__format__(spec)\n\n\nclass RecordProcess:\n    \"\"\"A class representing a process record with ID and name.\n\n    Attributes\n    ----------\n    id : int\n        The process ID\n    name : str\n        The process name\n    \"\"\"\n\n    __slots__ = (\"id\", \"name\")\n\n    def __init__(self, id_, name):\n        \"\"\"Initialize a RecordProcess instance.\n\n        Parameters\n        ----------\n        id_ : int\n            The process ID\n        name : str\n            The process name\n        \"\"\"\n        self.id = id_\n        self.name = name\n\n    def __repr__(self):\n        \"\"\"Return string representation of RecordProcess.\n\n        Returns\n        -------\n        str\n            Formatted string with id and name\n        \"\"\"\n        return \"(id=%r, name=%r)\" % (self.id, self.name)\n\n    def __format__(self, spec):\n        \"\"\"Format the RecordProcess instance.\n\n        Parameters\n        ----------\n        spec : str\n            Format specification\n\n        Returns\n        -------\n        str\n            Formatted ID according to specification\n        \"\"\"\n        return self.id.__format__(spec)\n\n\nclass RecordException(\n    namedtuple(\"RecordException\", (\"type\", \"value\", \"traceback\"))  # noqa: PYI024\n):\n    \"\"\"A class representing an exception record with type, value and traceback.\n\n    Attributes\n    ----------\n    type\n        The exception type\n    value\n        The exception value\n    traceback\n        The exception traceback\n    \"\"\"\n\n    def __repr__(self):\n        \"\"\"Return string representation of RecordException.\n\n        Returns\n        -------\n        str\n            Formatted string with type, value and traceback\n        \"\"\"\n        return \"(type=%r, value=%r, traceback=%r)\" % (self.type, self.value, self.traceback)\n\n    def __reduce__(self):\n        \"\"\"Reduce the RecordException for pickling.\n\n        This method handles pickling of the exception, managing cases where\n        the exception value or traceback might not be picklable.\n\n        Returns\n        -------\n        tuple\n            A tuple containing class and initialization arguments\n        \"\"\"\n        # The traceback is not picklable, therefore it needs to be removed. Additionally, there's a\n        # possibility that the exception value is not picklable either. In such cases, we also need\n        # to remove it. This is done for user convenience, aiming to prevent error logging caused by\n        # custom exceptions from third-party libraries. If the serialization succeeds, we can reuse\n        # the pickled value later for optimization (so that it's not pickled twice). It's important\n        # to note that custom exceptions might not necessarily raise a PickleError, hence the\n        # generic Exception catch.\n        try:\n            pickled_value = pickle.dumps(self.value)\n        except Exception:\n            return (RecordException, (self.type, None, None))\n        else:\n            return (RecordException._from_pickled_value, (self.type, pickled_value, None))\n\n    @classmethod\n    def _from_pickled_value(cls, type_, pickled_value, traceback_):\n        \"\"\"Create a RecordException instance from a pickled value.\n\n        Parameters\n        ----------\n        type_\n            The exception type\n        pickled_value\n            The pickled exception value\n        traceback_\n            The exception traceback\n\n        Returns\n        -------\n        RecordException\n            A new instance with unpickled value\n        \"\"\"\n        try:\n            # It's safe to use \"pickle.loads()\" in this case because the pickled value is generated\n            # by the same code and is not coming from an untrusted source.\n            value = pickle.loads(pickled_value)\n        except Exception:\n            return cls(type_, None, traceback_)\n        else:\n            return cls(type_, value, traceback_)\n"
  },
  {
    "path": "loguru/_simple_sinks.py",
    "content": "import inspect\nimport logging\nimport weakref\n\nfrom ._asyncio_loop import get_running_loop, get_task_loop\n\n\nclass StreamSink:\n    \"\"\"A sink that writes log messages to a stream object.\n\n    Parameters\n    ----------\n    stream\n        A stream object that supports write operations.\n    \"\"\"\n\n    def __init__(self, stream):\n        self._stream = stream\n        self._flushable = callable(getattr(stream, \"flush\", None))\n        self._stoppable = callable(getattr(stream, \"stop\", None))\n        self._completable = inspect.iscoroutinefunction(getattr(stream, \"complete\", None))\n\n    def write(self, message):\n        \"\"\"Write a message to the stream.\n\n        Parameters\n        ----------\n        message\n            The message to write.\n        \"\"\"\n        self._stream.write(message)\n        if self._flushable:\n            self._stream.flush()\n\n    def stop(self):\n        \"\"\"Stop the stream if it supports the stop operation.\"\"\"\n        if self._stoppable:\n            self._stream.stop()\n\n    def tasks_to_complete(self):\n        \"\"\"Return list of tasks that need to be completed.\n\n        Returns\n        -------\n        list\n            List of tasks to complete.\n        \"\"\"\n        if not self._completable:\n            return []\n        return [self._stream.complete()]\n\n\nclass StandardSink:\n    \"\"\"A sink that writes log messages using the standard logging module.\n\n    Parameters\n    ----------\n    handler\n        A logging handler instance.\n    \"\"\"\n\n    def __init__(self, handler):\n        self._handler = handler\n\n    def write(self, message):\n        \"\"\"Write a message using the standard logging handler.\n\n        Parameters\n        ----------\n        message\n            The message to write.\n        \"\"\"\n        if message.record[\"level\"].no < self._handler.level:\n            return\n        raw_record = message.record\n        message = str(message)\n        exc = raw_record[\"exception\"]\n        record = logging.getLogger().makeRecord(\n            raw_record[\"name\"],\n            raw_record[\"level\"].no,\n            raw_record[\"file\"].path,\n            raw_record[\"line\"],\n            message,\n            (),\n            (exc.type, exc.value, exc.traceback) if exc else None,\n            raw_record[\"function\"],\n            {\"extra\": raw_record[\"extra\"]},\n        )\n\n        # By default, the standard logging module will format the exception and assign it to the\n        # \"exc_text\" attribute. Then, the formatted exception will be automatically appended to the\n        # message when the record is formatted. This is a problem, because that would cause the\n        # exception to be duplicated in the log message, since it's also formatted by Loguru. To\n        # avoid this, we set \"exc_text\" to a simple newline character, which will end the message.\n        if exc:\n            record.exc_text = \"\\n\"\n\n        record.levelname = raw_record[\"level\"].name\n        self._handler.handle(record)\n\n    def stop(self):\n        \"\"\"Close the logging handler.\"\"\"\n        self._handler.close()\n\n    def tasks_to_complete(self):\n        \"\"\"Return list of tasks that need to be completed.\n\n        Returns\n        -------\n        list\n            Empty list as standard sink has no async tasks.\n        \"\"\"\n        return []\n\n\nclass AsyncSink:\n    \"\"\"A sink that handles asynchronous logging operations.\n\n    Parameters\n    ----------\n    function\n        The async function to execute.\n    loop\n        The event loop to use.\n    error_interceptor\n        An interceptor for handling errors.\n    \"\"\"\n\n    def __init__(self, function, loop, error_interceptor):\n        self._function = function\n        self._loop = loop\n        self._error_interceptor = error_interceptor\n        self._tasks = weakref.WeakSet()\n\n    def write(self, message):\n        \"\"\"Asynchronously write a message.\n\n        Parameters\n        ----------\n        message\n            The message to write.\n        \"\"\"\n        try:\n            loop = self._loop or get_running_loop()\n        except RuntimeError:\n            return\n\n        coroutine = self._function(message)\n        task = loop.create_task(coroutine)\n\n        def check_exception(future):\n            if future.cancelled() or future.exception() is None:\n                return\n            if not self._error_interceptor.should_catch():\n                raise future.exception()\n            self._error_interceptor.print(message.record, exception=future.exception())\n\n        task.add_done_callback(check_exception)\n        self._tasks.add(task)\n\n    def stop(self):\n        \"\"\"Cancel all pending tasks.\"\"\"\n        for task in self._tasks:\n            task.cancel()\n\n    def tasks_to_complete(self):\n        \"\"\"Return list of tasks that need to be completed.\n\n        Returns\n        -------\n        list\n            List of tasks to complete.\n        \"\"\"\n        # To avoid errors due to \"self._tasks\" being mutated while iterated, the\n        # \"tasks_to_complete()\" method must be protected by the same lock as \"write()\" (which\n        # happens to be the handler lock). However, the tasks must not be awaited while the lock is\n        # acquired as this could lead to a deadlock. Therefore, we first need to collect the tasks\n        # to complete, then return them so that they can be awaited outside of the lock.\n        return [self._complete_task(task) for task in self._tasks]\n\n    async def _complete_task(self, task):\n        \"\"\"Complete a single task.\n\n        Parameters\n        ----------\n        task\n            The task to complete.\n        \"\"\"\n        loop = get_running_loop()\n        if get_task_loop(task) is not loop:\n            return\n        try:\n            await task\n        except Exception:\n            pass  # Handled in \"check_exception()\"\n\n    def __getstate__(self):\n        state = self.__dict__.copy()\n        state[\"_tasks\"] = None\n        return state\n\n    def __setstate__(self, state):\n        self.__dict__.update(state)\n        self._tasks = weakref.WeakSet()\n\n\nclass CallableSink:\n    \"\"\"A sink that executes a callable function for each log message.\n\n    Parameters\n    ----------\n    function\n        The function to call for each message.\n    \"\"\"\n\n    def __init__(self, function):\n        self._function = function\n\n    def write(self, message):\n        \"\"\"Write a message by calling the function.\n\n        Parameters\n        ----------\n        message\n            The message to pass to the function.\n        \"\"\"\n        self._function(message)\n\n    def stop(self):\n        \"\"\"Stop the sink (no-op for callable sink).\"\"\"\n        pass\n\n    def tasks_to_complete(self):\n        \"\"\"Return list of tasks that need to be completed.\n\n        Returns\n        -------\n        list\n            Empty list as callable sink has no tasks.\n        \"\"\"\n        return []\n"
  },
  {
    "path": "loguru/_string_parsers.py",
    "content": "import datetime\nimport re\nfrom typing import Optional, Tuple\n\n\nclass Frequencies:\n    \"\"\"Provide static methods to compute the next occurrence of various time frequencies.\n\n    Includes hourly, daily, weekly, monthly, and yearly frequencies\n    based on a given datetime object.\n    \"\"\"\n\n    @staticmethod\n    def hourly(t: datetime.datetime) -> datetime.datetime:\n        \"\"\"Compute the next hour occurrence.\n\n        Parameters\n        ----------\n        t : datetime.datetime\n            The reference datetime.\n\n        Returns\n        -------\n        datetime.datetime\n            Next hour with minutes, seconds, microseconds set to zero.\n        \"\"\"\n        dt = t + datetime.timedelta(hours=1)\n        return dt.replace(minute=0, second=0, microsecond=0)\n\n    @staticmethod\n    def daily(t: datetime.datetime) -> datetime.datetime:\n        \"\"\"Compute the next day occurrence.\n\n        Parameters\n        ----------\n        t : datetime.datetime\n            The reference datetime.\n\n        Returns\n        -------\n        datetime.datetime\n            Next day with hour, minutes, seconds, microseconds set to zero.\n        \"\"\"\n        dt = t + datetime.timedelta(days=1)\n        return dt.replace(hour=0, minute=0, second=0, microsecond=0)\n\n    @staticmethod\n    def weekly(t: datetime.datetime) -> datetime.datetime:\n        \"\"\"Compute the next week occurrence.\n\n        Parameters\n        ----------\n        t : datetime.datetime\n            The reference datetime.\n\n        Returns\n        -------\n        datetime.datetime\n            Next Monday with hour, minutes, seconds, microseconds set to zero.\n        \"\"\"\n        dt = t + datetime.timedelta(days=7 - t.weekday())\n        return dt.replace(hour=0, minute=0, second=0, microsecond=0)\n\n    @staticmethod\n    def monthly(t: datetime.datetime) -> datetime.datetime:\n        \"\"\"Compute the next month occurrence.\n\n        Parameters\n        ----------\n        t : datetime.datetime\n            The reference datetime.\n\n        Returns\n        -------\n        datetime.datetime\n            First day of next month with hour, minutes, seconds, microseconds set to zero.\n        \"\"\"\n        if t.month == 12:\n            y, m = t.year + 1, 1\n        else:\n            y, m = t.year, t.month + 1\n        return t.replace(year=y, month=m, day=1, hour=0, minute=0, second=0, microsecond=0)\n\n    @staticmethod\n    def yearly(t: datetime.datetime) -> datetime.datetime:\n        \"\"\"Compute the next year occurrence.\n\n        Parameters\n        ----------\n        t : datetime.datetime\n            The reference datetime.\n\n        Returns\n        -------\n        datetime.datetime\n            First day of next year with hour, minutes, seconds, microseconds set to zero.\n        \"\"\"\n        y = t.year + 1\n        return t.replace(year=y, month=1, day=1, hour=0, minute=0, second=0, microsecond=0)\n\n\ndef parse_size(size: str) -> Optional[float]:\n    \"\"\"Parse a size string with optional units into bits.\n\n    Supports formats like '100MB', '2GiB', '1.5TB'. Case insensitive.\n\n    Parameters\n    ----------\n    size : str\n        Size string to parse (e.g., '100MB', '2GiB').\n\n    Returns\n    -------\n    float | None\n        Size in bits or None if invalid format.\n\n    Raises\n    ------\n    ValueError\n        If numeric value or unit is invalid.\n    \"\"\"\n    size = size.strip()\n    reg = re.compile(r\"([e\\+\\-\\.\\d]+)\\s*([kmgtpezy])?(i)?(b)\", flags=re.I)\n\n    match = reg.fullmatch(size)\n\n    if not match:\n        return None\n\n    s, u, i, b = match.groups()\n\n    try:\n        s = float(s)\n    except ValueError as err:\n        raise ValueError(\"Invalid float value while parsing size: '%s'\" % s) from err\n\n    u = \"kmgtpezy\".index(u.lower()) + 1 if u else 0\n    i = 1024 if i else 1000\n    b = {\"b\": 8, \"B\": 1}[b] if b else 1\n    return s * i**u / b\n\n\ndef parse_duration(duration: str) -> Optional[datetime.timedelta]:\n    \"\"\"Parse a duration string and return a corresponding timedelta object.\n\n    The string can include multiple units (years, months, weeks, days, hours, minutes, seconds).\n    Example: \"1h 30min\", \"2 days, 3h\", \"1.5y 2months\".\n\n    Parameters\n    ----------\n    duration : str\n        The duration string to parse.\n\n    Returns\n    -------\n    datetime.timedelta | None\n        The parsed duration or None if input is invalid.\n\n    Raises\n    ------\n    ValueError\n        If a value cannot be converted to float or if an invalid unit is encountered.\n    \"\"\"\n    duration = duration.strip()\n    reg = r\"(?:([e\\+\\-\\.\\d]+)\\s*([a-z]+)[\\s\\,]*)\"\n\n    units = [\n        (\"y|years?\", 31536000),\n        (\"months?\", 2628000),\n        (\"w|weeks?\", 604800),\n        (\"d|days?\", 86400),\n        (\"h|hours?\", 3600),\n        (\"min(?:ute)?s?\", 60),\n        (\"s|sec(?:ond)?s?\", 1),  # spellchecker: disable-line\n        (\"ms|milliseconds?\", 0.001),\n        (\"us|microseconds?\", 0.000001),\n    ]\n\n    if not re.fullmatch(reg + \"+\", duration, flags=re.I):\n        return None\n\n    seconds = 0\n\n    for value, unit in re.findall(reg, duration, flags=re.I):\n        try:\n            value = float(value)\n        except ValueError as e:\n            raise ValueError(\"Invalid float value while parsing duration: '%s'\" % value) from e\n\n        try:\n            unit = next(u for r, u in units if re.fullmatch(r, unit, flags=re.I))\n        except StopIteration:\n            raise ValueError(\"Invalid unit value while parsing duration: '%s'\" % unit) from None\n\n        seconds += value * unit\n\n    return datetime.timedelta(seconds=seconds)\n\n\ndef parse_frequency(frequency: str):\n    \"\"\"Parse a frequency string and return the corresponding Frequencies method.\n\n    Supported frequencies: hourly, daily, weekly, monthly, yearly.\n\n    Parameters\n    ----------\n    frequency : str\n        The frequency string.\n\n    Returns\n    -------\n    Callable | None\n        Corresponding Frequencies method or None if unrecognized.\n    \"\"\"\n    frequencies = {\n        \"hourly\": Frequencies.hourly,\n        \"daily\": Frequencies.daily,\n        \"weekly\": Frequencies.weekly,\n        \"monthly\": Frequencies.monthly,\n        \"yearly\": Frequencies.yearly,\n    }\n    frequency = frequency.strip().lower()\n    return frequencies.get(frequency, None)\n\n\ndef parse_day(day: str) -> Optional[int]:\n    \"\"\"Parse a weekday string and return its integer value.\n\n    Accepts full day names or \"w0\" to \"w6\".\n\n    Parameters\n    ----------\n    day : str\n        The day to parse.\n\n    Returns\n    -------\n    int | None\n        Integer value (Monday=0 ... Sunday=6), or None if invalid.\n\n    Raises\n    ------\n    ValueError\n        If the digit in 'wX' is not in range [0-6].\n    \"\"\"\n    days = {\n        \"monday\": 0,\n        \"tuesday\": 1,\n        \"wednesday\": 2,\n        \"thursday\": 3,\n        \"friday\": 4,\n        \"saturday\": 5,\n        \"sunday\": 6,\n    }\n    day = day.strip().lower()\n    if day in days:\n        return days[day]\n    if day.startswith(\"w\") and day[1:].isdigit():\n        day = int(day[1:])\n        if not 0 <= day < 7:\n            raise ValueError(\"Invalid weekday value while parsing day (expected [0-6]): '%d'\" % day)\n    else:\n        day = None\n\n    return day\n\n\ndef parse_time(time: str) -> datetime.time:\n    \"\"\"Parse a time string and return a `datetime.time` object.\n\n    Supports formats: HH, HH:MM, HH:MM:SS, HH AM/PM, etc.\n\n    Parameters\n    ----------\n    time : str\n        The time string.\n\n    Returns\n    -------\n    datetime.time\n        The parsed time.\n\n    Raises\n    ------\n    ValueError\n        If input doesn't match any supported format.\n    \"\"\"\n    time = time.strip()\n    reg = re.compile(r\"^[\\d\\.\\:]+\\s*(?:[ap]m)?$\", flags=re.I)\n\n    if not reg.match(time):\n        return None\n\n    formats = [\n        \"%H\",\n        \"%H:%M\",\n        \"%H:%M:%S\",\n        \"%H:%M:%S.%f\",\n        \"%I %p\",\n        \"%I:%M %S\",\n        \"%I:%M:%S %p\",\n        \"%I:%M:%S.%f %p\",\n    ]\n\n    for format_ in formats:\n        try:\n            dt = datetime.datetime.strptime(time, format_)\n        except ValueError:\n            pass\n        else:\n            return dt.time()\n\n    raise ValueError(\"Unrecognized format while parsing time: '%s'\" % time)\n\n\ndef parse_daytime(daytime: str) -> Optional[Tuple[int, datetime.time]]:\n    \"\"\"Parse a string representing a day and time separated by 'at'.\n\n    Parameters\n    ----------\n    daytime : str\n        The day and time string.\n\n    Returns\n    -------\n    tuple[int, datetime.time] | None\n        Parsed (day, time) or None.\n\n    Raises\n    ------\n    ValueError\n        If the day or time cannot be parsed.\n    \"\"\"\n    daytime = daytime.strip()\n    reg = re.compile(r\"^(.*?)\\s+at\\s+(.*)$\", flags=re.I)\n\n    match = reg.match(daytime)\n    if match:\n        day, time = match.groups()\n    else:\n        day = time = daytime\n\n    try:\n        parsed_day = parse_day(day)\n        if match and parsed_day is None:\n            raise ValueError(\"Unparsable day\")\n    except ValueError as e:\n        raise ValueError(\"Invalid day while parsing daytime: '%s'\" % day) from e\n\n    try:\n        parsed_time = parse_time(time)\n        if match and parsed_time is None:\n            raise ValueError(\"Unparsable time\")\n    except ValueError as e:\n        raise ValueError(\"Invalid time while parsing daytime: '%s'\" % time) from e\n\n    if parsed_day is None and parsed_time is None:\n        return None\n\n    return parsed_day, parsed_time\n"
  },
  {
    "path": "loguru/py.typed",
    "content": ""
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nbuild-backend = \"flit_core.buildapi\"\nrequires = [\"flit_core>=3,<4\"]\n\n[project]\nauthors = [{ name = \"Delgan\", email = \"delgan.py@gmail.com\" }]\nclassifiers = [\n  \"Development Status :: 5 - Production/Stable\",\n  \"Topic :: System :: Logging\",\n  \"Intended Audience :: Developers\",\n  \"Natural Language :: English\",\n  \"License :: OSI Approved :: MIT License\",\n  \"Operating System :: OS Independent\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3\",\n  \"Programming Language :: Python :: 3.5\",\n  \"Programming Language :: Python :: 3.6\",\n  \"Programming Language :: Python :: 3.7\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: 3.13\",\n  \"Programming Language :: Python :: 3.14\",\n  \"Programming Language :: Python :: 3 :: Only\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n]\ndependencies = [\n  \"colorama>=0.3.4 ; sys_platform=='win32'\",\n  \"aiocontextvars>=0.2.0 ; python_version<'3.7'\",\n  \"win32-setctime>=1.0.0 ; sys_platform=='win32'\",\n]\ndescription = \"Python logging made (stupidly) simple\"\ndynamic = ['version']\nkeywords = [\"loguru\", \"logging\", \"logger\", \"log\"]\nlicense = { file = \"LICENSE\" }\nname = \"loguru\"\nreadme = 'README.md'\nrequires-python = \">=3.5,<4.0\"\n\n[project.optional-dependencies]\ndev = [\n  # Setup.\n  \"pre-commit==4.3.0 ; python_version>='3.9'\",\n  \"tox==3.27.1 ; python_version<'3.8'\",\n  \"tox==4.24.1 ; python_version>='3.8' and python_version<'3.9'\",\n  \"tox==4.30.2 ; python_version>='3.9'\",\n  # Testing framework.\n  \"pytest==6.1.2 ; python_version<'3.8'\",\n  \"pytest==8.3.2 ; python_version>='3.8' and python_version<'3.9'\",\n  \"pytest==8.4.1 ; python_version>='3.9'\",\n  \"pytest-cov==2.12.1 ; python_version<'3.8'\",\n  \"pytest-cov==5.0.0 ; python_version>='3.8' and python_version<'3.9'\",\n  \"pytest-cov==6.2.1 ; python_version>='3.9'\",\n  \"pytest-mypy-plugins==1.9.3 ; python_version>='3.6' and python_version<'3.8'\",\n  \"pytest-mypy-plugins==3.1.0 ; python_version>='3.8' and python_version<'3.9'\",\n  \"pytest-mypy-plugins==3.2.0 ; python_version>='3.9'\",\n  # Testing utils.\n  \"colorama==0.4.5 ; python_version<'3.8'\",\n  \"colorama==0.4.6 ; python_version>='3.8'\",\n  \"freezegun==1.1.0 ; python_version<'3.8'\",\n  \"freezegun==1.5.0 ; python_version>='3.8'\",\n  \"exceptiongroup==1.1.3 ; python_version>='3.7' and python_version<'3.11'\",\n  # Type checking.\n  \"mypy==v0.910 ; python_version<'3.6'\",\n  \"mypy==v0.971 ; python_version>='3.6' and python_version<'3.7'\",\n  \"mypy==v1.4.1 ; python_version>='3.7' and python_version<'3.8'\",\n  \"mypy==v1.14.1 ; python_version>='3.8' and  python_version<'3.9'\",\n  \"mypy==v1.18.2 ; python_version>='3.9'\",\n  # Docs.\n  \"Sphinx==8.2.1 ; python_version>='3.11'\",\n  \"sphinx-rtd-theme==3.0.2 ; python_version>='3.11'\",\n  \"myst-parser==4.0.0 ; python_version>='3.11'\",\n  # Packaging.\n  \"build==1.3.0 ; python_version>='3.11'\",\n  \"twine==6.2.0 ; python_version>='3.11'\",\n]\n\n[project.urls]\nChangelog = \"https://github.com/Delgan/loguru/blob/master/CHANGELOG.rst\"\nDocumentation = \"https://loguru.readthedocs.io/en/stable/index.html\"\nHomepage = \"https://github.com/Delgan/loguru\"\n\n[tool.black]\nforce-exclude = \"tests/exceptions/source/modern/*\"\nline-length = 100\ntarget-version = [\"py35\"]\n\n[tool.pyright]\n# Types are defined in a stub file. Unfortunately, type checkers such as Pyright and Mypy are\n# unable to \"merge\" them with the file containing the actual Python implementation. This causes\n# many false positives, therefore type checking is disabled to avoid noisy errors in the editor.\ntypeCheckingMode = \"off\"\n\n[tool.flit.module]\nname = \"loguru\"\n\n[tool.pytest.ini_options]\naddopts = \"-l\"\nfilterwarnings = [\n  # By default all warnings are treated as errors.\n  'error',\n  # Mixing threads and \"fork()\" is deprecated, but we need to test it anyway.\n  'ignore:.*use of fork\\(\\) may lead to deadlocks in the child.*:DeprecationWarning',\n  # Using \"set_event_loop()\" is deprecated, but no alternative is provided.\n  \"ignore:.*'asyncio.set_event_loop' is deprecated.*:DeprecationWarning\",\n]\ntestpaths = [\"tests\"]\n\n[tool.ruff]\nexclude = [\"tests/exceptions/source/*\"]\nline-length = 100\n\n# Actually, we should target Python 3.5, but Ruff does not support it.\ntarget-version = \"py37\"\n\n[tool.ruff.lint]\n# See list of rules at: https://docs.astral.sh/ruff/rules/\nselect = [\"F\", \"E\", \"W\", \"I\", \"B\", \"N\", \"D\", \"PT\", \"PYI\", \"RET\", \"RUF\"]\n\n[tool.ruff.lint.per-file-ignores]\n\"tests/**\" = [\n  \"D1\", # Do not require documentation for tests.\n]\n\"loguru/__init__.pyi\" = [\n  \"PYI026\", # TypeAlias is not supported by Mypy 0.910 (Python 3.5).\n]\n\n[tool.ruff.lint.pycodestyle]\nmax-doc-length = 100\n\n[tool.ruff.lint.pydocstyle]\nconvention = \"numpy\"\n\n[tool.typos.default]\nextend-ignore-re = [\"(?Rm)^.*# spellchecker: disable-line$\"]\n\n[tool.typos.default.extend-identifiers]\nasend = \"asend\"\n\n[tool.typos.files]\nextend-exclude = [\n  \"tests/exceptions/output/**\", # False positive due to ansi sequences.\n]\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "import asyncio\nimport builtins\nimport contextlib\nimport datetime\nimport io\nimport logging\nimport multiprocessing\nimport os\nimport pathlib\nimport re\nimport sys\nimport threading\nimport time\nimport traceback\nimport warnings\nfrom typing import NamedTuple\n\nimport freezegun\nimport pytest\n\nimport loguru\n\nif sys.version_info < (3, 5, 3):\n\n    def run(coro):\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        res = loop.run_until_complete(coro)\n        loop.close()\n        asyncio.set_event_loop(None)\n        return res\n\n    asyncio.run = run\nelif sys.version_info < (3, 7):\n\n    def run(coro):\n        loop = asyncio.new_event_loop()\n        res = loop.run_until_complete(coro)\n        loop.close()\n        asyncio.set_event_loop(None)\n        return res\n\n    asyncio.run = run\n\nif sys.version_info < (3, 6):\n\n    @pytest.fixture\n    def tmp_path(tmp_path):\n        return pathlib.Path(str(tmp_path))\n\n\ntry:\n    from pytest_mypy_plugins.item import YamlTestItem\nexcept ImportError:\n    # The plugin is only available starting with Python 3.6.\n    # Additionally, it has dependencies that are not compatible with Python versions in development.\n    # In both cases, the Mypy tests will be skipped.\n    pass\nelse:\n\n    def _fix_positional_only_args(item: YamlTestItem):\n        \"\"\"Remove forward-slash marker from the expected output for Python 3.6.\"\"\"\n        for output in item.expected_output:\n            output.message = output.message.replace(\", /\", \"\")\n            # Also patch the \"severity\" attribute because there is a parsing bug in the plugin.\n            output.severity = output.severity.replace(\", /\", \"\")\n\n    def _fix_builtins_uppercase(item: YamlTestItem):\n        \"\"\"Adapt case of builtins from the expected output for Python 3.6 to 3.8.\"\"\"\n        for output in item.expected_output:\n            for builtin in [\"dict\", \"list\", \"set\", \"tuple\"]:\n                # Quite hacky, but should work for all tested cases.\n                pattern = re.compile(r\"(?<![a-zA-Z0-9.])\" + builtin + r\"\\[\")\n                replacement = builtin.capitalize() + \"[\"\n                output.message = pattern.sub(replacement, output.message)\n                output.severity = pattern.sub(replacement, output.severity)\n\n    def _add_mypy_config(item: YamlTestItem):\n        \"\"\"Add some extra options to the mypy configuration for Python 3.7+.\"\"\"\n        item.additional_mypy_config += \"\\n\".join(\n            [\n                \"show_error_codes = false\",\n                \"force_union_syntax = true\",\n            ]\n        )\n\n    def pytest_collection_modifyitems(config, items):\n        \"\"\"Modify the tests to ensure they produce the same output regardless of Python version.\"\"\"\n        for item in items:\n            if not isinstance(item, YamlTestItem):\n                continue\n\n            if sys.version_info >= (3, 7):\n                _add_mypy_config(item)\n            else:\n                _fix_positional_only_args(item)\n\n            if sys.version_info < (3, 9):\n                _fix_builtins_uppercase(item)\n\n\n@contextlib.contextmanager\ndef new_event_loop_context():\n    loop = asyncio.new_event_loop()\n    try:\n        yield loop\n    finally:\n        loop.close()\n\n\n@contextlib.contextmanager\ndef set_event_loop_context(loop):\n    asyncio.set_event_loop(loop)\n    try:\n        yield\n    finally:\n        asyncio.set_event_loop(None)\n\n\ndef parse(text, *, strip=False, strict=True):\n    parser = loguru._colorizer.AnsiParser()\n    parser.feed(text)\n    tokens = parser.done(strict=strict)\n\n    if strip:\n        return parser.strip(tokens)\n    return parser.colorize(tokens, \"\")\n\n\ndef check_dir(dir, *, files=None, size=None):\n    actual_files = set(dir.iterdir())\n    seen = set()\n    if size is not None:\n        assert len(actual_files) == size\n    if files is not None:\n        assert len(actual_files) == len(files)\n        for name, content in files:\n            filepath = dir / name\n            assert filepath in actual_files\n            assert filepath not in seen\n            if content is not None:\n                assert filepath.read_text() == content\n            seen.add(filepath)\n\n\nclass StubStream(io.StringIO):\n    def fileno(self):\n        return 1\n\n\nclass StreamIsattyTrue(StubStream):\n    def isatty(self):\n        return True\n\n\nclass StreamIsattyFalse(StubStream):\n    def isatty(self):\n        return False\n\n\nclass StreamIsattyException(StubStream):\n    def isatty(self):\n        raise RuntimeError\n\n\nclass StreamFilenoException(StreamIsattyTrue):\n    def fileno(self):\n        raise RuntimeError\n\n\n@contextlib.contextmanager\ndef default_threading_excepthook():\n    if not hasattr(threading, \"excepthook\"):\n        yield\n        return\n\n    # Pytest added \"PytestUnhandledThreadExceptionWarning\", we need to\n    # remove it temporarily for some tests checking exceptions in threads.\n\n    def excepthook(args):\n        print(\"Exception in thread:\", file=sys.stderr, flush=True)\n        traceback.print_exception(\n            args.exc_type, args.exc_value, args.exc_traceback, file=sys.stderr\n        )\n\n    old_excepthook = threading.excepthook\n    threading.excepthook = excepthook\n    yield\n    threading.excepthook = old_excepthook\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef check_env_variables():\n    for var in os.environ:\n        if var.startswith(\"LOGURU_\"):\n            warnings.warn(\n                \"A Loguru environment variable has been detected \"\n                \"and may interfere with the tests: '%s'\" % var,\n                RuntimeWarning,\n                stacklevel=1,\n            )\n\n\n@pytest.fixture(autouse=True)\ndef reset_logger():\n    def reset():\n        loguru.logger.remove()\n        loguru.logger.__init__(\n            loguru._logger.Core(), None, 0, False, False, False, False, True, [], {}\n        )\n        loguru._logger.context.set({})\n\n    reset()\n    yield\n    reset()\n\n\n@pytest.fixture\ndef writer():\n    def w(message):\n        w.written.append(message)\n\n    w.written = []\n    w.read = lambda: \"\".join(w.written)\n    w.clear = lambda: w.written.clear()\n\n    return w\n\n\n@pytest.fixture\ndef sink_with_logger():\n    class SinkWithLogger:\n        def __init__(self, logger):\n            self.logger = logger\n            self.out = \"\"\n\n        def write(self, message):\n            self.logger.info(message)\n            self.out += message\n\n    return SinkWithLogger\n\n\n@pytest.fixture\ndef freeze_time(monkeypatch):\n    ctimes = {}\n    freezegun_localtime = freezegun.api.fake_localtime\n    builtins_open = builtins.open\n\n    fakes = {\n        \"zone\": \"UTC\",\n        \"offset\": 0,\n        \"include_tm_zone\": True,\n        \"tm_gmtoff_override\": None,\n    }\n\n    def fake_localtime(t=None):\n        fix_struct = os.name == \"nt\" and sys.version_info < (3, 6)\n\n        struct_time_attributes = [\n            (\"tm_year\", int),\n            (\"tm_mon\", int),\n            (\"tm_mday\", int),\n            (\"tm_hour\", int),\n            (\"tm_min\", int),\n            (\"tm_sec\", int),\n            (\"tm_wday\", int),\n            (\"tm_yday\", int),\n            (\"tm_isdst\", int),\n            (\"tm_zone\", str),\n            (\"tm_gmtoff\", int),\n        ]\n\n        if not fakes[\"include_tm_zone\"]:\n            struct_time_attributes = struct_time_attributes[:-2]\n            struct_time = NamedTuple(\"struct_time\", struct_time_attributes)._make\n        elif fix_struct:\n            struct_time = NamedTuple(\"struct_time\", struct_time_attributes)._make\n        else:\n            struct_time = time.struct_time\n\n        struct = freezegun_localtime(t)\n        override = {\"tm_zone\": fakes[\"zone\"], \"tm_gmtoff\": fakes[\"offset\"]}\n        attributes = []\n\n        if fakes[\"tm_gmtoff_override\"] is not None:\n            override[\"tm_gmtoff\"] = fakes[\"tm_gmtoff_override\"]\n\n        for attribute, _ in struct_time_attributes:\n            if attribute in override:\n                value = override[attribute]\n            else:\n                value = getattr(struct, attribute)\n            attributes.append(value)\n\n        return struct_time(attributes)\n\n    def patched_open(filepath, *args, **kwargs):\n        if not os.path.exists(filepath):\n            tz = datetime.timezone(datetime.timedelta(seconds=fakes[\"offset\"]), name=fakes[\"zone\"])\n            ctimes[filepath] = datetime.datetime.now().replace(tzinfo=tz).timestamp()\n        return builtins_open(filepath, *args, **kwargs)\n\n    @contextlib.contextmanager\n    def freeze_time(date, timezone=(\"UTC\", 0), *, include_tm_zone=True, tm_gmtoff_override=None):\n        # Freezegun does not behave very well with UTC and timezones, see spulec/freezegun#348.\n        # In particular, \"now(tz=utc)\" does not return the converted datetime.\n        # For this reason, we re-implement date parsing here to properly handle aware date using\n        # the optional \"tz_offset\" argument.\n        if isinstance(date, str):\n            for accepted_format in [\"%Y-%m-%d\", \"%Y-%m-%d %H:%M:%S\", \"%Y-%m-%d %H:%M:%S.%f\"]:\n                try:\n                    date = datetime.datetime.strptime(date, accepted_format)\n                    break\n                except ValueError:\n                    pass\n\n        if not isinstance(date, datetime.datetime) or date.tzinfo is not None:\n            raise ValueError(\"Unsupported date provided\")\n\n        zone, offset = timezone\n        tz_offset = datetime.timedelta(seconds=offset)\n        tzinfo = datetime.timezone(tz_offset, zone)\n        date = date.replace(tzinfo=tzinfo)\n\n        with monkeypatch.context() as context:\n            context.setitem(fakes, \"zone\", zone)\n            context.setitem(fakes, \"offset\", offset)\n            context.setitem(fakes, \"include_tm_zone\", include_tm_zone)\n            context.setitem(fakes, \"tm_gmtoff_override\", tm_gmtoff_override)\n\n            context.setattr(loguru._file_sink, \"get_ctime\", ctimes.__getitem__)\n            context.setattr(loguru._file_sink, \"set_ctime\", ctimes.__setitem__)\n            context.setattr(builtins, \"open\", patched_open)\n\n            # Freezegun does not permit to override timezone name.\n            context.setattr(freezegun.api, \"fake_localtime\", fake_localtime)\n\n            with freezegun.freeze_time(date, tz_offset=tz_offset) as frozen:\n                yield frozen\n\n    return freeze_time\n\n\n@contextlib.contextmanager\ndef make_logging_logger(name, handler, fmt=\"%(message)s\", level=\"DEBUG\"):\n    original_logging_level = logging.getLogger().getEffectiveLevel()\n    logging_logger = logging.getLogger(name)\n    logging_logger.setLevel(level)\n    formatter = logging.Formatter(fmt)\n\n    handler.setLevel(level)\n    handler.setFormatter(formatter)\n    logging_logger.addHandler(handler)\n\n    try:\n        yield logging_logger\n    finally:\n        logging_logger.setLevel(original_logging_level)\n        logging_logger.removeHandler(handler)\n\n\ndef _simulate_f_globals_name_absent(monkeypatch):\n    \"\"\"Simulate execution in Dask environment, where \"__name__\" is not available in globals.\"\"\"\n    getframe_ = loguru._get_frame.load_get_frame_function()\n\n    def patched_getframe(*args, **kwargs):\n        frame = getframe_(*args, **kwargs)\n        frame.f_globals.pop(\"__name__\", None)\n        return frame\n\n    with monkeypatch.context() as context:\n        context.setattr(loguru._logger, \"get_frame\", patched_getframe)\n        yield\n\n\ndef _simulate_no_frame_available(monkeypatch):\n    \"\"\"Simulate execution in Cython, where there is no stack frame to retrieve.\"\"\"\n\n    def patched_getframe(*args, **kwargs):\n        raise ValueError(\"Call stack is not deep enough (dummy)\")\n\n    with monkeypatch.context() as context:\n        context.setattr(loguru._logger, \"get_frame\", patched_getframe)\n        yield\n\n\n@pytest.fixture(params=[_simulate_f_globals_name_absent, _simulate_no_frame_available])\ndef incomplete_frame_context(request, monkeypatch):\n    \"\"\"Simulate different scenarios where the stack frame is incomplete or entirely absent.\"\"\"\n    yield from request.param(monkeypatch)\n\n\n@pytest.fixture\ndef missing_frame_lineno_value(monkeypatch):\n    \"\"\"Simulate corner case where the \"f_lineno\" value is not available in stack frames.\"\"\"\n    getframe_ = loguru._get_frame.load_get_frame_function()\n\n    class MockedFrame:\n        def __init__(self, frame):\n            self._frame = frame\n\n        def __getattribute__(self, name):\n            if name == \"f_lineno\":\n                return None\n            frame = object.__getattribute__(self, \"_frame\")\n            return getattr(frame, name)\n\n    def patched_getframe(*args, **kwargs):\n        frame = getframe_(*args, **kwargs)\n        return MockedFrame(frame)\n\n    with monkeypatch.context() as context:\n        context.setattr(loguru._logger, \"get_frame\", patched_getframe)\n        yield\n\n\n@pytest.fixture(autouse=True)\ndef reset_multiprocessing_start_method():\n    multiprocessing.set_start_method(None, force=True)\n    yield\n    multiprocessing.set_start_method(None, force=True)\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/chained_expression_direct.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 12, in a_decorated\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 40, in <module>\n    b_decorator()\n> File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 25, in b_decorator\n    a_decorated()\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 14, in a_decorated\n    raise ValueError(\"NOK\")\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 19, in a_not_decorated\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 41, in <module>\n    b_context_manager()\n> File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 30, in b_context_manager\n    a_not_decorated()\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 21, in a_not_decorated\n    raise ValueError(\"NOK\")\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 19, in a_not_decorated\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 42, in <module>\n    b_explicit()\n> File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 35, in b_explicit\n    a_not_decorated()\n  File \"tests/exceptions/source/backtrace/chained_expression_direct.py\", line 21, in a_not_decorated\n    raise ValueError(\"NOK\")\nValueError: NOK\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/chained_expression_indirect.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 11, in a\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 21, in <module>\n    b()\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 18, in b\n    a()\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 13, in a\n    raise ValueError(\"NOK\")\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 11, in a\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 24, in <module>\n    a()\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 13, in a\n    raise ValueError(\"NOK\")\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 11, in a\n    1 / 0\nZeroDivisionError: division by zero\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 27, in <module>\n    a()\n  File \"tests/exceptions/source/backtrace/chained_expression_indirect.py\", line 13, in a\n    raise ValueError(\"NOK\")\nValueError: NOK\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/chaining_first.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chaining_first.py\", line 26, in <module>\n    a_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 11, in a_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 19, in b\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 23, in c\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chaining_first.py\", line 30, in <module>\n    a_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 15, in a_not_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 19, in b\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 23, in c\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/chaining_first.py\", line 33, in <module>\n    a_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 15, in a_not_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 19, in b\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_first.py\", line 23, in c\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/chaining_second.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 38, in <module>\n    a_decorator()\n> File \"tests/exceptions/source/backtrace/chaining_second.py\", line 10, in a_decorator\n    b_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 27, in b_decorated\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 35, in c\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 39, in <module>\n    a_context_manager()\n> File \"tests/exceptions/source/backtrace/chaining_second.py\", line 15, in a_context_manager\n    b_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 31, in b_not_decorated\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 35, in c\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 40, in <module>\n    a_explicit()\n> File \"tests/exceptions/source/backtrace/chaining_second.py\", line 20, in a_explicit\n    b_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 31, in b_not_decorated\n    c()\n  File \"tests/exceptions/source/backtrace/chaining_second.py\", line 35, in c\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/chaining_third.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 46, in <module>\n    a_decorator()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 10, in a_decorator\n    b_decorator()\n> File \"tests/exceptions/source/backtrace/chaining_third.py\", line 22, in b_decorator\n    c_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 39, in c_decorated\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 47, in <module>\n    a_context_manager()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 14, in a_context_manager\n    b_context_manager()\n> File \"tests/exceptions/source/backtrace/chaining_third.py\", line 27, in b_context_manager\n    c_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 43, in c_not_decorated\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 48, in <module>\n    a_explicit()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 18, in a_explicit\n    b_explicit()\n> File \"tests/exceptions/source/backtrace/chaining_third.py\", line 32, in b_explicit\n    c_not_decorated()\n  File \"tests/exceptions/source/backtrace/chaining_third.py\", line 43, in c_not_decorated\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/enqueue.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/enqueue.py\", line 9, in <module>\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/enqueue_with_others_handlers.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/enqueue_with_others_handlers.py\", line 26, in <module>\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/frame_values_backward.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/frame_values_backward.py\", line 24, in <module>\n    c(k)\n  File \"tests/exceptions/source/backtrace/frame_values_backward.py\", line 21, in c\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/frame_values_backward.py\", line 17, in b\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/frame_values_backward.py\", line 13, in a\n    1 / n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/frame_values_forward.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/frame_values_forward.py\", line 24, in <module>\n    c(k)\n  File \"tests/exceptions/source/backtrace/frame_values_forward.py\", line 21, in c\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/frame_values_forward.py\", line 16, in b\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/frame_values_forward.py\", line 12, in a\n    1 / n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/function.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/function.py\", line 22, in <module>\n    a()\n  File \"tests/exceptions/source/backtrace/function.py\", line 11, in a\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/function.py\", line 25, in <module>\n    b()\n  File \"tests/exceptions/source/backtrace/function.py\", line 15, in b\n    2 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/function.py\", line 28, in <module>\n    c()\n  File \"tests/exceptions/source/backtrace/function.py\", line 19, in c\n    3 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/head_recursion.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 32, in <module>\n    a(1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 13, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 33, in <module>\n    a(2)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 13, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 34, in <module>\n    a(3)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 13, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 36, in <module>\n    b(1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 20, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 37, in <module>\n    b(2)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 20, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 38, in <module>\n    b(3)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 20, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 40, in <module>\n    c(1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 29, in c\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 41, in <module>\n    c(2)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 29, in c\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 42, in <module>\n    c(3)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/head_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/head_recursion.py\", line 29, in c\n    1 / n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/missing_attributes_traceback_objects.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/missing_attributes_traceback_objects.py\", line 38, in <module>\n    foo()\n  File \"tests/exceptions/source/backtrace/missing_attributes_traceback_objects.py\", line 18, in foo\n    div(a, b)\n  File \"tests/exceptions/source/backtrace/missing_attributes_traceback_objects.py\", line 14, in div\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/missing_lineno_frame_objects.txt",
    "content": "44: An error occurred\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/missing_lineno_frame_objects.py\", line 0, in <module>\n  File \"tests/exceptions/source/backtrace/missing_lineno_frame_objects.py\", line 0, in b\n  File \"tests/exceptions/source/backtrace/missing_lineno_frame_objects.py\", line 0, in a\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/nested.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested.py\", line 17, in <module>\n    a(0)\n> File \"tests/exceptions/source/backtrace/nested.py\", line 14, in a\n    nested(x)\n  File \"tests/exceptions/source/backtrace/nested.py\", line 12, in nested\n    1 / i\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested.py\", line 28, in <module>\n    b(0)\n> File \"tests/exceptions/source/backtrace/nested.py\", line 25, in b\n    nested(x)\n  File \"tests/exceptions/source/backtrace/nested.py\", line 22, in nested\n    1 / i\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested.py\", line 41, in <module>\n    c(0)\n> File \"tests/exceptions/source/backtrace/nested.py\", line 36, in c\n    nested(x)\n  File \"tests/exceptions/source/backtrace/nested.py\", line 33, in nested\n    1 / i\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/nested_chained_catch_up.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 22, in main\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 11, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 16, in bar\n    1 / 0\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 27, in <module>\n    main()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 24, in main\n    raise ZeroDivisionError from e\nZeroDivisionError\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 22, in main\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 11, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 16, in bar\n    1 / 0\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 27, in <module>\n    main()\n  File \"tests/exceptions/source/backtrace/nested_chained_catch_up.py\", line 24, in main\n    raise ZeroDivisionError from e\nZeroDivisionError\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/nested_decorator_catch_up.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 20, in <module>\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 12, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 17, in bar\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 20, in <module>\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 12, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_decorator_catch_up.py\", line 17, in bar\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/nested_explicit_catch_up.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 20, in <module>\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 11, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 16, in bar\n    1 / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 20, in <module>\n    foo()\n  File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 11, in foo\n    bar()\n  File \"tests/exceptions/source/backtrace/nested_explicit_catch_up.py\", line 16, in bar\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/nested_wrapping.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 19, in <module>\n    a(0)\n  File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 16, in a\n    f(x)\n  File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 10, in f\n    1 / i\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 24, in <module>\n    f(0)\n  File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 10, in f\n    1 / i\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 29, in <module>\n    f(0)\n  File \"tests/exceptions/source/backtrace/nested_wrapping.py\", line 10, in f\n    1 / i\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/no_tb.txt",
    "content": "Test:\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/not_enough_arguments.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/not_enough_arguments.py\", line 18, in <module>\n    decorated(1)\nTypeError: decorated() missing 2 required positional arguments: 'y' and 'z'\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/not_enough_arguments.py\", line 21, in <module>\n    not_decorated(2)\nTypeError: not_decorated() missing 2 required positional arguments: 'y' and 'z'\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/not_enough_arguments.py\", line 24, in <module>\n    not_decorated(3)\nTypeError: not_decorated() missing 2 required positional arguments: 'y' and 'z'\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/raising_recursion.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 32, in <module>\n    a(1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 32, in <module>\n    a(1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 33, in <module>\n    a(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 33, in <module>\n    a(2)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 33, in <module>\n    a(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 34, in <module>\n    a(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 34, in <module>\n    a(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 34, in <module>\n    a(3)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 34, in <module>\n    a(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 13, in a\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 36, in <module>\n    b(1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 36, in <module>\n    b(1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 37, in <module>\n    b(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 37, in <module>\n    b(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 37, in <module>\n    b(2)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 38, in <module>\n    b(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 38, in <module>\n    b(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 38, in <module>\n    b(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 19, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 38, in <module>\n    b(3)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 20, in b\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 40, in <module>\n    c(1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 40, in <module>\n    c(1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 41, in <module>\n    c(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 41, in <module>\n    c(2)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 41, in <module>\n    c(2)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 42, in <module>\n    c(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 42, in <module>\n    c(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 42, in <module>\n    c(3)\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 26, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 42, in <module>\n    c(3)\n> File \"tests/exceptions/source/backtrace/raising_recursion.py\", line 27, in c\n    n / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/suppressed_expression_direct.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 16, in b_decorated\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 44, in <module>\n    c_decorator()\n> File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 29, in c_decorator\n    b_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 18, in b_decorated\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 23, in b_not_decorated\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 45, in <module>\n    c_context_manager()\n> File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 34, in c_context_manager\n    b_not_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 25, in b_not_decorated\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 23, in b_not_decorated\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 46, in <module>\n    c_explicit()\n> File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 39, in c_explicit\n    b_not_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_direct.py\", line 25, in b_not_decorated\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/suppressed_expression_indirect.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 15, in b\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 29, in <module>\n    c_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 22, in c_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 17, in b\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 15, in b\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 32, in <module>\n    c_not_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 26, in c_not_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 17, in b\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 15, in b\n    a(1, 0)\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 10, in a\n    x / y\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 35, in <module>\n    c_not_decorated()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 26, in c_not_decorated\n    b()\n  File \"tests/exceptions/source/backtrace/suppressed_expression_indirect.py\", line 17, in b\n    raise ValueError(\"NOK\") from e\nValueError: NOK\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/tail_recursion.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 29, in <module>\n    a(1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 11, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 30, in <module>\n    a(2)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 11, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 31, in <module>\n    a(3)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 12, in a\n    a(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 11, in a\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 33, in <module>\n    b(1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 16, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 34, in <module>\n    b(2)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 16, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 35, in <module>\n    b(3)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 18, in b\n    b(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 16, in b\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 37, in <module>\n    c(1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 22, in c\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 38, in <module>\n    c(2)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 22, in c\n    1 / n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 39, in <module>\n    c(3)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n> File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 24, in c\n    c(n - 1)\n  File \"tests/exceptions/source/backtrace/tail_recursion.py\", line 22, in c\n    1 / n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/backtrace/too_many_arguments.txt",
    "content": "\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/too_many_arguments.py\", line 18, in <module>\n    decorated(1)\nTypeError: decorated() takes 0 positional arguments but 1 was given\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/too_many_arguments.py\", line 21, in <module>\n    not_decorated(2)\nTypeError: not_decorated() takes 0 positional arguments but 1 was given\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/backtrace/too_many_arguments.py\", line 24, in <module>\n    not_decorated(3)\nTypeError: not_decorated() takes 0 positional arguments but 1 was given\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/assertion_error.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m9\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m55\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1mabc\u001b[0m \u001b[35m\u001b[1m>\u001b[0m \u001b[34m\u001b[1m10\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1mxyz\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m60\u001b[0m\n    \u001b[36m       │            └ \u001b[0m\u001b[36m\u001b[1m55\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m9\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m: \u001b[35m\u001b[1massert\u001b[0m \u001b[1mabc\u001b[0m \u001b[35m\u001b[1m>\u001b[0m \u001b[34m\u001b[1m10\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1mxyz\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m60\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/assertion_error_custom.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error_custom.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m9\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m55\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error_custom.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1mabc\u001b[0m \u001b[35m\u001b[1m>\u001b[0m \u001b[34m\u001b[1m10\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1mxyz\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m60\u001b[0m\u001b[1m,\u001b[0m \u001b[36m\"Foo assertion failed\"\u001b[0m\n    \u001b[36m       │            └ \u001b[0m\u001b[36m\u001b[1m55\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m9\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m:\u001b[1m Foo assertion failed\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/assertion_error_in_string.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error_in_string.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m9\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m55\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1massertion_error_in_string.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"assert abc > 10 and xyz == 60\"\u001b[0m\u001b[1m)\u001b[0m\n\n  File \"<string>\", line 1, in <module>\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/attributes.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mattributes.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mattributes.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1m...\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mbar\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m)\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mb\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1ma\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mforbidden\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1ma\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mnope\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1ma\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mx\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1m__bool__\u001b[0m \u001b[35m\u001b[1mor\u001b[0m \u001b[1ma\u001b[0m\u001b[35m\u001b[1m.\u001b[0m \u001b[1mb\u001b[0m \u001b[35m\u001b[1m.\u001b[0m \u001b[1misdigit\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[34m\u001b[1m.3\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[35m\u001b[1m...\u001b[0m\n    \u001b[36m              │      │ │           │          │ │           │  │   └ \u001b[0m\u001b[36m\u001b[1m<method 'isdigit' of 'str' objects>\u001b[0m\n    \u001b[36m              │      │ │           │          │ │           │  └ \u001b[0m\u001b[36m\u001b[1m'123'\u001b[0m\n    \u001b[36m              │      │ │           │          │ │           └ \u001b[0m\u001b[36m\u001b[1m<__main__.Obj object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m              │      │ │           │          │ └ \u001b[0m\u001b[36m\u001b[1m<slot wrapper '__bool__' of 'NoneType' objects>\u001b[0m\n    \u001b[36m              │      │ │           │          └ \u001b[0m\u001b[36m\u001b[1mNone\u001b[0m\n    \u001b[36m              │      │ │           └ \u001b[0m\u001b[36m\u001b[1m<__main__.Obj object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m              │      │ └ \u001b[0m\u001b[36m\u001b[1m<property object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m              │      └ \u001b[0m\u001b[36m\u001b[1m<__main__.Obj object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m              └ \u001b[0m\u001b[36m\u001b[1m<__main__.Obj object at 0xDEADBEEF>\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m unsupported operand type(s) for +: 'ellipsis' and 'int'\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/chained_both.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mcause\u001b[0m\n    \u001b[1mdiv\u001b[0m\u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[1m,\u001b[0m \u001b[1my\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│   │  └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function div at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mdiv\u001b[0m\n    \u001b[1mx\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1my\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\n\u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mcontext\u001b[0m\n    \u001b[1mcause\u001b[0m\u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[1m,\u001b[0m \u001b[1my\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│     │  └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m│     └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function cause at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mcause\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Division error\"\u001b[0m\u001b[1m)\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Division error\u001b[0m\n\n\n\u001b[1mThe above exception was the direct cause of the following exception:\u001b[0m\n\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m28\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mcontext\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function context at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mchained_both.py\u001b[0m\", line \u001b[33m24\u001b[0m, in \u001b[35mcontext\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Cause error\"\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[1me\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Cause error\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/encoding.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mencoding.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mdiv\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function div at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mencoding.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mdiv\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[1m_deep\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"天\"\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m<function _deep at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mencoding.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35m_deep\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mval\u001b[0m\n    \u001b[36m           └ \u001b[0m\u001b[36m\u001b[1m'天'\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m unsupported operand type(s) for /: 'int' and 'str'\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/global_variable.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mglobal_variable.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mfunc\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function func at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mglobal_variable.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfunc\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mfoo\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mbar\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36m\u001b[1mFalse\u001b[0m\n    \u001b[36m               │     └ \u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\n    \u001b[36m               └ \u001b[0m\u001b[36m\u001b[1mNone\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/indentation_error.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mindentation_error.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcode\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m     └ \u001b[0m\u001b[36m\u001b[1m'\\nif True:\\n    a = 5\\n        print(\"foobar\")  #intentional faulty indentation here.\\n    b = 7\\n'\u001b[0m\n\n  File \"<string>\", line 4\n    print(\"foobar\")  #intentional faulty indentation here.\n\n\u001b[31m\u001b[1mIndentationError\u001b[0m:\u001b[1m unexpected indent\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/keyword_argument.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mkeyword_argument.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mf\u001b[0m\u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1my\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function f at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mkeyword_argument.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mf\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mx\u001b[0m\n    \u001b[36m           └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mkeyword_argument.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mf\u001b[0m\u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mx\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function f at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mkeyword_argument.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mf\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mx\u001b[0m\n    \u001b[36m           └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/multilines_repr.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mmultilines_repr.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mmultiline\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function multiline at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mmultilines_repr.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mmultiline\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m[[1, 2, 3]\u001b[0m\n    \u001b[36m│     \u001b[0m\u001b[36m\u001b[1m [4, 5, 6]\u001b[0m\n    \u001b[36m│     \u001b[0m\u001b[36m\u001b[1m [7, 8, 9]]\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m[[1, 2, 3]\u001b[0m\n    \u001b[36m  \u001b[0m\u001b[36m\u001b[1m [4, 5, 6]\u001b[0m\n    \u001b[36m  \u001b[0m\u001b[36m\u001b[1m [7, 8, 9]]\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m unsupported operand type(s) for +: 'A' and 'A'\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/no_error_message.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mno_error_message.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mbar\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function bar at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mno_error_message.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mbar\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mno_error_message.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"\"\u001b[0m\u001b[1m)\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/parenthesis.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m47\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1me\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function e at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m43\u001b[0m, in \u001b[35me\u001b[0m\n    \u001b[1m)\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1md\u001b[0m\u001b[1m(\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1ma\u001b[0m\n    \u001b[36m    │       └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m    └ \u001b[0m\u001b[36m\u001b[1m<function d at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m36\u001b[0m, in \u001b[35md\u001b[0m\n    \u001b[1m;\u001b[0m \u001b[1mz\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[1m(\u001b[0m\u001b[1mx\u001b[0m \u001b[35m\u001b[1m*\u001b[0m \u001b[1my\u001b[0m\u001b[1m)\u001b[0m\u001b[1m;\u001b[0m \u001b[1my\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[1m(\u001b[0m\u001b[1mj\u001b[0m \u001b[35m\u001b[1mor\u001b[0m \u001b[1mxyz\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mval\u001b[0m \u001b[35m\u001b[1m*\u001b[0m \u001b[1mc\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \\\n    \u001b[36m  │    │   │   │    │    │   │     └ \u001b[0m\u001b[36m\u001b[1m<function c at 0xDEADBEEF>\u001b[0m\n    \u001b[36m  │    │   │   │    │    │   └ \u001b[0m\u001b[36m\u001b[1m123\u001b[0m\n    \u001b[36m  │    │   │   │    │    └ \u001b[0m\u001b[36m\u001b[1m<__main__.XYZ object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m  │    │   │   │    └ \u001b[0m\u001b[36m\u001b[1m()\u001b[0m\n    \u001b[36m  │    │   │   └ \u001b[0m\u001b[36m\u001b[1m5\u001b[0m\n    \u001b[36m  │    │   └ \u001b[0m\u001b[36m\u001b[1m5\u001b[0m\n    \u001b[36m  │    └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m  └ \u001b[0m\u001b[36m\u001b[1m10\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m28\u001b[0m, in \u001b[35mc\u001b[0m\n    \u001b[1mx\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mval\u001b[0m \u001b[35m\u001b[1m+=\u001b[0m \u001b[34m\u001b[1m456\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│ │              └ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n    \u001b[36m│ └ \u001b[0m\u001b[36m\u001b[1m123\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<__main__.XYZ object at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mb\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m[\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"baz\"\u001b[0m\u001b[1m)\u001b[0m\u001b[1m]\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[1mbar\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m5\u001b[0m\u001b[1m,\u001b[0m \u001b[1mbaz\u001b[0m\u001b[1m)\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│              │        │    └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m│              │        └ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    \u001b[36m│              └ \u001b[0m\u001b[36m\u001b[1m<class '__main__.XYZ'>\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m{}\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mparenthesis.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35ma\u001b[0m\n    \u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m,\u001b[0m \u001b[1mx\u001b[0m\u001b[35m\u001b[1m.\u001b[0m\u001b[1mval\u001b[0m\u001b[1m,\u001b[0m \u001b[1m)\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[34m\u001b[1m12\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m15\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mc\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m17\u001b[0m\n    \u001b[36m │  │  │ │                 └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m │  │  │ └ \u001b[0m\u001b[36m\u001b[1m9\u001b[0m\n    \u001b[36m │  │  └ \u001b[0m\u001b[36m\u001b[1m<__main__.XYZ object at 0xDEADBEEF>\u001b[0m\n    \u001b[36m │  └ \u001b[0m\u001b[36m\u001b[1m5\u001b[0m\n    \u001b[36m └ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/source_multilines.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m39\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mbug_1\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function bug_1 at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m12\u001b[0m, in \u001b[35mbug_1\u001b[0m\n    \"\"\" + n / 0)\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m45\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mbug_2\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1mstring\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m3\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│        └ \u001b[0m\u001b[36m\u001b[1m'multi-lines\\n'\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function bug_2 at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mbug_2\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mb\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \\\n    \u001b[36m                │   └ \u001b[0m\u001b[36m\u001b[1m'multi-lines\\n'\u001b[0m\n    \u001b[36m                └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m51\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mbug_3\u001b[0m\u001b[1m(\u001b[0m\u001b[1mstring\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│     └ \u001b[0m\u001b[36m\u001b[1m'multi-lines\\n'\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function bug_3 at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mbug_3\u001b[0m\n    \u001b[1m,\u001b[0m \u001b[1mstring\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m20\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m  └ \u001b[0m\u001b[36m\u001b[1m'multi-lines\\n'\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m57\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mbug_4\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function bug_4 at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_multilines.py\u001b[0m\", line \u001b[33m29\u001b[0m, in \u001b[35mbug_4\u001b[0m\n    \u001b[36m\"bar\"\u001b[0m\u001b[1m:\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mb\u001b[0m\u001b[1m,\u001b[0m\n    \u001b[36m       │   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/source_strings.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msource_strings.py\u001b[0m\", line \u001b[33m13\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36mb\"prefix\"\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36m'single'\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36m\"\"\"triple\"\"\"\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m│                                             └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m unsupported operand type(s) for +: 'int' and 'bytes'\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/syntax_error.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_error.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcode\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m     └ \u001b[0m\u001b[36m\u001b[1m'\\nif True:\\n    a = 5\\n    b = 7 *\\n'\u001b[0m\n\n  File \"<string>\", line 4\n    b = 7 *\n           ^\n\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/syntax_highlighting.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m31\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1me\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function e at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m27\u001b[0m, in \u001b[35me\u001b[0m\n    \u001b[1mx\u001b[0m \u001b[35m\u001b[1min\u001b[0m \u001b[1m[\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m]\u001b[0m\u001b[1m,\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1min\u001b[0m \u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m\u001b[1m)\u001b[0m\u001b[1m,\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1min\u001b[0m \u001b[1m{\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m}\u001b[0m\u001b[1m,\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1min\u001b[0m \u001b[1m{\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m}\u001b[0m\u001b[1m,\u001b[0m \u001b[1md\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│         │          │         │            └ \u001b[0m\u001b[36m\u001b[1m<function d at 0xDEADBEEF>\u001b[0m\n    \u001b[36m│         │          │         └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m│         │          └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m│         └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m23\u001b[0m, in \u001b[35md\u001b[0m\n    \u001b[1mmin\u001b[0m\u001b[1m(\u001b[0m\u001b[1mrange\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\u001b[1m)\u001b[0m\u001b[1m,\u001b[0m \u001b[1mlist\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdict\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\u001b[1m,\u001b[0m \u001b[1mc\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\u001b[1m,\u001b[0m \u001b[35m\u001b[1m...\u001b[0m\n    \u001b[36m                                   └ \u001b[0m\u001b[36m\u001b[1m<function c at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35mc\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m2.5\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m3.0\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0.4\u001b[0m\u001b[1m,\u001b[0m \u001b[36m\"str\"\u001b[0m\u001b[1m,\u001b[0m \u001b[36mr\"rrr\"\u001b[0m\u001b[1m,\u001b[0m \u001b[36mrb\"binary\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m                                             └ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mb\u001b[0m\n    \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mor\u001b[0m \u001b[36m\u001b[1mFalse\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[36m\u001b[1mNone\u001b[0m \u001b[35m\u001b[1m!=\u001b[0m \u001b[36m\u001b[1mTrue\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1msyntax_highlighting.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35ma\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m*\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m%\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m//\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[35m\u001b[1m**\u001b[0m\u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1m@\u001b[0m \u001b[34m\u001b[1m1\u001b[0m  \u001b[30m\u001b[1m# Error\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/truncating.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mtruncating.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mdiv\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function div at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1mtruncating.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mdiv\u001b[0m\n    \u001b[35m\u001b[1mreturn\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mvar\u001b[0m\n    \u001b[36m           └ \u001b[0m\u001b[36m\u001b[1m'9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999...\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m unsupported operand type(s) for /: 'int' and 'str'\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/diagnose/unprintable_object.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/diagnose/\u001b[0m\u001b[32m\u001b[1munprintable_object.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mobj\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<unprintable Object object>\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/decorate_async_generator.txt",
    "content": "Done\n"
  },
  {
    "path": "tests/exceptions/output/modern/exception_formatting_async_generator.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 20, in <module>\n    f.send(None)\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 14, in foo\n    yield a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 20, in <module>\n    f.send(None)\n    │ └ <method 'send' of 'coroutine' objects>\n    └ <coroutine object Logger.catch.<locals>.Catcher.__call__.<locals>.AsyncGenCatchWrapper.asend at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 14, in foo\n    yield a / b\n          │   └ 0\n          └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 20, in <module>\n    f.send(None)\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 14, in foo\n    yield a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 20, in <module>\n    f.send(None)\n    │ └ <method 'send' of 'coroutine' objects>\n    └ <coroutine object Logger.catch.<locals>.Catcher.__call__.<locals>.AsyncGenCatchWrapper.asend at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/modern/exception_formatting_async_generator.py\", line 14, in foo\n    yield a / b\n          │   └ 0\n          └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/modern/exception_group_catch.txt",
    "content": "\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mexception_group_catch.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35ma\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (1 sub-exception)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 1\u001b[0m\n    +------------------------------------\n\n\n\u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mexception_group_catch.py\u001b[0m\", line \u001b[33m25\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mexception_group_catch.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35mb\u001b[0m\n    \u001b[35m\u001b[1mexcept\u001b[0m\u001b[35m\u001b[1m*\u001b[0m \u001b[1mTypeError\u001b[0m\u001b[1m:\u001b[0m \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m                   └ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mexception_group_catch.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35ma\u001b[0m\n    \u001b[35m\u001b[1mexcept\u001b[0m\u001b[35m\u001b[1m*\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1mas\u001b[0m \u001b[1me\u001b[0m\u001b[1m:\u001b[0m \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m2\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m        └ \u001b[0m\u001b[36m\u001b[1m<class 'ValueError'>\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 2\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/f_string.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mf_string.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mhello\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function hello at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mf_string.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mhello\u001b[0m\n    \u001b[1moutput\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[36mf\"\u001b[0m\u001b[36mHello\u001b[0m\u001b[36m\"\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36mf'\u001b[0m\u001b[36m \u001b[0m\u001b[36m'\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[36mf\"\"\"\u001b[0m\u001b[36mWorld\u001b[0m\u001b[36m\"\"\"\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1mworld\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m                                            └ \u001b[0m\u001b[36m\u001b[1m<function world at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mf_string.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mworld\u001b[0m\n    \u001b[36mf\"\u001b[0m\u001b[1m{\u001b[0m\u001b[1mname\u001b[0m\u001b[1m}\u001b[0m\u001b[36m -> \u001b[0m\u001b[1m{\u001b[0m \u001b[1mf\u001b[0m \u001b[1m}\u001b[0m\u001b[36m\"\u001b[0m \u001b[35m\u001b[1mand\u001b[0m \u001b[1m{\u001b[0m\u001b[1m}\u001b[0m \u001b[35m\u001b[1mor\u001b[0m \u001b[36mf'\u001b[0m\u001b[36m{{\u001b[0m\u001b[36m \u001b[0m\u001b[1m{\u001b[0m\u001b[1mf\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m}\u001b[0m\u001b[36m }}\u001b[0m\u001b[36m'\u001b[0m\n    \u001b[36m   │          │                    └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m   │          └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m   └ \u001b[0m\u001b[36m\u001b[1m'world'\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_as_cause_and_context.txt",
    "content": "\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 31, in main\n  |     raise ExceptionGroup(\"group_1\", [error_1, error_2])\n  | ExceptionGroup: group_1 (2 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 20, in main\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 10, in a\n    |     1 / 0\n    | ZeroDivisionError: division by zero\n    +---------------- 2 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 25, in main\n    |     b()\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 14, in b\n    |     raise ValueError(\"Error\")\n    | ValueError: Error\n    +------------------------------------\n\nThe above exception was the direct cause of the following exception:\n\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 33, in main\n  |     raise ExceptionGroup(\"group_2\", [error_2, error_1]) from err\n  | ExceptionGroup: group_2 (2 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 25, in main\n    |     b()\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 14, in b\n    |     raise ValueError(\"Error\")\n    | ValueError: Error\n    +---------------- 2 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 20, in main\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 10, in a\n    |     1 / 0\n    | ZeroDivisionError: division by zero\n    +------------------------------------\n\nDuring handling of the above exception, another exception occurred:\n\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 42, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 35, in main\n  |     raise ExceptionGroup(\"group_3\", [err])\n  | ExceptionGroup: group_3 (1 sub-exception)\n  +-+---------------- 1 ----------------\n    | Exception Group Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 33, in main\n    |     raise ExceptionGroup(\"group_2\", [error_2, error_1]) from err\n    | ExceptionGroup: group_2 (2 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 25, in main\n      |     b()\n      |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 14, in b\n      |     raise ValueError(\"Error\")\n      | ValueError: Error\n      +---------------- 2 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 20, in main\n      |     a()\n      |   File \"tests/exceptions/source/modern/grouped_as_cause_and_context.py\", line 10, in a\n      |     1 / 0\n      | ZeroDivisionError: division by zero\n      +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m31\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_1\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_1\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_2\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m                                 │        └ \u001b[0m\u001b[36m\u001b[1mValueError('Error')\u001b[0m\n  |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mZeroDivisionError('division by zero')\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_1 (2 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m25\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mb\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Error\"\u001b[0m\u001b[1m)\u001b[0m\n    |\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Error\u001b[0m\n    +------------------------------------\n\n\n\u001b[1mThe above exception was the direct cause of the following exception:\u001b[0m\n\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m33\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_2\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_2\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_1\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[1merr\u001b[0m\n  |     \u001b[36m                                 │        └ \u001b[0m\u001b[36m\u001b[1mZeroDivisionError('division by zero')\u001b[0m\n  |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mValueError('Error')\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_2 (2 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m25\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mb\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Error\"\u001b[0m\u001b[1m)\u001b[0m\n    |\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Error\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    +------------------------------------\n\n\n\u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m42\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m35\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_3\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merr\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_3 (1 sub-exception)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m33\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_2\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_2\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_1\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[1merr\u001b[0m\n    |     \u001b[36m                                 │        └ \u001b[0m\u001b[36m\u001b[1mZeroDivisionError('division by zero')\u001b[0m\n    |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mValueError('Error')\u001b[0m\n    |\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_2 (2 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m25\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1mb\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mb\u001b[0m\n      |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Error\"\u001b[0m\u001b[1m)\u001b[0m\n      |\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Error\u001b[0m\n      +---------------- 2 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_as_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n      |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n      |\n      | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n      +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_max_depth.txt",
    "content": "\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_max_depth.py\", line 26, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_max_depth.py\", line 19, in main\n  |     raise ExceptionGroup(\"group\", [nesting_left, nesting_right, nesting_both])\n  | ExceptionGroup: group (3 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | ExceptionGroup: group (2 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | ValueError: -99\n      +---------------- 2 ----------------\n      | ExceptionGroup: group (2 sub-exceptions)\n      +-+---------------- 1 ----------------\n        | ValueError: -98\n        +---------------- 2 ----------------\n        | ExceptionGroup: group (2 sub-exceptions)\n        +-+---------------- 1 ----------------\n          | ValueError: -97\n          +---------------- 2 ----------------\n          | ExceptionGroup: group (2 sub-exceptions)\n          +-+---------------- 1 ----------------\n            | ValueError: -96\n            +---------------- 2 ----------------\n            | ExceptionGroup: group (2 sub-exceptions)\n            +-+---------------- 1 ----------------\n              | ValueError: -95\n              +---------------- 2 ----------------\n              | ExceptionGroup: group (2 sub-exceptions)\n              +-+---------------- 1 ----------------\n                | ValueError: -94\n                +---------------- 2 ----------------\n                | ExceptionGroup: group (2 sub-exceptions)\n                +-+---------------- 1 ----------------\n                  | ValueError: -93\n                  +---------------- 2 ----------------\n                  | ExceptionGroup: group (2 sub-exceptions)\n                  +-+---------------- 1 ----------------\n                    | ValueError: -92\n                    +---------------- 2 ----------------\n                    | ExceptionGroup: group (2 sub-exceptions)\n                    +-+---------------- 1 ----------------\n                      | ValueError: -91\n                      +---------------- 2 ----------------\n                      | ... (max_group_depth is 10)\n                      +------------------------------------\n    +---------------- 2 ----------------\n    | ExceptionGroup: group (2 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | ExceptionGroup: group (2 sub-exceptions)\n      +-+---------------- 1 ----------------\n        | ExceptionGroup: group (2 sub-exceptions)\n        +-+---------------- 1 ----------------\n          | ExceptionGroup: group (2 sub-exceptions)\n          +-+---------------- 1 ----------------\n            | ExceptionGroup: group (2 sub-exceptions)\n            +-+---------------- 1 ----------------\n              | ExceptionGroup: group (2 sub-exceptions)\n              +-+---------------- 1 ----------------\n                | ExceptionGroup: group (2 sub-exceptions)\n                +-+---------------- 1 ----------------\n                  | ExceptionGroup: group (2 sub-exceptions)\n                  +-+---------------- 1 ----------------\n                    | ExceptionGroup: group (2 sub-exceptions)\n                    +-+---------------- 1 ----------------\n                      | ... (max_group_depth is 10)\n                      +---------------- 2 ----------------\n                      | ValueError: 91\n                      +------------------------------------\n                    +---------------- 2 ----------------\n                    | ValueError: 92\n                    +------------------------------------\n                  +---------------- 2 ----------------\n                  | ValueError: 93\n                  +------------------------------------\n                +---------------- 2 ----------------\n                | ValueError: 94\n                +------------------------------------\n              +---------------- 2 ----------------\n              | ValueError: 95\n              +------------------------------------\n            +---------------- 2 ----------------\n            | ValueError: 96\n            +------------------------------------\n          +---------------- 2 ----------------\n          | ValueError: 97\n          +------------------------------------\n        +---------------- 2 ----------------\n        | ValueError: 98\n        +------------------------------------\n      +---------------- 2 ----------------\n      | ValueError: 99\n      +------------------------------------\n    +---------------- 3 ----------------\n    | ExceptionGroup: group (3 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | ValueError: -99\n      +---------------- 2 ----------------\n      | ExceptionGroup: group (3 sub-exceptions)\n      +-+---------------- 1 ----------------\n        | ValueError: -98\n        +---------------- 2 ----------------\n        | ExceptionGroup: group (3 sub-exceptions)\n        +-+---------------- 1 ----------------\n          | ValueError: -97\n          +---------------- 2 ----------------\n          | ExceptionGroup: group (3 sub-exceptions)\n          +-+---------------- 1 ----------------\n            | ValueError: -96\n            +---------------- 2 ----------------\n            | ExceptionGroup: group (3 sub-exceptions)\n            +-+---------------- 1 ----------------\n              | ValueError: -95\n              +---------------- 2 ----------------\n              | ExceptionGroup: group (3 sub-exceptions)\n              +-+---------------- 1 ----------------\n                | ValueError: -94\n                +---------------- 2 ----------------\n                | ExceptionGroup: group (3 sub-exceptions)\n                +-+---------------- 1 ----------------\n                  | ValueError: -93\n                  +---------------- 2 ----------------\n                  | ExceptionGroup: group (3 sub-exceptions)\n                  +-+---------------- 1 ----------------\n                    | ValueError: -92\n                    +---------------- 2 ----------------\n                    | ExceptionGroup: group (3 sub-exceptions)\n                    +-+---------------- 1 ----------------\n                      | ValueError: -91\n                      +---------------- 2 ----------------\n                      | ... (max_group_depth is 10)\n                      +---------------- 3 ----------------\n                      | ValueError: 91\n                      +------------------------------------\n                    +---------------- 3 ----------------\n                    | ValueError: 92\n                    +------------------------------------\n                  +---------------- 3 ----------------\n                  | ValueError: 93\n                  +------------------------------------\n                +---------------- 3 ----------------\n                | ValueError: 94\n                +------------------------------------\n              +---------------- 3 ----------------\n              | ValueError: 95\n              +------------------------------------\n            +---------------- 3 ----------------\n            | ValueError: 96\n            +------------------------------------\n          +---------------- 3 ----------------\n          | ValueError: 97\n          +------------------------------------\n        +---------------- 3 ----------------\n        | ValueError: 98\n        +------------------------------------\n      +---------------- 3 ----------------\n      | ValueError: 99\n      +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_max_depth.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_max_depth.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1mnesting_left\u001b[0m\u001b[1m,\u001b[0m \u001b[1mnesting_right\u001b[0m\u001b[1m,\u001b[0m \u001b[1mnesting_both\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m                               │             │              └ \u001b[0m\u001b[36m\u001b[1mExceptionGroup('group', [ValueError(-99), ExceptionGroup('group', [ValueError(-98), ExceptionGroup('group', [ValueError(-97),...\u001b[0m\n  |     \u001b[36m                               │             └ \u001b[0m\u001b[36m\u001b[1mExceptionGroup('group', [ExceptionGroup('group', [ExceptionGroup('group', [ExceptionGroup('group', [ExceptionGroup('group', [...\u001b[0m\n  |     \u001b[36m                               └ \u001b[0m\u001b[36m\u001b[1mExceptionGroup('group', [ValueError(-99), ExceptionGroup('group', [ValueError(-98), ExceptionGroup('group', [ValueError(-97),...\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -99\u001b[0m\n      +---------------- 2 ----------------\n      | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n      +-+---------------- 1 ----------------\n        | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -98\u001b[0m\n        +---------------- 2 ----------------\n        | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n        +-+---------------- 1 ----------------\n          | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -97\u001b[0m\n          +---------------- 2 ----------------\n          | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n          +-+---------------- 1 ----------------\n            | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -96\u001b[0m\n            +---------------- 2 ----------------\n            | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n            +-+---------------- 1 ----------------\n              | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -95\u001b[0m\n              +---------------- 2 ----------------\n              | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n              +-+---------------- 1 ----------------\n                | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -94\u001b[0m\n                +---------------- 2 ----------------\n                | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                +-+---------------- 1 ----------------\n                  | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -93\u001b[0m\n                  +---------------- 2 ----------------\n                  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                  +-+---------------- 1 ----------------\n                    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -92\u001b[0m\n                    +---------------- 2 ----------------\n                    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                    +-+---------------- 1 ----------------\n                      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -91\u001b[0m\n                      +---------------- 2 ----------------\n                      | ... (max_group_depth is 10)\n                      +------------------------------------\n    +---------------- 2 ----------------\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n      +-+---------------- 1 ----------------\n        | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n        +-+---------------- 1 ----------------\n          | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n          +-+---------------- 1 ----------------\n            | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n            +-+---------------- 1 ----------------\n              | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n              +-+---------------- 1 ----------------\n                | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                +-+---------------- 1 ----------------\n                  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                  +-+---------------- 1 ----------------\n                    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (2 sub-exceptions)\u001b[0m\n                    +-+---------------- 1 ----------------\n                      | ... (max_group_depth is 10)\n                      +---------------- 2 ----------------\n                      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 91\u001b[0m\n                      +------------------------------------\n                    +---------------- 2 ----------------\n                    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 92\u001b[0m\n                    +------------------------------------\n                  +---------------- 2 ----------------\n                  | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 93\u001b[0m\n                  +------------------------------------\n                +---------------- 2 ----------------\n                | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 94\u001b[0m\n                +------------------------------------\n              +---------------- 2 ----------------\n              | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 95\u001b[0m\n              +------------------------------------\n            +---------------- 2 ----------------\n            | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 96\u001b[0m\n            +------------------------------------\n          +---------------- 2 ----------------\n          | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 97\u001b[0m\n          +------------------------------------\n        +---------------- 2 ----------------\n        | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 98\u001b[0m\n        +------------------------------------\n      +---------------- 2 ----------------\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 99\u001b[0m\n      +------------------------------------\n    +---------------- 3 ----------------\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -99\u001b[0m\n      +---------------- 2 ----------------\n      | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n      +-+---------------- 1 ----------------\n        | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -98\u001b[0m\n        +---------------- 2 ----------------\n        | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n        +-+---------------- 1 ----------------\n          | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -97\u001b[0m\n          +---------------- 2 ----------------\n          | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n          +-+---------------- 1 ----------------\n            | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -96\u001b[0m\n            +---------------- 2 ----------------\n            | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n            +-+---------------- 1 ----------------\n              | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -95\u001b[0m\n              +---------------- 2 ----------------\n              | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n              +-+---------------- 1 ----------------\n                | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -94\u001b[0m\n                +---------------- 2 ----------------\n                | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n                +-+---------------- 1 ----------------\n                  | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -93\u001b[0m\n                  +---------------- 2 ----------------\n                  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n                  +-+---------------- 1 ----------------\n                    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -92\u001b[0m\n                    +---------------- 2 ----------------\n                    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n                    +-+---------------- 1 ----------------\n                      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -91\u001b[0m\n                      +---------------- 2 ----------------\n                      | ... (max_group_depth is 10)\n                      +---------------- 3 ----------------\n                      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 91\u001b[0m\n                      +------------------------------------\n                    +---------------- 3 ----------------\n                    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 92\u001b[0m\n                    +------------------------------------\n                  +---------------- 3 ----------------\n                  | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 93\u001b[0m\n                  +------------------------------------\n                +---------------- 3 ----------------\n                | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 94\u001b[0m\n                +------------------------------------\n              +---------------- 3 ----------------\n              | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 95\u001b[0m\n              +------------------------------------\n            +---------------- 3 ----------------\n            | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 96\u001b[0m\n            +------------------------------------\n          +---------------- 3 ----------------\n          | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 97\u001b[0m\n          +------------------------------------\n        +---------------- 3 ----------------\n        | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 98\u001b[0m\n        +------------------------------------\n      +---------------- 3 ----------------\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 99\u001b[0m\n      +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_max_length.txt",
    "content": "\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_max_length.py\", line 15, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_max_length.py\", line 8, in main\n  |     raise ExceptionGroup(\"group\", errors)\n  | ExceptionGroup: group (100 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | ValueError: 0\n    +---------------- 2 ----------------\n    | ValueError: 1\n    +---------------- 3 ----------------\n    | ValueError: 2\n    +---------------- 4 ----------------\n    | ValueError: 3\n    +---------------- 5 ----------------\n    | ValueError: 4\n    +---------------- 6 ----------------\n    | ValueError: 5\n    +---------------- 7 ----------------\n    | ValueError: 6\n    +---------------- 8 ----------------\n    | ValueError: 7\n    +---------------- 9 ----------------\n    | ValueError: 8\n    +---------------- 10 ---------------\n    | ValueError: 9\n    +---------------- 11 ---------------\n    | ValueError: 10\n    +---------------- 12 ---------------\n    | ValueError: 11\n    +---------------- 13 ---------------\n    | ValueError: 12\n    +---------------- 14 ---------------\n    | ValueError: 13\n    +---------------- 15 ---------------\n    | ValueError: 14\n    +--------------- ... ---------------\n    | and 85 more exceptions\n    +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_max_length.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_max_length.py\u001b[0m\", line \u001b[33m8\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1merrors\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m                              └ \u001b[0m\u001b[36m\u001b[1m[ValueError(0), ValueError(1), ValueError(2), ValueError(3), ValueError(4), ValueError(5), ValueError(6), ValueError(7), Valu...\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (100 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 0\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 1\u001b[0m\n    +---------------- 3 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 2\u001b[0m\n    +---------------- 4 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 3\u001b[0m\n    +---------------- 5 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 4\u001b[0m\n    +---------------- 6 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 5\u001b[0m\n    +---------------- 7 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 6\u001b[0m\n    +---------------- 8 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 7\u001b[0m\n    +---------------- 9 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 8\u001b[0m\n    +---------------- 10 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 9\u001b[0m\n    +---------------- 11 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 10\u001b[0m\n    +---------------- 12 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 11\u001b[0m\n    +---------------- 13 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 12\u001b[0m\n    +---------------- 14 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 13\u001b[0m\n    +---------------- 15 ---------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 14\u001b[0m\n    +--------------- ... ---------------\n    | and 85 more exceptions\n    +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_nested.txt",
    "content": "\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 40, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 33, in main\n  |     raise ExceptionGroup(\"group_2\", [error_4, error_3]) from None\n  | ExceptionGroup: group_2 (2 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | Exception Group Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 26, in main\n    |     raise ExceptionGroup(\"group_1\", [error_1, error_2])\n    | ExceptionGroup: group_1 (2 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 17, in main\n      |     divide_by_zero()\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 6, in divide_by_zero\n      |     1 / 0\n      | ZeroDivisionError: division by zero\n      +---------------- 2 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 22, in main\n      |     raise_value_error(100)\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 10, in raise_value_error\n      |     raise ValueError(value)\n      | ValueError: 100\n      +------------------------------------\n    |\n    | During handling of the above exception, another exception occurred:\n    |\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 29, in main\n    |     raise_value_error(-100)\n    |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 10, in raise_value_error\n    |     raise ValueError(value)\n    | ValueError: -100\n    +---------------- 2 ----------------\n    | Exception Group Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 26, in main\n    |     raise ExceptionGroup(\"group_1\", [error_1, error_2])\n    | ExceptionGroup: group_1 (2 sub-exceptions)\n    +-+---------------- 1 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 17, in main\n      |     divide_by_zero()\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 6, in divide_by_zero\n      |     1 / 0\n      | ZeroDivisionError: division by zero\n      +---------------- 2 ----------------\n      | Traceback (most recent call last):\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 22, in main\n      |     raise_value_error(100)\n      |   File \"tests/exceptions/source/modern/grouped_nested.py\", line 10, in raise_value_error\n      |     raise ValueError(value)\n      | ValueError: 100\n      +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m40\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m33\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_2\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_4\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_3\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[36m\u001b[1mNone\u001b[0m\n  |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mValueError(-100)\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_2 (2 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_1\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_1\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_2\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m                                 │        └ \u001b[0m\u001b[36m\u001b[1mValueError(100)\u001b[0m\n    |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mZeroDivisionError('division by zero')\u001b[0m\n    |\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_1 (2 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1mdivide_by_zero\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide_by_zero at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m6\u001b[0m, in \u001b[35mdivide_by_zero\u001b[0m\n      |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n      |\n      | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n      +---------------- 2 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1mraise_value_error\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m100\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function raise_value_error at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mraise_value_error\u001b[0m\n      |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[1mvalue\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m                 └ \u001b[0m\u001b[36m\u001b[1m100\u001b[0m\n      |\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 100\u001b[0m\n      +------------------------------------\n    |\n    |\n    | \u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n    |\n    |\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m29\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1mraise_value_error\u001b[0m\u001b[1m(\u001b[0m\u001b[35m\u001b[1m-\u001b[0m\u001b[34m\u001b[1m100\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function raise_value_error at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mraise_value_error\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[1mvalue\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m                 └ \u001b[0m\u001b[36m\u001b[1m-100\u001b[0m\n    |\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m -100\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group_1\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_1\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_2\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m                                 │        └ \u001b[0m\u001b[36m\u001b[1mValueError(100)\u001b[0m\n    |     \u001b[36m                                 └ \u001b[0m\u001b[36m\u001b[1mZeroDivisionError('division by zero')\u001b[0m\n    |\n    | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group_1 (2 sub-exceptions)\u001b[0m\n    +-+---------------- 1 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1mdivide_by_zero\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide_by_zero at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m6\u001b[0m, in \u001b[35mdivide_by_zero\u001b[0m\n      |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n      |\n      | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n      +---------------- 2 ----------------\n      | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35mmain\u001b[0m\n      |     \u001b[1mraise_value_error\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m100\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function raise_value_error at 0xDEADBEEF>\u001b[0m\n      |\n      |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_nested.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mraise_value_error\u001b[0m\n      |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[1mvalue\u001b[0m\u001b[1m)\u001b[0m\n      |     \u001b[36m                 └ \u001b[0m\u001b[36m\u001b[1m100\u001b[0m\n      |\n      | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 100\u001b[0m\n      +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_simple.txt",
    "content": "\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 41, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 34, in main\n  |     raise ExceptionGroup(\"group\", [error_1, error_2, error_3]) from None\n  | ExceptionGroup: group (3 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 26, in main\n    |     c(b)\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 20, in c\n    |     f()\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 16, in b\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 12, in a\n    |     x / y\n    | ZeroDivisionError: division by zero\n    +---------------- 2 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 29, in main\n    |     c(a)\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 20, in c\n    |     f()\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 12, in a\n    |     x / y\n    | ZeroDivisionError: division by zero\n    +---------------- 3 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 32, in main\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_simple.py\", line 12, in a\n    |     x / y\n    | ZeroDivisionError: division by zero\n    +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m41\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m34\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"group\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1merror_1\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_2\u001b[0m\u001b[1m,\u001b[0m \u001b[1merror_3\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[36m\u001b[1mNone\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m group (3 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1mc\u001b[0m\u001b[1m(\u001b[0m\u001b[1mb\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m│ └ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function c at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35mc\u001b[0m\n    |     \u001b[1mf\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function b at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mb\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m12\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[1mx\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1my\u001b[0m\n    |     \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m29\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1mc\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m│ └ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function c at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35mc\u001b[0m\n    |     \u001b[1mf\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m12\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[1mx\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1my\u001b[0m\n    |     \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    +---------------- 3 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m32\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_simple.py\u001b[0m\", line \u001b[33m12\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[1mx\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1my\u001b[0m\n    |     \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/grouped_with_cause_and_context.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 31, in main\n    a()\n  File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 10, in a\n    1 / 0\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 34, in main\n    raise ValueError(\"Error\") from err\nValueError: Error\n\nDuring handling of the above exception, another exception occurred:\n\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 43, in <module>\n  |     main()\n  |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 36, in main\n  |     raise ExceptionGroup(\"from_context\", [from_context, from_cause])\n  | ExceptionGroup: from_context (2 sub-exceptions)\n  +-+---------------- 1 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 17, in main\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 10, in a\n    |     1 / 0\n    | ZeroDivisionError: division by zero\n    |\n    | The above exception was the direct cause of the following exception:\n    |\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 19, in main\n    |     raise ValueError(\"ContextError\") from err\n    | ValueError: ContextError\n    +---------------- 2 ----------------\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 24, in main\n    |     a()\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 10, in a\n    |     1 / 0\n    | ZeroDivisionError: division by zero\n    |\n    | During handling of the above exception, another exception occurred:\n    |\n    | Traceback (most recent call last):\n    |   File \"tests/exceptions/source/modern/grouped_with_cause_and_context.py\", line 26, in main\n    |     raise ValueError(\"CauseError\")\n    | ValueError: CauseError\n    +------------------------------------\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m31\u001b[0m, in \u001b[35mmain\u001b[0m\n    \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\n\u001b[1mThe above exception was the direct cause of the following exception:\u001b[0m\n\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m34\u001b[0m, in \u001b[35mmain\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"Error\"\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[1merr\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m Error\u001b[0m\n\n\n\u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m43\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n  |\n  |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m36\u001b[0m, in \u001b[35mmain\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mExceptionGroup\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"from_context\"\u001b[0m\u001b[1m,\u001b[0m \u001b[1m[\u001b[0m\u001b[1mfrom_context\u001b[0m\u001b[1m,\u001b[0m \u001b[1mfrom_cause\u001b[0m\u001b[1m]\u001b[0m\u001b[1m)\u001b[0m\n  |     \u001b[36m                                      │             └ \u001b[0m\u001b[36m\u001b[1mValueError('CauseError')\u001b[0m\n  |     \u001b[36m                                      └ \u001b[0m\u001b[36m\u001b[1mValueError('ContextError')\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m from_context (2 sub-exceptions)\u001b[0m\n  +-+---------------- 1 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    |\n    |\n    | \u001b[1mThe above exception was the direct cause of the following exception:\u001b[0m\n    |\n    |\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"ContextError\"\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1mfrom\u001b[0m \u001b[1merr\u001b[0m\n    |\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m ContextError\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m24\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[1ma\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    |     \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function a at 0xDEADBEEF>\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35ma\u001b[0m\n    |     \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    |\n    | \u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n    |\n    |\n    | \u001b[1mDuring handling of the above exception, another exception occurred:\u001b[0m\n    |\n    |\n    | \u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n    |\n    |   File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mgrouped_with_cause_and_context.py\u001b[0m\", line \u001b[33m26\u001b[0m, in \u001b[35mmain\u001b[0m\n    |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"CauseError\"\u001b[0m\u001b[1m)\u001b[0m\n    |\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m CauseError\u001b[0m\n    +------------------------------------\n"
  },
  {
    "path": "tests/exceptions/output/modern/match_statement.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mmatch_statement.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mmatch\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function match at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mmatch_statement.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mmatch\u001b[0m\n    \u001b[1mcase\u001b[0m \u001b[1my\u001b[0m\u001b[1m:\u001b[0m \u001b[1mcase\u001b[0m\u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│    │  │    └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m│    │  └ \u001b[0m\u001b[36m\u001b[1m<function case at 0xDEADBEEF>\u001b[0m\n    \u001b[36m│    └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function case at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mmatch_statement.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mcase\u001b[0m\n    \u001b[1mmatch\u001b[0m \u001b[1my\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m:\u001b[0m\n    \u001b[36m│     └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function match at 0xDEADBEEF>\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/notes.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/notes.py\", line 13, in <module>\n    raise e\nValueError: invalid value\nNote\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m13\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n    \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mValueError('invalid value')\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m invalid value\u001b[0m\nNote\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/notes.py\", line 20, in <module>\n    raise e\nValueError: invalid value\nNote1\nNote2\nNote3\n\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n    \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mValueError('invalid value')\u001b[0m\n\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m invalid value\u001b[0m\nNote1\nNote2\nNote3\n\n\n  + Exception Group Traceback (most recent call last):\n  |   File \"tests/exceptions/source/modern/notes.py\", line 27, in <module>\n  |     raise e\n  | ExceptionGroup: Grouped (2 sub-exceptions)\n  | Note 1\n  | Note 2\n  | Note 3\n  +-+---------------- 1 ----------------\n    | ValueError: 1\n    +---------------- 2 ----------------\n    | ValueError: 2\n    +------------------------------------\n\n  + \u001b[33m\u001b[1mException Group Traceback (most recent call last):\u001b[0m\n  |\n  | > File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m27\u001b[0m, in \u001b[35m<module>\u001b[0m\n  |     \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n  |     \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mExceptionGroup('Grouped', [ValueError(1), ValueError(2)])\u001b[0m\n  |\n  | \u001b[31m\u001b[1mExceptionGroup\u001b[0m:\u001b[1m Grouped (2 sub-exceptions)\u001b[0m\n  | Note 1\n  | Note 2\n  | Note 3\n  +-+---------------- 1 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 1\u001b[0m\n    +---------------- 2 ----------------\n    | \u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m 2\u001b[0m\n    +------------------------------------\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/notes.py\", line 32, in <module>\n    raise e\nTabError: tab error\nNote\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m32\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n    \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mTabError('tab error')\u001b[0m\n\n\u001b[31m\u001b[1mTabError\u001b[0m:\u001b[1m tab error\u001b[0m\nNote\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/notes.py\", line 38, in <module>\n    raise e\n  File \"<string>\", line 1\n    a = 7 *\n           ^\nSyntaxError: syntax error\nNote 1\nNote 2\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m38\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n    \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mSyntaxError('syntax error', ('<string>', 1, 8, 'a = 7 *\\n', 1, 8))\u001b[0m\n\n  File \"<string>\", line 1\n    a = 7 *\n           ^\n\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m syntax error\u001b[0m\nNote 1\nNote 2\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/modern/notes.py\", line 43, in <module>\n    raise e\nTypeError: type error\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mnotes.py\u001b[0m\", line \u001b[33m43\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1me\u001b[0m\n    \u001b[36m      └ \u001b[0m\u001b[36m\u001b[1mTypeError('type error')\u001b[0m\n\n\u001b[31m\u001b[1mTypeError\u001b[0m:\u001b[1m type error\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/positional_only_argument.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mpositional_only_argument.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mpositional_only_argument.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mmain\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m2\u001b[0m\u001b[1m,\u001b[0m \u001b[1mc\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[34m\u001b[1m3\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mpositional_only_argument.py\u001b[0m\", line \u001b[33m10\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1mdef\u001b[0m \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[35m\u001b[1m/\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m,\u001b[0m \u001b[35m\u001b[1m*\u001b[0m\u001b[1m,\u001b[0m \u001b[1mc\u001b[0m\u001b[1m,\u001b[0m \u001b[35m\u001b[1m**\u001b[0m\u001b[1md\u001b[0m\u001b[1m)\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    \u001b[36m    │   │     │     │    └ \u001b[0m\u001b[36m\u001b[1m{}\u001b[0m\n    \u001b[36m    │   │     │     └ \u001b[0m\u001b[36m\u001b[1m3\u001b[0m\n    \u001b[36m    │   │     └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m    │   └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m    └ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/type_hints.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mtype_hints.py\u001b[0m\", line \u001b[33m23\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mtype_hints.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35mmain\u001b[0m\n    \u001b[1mbar\u001b[0m\u001b[1m:\u001b[0m \u001b[1mName\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m2\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m3\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m            └ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mtype_hints.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1mdef\u001b[0m \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m:\u001b[0m \u001b[1mint\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m:\u001b[0m \u001b[1mTuple\u001b[0m\u001b[1m[\u001b[0m\u001b[1mName\u001b[0m\u001b[1m,\u001b[0m \u001b[1mfloat\u001b[0m\u001b[1m]\u001b[0m\u001b[1m,\u001b[0m \u001b[1mc\u001b[0m\u001b[1m:\u001b[0m \u001b[36m\"Name\"\u001b[0m\u001b[1m)\u001b[0m \u001b[35m\u001b[1m->\u001b[0m \u001b[1mT\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n    \u001b[36m    │   │       │  │     │             │             └ \u001b[0m\u001b[36m\u001b[1m~T\u001b[0m\n    \u001b[36m    │   │       │  │     │             └ \u001b[0m\u001b[36m\u001b[1m3\u001b[0m\n    \u001b[36m    │   │       │  │     └ \u001b[0m\u001b[36m\u001b[1m<class 'str'>\u001b[0m\n    \u001b[36m    │   │       │  └ \u001b[0m\u001b[36m\u001b[1mtyping.Tuple\u001b[0m\n    \u001b[36m    │   │       └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m    │   └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m    └ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/modern/walrus_operator.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mwalrus_operator.py\u001b[0m\", line \u001b[33m25\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mmain\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function main at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mwalrus_operator.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35mmain\u001b[0m\n    \u001b[1m(\u001b[0m\u001b[1mwalrus\u001b[0m \u001b[35m\u001b[1m:=\u001b[0m \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m │         └ \u001b[0m\u001b[36m\u001b[1m<function foo at 0xDEADBEEF>\u001b[0m\n    \u001b[36m └ \u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/modern/\u001b[0m\u001b[32m\u001b[1mwalrus_operator.py\u001b[0m\", line \u001b[33m8\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[35m\u001b[1mif\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m:=\u001b[0m \u001b[36m\"a\"\u001b[0m \u001b[35m\u001b[1m+\u001b[0m \u001b[1m(\u001b[0m\u001b[1mx\u001b[0m\u001b[35m\u001b[1m:=\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[35m\u001b[1m/\u001b[0m\u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\u001b[1m:\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/others/assertionerror_without_traceback.txt",
    "content": "\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/others/broken_but_decorated_repr.txt",
    "content": "\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/broken_but_decorated_repr.py\", line 25, in <module>\n    repr(foo)\n         └ <unprintable Foo object>\n\n  File \"tests/exceptions/source/others/broken_but_decorated_repr.py\", line 12, in __repr__\n    raise ValueError(\"Something went wrong (Foo)\")\n\nValueError: Something went wrong (Foo)\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/broken_but_decorated_repr.py\", line 31, in <module>\n    repr(bar)\n         └ <unprintable Bar object>\n\n> File \"tests/exceptions/source/others/broken_but_decorated_repr.py\", line 18, in __repr__\n    raise ValueError(\"Something went wrong (Bar)\")\n\nValueError: Something went wrong (Bar)\n"
  },
  {
    "path": "tests/exceptions/output/others/catch_as_context_manager.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_as_context_manager.py\", line 10, in <module>\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/catch_as_decorator_with_parentheses.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_as_decorator_with_parentheses.py\", line 14, in <module>\n    c(5, b=0)\n  File \"tests/exceptions/source/others/catch_as_decorator_with_parentheses.py\", line 11, in c\n    a / b\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/catch_as_decorator_without_parentheses.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_as_decorator_without_parentheses.py\", line 14, in <module>\n    c(2)\n  File \"tests/exceptions/source/others/catch_as_decorator_without_parentheses.py\", line 11, in c\n    a / b\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/catch_as_function.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_as_function.py\", line 14, in <module>\n    a()\n  File \"tests/exceptions/source/others/catch_as_function.py\", line 10, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/catch_message.txt",
    "content": "An error occurred (1):\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_message.py\", line 14, in <module>\n    a()\n  File \"tests/exceptions/source/others/catch_message.py\", line 10, in a\n    1 / 0\nZeroDivisionError: division by zero\nAn error occurred (2):\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/catch_message.py\", line 17, in <module>\n    a()\n  File \"tests/exceptions/source/others/catch_message.py\", line 10, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/exception_formatting_coroutine.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 20, in <module>\n    f.send(None)\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 20, in <module>\n    f.send(None)\n    │ └ <method 'send' of 'coroutine' objects>\n    └ <coroutine object foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 20, in <module>\n    f.send(None)\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 20, in <module>\n    f.send(None)\n    │ └ <method 'send' of 'coroutine' objects>\n    └ <coroutine object foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_coroutine.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/exception_formatting_function.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 17, in <module>\n    a(1, 0)\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 14, in a\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 17, in <module>\n    a(1, 0)\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 14, in a\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/exception_formatting_function.py\", line 17, in <module>\n    a(1, 0)\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 14, in a\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/exception_formatting_function.py\", line 17, in <module>\n    a(1, 0)\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_function.py\", line 14, in a\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/exception_formatting_generator.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 20, in <module>\n    next(f)\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 14, in foo\n    yield a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 20, in <module>\n    next(f)\n         └ <generator object foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 14, in foo\n    yield a / b\n          │   └ 0\n          └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 20, in <module>\n    next(f)\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 14, in foo\n    yield a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 20, in <module>\n    next(f)\n         └ <generator object foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/exception_formatting_generator.py\", line 14, in foo\n    yield a / b\n          │   └ 0\n          └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/exception_in_property.txt",
    "content": "\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/exception_in_property.py\", line 22, in <module>\n    value = a.value\n            │ └ <property object at 0xDEADBEEF>\n            └ <__main__.A object at 0xDEADBEEF>\n\n> File \"tests/exceptions/source/others/exception_in_property.py\", line 13, in value\n    1 / 0\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/handler_formatting_with_context_manager.txt",
    "content": "__main__ handler_formatting_with_context_manager.py a 16\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/handler_formatting_with_context_manager.py\", line 17, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/handler_formatting_with_decorator.txt",
    "content": "__main__ handler_formatting_with_decorator.py <module> 20\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/handler_formatting_with_decorator.py\", line 20, in <module>\n    a()\n  File \"tests/exceptions/source/others/handler_formatting_with_decorator.py\", line 17, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/level_name.txt",
    "content": "DEBUG | 10\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/level_name.py\", line 13, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/level_number.txt",
    "content": "Level 13 | 13\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/level_number.py\", line 13, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/message_formatting_with_context_manager.txt",
    "content": "__main__ message_formatting_with_context_manager.py a 10\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/message_formatting_with_context_manager.py\", line 13, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/message_formatting_with_decorator.txt",
    "content": "__main__ message_formatting_with_decorator.py <module> 14\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/message_formatting_with_decorator.py\", line 14, in <module>\n    a()\n  File \"tests/exceptions/source/others/message_formatting_with_decorator.py\", line 11, in a\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/nested_with_reraise.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n        │   │  └ 0\n        │   └ 1\n        └ <function foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 30, in <module>\n    baz()\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n> File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 30, in <module>\n    baz()\n    └ <function baz at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n    └ <function bar at 0xDEADBEEF>\n\n> File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n        │   │  └ 0\n        │   └ 1\n        └ <function foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 22, in bar\n    raise ValueError from e\nValueError\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n        │   │  └ 0\n        │   └ 1\n        └ <function foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\n\nThe above exception was the direct cause of the following exception:\n\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n    └ <function bar at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 22, in bar\n    raise ValueError from e\n\nValueError\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\nZeroDivisionError: division by zero\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 30, in <module>\n    baz()\n> File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 22, in bar\n    raise ValueError from e\nValueError\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 20, in bar\n    f = foo(x, y)\n        │   │  └ 0\n        │   └ 1\n        └ <function foo at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 14, in foo\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\n\nThe above exception was the direct cause of the following exception:\n\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 30, in <module>\n    baz()\n    └ <function baz at 0xDEADBEEF>\n\n> File \"tests/exceptions/source/others/nested_with_reraise.py\", line 26, in baz\n    bar(1, 0)\n    └ <function bar at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/nested_with_reraise.py\", line 22, in bar\n    raise ValueError from e\n\nValueError\n"
  },
  {
    "path": "tests/exceptions/output/others/one_liner_recursion.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <module>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  [Previous line repeated 8 more times]\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <module>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n  [Previous line repeated 8 more times]\nZeroDivisionError: division by zero\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mone_liner_recursion.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mrec\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[35m\u001b[1mlambda\u001b[0m \u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1mif\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1melse\u001b[0m \u001b[1mr\u001b[0m\u001b[1m(\u001b[0m\u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\u001b[1m;\u001b[0m \u001b[1mrec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mrec\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mone_liner_recursion.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<lambda>\u001b[0m\n    \u001b[1mrec\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[35m\u001b[1mlambda\u001b[0m \u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1mif\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1melse\u001b[0m \u001b[1mr\u001b[0m\u001b[1m(\u001b[0m\u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\u001b[1m;\u001b[0m \u001b[1mrec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mrec\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mone_liner_recursion.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<lambda>\u001b[0m\n    \u001b[1mrec\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[35m\u001b[1mlambda\u001b[0m \u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1mif\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1melse\u001b[0m \u001b[1mr\u001b[0m\u001b[1m(\u001b[0m\u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\u001b[1m;\u001b[0m \u001b[1mrec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mrec\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mone_liner_recursion.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35m<lambda>\u001b[0m\n    \u001b[1mrec\u001b[0m \u001b[35m\u001b[1m=\u001b[0m \u001b[35m\u001b[1mlambda\u001b[0m \u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m\u001b[1m:\u001b[0m \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1mif\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[34m\u001b[1m0\u001b[0m \u001b[35m\u001b[1melse\u001b[0m \u001b[1mr\u001b[0m\u001b[1m(\u001b[0m\u001b[1mr\u001b[0m\u001b[1m,\u001b[0m \u001b[1mi\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\u001b[1m;\u001b[0m \u001b[1mrec\u001b[0m\u001b[1m(\u001b[0m\u001b[1mrec\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 8 more times]\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <module>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n                                                         │   └ <function <lambda> at 0xDEADBEEF>\n                                                         └ <function <lambda> at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n                 │  │           │           │ │  │       │   └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  │       └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  └ 10\n                 │  │           │           │ └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           └ <function <lambda> at 0xDEADBEEF>\n                 │  │           └ 10\n                 │  └ 10\n                 └ <function <lambda> at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n                 │  │           │           │ │  │       │   └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  │       └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  └ 9\n                 │  │           │           │ └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           └ <function <lambda> at 0xDEADBEEF>\n                 │  │           └ 9\n                 │  └ 9\n                 └ <function <lambda> at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/one_liner_recursion.py\", line 14, in <lambda>\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\n                 │  │           │           │ │  │       │   └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  │       └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           │ │  └ 8\n                 │  │           │           │ └ <function <lambda> at 0xDEADBEEF>\n                 │  │           │           └ <function <lambda> at 0xDEADBEEF>\n                 │  │           └ 8\n                 │  └ 8\n                 └ <function <lambda> at 0xDEADBEEF>\n  [Previous line repeated 8 more times]\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/recursion_error.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/recursion_error.py\", line 19, in <module>\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  [Previous line repeated 996 more times]\nRecursionError: maximum recursion depth exceeded\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/recursion_error.py\", line 19, in <module>\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n  [Previous line repeated 996 more times]\nRecursionError: maximum recursion depth exceeded\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrecursion_error.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrecursion_error.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrecursion_error.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrecursion_error.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 996 more times]\n\u001b[31m\u001b[1mRecursionError\u001b[0m:\u001b[1m maximum recursion depth exceeded\u001b[0m\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/recursion_error.py\", line 19, in <module>\n    recursive()\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/recursion_error.py\", line 15, in recursive\n    recursive()\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 996 more times]\n\nRecursionError: maximum recursion depth exceeded\n"
  },
  {
    "path": "tests/exceptions/output/others/repeated_lines.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 22, in <module>\n    recursive(10, 10)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 7 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 6 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 5 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 4 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 3 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 2 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 1 more time]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 15, in recursive\n    raise ValueError(\"End of recursion\")\nValueError: End of recursion\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/repeated_lines.py\", line 22, in <module>\n    recursive(10, 10)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 7 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 6 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 5 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 4 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 3 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 2 more times]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  [Previous line repeated 1 more time]\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 15, in recursive\n    raise ValueError(\"End of recursion\")\nValueError: End of recursion\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m10\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 7 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 6 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 5 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 4 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 3 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 2 more times]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  [Previous line repeated 1 more time]\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1minner\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[1mrecursive\u001b[0m\u001b[1m(\u001b[0m\u001b[1mouter\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[1minner\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[1mouter\u001b[0m \u001b[35m\u001b[1m-\u001b[0m \u001b[34m\u001b[1m1\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/others/\u001b[0m\u001b[32m\u001b[1mrepeated_lines.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mrecursive\u001b[0m\n    \u001b[35m\u001b[1mraise\u001b[0m \u001b[1mValueError\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"End of recursion\"\u001b[0m\u001b[1m)\u001b[0m\n\u001b[31m\u001b[1mValueError\u001b[0m:\u001b[1m End of recursion\u001b[0m\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 22, in <module>\n    recursive(10, 10)\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 10\n    │               └ 10\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 9\n    │               └ 10\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 8\n    │               └ 10\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 7 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 10\n    │               └ 10\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 9\n    │               └ 9\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 8\n    │               └ 9\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 7\n    │               └ 9\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 6 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 9\n    │               └ 9\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 8\n    │               └ 8\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 7\n    │               └ 8\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 6\n    │               └ 8\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 5 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 8\n    │               └ 8\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 7\n    │               └ 7\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 6\n    │               └ 7\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 5\n    │               └ 7\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 4 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 7\n    │               └ 7\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 6\n    │               └ 6\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 5\n    │               └ 6\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 4\n    │               └ 6\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 3 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 6\n    │               └ 6\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 5\n    │               └ 5\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 4\n    │               └ 5\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 3\n    │               └ 5\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 2 more times]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 5\n    │               └ 5\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 4\n    │               └ 4\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 3\n    │               └ 4\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 2\n    │               └ 4\n    └ <function recursive at 0xDEADBEEF>\n  [Previous line repeated 1 more time]\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 4\n    │               └ 4\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 3\n    │               └ 3\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 2\n    │               └ 3\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 1\n    │               └ 3\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 3\n    │               └ 3\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 2\n    │               └ 2\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 1\n    │               └ 2\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 2\n    │               └ 2\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 18, in recursive\n    recursive(outer=outer, inner=inner - 1)\n    │               │            └ 1\n    │               └ 1\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 17, in recursive\n    recursive(outer=outer - 1, inner=outer - 1)\n    │               │                └ 1\n    │               └ 1\n    └ <function recursive at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/repeated_lines.py\", line 15, in recursive\n    raise ValueError(\"End of recursion\")\n\nValueError: End of recursion\n"
  },
  {
    "path": "tests/exceptions/output/others/syntaxerror_without_traceback.txt",
    "content": "\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/others/sys_tracebacklimit.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/sys_tracebacklimit_negative.txt",
    "content": "\nZeroDivisionError: division by zero\n\nZeroDivisionError: division by zero\n\nZeroDivisionError: division by zero\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/sys_tracebacklimit_none.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 55, in <module>\n    a()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 13, in a\n    b()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 17, in b\n    c()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 21, in c\n    d()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 25, in d\n    e()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 29, in e\n    f()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 55, in <module>\n    a()\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 13, in a\n    b()\n    └ <function b at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 17, in b\n    c()\n    └ <function c at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 21, in c\n    d()\n    └ <function d at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 25, in d\n    e()\n    └ <function e at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 29, in e\n    f()\n    └ <function f at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 55, in <module>\n    a()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 13, in a\n    b()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 17, in b\n    c()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 21, in c\n    d()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 25, in d\n    e()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 29, in e\n    f()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 55, in <module>\n    a()\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 13, in a\n    b()\n    └ <function b at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 17, in b\n    c()\n    └ <function c at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 21, in c\n    d()\n    └ <function d at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 25, in d\n    e()\n    └ <function e at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 29, in e\n    f()\n    └ <function f at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_none.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/sys_tracebacklimit_unset.txt",
    "content": "\nTraceback (most recent call last):\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 58, in <module>\n    a()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 13, in a\n    b()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 17, in b\n    c()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 21, in c\n    d()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 25, in d\n    e()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 29, in e\n    f()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 58, in <module>\n    a()\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 13, in a\n    b()\n    └ <function b at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 17, in b\n    c()\n    └ <function c at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 21, in c\n    d()\n    └ <function d at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 25, in d\n    e()\n    └ <function e at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 29, in e\n    f()\n    └ <function f at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n> File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 58, in <module>\n    a()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 13, in a\n    b()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 17, in b\n    c()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 21, in c\n    d()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 25, in d\n    e()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 29, in e\n    f()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 33, in f\n    g()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 37, in g\n    h()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 41, in h\n    i()\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 45, in i\n    j(1, 0)\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 49, in j\n    a / b\nZeroDivisionError: division by zero\n\nTraceback (most recent call last):\n\n> File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 58, in <module>\n    a()\n    └ <function a at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 13, in a\n    b()\n    └ <function b at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 17, in b\n    c()\n    └ <function c at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 21, in c\n    d()\n    └ <function d at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 25, in d\n    e()\n    └ <function e at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 29, in e\n    f()\n    └ <function f at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 33, in f\n    g()\n    └ <function g at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 37, in g\n    h()\n    └ <function h at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 41, in h\n    i()\n    └ <function i at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 45, in i\n    j(1, 0)\n    └ <function j at 0xDEADBEEF>\n\n  File \"tests/exceptions/source/others/sys_tracebacklimit_unset.py\", line 49, in j\n    a / b\n    │   └ 0\n    └ 1\n\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/others/zerodivisionerror_without_traceback.txt",
    "content": "\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n"
  },
  {
    "path": "tests/exceptions/output/ownership/assertion_from_lib.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1massertionerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│              │  └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m│              └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function assertionerror at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 22, in assertionerror\n    assert x == y\n           │    └ 2\n           └ 1\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m: \u001b[35m\u001b[1massert\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1my\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1massertionerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│              │  └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m│              └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function assertionerror at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 22, in assertionerror\n    assert x == y\n           │    └ 2\n           └ 1\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m: \u001b[35m\u001b[1massert\u001b[0m \u001b[1mx\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1my\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1massertionerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 22, in assertionerror\n    assert x == y\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1massertionerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1ma\u001b[0m\u001b[1m,\u001b[0m \u001b[1mb\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 22, in assertionerror\n    assert x == y\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/assertion_from_lib.py\", line 15, in test\n    assertionerror(a, b)\n  File \"/usr/lib/python/somelib/__init__.py\", line 22, in assertionerror\n    assert x == y\nAssertionError\n"
  },
  {
    "path": "tests/exceptions/output/ownership/assertion_from_local.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m       │    └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m: \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m       │    └ \u001b[0m\u001b[36m\u001b[1m2\u001b[0m\n    \u001b[36m       └ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mAssertionError\u001b[0m: \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1massertion_from_local.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[35m\u001b[1massert\u001b[0m \u001b[1ma\u001b[0m \u001b[35m\u001b[1m==\u001b[0m \u001b[1mb\u001b[0m\n\u001b[31m\u001b[1mAssertionError\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/assertion_from_local.py\", line 15, in test\n    assert a == b\nAssertionError\n"
  },
  {
    "path": "tests/exceptions/output/ownership/callback.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│      └ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.callback at 0xDEADBEEF>\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function callme at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│      └ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.callback at 0xDEADBEEF>\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function callme at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m24\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcallback.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/callback.py\", line 17, in test\n    callme(callback)\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"tests/exceptions/source/ownership/callback.py\", line 14, in callback\n    divide(1, 0)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/catch_decorator.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.foo at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mfoo\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/catch_decorator.py\", line 17, in test\n    foo()\n  File \"tests/exceptions/source/ownership/catch_decorator.py\", line 15, in foo\n    divide(1, 0)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/catch_decorator_from_lib.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m20\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│      └ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.callback at 0xDEADBEEF>\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function callme at 0xDEADBEEF>\u001b[0m\n\n> File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m22\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m17\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n> File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mcatch_decorator_from_lib.py\u001b[0m\", line \u001b[33m15\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m1\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"tests/exceptions/source/ownership/catch_decorator_from_lib.py\", line 15, in callback\n    divide(1, 0)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/decorated_callback.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m│      └ \u001b[0m\u001b[36m\u001b[1m<function test.<locals>.callback at 0xDEADBEEF>\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function callme at 0xDEADBEEF>\u001b[0m\n\n> File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n    └ <function test.<locals>.callback at 0xDEADBEEF>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mb\u001b[0m\n    \u001b[36m│   └ \u001b[0m\u001b[36m\u001b[1m0\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m1\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m23\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m18\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mcallme\u001b[0m\u001b[1m(\u001b[0m\u001b[1mcallback\u001b[0m\u001b[1m)\u001b[0m\n> File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mb\u001b[0m\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdecorated_callback.py\u001b[0m\", line \u001b[33m16\u001b[0m, in \u001b[35mcallback\u001b[0m\n    \u001b[1ma\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[1mb\u001b[0m\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"/usr/lib/python/somelib/__init__.py\", line 10, in callme\n    callback()\n  File \"tests/exceptions/source/ownership/decorated_callback.py\", line 16, in callback\n    a / b\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/direct.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 10\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 10\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mdirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/direct.py\", line 14, in test\n    divide(10, 0)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/indirect.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide_indirect\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide_indirect at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 6, in divide_indirect\n    divide(a, b)\n    │      │  └ 0\n    │      └ 10\n    └ <function divide at 0xDEADBEEF>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 10\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide_indirect\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function divide_indirect at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 6, in divide_indirect\n    divide(a, b)\n    │      │  └ 0\n    │      └ 10\n    └ <function divide at 0xDEADBEEF>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 10\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide_indirect\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 6, in divide_indirect\n    divide(a, b)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mindirect.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mdivide_indirect\u001b[0m\u001b[1m(\u001b[0m\u001b[34m\u001b[1m10\u001b[0m\u001b[1m,\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 6, in divide_indirect\n    divide(a, b)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/indirect.py\", line 14, in test\n    divide_indirect(10, 0)\n  File \"/usr/lib/python/somelib/__init__.py\", line 6, in divide_indirect\n    divide(a, b)\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/string_lib.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexecute\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function execute at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 14, in execute\n    exec(\"divide(1, 0)\")\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexecute\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function execute at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 14, in execute\n    exec(\"divide(1, 0)\")\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n    │   └ 0\n    └ 1\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexecute\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 14, in execute\n    exec(\"divide(1, 0)\")\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_lib.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexecute\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 14, in execute\n    exec(\"divide(1, 0)\")\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/string_lib.py\", line 14, in test\n    execute()\n  File \"/usr/lib/python/somelib/__init__.py\", line 14, in execute\n    exec(\"divide(1, 0)\")\n  File \"<string>\", line 1, in <module>\n  File \"/usr/lib/python/somelib/__init__.py\", line 2, in divide\n    x / y\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/string_source.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"foo()\"\u001b[0m\u001b[1m)\u001b[0m\n\n  File \"<string>\", line 1, in <module>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"foo()\"\u001b[0m\u001b[1m)\u001b[0m\n\n  File \"<string>\", line 1, in <module>\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"foo()\"\u001b[0m\u001b[1m)\u001b[0m\n  File \"<string>\", line 1, in <module>\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1mexec\u001b[0m\u001b[1m(\u001b[0m\u001b[36m\"foo()\"\u001b[0m\u001b[1m)\u001b[0m\n  File \"<string>\", line 1, in <module>\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1mstring_source.py\u001b[0m\", line \u001b[33m11\u001b[0m, in \u001b[35mfoo\u001b[0m\n    \u001b[34m\u001b[1m1\u001b[0m \u001b[35m\u001b[1m/\u001b[0m \u001b[34m\u001b[1m0\u001b[0m\n\u001b[31m\u001b[1mZeroDivisionError\u001b[0m:\u001b[1m division by zero\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/string_source.py\", line 14, in test\n    exec(\"foo()\")\n  File \"<string>\", line 1, in <module>\n  File \"tests/exceptions/source/ownership/string_source.py\", line 11, in foo\n    1 / 0\nZeroDivisionError: division by zero\n"
  },
  {
    "path": "tests/exceptions/output/ownership/syntaxerror.txt",
    "content": "\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m19\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function test at 0xDEADBEEF>\u001b[0m\n\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1msyntaxerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function syntaxerror at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 18, in syntaxerror\n    exec(\"foo =\")\n  File \"<string>\", line 1\n    foo =\n         ^\n\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1msyntaxerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n    \u001b[36m└ \u001b[0m\u001b[36m\u001b[1m<function syntaxerror at 0xDEADBEEF>\u001b[0m\n\n  File \"/usr/lib/python/somelib/__init__.py\", line 18, in syntaxerror\n    exec(\"foo =\")\n  File \"<string>\", line 1\n    foo =\n         ^\n\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m21\u001b[0m, in \u001b[35m<module>\u001b[0m\n    \u001b[1mtest\u001b[0m\u001b[1m(\u001b[0m\u001b[1mbacktrace\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mcolorize\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mTrue\u001b[0m\u001b[1m,\u001b[0m \u001b[1mdiagnose\u001b[0m\u001b[35m\u001b[1m=\u001b[0m\u001b[36m\u001b[1mFalse\u001b[0m\u001b[1m)\u001b[0m\n> File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1msyntaxerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 18, in syntaxerror\n    exec(\"foo =\")\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\n\u001b[33m\u001b[1mTraceback (most recent call last):\u001b[0m\n  File \"\u001b[32mtests/exceptions/source/ownership/\u001b[0m\u001b[32m\u001b[1msyntaxerror.py\u001b[0m\", line \u001b[33m14\u001b[0m, in \u001b[35mtest\u001b[0m\n    \u001b[1msyntaxerror\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m\n  File \"/usr/lib/python/somelib/__init__.py\", line 18, in syntaxerror\n    exec(\"foo =\")\n  File \"<string>\", line 1\n    foo =\n         ^\n\u001b[31m\u001b[1mSyntaxError\u001b[0m:\u001b[1m invalid syntax\u001b[0m\n\nTraceback (most recent call last):\n  File \"tests/exceptions/source/ownership/syntaxerror.py\", line 14, in test\n    syntaxerror()\n  File \"/usr/lib/python/somelib/__init__.py\", line 18, in syntaxerror\n    exec(\"foo =\")\n  File \"<string>\", line 1\n    foo =\n         ^\nSyntaxError: invalid syntax\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/chained_expression_direct.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch()\ndef a_decorated():\n    try:\n        1 / 0\n    except ZeroDivisionError:\n        raise ValueError(\"NOK\")\n\n\ndef a_not_decorated():\n    try:\n        1 / 0\n    except ZeroDivisionError:\n        raise ValueError(\"NOK\")\n\n\ndef b_decorator():\n    a_decorated()\n\n\ndef b_context_manager():\n    with logger.catch():\n        a_not_decorated()\n\n\ndef b_explicit():\n    try:\n        a_not_decorated()\n    except ValueError:\n        logger.exception(\"\")\n\n\nb_decorator()\nb_context_manager()\nb_explicit()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/chained_expression_indirect.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a():\n    try:\n        1 / 0\n    except ZeroDivisionError:\n        raise ValueError(\"NOK\")\n\n\n@logger.catch\ndef b():\n    a()\n\n\nb()\n\nwith logger.catch():\n    a()\n\ntry:\n    a()\nexcept ValueError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/chaining_first.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch\ndef a_decorated():\n    b()\n\n\ndef a_not_decorated():\n    b()\n\n\ndef b():\n    c()\n\n\ndef c():\n    1 / 0\n\n\na_decorated()\n\n\nwith logger.catch():\n    a_not_decorated()\n\ntry:\n    a_not_decorated()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/chaining_second.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a_decorator():\n    b_decorated()\n\n\ndef a_context_manager():\n    with logger.catch():\n        b_not_decorated()\n\n\ndef a_explicit():\n    try:\n        b_not_decorated()\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\n@logger.catch()\ndef b_decorated():\n    c()\n\n\ndef b_not_decorated():\n    c()\n\n\ndef c():\n    1 / 0\n\n\na_decorator()\na_context_manager()\na_explicit()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/chaining_third.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a_decorator():\n    b_decorator()\n\n\ndef a_context_manager():\n    b_context_manager()\n\n\ndef a_explicit():\n    b_explicit()\n\n\ndef b_decorator():\n    c_decorated()\n\n\ndef b_context_manager():\n    with logger.catch():\n        c_not_decorated()\n\n\ndef b_explicit():\n    try:\n        c_not_decorated()\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\n@logger.catch\ndef c_decorated():\n    1 / 0\n\n\ndef c_not_decorated():\n    1 / 0\n\n\na_decorator()\na_context_manager()\na_explicit()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/enqueue.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, enqueue=True, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\ntry:\n    1 / 0\nexcept ZeroDivisionError:\n    logger.exception(\"Error\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/enqueue_with_others_handlers.py",
    "content": "import sys\n\nfrom loguru import logger\n\n\ndef check_tb_sink(message):\n    exception = message.record[\"exception\"]\n    if exception is None:\n        return\n    assert exception.traceback is not None\n\n\nlogger.remove()\n\nlogger.add(\n    check_tb_sink, enqueue=False, catch=False, colorize=False, backtrace=True, diagnose=False\n)\nlogger.add(\n    sys.stderr, format=\"\", enqueue=True, catch=False, colorize=False, backtrace=True, diagnose=False\n)\nlogger.add(\n    check_tb_sink, enqueue=False, catch=False, colorize=False, backtrace=True, diagnose=False\n)\n\ntry:\n    1 / 0\nexcept ZeroDivisionError:\n    logger.exception(\"Error\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/frame_values_backward.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\nk = 2\n\n\n@logger.catch\ndef a(n):\n    1 / n\n\n\ndef b(n):\n    a(n - 1)\n\n\ndef c(n):\n    b(n - 1)\n\n\nc(k)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/frame_values_forward.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\nk = 2\n\n\ndef a(n):\n    1 / n\n\n\ndef b(n):\n    a(n - 1)\n\n\n@logger.catch\ndef c(n):\n    b(n - 1)\n\n\nc(k)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/function.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch()\ndef a():\n    1 / 0\n\n\ndef b():\n    2 / 0\n\n\ndef c():\n    3 / 0\n\n\na()\n\nwith logger.catch():\n    b()\n\ntry:\n    c()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/head_recursion.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch()\ndef a(n):\n    if n:\n        a(n - 1)\n    1 / n\n\n\ndef b(n):\n    if n:\n        with logger.catch():\n            b(n - 1)\n    1 / n\n\n\ndef c(n):\n    if n:\n        try:\n            c(n - 1)\n        except ZeroDivisionError:\n            logger.exception(\"\")\n    1 / n\n\n\na(1)\na(2)\na(3)\n\nb(1)\nb(2)\nb(3)\n\nc(1)\nc(2)\nc(3)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/missing_attributes_traceback_objects.py",
    "content": "import sys\nfrom collections import namedtuple\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\na, b = 1, 0\n\n\ndef div(x, y):\n    x / y\n\n\ndef foo():\n    div(a, b)\n\n\n# See Twisted: https://github.com/twisted/twisted/blob/29cbe/src/twisted/python/failure.py#L175-L181\n# See Billiard: https://github.com/celery/billiard/blob/529a3/billiard/einfo.py#L11-L26\nfake_code = namedtuple(\"fake_code\", (\"co_filename\", \"co_name\"))\nfake_frame = namedtuple(\"fake_frame\", (\"f_back\", \"f_code\", \"f_globals\", \"f_lineno\", \"f_locals\"))\nfake_traceback = namedtuple(\"fake_traceback\", (\"tb_frame\", \"tb_lasti\", \"tb_lineno\", \"tb_next\"))\n\n\ndef make_fake(tb):\n    if not tb:\n        return None\n    code = fake_code(tb.tb_frame.f_code.co_filename, tb.tb_frame.f_code.co_name)\n    frame = fake_frame(None, code, {}, tb.tb_lineno, {})\n    tb = fake_traceback(frame, tb.tb_lasti, tb.tb_lineno, make_fake(tb.tb_next))\n    return tb\n\n\ntry:\n    foo()\nexcept ZeroDivisionError:\n    type_, value, tb = sys.exc_info()\n    tb = make_fake(tb)\n    logger.opt(exception=(type_, value, tb)).error(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/missing_lineno_frame_objects.py",
    "content": "import sys\nfrom collections import namedtuple\n\nfrom loguru import logger\n\n\nlogger.remove()\nlogger.add(\n    sys.stderr,\n    format=\"{line}: {message}\",\n    colorize=False,\n    backtrace=True,\n    diagnose=False,\n)\n\n# Regression since CPython 3.10: the `lineno` can be `None`: https://github.com/python/cpython/issues/89726\nfake_code = namedtuple(\"fake_code\", (\"co_filename\", \"co_name\"))\nfake_frame = namedtuple(\"fake_frame\", (\"f_back\", \"f_code\", \"f_globals\", \"f_lineno\", \"f_locals\"))\nfake_traceback = namedtuple(\"fake_traceback\", (\"tb_frame\", \"tb_lasti\", \"tb_lineno\", \"tb_next\"))\n\n\ndef make_fake(tb):\n    if not tb:\n        return None\n    code = fake_code(tb.tb_frame.f_code.co_filename, tb.tb_frame.f_code.co_name)\n    frame = fake_frame(None, code, {}, None, {})\n    tb = fake_traceback(frame, tb.tb_lasti, None, make_fake(tb.tb_next))\n    return tb\n\n\ndef a():\n    1 / 0\n\n\ndef b():\n    a()\n\n\ntry:\n    b()\nexcept Exception as e:\n    type_, value, tb = sys.exc_info()\n    tb = make_fake(tb)\n    logger.opt(exception=(type_, value, tb)).error(\"An error occurred\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/nested.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a(x):\n    @logger.catch\n    def nested(i):\n        1 / i\n\n    nested(x)\n\n\na(0)\n\n\ndef b(x):\n    def nested(i):\n        1 / i\n\n    with logger.catch():\n        nested(x)\n\n\nb(0)\n\n\ndef c(x):\n    def nested(i):\n        1 / i\n\n    try:\n        nested(x)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\nc(0)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/nested_chained_catch_up.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=False, diagnose=False)\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef foo():\n    bar()\n\n\n@logger.catch(ValueError)\ndef bar():\n    1 / 0\n\n\n@logger.catch\ndef main():\n    try:\n        foo()\n    except Exception as e:\n        raise ZeroDivisionError from e\n\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/nested_decorator_catch_up.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=False, diagnose=False)\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch(ZeroDivisionError)\ndef foo():\n    bar()\n\n\n@logger.catch(NotImplementedError)\ndef bar():\n    1 / 0\n\n\nfoo()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/nested_explicit_catch_up.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=False, diagnose=False)\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef foo():\n    bar()\n\n\n@logger.catch(NotImplementedError)\ndef bar():\n    1 / 0\n\n\ntry:\n    foo()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/nested_wrapping.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef f(i):\n    1 / i\n\n\n@logger.catch\n@logger.catch()\ndef a(x):\n    f(x)\n\n\na(0)\n\n\nwith logger.catch():\n    with logger.catch():\n        f(0)\n\n\ntry:\n    try:\n        f(0)\n    except ZeroDivisionError:\n        logger.exception(\"\")\nexcept Exception:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/no_tb.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"{message}\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef f():\n    try:\n        1 / 0\n    except ZeroDivisionError:\n        ex_type, ex, tb = sys.exc_info()\n        tb = None\n\n    logger.opt(exception=(ex_type, ex, tb)).debug(\"Test:\")\n\n\nf()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/not_enough_arguments.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch\ndef decorated(x, y, z):\n    pass\n\n\ndef not_decorated(x, y, z):\n    pass\n\n\ndecorated(1)\n\nwith logger.catch():\n    not_decorated(2)\n\ntry:\n    not_decorated(3)\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/raising_recursion.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch\ndef a(n):\n    if n:\n        a(n - 1)\n    n / 0\n\n\ndef b(n):\n    with logger.catch():\n        if n:\n            b(n - 1)\n        n / 0\n\n\ndef c(n):\n    try:\n        if n:\n            c(n - 1)\n        n / 0\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\na(1)\na(2)\na(3)\n\nb(1)\nb(2)\nb(3)\n\nc(1)\nc(2)\nc(3)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/suppressed_expression_direct.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a(x, y):\n    x / y\n\n\n@logger.catch\ndef b_decorated():\n    try:\n        a(1, 0)\n    except ZeroDivisionError as e:\n        raise ValueError(\"NOK\") from e\n\n\ndef b_not_decorated():\n    try:\n        a(1, 0)\n    except ZeroDivisionError as e:\n        raise ValueError(\"NOK\") from e\n\n\ndef c_decorator():\n    b_decorated()\n\n\ndef c_context_manager():\n    with logger.catch():\n        b_not_decorated()\n\n\ndef c_explicit():\n    try:\n        b_not_decorated()\n    except ValueError:\n        logger.exception(\"\")\n\n\nc_decorator()\nc_context_manager()\nc_explicit()\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/suppressed_expression_indirect.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\ndef a(x, y):\n    x / y\n\n\ndef b():\n    try:\n        a(1, 0)\n    except ZeroDivisionError as e:\n        raise ValueError(\"NOK\") from e\n\n\n@logger.catch\ndef c_decorated():\n    b()\n\n\ndef c_not_decorated():\n    b()\n\n\nc_decorated()\n\nwith logger.catch():\n    c_not_decorated()\n\ntry:\n    c_not_decorated()\nexcept ValueError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/tail_recursion.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch()\ndef a(n):\n    1 / n\n    a(n - 1)\n\n\ndef b(n):\n    1 / n\n    with logger.catch():\n        b(n - 1)\n\n\ndef c(n):\n    1 / n\n    try:\n        c(n - 1)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\na(1)\na(2)\na(3)\n\nb(1)\nb(2)\nb(3)\n\nc(1)\nc(2)\nc(3)\n"
  },
  {
    "path": "tests/exceptions/source/backtrace/too_many_arguments.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=False, backtrace=True, diagnose=False)\n\n\n@logger.catch\ndef decorated():\n    pass\n\n\ndef not_decorated():\n    pass\n\n\ndecorated(1)\n\nwith logger.catch():\n    not_decorated(2)\n\ntry:\n    not_decorated(3)\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/assertion_error.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef foo(abc, xyz):\n    assert abc > 10 and xyz == 60\n\n\ntry:\n    foo(9, 55)\nexcept AssertionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/assertion_error_custom.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef foo(abc, xyz):\n    assert abc > 10 and xyz == 60, \"Foo assertion failed\"\n\n\ntry:\n    foo(9, 55)\nexcept AssertionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/assertion_error_in_string.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef foo(abc, xyz):\n    exec(\"assert abc > 10 and xyz == 60\")\n\n\ntry:\n    foo(9, 55)\nexcept AssertionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/attributes.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nclass Obj:\n    @property\n    def forbidden(self):\n        raise RuntimeError\n\n\na = Obj()\na.b = \"123\"\n\n\ndef foo():\n    x = None\n    ... + 1 + bar(a).b + a.forbidden + a.nope.a + x.__bool__ or a. b . isdigit() and .3 + ...\n\n\ntry:\n    foo()\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/chained_both.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef div(x, y):\n    x / y\n\n\ndef cause(x, y):\n    try:\n        div(x, y)\n    except Exception:\n        raise ValueError(\"Division error\")\n\n\ndef context(x, y):\n    try:\n        cause(x, y)\n    except Exception as e:\n        raise ValueError(\"Cause error\") from e\n\n\ntry:\n    context(1, 0)\nexcept ValueError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/encoding.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef _deep(val):\n    return 1 / val\n\n\ndef div():\n    return _deep(\"天\")\n\n\ntry:\n    div()\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/global_variable.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nfoo = True\nbar = False\n\n\ndef func():\n    foo = None\n    return 1 / 0 + foo + bar + False\n\n\ntry:\n    func()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/indentation_error.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ncode = \"\"\"\nif True:\n    a = 5\n        print(\"foobar\")  #intentional faulty indentation here.\n    b = 7\n\"\"\"\n\ntry:\n    exec(code)\nexcept IndentationError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/keyword_argument.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef f(x):\n    return 1 / x\n\n\ny = 0\n\nwith logger.catch():\n    f(x=y)\n\nx = 0\n\nwith logger.catch():\n    f(x=x)\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/multilines_repr.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nclass A:\n    def __repr__(self):\n        return \"[[1, 2, 3]\\n\" \" [4, 5, 6]\\n\" \" [7, 8, 9]]\"\n\n\ndef multiline():\n    a = b = A()\n    a + b\n\n\ntry:\n    multiline()\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/no_error_message.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef foo():\n    raise ValueError(\"\")\n\n\ndef bar():\n    foo()\n\n\ntry:\n    bar()\nexcept ValueError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/parenthesis.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nclass XYZ:\n    pass\n\n\ndef a(b, c):\n    x = XYZ()\n    x.val = 9\n    (a, b, x.val, ) = 12, 15 / c, 17\n\n\ndef b():\n    foo, bar, baz = {}, XYZ, 0\n    foo[(\"baz\")] = bar() + (a(5, baz))\n\n\ndef c():\n    x = XYZ()\n    x.val = 123\n    x.val += 456 and b()\n\n\ndef d(j):\n    x, y, z = 2, 5, 3\n    xyz = XYZ()\n    xyz.val = 123\n    i = 12 \\\n        ; z = (x * y); y = (j or xyz.val * c() \\\n            + 3)\n\n\ndef e():\n    a = 1\n    (5 \\\n        ) + d(()) + a\n\n\nwith logger.catch():\n    e()\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/source_multilines.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef bug_1(n):\n    return (\"\"\"multi-lines\n\"\"\" + n / 0)\n\n\ndef bug_2(a, b, c):\n    return (1 / 0 + a + b + \\\n            c)\n\n\ndef bug_3(string):\n    return min(10\n           , string, 20 / 0)\n\n\ndef bug_4():\n    a, b = 1, 0\n    dct = {\n        \"foo\": 1,\n        \"bar\": a / b,\n    }\n    return dct\n\n\nstring = \"\"\"multi-lines\n\"\"\"\n\n\ntry:\n    bug_1(10)\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n\n\ntry:\n    bug_2(1, string, 3)\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n\n\ntry:\n    bug_3(string)\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n\n\ntry:\n    bug_4()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/source_strings.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\na = b = 0\n\ntry:\n    a + b\"prefix\" + 'single' + \"\"\"triple\"\"\" + 1 + b\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/syntax_error.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ncode = \"\"\"\nif True:\n    a = 5\n    b = 7 *\n\"\"\"\n\n\ntry:\n    exec(code)\nexcept SyntaxError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/syntax_highlighting.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef a():\n    1 / 0 + 1 * 0 - 1 % 0 // 1**0 @ 1  # Error\n\n\ndef b():\n    a() or False == None != True\n\n\ndef c():\n    1, 2.5, 3.0, 0.4, \"str\", r\"rrr\", rb\"binary\", b()\n\n\ndef d():\n    min(range(1, 10)), list(), dict(), c(), ...\n\n\ndef e(x):\n    x in [1], x in (1,), x in {1}, x in {1: 1}, d()\n\n\ntry:\n    e(0)\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/truncating.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef div():\n    var = \"9\" * 150\n    return 1 / var\n\n\ntry:\n    div()\nexcept TypeError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/diagnose/unprintable_object.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nclass Object:\n    def __repr__(self):\n        raise RuntimeError(\"No way!\")\n\n\ntry:\n    obj = Object()\n    obj + 1 / 0\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/modern/decorate_async_generator.py",
    "content": "from loguru import logger\nimport asyncio\nimport sys\n\nlogger.remove()\n\n# We're truly only testing whether the tests succeed, we do not care about the formatting.\n# These should be regular Pytest test cases, but that is not possible because the syntax is not valid in Python 3.5.\nlogger.add(lambda m: None, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\ndef test_decorate_async_generator():\n    @logger.catch(reraise=True)\n    async def generator(x, y):\n        yield x\n        yield y\n\n    async def coro():\n        out = []\n        async for val in generator(1, 2):\n            out.append(val)\n        return out\n\n    res = asyncio.run(coro())\n    assert res == [1, 2]\n\n\ndef test_decorate_async_generator_with_error():\n    @logger.catch(reraise=False)\n    async def generator(x, y):\n        yield x\n        yield y\n        raise ValueError\n\n    async def coro():\n        out = []\n        async for val in generator(1, 2):\n            out.append(val)\n        return out\n\n    res = asyncio.run(coro())\n    assert res == [1, 2]\n\ndef test_decorate_async_generator_with_error_reraised():\n    @logger.catch(reraise=True)\n    async def generator(x, y):\n        yield x\n        yield y\n        raise ValueError\n\n    async def coro():\n        out = []\n        try:\n            async for val in generator(1, 2):\n                out.append(val)\n        except ValueError:\n            pass\n        else:\n            raise AssertionError(\"ValueError not raised\")\n        return out\n\n    res = asyncio.run(coro())\n    assert res == [1, 2]\n\n\ndef test_decorate_async_generator_then_async_send():\n    @logger.catch\n    async def generator(x, y):\n        yield x\n        yield y\n\n    async def coro():\n        gen = generator(1, 2)\n        await gen.asend(None)\n        await gen.asend(None)\n        try:\n            await gen.asend(None)\n        except StopAsyncIteration:\n            pass\n        else:\n            raise AssertionError(\"StopAsyncIteration not raised\")\n\n    asyncio.run(coro())\n\n\ndef test_decorate_async_generator_then_async_throw():\n    @logger.catch\n    async def generator(x, y):\n        yield x\n        yield y\n\n    async def coro():\n        gen = generator(1, 2)\n        await gen.asend(None)\n        try:\n            await gen.athrow(ValueError)\n        except ValueError:\n            pass\n        else:\n            raise AssertionError(\"ValueError not raised\")\n\n    asyncio.run(coro())\n\n\ntest_decorate_async_generator()\ntest_decorate_async_generator_with_error()\ntest_decorate_async_generator_with_error_reraised()\ntest_decorate_async_generator_then_async_send()\ntest_decorate_async_generator_then_async_throw()\n\nlogger.add(sys.stderr, format=\"{message}\")\nlogger.info(\"Done\")\n"
  },
  {
    "path": "tests/exceptions/source/modern/exception_formatting_async_generator.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\n@logger.catch\nasync def foo(a, b):\n    yield a / b\n\n\nf = foo(1, 0).asend(None)\n\ntry:\n    f.send(None)\nexcept StopAsyncIteration:\n    pass\n"
  },
  {
    "path": "tests/exceptions/source/modern/exception_group_catch.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\nx = ValueError\n\n\ndef a():\n    try:\n        raise ExceptionGroup(\"group\", [ValueError(1)])\n    except* x as e: raise ValueError(2)\n\n\ndef b():\n    try:\n        raise ExceptionGroup(\"group\", [TypeError(1)])\n    except* TypeError: a()\n\n\nwith logger.catch():\n    b()\n"
  },
  {
    "path": "tests/exceptions/source/modern/f_string.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef hello():\n    output = f\"Hello\" + f' ' + f\"\"\"World\"\"\" and world()\n\n\ndef world():\n    name = \"world\"\n    f = 1\n    f\"{name} -> { f }\" and {} or f'{{ {f / 0} }}'\n\n\nwith logger.catch():\n    hello()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_as_cause_and_context.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    1 / 0\n\n\ndef b():\n    raise ValueError(\"Error\")\n\n\n@logger.catch\ndef main():\n    try:\n        a()\n    except Exception as err:\n        error_1 = err\n\n    try:\n        b()\n    except Exception as err:\n        error_2 = err\n\n    try:\n        try:\n            raise ExceptionGroup(\"group_1\", [error_1, error_2])\n        except Exception as err:\n            raise ExceptionGroup(\"group_2\", [error_2, error_1]) from err\n    except Exception as err:\n        raise ExceptionGroup(\"group_3\", [err])\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_max_depth.py",
    "content": "from loguru import logger\nimport sys\n\n\n@logger.catch\ndef main():\n    nesting_left = ValueError(\"Left\")\n    for i in range(100):\n        nesting_left = ExceptionGroup(\"group\", [ValueError(-i), nesting_left])\n\n    nesting_right = ValueError(\"Right\")\n    for i in range(100):\n        nesting_right = ExceptionGroup(\"group\", [nesting_right, ValueError(i)])\n\n    nesting_both = ValueError(\"Both\")\n    for i in range(100):\n        nesting_both = ExceptionGroup(\"group\", [ValueError(-i), nesting_both, ValueError(i)])\n\n    raise ExceptionGroup(\"group\", [nesting_left, nesting_right, nesting_both])\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_max_length.py",
    "content": "from loguru import logger\nimport sys\n\n\n@logger.catch\ndef main():\n    errors = [ValueError(i) for i in range(100)]\n    raise ExceptionGroup(\"group\", errors)\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_nested.py",
    "content": "from loguru import logger\nimport sys\n\n\ndef divide_by_zero():\n    1 / 0\n\n\ndef raise_value_error(value):\n    raise ValueError(value)\n\n\n@logger.catch\ndef main():\n    try:\n        try:\n            divide_by_zero()\n        except Exception as err:\n            error_1 = err\n\n        try:\n            raise_value_error(100)\n        except Exception as err:\n            error_2 = err\n\n        raise ExceptionGroup(\"group_1\", [error_1, error_2])\n    except ExceptionGroup as error_3:\n        try:\n            raise_value_error(-100)\n        except Exception as err:\n            error_4 = err\n\n        raise ExceptionGroup(\"group_2\", [error_4, error_3]) from None\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_simple.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    x = 1\n    y = 0\n    x / y\n\n\ndef b():\n    a()\n\n\ndef c(f):\n    f()\n\n\n@logger.catch\ndef main():\n    try:\n        c(b)\n    except Exception as error_1:\n        try:\n            c(a)\n        except Exception as error_2:\n            try:\n                a()\n            except Exception as error_3:\n                raise ExceptionGroup(\"group\", [error_1, error_2, error_3]) from None\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/grouped_with_cause_and_context.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    1 / 0\n\n\n@logger.catch\ndef main():\n    try:\n        try:\n            a()\n        except Exception as err:\n            raise ValueError(\"ContextError\") from err\n    except Exception as err:\n        from_context = err\n    try:\n        try:\n            a()\n        except Exception as err:\n            raise ValueError(\"CauseError\")\n    except Exception as err:\n        from_cause = err\n\n    try:\n        a()\n    except Exception as err:\n        try:\n            raise ValueError(\"Error\") from err\n        except Exception:\n            raise ExceptionGroup(\"from_context\", [from_context, from_cause])\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/modern/match_statement.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\ndef case(x):\n    y = 1\n    match y / 0:\n        case 1:\n            pass\n\ndef match(x):\n    y = 1\n    match x:\n        case y: case(x)\n\nwith logger.catch():\n    match(1)\n"
  },
  {
    "path": "tests/exceptions/source/modern/notes.py",
    "content": "from loguru import logger\nimport sys\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\n\nwith logger.catch():\n    e = ValueError(\"invalid value\")\n    e.add_note(\"Note\")\n    raise e\n\n\nwith logger.catch():\n    e = ValueError(\"invalid value\")\n    e.add_note(\"Note1\")\n    e.add_note(\"Note2\\nNote3\\n\")\n    raise e\n\n\nwith logger.catch():\n    e = ExceptionGroup(\"Grouped\", [ValueError(1), ValueError(2)])\n    e.add_note(\"Note 1\\nNote 2\")\n    e.add_note(\"Note 3\")\n    raise e\n\nwith logger.catch():\n    e = TabError(\"tab error\")\n    e.add_note(\"Note\")\n    raise e\n\nwith logger.catch():\n    e = SyntaxError(\"syntax error\", (\"<string>\", 1, 8, \"a = 7 *\\n\", 1, 8))\n    e.add_note(\"Note 1\")\n    e.add_note(\"Note 2\")\n    raise e\n\nwith logger.catch():\n    e = TypeError(\"type error\")\n    e.__notes__ = None\n    raise e\n"
  },
  {
    "path": "tests/exceptions/source/modern/positional_only_argument.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\ndef foo(a, /, b, *, c, **d): 1 / 0\n\n\ndef main():\n    foo(1, 2, c=3)\n\n\nwith logger.catch():\n    main()\n"
  },
  {
    "path": "tests/exceptions/source/modern/type_hints.py",
    "content": "# fmt: off\nimport sys\nfrom typing import TypeVar, Tuple\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", colorize=True, backtrace=False, diagnose=True)\n\n\nT = TypeVar(\"T\")\nName = str\n\n\ndef foo(a: int, b: Tuple[Name, float], c: \"Name\") -> T: 1 / 0\n\n\ndef main():\n    bar: Name = foo(1, 2, 3)\n\n\nwith logger.catch():\n    main()\n"
  },
  {
    "path": "tests/exceptions/source/modern/walrus_operator.py",
    "content": "# fmt: off\nimport sys\n\nfrom loguru import logger\n\n\ndef foo():\n    if a := \"a\" + (x:=1/0):\n        pass\n\n\ndef bar():\n    return [y for x in [1, 2] if (y := foo()) != 0]\n\n\n@logger.catch\ndef main():\n    walrus = False\n    (walrus := foo())\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=True)\n\nmain()\n"
  },
  {
    "path": "tests/exceptions/source/others/assertionerror_without_traceback.py",
    "content": "import sys\n\nfrom loguru import logger\n\n\ndef test(diagnose, backtrace):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", diagnose=diagnose, backtrace=backtrace, colorize=True)\n\n    try:\n        assert False\n    except AssertionError:\n        type_, value, _ = sys.exc_info()\n        logger.opt(exception=(type_, value, None)).error(\"\")\n\n\ntest(False, False)\ntest(True, False)\ntest(False, True)\ntest(True, True)\n"
  },
  {
    "path": "tests/exceptions/source/others/broken_but_decorated_repr.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False, catch=True)\n\n\nclass Foo:\n    @logger.catch(reraise=True)\n    def __repr__(self):\n        raise ValueError(\"Something went wrong (Foo)\")\n\n\nclass Bar:\n    def __repr__(self):\n        with logger.catch(reraise=True):\n            raise ValueError(\"Something went wrong (Bar)\")\n\n\nfoo = Foo()\nbar = Bar()\n\ntry:\n    repr(foo)\nexcept ValueError:\n    pass\n\n\ntry:\n    repr(bar)\nexcept ValueError:\n    pass\n"
  },
  {
    "path": "tests/exceptions/source/others/catch_as_context_manager.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\n\n\nwith logger.catch():\n    1 / 0\n"
  },
  {
    "path": "tests/exceptions/source/others/catch_as_decorator_with_parentheses.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\n\n\n@logger.catch()\ndef c(a, b):\n    a / b\n\n\nc(5, b=0)\n"
  },
  {
    "path": "tests/exceptions/source/others/catch_as_decorator_without_parentheses.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\n\n\n@logger.catch\ndef c(a, b=0):\n    a / b\n\n\nc(2)\n"
  },
  {
    "path": "tests/exceptions/source/others/catch_as_function.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\n\n\ndef a():\n    1 / 0\n\n\na = logger.catch()(a)\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/catch_message.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"{message}\", diagnose=False, backtrace=False, colorize=False)\n\n\ndef a():\n    1 / 0\n\n\nwith logger.catch(message=\"An error occurred (1):\"):\n    a()\n\na = logger.catch(message=\"An error occurred (2):\")(a)\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/exception_formatting_coroutine.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\n@logger.catch\nasync def foo(a, b):\n    a / b\n\n\nf = foo(1, 0)\n\ntry:\n    f.send(None)\nexcept StopIteration:\n    pass\n"
  },
  {
    "path": "tests/exceptions/source/others/exception_formatting_function.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\n@logger.catch\ndef a(a, b):\n    a / b\n\n\na(1, 0)\n"
  },
  {
    "path": "tests/exceptions/source/others/exception_formatting_generator.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\n@logger.catch\ndef foo(a, b):\n    yield a / b\n\n\nf = foo(1, 0)\n\ntry:\n    next(f)\nexcept StopIteration:\n    pass\n"
  },
  {
    "path": "tests/exceptions/source/others/exception_in_property.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\nclass A:\n    @property\n    def value(self):\n        try:\n            1 / 0\n        except:\n            logger.opt(exception=True).debug(\"test\")\n            return None\n        else:\n            return \"Never\"\n\n\na = A()\nvalue = a.value\n"
  },
  {
    "path": "tests/exceptions/source/others/handler_formatting_with_context_manager.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(\n    sys.stderr,\n    format=\"{name} {file.name} {function} {line}\",\n    diagnose=False,\n    backtrace=False,\n    colorize=False,\n)\n\n\ndef a():\n    with logger.catch():\n        1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/handler_formatting_with_decorator.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(\n    sys.stderr,\n    format=\"{name} {file.name} {function} {line}\",\n    diagnose=False,\n    backtrace=False,\n    colorize=False,\n)\n\n\n@logger.catch\ndef a():\n    1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/level_name.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(\n    sys.stderr, format=\"{level.name} | {level.no}\", diagnose=False, backtrace=False, colorize=False\n)\n\n\ndef a():\n    with logger.catch(level=\"DEBUG\"):\n        1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/level_number.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(\n    sys.stderr, format=\"{level.name} | {level.no}\", diagnose=False, backtrace=False, colorize=False\n)\n\n\ndef a():\n    with logger.catch(level=13):\n        1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/message_formatting_with_context_manager.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"{message}\", diagnose=False, backtrace=False, colorize=False)\n\n\ndef a():\n    with logger.catch(\n        message=\"{record[name]} {record[file].name} {record[function]} {record[line]}\"\n    ):\n        1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/message_formatting_with_decorator.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"{message}\", diagnose=False, backtrace=False, colorize=False)\n\n\n@logger.catch(message=\"{record[name]} {record[file].name} {record[function]} {record[line]}\")\ndef a():\n    1 / 0\n\n\na()\n"
  },
  {
    "path": "tests/exceptions/source/others/nested_with_reraise.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\n@logger.catch(reraise=True)\ndef foo(a, b):\n    a / b\n\n\n@logger.catch\ndef bar(x, y):\n    try:\n        f = foo(x, y)\n    except Exception as e:\n        raise ValueError from e\n\n\ndef baz():\n    bar(1, 0)\n\n\nif __name__ == \"__main__\":\n    baz()\n"
  },
  {
    "path": "tests/exceptions/source/others/one_liner_recursion.py",
    "content": "# fmt: off\nfrom loguru import logger\n\nimport sys\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=True)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\n\ntry:\n    rec = lambda r, i: 1 / 0 if i == 0 else r(r, i - 1); rec(rec, 10)\nexcept Exception:\n    logger.exception(\"Error\")\n"
  },
  {
    "path": "tests/exceptions/source/others/recursion_error.py",
    "content": "from loguru import logger\n\nimport sys\n\nsys.setrecursionlimit(1000)\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=True)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\n\n\ndef recursive():\n    recursive()\n\n\ntry:\n    recursive()\nexcept Exception:\n    logger.exception(\"Oups\")\n"
  },
  {
    "path": "tests/exceptions/source/others/repeated_lines.py",
    "content": "from loguru import logger\n\nimport sys\n\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=True)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\n\n\ndef recursive(outer, inner):\n    if outer == 0:\n        raise ValueError(\"End of recursion\")\n    if inner == 0:\n        recursive(outer=outer - 1, inner=outer - 1)\n    recursive(outer=outer, inner=inner - 1)\n\n\ntry:\n    recursive(10, 10)\nexcept Exception:\n    logger.exception(\"Oups\")\n"
  },
  {
    "path": "tests/exceptions/source/others/syntaxerror_without_traceback.py",
    "content": "import sys\n\nfrom loguru import logger\n\n\ndef test(diagnose, backtrace):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", diagnose=diagnose, backtrace=backtrace, colorize=True)\n\n    try:\n        exec(\"foo =\")\n    except SyntaxError:\n        type_, value, _ = sys.exc_info()\n        logger.opt(exception=(type_, value, None)).error(\"\")\n\n\ntest(False, False)\ntest(True, False)\ntest(False, True)\ntest(True, True)\n"
  },
  {
    "path": "tests/exceptions/source/others/sys_tracebacklimit.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    b()\n\n\ndef b():\n    c()\n\n\ndef c():\n    d()\n\n\ndef d():\n    e()\n\n\ndef e():\n    f()\n\n\ndef f():\n    g()\n\n\ndef g():\n    h()\n\n\ndef h():\n    i()\n\n\ndef i():\n    j(1, 0)\n\n\ndef j(a, b):\n    a / b\n\n\nsys.tracebacklimit = 5\n\ntry:\n    a()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/others/sys_tracebacklimit_negative.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    b()\n\n\ndef b():\n    c()\n\n\ndef c():\n    d()\n\n\ndef d():\n    e()\n\n\ndef e():\n    f()\n\n\ndef f():\n    g()\n\n\ndef g():\n    h()\n\n\ndef h():\n    i()\n\n\ndef i():\n    j(1, 0)\n\n\ndef j(a, b):\n    a / b\n\n\nsys.tracebacklimit = -1\n\ntry:\n    a()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/others/sys_tracebacklimit_none.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    b()\n\n\ndef b():\n    c()\n\n\ndef c():\n    d()\n\n\ndef d():\n    e()\n\n\ndef e():\n    f()\n\n\ndef f():\n    g()\n\n\ndef g():\n    h()\n\n\ndef h():\n    i()\n\n\ndef i():\n    j(1, 0)\n\n\ndef j(a, b):\n    a / b\n\n\nsys.tracebacklimit = None\n\ntry:\n    a()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/others/sys_tracebacklimit_unset.py",
    "content": "import sys\n\nfrom loguru import logger\n\nlogger.remove()\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=False, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=False, backtrace=True, colorize=False)\nlogger.add(sys.stderr, format=\"\", diagnose=True, backtrace=True, colorize=False)\n\n\ndef a():\n    b()\n\n\ndef b():\n    c()\n\n\ndef c():\n    d()\n\n\ndef d():\n    e()\n\n\ndef e():\n    f()\n\n\ndef f():\n    g()\n\n\ndef g():\n    h()\n\n\ndef h():\n    i()\n\n\ndef i():\n    j(1, 0)\n\n\ndef j(a, b):\n    a / b\n\n\ntry:\n    del sys.tracebacklimit\nexcept AttributeError:\n    pass\n\ntry:\n    a()\nexcept ZeroDivisionError:\n    logger.exception(\"\")\n"
  },
  {
    "path": "tests/exceptions/source/others/zerodivisionerror_without_traceback.py",
    "content": "import sys\n\nfrom loguru import logger\n\n\ndef test(diagnose, backtrace):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", diagnose=diagnose, backtrace=backtrace, colorize=True)\n\n    try:\n        1 / 0\n    except ZeroDivisionError:\n        type_, value, _ = sys.exc_info()\n        logger.opt(exception=(type_, value, None)).error(\"\")\n\n\ntest(False, False)\ntest(True, False)\ntest(False, True)\ntest(True, True)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/_init.py",
    "content": "import os\nimport sys\nimport sysconfig\n\nusersite = os.path.abspath(os.path.join(os.path.dirname(__file__), \"usersite\"))\nsys.path.append(usersite)\nsysconfig._INSTALL_SCHEMES[\"posix_user\"][\"purelib\"] = usersite\n"
  },
  {
    "path": "tests/exceptions/source/ownership/assertion_from_lib.py",
    "content": "import sys\n\nimport _init\nfrom somelib import assertionerror\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        a, b = 1, 2\n        assertionerror(a, b)\n    except AssertionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/assertion_from_local.py",
    "content": "import sys\n\nimport _init\nfrom somelib import assertionerror\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        a, b = 1, 2\n        assert a == b\n    except AssertionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/callback.py",
    "content": "import sys\n\nimport _init\nfrom somelib import callme, divide\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    def callback():\n        divide(1, 0)\n\n    try:\n        callme(callback)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/catch_decorator.py",
    "content": "import sys\n\nimport _init\nfrom somelib import divide\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    @logger.catch\n    def foo():\n        divide(1, 0)\n\n    foo()\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/catch_decorator_from_lib.py",
    "content": "import sys\n\nimport _init\nfrom somelib import callme, divide\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    @logger.catch\n    def callback():\n        divide(1, 0)\n\n    callme(callback)\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/decorated_callback.py",
    "content": "import sys\n\nimport _init\nfrom somelib import callme, divide\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    @logger.catch\n    def callback():\n        a, b = 1, 0\n        a / b\n\n    callme(callback)\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/direct.py",
    "content": "import sys\n\nimport _init\nfrom somelib import divide\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        divide(10, 0)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/indirect.py",
    "content": "import sys\n\nimport _init\nfrom somelib import divide_indirect\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        divide_indirect(10, 0)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/string_lib.py",
    "content": "import sys\n\nimport _init\nfrom somelib import execute\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        execute()\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/string_source.py",
    "content": "import sys\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    def foo():\n        1 / 0\n\n    try:\n        exec(\"foo()\")\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/syntaxerror.py",
    "content": "import sys\n\nimport _init\nfrom somelib import syntaxerror\n\nfrom loguru import logger\n\n\ndef test(*, backtrace, colorize, diagnose):\n    logger.remove()\n    logger.add(sys.stderr, format=\"\", colorize=colorize, backtrace=backtrace, diagnose=diagnose)\n\n    try:\n        syntaxerror()\n    except SyntaxError:\n        logger.exception(\"\")\n\n\ntest(backtrace=True, colorize=True, diagnose=True)\ntest(backtrace=False, colorize=True, diagnose=True)\ntest(backtrace=True, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=True, diagnose=False)\ntest(backtrace=False, colorize=False, diagnose=False)\n"
  },
  {
    "path": "tests/exceptions/source/ownership/usersite/somelib/__init__.py",
    "content": "def divide(x, y):\n    x / y\n\n\ndef divide_indirect(a, b):\n    divide(a, b)\n\n\ndef callme(callback):\n    callback()\n\n\ndef execute():\n    exec(\"divide(1, 0)\")\n\n\ndef syntaxerror():\n    exec(\"foo =\")\n\n\ndef assertionerror(x, y):\n    assert x == y\n"
  },
  {
    "path": "tests/test_activation.py",
    "content": "import pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\n    (\"name\", \"should_log\"),\n    [\n        (\"\", False),\n        (\"tests\", False),\n        (\"test\", True),\n        (\"testss\", True),\n        (\"tests.\", True),\n        (\"tests.test_activation\", False),\n        (\"tests.test_activation.\", True),\n        (\"test_activation\", True),\n        (\".\", True),\n    ],\n)\ndef test_disable(writer, name, should_log):\n    logger.add(writer, format=\"{message}\")\n    logger.disable(name)\n    logger.debug(\"message\")\n    result = writer.read()\n\n    if should_log:\n        assert result == \"message\\n\"\n    else:\n        assert result == \"\"\n\n\n@pytest.mark.parametrize(\n    (\"name\", \"should_log\"),\n    [\n        (\"\", True),\n        (\"tests\", True),\n        (\"test\", False),\n        (\"testss\", False),\n        (\"tests.\", False),\n        (\"tests.test_activation\", True),\n        (\"tests.test_activation.\", False),\n        (\"test_activation\", False),\n        (\".\", False),\n    ],\n)\ndef test_enable(writer, name, should_log):\n    logger.add(writer, format=\"{message}\")\n    logger.disable(\"\")\n    logger.enable(name)\n    logger.debug(\"message\")\n    result = writer.read()\n\n    if should_log:\n        assert result == \"message\\n\"\n    else:\n        assert result == \"\"\n\n\ndef test_log_before_enable(writer):\n    logger.add(writer, format=\"{message}\")\n    logger.disable(\"\")\n    logger.debug(\"nope\")\n    logger.enable(\"tests\")\n    logger.debug(\"yes\")\n    result = writer.read()\n    assert result == \"yes\\n\"\n\n\ndef test_log_before_disable(writer):\n    logger.add(writer, format=\"{message}\")\n    logger.enable(\"\")\n    logger.debug(\"yes\")\n    logger.disable(\"tests\")\n    logger.debug(\"nope\")\n    result = writer.read()\n    assert result == \"yes\\n\"\n\n\ndef test_multiple_activations():\n    def n():\n        return len(logger._core.activation_list)\n\n    assert n() == 0\n    logger.enable(\"\")\n    assert n() == 0\n    logger.disable(\"\")\n    assert n() == 1\n    logger.enable(\"foo\")\n    assert n() == 2\n    logger.enable(\"foo.bar\")\n    assert n() == 2\n    logger.disable(\"foo\")\n    assert n() == 1\n    logger.disable(\"foo.bar\")\n    assert n() == 1\n    logger.enable(\"foo.bar\")\n    assert n() == 2\n    logger.disable(\"foo.bar.baz\")\n    assert n() == 3\n    logger.disable(\"foo.baz\")\n    assert n() == 3\n    logger.disable(\"foo.baz.bar\")\n    assert n() == 3\n    logger.enable(\"foo.baz.bar\")\n    assert n() == 4\n    logger.enable(\"\")\n    assert n() == 0\n\n\ndef test_log_before_enable_incomplete_frame_context(writer, incomplete_frame_context):\n    logger.add(writer, format=\"{message}\")\n    logger.disable(None)\n    logger.debug(\"nope\")\n    logger.enable(None)\n    logger.debug(\"yes\")\n    result = writer.read()\n    assert result == \"yes\\n\"\n\n\ndef test_log_before_disable_incomplete_frame_context(writer, incomplete_frame_context):\n    logger.add(writer, format=\"{message}\")\n    logger.enable(None)\n    logger.debug(\"yes\")\n    logger.disable(None)\n    logger.debug(\"nope\")\n    result = writer.read()\n    assert result == \"yes\\n\"\n\n\ndef test_incomplete_frame_context_with_others(writer, incomplete_frame_context):\n    logger.add(writer, format=\"{message}\")\n    logger.info(\"1\")\n    logger.enable(None)\n    logger.disable(\"foobar\")\n    logger.enable(\"foo.bar\")\n    logger.disable(None)\n    logger.info(\"2\")\n    logger.enable(\"foobar\")\n    logger.enable(None)\n    logger.info(\"3\")\n    assert writer.read() == \"1\\n3\\n\"\n\n\n@pytest.mark.parametrize(\"name\", [42, [], object()])\ndef test_invalid_enable_name(name):\n    with pytest.raises(TypeError):\n        logger.enable(name)\n\n\n@pytest.mark.parametrize(\"name\", [42, [], object()])\ndef test_invalid_disable_name(name):\n    with pytest.raises(TypeError):\n        logger.disable(name)\n"
  },
  {
    "path": "tests/test_add_option_backtrace.py",
    "content": "from loguru import logger\n\n# See \"test_catch_exceptions.py\" for extended testing\n\n\ndef test_backtrace(writer):\n    logger.add(writer, format=\"{message}\", backtrace=True)\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.exception(\"\")\n    result_with = writer.read().strip()\n\n    logger.remove()\n    writer.clear()\n\n    logger.add(writer, format=\"{message}\", backtrace=False)\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.exception(\"\")\n    result_without = writer.read().strip()\n\n    assert len(result_with.splitlines()) > len(result_without.splitlines())\n"
  },
  {
    "path": "tests/test_add_option_catch.py",
    "content": "import re\nimport sys\nimport time\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import default_threading_excepthook\n\n\ndef broken_sink(m):\n    raise ValueError(\"Error!\")\n\n\ndef test_catch_is_true(capsys):\n    logger.add(broken_sink, catch=True)\n    logger.debug(\"Fail\")\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err != \"\"\n\n\ndef test_catch_is_false(capsys):\n    logger.add(broken_sink, catch=False)\n    with pytest.raises(ValueError, match=\"Error!\"):\n        logger.debug(\"Fail\")\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\ndef test_no_sys_stderr(capsys, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(sys, \"stderr\", None)\n        logger.add(broken_sink, catch=True)\n        logger.debug(\"a\")\n\n        out, err = capsys.readouterr()\n        assert out == err == \"\"\n\n\ndef test_broken_sys_stderr(capsys, monkeypatch):\n    def broken_write(*args, **kwargs):\n        raise OSError\n\n    with monkeypatch.context() as context:\n        context.setattr(sys.stderr, \"write\", broken_write)\n        logger.add(broken_sink, catch=True)\n        logger.debug(\"a\")\n\n        out, err = capsys.readouterr()\n        assert out == err == \"\"\n\n\ndef test_encoding_error(capsys):\n    def sink(m):\n        raise UnicodeEncodeError(\"utf8\", \"\", 10, 11, \"too bad\")\n\n    logger.add(sink, catch=True)\n    logger.debug(\"test\")\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert lines[1].startswith(\"Record was: {\")\n    assert lines[1].endswith(\"}\")\n    assert lines[-2].startswith(\"UnicodeEncodeError:\")\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_unprintable_record(writer, capsys):\n    class Unprintable:\n        def __repr__(self):\n            raise ValueError(\"Failed\")\n\n    logger.add(writer, format=\"{message} {extra[unprintable]}\", catch=True)\n    logger.bind(unprintable=1).debug(\"a\")\n    logger.bind(unprintable=Unprintable()).debug(\"b\")\n    logger.bind(unprintable=2).debug(\"c\")\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    assert writer.read() == \"a 1\\nc 2\\n\"\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert lines[1] == \"Record was: /!\\\\ Unprintable record /!\\\\\"\n    assert lines[-2] == \"ValueError: Failed\"\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\n@pytest.mark.parametrize(\"enqueue\", [False, True])\ndef test_broken_sink_message(capsys, enqueue):\n    logger.add(broken_sink, catch=True, enqueue=enqueue)\n    logger.debug(\"Oops\")\n    time.sleep(0.1)\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Oops.*\\}\", lines[1])\n    assert lines[-2].startswith(\"ValueError: Error!\")\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\n@pytest.mark.parametrize(\"enqueue\", [False, True])\ndef test_broken_sink_caught_keep_working(enqueue):\n    output = \"\"\n\n    def half_broken_sink(m):\n        nonlocal output\n        if m.startswith(\"NOK\"):\n            raise ValueError(\"Broken!\")\n        output += m\n\n    logger.add(half_broken_sink, format=\"{message}\", enqueue=enqueue, catch=True)\n    logger.info(\"A\")\n    logger.info(\"NOK\")\n    logger.info(\"B\")\n\n    time.sleep(0.1)\n    assert output == \"A\\nB\\n\"\n\n\ndef test_broken_sink_not_caught_enqueue():\n    called = 0\n\n    def broken_sink(m):\n        nonlocal called\n        called += 1\n        raise ValueError(\"Nop\")\n\n    logger.add(broken_sink, format=\"{message}\", enqueue=True, catch=False)\n\n    with default_threading_excepthook():\n        logger.info(\"A\")\n        logger.info(\"B\")\n        time.sleep(0.1)\n\n    assert called == 2\n"
  },
  {
    "path": "tests/test_add_option_colorize.py",
    "content": "import os\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import StreamIsattyException, StreamIsattyFalse, StreamIsattyTrue, parse\n\n\n@pytest.mark.parametrize(\n    (\"format\", \"message\", \"expected\"),\n    [\n        (\"<red>{message}</red>\", \"Foo\", parse(\"<red>Foo</red>\\n\")),\n        (lambda _: \"<red>{message}</red>\", \"Bar\", parse(\"<red>Bar</red>\")),\n        (\"{message}\", \"<red>Baz</red>\", \"<red>Baz</red>\\n\"),\n        (\"{{<red>{message:}</red>}}\", \"A\", parse(\"{<red>A</red>}\\n\")),\n    ],\n)\ndef test_colorized_format(format, message, expected, writer):\n    logger.add(writer, format=format, colorize=True)\n    logger.debug(message)\n    assert writer.read() == expected\n\n\n@pytest.mark.parametrize(\n    (\"format\", \"message\", \"expected\"),\n    [\n        (\"<red>{message}</red>\", \"Foo\", \"Foo\\n\"),\n        (lambda _: \"<red>{message}</red>\", \"Bar\", \"Bar\"),\n        (\"{message}\", \"<red>Baz</red>\", \"<red>Baz</red>\\n\"),\n        (\"{{<red>{message:}</red>}}\", \"A\", \"{A}\\n\"),\n    ],\n)\ndef test_decolorized_format(format, message, expected, writer):\n    logger.add(writer, format=format, colorize=False)\n    logger.debug(message)\n    assert writer.read() == expected\n\n\n@pytest.mark.parametrize(\n    \"stream\", [StreamIsattyTrue(), StreamIsattyFalse(), StreamIsattyException()]\n)\ndef test_colorize_stream(stream):\n    logger.add(stream, format=\"<blue>{message}</blue>\", colorize=True)\n    logger.debug(\"Message\")\n    assert stream.getvalue() == parse(\"<blue>Message</blue>\\n\")\n\n\n@pytest.mark.parametrize(\n    \"stream\", [StreamIsattyTrue(), StreamIsattyFalse(), StreamIsattyException()]\n)\ndef test_decolorize_stream(stream):\n    stream = StreamIsattyFalse()\n    logger.add(stream, format=\"<blue>{message}</blue>\", colorize=False)\n    logger.debug(\"Message\")\n    assert stream.getvalue() == \"Message\\n\"\n\n\ndef test_automatic_detection_when_stream_is_a_tty():\n    stream = StreamIsattyTrue()\n    logger.add(stream, format=\"<blue>{message}</blue>\", colorize=None)\n    logger.debug(\"Message\")\n    assert stream.getvalue() == parse(\"<blue>Message</blue>\\n\")\n\n\ndef test_automatic_detection_when_stream_is_not_a_tty():\n    stream = StreamIsattyFalse()\n    logger.add(stream, format=\"<blue>{message}</blue>\", colorize=None)\n    logger.debug(\"Message\")\n    assert stream.getvalue() == \"Message\\n\"\n\n\ndef test_automatic_detection_when_stream_has_no_isatty():\n    stream = StreamIsattyException()\n    logger.add(stream, format=\"<blue>{message}</blue>\", colorize=None)\n    logger.debug(\"Message\")\n    assert stream.getvalue() == \"Message\\n\"\n\n\ndef test_override_no_color(monkeypatch):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"NO_COLOR\", \"1\")\n        logger.add(stream, format=\"<blue>{message}</blue>\", colorize=True)\n        logger.debug(\"Message\", colorize=False)\n        assert stream.getvalue() == parse(\"<blue>Message</blue>\\n\")\n\n\ndef test_override_force_color(monkeypatch):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"FORCE_COLOR\", \"1\")\n        logger.add(stream, format=\"<blue>{message}</blue>\", colorize=False)\n        logger.debug(\"Message\", colorize=False)\n        assert stream.getvalue() == \"Message\\n\"\n"
  },
  {
    "path": "tests/test_add_option_context.py",
    "content": "import multiprocessing\nimport os\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom loguru import logger\n\n\ndef test_using_multiprocessing_directly_if_context_is_none():\n    logger.add(lambda _: None, enqueue=True, context=None)\n    assert multiprocessing.get_start_method(allow_none=True) is not None\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\n@pytest.mark.parametrize(\"context_name\", [\"fork\", \"forkserver\"])\ndef test_fork_context_as_string(context_name):\n    context = multiprocessing.get_context(context_name)\n    with patch.object(type(context), \"Lock\", wraps=context.Lock) as mock:\n        logger.add(lambda _: None, context=context_name, enqueue=True)\n        assert mock.called\n    assert multiprocessing.get_start_method(allow_none=True) is None\n\n\ndef test_spawn_context_as_string():\n    context = multiprocessing.get_context(\"spawn\")\n    with patch.object(type(context), \"Lock\", wraps=context.Lock) as mock:\n        logger.add(lambda _: None, context=\"spawn\", enqueue=True)\n        assert mock.called\n    assert multiprocessing.get_start_method(allow_none=True) is None\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\n@pytest.mark.parametrize(\"context_name\", [\"fork\", \"forkserver\"])\ndef test_fork_context_as_object(context_name):\n    context = multiprocessing.get_context(context_name)\n    with patch.object(type(context), \"Lock\", wraps=context.Lock) as mock:\n        logger.add(lambda _: None, context=context, enqueue=True)\n        assert mock.called\n    assert multiprocessing.get_start_method(allow_none=True) is None\n\n\ndef test_spawn_context_as_object():\n    context = multiprocessing.get_context(\"spawn\")\n    with patch.object(type(context), \"Lock\", wraps=context.Lock) as mock:\n        logger.add(lambda _: None, context=context, enqueue=True)\n        assert mock.called\n    assert multiprocessing.get_start_method(allow_none=True) is None\n\n\ndef test_global_start_method_is_none_if_enqueue_is_false():\n    logger.add(lambda _: None, enqueue=False, context=None)\n    assert multiprocessing.get_start_method(allow_none=True) is None\n\n\ndef test_invalid_context_name():\n    with pytest.raises(ValueError, match=r\"cannot find context for\"):\n        logger.add(lambda _: None, context=\"foobar\")\n\n\n@pytest.mark.parametrize(\"context\", [42, object()])\ndef test_invalid_context_object(context):\n    with pytest.raises(\n        TypeError,\n        match=r\"Invalid context, it should be a string or a multiprocessing context\",\n    ):\n        logger.add(lambda _: None, context=context)\n"
  },
  {
    "path": "tests/test_add_option_diagnose.py",
    "content": "from loguru import logger\n\n# See \"test_catch_exceptions.py\" for extended testing\n\n\ndef test_diagnose(writer):\n    logger.add(writer, format=\"{message}\", diagnose=True)\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.exception(\"\")\n    result_with = writer.read().strip()\n\n    logger.remove()\n    writer.clear()\n\n    logger.add(writer, format=\"{message}\", diagnose=False)\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.exception(\"\")\n    result_without = writer.read().strip()\n\n    assert len(result_with.splitlines()) > len(result_without.splitlines())\n"
  },
  {
    "path": "tests/test_add_option_enqueue.py",
    "content": "import pickle\nimport re\nimport sys\nimport time\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import default_threading_excepthook\n\n\nclass NotPicklable:\n    def __getstate__(self):\n        raise pickle.PicklingError(\"You shall not serialize me!\")\n\n    def __setstate__(self, state):\n        pass\n\n\nclass NotPicklableTypeError:\n    def __getstate__(self):\n        raise TypeError(\"You shall not serialize me!\")\n\n    def __setstate__(self, state):\n        pass\n\n\nclass NotUnpicklable:\n    def __getstate__(self):\n        return \"...\"\n\n    def __setstate__(self, state):\n        raise pickle.UnpicklingError(\"You shall not de-serialize me!\")\n\n\nclass NotUnpicklableTypeError:\n    def __getstate__(self):\n        return \"...\"\n\n    def __setstate__(self, state):\n        raise TypeError(\"You shall not de-serialize me!\")\n\n\nclass NotWritable:\n    def write(self, message):\n        if \"fail\" in message.record[\"extra\"]:\n            raise RuntimeError(\"You asked me to fail...\")\n        print(message, end=\"\")\n\n\ndef test_enqueue():\n    x = []\n\n    def sink(message):\n        time.sleep(0.1)\n        x.append(message)\n\n    logger.add(sink, format=\"{message}\", enqueue=True)\n    logger.debug(\"Test\")\n    assert len(x) == 0\n    logger.complete()\n    assert len(x) == 1\n    assert x[0] == \"Test\\n\"\n\n\ndef test_enqueue_with_exception():\n    x = []\n\n    def sink(message):\n        time.sleep(0.1)\n        x.append(message)\n\n    logger.add(sink, format=\"{message}\", enqueue=True)\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error\")\n\n    assert len(x) == 0\n    logger.complete()\n    assert len(x) == 1\n    lines = x[0].splitlines()\n\n    assert lines[0] == \"Error\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\ndef test_caught_exception_queue_put(writer, capsys):\n    logger.add(writer, enqueue=True, catch=True, format=\"{message}\")\n\n    logger.info(\"It's fine\")\n    logger.bind(broken=NotPicklable()).info(\"Bye bye...\")\n    logger.info(\"It's fine again\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert writer.read() == \"It's fine\\nIt's fine again\\n\"\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Bye bye.*\\}\", lines[1])\n    assert \"PicklingError: You shall not serialize me!\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_caught_exception_queue_get(writer, capsys):\n    logger.add(writer, enqueue=True, catch=True, format=\"{message}\")\n\n    logger.info(\"It's fine\")\n    logger.bind(broken=NotUnpicklable()).info(\"Bye bye...\")\n    logger.info(\"It's fine again\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert writer.read() == \"It's fine\\nIt's fine again\\n\"\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert lines[1] == \"Record was: None\"\n    assert \"UnpicklingError: You shall not de-serialize me!\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_caught_exception_sink_write(capsys):\n    logger.add(NotWritable(), enqueue=True, catch=True, format=\"{message}\")\n\n    logger.info(\"It's fine\")\n    logger.bind(fail=True).info(\"Bye bye...\")\n    logger.info(\"It's fine again\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert out == \"It's fine\\nIt's fine again\\n\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Bye bye.*\\}\", lines[1])\n    assert \"RuntimeError: You asked me to fail...\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_not_caught_exception_queue_put(writer, capsys):\n    logger.add(writer, enqueue=True, catch=False, format=\"{message}\")\n\n    logger.info(\"It's fine\")\n\n    with pytest.raises(pickle.PicklingError, match=r\"You shall not serialize me!\"):\n        logger.bind(broken=NotPicklable()).info(\"Bye bye...\")\n\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert writer.read() == \"It's fine\\n\"\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_not_caught_exception_queue_get(writer, capsys):\n    logger.add(writer, enqueue=True, catch=False, format=\"{message}\")\n\n    with default_threading_excepthook():\n        logger.info(\"It's fine\")\n        logger.bind(broken=NotUnpicklable()).info(\"Bye bye...\")\n        logger.info(\"It's fine again\")\n        logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert writer.read() == \"It's fine\\nIt's fine again\\n\"\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert lines[1] == \"Record was: None\"\n    assert \"UnpicklingError: You shall not de-serialize me!\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_not_caught_exception_sink_write(capsys):\n    logger.add(NotWritable(), enqueue=True, catch=False, format=\"{message}\")\n\n    with default_threading_excepthook():\n        logger.info(\"It's fine\")\n        logger.bind(fail=True).info(\"Bye bye...\")\n        logger.info(\"It's fine again\")\n        logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert out == \"It's fine\\nIt's fine again\\n\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Bye bye.*\\}\", lines[1])\n    assert \"RuntimeError: You asked me to fail...\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_not_caught_exception_sink_write_then_complete(capsys):\n    logger.add(NotWritable(), enqueue=True, catch=False, format=\"{message}\")\n\n    with default_threading_excepthook():\n        logger.bind(fail=True).info(\"Bye bye...\")\n        logger.complete()\n        logger.complete()  # Called twice to ensure it's re-usable.\n        logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Bye bye.*\\}\", lines[1])\n    assert \"RuntimeError: You asked me to fail...\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_not_caught_exception_queue_get_then_complete(writer, capsys):\n    logger.add(writer, enqueue=True, catch=False, format=\"{message}\")\n\n    with default_threading_excepthook():\n        logger.bind(broken=NotUnpicklable()).info(\"Bye bye...\")\n        logger.complete()\n        logger.complete()\n        logger.remove()\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n    assert writer.read() == \"\"\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert lines[1] == \"Record was: None\"\n    assert \"UnpicklingError: You shall not de-serialize me!\" in err\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_wait_for_all_messages_enqueued(capsys):\n    def slow_sink(message):\n        time.sleep(0.01)\n        sys.stderr.write(message)\n\n    logger.add(slow_sink, enqueue=True, catch=False, format=\"{message}\")\n\n    for i in range(10):\n        logger.info(i)\n\n    logger.complete()\n\n    out, err = capsys.readouterr()\n\n    assert out == \"\"\n    assert err == \"\".join(\"%d\\n\" % i for i in range(10))\n\n\n@pytest.mark.parametrize(\"exception_value\", [NotPicklable(), NotPicklableTypeError()])\ndef test_logging_not_picklable_exception(exception_value):\n    exception = None\n\n    def sink(message):\n        nonlocal exception\n        exception = message.record[\"exception\"]\n\n    logger.add(sink, enqueue=True, catch=False)\n\n    try:\n        raise ValueError(exception_value)\n    except Exception:\n        logger.exception(\"Oups\")\n\n    logger.remove()\n\n    type_, value, traceback_ = exception\n    assert type_ is ValueError\n    assert value is None\n    assert traceback_ is None\n\n\n@pytest.mark.parametrize(\"exception_value\", [NotUnpicklable(), NotUnpicklableTypeError()])\ndef test_logging_not_unpicklable_exception(exception_value):\n    exception = None\n\n    def sink(message):\n        nonlocal exception\n        exception = message.record[\"exception\"]\n\n    logger.add(sink, enqueue=True, catch=False)\n\n    try:\n        raise ValueError(exception_value)\n    except Exception:\n        logger.exception(\"Oups\")\n\n    logger.remove()\n\n    type_, value, traceback_ = exception\n    assert type_ is ValueError\n    assert value is None\n    assert traceback_ is None\n"
  },
  {
    "path": "tests/test_add_option_filter.py",
    "content": "import re\n\nimport pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\n    \"filter\",\n    [\n        None,\n        \"\",\n        \"tests\",\n        \"tests.test_add_option_filter\",\n        (lambda r: True),\n        (lambda r: r[\"level\"].name == \"DEBUG\"),\n        {},\n        {\"\": \"DEBUG\"},\n        {\"tests\": True},\n        {\"tests.test_add_option_filter\": 10},\n        {\"\": \"WARNING\", \"tests\": 0},\n        {\"tests.test_add_option_filter\": 5, \"tests\": False},\n        {\"tests.test_add_option_filter.foobar\": False},\n        {\"tests.\": False},\n        {\"tests.test_add_option_filter.\": False},\n    ],\n)\ndef test_filtered_in(filter, writer):\n    logger.add(writer, filter=filter, format=\"{message}\")\n    logger.debug(\"Test Filter\")\n    assert writer.read() == \"Test Filter\\n\"\n\n\n@pytest.mark.parametrize(\n    \"filter\",\n    [\n        \"test\",\n        \"testss\",\n        \"tests.\",\n        \"tests.test_add_option_filter.\",\n        \".\",\n        (lambda r: False),\n        (lambda r: r[\"level\"].no != 10),\n        {\"\": False},\n        {\"\": True, \"tests\": 50},\n        {\"tests.test_add_option_filter\": False},\n        {\"tests\": \"WARNING\"},\n        {\"tests\": 5, \"tests.test_add_option_filter\": 40},\n        {\"\": 100, \"tests.test_add_option_filter.foobar\": True},\n    ],\n)\ndef test_filtered_out(filter, writer):\n    logger.add(writer, filter=filter, format=\"{message}\")\n    logger.debug(\"Test Filter\")\n    assert writer.read() == \"\"\n\n\n@pytest.mark.parametrize(\n    \"filter\",\n    [\n        None,\n        lambda _: True,\n        {},\n        {None: 0},\n        {\"\": False},\n        {\"tests\": False, None: True},\n        {\"unrelated\": 100},\n        {None: \"INFO\", \"\": \"WARNING\"},\n    ],\n)\ndef test_filtered_in_incomplete_frame_context(writer, filter, incomplete_frame_context):\n    logger.add(writer, filter=filter, format=\"{message}\", catch=False)\n    logger.info(\"It's ok\")\n    assert writer.read() == \"It's ok\\n\"\n\n\n@pytest.mark.parametrize(\n    \"filter\",\n    [\n        \"tests\",\n        \"\",\n        lambda _: False,\n        {None: False},\n        {\"\": 0, None: \"WARNING\"},\n        {None: 100, \"tests\": True},\n    ],\n)\ndef test_filtered_out_incomplete_frame_context(writer, filter, incomplete_frame_context):\n    logger.add(writer, filter=filter, format=\"{message}\", catch=False)\n    logger.info(\"It's not ok\")\n    assert writer.read() == \"\"\n\n\n@pytest.mark.parametrize(\"filter\", [-1, 3.4, object()])\ndef test_invalid_filter(writer, filter):\n    with pytest.raises(TypeError):\n        logger.add(writer, filter=filter)\n\n\n@pytest.mark.parametrize(\"filter\", [{\"foo\": None}, {\"foo\": 2.5}, {\"a\": \"DEBUG\", \"b\": object()}])\ndef test_invalid_filter_dict_level_types(writer, filter):\n    with pytest.raises(TypeError):\n        logger.add(writer, filter=filter)\n\n\n@pytest.mark.parametrize(\"filter\", [{1: \"DEBUG\"}, {object(): 10}])\ndef test_invalid_filter_dict_module_types(writer, filter):\n    with pytest.raises(TypeError):\n        logger.add(writer, filter=filter)\n\n\n@pytest.mark.parametrize(\"filter\", [{\"foo\": \"UNKNOWN_LEVEL\"}, {\"tests.test_add_option_filter\": \"\"}])\ndef test_invalid_filter_dict_values_unknown_level(writer, filter):\n    with pytest.raises(\n        ValueError,\n        match=(\n            \"The filter dict contains a module '[^']*' associated to \"\n            \"a level name which does not exist: '[^']*'\"\n        ),\n    ):\n        logger.add(writer, filter=filter)\n\n\ndef test_invalid_filter_dict_values_wrong_integer_value(writer):\n    with pytest.raises(\n        ValueError,\n        match=(\n            \"The filter dict contains a module '[^']*' associated to an invalid level, \"\n            \"it should be a positive integer, not: '[^']*'\"\n        ),\n    ):\n        logger.add(writer, filter={\"tests\": -1})\n\n\ndef test_filter_dict_with_custom_level(writer):\n    logger.level(\"MY_LEVEL\", 6, color=\"\", icon=\"\")\n    logger.add(writer, level=0, filter={\"tests\": \"MY_LEVEL\"}, format=\"{message}\")\n    logger.log(3, \"No\")\n    logger.log(9, \"Yes\")\n    assert writer.read() == \"Yes\\n\"\n\n\ndef test_invalid_filter_builtin(writer):\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            \"The built-in 'filter()' function cannot be used as a 'filter' parameter, this is \"\n            \"most likely a mistake (please double-check the arguments passed to 'logger.add()'\"\n        ),\n    ):\n        logger.add(writer, filter=filter)\n"
  },
  {
    "path": "tests/test_add_option_format.py",
    "content": "import pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\n    (\"message\", \"format\", \"expected\"),\n    [\n        (\"a\", \"Message: {message}\", \"Message: a\\n\"),\n        (\"b\", \"Nope\", \"Nope\\n\"),\n        (\"c\", \"{level} {message} {level}\", \"DEBUG c DEBUG\\n\"),\n        (\"d\", \"{message} {level} {level.no} {level.name}\", \"d DEBUG 10 DEBUG\\n\"),\n        (\"e\", lambda _: \"{message}\", \"e\"),\n        (\"f\", lambda r: \"{message} \" + r[\"level\"].name, \"f DEBUG\"),\n    ],\n)\ndef test_format(message, format, expected, writer):\n    logger.add(writer, format=format)\n    logger.debug(message)\n    assert writer.read() == expected\n\n\ndef test_progressive_format(writer):\n    def formatter(record):\n        fmt = \"[{level.name}] {message}\"\n        if \"noend\" not in record[\"extra\"]:\n            fmt += \"\\n\"\n        return fmt\n\n    logger.add(writer, format=formatter)\n    logger.bind(noend=True).debug(\"Start: \")\n    for _ in range(5):\n        logger.opt(raw=True).debug(\".\")\n    logger.opt(raw=True).debug(\"\\n\")\n    logger.debug(\"End\")\n    assert writer.read() == (\"[DEBUG] Start: .....\\n\" \"[DEBUG] End\\n\")\n\n\ndef test_function_format_without_exception(writer):\n    logger.add(writer, format=lambda _: \"{message}\\n\")\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error!\")\n    assert writer.read() == \"Error!\\n\"\n\n\ndef test_function_format_with_exception(writer):\n    logger.add(writer, format=lambda _: \"{message}\\n{exception}\")\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error!\")\n    lines = writer.read().splitlines()\n    assert lines[0] == \"Error!\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\n@pytest.mark.parametrize(\"format\", [-1, 3.4, object()])\ndef test_invalid_format(writer, format):\n    with pytest.raises(TypeError):\n        logger.add(writer, format=format)\n\n\n@pytest.mark.parametrize(\"format\", [\"<red>\", \"</red>\", \"</level><level>\", \"</>\", \"<foobar>\"])\ndef test_invalid_markups(writer, format):\n    with pytest.raises(\n        ValueError, match=\"^Invalid format, color markups could not be parsed correctly$\"\n    ):\n        logger.add(writer, format=format)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_markup_in_field(writer, colorize):\n    class F:\n        def __format__(self, spec):\n            return spec\n\n    logger.add(writer, format=\"{extra[f]:</>} {extra[f]: <blue> } {message}\", colorize=colorize)\n    logger.bind(f=F()).info(\"Test\")\n\n    assert writer.read() == \"</>  <blue>  Test\\n\"\n\n\ndef test_invalid_format_builtin(writer):\n    with pytest.raises(ValueError, match=r\".* most likely a mistake\"):\n        logger.add(writer, format=format)\n"
  },
  {
    "path": "tests/test_add_option_kwargs.py",
    "content": "import pytest\n\nfrom loguru import logger\n\n\ndef test_file_mode_a(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"base\\n\")\n    logger.add(file, format=\"{message}\", mode=\"a\")\n    logger.debug(\"msg\")\n    assert file.read_text() == \"base\\nmsg\\n\"\n\n\ndef test_file_mode_w(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"base\\n\")\n    logger.add(file, format=\"{message}\", mode=\"w\")\n    logger.debug(\"msg\")\n    assert file.read_text() == \"msg\\n\"\n\n\ndef test_file_auto_buffering(tmp_path):\n    # There doesn't seem to be a reliable way to known buffer size for text files.\n    # We perform a preliminary test to ensure empirically that 128 <= buffer size <= 65536.\n    dummy_filepath = tmp_path / \"dummy.txt\"\n    with open(str(dummy_filepath), buffering=-1, mode=\"w\") as dummy_file:\n        dummy_file.write(\".\" * 127)\n        if dummy_filepath.read_text() != \"\":\n            pytest.skip(\"Size buffer for text files is too small.\")\n        dummy_file.write(\".\" * (65536 - 127))\n        if dummy_filepath.read_text() == \"\":\n            pytest.skip(\"Size buffer for text files is too big.\")\n\n    filepath = tmp_path / \"test.log\"\n    logger.add(filepath, format=\"{message}\", buffering=-1)\n    logger.debug(\"A short message.\")\n    assert filepath.read_text() == \"\"\n    logger.debug(\"A long message\" + \".\" * 65536)\n    assert filepath.read_text() != \"\"\n\n\ndef test_file_line_buffering(tmp_path):\n    filepath = tmp_path / \"test.log\"\n    logger.add(filepath, format=lambda _: \"{message}\", buffering=1)\n    logger.debug(\"Without newline\")\n    assert filepath.read_text() == \"\"\n    logger.debug(\"With newline\\n\")\n    assert filepath.read_text() != \"\"\n\n\ndef test_invalid_function_kwargs():\n    def function(message):\n        pass\n\n    with pytest.raises(TypeError, match=r\"add\\(\\) got an unexpected keyword argument\"):\n        logger.add(function, b=\"X\")\n\n\ndef test_invalid_file_object_kwargs():\n    class Writer:\n        def __init__(self):\n            self.out = \"\"\n\n        def write(self, m):\n            pass\n\n    writer = Writer()\n\n    with pytest.raises(TypeError, match=r\"add\\(\\) got an unexpected keyword argument\"):\n        logger.add(writer, format=\"{message}\", kw1=\"1\", kw2=\"2\")\n\n\ndef test_invalid_file_kwargs():\n    with pytest.raises(TypeError, match=r\".*keyword argument;*\"):\n        logger.add(\"file.log\", nope=123)\n\n\ndef test_invalid_coroutine_kwargs():\n    async def foo():\n        pass\n\n    with pytest.raises(TypeError, match=r\"add\\(\\) got an unexpected keyword argument\"):\n        logger.add(foo, nope=123)\n"
  },
  {
    "path": "tests/test_add_option_level.py",
    "content": "import pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\"level\", [0, \"TRACE\", \"INFO\", 20])\ndef test_level_low_enough(writer, level):\n    logger.add(writer, level=level, format=\"{message}\")\n    logger.info(\"Test level\")\n    assert writer.read() == \"Test level\\n\"\n\n\n@pytest.mark.parametrize(\"level\", [\"WARNING\", 25])\ndef test_level_too_high(writer, level):\n    logger.add(writer, level=level, format=\"{message}\")\n    logger.info(\"Test level\")\n    assert writer.read() == \"\"\n\n\n@pytest.mark.parametrize(\"level\", [3.4, object()])\ndef test_invalid_level_type(writer, level):\n    with pytest.raises(TypeError):\n        logger.add(writer, level=level)\n\n\ndef test_invalid_level_value(writer):\n    with pytest.raises(\n        ValueError, match=\"^Invalid level value, it should be a positive integer, not: -1$\"\n    ):\n        logger.add(writer, level=-1)\n\n\ndef test_unknown_level(writer):\n    with pytest.raises(ValueError, match=\"^Level 'foo' does not exist$\"):\n        logger.add(writer, level=\"foo\")\n"
  },
  {
    "path": "tests/test_add_option_serialize.py",
    "content": "import json\nimport re\nimport sys\n\nfrom loguru import logger\n\n\nclass JsonSink:\n    def __init__(self):\n        self.message = None\n        self.dict = None\n        self.json = None\n\n    def write(self, message):\n        self.message = message\n        self.dict = message.record\n        self.json = json.loads(message)\n\n\ndef test_serialize():\n    sink = JsonSink()\n    logger.add(sink, format=\"{level} {message}\", serialize=True)\n    logger.debug(\"Test\")\n    assert sink.json[\"text\"] == \"DEBUG Test\\n\"\n    assert sink.dict[\"message\"] == sink.json[\"record\"][\"message\"] == \"Test\"\n    assert set(sink.dict.keys()) == set(sink.json[\"record\"].keys())\n\n\ndef test_serialize_non_ascii_characters():\n    sink = JsonSink()\n    logger.add(sink, format=\"{level.icon} {message}\", serialize=True)\n    logger.debug(\"天\")\n    assert re.search(r'\"message\": \"([^\\\"]+)\"', sink.message).group(1) == \"天\"\n    assert re.search(r'\"text\": \"([^\\\"]+)\"', sink.message).group(1) == \"🐞 天\\\\n\"\n    assert re.search(r'\"icon\": \"([^\\\"]+)\"', sink.message).group(1) == \"🐞\"\n    assert sink.json[\"text\"] == \"🐞 天\\n\"\n    assert sink.dict[\"message\"] == sink.json[\"record\"][\"message\"] == \"天\"\n\n\ndef test_serialize_exception():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error\")\n\n    lines = sink.json[\"text\"].splitlines()\n    assert lines[0] == \"Error\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n    assert sink.json[\"record\"][\"exception\"] == {\n        \"type\": \"ZeroDivisionError\",\n        \"value\": \"division by zero\",\n        \"traceback\": True,\n    }\n\n\ndef test_serialize_exception_without_context():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    logger.exception(\"No Error\")\n\n    lines = sink.json[\"text\"].splitlines()\n    assert lines[0] == \"No Error\"\n    assert lines[-1] == \"NoneType\" if sys.version_info < (3, 5, 3) else \"NoneType: None\"\n\n    assert sink.json[\"record\"][\"exception\"] == {\n        \"type\": None,\n        \"value\": None,\n        \"traceback\": False,\n    }\n\n\ndef test_serialize_exception_none_tuple():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    logger.opt(exception=(None, None, None)).error(\"No Error\")\n\n    lines = sink.json[\"text\"].splitlines()\n    assert lines[0] == \"No Error\"\n    assert lines[-1] == \"NoneType\" if sys.version_info < (3, 5, 3) else \"NoneType: None\"\n\n    assert sink.json[\"record\"][\"exception\"] == {\n        \"type\": None,\n        \"value\": None,\n        \"traceback\": False,\n    }\n\n\ndef test_serialize_exception_instance():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    logger.opt(exception=ZeroDivisionError(\"Oops\")).error(\"Failure\")\n\n    lines = sink.json[\"text\"].splitlines()\n    assert lines[0] == \"Failure\"\n    assert lines[-1] == \"ZeroDivisionError: Oops\"\n\n    assert sink.json[\"record\"][\"exception\"] == {\n        \"type\": \"ZeroDivisionError\",\n        \"value\": \"Oops\",\n        \"traceback\": False,\n    }\n\n\ndef test_serialize_with_catch_decorator():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    @logger.catch\n    def foo():\n        1 / 0  # noqa: B018\n\n    foo()\n\n    lines = sink.json[\"text\"].splitlines()\n    assert lines[0].startswith(\"An error has been caught\")\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n    assert bool(sink.json[\"record\"][\"exception\"])\n\n\ndef test_serialize_with_record_option():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", serialize=True, catch=False)\n\n    logger.opt(record=True).info(\"Test\", foo=123)\n\n    assert sink.json[\"text\"] == \"Test\\n\"\n    assert sink.dict[\"extra\"] == {\"foo\": 123}\n\n\ndef test_serialize_not_serializable():\n    sink = JsonSink()\n    logger.add(sink, format=\"{message}\", catch=False, serialize=True)\n    not_serializable = object()\n    logger.bind(not_serializable=not_serializable).debug(\"Test\")\n    assert sink.dict[\"extra\"][\"not_serializable\"] == not_serializable\n    assert bool(sink.json[\"record\"][\"extra\"][\"not_serializable\"])\n"
  },
  {
    "path": "tests/test_add_sinks.py",
    "content": "import asyncio\nimport logging\nimport os\nimport pathlib\nimport sys\n\nimport pytest\n\nfrom loguru import logger\n\nmessage = \"test message\"\nexpected = message + \"\\n\"\n\nrepetitions = pytest.mark.parametrize(\"rep\", [0, 1, 2])\n\n\ndef log(sink, rep=1):\n    logger.debug(\"This shouldn't be printed.\")\n    i = logger.add(sink, format=\"{message}\")\n    for _ in range(rep):\n        logger.debug(message)\n    logger.remove(i)\n    logger.debug(\"This shouldn't be printed neither.\")\n\n\nasync def async_log(sink, rep=1):\n    logger.debug(\"This shouldn't be printed.\")\n    i = logger.add(sink, format=\"{message}\")\n    for _ in range(rep):\n        logger.debug(message)\n    await logger.complete()\n    logger.remove(i)\n    logger.debug(\"This shouldn't be printed neither.\")\n\n\n@repetitions\ndef test_stdout_sink(rep, capsys):\n    log(sys.stdout, rep)\n    out, err = capsys.readouterr()\n    assert out == expected * rep\n    assert err == \"\"\n\n\n@repetitions\ndef test_stderr_sink(rep, capsys):\n    log(sys.stderr, rep)\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == expected * rep\n\n\n@repetitions\ndef test_devnull(rep):\n    log(os.devnull, rep)\n\n\n@repetitions\n@pytest.mark.parametrize(\"path_type\", [str, pathlib.Path])\ndef test_file_path_sink(rep, path_type, tmp_path):\n    file = tmp_path / \"test.log\"\n    sink = path_type(str(file))\n    log(sink, rep)\n    assert file.read_text() == expected * rep\n\n\n@repetitions\ndef test_file_opened_sink(rep, tmp_path):\n    file = tmp_path / \"test.log\"\n    with open(str(file), \"w\") as sink:\n        log(sink, rep)\n    assert file.read_text() == expected * rep\n\n\n@repetitions\ndef test_file_sink_folder_creation(rep, tmp_path):\n    file = tmp_path.joinpath(\"some\", \"sub\", \"folder\", \"not\", \"existing\", \"test.log\")\n    log(file, rep)\n    assert file.read_text() == expected * rep\n\n\n@repetitions\ndef test_function_sink(rep):\n    a = []\n\n    def func(log_message):\n        a.append(log_message)\n\n    log(func, rep)\n    assert a == [expected] * rep\n\n\n@repetitions\ndef test_coroutine_sink(capsys, rep):\n    async def async_print(msg):\n        await asyncio.sleep(0.01)\n        print(msg, end=\"\")\n        await asyncio.sleep(0.01)\n\n    asyncio.run(async_log(async_print, rep))\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == expected * rep\n\n\n@repetitions\ndef test_file_object_sink(rep):\n    class A:\n        def __init__(self):\n            self.out = \"\"\n\n        def write(self, m):\n            self.out += m\n\n    a = A()\n    log(a, rep)\n    assert a.out == expected * rep\n\n\n@repetitions\ndef test_standard_handler_sink(rep):\n    out = []\n\n    class H(logging.Handler):\n        def emit(self, record):\n            out.append(record.getMessage() + \"\\n\")\n\n    h = H()\n    log(h, rep)\n    assert out == [expected] * rep\n\n\n@repetitions\ndef test_flush(rep):\n    flushed = []\n    out = []\n\n    class A:\n        def write(self, m):\n            out.append(m)\n\n        def flush(self):\n            flushed.append(out[-1])\n\n    log(A(), rep)\n    assert flushed == [expected] * rep\n\n\ndef test_file_sink_ascii_encoding(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, encoding=\"ascii\", format=\"{message}\", errors=\"backslashreplace\", catch=False)\n    logger.info(\"天\")\n    logger.remove()\n    assert file.read_text(\"ascii\") == \"\\\\u5929\\n\"\n\n\ndef test_file_sink_utf8_encoding(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, encoding=\"utf8\", format=\"{message}\", errors=\"strict\", catch=False)\n    logger.info(\"天\")\n    logger.remove()\n    assert file.read_text(\"utf8\") == \"天\\n\"\n\n\ndef test_file_sink_default_encoding(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", errors=\"strict\", catch=False)\n    logger.info(\"天\")\n    logger.remove()\n    assert file.read_text(\"utf8\") == \"天\\n\"\n\n\ndef test_disabled_logger_in_sink(sink_with_logger):\n    sink = sink_with_logger(logger)\n    logger.disable(\"tests.conftest\")\n    logger.add(sink, format=\"{message}\")\n    logger.info(\"Disabled test\")\n    assert sink.out == \"Disabled test\\n\"\n\n\n@pytest.mark.parametrize(\"flush\", [123, None])\ndef test_custom_sink_invalid_flush(capsys, flush):\n    class Sink:\n        def __init__(self):\n            self.flush = flush\n\n        def write(self, message):\n            print(message, end=\"\")\n\n    logger.add(Sink(), format=\"{message}\")\n    logger.info(\"Test\")\n\n    out, err = capsys.readouterr()\n    assert out == \"Test\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"stop\", [123, None])\ndef test_custom_sink_invalid_stop(capsys, stop):\n    class Sink:\n        def __init__(self):\n            self.stop = stop\n\n        def write(self, message):\n            print(message, end=\"\")\n\n    logger.add(Sink(), format=\"{message}\")\n    logger.info(\"Test\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert out == \"Test\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"complete\", [123, None, lambda: None])\ndef test_custom_sink_invalid_complete(capsys, complete):\n    class Sink:\n        def __init__(self):\n            self.complete = complete\n\n        def write(self, message):\n            print(message, end=\"\")\n\n    async def worker():\n        logger.info(\"Test\")\n        await logger.complete()\n\n    logger.add(Sink(), format=\"{message}\")\n    asyncio.run(worker())\n\n    out, err = capsys.readouterr()\n    assert out == \"Test\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"sink\", [123, sys, object(), int])\ndef test_invalid_sink(sink):\n    with pytest.raises(TypeError):\n        log(sink, \"\")\n\n\ndef test_deprecated_start_and_stop(writer):\n    with pytest.warns(DeprecationWarning, match=r\"The 'start\\(\\)' method is deprecated\"):\n        i = logger.start(writer, format=\"{message}\")\n    logger.debug(\"Test\")\n    assert writer.read() == \"Test\\n\"\n    writer.clear()\n    with pytest.warns(DeprecationWarning, match=r\"The 'stop\\(\\)' method is deprecated\"):\n        logger.stop(i)\n    logger.debug(\"Test\")\n    assert writer.read() == \"\"\n"
  },
  {
    "path": "tests/test_ansimarkup_basic.py",
    "content": "import pytest\nfrom colorama import Back, Fore, Style\n\nfrom .conftest import parse\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<bold>1</bold>\", Style.BRIGHT + \"1\" + Style.RESET_ALL),\n        (\"<dim>1</dim>\", Style.DIM + \"1\" + Style.RESET_ALL),\n        (\"<normal>1</normal>\", Style.NORMAL + \"1\" + Style.RESET_ALL),\n        (\"<b>1</b>\", Style.BRIGHT + \"1\" + Style.RESET_ALL),\n        (\"<d>1</d>\", Style.DIM + \"1\" + Style.RESET_ALL),\n        (\"<n>1</n>\", Style.NORMAL + \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_styles(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<RED>1</RED>\", Back.RED + \"1\" + Style.RESET_ALL),\n        (\"<R>1</R>\", Back.RED + \"1\" + Style.RESET_ALL),\n        (\"<LIGHT-GREEN>1</LIGHT-GREEN>\", Back.LIGHTGREEN_EX + \"1\" + Style.RESET_ALL),\n        (\"<LG>1</LG>\", Back.LIGHTGREEN_EX + \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_background_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<yellow>1</yellow>\", Fore.YELLOW + \"1\" + Style.RESET_ALL),\n        (\"<y>1</y>\", Fore.YELLOW + \"1\" + Style.RESET_ALL),\n        (\"<light-white>1</light-white>\", Fore.LIGHTWHITE_EX + \"1\" + Style.RESET_ALL),\n        (\"<lw>1</lw>\", Fore.LIGHTWHITE_EX + \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_foreground_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\n            \"<b>1</b><d>2</d>\",\n            Style.BRIGHT + \"1\" + Style.RESET_ALL + Style.DIM + \"2\" + Style.RESET_ALL,\n        ),\n        (\n            \"<b>1</b>2<d>3</d>\",\n            Style.BRIGHT + \"1\" + Style.RESET_ALL + \"2\" + Style.DIM + \"3\" + Style.RESET_ALL,\n        ),\n        (\n            \"0<b>1<d>2</d>3</b>4\",\n            \"0\"\n            + Style.BRIGHT\n            + \"1\"\n            + Style.DIM\n            + \"2\"\n            + Style.RESET_ALL\n            + Style.BRIGHT\n            + \"3\"\n            + Style.RESET_ALL\n            + \"4\",\n        ),\n        (\n            \"<d>0<b>1<d>2</d>3</b>4</d>\",\n            Style.DIM\n            + \"0\"\n            + Style.BRIGHT\n            + \"1\"\n            + Style.DIM\n            + \"2\"\n            + Style.RESET_ALL\n            + Style.DIM\n            + Style.BRIGHT\n            + \"3\"\n            + Style.RESET_ALL\n            + Style.DIM\n            + \"4\"\n            + Style.RESET_ALL,\n        ),\n    ],\n)\ndef test_nested(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\"text\", [\"<b>\", \"<Y><b></b>\", \"<b><b></b>\"])\ndef test_strict_parsing(text):\n    with pytest.raises(\n        ValueError, match='^Opening tag \"<[^>]*>\" has no corresponding closing tag$'\n    ):\n        parse(text, strip=False)\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<b>\", Style.BRIGHT),\n        (\"<Y><b></b>\", Back.YELLOW + Style.BRIGHT + Style.RESET_ALL + Back.YELLOW),\n        (\"<b><b></b>\", Style.BRIGHT + Style.BRIGHT + Style.RESET_ALL + Style.BRIGHT),\n    ],\n)\ndef test_permissive_parsing(text, expected):\n    assert parse(text, strip=False, strict=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<red>foo</>\", Fore.RED + \"foo\" + Style.RESET_ALL),\n        (\n            \"<green><bold>bar</></green>\",\n            Fore.GREEN + Style.BRIGHT + \"bar\" + Style.RESET_ALL + Fore.GREEN + Style.RESET_ALL,\n        ),\n        (\n            \"a<yellow>b<b>c</>d</>e\",\n            \"a\"\n            + Fore.YELLOW\n            + \"b\"\n            + Style.BRIGHT\n            + \"c\"\n            + Style.RESET_ALL\n            + Fore.YELLOW\n            + \"d\"\n            + Style.RESET_ALL\n            + \"e\",\n        ),\n    ],\n)\ndef test_autoclose(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (r\"\\<red>foobar\\</red>\", \"<red>foobar</red>\"),\n        (r\"\\\\<red>foobar\\\\</red>\", \"\\\\\" + Fore.RED + \"foobar\\\\\" + Style.RESET_ALL),\n        (r\"\\\\\\<red>foobar\\\\\\</red>\", \"\\\\<red>foobar\\\\</red>\"),\n        (r\"\\\\\\\\<red>foobar\\\\\\\\</red>\", \"\\\\\\\\\" + Fore.RED + \"foobar\\\\\\\\\" + Style.RESET_ALL),\n        (r\"<red>foo\\</red>bar</red>\", Fore.RED + \"foo</red>bar\" + Style.RESET_ALL),\n        (r\"<red>foo\\<red>bar</red>\", Fore.RED + \"foo<red>bar\" + Style.RESET_ALL),\n        (r\"\\<red>\\</red>\", \"<red></red>\"),\n        (r\"foo\\</>bar\\</>baz\", \"foo</>bar</>baz\"),\n        (r\"\\a \\\\b \\\\\\c \\\\\\\\d\", \"\\\\a \\\\\\\\b \\\\\\\\\\\\c \\\\\\\\\\\\\\\\d\"),\n    ],\n)\ndef test_escaping(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"<b>1</d>\",\n        \"</b>\",\n        \"<b>1</b></b>\",\n        \"<red><b>1</b></b></red>\",\n        \"<green>1</b>\",\n        \"<green>foo</bar>\",\n        \"</>\",\n        \"<red><green>X</></green>\",\n    ],\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_mismatched_error(text, strip):\n    with pytest.raises(\n        ValueError, match='^Closing tag \"<[^>]*>\" has no corresponding opening tag$'\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    \"text\", [\"<r><Y>1</r>2</Y>\", \"<r><r><Y>1</r>2</Y></r>\", \"<r><Y><r></r></r></Y>\"]\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_unbalanced_error(text, strip):\n    with pytest.raises(ValueError, match='^Closing tag \"<[^>]*>\" violates nesting rules$'):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\"text\", [\"<b>\", \"<Y><b></b>\", \"<b><b></b>\", \"<fg red>1<fg red>\"])\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_unclosed_error(text, strip):\n    with pytest.raises(\n        ValueError, match='^Opening tag \"<[^>]*>\" has no corresponding closing tag$'\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"<foo>bar</foo>\",\n        \"<Green>foobar</Green>\",\n        \"<bar>foo</green>\",\n        \"<b>1</b><tag>2</tag>\",\n        \"<tag>1</tag><b>2</b>\",\n        \"<b>1</b><tag>2</tag><b>3</b>\",\n        \"<tag>1</tag><b>2</b><tag>3</tag>\",\n        \"<b><tag>1</tag></b>\",\n        \"<tag><b>1</b></tag>\",\n        \"<tag>1</b>\",\n        \"<b></b><tag>1</tag>\",\n        \"<tag>1</tag><b></b>\",\n    ],\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_invalid_color(text, strip):\n    with pytest.raises(\n        ValueError,\n        match=(\n            '^Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)$\"\n        ),\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<red>foo</red>\", \"foo\"),\n        (\"<BLACK>bar</BLACK>\", \"bar\"),\n        (\"<b>baz</b>\", \"baz\"),\n        (\"<b>1</b>2<d>3</d>\", \"123\"),\n        (\"<red>foo</>\", \"foo\"),\n    ],\n)\ndef test_strip(text, expected):\n    assert parse(text, strip=True) == expected\n"
  },
  {
    "path": "tests/test_ansimarkup_extended.py",
    "content": "import pytest\nfrom colorama import Back, Fore, Style\n\nfrom .conftest import parse\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<bg red>1</bg red>\", Back.RED + \"1\" + Style.RESET_ALL),\n        (\"<bg BLACK>1</bg BLACK>\", Back.BLACK + \"1\" + Style.RESET_ALL),\n        (\"<bg light-green>1</bg light-green>\", Back.LIGHTGREEN_EX + \"1\" + Style.RESET_ALL),\n        (\"<bg LIGHT-MAGENTA>1</bg LIGHT-MAGENTA>\", Back.LIGHTMAGENTA_EX + \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_background_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<fg yellow>1</fg yellow>\", Fore.YELLOW + \"1\" + Style.RESET_ALL),\n        (\"<fg BLUE>1</fg BLUE>\", Fore.BLUE + \"1\" + Style.RESET_ALL),\n        (\"<fg light-white>1</fg light-white>\", Fore.LIGHTWHITE_EX + \"1\" + Style.RESET_ALL),\n        (\"<fg LIGHT-CYAN>1</fg LIGHT-CYAN>\", Fore.LIGHTCYAN_EX + \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_foreground_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<fg #ff0000>1</fg #ff0000>\", \"\\x1b[38;2;255;0;0m\" \"1\" + Style.RESET_ALL),\n        (\"<bg #00A000>1</bg #00A000>\", \"\\x1b[48;2;0;160;0m\" \"1\" + Style.RESET_ALL),\n        (\"<fg #F12>1</fg #F12>\", \"\\x1b[38;2;255;17;34m\" \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_8bit_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<fg #ff0000>1</fg #ff0000>\", \"\\x1b[38;2;255;0;0m\" \"1\" + Style.RESET_ALL),\n        (\"<bg #00A000>1</bg #00A000>\", \"\\x1b[48;2;0;160;0m\" \"1\" + Style.RESET_ALL),\n        (\"<fg #F12>1</fg #F12>\", \"\\x1b[38;2;255;17;34m\" \"1\" + Style.RESET_ALL),\n        (\"<bg #BEE>1</bg #BEE>\", \"\\x1b[48;2;187;238;238m\" \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_hex_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"short_code\", \"long_code\"),\n    [\n        (\"abc\", \"aabbcc\"),\n        (\"f00\", \"ff0000\"),\n        (\"f12\", \"ff1122\"),\n        (\"bee\", \"bbeeee\"),\n        (\"Ace\", \"AAccee\"),\n    ],\n    ids=lambda code: \"#\" + code,\n)\n@pytest.mark.parametrize(\"layer\", [\"fg\", \"bg\"])\ndef test_hex_short_code_equals_long_code(layer, short_code, long_code):\n    short_code_colorized = parse(\"<%s #%s>_</>\" % (layer, short_code))\n    long_code_colorized = parse(\"<%s #%s>_</>\" % (layer, long_code))\n    assert short_code_colorized == long_code_colorized\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<fg 200>1</fg 200>\", \"\\x1b[38;5;200m\" \"1\" + Style.RESET_ALL),\n        (\"<bg 49>1</bg 49>\", \"\\x1b[48;5;49m\" \"1\" + Style.RESET_ALL),\n    ],\n)\ndef test_rgb_colors(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\n            \"<red><b><bg #00A000>1</bg #00A000></b></red>\",\n            Fore.RED + Style.BRIGHT + \"\\x1b[48;2;0;160;0m\"\n            \"1\"\n            + Style.RESET_ALL\n            + Fore.RED\n            + Style.BRIGHT\n            + Style.RESET_ALL\n            + Fore.RED\n            + Style.RESET_ALL,\n        ),\n        (\n            \"<bg 100><fg 200>1</fg 200></bg 100>\",\n            \"\\x1b[48;5;100m\" \"\\x1b[38;5;200m\" \"1\" \"\\x1b[0m\" \"\\x1b[48;5;100m\" \"\\x1b[0m\",\n        ),\n        (\n            \"<bg #00a000><fg #FF0000>1</fg #FF0000></bg #00a000>\",\n            \"\\x1b[48;2;0;160;0m\" \"\\x1b[38;2;255;0;0m\" \"1\" \"\\x1b[0m\" \"\\x1b[48;2;0;160;0m\" \"\\x1b[0m\",\n        ),\n        (\n            \"<bg 0,160,0><fg 255,0,0>1</fg 255,0,0></bg 0,160,0>\",\n            \"\\x1b[48;2;0;160;0m\" \"\\x1b[38;2;255;0;0m\" \"1\" \"\\x1b[0m\" \"\\x1b[48;2;0;160;0m\" \"\\x1b[0m\",\n        ),\n    ],\n)\ndef test_nested(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<r>2 > 1</r>\", Fore.RED + \"2 > 1\" + Style.RESET_ALL),\n        (\"<r>1 < 2</r>\", Fore.RED + \"1 < 2\" + Style.RESET_ALL),\n        (\"<r>1 </ 2</r>\", Fore.RED + \"1 </ 2\" + Style.RESET_ALL),\n        (\"{: <10}<r>1</r>\", \"{: <10}\" + Fore.RED + \"1\" + Style.RESET_ALL),\n        (\"{: </10}<r>1</r>\", \"{: </10}\" + Fore.RED + \"1\" + Style.RESET_ALL),\n        (\"<r>1</r>{: >10}\", Fore.RED + \"1\" + Style.RESET_ALL + \"{: >10}\"),\n        (\"<1<r>2</r>3>\", \"<1\" + Fore.RED + \"2\" + Style.RESET_ALL + \"3>\"),\n        (\"</1<r>2</r>3>\", \"</1\" + Fore.RED + \"2\" + Style.RESET_ALL + \"3>\"),\n        (\"<1<r>2 < 3</r>4>\", \"<1\" + Fore.RED + \"2 < 3\" + Style.RESET_ALL + \"4>\"),\n        (\"<1<r>2 </ 3</r>4>\", \"<1\" + Fore.RED + \"2 </ 3\" + Style.RESET_ALL + \"4>\"),\n        (\"<1<r>3 > 2</r>4>\", \"<1\" + Fore.RED + \"3 > 2\" + Style.RESET_ALL + \"4>\"),\n    ],\n)\ndef test_tricky_parse(text, expected):\n    assert parse(text, strip=False) == expected\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"<fg light-blue2>1</fg light-blue2>\",\n        \"<bg ,red>1</bg ,red>\",\n        \"<bg red,>1</bg red,>\",\n        \"<bg a,z>1</bg a,z>\",\n        \"<bg blue,yelllow>1</bg blue,yelllow>\",\n        \"<>1</>\",\n        \"<,>1</,>\",\n        \"<z,z>1</z,z>\",\n        \"<z,z,z>1</z,z,z>\",\n        \"<fg>1</fg>\",\n    ],\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_invalid_color(text, strip):\n    with pytest.raises(\n        ValueError,\n        match=(\n            '^Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)$\"\n        ),\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"<fg #>1</fg #>\",\n        \"<bg #12>1</bg #12>\",\n        \"<fg #1234567>1</fg #1234567>\",\n        \"<bg #E7G>1</bg #E7G>\",\n        \"<fg #F2D1GZ>1</fg #F2D1GZ>\",\n    ],\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_invalid_hex(text, strip):\n    with pytest.raises(\n        ValueError,\n        match=(\n            '^Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)$\"\n        ),\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\"text\", [\"<fg 256>1</fg 256>\", \"<bg 2222>1</bg 2222>\", \"<bg -1>1</bg -1>\"])\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_invalid_8bit(text, strip):\n    with pytest.raises(\n        ValueError,\n        match=(\n            '^Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)$\"\n        ),\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    \"text\",\n    [\n        \"<fg 1,2,>1</fg 1,2,>\",\n        \"<bg ,>1</bg ,>\",\n        \"<fg ,,>1</fg ,,>\",\n        \"<fg 256,120,120>1</fg 256,120,120>\",\n        \"<bg 1,2,3,4>1</bg 1,2,3,4>\",\n    ],\n)\n@pytest.mark.parametrize(\"strip\", [True, False])\ndef test_invalid_rgb(text, strip):\n    with pytest.raises(\n        ValueError,\n        match=(\n            '^Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)$\"\n        ),\n    ):\n        parse(text, strip=strip)\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<fg #ff0000>foobar</fg #ff0000>\", \"foobar\"),\n        (\"<fg 55>baz</fg 55>\", \"baz\"),\n        (\"<bg 23,12,12>bar</bg 23,12,12>\", \"bar\"),\n    ],\n)\ndef test_strip(text, expected):\n    assert parse(text, strip=True) == expected\n\n\n@pytest.mark.parametrize(\n    (\"text\", \"expected\"),\n    [\n        (\"<r>2 > 1</r>\", \"2 > 1\"),\n        (\"<r>1 < 2</r>\", \"1 < 2\"),\n        (\"<r>1 </ 2</r>\", \"1 </ 2\"),\n        (\"{: <10}<r>1</r>\", \"{: <10}1\"),\n        (\"{: </10}<r>1</r>\", \"{: </10}1\"),\n        (\"<r>1</r>{: >10}\", \"1{: >10}\"),\n        (\"<1<r>2</r>3>\", \"<123>\"),\n        (\"</1<r>2</r>3>\", \"</123>\"),\n        (\"<1<r>2 < 3</r>4>\", \"<12 < 34>\"),\n        (\"<1<r>2 </ 3</r>4>\", \"<12 </ 34>\"),\n        (\"<1<r>3 > 2</r>4>\", \"<13 > 24>\"),\n    ],\n)\ndef test_tricky_strip(text, expected):\n    assert parse(text, strip=True) == expected\n"
  },
  {
    "path": "tests/test_bind.py",
    "content": "import pytest\n\nfrom loguru import logger\n\n\ndef test_bind_after_add(writer):\n    logger.add(writer, format=\"{extra[a]} {message}\")\n    logger_bound = logger.bind(a=0)\n    logger_bound.debug(\"A\")\n\n    assert writer.read() == \"0 A\\n\"\n\n\ndef test_bind_before_add(writer):\n    logger_bound = logger.bind(a=0)\n    logger.add(writer, format=\"{extra[a]} {message}\")\n    logger_bound.debug(\"A\")\n\n    assert writer.read() == \"0 A\\n\"\n\n\ndef test_add_using_bound(writer):\n    logger.configure(extra={\"a\": -1})\n    logger_bound = logger.bind(a=0)\n    logger_bound.add(writer, format=\"{extra[a]} {message}\")\n    logger.debug(\"A\")\n    logger_bound.debug(\"B\")\n\n    assert writer.read() == \"-1 A\\n0 B\\n\"\n\n\ndef test_not_override_parent_logger(writer):\n    logger_1 = logger.bind(a=\"a\")\n    logger_2 = logger_1.bind(a=\"A\")\n    logger.add(writer, format=\"{extra[a]} {message}\")\n\n    logger_1.debug(\"1\")\n    logger_2.debug(\"2\")\n\n    assert writer.read() == \"a 1\\nA 2\\n\"\n\n\ndef test_override_previous_bound(writer):\n    logger.add(writer, format=\"{extra[x]} {message}\")\n    logger.bind(x=1).bind(x=2).debug(\"3\")\n    assert writer.read() == \"2 3\\n\"\n\n\ndef test_no_conflict(writer):\n    logger_ = logger.bind()\n    logger_2 = logger_.bind(a=2)\n    logger_3 = logger_.bind(a=3)\n\n    logger.add(writer, format=\"{extra[a]} {message}\")\n\n    logger_2.debug(\"222\")\n    logger_3.debug(\"333\")\n\n    assert writer.read() == \"2 222\\n3 333\\n\"\n\n\n@pytest.mark.parametrize(\"using_bound\", [True, False])\ndef test_bind_and_add_level(writer, using_bound):\n    logger_bound = logger.bind()\n    logger.add(writer, format=\"{level.name} {message}\")\n\n    if using_bound:\n        logger_bound.level(\"bar\", 15)\n    else:\n        logger.level(\"bar\", 15)\n\n    logger.log(\"bar\", \"root\")\n    logger_bound.log(\"bar\", \"bound\")\n\n    assert writer.read() == \"bar root\\nbar bound\\n\"\n\n\ndef test_override_configured(writer):\n    logger.configure(extra={\"a\": 1})\n    logger2 = logger.bind(a=2)\n\n    logger2.add(writer, format=\"{extra[a]} {message}\")\n\n    logger2.debug(\"?\")\n\n    assert writer.read() == \"2 ?\\n\"\n"
  },
  {
    "path": "tests/test_colorama.py",
    "content": "import builtins\nimport os\nimport sys\nfrom unittest.mock import MagicMock\n\nimport pytest\n\nfrom loguru import logger\nfrom loguru._colorama import should_colorize, should_wrap\n\nfrom .conftest import (\n    StreamFilenoException,\n    StreamIsattyException,\n    StreamIsattyFalse,\n    StreamIsattyTrue,\n)\n\n\n@pytest.fixture(autouse=True)\ndef clear_environment():\n    env = os.environ.copy()\n    os.environ.clear()\n    yield\n    os.environ.update(env)\n\n\n@pytest.fixture\ndef patch_colorama(monkeypatch):\n    ansi_to_win32_class = MagicMock()\n    winapi_test = MagicMock(return_value=True)\n    enable_vt_processing = MagicMock(return_value=False)\n    win32 = MagicMock(winapi_test=winapi_test)\n    winterm = MagicMock(enable_vt_processing=enable_vt_processing)\n    colorama = MagicMock(AnsiToWin32=ansi_to_win32_class, win32=win32, winterm=winterm)\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"colorama\", colorama)\n        context.setitem(sys.modules, \"colorama.win32\", win32)\n        context.setitem(sys.modules, \"colorama.winterm\", winterm)\n        yield colorama\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_stream_wrapped_on_windows_if_no_vt_support(patched, monkeypatch, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        patch_colorama.winterm.enable_vt_processing.return_value = False\n        logger.add(stream, colorize=True)\n        assert patch_colorama.AnsiToWin32.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_stream_not_wrapped_on_windows_if_vt_support(patched, monkeypatch, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        patch_colorama.winterm.enable_vt_processing.return_value = True\n        logger.add(stream, colorize=True)\n        assert not patch_colorama.AnsiToWin32.called\n\n\ndef test_stream_is_none():\n    assert not should_colorize(None)\n\n\ndef test_is_a_tty():\n    assert should_colorize(StreamIsattyTrue())\n\n\ndef test_is_not_a_tty():\n    assert not should_colorize(StreamIsattyFalse())\n\n\ndef test_is_a_tty_exception():\n    assert not should_colorize(StreamIsattyException())\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"expected\"),\n    [\n        (\"__stdout__\", True),\n        (\"__stderr__\", True),\n        (\"stdout\", False),\n        (\"stderr\", False),\n        (\"\", False),\n    ],\n)\ndef test_pycharm_fixed(monkeypatch, patched, expected):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        context.setitem(os.environ, \"PYCHARM_HOSTED\", \"1\")\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"expected\"),\n    [\n        (\"__stdout__\", True),\n        (\"__stderr__\", True),\n        (\"stdout\", False),\n        (\"stderr\", False),\n        (\"\", False),\n    ],\n)\ndef test_github_actions_fixed(monkeypatch, patched, expected):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"CI\", \"1\")\n        context.setitem(os.environ, \"GITHUB_ACTIONS\", \"1\")\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"expected\"),\n    [\n        (\"__stdout__\", True),\n        (\"__stderr__\", True),\n        (\"stdout\", False),\n        (\"stderr\", False),\n        (\"\", False),\n    ],\n)\n@pytest.mark.skipif(os.name != \"nt\", reason=\"The fix is applied only on Windows\")\ndef test_mintty_fixed_windows(monkeypatch, patched, expected):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"TERM\", \"xterm\")\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"expected\"),\n    [\n        (\"__stdout__\", False),\n        (\"__stderr__\", False),\n        (\"stdout\", True),\n        (\"stderr\", True),\n        (\"\", True),\n    ],\n)\ndef test_dumb_term_not_colored(monkeypatch, patched, expected):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"TERM\", \"dumb\")\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"no_color\", \"expected\"),\n    [\n        (\"__stdout__\", \"1\", False),\n        (\"__stderr__\", \"1\", False),\n        (\"stdout\", \"1\", False),\n        (\"stderr\", \"1\", False),\n        # Only standard out and err should be affected.\n        (\"\", \"1\", True),\n        # An empty value for NO_COLOR should not be applied:\n        (\"__stdout__\", \"\", True),\n        (\"__stderr__\", \"\", True),\n        (\"stdout\", \"\", True),\n        (\"stderr\", \"\", True),\n        (\"\", \"\", True),\n    ],\n)\ndef test_honor_no_color_standard(monkeypatch, patched, no_color, expected):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"NO_COLOR\", no_color)\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"force_color\", \"expected\"),\n    [\n        (\"__stdout__\", \"1\", True),\n        (\"__stderr__\", \"1\", True),\n        (\"stdout\", \"1\", True),\n        (\"stderr\", \"1\", True),\n        # Only standard out and err should be affected.\n        (\"\", \"1\", False),\n        # An empty value for FORCE_COLOR should not be applied:\n        (\"__stdout__\", \"\", False),\n        (\"__stderr__\", \"\", False),\n        (\"stdout\", \"\", False),\n        (\"stderr\", \"\", False),\n        (\"\", \"\", False),\n    ],\n)\ndef test_honor_force_color_standard(monkeypatch, patched, force_color, expected):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"FORCE_COLOR\", force_color)\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\ndef test_no_color_takes_precedence_over_force_color(monkeypatch):\n    stream_tty = StreamIsattyTrue()\n    stream_not_tty = StreamIsattyFalse()\n\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"NO_COLOR\", \"1\")\n        context.setitem(os.environ, \"FORCE_COLOR\", \"1\")\n\n        context.setattr(sys, \"__stderr__\", stream_tty, raising=False)\n        assert not should_colorize(stream_tty)\n\n        context.setattr(sys, \"__stderr__\", stream_not_tty, raising=False)\n        assert not should_colorize(stream_not_tty)\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"expected\"),\n    [\n        (\"__stdout__\", False),\n        (\"__stderr__\", False),\n        (\"stdout\", False),\n        (\"stderr\", False),\n        (\"\", False),\n    ],\n)\n@pytest.mark.skipif(os.name == \"nt\", reason=\"The fix will be applied on Windows\")\ndef test_mintty_not_fixed_linux(monkeypatch, patched, expected):\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setitem(os.environ, \"TERM\", \"xterm\")\n        context.setattr(sys, patched, stream, raising=False)\n        assert should_colorize(stream) is expected\n\n\n@pytest.mark.parametrize(\n    (\"patched\", \"out_class\", \"expected\"),\n    [\n        (\"stdout\", StreamIsattyFalse, True),\n        (\"stderr\", StreamIsattyFalse, True),\n        (\"__stdout__\", StreamIsattyFalse, False),\n        (\"__stderr__\", StreamIsattyFalse, False),\n        (\"stdout\", StreamIsattyTrue, False),\n        (\"stderr\", StreamIsattyTrue, False),\n        (\"\", StreamIsattyFalse, False),\n    ],\n)\ndef test_jupyter_fixed(monkeypatch, patched, out_class, expected):\n    stream = StreamIsattyFalse()\n\n    class Shell:\n        pass\n\n    ipython = MagicMock()\n    ipykernel = MagicMock()\n    instance = MagicMock()\n    instance.__class__ = Shell\n    ipython.get_ipython.return_value = instance\n    ipykernel.zmqshell.ZMQInteractiveShell = Shell\n    ipykernel.iostream.OutStream = out_class\n\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        context.setattr(builtins, \"__IPYTHON__\", True, raising=False)\n        context.setitem(sys.modules, \"IPython\", ipython)\n        context.setitem(sys.modules, \"ipykernel\", ipykernel)\n        assert should_colorize(stream) is expected\n\n\ndef test_jupyter_missing_lib(monkeypatch):\n    # Missing ipykernal so jupyter block will err, should handle gracefully\n    stream = StreamIsattyFalse()\n    with monkeypatch.context() as context:\n        context.setattr(sys, \"stdout\", stream, raising=False)\n        context.setattr(builtins, \"__IPYTHON__\", True, raising=False)\n        assert should_colorize(stream) is False\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Colorama is required on Windows\")\ndef test_dont_wrap_on_linux(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        assert not should_wrap(stream)\n        assert not patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"stdout\", \"stderr\", \"\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_dont_wrap_if_not_original_stdout_or_stderr(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        assert not should_wrap(stream)\n        assert not patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_dont_wrap_if_terminal_has_vt_support(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        patch_colorama.winterm.enable_vt_processing.return_value = True\n        assert not should_wrap(stream)\n        assert patch_colorama.winterm.enable_vt_processing.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_dont_wrap_if_winapi_false(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = False\n        patch_colorama.winterm.enable_vt_processing.return_value = False\n        assert not should_wrap(stream)\n        assert patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_wrap_if_winapi_true_and_no_vt_support(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        patch_colorama.winterm.enable_vt_processing.return_value = False\n        assert should_wrap(stream)\n        assert patch_colorama.winterm.enable_vt_processing.called\n        assert patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_wrap_if_winapi_true_and_vt_check_fails(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        patch_colorama.winterm.enable_vt_processing.side_effect = RuntimeError\n        assert should_wrap(stream)\n        assert patch_colorama.winterm.enable_vt_processing.called\n        assert patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_wrap_if_winapi_true_and_stream_has_no_fileno(monkeypatch, patched, patch_colorama):\n    stream = StreamFilenoException()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        assert should_wrap(stream)\n        assert not patch_colorama.winterm.enable_vt_processing.called\n        assert patch_colorama.win32.winapi_test.called\n\n\n@pytest.mark.parametrize(\"patched\", [\"__stdout__\", \"__stderr__\"])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Only Windows requires Colorama\")\ndef test_wrap_if_winapi_true_and_old_colorama_version(monkeypatch, patched, patch_colorama):\n    stream = StreamIsattyTrue()\n    with monkeypatch.context() as context:\n        context.setattr(sys, patched, stream, raising=False)\n        patch_colorama.win32.winapi_test.return_value = True\n        del patch_colorama.winterm.enable_vt_processing\n        assert should_wrap(stream)\n        assert patch_colorama.win32.winapi_test.called\n"
  },
  {
    "path": "tests/test_configure.py",
    "content": "import sys\n\nimport pytest\n\nfrom loguru import logger\n\n\ndef test_handlers(capsys, tmp_path):\n    file = tmp_path / \"test.log\"\n\n    handlers = [\n        {\"sink\": file, \"format\": \"FileSink: {message}\"},\n        {\"sink\": sys.stdout, \"format\": \"StdoutSink: {message}\"},\n    ]\n\n    logger.configure(handlers=handlers)\n    logger.debug(\"test\")\n\n    out, err = capsys.readouterr()\n\n    assert file.read_text() == \"FileSink: test\\n\"\n    assert out == \"StdoutSink: test\\n\"\n    assert err == \"\"\n\n\ndef test_levels(writer):\n    levels = [{\"name\": \"my_level\", \"icon\": \"X\", \"no\": 12}, {\"name\": \"DEBUG\", \"icon\": \"!\"}]\n\n    logger.add(writer, format=\"{level.no}|{level.name}|{level.icon}|{message}\")\n    logger.configure(levels=levels)\n\n    logger.log(\"my_level\", \"test\")\n    logger.debug(\"no bug\")\n\n    assert writer.read() == (\"12|my_level|X|test\\n\" \"10|DEBUG|!|no bug\\n\")\n\n\ndef test_extra(writer):\n    extra = {\"a\": 1, \"b\": 9}\n\n    logger.add(writer, format=\"{extra[a]} {extra[b]}\")\n    logger.configure(extra=extra)\n\n    logger.debug(\"\")\n\n    assert writer.read() == \"1 9\\n\"\n\n\ndef test_patcher(writer):\n    logger.add(writer, format=\"{extra[a]} {extra[b]}\")\n    logger.configure(patcher=lambda record: record[\"extra\"].update(a=1, b=2))\n\n    logger.debug(\"\")\n\n    assert writer.read() == \"1 2\\n\"\n\n\ndef test_activation(writer):\n    activation = [(\"tests\", False), (\"tests.test_configure\", True)]\n\n    logger.add(writer, format=\"{message}\")\n    logger.configure(activation=activation)\n\n    logger.debug(\"Logging\")\n\n    assert writer.read() == \"Logging\\n\"\n\n\ndef test_dict_unpacking(writer):\n    config = {\n        \"handlers\": [{\"sink\": writer, \"format\": \"{level.no} - {extra[x]} {extra[z]} - {message}\"}],\n        \"levels\": [{\"name\": \"test\", \"no\": 30}],\n        \"extra\": {\"x\": 1, \"y\": 2, \"z\": 3},\n    }\n\n    logger.debug(\"NOPE\")\n\n    logger.configure(**config)\n\n    logger.log(\"test\", \"Yes!\")\n\n    assert writer.read() == \"30 - 1 3 - Yes!\\n\"\n\n\ndef test_returned_ids(capsys):\n    ids = logger.configure(\n        handlers=[\n            {\"sink\": sys.stdout, \"format\": \"{message}\"},\n            {\"sink\": sys.stderr, \"format\": \"{message}\"},\n        ]\n    )\n\n    assert len(ids) == 2\n\n    logger.debug(\"Test\")\n\n    out, err = capsys.readouterr()\n\n    assert out == \"Test\\n\"\n    assert err == \"Test\\n\"\n\n    for i in ids:\n        logger.remove(i)\n\n    logger.debug(\"Nope\")\n\n    out, err = capsys.readouterr()\n\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_dont_reset_by_default(writer):\n    logger.configure(extra={\"a\": 1}, patcher=lambda r: r[\"extra\"].update(b=2))\n    logger.level(\"b\", no=30)\n    logger.add(writer, format=\"{level} {extra[a]} {extra[b]} {message}\")\n\n    logger.configure()\n\n    logger.log(\"b\", \"Test\")\n\n    assert writer.read() == \"b 1 2 Test\\n\"\n\n\ndef test_reset_previous_handlers(writer):\n    logger.add(writer, format=\"{message}\")\n\n    logger.configure(handlers=[])\n\n    logger.debug(\"Test\")\n\n    assert writer.read() == \"\"\n\n\ndef test_reset_previous_extra(writer):\n    logger.configure(extra={\"a\": 123})\n    logger.add(writer, format=\"{extra[a]}\", catch=False)\n\n    logger.configure(extra={})\n\n    with pytest.raises(KeyError):\n        logger.debug(\"Nope\")\n\n\ndef test_reset_previous_patcher(writer):\n    logger.configure(patcher=lambda r: r.update(a=123))\n    logger.add(writer, format=\"{extra[a]}\", catch=False)\n\n    logger.configure(patcher=lambda r: None)\n\n    with pytest.raises(KeyError):\n        logger.debug(\"Nope\")\n\n\ndef test_dont_reset_previous_levels(writer):\n    logger.level(\"abc\", no=30)\n\n    logger.configure(levels=[])\n\n    logger.add(writer, format=\"{level} {message}\")\n\n    logger.log(\"abc\", \"Test\")\n\n    assert writer.read() == \"abc Test\\n\"\n\n\ndef test_configure_handler_using_new_level(writer):\n    logger.configure(\n        levels=[{\"name\": \"CONF_LVL\", \"no\": 33, \"icon\": \"\", \"color\": \"\"}],\n        handlers=[\n            {\"sink\": writer, \"level\": \"CONF_LVL\", \"format\": \"{level.name} {level.no} {message}\"}\n        ],\n    )\n\n    logger.log(\"CONF_LVL\", \"Custom\")\n    assert writer.read() == \"CONF_LVL 33 Custom\\n\"\n\n\ndef test_configure_filter_using_new_level(writer):\n    logger.configure(\n        levels=[{\"name\": \"CONF_LVL_2\", \"no\": 33, \"icon\": \"\", \"color\": \"\"}],\n        handlers=[\n            {\"sink\": writer, \"level\": 0, \"filter\": {\"tests\": \"CONF_LVL_2\"}, \"format\": \"{message}\"}\n        ],\n    )\n\n    logger.log(\"CONF_LVL_2\", \"Custom\")\n    assert writer.read() == \"Custom\\n\"\n\n\ndef test_configure_before_bind(writer):\n    logger.configure(extra={\"a\": \"default_a\", \"b\": \"default_b\"})\n    logger.add(writer, format=\"{extra[a]} {extra[b]} {message}\")\n\n    logger.debug(\"init\")\n\n    logger_a = logger.bind(a=\"A\")\n    logger_b = logger.bind(b=\"B\")\n\n    logger_a.debug(\"aaa\")\n    logger_b.debug(\"bbb\")\n\n    assert writer.read() == (\"default_a default_b init\\n\" \"A default_b aaa\\n\" \"default_a B bbb\\n\")\n\n\ndef test_configure_after_bind(writer):\n    logger_a = logger.bind(a=\"A\")\n    logger_b = logger.bind(b=\"B\")\n\n    logger.configure(extra={\"a\": \"default_a\", \"b\": \"default_b\"})\n    logger.add(writer, format=\"{extra[a]} {extra[b]} {message}\")\n\n    logger.debug(\"init\")\n\n    logger_a.debug(\"aaa\")\n    logger_b.debug(\"bbb\")\n\n    assert writer.read() == (\"default_a default_b init\\n\" \"A default_b aaa\\n\" \"default_a B bbb\\n\")\n"
  },
  {
    "path": "tests/test_contextualize.py",
    "content": "import asyncio\nimport sys\nimport threading\nfrom unittest.mock import MagicMock\n\nimport pytest\n\nfrom loguru import logger\nfrom loguru._contextvars import load_contextvar_class\n\n\ndef test_contextualize(writer):\n    logger.add(writer, format=\"{message} {extra[foo]} {extra[baz]}\")\n\n    with logger.contextualize(foo=\"bar\", baz=123):\n        logger.info(\"Contextualized\")\n\n    assert writer.read() == \"Contextualized bar 123\\n\"\n\n\ndef test_contextualize_as_decorator(writer):\n    logger.add(writer, format=\"{message} {extra[foo]} {extra[baz]}\")\n\n    @logger.contextualize(foo=123, baz=\"bar\")\n    def task():\n        logger.info(\"Contextualized\")\n\n    task()\n\n    assert writer.read() == \"Contextualized 123 bar\\n\"\n\n\ndef test_contextualize_in_function(writer):\n    logger.add(writer, format=\"{message} {extra}\")\n\n    def foobar():\n        logger.info(\"Foobar!\")\n\n    with logger.contextualize(foobar=\"baz\"):\n        foobar()\n\n    assert writer.read() == \"Foobar! {'foobar': 'baz'}\\n\"\n\n\ndef test_contextualize_reset():\n    contexts = []\n    output = []\n\n    def sink(message):\n        contexts.append(message.record[\"extra\"])\n        output.append(str(message))\n\n    logger.add(sink, format=\"{level} {message}\")\n\n    logger.info(\"A\")\n\n    with logger.contextualize(abc=\"def\"):\n        logger.debug(\"B\")\n        logger.warning(\"C\")\n\n    logger.info(\"D\")\n\n    assert contexts == [{}, {\"abc\": \"def\"}, {\"abc\": \"def\"}, {}]\n    assert output == [\"INFO A\\n\", \"DEBUG B\\n\", \"WARNING C\\n\", \"INFO D\\n\"]\n\n\n@pytest.mark.xfail(sys.version_info < (3, 5, 3), reason=\"ContextVar backport not supported\")\ndef test_contextualize_async(writer):\n    logger.add(writer, format=\"{message} {extra[i]}\", catch=False)\n\n    async def task():\n        logger.info(\"Start\")\n        await asyncio.sleep(0.1)\n        logger.info(\"End\")\n\n    async def worker(i):\n        with logger.contextualize(i=i):\n            await task()\n\n    async def main():\n        workers = [worker(i) for i in range(5)]\n        await asyncio.gather(*workers)\n        await logger.complete()\n\n    asyncio.run(main())\n\n    assert sorted(writer.read().splitlines()) == [\"End %d\" % i for i in range(5)] + [\n        \"Start %d\" % i for i in range(5)\n    ]\n\n\ndef test_contextualize_thread(writer):\n    logger.add(writer, format=\"{message} {extra[i]}\")\n\n    def task():\n        logger.info(\"Processing\")\n\n    def worker(entry_barrier, exit_barrier, i):\n        with logger.contextualize(i=i):\n            entry_barrier.wait()\n            task()\n            exit_barrier.wait()\n\n    entry_barrier = threading.Barrier(5)\n    exit_barrier = threading.Barrier(5)\n\n    threads = [\n        threading.Thread(target=worker, args=(entry_barrier, exit_barrier, i)) for i in range(5)\n    ]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    assert sorted(writer.read().splitlines()) == [\"Processing %d\" % i for i in range(5)]\n\n\ndef test_contextualize_before_bind(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    logger_2 = logger.bind(foobar=\"baz\")\n\n    with logger.contextualize(foobar=\"baz_2\"):\n        logger.info(\"A\")\n        logger_2.info(\"B\")\n\n    logger_2.info(\"C\")\n\n    assert writer.read() == \"A baz_2\\nB baz\\nC baz\\n\"\n\n\ndef test_contextualize_after_bind(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    with logger.contextualize(foobar=\"baz\"):\n        logger_2 = logger.bind(foobar=\"baz_2\")\n        logger.info(\"A\")\n        logger_2.info(\"B\")\n\n    logger_2.info(\"C\")\n\n    assert writer.read() == \"A baz\\nB baz_2\\nC baz_2\\n\"\n\n\ndef test_contextualize_using_bound(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    logger_2 = logger.bind(foobar=\"baz\")\n\n    with logger_2.contextualize(foobar=\"baz_2\"):\n        logger.info(\"A\")\n        logger_2.info(\"B\")\n\n    logger_2.info(\"C\")\n\n    assert writer.read() == \"A baz_2\\nB baz\\nC baz\\n\"\n\n\ndef test_contextualize_before_configure(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    logger.configure(extra={\"foobar\": \"baz\"})\n\n    with logger.contextualize(foobar=\"baz_2\"):\n        logger.info(\"A\")\n\n    logger.info(\"B\")\n\n    assert writer.read() == \"A baz_2\\nB baz\\n\"\n\n\ndef test_contextualize_after_configure(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    with logger.contextualize(foobar=\"baz\"):\n        logger.configure(extra={\"foobar\": \"baz_2\"})\n        logger.info(\"A\")\n\n    logger.info(\"B\")\n\n    assert writer.read() == \"A baz\\nB baz_2\\n\"\n\n\ndef test_nested_contextualize(writer):\n    logger.add(writer, format=\"{message} {extra[foobar]}\")\n\n    with logger.contextualize(foobar=\"a\"):\n        with logger.contextualize(foobar=\"b\"):\n            logger.info(\"B\")\n\n        logger.info(\"A\")\n\n        with logger.contextualize(foobar=\"c\"):\n            logger.info(\"C\")\n\n    assert writer.read() == \"B b\\nA a\\nC c\\n\"\n\n\ndef test_context_reset_despite_error(writer):\n    logger.add(writer, format=\"{message} {extra}\")\n\n    try:\n        with logger.contextualize(foobar=456):\n            logger.info(\"Division\")\n            1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.info(\"Error\")\n\n    assert writer.read() == \"Division {'foobar': 456}\\nError {}\\n\"\n\n\n# There is not CI runner available for Python 3.5.2. Consequently, we are just\n# verifying third-library is properly imported to reach 100% coverage.\ndef test_contextvars_fallback_352(monkeypatch):\n    mock_module = MagicMock()\n    with monkeypatch.context() as context:\n        context.setattr(sys, \"version_info\", (3, 5, 2))\n        context.setitem(sys.modules, \"contextvars\", mock_module)\n        assert load_contextvar_class() == mock_module.ContextVar\n"
  },
  {
    "path": "tests/test_coroutine_sink.py",
    "content": "import asyncio\nimport logging\nimport multiprocessing\nimport re\nimport sys\nimport threading\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import new_event_loop_context, set_event_loop_context\n\n\nasync def async_writer(msg):\n    await asyncio.sleep(0.01)\n    print(msg, end=\"\")\n\n\nclass AsyncWriter:\n    async def __call__(self, msg):\n        await asyncio.sleep(0.01)\n        print(msg, end=\"\")\n\n\ndef test_coroutine_function(capsys):\n    async def worker():\n        logger.debug(\"A message\")\n        await logger.complete()\n\n    logger.add(async_writer, format=\"{message}\")\n\n    asyncio.run(worker())\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == \"A message\\n\"\n\n\ndef test_async_callable_sink(capsys):\n    async def worker():\n        logger.debug(\"A message\")\n        await logger.complete()\n\n    logger.add(AsyncWriter(), format=\"{message}\")\n\n    asyncio.run(worker())\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == \"A message\\n\"\n\n\ndef test_concurrent_execution(capsys):\n    async def task(i):\n        logger.debug(\"=> {}\", i)\n\n    async def main():\n        tasks = [task(i) for i in range(10)]\n        await asyncio.gather(*tasks)\n        await logger.complete()\n\n    logger.add(async_writer, format=\"{message}\")\n\n    asyncio.run(main())\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert sorted(out.splitlines()) == sorted(\"=> %d\" % i for i in range(10))\n\n\ndef test_recursive_coroutine(capsys):\n    async def task(i):\n        if i == 0:\n            await logger.complete()\n            return\n        logger.info(\"{}!\", i)\n        await task(i - 1)\n\n    logger.add(async_writer, format=\"{message}\")\n\n    asyncio.run(task(9))\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert sorted(out.splitlines()) == sorted(\"%d!\" % i for i in range(1, 10))\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\ndef test_using_another_event_loop(capsys):\n    async def worker():\n        logger.debug(\"A message\")\n        await logger.complete()\n\n    with new_event_loop_context() as loop:\n        logger.add(async_writer, format=\"{message}\", loop=loop)\n\n        loop.run_until_complete(worker())\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == \"A message\\n\"\n\n\ndef test_run_multiple_different_loops(capsys):\n    async def worker(i):\n        logger.debug(\"Message {}\", i)\n        await logger.complete()\n\n    logger.add(async_writer, format=\"{message}\", loop=None)\n\n    asyncio.run(worker(1))\n    asyncio.run(worker(2))\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == \"Message 1\\nMessage 2\\n\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\ndef test_run_multiple_same_loop(capsys):\n    async def worker(i):\n        logger.debug(\"Message {}\", i)\n        await logger.complete()\n\n    with new_event_loop_context() as loop:\n        logger.add(async_writer, format=\"{message}\", loop=loop)\n\n        loop.run_until_complete(worker(1))\n        loop.run_until_complete(worker(2))\n\n    out, err = capsys.readouterr()\n    assert err == \"\"\n    assert out == \"Message 1\\nMessage 2\\n\"\n\n\ndef test_using_sink_without_running_loop_not_none(capsys):\n    with new_event_loop_context() as loop:\n        logger.add(sys.stderr, format=\"=> {message}\")\n        logger.add(async_writer, format=\"{message}\", loop=loop)\n\n        logger.info(\"A message\")\n\n        loop.run_until_complete(logger.complete())\n\n    out, err = capsys.readouterr()\n    assert err == \"=> A message\\n\"\n    assert out == \"A message\\n\"\n\n\ndef test_using_sink_without_running_loop_none(capsys):\n    with new_event_loop_context() as loop:\n        logger.add(sys.stderr, format=\"=> {message}\")\n        logger.add(async_writer, format=\"{message}\", loop=None)\n\n        logger.info(\"A message\")\n\n        loop.run_until_complete(logger.complete())\n\n    out, err = capsys.readouterr()\n    assert err == \"=> A message\\n\"\n    assert out == \"\"\n\n\n@pytest.mark.skipif(sys.version_info >= (3, 16), reason=\"The 'set_event_loop' function is removed\")\ndef test_global_loop_not_used(capsys):\n    with new_event_loop_context() as loop:\n        with set_event_loop_context(loop):\n            logger.add(sys.stderr, format=\"=> {message}\")\n            logger.add(async_writer, format=\"{message}\", loop=None)\n\n            logger.info(\"A message\")\n\n            loop.run_until_complete(logger.complete())\n\n    out, err = capsys.readouterr()\n    assert err == \"=> A message\\n\"\n    assert out == \"\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\ndef test_complete_in_another_run(capsys):\n    async def worker_1():\n        logger.debug(\"A\")\n\n    async def worker_2():\n        logger.debug(\"B\")\n        await logger.complete()\n\n    with new_event_loop_context() as loop:\n        logger.add(async_writer, format=\"{message}\", loop=loop)\n\n        loop.run_until_complete(worker_1())\n        loop.run_until_complete(worker_2())\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\nB\\n\"\n    assert err == \"\"\n\n\ndef test_tasks_cancelled_on_remove(capsys):\n    logger.add(async_writer, format=\"{message}\", catch=False)\n\n    async def foo():\n        logger.info(\"A\")\n        logger.info(\"B\")\n        logger.info(\"C\")\n        logger.remove()\n        await logger.complete()\n\n    asyncio.run(foo())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\ndef test_remove_without_tasks(capsys):\n    logger.add(async_writer, format=\"{message}\", catch=False)\n    logger.remove()\n\n    async def foo():\n        logger.info(\"!\")\n        await logger.complete()\n\n    asyncio.run(foo())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\ndef test_complete_without_tasks(capsys):\n    logger.add(async_writer, catch=False)\n\n    async def worker():\n        await logger.complete()\n\n    asyncio.run(worker())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\ndef test_complete_stream_noop(capsys):\n    logger.add(sys.stderr, format=\"{message}\", catch=False)\n    logger.info(\"A\")\n\n    async def worker():\n        logger.info(\"B\")\n        await logger.complete()\n        logger.info(\"C\")\n\n    asyncio.run(worker())\n\n    logger.info(\"D\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"A\\nB\\nC\\nD\\n\"\n\n\ndef test_complete_file_noop(tmp_path):\n    filepath = tmp_path / \"test.log\"\n\n    logger.add(filepath, format=\"{message}\", catch=False)\n    logger.info(\"A\")\n\n    async def worker():\n        logger.info(\"B\")\n        await logger.complete()\n        logger.info(\"C\")\n\n    asyncio.run(worker())\n\n    logger.info(\"D\")\n\n    assert filepath.read_text() == \"A\\nB\\nC\\nD\\n\"\n\n\ndef test_complete_function_noop():\n    out = \"\"\n\n    def write(msg):\n        nonlocal out\n        out += msg\n\n    logger.add(write, format=\"{message}\", catch=False)\n    logger.info(\"A\")\n\n    async def worker():\n        logger.info(\"B\")\n        await logger.complete()\n        logger.info(\"C\")\n\n    asyncio.run(worker())\n\n    logger.info(\"D\")\n\n    assert out == \"A\\nB\\nC\\nD\\n\"\n\n\ndef test_complete_standard_noop(capsys):\n    logger.add(logging.StreamHandler(sys.stderr), format=\"{message}\", catch=False)\n    logger.info(\"A\")\n\n    async def worker():\n        logger.info(\"B\")\n        await logger.complete()\n        logger.info(\"C\")\n\n    asyncio.run(worker())\n\n    logger.info(\"D\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"A\\nB\\nC\\nD\\n\"\n\n\ndef test_exception_in_coroutine_caught(capsys):\n    async def sink(msg):\n        raise Exception(\"Oh no\")\n\n    async def main():\n        logger.add(sink, catch=True)\n        logger.info(\"Hello world\")\n        await asyncio.sleep(0.1)\n        await logger.complete()\n\n    asyncio.run(main())\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Hello world.*\\}\", lines[1])\n    assert lines[-2] == \"Exception: Oh no\"\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_exception_in_coroutine_not_caught(capsys, caplog):\n    async def sink(msg):\n        raise ValueError(\"Oh no\")\n\n    async def main():\n        logger.add(sink, catch=False)\n        logger.info(\"Hello world\")\n        await asyncio.sleep(0.1)\n        await logger.complete()\n\n    asyncio.run(main())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n    records = caplog.records\n    assert len(records) == 1\n    record = records[0]\n\n    message = record.getMessage()\n    assert \"Logging error in Loguru Handler\" not in message\n    assert \"was never retrieved\" not in message\n\n    exc_type, exc_value, _ = record.exc_info\n    assert exc_type is ValueError\n    assert str(exc_value) == \"Oh no\"\n\n\ndef test_exception_in_coroutine_during_complete_caught(capsys):\n    async def sink(msg):\n        await asyncio.sleep(0.1)\n        raise Exception(\"Oh no\")\n\n    async def main():\n        logger.add(sink, catch=True)\n        logger.info(\"Hello world\")\n        await logger.complete()\n\n    asyncio.run(main())\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    assert out == \"\"\n    assert lines[0] == \"--- Logging error in Loguru Handler #0 ---\"\n    assert re.match(r\"Record was: \\{.*Hello world.*\\}\", lines[1])\n    assert lines[-2] == \"Exception: Oh no\"\n    assert lines[-1] == \"--- End of logging error ---\"\n\n\ndef test_exception_in_coroutine_during_complete_not_caught(capsys, caplog):\n    async def sink(msg):\n        await asyncio.sleep(0.1)\n        raise ValueError(\"Oh no\")\n\n    async def main():\n        logger.add(sink, catch=False)\n        logger.info(\"Hello world\")\n        await logger.complete()\n\n    asyncio.run(main())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n    records = caplog.records\n    assert len(records) == 1\n    record = records[0]\n\n    message = record.getMessage()\n    assert \"Logging error in Loguru Handler\" not in message\n    assert \"was never retrieved\" not in message\n\n    exc_type, exc_value, _ = record.exc_info\n    assert exc_type is ValueError\n    assert str(exc_value) == \"Oh no\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\ndef test_enqueue_coroutine_loop(capsys):\n    with new_event_loop_context() as loop:\n        logger.add(async_writer, enqueue=True, loop=loop, format=\"{message}\", catch=False)\n\n        async def worker():\n            logger.info(\"A\")\n            await logger.complete()\n\n        loop.run_until_complete(worker())\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_enqueue_coroutine_from_inside_coroutine_without_loop(capsys):\n    with new_event_loop_context() as loop:\n\n        async def worker():\n            logger.add(async_writer, enqueue=True, loop=None, format=\"{message}\", catch=False)\n            logger.info(\"A\")\n            await logger.complete()\n\n        loop.run_until_complete(worker())\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_custom_complete_function(capsys):\n    awaited = False\n\n    class Handler:\n        def write(self, message):\n            print(message, end=\"\")\n\n        async def complete(self):\n            nonlocal awaited\n            awaited = True\n\n    async def worker():\n        logger.info(\"A\")\n        await logger.complete()\n\n    logger.add(Handler(), catch=False, format=\"{message}\")\n\n    asyncio.run(worker())\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n    assert awaited\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\n@pytest.mark.parametrize(\"loop_is_none\", [True, False])\ndef test_complete_from_another_loop(capsys, loop_is_none):\n    with new_event_loop_context() as main_loop, new_event_loop_context() as second_loop:\n        loop = None if loop_is_none else main_loop\n        logger.add(async_writer, loop=loop, format=\"{message}\")\n\n        async def worker_1():\n            logger.info(\"A\")\n\n        async def worker_2():\n            await logger.complete()\n\n        main_loop.run_until_complete(worker_1())\n        second_loop.run_until_complete(worker_2())\n\n        out, err = capsys.readouterr()\n        assert out == err == \"\"\n\n        main_loop.run_until_complete(worker_2())\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_complete_from_multiple_threads_loop_is_none(capsys):\n    async def worker(i):\n        for _ in range(100):\n            await asyncio.sleep(0)\n            logger.info(\"{:03}\", i)\n        await logger.complete()\n\n    async def sink(msg):\n        print(msg, end=\"\")\n\n    def worker_(i):\n        asyncio.run(worker(i))\n\n    logger.add(sink, catch=False, format=\"{message}\")\n\n    threads = [threading.Thread(target=worker_, args=(i,)) for i in range(10)]\n\n    for t in threads:\n        t.start()\n\n    for t in threads:\n        t.join()\n\n    out, err = capsys.readouterr()\n    assert sorted(out.splitlines()) == [\"{:03}\".format(i) for i in range(10) for _ in range(100)]\n    assert err == \"\"\n\n\ndef test_complete_from_multiple_threads_loop_is_not_none(capsys):\n    async def worker(i):\n        for _ in range(100):\n            await asyncio.sleep(0)\n            logger.info(\"{:03}\", i)\n        await logger.complete()\n\n    async def sink(msg):\n        print(msg, end=\"\")\n\n    def worker_(i):\n        asyncio.run(worker(i))\n\n    with new_event_loop_context() as loop:\n        logger.add(sink, catch=False, format=\"{message}\", loop=loop)\n\n        threads = [threading.Thread(target=worker_, args=(i,)) for i in range(10)]\n\n        for t in threads:\n            t.start()\n\n        for t in threads:\n            t.join()\n\n        async def complete():\n            await logger.complete()\n\n        loop.run_until_complete(complete())\n\n    out, err = capsys.readouterr()\n    assert sorted(out.splitlines()) == [\"{:03}\".format(i) for i in range(10) for _ in range(100)]\n    assert err == \"\"\n\n\ndef test_complete_and_sink_write_concurrency():\n    count = 1000\n    n = 0\n\n    async def sink(message):\n        nonlocal n\n        n += 1\n\n    async def some_task():\n        for _ in range(count):\n            logger.info(\"Message\")\n            await asyncio.sleep(0)\n\n    async def another_task():\n        for _ in range(count):\n            await logger.complete()\n            await asyncio.sleep(0)\n\n    async def main():\n        logger.remove()\n        logger.add(sink, catch=False)\n\n        await asyncio.gather(some_task(), another_task())\n\n    asyncio.run(main())\n\n    assert n == count\n\n\ndef test_complete_and_contextualize_concurrency():\n    called = False\n\n    async def main():\n        logging_event = asyncio.Event()\n        contextualize_event = asyncio.Event()\n\n        async def sink(message):\n            nonlocal called\n            logging_event.set()\n            await contextualize_event.wait()\n            called = True\n\n        async def logging_task():\n            logger.info(\"Message\")\n            await logger.complete()\n\n        async def contextualize_task():\n            with logger.contextualize():\n                contextualize_event.set()\n                await logging_event.wait()\n\n        logger.remove()\n        logger.add(sink, catch=False)\n\n        await asyncio.gather(logging_task(), contextualize_task())\n\n    asyncio.run(main())\n\n    assert called\n\n\nasync def async_subworker(logger_):\n    logger_.info(\"Child\")\n    await logger_.complete()\n\n\nasync def async_mainworker(logger_):\n    logger_.info(\"Main\")\n    await logger_.complete()\n\n\ndef subworker(logger_):\n    with new_event_loop_context() as loop:\n        loop.run_until_complete(async_subworker(logger_))\n\n\nclass Writer:\n    def __init__(self):\n        self.output = \"\"\n\n    async def write(self, message):\n        self.output += message\n\n\ndef test_complete_with_sub_processes(capsys):\n    spawn_context = multiprocessing.get_context(\"spawn\")\n\n    with new_event_loop_context() as loop:\n        writer = Writer()\n        logger.add(writer.write, context=spawn_context, format=\"{message}\", enqueue=True, loop=loop)\n\n        process = spawn_context.Process(target=subworker, args=[logger])\n        process.start()\n        process.join()\n\n        async def complete():\n            await logger.complete()\n\n        loop.run_until_complete(complete())\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n    assert writer.output == \"Child\\n\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 5, 3), reason=\"Coroutine can't access running loop\")\ndef test_invalid_coroutine_sink_if_no_loop_with_enqueue():\n    with pytest.raises(\n        ValueError,\n        match=(\n            \"^An event loop is required to add a coroutine sink with `enqueue=True`, \"\n            \"but none has been passed as argument and none is currently running.$\"\n        ),\n    ):\n        logger.add(async_writer, enqueue=True, loop=None)\n"
  },
  {
    "path": "tests/test_datetime.py",
    "content": "import datetime\nimport os\nimport sys\nfrom time import strftime\nfrom unittest.mock import Mock\n\nimport freezegun\nimport pytest\n\nimport loguru\nfrom loguru import logger\n\nif sys.version_info < (3, 6):\n    UTC_NAME = \"UTC+00:00\"\nelse:\n    UTC_NAME = \"UTC\"\n\n\ndef _expected_fallback_time_zone():\n    # For some reason, Python versions and interepreters return different time zones here.\n    return strftime(\"%Z\")\n\n\n@pytest.mark.parametrize(\n    (\"time_format\", \"date\", \"timezone\", \"expected\"),\n    [\n        (\n            \"%Y-%m-%d %H-%M-%S %f %Z %z\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"UTC\", 0),\n            \"2018-06-09 01-02-03 000045 UTC +0000\",\n        ),\n        (\n            \"YYYY-MM-DD HH-mm-ss SSSSSS zz ZZ\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"UTC\", 0),\n            \"2018-06-09 01-02-03 000045 UTC +0000\",\n        ),\n        (\n            \"%Y-%m-%d %H-%M-%S %f %Z %z\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"EST\", -18000),\n            \"2018-06-09 01-02-03 000045 EST -0500\",\n        ),\n        (\n            \"YYYY-MM-DD HH-mm-ss SSSSSS zz ZZ\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"EST\", -18000),\n            \"2018-06-09 01-02-03 000045 EST -0500\",\n        ),\n        (\n            \"%Y-%m-%d %H-%M-%S %f %Z!UTC\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"UTC\", 0),\n            \"2018-06-09 01-02-03 000045 %s\" % UTC_NAME,\n        ),\n        (\n            \"YYYY-MM-DD HH-mm-ss SSSSSS zz!UTC\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"UTC\", 0),\n            \"2018-06-09 01-02-03 000045 %s\" % UTC_NAME,\n        ),\n        (\n            \"%Y-%m-%d %H-%M-%S %f %Z %z!UTC\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"EST\", -18000),\n            \"2018-06-09 06-02-03 000045 %s +0000\" % UTC_NAME,\n        ),\n        (\n            \"YYYY-MM-DD HH-mm-ss SSSSSS zz ZZ!UTC\",\n            \"2018-06-09 01:02:03.000045\",\n            (\"UTC\", -18000),\n            \"2018-06-09 06-02-03 000045 %s +0000\" % UTC_NAME,\n        ),\n        (\n            \"YY-M-D H-m-s SSS Z\",\n            \"2005-04-07 09:03:08.002320\",\n            (\"A\", 3600),\n            \"05-4-7 9-3-8 002 +01:00\",\n        ),\n        (\n            \"Q_DDDD_DDD d_E h_hh A SS ZZ\",\n            \"2000-01-01 14:00:00.9\",\n            (\"B\", -1800),\n            \"1_001_1 5_6 2_02 PM 90 -0030\",\n        ),\n        (\"hh A\", \"2018-01-01 00:01:02.000003\", (\"UTC\", 0), \"12 AM\"),\n        (\"hh A\", \"2018-01-01 12:00:00.0\", (\"UTC\", 0), \"12 PM\"),\n        (\"hh A\", \"2018-01-01 23:00:00.0\", (\"UTC\", 0), \"11 PM\"),\n        (\"[YYYY] MM [DD]\", \"2018-02-03 11:09:00.000002\", (\"UTC\", 0), \"YYYY 02 DD\"),\n        (\"[YYYY MM DD]\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"[2018 01 03]\"),\n        (\"[[YY]]\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"[YY]\"),\n        (\"[]\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"\"),\n        (\"[[]]\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"[]\"),\n        (\"SSSSSS[]SSS[]SSSSSS\", \"2018-01-03 11:03:04.100002\", (\"UTC\", 0), \"100002100100002\"),\n        (\"[HHmmss\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"[110304\"),\n        (\"HHmmss]\", \"2018-01-03 11:03:04.000002\", (\"UTC\", 0), \"110304]\"),\n        (\"HH:mm:ss!UTC\", \"2018-01-01 11:30:00.0\", (\"A\", 7200), \"09:30:00\"),\n        (\"UTC! HH:mm:ss\", \"2018-01-01 11:30:00.0\", (\"A\", 7200), \"UTC! 11:30:00\"),\n        (\"!UTC HH:mm:ss\", \"2018-01-01 11:30:00.0\", (\"A\", 7200), \"!UTC 11:30:00\"),\n        (\n            \"hh:mm:ss A - Z ZZ !UTC\",\n            \"2018-01-01 12:30:00.0\",\n            (\"A\", 5400),\n            \"11:00:00 AM - +00:00 +0000 \",\n        ),\n        (\n            \"YYYY-MM-DD HH:mm:ss[Z]!UTC\",\n            \"2018-01-03 11:03:04.2\",\n            (\"XYZ\", -7200),\n            \"2018-01-03 13:03:04Z\",\n        ),\n        (\"HH:mm:ss[!UTC]\", \"2018-01-01 11:30:00.0\", (\"A\", 7200), \"11:30:00!UTC\"),\n        (\"\", \"2018-02-03 11:09:00.000002\", (\"Z\", 1800), \"2018-02-03T11:09:00.000002+0030\"),\n        (\"!UTC\", \"2018-02-03 11:09:00.000002\", (\"Z\", 1800), \"2018-02-03T10:39:00.000002+0000\"),\n        (\n            \"X x\",\n            \"2023-01-01 00:00:00.000500\",\n            (\"UTC\", 0),\n            \"1672531200 1672531200000500\",\n        ),\n        (\n            \"YYYY-MM-DD HH:mm:ss.SSSSSS x\",\n            datetime.datetime(2242, 3, 16, 12, 56, 32, 999999),  # The year 2242 bug!\n            (\"UTC\", 0),\n            \"2242-03-16 12:56:32.999999 8589934592999999\",\n        ),\n    ],\n)\ndef test_formatting(writer, freeze_time, time_format, date, timezone, expected):\n    with freeze_time(date, timezone):\n        logger.add(writer, format=\"{time:%s}\" % time_format)\n        logger.debug(\"X\")\n        result = writer.read()\n        assert result == expected + \"\\n\"\n\n\n@pytest.mark.parametrize(\n    (\"time_format\", \"offset\", \"expected\"),\n    [\n        (\"%Y-%m-%d %H-%M-%S %f %Z %z\", 7230.099, \"2018-06-09 01-02-03 000000 ABC +020030.099000\"),\n        (\"YYYY-MM-DD HH-mm-ss zz Z ZZ\", 6543, \"2018-06-09 01-02-03 ABC +01:49:03 +014903\"),\n        (\"HH-mm-ss zz Z ZZ\", -12345.06702, \"01-02-03 ABC -03:26:45.067020 -032645.067020\"),\n    ],\n)\n@pytest.mark.skipif(sys.version_info < (3, 7), reason=\"Offset must be a whole number of minutes\")\ndef test_formatting_timezone_offset_down_to_the_second(\n    writer, freeze_time, time_format, offset, expected\n):\n    date = datetime.datetime(2018, 6, 9, 1, 2, 3)\n    with freeze_time(date, (\"ABC\", offset)):\n        logger.add(writer, format=\"{time:%s}\" % time_format)\n        logger.debug(\"Test\")\n        result = writer.read()\n        assert result == expected + \"\\n\"\n\n\ndef test_locale_formatting(writer, freeze_time):\n    dt = datetime.datetime(2011, 1, 1, 22, 22, 22, 0)\n    with freeze_time(dt):\n        logger.add(writer, format=\"{time:MMMM MMM dddd ddd}\")\n        logger.debug(\"Test\")\n        assert writer.read() == dt.strftime(\"%B %b %A %a\\n\")\n\n\ndef test_stdout_formatting(freeze_time, capsys):\n    with freeze_time(\"2015-12-25 19:13:18\", (\"A\", 5400)):\n        logger.add(sys.stdout, format=\"{time:YYYY [MM] DD HHmmss Z} {message}\")\n        logger.debug(\"Y\")\n        out, err = capsys.readouterr()\n        assert out == \"2015 MM 25 191318 +01:30 Y\\n\"\n        assert err == \"\"\n\n\ndef test_file_formatting(freeze_time, tmp_path):\n    with freeze_time(\"2015-12-25 19:13:18\", (\"A\", -5400)):\n        logger.add(tmp_path / \"{time:YYYY [MM] DD HHmmss ZZ}.log\")\n        logger.debug(\"Z\")\n        assert list(tmp_path.iterdir()) == [tmp_path / \"2015 MM 25 191318 -0130.log\"]\n\n\ndef test_missing_struct_time_fields(writer, freeze_time):\n    with freeze_time(\"2011-01-02 03:04:05.6\", (\"A\", 7200), include_tm_zone=False):\n        logger.add(writer, format=\"{time:YYYY MM DD HH mm ss SSSSSS ZZ zz}\")\n        logger.debug(\"X\")\n\n        result = writer.read()\n        zone = _expected_fallback_time_zone()\n\n        assert result == \"2011 01 02 03 04 05 600000 +0200 %s\\n\" % zone\n\n\n@pytest.mark.parametrize(\"tm_gmtoff\", [-4294963696, 4294963696])\ndef test_value_of_gmtoff_is_invalid(writer, freeze_time, tm_gmtoff):\n    with freeze_time(\"2011-01-02 03:04:05.6\", (\"ABC\", -3600), tm_gmtoff_override=tm_gmtoff):\n        logger.add(writer, format=\"{time:YYYY MM DD HH mm ss SSSSSS ZZ zz}\")\n        logger.debug(\"X\")\n\n        result = writer.read()\n        zone = _expected_fallback_time_zone()\n\n        assert result == \"2011 01 02 03 04 05 600000 -0100 %s\\n\" % zone\n\n\n@pytest.mark.parametrize(\"exception\", [OSError, OverflowError])\ndef test_localtime_raising_exception(writer, freeze_time, monkeypatch, exception):\n    with freeze_time(\"2011-01-02 03:04:05.6\", (\"A\", 7200), include_tm_zone=True):\n        with monkeypatch.context() as context:\n            mock = Mock(side_effect=exception)\n            context.setattr(loguru._datetime, \"localtime\", mock, raising=True)\n\n            logger.add(writer, format=\"{time:YYYY MM DD HH mm ss SSSSSS ZZ zz}\")\n            logger.debug(\"X\")\n\n            assert mock.called\n\n            result = writer.read()\n            zone = _expected_fallback_time_zone()\n\n            assert result == \"2011 01 02 03 04 05 600000 +0200 %s\\n\" % zone\n\n\n@pytest.mark.skipif(sys.version_info < (3, 9), reason=\"No zoneinfo module available\")\n@pytest.mark.skipif(os.name == \"nt\", reason=\"No IANA database available\")\n@pytest.mark.parametrize(\n    (\"date\", \"expected_result\"),\n    [\n        (\"2023-07-01 12:00:00\", \"2023 07 01 14 00 00 000000 +0200 CEST +02:00\"),  # DST.\n        (\"2023-01-01 12:00:00\", \"2023 01 01 13 00 00 000000 +0100 CET +01:00\"),  # Non-DST.\n    ],\n)\ndef test_update_with_zone_info(writer, freeze_time, date, expected_result):\n    from zoneinfo import ZoneInfo\n\n    def tz_converter(record):\n        record[\"time\"] = record[\"time\"].astimezone(tz=ZoneInfo(\"Europe/Paris\"))\n\n    with freeze_time(date):\n        logger.add(writer, format=\"{time:YYYY MM DD HH mm ss SSSSSS ZZ zz Z}\")\n\n        logger.patch(tz_converter).debug(\"Message\")\n\n        result = writer.read()\n        assert result == expected_result + \"\\n\"\n\n\ndef test_freezegun_mocking(writer):\n    logger.add(writer, format=\"[{time:YYYY MM DD HH:mm:ss}] {message}\")\n\n    with freezegun.freeze_time(\"2000-01-01 18:00:05\"):\n        logger.info(\"Frozen\")\n\n    assert writer.read() == \"[2000 01 01 18:00:05] Frozen\\n\"\n\n\n@pytest.mark.parametrize(\n    \"time_format\", [\"ss.SSSSSSS\", \"SS.SSSSSSSS.SS\", \"HH:mm:ss.SSSSSSSSS\", \"SSSSSSSSSS\"]\n)\ndef test_invalid_time_format(writer, time_format):\n    logger.add(writer, format=\"{time:%s} {message}\" % time_format, catch=False)\n    with pytest.raises(ValueError, match=\"Invalid time format\"):\n        logger.info(\"Test\")\n"
  },
  {
    "path": "tests/test_deepcopy.py",
    "content": "import copy\n\nfrom loguru import logger\n\n\ndef print_(message):\n    print(message, end=\"\")\n\n\ndef test_add_sink_after_deepcopy(capsys):\n    logger_ = copy.deepcopy(logger)\n\n    logger_.add(print_, format=\"{message}\", catch=False)\n\n    logger_.info(\"A\")\n    logger.info(\"B\")\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_add_sink_before_deepcopy(capsys):\n    logger.add(print_, format=\"{message}\", catch=False)\n\n    logger_ = copy.deepcopy(logger)\n\n    logger_.info(\"A\")\n    logger.info(\"B\")\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\nB\\n\"\n    assert err == \"\"\n\n\ndef test_remove_from_original(capsys):\n    logger.add(print_, format=\"{message}\", catch=False)\n\n    logger_ = copy.deepcopy(logger)\n    logger.remove()\n\n    logger_.info(\"A\")\n    logger.info(\"B\")\n\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_remove_from_copy(capsys):\n    logger.add(print_, format=\"{message}\", catch=False)\n\n    logger_ = copy.deepcopy(logger)\n    logger_.remove()\n\n    logger_.info(\"A\")\n    logger.info(\"B\")\n\n    out, err = capsys.readouterr()\n    assert out == \"B\\n\"\n    assert err == \"\"\n"
  },
  {
    "path": "tests/test_defaults.py",
    "content": "import pytest\n\nfrom loguru._defaults import env\n\n\n@pytest.mark.parametrize(\"value\", [\"test\", \"\"])\ndef test_string(value, monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"VALID_STRING\"\n        context.setenv(key, value)\n        assert env(key, str) == value\n\n\n@pytest.mark.parametrize(\"value\", [\"y\", \"1\", \"TRUE\"])\ndef test_bool_positive(value, monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"VALID_BOOL_POS\"\n        context.setenv(key, value)\n        assert env(key, bool) is True\n\n\n@pytest.mark.parametrize(\"value\", [\"NO\", \"0\", \"false\"])\ndef test_bool_negative(value, monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"VALID_BOOL_NEG\"\n        context.setenv(key, value)\n        assert env(key, bool) is False\n\n\ndef test_int(monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"VALID_INT\"\n        context.setenv(key, \"42\")\n        assert env(key, int) == 42\n\n\n@pytest.mark.parametrize(\"value\", [\"\", \"a\"])\ndef test_invalid_int(value, monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"INVALID_INT\"\n        context.setenv(key, value)\n        with pytest.raises(\n            ValueError,\n            match=r\"^Invalid environment variable 'INVALID_INT' \\(expected an integer\\): '[^']*'$\",\n        ):\n            env(key, int)\n\n\n@pytest.mark.parametrize(\"value\", [\"\", \"a\"])\ndef test_invalid_bool(value, monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"INVALID_BOOL\"\n        context.setenv(key, value)\n        with pytest.raises(\n            ValueError,\n            match=r\"^Invalid environment variable 'INVALID_BOOL' \\(expected a boolean\\): '[^']*'$\",\n        ):\n            env(key, bool)\n\n\ndef test_invalid_type(monkeypatch):\n    with monkeypatch.context() as context:\n        key = \"INVALID_TYPE\"\n        context.setenv(key, \"42.0\")\n        with pytest.raises(ValueError, match=\"^The requested type '[^']+' is not supported\"):\n            env(key, float)\n"
  },
  {
    "path": "tests/test_exceptions_catch.py",
    "content": "import asyncio\nimport site\nimport sys\nimport sysconfig\nimport threading\nimport types\n\nimport pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\"diagnose\", [False, True])\ndef test_caret_not_masked(writer, diagnose):\n    logger.add(writer, backtrace=True, diagnose=diagnose, colorize=False, format=\"\")\n\n    @logger.catch\n    def f(n):\n        1 / n\n        f(n - 1)\n\n    f(30)\n\n    assert sum(line.startswith(\"> \") for line in writer.read().splitlines()) == 1\n\n\n@pytest.mark.parametrize(\"diagnose\", [False, True])\ndef test_no_caret_if_no_backtrace(writer, diagnose):\n    logger.add(writer, backtrace=False, diagnose=diagnose, colorize=False, format=\"\")\n\n    @logger.catch\n    def f(n):\n        1 / n\n        f(n - 1)\n\n    f(30)\n\n    assert sum(line.startswith(\"> \") for line in writer.read().splitlines()) == 0\n\n\n@pytest.mark.parametrize(\"encoding\", [\"ascii\", \"UTF8\", None, \"unknown-encoding\", \"\", object()])\ndef test_sink_encoding(writer, encoding):\n    class Writer:\n        def __init__(self, encoding):\n            self.encoding = encoding\n            self.output = \"\"\n\n        def write(self, message):\n            self.output += message\n\n    writer = Writer(encoding)\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    def foo(a, b):\n        a / b\n\n    def bar(c):\n        foo(c, 0)\n\n    try:\n        bar(4)\n    except ZeroDivisionError:\n        logger.exception(\"\")\n\n    assert writer.output.endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_file_sink_ascii_encoding(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"\", encoding=\"ascii\", errors=\"backslashreplace\", catch=False)\n    a = \"天\"\n\n    try:\n        \"天\" * a\n    except Exception:\n        logger.exception(\"\")\n\n    logger.remove()\n    result = file.read_text(\"ascii\")\n    assert result.count('\"\\\\u5929\" * a') == 1\n    assert result.count(\"-> '\\\\u5929'\") == 1\n\n\ndef test_file_sink_utf8_encoding(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"\", encoding=\"utf8\", errors=\"strict\", catch=False)\n    a = \"天\"\n\n    try:\n        \"天\" * a\n    except Exception:\n        logger.exception(\"\")\n\n    logger.remove()\n    result = file.read_text(\"utf8\")\n    assert result.count('\"天\" * a') == 1\n    assert result.count(\"└ '天'\") == 1\n\n\ndef test_has_sys_real_prefix(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(sys, \"real_prefix\", \"/foo/bar/baz\", raising=False)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_no_sys_real_prefix(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.delattr(sys, \"real_prefix\", raising=False)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_has_site_getsitepackages(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(site, \"getsitepackages\", lambda: [\"foo\", \"bar\", \"baz\"], raising=False)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_no_site_getsitepackages(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.delattr(site, \"getsitepackages\", raising=False)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_user_site_is_path(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(site, \"USER_SITE\", \"/foo/bar/baz\")\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_user_site_is_none(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(site, \"USER_SITE\", None)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_sysconfig_get_path_return_path(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(sysconfig, \"get_path\", lambda *a, **k: \"/foo/bar/baz\")\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_sysconfig_get_path_return_none(writer, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setattr(sysconfig, \"get_path\", lambda *a, **k: None)\n        logger.add(writer, backtrace=False, diagnose=True, colorize=False, format=\"\")\n\n        try:\n            1 / 0  # noqa: B018\n        except ZeroDivisionError:\n            logger.exception(\"\")\n\n        assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_no_exception(writer):\n    logger.add(writer, backtrace=False, diagnose=False, colorize=False, format=\"{message}\")\n\n    logger.exception(\"No Error.\")\n\n    assert writer.read() in (\n        \"No Error.\\nNoneType\\n\",\n        \"No Error.\\nNoneType: None\\n\",  # Old versions of Python 3.5\n    )\n\n\ndef test_exception_is_none():\n    err = object()\n\n    def writer(msg):\n        nonlocal err\n        err = msg.record[\"exception\"]\n\n    logger.add(writer)\n\n    logger.error(\"No exception\")\n\n    assert err is None\n\n\ndef test_exception_is_tuple():\n    exception = None\n\n    def writer(msg):\n        nonlocal exception\n        exception = msg.record[\"exception\"]\n\n    logger.add(writer, catch=False)\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Exception\")\n        reference = sys.exc_info()\n\n    t_1, v_1, tb_1 = exception\n    t_2, v_2, tb_2 = (x for x in exception)\n    t_3, v_3, tb_3 = exception[0], exception[1], exception[2]\n    t_4, v_4, tb_4 = exception.type, exception.value, exception.traceback\n\n    assert isinstance(exception, tuple)\n    assert len(exception) == 3\n    assert exception == reference\n    assert reference == exception\n    assert not (exception != reference)\n    assert not (reference != exception)\n    assert all(t is ZeroDivisionError for t in (t_1, t_2, t_3, t_4))\n    assert all(isinstance(v, ZeroDivisionError) for v in (v_1, v_2, v_3, v_4))\n    assert all(isinstance(tb, types.TracebackType) for tb in (tb_1, tb_2, tb_3, tb_4))\n\n\n@pytest.mark.parametrize(\n    \"exception\", [ZeroDivisionError, ArithmeticError, (ValueError, ZeroDivisionError)]\n)\ndef test_exception_not_raising(writer, exception):\n    logger.add(writer)\n\n    @logger.catch(exception)\n    def a():\n        1 / 0  # noqa: B018\n\n    a()\n    assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\n@pytest.mark.parametrize(\"exception\", [ValueError, ((SyntaxError, TypeError))])\ndef test_exception_raising(writer, exception):\n    logger.add(writer)\n\n    @logger.catch(exception=exception)\n    def a():\n        1 / 0  # noqa: B018\n\n    with pytest.raises(ZeroDivisionError):\n        a()\n\n    assert writer.read() == \"\"\n\n\n@pytest.mark.parametrize(\n    \"exclude\", [ZeroDivisionError, ArithmeticError, (ValueError, ZeroDivisionError)]\n)\n@pytest.mark.parametrize(\"exception\", [BaseException, ZeroDivisionError])\ndef test_exclude_exception_raising(writer, exclude, exception):\n    logger.add(writer)\n\n    @logger.catch(exception, exclude=exclude)\n    def a():\n        1 / 0  # noqa: B018\n\n    with pytest.raises(ZeroDivisionError):\n        a()\n\n    assert writer.read() == \"\"\n\n\n@pytest.mark.parametrize(\"exclude\", [ValueError, ((SyntaxError, TypeError))])\n@pytest.mark.parametrize(\"exception\", [BaseException, ZeroDivisionError])\ndef test_exclude_exception_not_raising(writer, exclude, exception):\n    logger.add(writer)\n\n    @logger.catch(exception, exclude=exclude)\n    def a():\n        1 / 0  # noqa: B018\n\n    a()\n    assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_reraise(writer):\n    logger.add(writer)\n\n    @logger.catch(reraise=True)\n    def a():\n        1 / 0  # noqa: B018\n\n    with pytest.raises(ZeroDivisionError):\n        a()\n\n    assert writer.read().endswith(\"ZeroDivisionError: division by zero\\n\")\n\n\ndef test_onerror(writer):\n    is_error_valid = False\n    logger.add(writer, format=\"{message}\")\n\n    def onerror(error):\n        nonlocal is_error_valid\n        logger.info(\"Called after logged message\")\n        _, exception, _ = sys.exc_info()\n        is_error_valid = (error == exception) and isinstance(error, ZeroDivisionError)\n\n    @logger.catch(onerror=onerror)\n    def a():\n        1 / 0  # noqa: B018\n\n    a()\n\n    assert is_error_valid\n    assert writer.read().endswith(\n        \"ZeroDivisionError: division by zero\\n\" \"Called after logged message\\n\"\n    )\n\n\ndef test_onerror_with_reraise(writer):\n    called = False\n    logger.add(writer, format=\"{message}\")\n\n    def onerror(_):\n        nonlocal called\n        called = True\n\n    with pytest.raises(ZeroDivisionError):\n        with logger.catch(onerror=onerror, reraise=True):\n            1 / 0  # noqa: B018\n\n    assert called\n\n\ndef test_decorate_function(writer):\n    logger.add(writer, format=\"{message}\", diagnose=False, backtrace=False, colorize=False)\n\n    @logger.catch\n    def a(x):\n        return 100 / x\n\n    assert a(50) == 2\n    assert writer.read() == \"\"\n\n\ndef test_decorate_coroutine(writer):\n    logger.add(writer, format=\"{message}\", diagnose=False, backtrace=False, colorize=False)\n\n    @logger.catch\n    async def foo(a, b):\n        return a + b\n\n    result = asyncio.run(foo(100, 5))\n\n    assert result == 105\n    assert writer.read() == \"\"\n\n\ndef test_decorate_generator(writer):\n    @logger.catch\n    def foo(x, y, z):\n        yield x\n        yield y\n        return z\n\n    f = foo(1, 2, 3)\n    assert next(f) == 1\n    assert next(f) == 2\n\n    with pytest.raises(StopIteration, match=r\"3\"):\n        next(f)\n\n\ndef test_decorate_generator_with_error():\n    @logger.catch\n    def foo():\n        yield 0\n        yield 1\n        raise ValueError\n\n    assert list(foo()) == [0, 1]\n\n\ndef test_default_with_function():\n    @logger.catch(default=42)\n    def foo():\n        1 / 0  # noqa: B018\n\n    assert foo() == 42\n\n\ndef test_default_with_generator():\n    @logger.catch(default=42)\n    def foo():\n        yield 1 / 0\n\n    with pytest.raises(StopIteration, match=r\"42\"):\n        next(foo())\n\n\ndef test_default_with_coroutine():\n    @logger.catch(default=42)\n    async def foo():\n        return 1 / 0\n\n    assert asyncio.run(foo()) == 42\n\n\ndef test_async_context_manager():\n    async def coro():\n        async with logger.catch():\n            return 1 / 0\n        return 1\n\n    assert asyncio.run(coro()) == 1\n\n\ndef test_error_when_decorating_class_without_parentheses():\n    with pytest.raises(TypeError):\n\n        @logger.catch\n        class Foo:\n            pass\n\n\ndef test_error_when_decorating_class_with_parentheses():\n    with pytest.raises(TypeError):\n\n        @logger.catch()\n        class Foo:\n            pass\n\n\ndef test_unprintable_but_decorated_repr(writer):\n\n    class Foo:\n        @logger.catch(reraise=True)\n        def __repr__(self):\n            raise ValueError(\"Something went wrong\")\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_but_decorated_repr_without_reraise(writer):\n    class Foo:\n        @logger.catch(reraise=False, default=\"?\")\n        def __repr__(self):\n            raise ValueError(\"Something went wrong\")\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    foo = Foo()\n\n    repr(foo)\n\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_but_decorated_multiple_sinks(capsys):\n    class Foo:\n        @logger.catch(reraise=True)\n        def __repr__(self):\n            raise ValueError(\"Something went wrong\")\n\n    logger.add(sys.stderr, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n    logger.add(sys.stdout, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    out, err = capsys.readouterr()\n    assert out.endswith(\"ValueError: Something went wrong\\n\")\n    assert err.endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_but_decorated_repr_with_enqueue(writer):\n    class Foo:\n        @logger.catch(reraise=True)\n        def __repr__(self):\n            raise ValueError(\"Something went wrong\")\n\n    logger.add(\n        writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False, enqueue=True\n    )\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    logger.complete()\n\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_but_decorated_repr_twice(writer):\n    class Foo:\n        @logger.catch(reraise=True)\n        @logger.catch(reraise=True)\n        def __repr__(self):\n            raise ValueError(\"Something went wrong\")\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_with_catch_context_manager(writer):\n    class Foo:\n        def __repr__(self):\n            with logger.catch(reraise=True):\n                raise ValueError(\"Something went wrong\")\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n\n\ndef test_unprintable_with_catch_context_manager_reused(writer):\n    def sink(_):\n        raise ValueError(\"Sink error\")\n\n    logger.remove()\n    logger.add(sink, catch=False)\n\n    catcher = logger.catch(reraise=False)\n\n    class Foo:\n        def __repr__(self):\n            with catcher:\n                raise ValueError(\"Something went wrong\")\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Sink error$\"):\n        repr(foo)\n\n    logger.remove()\n    logger.add(writer)\n\n    with catcher:\n        raise ValueError(\"Error\")\n\n    assert writer.read().endswith(\"ValueError: Error\\n\")\n\n\ndef test_unprintable_but_decorated_repr_multiple_threads(writer):\n    wait_for_repr_block = threading.Event()\n    wait_for_worker_finish = threading.Event()\n\n    recursive = False\n\n    class Foo:\n        @logger.catch(reraise=True)\n        def __repr__(self):\n            nonlocal recursive\n            if not recursive:\n                recursive = True\n            else:\n                wait_for_repr_block.set()\n                wait_for_worker_finish.wait()\n            raise ValueError(\"Something went wrong\")\n\n    def worker():\n        wait_for_repr_block.wait()\n        with logger.catch(reraise=False):\n            raise ValueError(\"Worker error\")\n        wait_for_worker_finish.set()\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\", catch=False)\n\n    thread = threading.Thread(target=worker)\n    thread.start()\n\n    foo = Foo()\n\n    with pytest.raises(ValueError, match=\"^Something went wrong$\"):\n        repr(foo)\n\n    thread.join()\n\n    assert \"ValueError: Worker error\\n\" in writer.read()\n    assert writer.read().endswith(\"ValueError: Something went wrong\\n\")\n"
  },
  {
    "path": "tests/test_exceptions_formatting.py",
    "content": "import os\nimport platform\nimport re\nimport subprocess\nimport sys\nimport traceback\nfrom unittest.mock import MagicMock\n\nimport pytest\n\nfrom loguru import logger\n\n\ndef normalize(exception):\n    \"\"\"Normalize exception output for reproducible test cases.\"\"\"\n    if os.name == \"nt\":\n        exception = re.sub(\n            r'File[^\"]+\"[^\"]+\\.py[^\"]*\"', lambda m: m.group().replace(\"\\\\\", \"/\"), exception\n        )\n        exception = re.sub(r\"(\\r\\n|\\r|\\n)\", \"\\n\", exception)\n\n    if sys.version_info >= (3, 9, 0):\n\n        def fix_filepath(match):\n            filepath = match.group(1)\n\n            # Pattern to check if the filepath contains ANSI escape codes.\n            pattern = (\n                r'((?:\\x1b\\[[0-9]*m)+)([^\"]+?)((?:\\x1b\\[[0-9]*m)+)([^\"]+?)((?:\\x1b\\[[0-9]*m)+)'\n            )\n\n            match = re.match(pattern, filepath)\n            start_directory = os.path.dirname(os.path.dirname(__file__))\n            if match:\n                # Simplify the path while preserving the color highlighting of the file basename.\n                groups = list(match.groups())\n                groups[1] = os.path.relpath(os.path.abspath(groups[1]), start_directory) + \"/\"\n                relpath = \"\".join(groups)\n            else:\n                # We can straightforwardly convert from absolute to relative path.\n                relpath = os.path.relpath(os.path.abspath(filepath), start_directory)\n            return 'File \"%s\"' % relpath.replace(\"\\\\\", \"/\")\n\n        exception = re.sub(\n            r'File \"([^\"]+\\.py[^\"]*)\"',\n            fix_filepath,\n            exception,\n        )\n\n    if sys.version_info < (3, 9, 0):\n        if \"SyntaxError\" in exception:\n            exception = re.sub(r\"(\\n *)(\\^ *\\n)\", r\"\\1 \\2\", exception)\n        elif \"IndentationError\" in exception:\n            exception = re.sub(r\"\\n *\\^ *\\n\", \"\\n\", exception)\n\n    if sys.version_info < (3, 10, 0):\n        for module, line_before, line_after in [\n            (\"handler_formatting_with_context_manager.py\", 17, 16),\n            (\"message_formatting_with_context_manager.py\", 13, 10),\n        ]:\n            if module not in exception:\n                continue\n            expression = r\"^(__main__ %s a) %d\\n\" % (module, line_before)\n            exception = re.sub(expression, r\"\\1 %d\\n\" % line_after, exception)\n\n    exception = re.sub(\n        r'\"[^\"]*/somelib/__init__.py\"', '\"/usr/lib/python/somelib/__init__.py\"', exception\n    )\n\n    exception = re.sub(r\"\\b0x[0-9a-fA-F]+\\b\", \"0xDEADBEEF\", exception)\n\n    if platform.python_implementation() == \"PyPy\":\n        exception = (\n            exception.replace(\n                \"<function str.isdigit at 0xDEADBEEF>\", \"<method 'isdigit' of 'str' objects>\"\n            )\n            .replace(\n                \"<function coroutine.send at 0xDEADBEEF>\", \"<method 'send' of 'coroutine' objects>\"\n            )\n            .replace(\n                \"<function NoneType.__bool__ at 0xDEADBEEF>\",\n                \"<slot wrapper '__bool__' of 'NoneType' objects>\",\n            )\n        )\n\n    return exception\n\n\ndef generate(output, outpath):\n    \"\"\"Generate new output file if exception formatting is updated.\"\"\"\n    os.makedirs(os.path.dirname(outpath), exist_ok=True)\n    with open(outpath, \"w\") as file:\n        file.write(output)\n    raise AssertionError(\"The method 'generate()' was called while running tests.\")\n\n\ndef compare_exception(dirname, filename):\n    cwd = os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\"))\n    python = sys.executable or \"python\"\n    filepath = os.path.join(\"tests\", \"exceptions\", \"source\", dirname, filename + \".py\")\n    outpath = os.path.join(cwd, \"tests\", \"exceptions\", \"output\", dirname, filename + \".txt\")\n\n    with subprocess.Popen(\n        [python, filepath],\n        shell=False,\n        cwd=cwd,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        universal_newlines=True,\n        env=dict(os.environ, PYTHONPATH=cwd, PYTHONIOENCODING=\"utf8\"),\n    ) as proc:\n        stdout, stderr = proc.communicate()\n        print(stderr, file=sys.stderr)\n        assert proc.returncode == 0\n        assert stdout == \"\"\n        assert stderr != \"\"\n\n    stderr = normalize(stderr)\n\n    # generate(stderr, outpath)\n\n    with open(outpath, \"r\") as file:\n        assert stderr == file.read()\n\n\n@pytest.mark.parametrize(\n    \"filename\",\n    [\n        \"chained_expression_direct\",\n        \"chained_expression_indirect\",\n        \"chaining_first\",\n        \"chaining_second\",\n        \"chaining_third\",\n        \"enqueue\",\n        \"enqueue_with_others_handlers\",\n        \"frame_values_backward\",\n        \"frame_values_forward\",\n        \"function\",\n        \"head_recursion\",\n        \"missing_attributes_traceback_objects\",\n        \"missing_lineno_frame_objects\",\n        \"nested\",\n        \"nested_chained_catch_up\",\n        \"nested_decorator_catch_up\",\n        \"nested_explicit_catch_up\",\n        \"nested_wrapping\",\n        \"no_tb\",\n        \"not_enough_arguments\",\n        \"raising_recursion\",\n        \"suppressed_expression_direct\",\n        \"suppressed_expression_indirect\",\n        \"tail_recursion\",\n        \"too_many_arguments\",\n    ],\n)\ndef test_backtrace(filename):\n    compare_exception(\"backtrace\", filename)\n\n\n@pytest.mark.parametrize(\n    \"filename\",\n    [\n        \"assertion_error\",\n        \"assertion_error_custom\",\n        \"assertion_error_in_string\",\n        \"attributes\",\n        \"chained_both\",\n        \"encoding\",\n        \"global_variable\",\n        \"indentation_error\",\n        \"keyword_argument\",\n        \"multilines_repr\",\n        \"no_error_message\",\n        \"parenthesis\",\n        \"source_multilines\",\n        \"source_strings\",\n        \"syntax_error\",\n        \"syntax_highlighting\",\n        \"truncating\",\n        \"unprintable_object\",\n    ],\n)\ndef test_diagnose(filename):\n    compare_exception(\"diagnose\", filename)\n\n\n@pytest.mark.parametrize(\n    \"filename\",\n    [\n        \"assertion_from_lib\",\n        \"assertion_from_local\",\n        \"callback\",\n        \"catch_decorator\",\n        \"catch_decorator_from_lib\",\n        \"decorated_callback\",\n        \"direct\",\n        \"indirect\",\n        \"string_lib\",\n        \"string_source\",\n        \"syntaxerror\",\n    ],\n)\ndef test_exception_ownership(filename):\n    compare_exception(\"ownership\", filename)\n\n\n@pytest.mark.parametrize(\n    \"filename\",\n    [\n        \"assertionerror_without_traceback\",\n        \"broken_but_decorated_repr\",\n        \"catch_as_context_manager\",\n        \"catch_as_decorator_with_parentheses\",\n        \"catch_as_decorator_without_parentheses\",\n        \"catch_as_function\",\n        \"catch_message\",\n        \"exception_formatting_coroutine\",\n        \"exception_formatting_function\",\n        \"exception_formatting_generator\",\n        \"exception_in_property\",\n        \"handler_formatting_with_context_manager\",\n        \"handler_formatting_with_decorator\",\n        \"level_name\",\n        \"level_number\",\n        \"message_formatting_with_context_manager\",\n        \"message_formatting_with_decorator\",\n        \"nested_with_reraise\",\n        \"one_liner_recursion\",\n        \"recursion_error\",\n        \"repeated_lines\",\n        \"syntaxerror_without_traceback\",\n        \"sys_tracebacklimit\",\n        \"sys_tracebacklimit_negative\",\n        \"sys_tracebacklimit_none\",\n        \"sys_tracebacklimit_unset\",\n        \"zerodivisionerror_without_traceback\",\n    ],\n)\ndef test_exception_others(filename):\n    if filename == \"recursion_error\" and platform.python_implementation() == \"PyPy\":\n        pytest.skip(\"RecursionError is not reliable on PyPy\")\n\n    compare_exception(\"others\", filename)\n\n\n@pytest.mark.parametrize(\n    (\"filename\", \"minimum_python_version\"),\n    [\n        (\"type_hints\", (3, 6)),\n        (\"exception_formatting_async_generator\", (3, 6)),\n        (\"decorate_async_generator\", (3, 7)),\n        (\"positional_only_argument\", (3, 8)),\n        (\"walrus_operator\", (3, 8)),\n        (\"match_statement\", (3, 10)),\n        (\"exception_group_catch\", (3, 11)),\n        (\"notes\", (3, 11)),\n        (\"grouped_simple\", (3, 11)),\n        (\"grouped_nested\", (3, 11)),\n        (\"grouped_with_cause_and_context\", (3, 11)),\n        (\"grouped_as_cause_and_context\", (3, 11)),\n        (\"grouped_max_length\", (3, 11)),\n        (\"grouped_max_depth\", (3, 11)),\n        (\"f_string\", (3, 12)),  # Available since 3.6 but in 3.12 the lexer for f-string changed.\n    ],\n)\ndef test_exception_modern(filename, minimum_python_version):\n    if sys.version_info < minimum_python_version:\n        pytest.skip(\"Feature not supported in this Python version\")\n\n    if filename == \"exception_group_catch\" and platform.python_implementation() == \"PyPy\":\n        pytest.skip(\"Incorrect traceback formatting on PyPy\")  #  Issue #5338.\n\n    compare_exception(\"modern\", filename)\n\n\n@pytest.mark.skipif(\n    not (3, 7) <= sys.version_info < (3, 11), reason=\"No backport available or needed\"\n)\ndef test_group_exception_using_backport(writer):\n    from exceptiongroup import ExceptionGroup\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\")\n\n    try:\n        raise ExceptionGroup(\"Test\", [ValueError(1), ValueError(2)])\n    except Exception:\n        logger.exception(\"\")\n\n    assert writer.read().strip().startswith(\"+ Exception Group Traceback (most recent call last):\")\n\n\ndef test_invalid_format_exception_only_no_output(writer, monkeypatch):\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\")\n\n    with monkeypatch.context() as context:\n        context.setattr(traceback, \"format_exception_only\", lambda _e, _v: [])\n        error = ValueError(0)\n        logger.opt(exception=error).error(\"Error\")\n\n        assert writer.read() == \"\\n\"\n\n\ndef test_invalid_format_exception_only_indented_error_message(writer, monkeypatch):\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\")\n\n    with monkeypatch.context() as context:\n        context.setattr(traceback, \"format_exception_only\", lambda _e, _v: [\"    ValueError: 0\\n\"])\n        error = ValueError(0)\n        logger.opt(exception=error).error(\"Error\")\n\n        assert writer.read() == \"\\n    ValueError: 0\\n\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 11), reason=\"No builtin GroupedException\")\ndef test_invalid_grouped_exception_no_exceptions(writer):\n    error = MagicMock(spec=ExceptionGroup)  # noqa: F821\n    error.__cause__ = None\n    error.__context__ = None\n    error.__traceback__ = None\n\n    logger.add(writer, backtrace=True, diagnose=True, colorize=False, format=\"\")\n    logger.opt(exception=error).error(\"Error\")\n\n    assert writer.read().strip().startswith(\"| unittest.mock.MagicMock:\")\n"
  },
  {
    "path": "tests/test_filesink_compression.py",
    "content": "import os\nimport sys\nimport threading\nimport time\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import check_dir\n\n\n@pytest.mark.parametrize(\n    \"compression\", [\"gz\", \"bz2\", \"zip\", \"xz\", \"lzma\", \"tar\", \"tar.gz\", \"tar.bz2\", \"tar.xz\"]\n)\ndef test_compression_ext(tmp_path, compression):\n    i = logger.add(tmp_path / \"file.log\", compression=compression)\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"file.log.%s\" % compression, None)])\n\n\ndef test_compression_function(tmp_path):\n    def compress(file):\n        os.replace(file, file + \".rar\")\n\n    i = logger.add(tmp_path / \"file.log\", compression=compress)\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"file.log.rar\", None)])\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\", \"w\", \"x\"])\ndef test_compression_at_rotation(tmp_path, mode, freeze_time):\n    with freeze_time(\"2010-10-09 11:30:59\"):\n        logger.add(\n            tmp_path / \"file.log\", format=\"{message}\", rotation=0, compression=\"gz\", mode=mode\n        )\n        logger.debug(\"After compression\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.2010-10-09_11-30-59_000000.log.gz\", None),\n            (\"file.log\", \"After compression\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\", \"w\", \"x\"])\ndef test_compression_at_remove_without_rotation(tmp_path, mode):\n    i = logger.add(tmp_path / \"file.log\", compression=\"gz\", mode=mode)\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"file.log.gz\", None)])\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\", \"w\", \"x\"])\ndef test_no_compression_at_remove_with_rotation(tmp_path, mode):\n    i = logger.add(tmp_path / \"test.log\", compression=\"gz\", rotation=\"100 MB\", mode=mode)\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"test.log\", None)])\n\n\ndef test_rename_existing_with_creation_time(tmp_path, freeze_time):\n    with freeze_time(\"2018-01-01\") as frozen:\n        i = logger.add(tmp_path / \"test.log\", compression=\"tar.gz\")\n        logger.debug(\"test\")\n        logger.remove(i)\n        frozen.tick()\n        j = logger.add(tmp_path / \"test.log\", compression=\"tar.gz\")\n        logger.debug(\"test\")\n        logger.remove(j)\n\n    check_dir(\n        tmp_path,\n        files=[(\"test.2018-01-01_00-00-00_000000.log.tar.gz\", None), (\"test.log.tar.gz\", None)],\n    )\n\n\ndef test_renaming_compression_dest_exists(freeze_time, tmp_path):\n    with freeze_time(\"2019-01-02 03:04:05.000006\"):\n        for i in range(4):\n            logger.add(tmp_path / \"rotate.log\", compression=\".tar.gz\", format=\"{message}\")\n            logger.info(str(i))\n            logger.remove()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"rotate.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.2.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.3.log.tar.gz\", None),\n        ],\n    )\n\n\ndef test_renaming_compression_dest_exists_with_time(freeze_time, tmp_path):\n    with freeze_time(\"2019-01-02 03:04:05.000006\"):\n        for i in range(4):\n            logger.add(tmp_path / \"rotate.{time}.log\", compression=\".tar.gz\", format=\"{message}\")\n            logger.info(str(i))\n            logger.remove()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"rotate.2019-01-02_03-04-05_000006.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.2.log.tar.gz\", None),\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.3.log.tar.gz\", None),\n        ],\n    )\n\n\ndef test_compression_use_renamed_file_after_rotation(tmp_path, freeze_time):\n    def rotation(message, _):\n        return message.record[\"extra\"].get(\"rotate\", False)\n\n    compression = Mock()\n\n    with freeze_time(\"2020-01-02\"):\n        logger.add(\n            tmp_path / \"test.log\", format=\"{message}\", compression=compression, rotation=rotation\n        )\n\n        logger.info(\"Before\")\n        logger.bind(rotate=True).info(\"Rotation\")\n        logger.info(\"After\")\n\n    compression.assert_called_once_with(str(tmp_path / \"test.2020-01-02_00-00-00_000000.log\"))\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2020-01-02_00-00-00_000000.log\", \"Before\\n\"),\n            (\"test.log\", \"Rotation\\nAfter\\n\"),\n        ],\n    )\n\n\ndef test_threaded_compression_after_rotation(tmp_path):\n    thread = None\n\n    def rename(filepath):\n        time.sleep(1)\n        os.rename(filepath, str(tmp_path / \"test.log.mv\"))\n\n    def compression(filepath):\n        nonlocal thread\n        thread = threading.Thread(target=rename, args=(filepath,))\n        thread.start()\n\n    def rotation(message, _):\n        return message.record[\"extra\"].get(\"rotate\", False)\n\n    logger.add(\n        tmp_path / \"test.log\", format=\"{message}\", compression=compression, rotation=rotation\n    )\n\n    logger.info(\"Before\")\n    logger.bind(rotate=True).info(\"Rotation\")\n    logger.info(\"After\")\n\n    thread.join()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.log\", \"Rotation\\nAfter\\n\"),\n            (\"test.log.mv\", \"Before\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_compression_at_rotation(freeze_time, tmp_path, capsys, delay):\n    with freeze_time(\"2017-07-01\") as frozen:\n        logger.add(\n            tmp_path / \"test.log\",\n            format=\"{message}\",\n            compression=Mock(side_effect=[Exception(\"Compression error\"), None]),\n            rotation=0,\n            catch=True,\n            delay=delay,\n        )\n        logger.debug(\"AAA\")\n        frozen.tick()\n        logger.debug(\"BBB\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2017-07-01_00-00-00_000000.log\", \"\"),\n            (\"test.2017-07-01_00-00-01_000000.log\", \"\"),\n            (\"test.log\", \"BBB\\n\"),\n        ],\n    )\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err.count(\"Logging error in Loguru Handler\") == 1\n    assert err.count(\"Exception: Compression error\") == 1\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_compression_at_rotation_not_caught(freeze_time, tmp_path, capsys, delay):\n    with freeze_time(\"2017-07-01\") as frozen:\n        logger.add(\n            tmp_path / \"test.log\",\n            format=\"{message}\",\n            compression=Mock(side_effect=[OSError(\"Compression error\"), None]),\n            rotation=0,\n            catch=False,\n            delay=delay,\n        )\n        with pytest.raises(OSError, match=\"^Compression error$\"):\n            logger.debug(\"AAA\")\n\n        frozen.tick()\n        logger.debug(\"BBB\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2017-07-01_00-00-00_000000.log\", \"\"),\n            (\"test.2017-07-01_00-00-01_000000.log\", \"\"),\n            (\"test.log\", \"BBB\\n\"),\n        ],\n    )\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_compression_at_remove(tmp_path, capsys, delay):\n    i = logger.add(\n        tmp_path / \"test.log\",\n        format=\"{message}\",\n        compression=Mock(side_effect=[OSError(\"Compression error\"), None]),\n        catch=True,\n        delay=delay,\n    )\n    logger.debug(\"AAA\")\n\n    with pytest.raises(OSError, match=\"^Compression error$\"):\n        logger.remove(i)\n\n    logger.debug(\"Nope\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.log\", \"AAA\\n\"),\n        ],\n    )\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\n@pytest.mark.parametrize(\"compression\", [0, True, os, object(), {\"zip\"}])\ndef test_invalid_compression_type(compression):\n    with pytest.raises(TypeError):\n        logger.add(\"test.log\", compression=compression)\n\n\n@pytest.mark.parametrize(\"compression\", [\"rar\", \".7z\", \"tar.zip\", \"__dict__\"])\ndef test_unknown_compression(compression):\n    with pytest.raises(ValueError, match=\"^Invalid compression format: '[^']+'$\"):\n        logger.add(\"test.log\", compression=compression)\n\n\n@pytest.mark.parametrize(\"ext\", [\"gz\", \"tar.gz\"])\ndef test_gzip_module_unavailable(ext, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"gzip\", None)\n        with pytest.raises(ImportError):\n            logger.add(\"test.log\", compression=ext)\n\n\n@pytest.mark.parametrize(\"ext\", [\"bz2\", \"tar.bz2\"])\ndef test_bz2_module_unavailable(ext, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"bz2\", None)\n        with pytest.raises(ImportError):\n            logger.add(\"test.log\", compression=ext)\n\n\n@pytest.mark.parametrize(\"ext\", [\"xz\", \"lzma\", \"tar.xz\"])\ndef test_lzma_module_unavailable(ext, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"lzma\", None)\n        with pytest.raises(ImportError):\n            logger.add(\"test.log\", compression=ext)\n\n\n@pytest.mark.parametrize(\"ext\", [\"tar\", \"tar.gz\", \"tar.bz2\", \"tar.xz\"])\ndef test_tarfile_module_unavailable(ext, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"tarfile\", None)\n        with pytest.raises(ImportError):\n            logger.add(\"test.log\", compression=ext)\n\n\n@pytest.mark.parametrize(\"ext\", [\"zip\"])\ndef test_zipfile_module_unavailable(ext, monkeypatch):\n    with monkeypatch.context() as context:\n        context.setitem(sys.modules, \"zipfile\", None)\n        with pytest.raises(ImportError):\n            logger.add(\"test.log\", compression=ext)\n"
  },
  {
    "path": "tests/test_filesink_delay.py",
    "content": "import datetime\nimport time\n\nfrom loguru import logger\n\nfrom .conftest import check_dir\n\n\ndef test_file_not_delayed(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", delay=False)\n    assert file.read_text() == \"\"\n    logger.debug(\"Not delayed\")\n    assert file.read_text() == \"Not delayed\\n\"\n\n\ndef test_file_delayed(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", delay=True)\n    assert not file.exists()\n    logger.debug(\"Delayed\")\n    assert file.read_text() == \"Delayed\\n\"\n\n\ndef test_compression(tmp_path):\n    i = logger.add(tmp_path / \"file.log\", compression=\"gz\", delay=True)\n    logger.debug(\"a\")\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"file.log.gz\", None)])\n\n\ndef test_compression_early_remove(tmp_path):\n    i = logger.add(tmp_path / \"file.log\", compression=\"gz\", delay=True)\n    logger.remove(i)\n    check_dir(tmp_path, size=0)\n\n\ndef test_retention(tmp_path):\n    for i in range(5):\n        tmp_path.joinpath(\"test.2020-01-01_01-01-%d_000001.log\" % i).write_text(\"test\")\n\n    i = logger.add(tmp_path / \"test.log\", retention=0, delay=True)\n    logger.debug(\"a\")\n    logger.remove(i)\n\n    check_dir(tmp_path, size=0)\n\n\ndef test_retention_early_remove(tmp_path):\n    for i in range(5):\n        tmp_path.joinpath(\"test.2020-01-01_01-01-%d_000001.log\" % i).write_text(\"test\")\n\n    i = logger.add(tmp_path / \"test.log\", retention=0, delay=True)\n    logger.remove(i)\n\n    check_dir(tmp_path, size=0)\n\n\ndef test_rotation(tmp_path, freeze_time):\n    with freeze_time(\"2001-02-03\"):\n        i = logger.add(tmp_path / \"file.log\", rotation=0, delay=True, format=\"{message}\")\n        logger.debug(\"a\")\n        logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.2001-02-03_00-00-00_000000.log\", \"\"),\n            (\"file.log\", \"a\\n\"),\n        ],\n    )\n\n\ndef test_rotation_early_remove(tmp_path):\n    i = logger.add(tmp_path / \"file.log\", rotation=0, delay=True, format=\"{message}\")\n    logger.remove(i)\n\n    check_dir(tmp_path, size=0)\n\n\ndef test_rotation_and_retention(freeze_time, tmp_path):\n    with freeze_time(\"1999-12-12\") as frozen:\n        filepath = tmp_path / \"file.log\"\n        logger.add(filepath, rotation=30, retention=2, delay=True, format=\"{message}\")\n        for i in range(1, 10):\n            time.sleep(0.05)  # Retention is based on mtime.\n            frozen.tick(datetime.timedelta(seconds=0.05))\n            logger.info(str(i) * 20)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.1999-12-12_00-00-00_350000.log\", \"7\" * 20 + \"\\n\"),\n            (\"file.1999-12-12_00-00-00_400000.log\", \"8\" * 20 + \"\\n\"),\n            (\"file.log\", \"9\" * 20 + \"\\n\"),\n        ],\n    )\n\n\ndef test_rotation_and_retention_timed_file(freeze_time, tmp_path):\n    with freeze_time(\"1999-12-12\") as frozen:\n        filepath = tmp_path / \"file.{time}.log\"\n        logger.add(filepath, rotation=30, retention=2, delay=True, format=\"{message}\")\n        for i in range(1, 10):\n            time.sleep(0.05)  # Retention is based on mtime.\n            frozen.tick(datetime.timedelta(seconds=0.05))\n            logger.info(str(i) * 20)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.1999-12-12_00-00-00_350000.log\", \"7\" * 20 + \"\\n\"),\n            (\"file.1999-12-12_00-00-00_400000.log\", \"8\" * 20 + \"\\n\"),\n            (\"file.1999-12-12_00-00-00_450000.log\", \"9\" * 20 + \"\\n\"),\n        ],\n    )\n"
  },
  {
    "path": "tests/test_filesink_permissions.py",
    "content": "import os\nfrom stat import S_IMODE\n\nimport pytest\n\nfrom loguru import logger\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef set_umask():\n    default = os.umask(0)\n    yield\n    os.umask(default)\n\n\n@pytest.mark.parametrize(\"permissions\", [0o777, 0o766, 0o744, 0o700, 0o611])\ndef test_log_file_permissions(tmp_path, permissions):\n    def file_permission_opener(file, flags):\n        return os.open(file, flags, permissions)\n\n    filepath = tmp_path / \"file.log\"\n    logger.add(filepath, opener=file_permission_opener)\n\n    logger.debug(\"Message\")\n    stat_result = os.stat(str(filepath))\n    expected = 0o666 if os.name == \"nt\" else permissions\n    assert S_IMODE(stat_result.st_mode) == expected\n\n\n@pytest.mark.parametrize(\"permissions\", [0o777, 0o766, 0o744, 0o700, 0o611])\ndef test_rotation_permissions(tmp_path, permissions, set_umask):\n    def file_permission_opener(file, flags):\n        return os.open(file, flags, permissions)\n\n    logger.add(tmp_path / \"file.log\", rotation=0, opener=file_permission_opener)\n\n    logger.debug(\"Message\")\n\n    files = list(tmp_path.iterdir())\n    assert len(files) == 2\n\n    for filepath in files:\n        stat_result = os.stat(str(filepath))\n        expected = 0o666 if os.name == \"nt\" else permissions\n        assert S_IMODE(stat_result.st_mode) == expected\n"
  },
  {
    "path": "tests/test_filesink_retention.py",
    "content": "import datetime\nimport os\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import check_dir\n\n\n@pytest.mark.parametrize(\"retention\", [\"1 hour\", \"1H\", \" 1 h \", datetime.timedelta(hours=1)])\ndef test_retention_time(freeze_time, tmp_path, retention):\n    i = logger.add(tmp_path / \"test.log.x\", retention=retention)\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, size=1)\n\n    future = datetime.datetime.now() + datetime.timedelta(days=1)\n    with freeze_time(future):\n        i = logger.add(tmp_path / \"test.log\", retention=retention)\n        logger.debug(\"test\")\n\n        check_dir(tmp_path, size=2)\n        logger.remove(i)\n        check_dir(tmp_path, size=0)\n\n\n@pytest.mark.parametrize(\"retention\", [0, 1, 10])\ndef test_retention_count(tmp_path, retention):\n    file = tmp_path / \"test.log\"\n\n    for i in range(retention):\n        tmp_path.joinpath(\"test.2011-01-01_01-01-%d_000001.log\" % i).write_text(\"test\")\n\n    i = logger.add(file, retention=retention)\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, size=retention)\n\n\ndef test_retention_function(tmp_path):\n    def func(logs):\n        for log in logs:\n            os.rename(log, log + \".xyz\")\n\n    tmp_path.joinpath(\"test.log.1\").write_text(\"A\")\n    tmp_path.joinpath(\"test\").write_text(\"B\")\n\n    i = logger.add(tmp_path / \"test.log\", retention=func)\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.log.1.xyz\", \"A\"),\n            (\"test\", \"B\"),\n            (\"test.log.xyz\", \"\"),\n        ],\n    )\n\n\ndef test_managed_files(tmp_path):\n    others = {\n        \"test.log\",\n        \"test.log.1\",\n        \"test.log.1.gz\",\n        \"test.log.rar\",\n        \"test.2019-11-12_03-22-07_018985.log\",\n        \"test.2019-11-12_03-22-07_018985.log.tar.gz\",\n        \"test.2019-11-12_03-22-07_018985.2.log\",\n        \"test.2019-11-12_03-22-07_018985.2.log.tar.gz\",\n        \"test.foo.log\",\n        \"test.123.log\",\n        \"test.2019-11-12_03-22-07_018985.abc.log\",\n        \"test.2019-11-12_03-22-07_018985.123.abc.log\",\n        \"test.foo.log.bar\",\n        \"test.log.log\",\n    }\n\n    for other in others:\n        tmp_path.joinpath(other).write_text(other)\n\n    i = logger.add(tmp_path / \"test.log\", retention=0, catch=False)\n    logger.remove(i)\n\n    check_dir(tmp_path, size=0)\n\n\ndef test_not_managed_files(tmp_path):\n    others = {\n        \"test_.log\",\n        \"_test.log\",\n        \"tes.log\",\n        \"te.st.log\",\n        \"testlog\",\n        \"test\",\n        \"test.tar.gz\",\n        \"test.logs\",\n        \"test.foo\",\n        \"test.foo.logs\",\n        \"tests.logs.zip\",\n        \"foo.test.log\",\n        \"foo.test.log.zip\",\n    }\n\n    if os.name != \"nt\":\n        others.add(\"test.\")\n\n    for other in others:\n        tmp_path.joinpath(other).write_text(other)\n\n    i = logger.add(tmp_path / \"test.log\", retention=0, catch=False)\n    logger.remove(i)\n\n    assert set(f.name for f in tmp_path.iterdir()) == others\n\n\n@pytest.mark.parametrize(\"filename\", [\"test\", \"test.log\"])\ndef test_no_duplicates_in_listed_files(tmp_path, filename):\n    others = [\n        \"test.log\",\n        \"test.log.log\",\n        \"test.log.log.log\",\n        \"test\",\n        \"test..\",\n        \"test.log..\",\n        \"test..log\",\n        \"test...log\",\n        \"test.log..\",\n        \"test.log.a.log.b\",\n    ]\n\n    for other in others:\n        tmp_path.joinpath(other).write_text(other)\n\n    retention = Mock()\n    i = logger.add(tmp_path / filename, retention=retention, catch=False)\n    logger.remove(i)\n\n    assert retention.call_count == 1\n    assert len(retention.call_args.args[0]) == len(set(retention.call_args.args[0]))\n\n\ndef test_directories_ignored(tmp_path):\n    others = [\"test.log.2\", \"test.123.log\", \"test.log.tar.gz\", \"test.archive\"]\n\n    for other in others:\n        tmp_path.joinpath(other).mkdir()\n\n    i = logger.add(tmp_path / \"test.log\", retention=0, catch=False)\n    logger.remove(i)\n\n    check_dir(tmp_path, size=len(others))\n\n\ndef test_manage_formatted_files(freeze_time, tmp_path):\n    with freeze_time(\"2018-01-01 00:00:00\"):\n        f1 = tmp_path / \"temp/2018/file.log\"\n        f2 = tmp_path / \"temp/file2018.log\"\n        f3 = tmp_path / \"temp/d2018/f2018.2018.log\"\n\n        a = logger.add(tmp_path / \"temp/{time:YYYY}/file.log\", retention=0)\n        b = logger.add(tmp_path / \"temp/file{time:YYYY}.log\", retention=0)\n        c = logger.add(tmp_path / \"temp/d{time:YYYY}/f{time:YYYY}.{time:YYYY}.log\", retention=0)\n\n        logger.debug(\"test\")\n\n        assert f1.exists()\n        assert f2.exists()\n        assert f3.exists()\n\n        logger.remove(a)\n        logger.remove(b)\n        logger.remove(c)\n\n        assert not f1.exists()\n        assert not f2.exists()\n        assert not f3.exists()\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support '*' in filename\")\ndef test_date_with_dot_after_extension(tmp_path):\n    file = tmp_path / \"file.{time:YYYY.MM}_log\"\n\n    i = logger.add(tmp_path / \"file*.log\", retention=0, catch=False)\n    logger.remove(i)\n\n    assert not file.exists()\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support '*' in filename\")\ndef test_symbol_in_filename(tmp_path):\n    file = tmp_path / \"file123.log\"\n    file.touch()\n\n    i = logger.add(tmp_path / \"file*.log\", retention=0, catch=False)\n    logger.remove(i)\n\n    assert file.exists()\n\n\ndef test_manage_file_without_extension(tmp_path):\n    file = tmp_path / \"file\"\n\n    i = logger.add(file, retention=0)\n    logger.debug(\"?\")\n    check_dir(tmp_path, files=[(\"file\", None)])\n    logger.remove(i)\n    check_dir(tmp_path, files=[])\n\n\ndef test_manage_formatted_files_without_extension(tmp_path):\n    tmp_path.joinpath(\"file_8\").touch()\n    tmp_path.joinpath(\"file_7\").touch()\n    tmp_path.joinpath(\"file_6\").touch()\n\n    i = logger.add(tmp_path / \"file_{time}\", retention=0)\n    logger.debug(\"1\")\n    logger.remove(i)\n\n    check_dir(tmp_path, size=0)\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\", \"w\", \"x\"])\ndef test_retention_at_rotation(tmp_path, mode):\n    tmp_path.joinpath(\"test.log.1\").touch()\n    tmp_path.joinpath(\"test.log.2\").touch()\n    tmp_path.joinpath(\"test.log.3\").touch()\n\n    logger.add(tmp_path / \"test.log\", retention=1, rotation=0, mode=mode)\n    logger.debug(\"test\")\n\n    check_dir(tmp_path, size=2)\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\", \"w\", \"x\"])\ndef test_retention_at_remove_without_rotation(tmp_path, mode):\n    i = logger.add(tmp_path / \"file.log\", retention=0, mode=mode)\n    logger.debug(\"1\")\n    check_dir(tmp_path, size=1)\n    logger.remove(i)\n    check_dir(tmp_path, size=0)\n\n\n@pytest.mark.parametrize(\"mode\", [\"w\", \"x\", \"a\", \"a+\"])\ndef test_no_retention_at_remove_with_rotation(tmp_path, mode):\n    i = logger.add(tmp_path / \"file.log\", retention=0, rotation=\"100 MB\", mode=mode)\n    logger.debug(\"1\")\n    check_dir(tmp_path, size=1)\n    logger.remove(i)\n    check_dir(tmp_path, size=1)\n\n\ndef test_no_renaming(tmp_path):\n    i = logger.add(tmp_path / \"test.log\", format=\"{message}\", retention=10)\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"test.log\", \"test\\n\")])\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_retention_at_rotation(freeze_time, tmp_path, capsys, delay):\n    with freeze_time(\"2022-02-22\") as frozen:\n        logger.add(\n            tmp_path / \"test.log\",\n            format=\"{message}\",\n            retention=Mock(side_effect=[Exception(\"Retention error\"), None]),\n            rotation=0,\n            catch=True,\n            delay=delay,\n        )\n        logger.debug(\"AAA\")\n        frozen.tick()\n        logger.debug(\"BBB\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2022-02-22_00-00-00_000000.log\", \"\"),\n            (\"test.2022-02-22_00-00-01_000000.log\", \"\"),\n            (\"test.log\", \"BBB\\n\"),\n        ],\n    )\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err.count(\"Logging error in Loguru Handler\") == 1\n    assert err.count(\"Exception: Retention error\") == 1\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_retention_at_rotation_not_caught(freeze_time, tmp_path, capsys, delay):\n    with freeze_time(\"2022-02-22\") as frozen:\n        logger.add(\n            tmp_path / \"test.log\",\n            format=\"{message}\",\n            retention=Mock(side_effect=[OSError(\"Retention error\"), None]),\n            rotation=0,\n            catch=False,\n            delay=delay,\n        )\n        with pytest.raises(OSError, match=\"^Retention error$\"):\n            logger.debug(\"AAA\")\n        frozen.tick()\n        logger.debug(\"BBB\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2022-02-22_00-00-00_000000.log\", \"\"),\n            (\"test.2022-02-22_00-00-01_000000.log\", \"\"),\n            (\"test.log\", \"BBB\\n\"),\n        ],\n    )\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\ndef test_exception_during_retention_at_remove(tmp_path, capsys, delay):\n    i = logger.add(\n        tmp_path / \"test.log\",\n        format=\"{message}\",\n        retention=Mock(side_effect=[OSError(\"Retention error\"), None]),\n        catch=False,\n        delay=delay,\n    )\n    logger.debug(\"AAA\")\n\n    with pytest.raises(OSError, match=\"^Retention error$\"):\n        logger.remove(i)\n\n    logger.debug(\"Nope\")\n\n    check_dir(tmp_path, files=[(\"test.log\", \"AAA\\n\")])\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\n@pytest.mark.parametrize(\"retention\", [datetime.time(12, 12, 12), os, object()])\ndef test_invalid_retention_type(retention):\n    with pytest.raises(TypeError):\n        logger.add(\"test.log\", retention=retention)\n\n\n@pytest.mark.parametrize(\n    \"retention\", [\"W5\", \"monday at 14:00\", \"sunday\", \"nope\", \"d\", \"H\", \"__dict__\"]\n)\ndef test_unparsable_retention(retention):\n    with pytest.raises(ValueError, match=\"^Cannot parse retention from: '[^']+'$\"):\n        logger.add(\"test.log\", retention=retention)\n\n\n@pytest.mark.parametrize(\"retention\", [\"5 MB\", \"3 hours 2 dayz\"])\ndef test_invalid_value_retention_duration(retention):\n    with pytest.raises(ValueError, match=\"^Invalid unit value while parsing duration: '[^']+'$\"):\n        logger.add(\"test.log\", retention=retention)\n"
  },
  {
    "path": "tests/test_filesink_rotation.py",
    "content": "import datetime\nimport os\nimport pathlib\nimport tempfile\nimport time\nfrom unittest.mock import Mock\n\nimport pytest\n\nimport loguru\nfrom loguru import logger\nfrom loguru._ctime_functions import load_ctime_functions\n\nfrom .conftest import check_dir\n\n\n@pytest.fixture\ndef tmp_path_local(reset_logger):\n    # Pytest 'tmp_path' creates directories in /tmp, but /tmp does not support xattr,\n    # causing some tests would fail.\n    with tempfile.TemporaryDirectory(dir=\".\") as tmp_path:\n        yield pathlib.Path(tmp_path)\n        logger.remove()  # Deleting file not possible if still in use by Loguru.\n\n\ndef test_renaming(freeze_time, tmp_path):\n    with freeze_time(\"2020-01-01\") as frozen:\n        logger.add(tmp_path / \"file.log\", rotation=0, format=\"{message}\")\n\n        frozen.tick()\n        logger.debug(\"a\")\n\n        check_dir(\n            tmp_path,\n            files=[\n                (\"file.2020-01-01_00-00-00_000000.log\", \"\"),\n                (\"file.log\", \"a\\n\"),\n            ],\n        )\n\n        frozen.tick()\n        logger.debug(\"b\")\n\n        check_dir(\n            tmp_path,\n            files=[\n                (\"file.2020-01-01_00-00-00_000000.log\", \"\"),\n                (\"file.2020-01-01_00-00-01_000000.log\", \"a\\n\"),\n                (\"file.log\", \"b\\n\"),\n            ],\n        )\n\n\ndef test_no_renaming(freeze_time, tmp_path):\n    with freeze_time(\"2018-01-01 00:00:00\") as frozen:\n        logger.add(tmp_path / \"file_{time}.log\", rotation=0, format=\"{message}\")\n\n        frozen.move_to(\"2019-01-01 00:00:00\")\n        logger.debug(\"a\")\n        check_dir(\n            tmp_path,\n            files=[\n                (\"file_2018-01-01_00-00-00_000000.log\", \"\"),\n                (\"file_2019-01-01_00-00-00_000000.log\", \"a\\n\"),\n            ],\n        )\n\n        frozen.move_to(\"2020-01-01 00:00:00\")\n        logger.debug(\"b\")\n        check_dir(\n            tmp_path,\n            files=[\n                (\"file_2018-01-01_00-00-00_000000.log\", \"\"),\n                (\"file_2019-01-01_00-00-00_000000.log\", \"a\\n\"),\n                (\"file_2020-01-01_00-00-00_000000.log\", \"b\\n\"),\n            ],\n        )\n\n\n@pytest.mark.parametrize(\"size\", [8, 8.0, 7.99, \"8 B\", \"8e-6MB\", \"0.008 kiB\", \"64b\"])\ndef test_size_rotation(freeze_time, tmp_path, size):\n    with freeze_time(\"2018-01-01 00:00:00\") as frozen:\n        i = logger.add(tmp_path / \"test_{time}.log\", format=\"{message}\", rotation=size, mode=\"w\")\n\n        frozen.tick()\n        logger.debug(\"abcde\")\n\n        frozen.tick()\n        logger.debug(\"fghij\")\n\n        frozen.tick()\n        logger.debug(\"klmno\")\n\n        frozen.tick()\n        logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-01-01_00-00-00_000000.log\", \"abcde\\n\"),\n            (\"test_2018-01-01_00-00-02_000000.log\", \"fghij\\n\"),\n            (\"test_2018-01-01_00-00-03_000000.log\", \"klmno\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    (\"when\", \"hours\"),\n    [\n        # hours = [\n        #   Should not trigger, should trigger, should not trigger, should trigger, should trigger\n        # ]\n        (\"13\", [0, 1, 20, 4, 24]),\n        (\"13:00\", [0.2, 0.9, 23, 1, 48]),\n        (\"13:00:00\", [0.5, 1.5, 10, 15, 72]),\n        (\"13:00:00.123456\", [0.9, 2, 10, 15, 256]),\n        (\"11:00\", [22.9, 0.2, 23, 1, 24]),\n        (\"w0\", [11, 1, 24 * 7 - 1, 1, 24 * 7]),\n        (\"W0 at 00:00\", [10, 24 * 7 - 5, 0.1, 24 * 30, 24 * 14]),\n        (\"W6\", [24, 24 * 28, 24 * 5, 24, 364 * 24]),\n        (\"saturday\", [25, 25 * 12, 0, 25 * 12, 24 * 8]),\n        (\"w6 at 00\", [8, 24 * 7, 24 * 6, 24, 24 * 8]),\n        (\" W6 at 13 \", [0.5, 1, 24 * 6, 24 * 6, 365 * 24]),\n        (\"w2  at  11:00:00 AM\", [48 + 22, 3, 24 * 6, 24, 366 * 24]),\n        (\"MonDaY at 11:00:30.123\", [22, 24, 24, 24 * 7, 24 * 7]),\n        (\"sunday\", [0.1, 24 * 7 - 10, 24, 24 * 6, 24 * 7]),\n        (\"SUNDAY at 11:00\", [1, 24 * 7, 2, 24 * 7, 30 * 12]),\n        (\"sunDAY at 1:0:0.0 pm\", [0.9, 0.2, 24 * 7 - 2, 3, 24 * 8]),\n        (datetime.time(15), [2, 3, 19, 5, 24]),\n        (datetime.time(18, 30, 11, 123), [1, 5.51, 20, 24, 40]),\n        (\"2 h\", [1, 2, 0.9, 0.5, 10]),\n        (\"1 hour\", [0.5, 1, 0.1, 100, 1000]),\n        (\"7 days\", [24 * 7 - 1, 1, 48, 24 * 10, 24 * 365]),\n        (\"1h 30 minutes\", [1.4, 0.2, 1, 2, 10]),\n        (\"1 w, 2D\", [24 * 8, 24 * 2, 24, 24 * 9, 24 * 9]),\n        (\"1.5d\", [30, 10, 0.9, 48, 35]),\n        (\"1.222 hours, 3.44s\", [1.222, 0.1, 1, 1.2, 2]),\n        (datetime.timedelta(hours=1), [0.9, 0.2, 0.7, 0.5, 3]),\n        (datetime.timedelta(minutes=30), [0.48, 0.04, 0.07, 0.44, 0.5]),\n        (\"hourly\", [0.9, 0.2, 0.8, 3, 1]),\n        (\"daily\", [11, 1, 23, 1, 24]),\n        (\"WEEKLY\", [11, 2, 24 * 6, 24, 24 * 7]),\n        (\"mOnthLY\", [0, 24 * 13, 29 * 24, 60 * 24, 24 * 35]),\n        (\"monthly\", [10 * 24, 30 * 24 * 6, 24, 24 * 7, 24 * 31]),\n        (\"Yearly \", [100, 24 * 7 * 30, 24 * 300, 24 * 100, 24 * 400]),\n    ],\n)\ndef test_time_rotation(freeze_time, tmp_path, when, hours):\n    with freeze_time(\"2017-06-18 12:00:00\") as frozen:  # Sunday\n        i = logger.add(\n            tmp_path / \"test_{time}.log\",\n            format=\"{message}\",\n            rotation=when,\n            mode=\"w\",\n        )\n\n        for h, m in zip(hours, [\"a\", \"b\", \"c\", \"d\", \"e\"]):\n            frozen.tick(delta=datetime.timedelta(hours=h))\n            logger.debug(m)\n\n        logger.remove(i)\n\n    content = [path.read_text() for path in sorted(tmp_path.iterdir())]\n    assert content == [\"a\\n\", \"b\\nc\\n\", \"d\\n\", \"e\\n\"]\n\n\ndef test_time_rotation_dst(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 05:00:00\", (\"CET\", 3600)):\n        i = logger.add(tmp_path / \"test_{time}.log\", format=\"{message}\", rotation=\"1 day\")\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-28 05:30:00\", (\"CEST\", 7200)):\n            logger.debug(\"Second\")\n\n            with freeze_time(\"2018-10-29 06:00:00\", (\"CET\", 3600)):\n                logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_05-00-00_000000.log\", \"First\\n\"),\n            (\"test_2018-10-28_05-30-00_000000.log\", \"Second\\n\"),\n            (\"test_2018-10-29_06-00-00_000000.log\", \"Third\\n\"),\n        ],\n    )\n\n\ndef test_time_rotation_with_tzinfo_diff_bigger(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 05:00:00\", (\"CET\", 3600)) as frozen:\n        tzinfo = datetime.timezone(datetime.timedelta(seconds=7200))\n        rotation = datetime.time(7, 0, 0, tzinfo=tzinfo)\n\n        i = logger.add(tmp_path / \"test_{time}.log\", format=\"{message}\", rotation=rotation)\n\n        frozen.tick(delta=datetime.timedelta(minutes=30))\n        logger.debug(\"First\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Second\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_05-00-00_000000.log\", \"First\\n\"),\n            (\"test_2018-10-27_06-30-00_000000.log\", \"Second\\n\"),\n        ],\n    )\n\n\ndef test_time_rotation_with_tzinfo_diff_lower(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 06:00:00\", (\"CEST\", 7200)) as frozen:\n        tzinfo = datetime.timezone(datetime.timedelta(seconds=3600))\n        rotation = datetime.time(6, 0, 0, tzinfo=tzinfo)\n\n        i = logger.add(tmp_path / \"test_{time}.log\", format=\"{message}\", rotation=rotation)\n\n        frozen.tick(delta=datetime.timedelta(minutes=30))\n        logger.debug(\"First\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Second\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_06-00-00_000000.log\", \"First\\n\"),\n            (\"test_2018-10-27_07-30-00_000000.log\", \"Second\\n\"),\n        ],\n    )\n\n\ndef test_time_rotation_with_tzinfo_utc(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 05:00:00\", (\"CET\", 3600)) as frozen:\n        rotation = datetime.time(5, 0, 0, tzinfo=datetime.timezone.utc)\n\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        frozen.tick(delta=datetime.timedelta(minutes=30))\n        logger.debug(\"First\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Second\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_04-00-00.log\", \"First\\n\"),\n            (\"test_2018-10-27_05-30-00.log\", \"Second\\n\"),\n        ],\n    )\n\n\ndef test_time_rotation_multiple_days_at_midnight_utc(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 10:00:00\", (\"CET\", 3600)) as frozen:\n        rotation = datetime.time(0, 0, 0, tzinfo=datetime.timezone.utc)\n\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        frozen.tick(delta=datetime.timedelta(hours=13, minutes=30))\n        logger.debug(\"First\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Second\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Third\")\n        frozen.tick(delta=datetime.timedelta(hours=24))\n        logger.debug(\"Fourth\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-28.log\", \"Third\\n\"),\n            (\"test_2018-10-29.log\", \"Fourth\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"offset\", [-3600, 0, 3600])\ndef test_daily_rotation_with_different_timezone(freeze_time, tmp_path, offset):\n    with freeze_time(\"2018-10-27 00:00:00\", (\"A\", offset)) as frozen:\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD}.log\",\n            format=\"{message}\",\n            rotation=\"daily\",\n        )\n\n        logger.debug(\"First\")\n        frozen.tick(delta=datetime.timedelta(hours=23, minutes=30))\n        logger.debug(\"Second\")\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.debug(\"Third\")\n        frozen.tick(delta=datetime.timedelta(hours=24))\n        logger.debug(\"Fourth\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-28.log\", \"Third\\n\"),\n            (\"test_2018-10-29.log\", \"Fourth\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        datetime.time(1, 30, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(2, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600))),\n        datetime.time(0, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n        datetime.time(3, 30, 0),\n        \"03:30:00\",\n    ],\n)\ndef test_time_rotation_after_positive_timezone_changes_forward(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", 3600)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", 7200)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_01-00-00.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-27_02-00-00.log\", \"Third\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"rotation\", [datetime.time(2, 30, 0), \"02:30:00\"])\ndef test_time_rotation_when_positive_timezone_changes_forward(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", 3600)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", 7200)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_02-00-00.log\", \"First\\n\"),\n            (\"test_2018-10-27_03-00-00.log\", \"Second\\nThird\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        datetime.time(4, 30, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(5, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600))),\n        datetime.time(3, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n        datetime.time(3, 30, 0),\n        \"03:30:00\",\n    ],\n)\ndef test_time_rotation_after_negative_timezone_changes_forward(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", -7200)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", -3600)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_04-00-00.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-27_05-00-00.log\", \"Third\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"rotation\", [datetime.time(2, 30, 0), \"02:30:00\"])\ndef test_time_rotation_when_negative_timezone_changes_forward(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", -7200)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", -3600)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_02-00-00.log\", \"First\\n\"),\n            (\"test_2018-10-27_03-00-00.log\", \"Second\\nThird\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        datetime.time(1, 30, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(2, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600))),\n        datetime.time(0, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n    ],\n)\ndef test_time_rotation_after_positive_timezone_changes_backward_aware(\n    freeze_time, tmp_path, rotation\n):\n    with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", 7200)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", 3600)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_01-00-00.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-27_02-00-00.log\", \"Third\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"rotation\", [datetime.time(2, 30, 0), \"02:30:00\"])\ndef test_time_rotation_after_positive_timezone_changes_backward_naive(\n    freeze_time, tmp_path, rotation\n):\n    with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", 7200)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", 3600)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n            frozen.tick(delta=datetime.timedelta(days=1))\n            logger.debug(\"Fourth\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_01-00-00.log\", \"First\\nSecond\\nThird\\n\"),\n            (\"test_2018-10-28_02-00-00.log\", \"Fourth\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        datetime.time(4, 30, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(5, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600))),\n        datetime.time(3, 30, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n    ],\n)\ndef test_time_rotation_after_negative_timezone_changes_backward_aware(\n    freeze_time, tmp_path, rotation\n):\n    with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", -3600)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", -7200)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_04-00-00.log\", \"First\\nSecond\\n\"),\n            (\"test_2018-10-27_05-00-00.log\", \"Third\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"rotation\", [datetime.time(2, 30, 0), \"02:30:00\"])\ndef test_time_rotation_after_negative_timezone_changes_backward_naive(\n    freeze_time, tmp_path, rotation\n):\n    with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", -3600)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=rotation,\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", -7200)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n            frozen.tick(delta=datetime.timedelta(days=1))\n            logger.debug(\"Fourth\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_04-00-00.log\", \"First\\nSecond\\nThird\\n\"),\n            (\"test_2018-10-28_05-00-00.log\", \"Fourth\\n\"),\n        ],\n    )\n\n\ndef test_time_rotation_when_timezone_changes_backward_rename_file(freeze_time, tmp_path):\n    with freeze_time(\"2018-10-27 02:00:00\", (\"CET\", 3600)):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY-MM-DD_HH-mm-ss!UTC}.log\",\n            format=\"{message}\",\n            rotation=\"02:30:00\",\n        )\n\n        logger.debug(\"First\")\n\n        with freeze_time(\"2018-10-27 03:00:00\", (\"CET\", 7200)) as frozen:\n            logger.debug(\"Second\")\n            frozen.tick(delta=datetime.timedelta(hours=1))\n            logger.debug(\"Third\")\n\n    logger.remove(i)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test_2018-10-27_01-00-00.2018-10-27_03-00-00_000000.log\", \"First\\n\"),\n            (\"test_2018-10-27_01-00-00.log\", \"Second\\nThird\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        \"00:15\",\n        datetime.time(0, 15, 0),\n        datetime.time(23, 15, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(0, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=+3600))),\n        datetime.time(22, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n    ],\n)\ndef test_dont_rotate_earlier_when_utc_is_one_day_before(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-24 00:30:00\", (\"CET\", +3600)) as frozen:\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"First\")\n        logger.remove()\n\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"Second\")\n        logger.remove()\n\n        frozen.tick(delta=datetime.timedelta(hours=23))\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"Third\")\n        logger.remove()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2018-10-24_00-30-00_000000.log\", \"First\\nSecond\\n\"),\n            (\"test.log\", \"Third\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        \"23:45\",\n        datetime.time(23, 45, 0),\n        datetime.time(0, 45, 0, tzinfo=datetime.timezone.utc),\n        datetime.time(1, 45, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=+3600))),\n        datetime.time(23, 45, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),\n    ],\n)\ndef test_dont_rotate_later_when_utc_is_one_day_after(freeze_time, tmp_path, rotation):\n    with freeze_time(\"2018-10-23 23:30:00\", (\"CET\", -3600)) as frozen:\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"First\")\n        logger.remove()\n\n        frozen.tick(delta=datetime.timedelta(hours=1))\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"Second\")\n        logger.remove()\n\n        frozen.tick(delta=datetime.timedelta(hours=23))\n        logger.add(tmp_path / \"test.log\", format=\"{message}\", rotation=rotation)\n        logger.info(\"Third\")\n        logger.remove()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2018-10-23_23-30-00_000000.log\", \"First\\n\"),\n            (\"test.log\", \"Second\\nThird\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"timezone\", [(\"CET\", +3600), (\"CET\", -3600), (\"UTC\", 0)])\ndef test_rotation_at_midnight_with_date_in_filename(freeze_time, tmp_path, timezone):\n    with freeze_time(\"2018-10-23 23:55:00\", timezone) as frozen:\n        logger.add(tmp_path / \"test.{time:YYYY-MM-DD}.log\", format=\"{message}\", rotation=\"00:00\")\n        logger.info(\"First\")\n        logger.remove()\n\n        frozen.tick(delta=datetime.timedelta(minutes=10))\n\n        logger.add(tmp_path / \"test.{time:YYYY-MM-DD}.log\", format=\"{message}\", rotation=\"00:00\")\n        logger.info(\"Second\")\n        logger.remove()\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2018-10-23.log\", \"First\\n\"),\n            (\"test.2018-10-24.log\", \"Second\\n\"),\n        ],\n    )\n\n\n@pytest.mark.parametrize(\"delay\", [False, True])\ndef test_time_rotation_reopening_native(tmp_path_local, delay):\n    with tempfile.TemporaryDirectory(dir=str(tmp_path_local)) as test_dir:\n        get_ctime, set_ctime = load_ctime_functions()\n        test_file = pathlib.Path(test_dir) / \"test.txt\"\n        test_file.touch()\n        timestamp_in = 946681200\n        set_ctime(str(test_file), timestamp_in)\n        timestamp_out = get_ctime(str(test_file))\n        if timestamp_in != timestamp_out:\n            pytest.skip(\n                \"The current system does not support getting and setting file creation dates, \"\n                \"the test can't be run.\"\n            )\n\n    filepath = tmp_path_local / \"test.log\"\n    i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n    logger.info(\"1\")\n    time.sleep(1.5)\n    logger.info(\"2\")\n    logger.remove(i)\n    i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n    logger.info(\"3\")\n\n    check_dir(tmp_path_local, size=1)\n    assert filepath.read_text() == \"1\\n2\\n3\\n\"\n\n    time.sleep(1)\n    logger.info(\"4\")\n\n    check_dir(tmp_path_local, size=2)\n    assert filepath.read_text() == \"4\\n\"\n\n    logger.remove(i)\n    time.sleep(1)\n    i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n    logger.info(\"5\")\n\n    check_dir(tmp_path_local, size=2)\n    assert filepath.read_text() == \"4\\n5\\n\"\n\n    time.sleep(1.5)\n    logger.info(\"6\")\n    logger.remove(i)\n\n    check_dir(tmp_path_local, size=3)\n    assert filepath.read_text() == \"6\\n\"\n\n\n@pytest.mark.parametrize(\"delay\", [False, True])\n@pytest.mark.skipif(\n    os.name == \"nt\"\n    or hasattr(os.stat_result, \"st_birthtime\")\n    or not hasattr(os, \"setxattr\")\n    or not hasattr(os, \"getxattr\"),\n    reason=\"Testing implementation specific to Linux\",\n)\ndef test_time_rotation_reopening_xattr_attributeerror(tmp_path_local, monkeypatch, delay):\n    with monkeypatch.context() as context:\n        context.delattr(os, \"setxattr\")\n        context.delattr(os, \"getxattr\")\n        get_ctime, set_ctime = load_ctime_functions()\n\n        context.setattr(loguru._file_sink, \"get_ctime\", get_ctime)\n        context.setattr(loguru._file_sink, \"set_ctime\", set_ctime)\n\n        filepath = tmp_path_local / \"test.log\"\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        time.sleep(1)\n        logger.info(\"1\")\n        logger.remove(i)\n        time.sleep(1.5)\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        logger.info(\"2\")\n        logger.remove(i)\n        check_dir(tmp_path_local, size=1)\n        assert filepath.read_text() == \"1\\n2\\n\"\n        time.sleep(2.5)\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        logger.info(\"3\")\n        logger.remove(i)\n        check_dir(tmp_path_local, size=2)\n        assert filepath.read_text() == \"3\\n\"\n\n\n@pytest.mark.parametrize(\"delay\", [False, True])\n@pytest.mark.skipif(\n    os.name == \"nt\"\n    or hasattr(os.stat_result, \"st_birthtime\")\n    or not hasattr(os, \"setxattr\")\n    or not hasattr(os, \"getxattr\"),\n    reason=\"Testing implementation specific to Linux\",\n)\ndef test_time_rotation_reopening_xattr_oserror(tmp_path_local, monkeypatch, delay):\n    with monkeypatch.context() as context:\n        context.setattr(os, \"setxattr\", Mock(side_effect=OSError))\n        context.setattr(os, \"getxattr\", Mock(side_effect=OSError))\n        get_ctime, set_ctime = load_ctime_functions()\n\n        context.setattr(loguru._file_sink, \"get_ctime\", get_ctime)\n        context.setattr(loguru._file_sink, \"set_ctime\", set_ctime)\n\n        filepath = tmp_path_local / \"test.log\"\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        time.sleep(1)\n        logger.info(\"1\")\n        logger.remove(i)\n        time.sleep(1.5)\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        logger.info(\"2\")\n        logger.remove(i)\n        check_dir(tmp_path_local, size=1)\n        assert filepath.read_text() == \"1\\n2\\n\"\n        time.sleep(2.5)\n        i = logger.add(filepath, format=\"{message}\", delay=delay, rotation=\"2 s\")\n        logger.info(\"3\")\n        logger.remove(i)\n        check_dir(tmp_path_local, size=2)\n        assert filepath.read_text() == \"3\\n\"\n\n\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Testing implementation specific to Windows\")\ndef test_time_rotation_windows_no_setctime(tmp_path, monkeypatch):\n    import win32_setctime\n\n    with monkeypatch.context() as context:\n        context.setattr(win32_setctime, \"SUPPORTED\", False)\n        context.setattr(win32_setctime, \"setctime\", Mock())\n\n        filepath = tmp_path / \"test.log\"\n        logger.add(filepath, format=\"{message}\", rotation=\"2 s\")\n        logger.info(\"1\")\n        time.sleep(1.5)\n        logger.info(\"2\")\n        check_dir(tmp_path, size=1)\n        assert filepath.read_text() == \"1\\n2\\n\"\n        time.sleep(1)\n        logger.info(\"3\")\n        check_dir(tmp_path, size=2)\n        assert filepath.read_text() == \"3\\n\"\n\n        assert not win32_setctime.setctime.called\n\n\n@pytest.mark.parametrize(\"exception\", [ValueError, OSError])\n@pytest.mark.skipif(os.name != \"nt\", reason=\"Testing implementation specific to Windows\")\ndef test_time_rotation_windows_setctime_exception(tmp_path, monkeypatch, exception):\n    import win32_setctime\n\n    with monkeypatch.context() as context:\n        context.setattr(win32_setctime, \"setctime\", Mock(side_effect=exception))\n\n        filepath = tmp_path / \"test.log\"\n        logger.add(filepath, format=\"{message}\", rotation=\"2 s\")\n        logger.info(\"1\")\n        time.sleep(1.5)\n        logger.info(\"2\")\n        check_dir(tmp_path, size=1)\n        assert filepath.read_text() == \"1\\n2\\n\"\n        time.sleep(1)\n        logger.info(\"3\")\n        check_dir(tmp_path, size=2)\n        assert filepath.read_text() == \"3\\n\"\n\n        assert win32_setctime.setctime.called\n\n\ndef test_function_rotation(freeze_time, tmp_path):\n    with freeze_time(\"2018-01-01 00:00:00\") as frozen:\n        logger.add(\n            tmp_path / \"test_{time}.log\",\n            rotation=Mock(side_effect=[False, True, False]),\n            format=\"{message}\",\n        )\n        logger.debug(\"a\")\n        check_dir(tmp_path, files=[(\"test_2018-01-01_00-00-00_000000.log\", \"a\\n\")])\n\n        frozen.move_to(\"2019-01-01 00:00:00\")\n        logger.debug(\"b\")\n        check_dir(\n            tmp_path,\n            files=[\n                (\"test_2018-01-01_00-00-00_000000.log\", \"a\\n\"),\n                (\"test_2019-01-01_00-00-00_000000.log\", \"b\\n\"),\n            ],\n        )\n\n        frozen.move_to(\"2020-01-01 00:00:00\")\n        logger.debug(\"c\")\n        check_dir(\n            tmp_path,\n            files=[\n                (\"test_2018-01-01_00-00-00_000000.log\", \"a\\n\"),\n                (\"test_2019-01-01_00-00-00_000000.log\", \"b\\nc\\n\"),\n            ],\n        )\n\n\n@pytest.mark.parametrize(\"mode\", [\"w\", \"x\"])\ndef test_rotation_at_remove(freeze_time, tmp_path, mode):\n    with freeze_time(\"2018-01-01\"):\n        i = logger.add(\n            tmp_path / \"test_{time:YYYY}.log\",\n            rotation=\"10 MB\",\n            mode=mode,\n            format=\"{message}\",\n        )\n        logger.debug(\"test\")\n        logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"test_2018.log\", \"test\\n\")])\n\n\n@pytest.mark.parametrize(\"mode\", [\"a\", \"a+\"])\ndef test_no_rotation_at_remove(tmp_path, mode):\n    i = logger.add(tmp_path / \"test.log\", rotation=\"10 MB\", mode=mode, format=\"{message}\")\n    logger.debug(\"test\")\n    logger.remove(i)\n\n    check_dir(tmp_path, files=[(\"test.log\", \"test\\n\")])\n\n\ndef test_rename_existing_with_creation_time(freeze_time, tmp_path):\n    with freeze_time(\"2018-01-01\") as frozen:\n        logger.add(tmp_path / \"test.log\", rotation=10, format=\"{message}\")\n        logger.debug(\"X\")\n        frozen.tick()\n        logger.debug(\"Y\" * 20)\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"test.2018-01-01_00-00-00_000000.log\", \"X\\n\"),\n            (\"test.log\", \"Y\" * 20 + \"\\n\"),\n        ],\n    )\n\n\ndef test_renaming_rotation_dest_exists(freeze_time, tmp_path):\n    with freeze_time(\"2019-01-02 03:04:05.000006\"):\n        logger.add(tmp_path / \"rotate.log\", rotation=Mock(return_value=True), format=\"{message}\")\n        logger.info(\"A\")\n        logger.info(\"B\")\n        logger.info(\"C\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"rotate.2019-01-02_03-04-05_000006.log\", \"\"),\n            (\"rotate.2019-01-02_03-04-05_000006.2.log\", \"A\\n\"),\n            (\"rotate.2019-01-02_03-04-05_000006.3.log\", \"B\\n\"),\n            (\"rotate.log\", \"C\\n\"),\n        ],\n    )\n\n\ndef test_renaming_rotation_dest_exists_with_time(freeze_time, tmp_path):\n    with freeze_time(\"2019-01-02 03:04:05.000006\"):\n        logger.add(\n            tmp_path / \"rotate.{time}.log\", rotation=Mock(return_value=True), format=\"{message}\"\n        )\n        logger.info(\"A\")\n        logger.info(\"B\")\n        logger.info(\"C\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.log\", \"\"),\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.2.log\", \"A\\n\"),\n            (\"rotate.2019-01-02_03-04-05_000006.2019-01-02_03-04-05_000006.3.log\", \"B\\n\"),\n            (\"rotate.2019-01-02_03-04-05_000006.log\", \"C\\n\"),\n        ],\n    )\n\n\ndef test_exception_during_rotation(tmp_path, capsys):\n    logger.add(\n        tmp_path / \"test.log\",\n        rotation=Mock(side_effect=[Exception(\"Rotation error\"), False]),\n        format=\"{message}\",\n        catch=True,\n    )\n\n    logger.info(\"A\")\n    logger.info(\"B\")\n\n    check_dir(tmp_path, files=[(\"test.log\", \"B\\n\")])\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err.count(\"Logging error in Loguru Handler\") == 1\n    assert err.count(\"Exception: Rotation error\") == 1\n\n\ndef test_exception_during_rotation_not_caught(tmp_path, capsys):\n    logger.add(\n        tmp_path / \"test.log\",\n        rotation=Mock(side_effect=[OSError(\"Rotation error\"), False]),\n        format=\"{message}\",\n        catch=False,\n    )\n\n    with pytest.raises(OSError, match=\"^Rotation error$\"):\n        logger.info(\"A\")\n\n    logger.info(\"B\")\n\n    check_dir(tmp_path, files=[(\"test.log\", \"B\\n\")])\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n\n\ndef test_recipe_rotation_both_size_and_time(freeze_time, tmp_path):\n    class Rotator:\n        def __init__(self, *, size, at):\n            now = datetime.datetime.now()\n\n            self._size_limit = size\n            self._time_limit = now.replace(hour=at.hour, minute=at.minute, second=at.second)\n\n            if now >= self._time_limit:\n                # The current time is already past the target time so it would rotate already.\n                # Add one day to prevent an immediate rotation.\n                self._time_limit += datetime.timedelta(days=1)\n\n        def should_rotate(self, message, file):\n            file.seek(0, 2)\n            if file.tell() + len(message) > self._size_limit:\n                return True\n            excess = message.record[\"time\"].timestamp() - self._time_limit.timestamp()\n            if excess >= 0:\n                elapsed_days = datetime.timedelta(seconds=excess).days\n                self._time_limit += datetime.timedelta(days=elapsed_days + 1)\n                return True\n            return False\n\n    with freeze_time(\"2020-01-01 20:00:00\") as frozen:\n        rotator = Rotator(size=20, at=datetime.time(12, 0, 0))\n        logger.add(tmp_path / \"file.log\", rotation=rotator.should_rotate, format=\"{message}\")\n        logger.info(\"A\" * 15)\n        frozen.tick()\n        logger.info(\"B\" * 10)\n        frozen.move_to(\"2020-01-02 13:00:00\")\n        logger.info(\"C\")\n        frozen.move_to(\"2020-01-10 13:10:00\")\n        logger.info(\"D\")\n        logger.info(\"E\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.2020-01-01_20-00-00_000000.log\", \"A\" * 15 + \"\\n\"),\n            (\"file.2020-01-01_20-00-01_000000.log\", \"B\" * 10 + \"\\n\"),\n            (\"file.2020-01-02_13-00-00_000000.log\", \"C\\n\"),\n            (\"file.log\", \"D\\nE\\n\"),\n        ],\n    )\n\n\ndef test_multiple_rotation_conditions(freeze_time, tmp_path):\n    with freeze_time(\"2020-01-01 20:00:00\") as frozen:\n        logger.add(tmp_path / \"file.log\", rotation=[\"8 B\", \"1 min\"], format=\"{message}\")\n        logger.info(\"abcde\")\n        frozen.tick()\n\n        logger.info(\"fghij\")\n        frozen.tick()\n\n        logger.info(\"klm\")\n        frozen.move_to(\"2020-01-01 20:01:01\")\n\n        logger.info(\"no\")\n\n    check_dir(\n        tmp_path,\n        files=[\n            (\"file.2020-01-01_20-00-00_000000.log\", \"abcde\\n\"),\n            (\"file.2020-01-01_20-00-01_000000.log\", \"fghij\\n\"),\n            (\"file.2020-01-01_20-00-02_000000.log\", \"klm\\n\"),\n            (\"file.log\", \"no\\n\"),\n        ],\n    )\n\n\ndef test_empty_rotation_condition_list():\n    with pytest.raises(ValueError, match=\"^Must provide at least one rotation condition$\"):\n        logger.add(\"test.log\", rotation=[])\n\n\n@pytest.mark.parametrize(\n    \"rotation\", [object(), os, datetime.date(2017, 11, 11), datetime.datetime.now(), 1j]\n)\ndef test_invalid_rotation_type(rotation):\n    with pytest.raises(TypeError):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        \"w-1\",\n        \"h\",\n        \"M\",\n        \"w1at13\",\n        \"www\",\n        \"w\",\n        \"K\",\n        \"foobar MB\",\n        \"01:00:00!UTC\",\n        \"foobar\",\n        \"__dict__\",\n    ],\n)\ndef test_unparsable_rotation(rotation):\n    with pytest.raises(ValueError, match=\"^Cannot parse rotation from: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\"rotation\", [\"w7\", \"w10\", \"13 at w2\", \"[not|a|day] at 12:00\"])\ndef test_invalid_day_rotation(rotation):\n    with pytest.raises(ValueError, match=\"^Invalid day while parsing daytime: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\n    \"rotation\", [\"2017.11.12\", \"11:99\", \"monday at 2017\", \"w5 at [not|a|time]\"]\n)\ndef test_invalid_time_rotation(rotation):\n    with pytest.raises(ValueError, match=\"^Invalid time while parsing daytime: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\"rotation\", [\"111.111.111 kb\", \"e KB\"])\ndef test_invalid_value_size_rotation(rotation):\n    with pytest.raises(ValueError, match=\"^Invalid float value while parsing size: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\"rotation\", [\"2 days 8 foobar\", \"1 foobar 3 days\", \"3 Ki\"])\ndef test_invalid_unit_rotation_duration(rotation):\n    with pytest.raises(ValueError, match=\"^Invalid unit value while parsing duration: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n\n\n@pytest.mark.parametrize(\"rotation\", [\"e days\", \"1.2.3 days\"])\ndef test_invalid_value_rotation_duration(rotation):\n    with pytest.raises(ValueError, match=\"^Invalid float value while parsing duration: '[^']+'$\"):\n        logger.add(\"test.log\", rotation=rotation)\n"
  },
  {
    "path": "tests/test_filesink_watch.py",
    "content": "import os\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import check_dir\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_deleted_before_write_without_delay(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", watch=True, delay=False)\n    os.remove(str(file))\n    logger.info(\"Test\")\n    assert file.read_text() == \"Test\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_deleted_before_write_with_delay(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", watch=True, delay=True)\n    logger.info(\"Test 1\")\n    os.remove(str(file))\n    logger.info(\"Test 2\")\n    assert file.read_text() == \"Test 2\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_path_containing_placeholder(tmp_path):\n    logger.add(tmp_path / \"test_{time}.log\", format=\"{message}\", watch=True)\n    check_dir(tmp_path, size=1)\n    filepath = next(tmp_path.iterdir())\n    os.remove(str(filepath))\n    logger.info(\"Test\")\n    check_dir(tmp_path, size=1)\n    assert filepath.read_text() == \"Test\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_reopened_with_arguments(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", watch=True, encoding=\"ascii\", errors=\"replace\")\n    os.remove(str(file))\n    logger.info(\"é\")\n    assert file.read_text() == \"?\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_manually_changed(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{message}\", watch=True, mode=\"w\")\n    os.remove(str(file))\n    file.write_text(\"Placeholder\")\n    logger.info(\"Test\")\n    assert file.read_text() == \"Test\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_folder_deleted(tmp_path):\n    file = tmp_path / \"foo/bar/test.log\"\n    logger.add(file, format=\"{message}\", watch=True)\n    os.remove(str(file))\n    os.rmdir(str(tmp_path / \"foo/bar\"))\n    logger.info(\"Test\")\n    assert file.read_text() == \"Test\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_deleted_before_rotation(tmp_path):\n    exists = None\n    file = tmp_path / \"test.log\"\n\n    def rotate(_, __):\n        nonlocal exists\n        exists = file.exists()\n        return False\n\n    logger.add(file, format=\"{message}\", watch=True, rotation=rotate)\n    os.remove(str(file))\n    logger.info(\"Test\")\n    assert exists is True\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_deleted_before_compression(tmp_path):\n    exists = None\n    file = tmp_path / \"test.log\"\n\n    def compress(_):\n        nonlocal exists\n        exists = file.exists()\n        return False\n\n    logger.add(file, format=\"{message}\", watch=True, compression=compress)\n    os.remove(str(file))\n    logger.remove()\n    assert exists is True\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows can't delete file in use\")\ndef test_file_deleted_before_retention(tmp_path):\n    exists = None\n    file = tmp_path / \"test.log\"\n\n    def retain(_):\n        nonlocal exists\n        exists = file.exists()\n        return False\n\n    logger.add(file, format=\"{message}\", watch=True, retention=retain)\n    os.remove(str(file))\n    logger.remove()\n    assert exists is True\n\n\ndef test_file_correctly_reused_after_rotation(tmp_path):\n    filepath = tmp_path / \"test.log\"\n    logger.add(\n        filepath,\n        format=\"{message}\",\n        mode=\"w\",\n        watch=True,\n        rotation=Mock(side_effect=[False, True, False]),\n    )\n    logger.info(\"Test 1\")\n    logger.info(\"Test 2\")\n    logger.info(\"Test 3\")\n    check_dir(tmp_path, size=2)\n    rotated = next(f for f in tmp_path.iterdir() if f != filepath)\n    assert rotated.read_text() == \"Test 1\\n\"\n    assert filepath.read_text() == \"Test 2\\nTest 3\\n\"\n\n\n@pytest.mark.parametrize(\"delay\", [True, False])\n@pytest.mark.parametrize(\"compression\", [None, lambda _: None])\ndef test_file_closed_without_being_logged(tmp_path, delay, compression):\n    filepath = tmp_path / \"test.log\"\n    logger.add(\n        filepath,\n        format=\"{message}\",\n        watch=True,\n        delay=delay,\n        compression=compression,\n    )\n    logger.remove()\n    assert filepath.exists() is (False if delay else True)\n"
  },
  {
    "path": "tests/test_formatting.py",
    "content": "import os\nimport re\n\nimport pytest\n\nfrom loguru import logger\n\n\n@pytest.mark.parametrize(\n    (\"format\", \"validator\"),\n    [\n        (\"{name}\", lambda r: r == \"tests.test_formatting\"),\n        (\"{time}\", lambda r: re.fullmatch(r\"\\d+-\\d+-\\d+T\\d+:\\d+:\\d+[.,]\\d+[+-]\\d{4}\", r)),\n        (\"{elapsed}\", lambda r: re.fullmatch(r\"\\d:\\d{2}:\\d{2}\\.\\d{6}\", r)),\n        (\"{elapsed.seconds}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{line}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{level}\", lambda r: r == \"DEBUG\"),\n        (\"{level.name}\", lambda r: r == \"DEBUG\"),\n        (\"{level.no}\", lambda r: r == \"10\"),\n        (\"{level.icon}\", lambda r: r == \"🐞\"),\n        (\"{file}\", lambda r: r == \"test_formatting.py\"),\n        (\"{file.name}\", lambda r: r == \"test_formatting.py\"),\n        (\"{file.path}\", lambda r: os.path.normcase(r) == os.path.normcase(__file__)),\n        (\"{function}\", lambda r: r == \"test_log_formatters\"),\n        (\"{module}\", lambda r: r == \"test_formatting\"),\n        (\"{thread}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{thread.id}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{thread.name}\", lambda r: isinstance(r, str) and r != \"\"),\n        (\"{process}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{process.id}\", lambda r: re.fullmatch(r\"\\d+\", r)),\n        (\"{process.name}\", lambda r: isinstance(r, str) and r != \"\"),\n        (\"{message}\", lambda r: r == \"Message\"),\n        (\"%s {{a}} 天 {{1}} %d\", lambda r: r == \"%s {a} 天 {1} %d\"),\n    ],\n)\n@pytest.mark.parametrize(\"use_log_function\", [False, True])\ndef test_log_formatters(format, validator, writer, use_log_function):\n    message = \"Message\"\n\n    logger.add(writer, format=format)\n\n    if use_log_function:\n        logger.log(\"DEBUG\", message)\n    else:\n        logger.debug(message)\n\n    result = writer.read().rstrip(\"\\n\")\n    assert validator(result)\n\n\n@pytest.mark.parametrize(\n    (\"format\", \"validator\"),\n    [\n        (\"{time}.log\", lambda r: re.fullmatch(r\"\\d+-\\d+-\\d+_\\d+-\\d+-\\d+\\_\\d+.log\", r)),\n        (\"%s_{{a}}_天_{{1}}_%d\", lambda r: r == \"%s_{a}_天_{1}_%d\"),\n    ],\n)\n@pytest.mark.parametrize(\"part\", [\"file\", \"dir\", \"both\"])\ndef test_file_formatters(tmp_path, format, validator, part):\n    if part == \"file\":\n        file = tmp_path.joinpath(format)\n    elif part == \"dir\":\n        file = tmp_path.joinpath(format, \"log.log\")\n    elif part == \"both\":\n        file = tmp_path.joinpath(format, format)\n\n    logger.add(file)\n    logger.debug(\"Message\")\n\n    files = [f for f in tmp_path.glob(\"**/*\") if f.is_file()]\n\n    assert len(files) == 1\n\n    file = files[0]\n\n    if part == \"file\":\n        assert validator(file.name)\n    elif part == \"dir\":\n        assert file.name == \"log.log\"\n        assert validator(file.parent.name)\n    elif part == \"both\":\n        assert validator(file.name)\n        assert validator(file.parent.name)\n\n\n@pytest.mark.parametrize(\n    (\"message\", \"args\", \"kwargs\", \"expected\"),\n    [\n        (\"{1, 2, 3} - {0} - {\", [], {}, \"{1, 2, 3} - {0} - {\"),\n        (\"{} + {} = {}\", [1, 2, 3], {}, \"1 + 2 = 3\"),\n        (\"{a} + {b} = {c}\", [], dict(a=1, b=2, c=3), \"1 + 2 = 3\"),\n        (\"{0} + {two} = {1}\", [1, 3], dict(two=2, nope=4), \"1 + 2 = 3\"),\n        (\n            \"{self} or {message} or {level}\",\n            [],\n            dict(self=\"a\", message=\"b\", level=\"c\"),\n            \"a or b or c\",\n        ),\n        (\"{:.2f}\", [1], {}, \"1.00\"),\n        (\"{0:0{three}d}\", [5], dict(three=3), \"005\"),\n        (\"{{nope}} {my_dict} {}\", [\"{{!}}\"], dict(my_dict={\"a\": 1}), \"{nope} {'a': 1} {{!}}\"),\n    ],\n)\n@pytest.mark.parametrize(\"use_log_function\", [False, True])\ndef test_log_formatting(writer, message, args, kwargs, expected, use_log_function):\n    logger.add(writer, format=\"{message}\", colorize=False)\n\n    if use_log_function:\n        logger.log(10, message, *args, **kwargs)\n    else:\n        logger.debug(message, *args, **kwargs)\n\n    assert writer.read() == expected + \"\\n\"\n\n\ndef test_formatting_missing_lineno_frame_context(writer, missing_frame_lineno_value):\n    logger.add(writer, format=\"{line} {message}\", colorize=False)\n    logger.info(\"Foobar\")\n    assert writer.read() == \"0 Foobar\\n\"\n\n\ndef test_formatting_incomplete_frame_context(writer, incomplete_frame_context):\n    logger.add(writer, format=\"{name} {message}\", colorize=False)\n    logger.info(\"Foobar\")\n    assert writer.read() == \"None Foobar\\n\"\n\n\ndef test_extra_formatting(writer):\n    logger.configure(extra={\"test\": \"my_test\", \"dict\": {\"a\": 10}})\n    logger.add(writer, format=\"{extra[test]} -> {extra[dict]} -> {message}\")\n    logger.debug(\"level: {name}\", name=\"DEBUG\")\n    assert writer.read() == \"my_test -> {'a': 10} -> level: DEBUG\\n\"\n\n\ndef test_kwargs_in_extra_dict():\n    extra_dicts = []\n    messages = []\n\n    def sink(message):\n        extra_dicts.append(message.record[\"extra\"])\n        messages.append(str(message))\n\n    logger.add(sink, format=\"{message}\")\n    logger.info(\"A\")\n    logger.info(\"B\", foo=123)\n    logger.bind(merge=True).info(\"C\", other=False)\n    logger.bind(override=False).info(\"D\", override=True)\n    logger.info(\"Formatted kwargs: {foobar}\", foobar=123)\n    logger.info(\"Ignored args: {}\", 456)\n    logger.info(\"Both: {foobar} {}\", 456, foobar=123)\n    logger.opt(lazy=True).info(\"Lazy: {lazy}\", lazy=lambda: 789)\n\n    assert messages == [\n        \"A\\n\",\n        \"B\\n\",\n        \"C\\n\",\n        \"D\\n\",\n        \"Formatted kwargs: 123\\n\",\n        \"Ignored args: 456\\n\",\n        \"Both: 123 456\\n\",\n        \"Lazy: 789\\n\",\n    ]\n\n    assert extra_dicts == [\n        {},\n        {\"foo\": 123},\n        {\"merge\": True, \"other\": False},\n        {\"override\": True},\n        {\"foobar\": 123},\n        {},\n        {\"foobar\": 123},\n        {\"lazy\": 789},\n    ]\n\n\ndef test_non_string_message(writer):\n    logger.add(writer, format=\"{message}\")\n\n    logger.info(1)\n    logger.info({})\n    logger.info(b\"test\")\n\n    assert writer.read() == \"1\\n{}\\nb'test'\\n\"\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_non_string_message_is_str_in_record(writer, colors):\n    output = \"\"\n\n    def sink(message):\n        nonlocal output\n        assert isinstance(message.record[\"message\"], str)\n        output += message\n\n    def format(record):\n        assert isinstance(record[\"message\"], str)\n        return \"[{message}]\\n\"\n\n    logger.add(sink, format=format, catch=False)\n    logger.opt(colors=colors).info(123)\n    assert output == \"[123]\\n\"\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_missing_positional_field_during_formatting(writer, colors):\n    logger.add(writer)\n\n    with pytest.raises(ValueError, match=\"^The logging message could not be formatted\") as e:\n        logger.opt(colors=colors).info(\"Foo {} {}\", 123)\n\n    assert isinstance(e.value.__cause__, IndexError)\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_missing_named_field_during_formatting(writer, colors):\n    logger.add(writer)\n\n    with pytest.raises(ValueError, match=\"^The logging message could not be formatted\") as e:\n        logger.opt(colors=colors).info(\"Foo {bar}\", baz=123)\n\n    assert isinstance(e.value.__cause__, KeyError)\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_malformed_curly_braces_during_formatting(writer, colors):\n    logger.add(writer)\n\n    with pytest.raises(ValueError, match=\"^The logging message could not be formatted\") as e:\n        logger.opt(colors=colors).info(\"This is a curly bracket: {\", foo=\"bar\")\n\n    assert isinstance(e.value.__cause__, ValueError)\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_not_formattable_message(writer, colors):\n    logger.add(writer)\n\n    with pytest.raises(ValueError, match=\"^The logging message could not be formatted\") as e:\n        logger.opt(colors=colors).info(123, baz=456)\n\n    assert isinstance(e.value.__cause__, TypeError if colors else AttributeError)\n\n\ndef test_invalid_color_markup(writer):\n    with pytest.raises(\n        ValueError, match=\"^Invalid format, color markups could not be parsed correctly$\"\n    ):\n        logger.add(writer, format=\"<red>Not closed tag\", colorize=True)\n"
  },
  {
    "path": "tests/test_get_frame.py",
    "content": "import sys\n\nimport loguru\nfrom loguru._get_frame import load_get_frame_function\n\n\ndef test_with_sys_getframe(monkeypatch):\n    def patched():\n        return\n\n    with monkeypatch.context() as context:\n        context.setattr(sys, \"_getframe\", patched())\n        assert load_get_frame_function() == patched()\n\n\ndef test_without_sys_getframe(monkeypatch):\n    with monkeypatch.context() as context:\n        context.delattr(sys, \"_getframe\")\n        assert load_get_frame_function() == loguru._get_frame.get_frame_fallback\n\n\ndef test_get_frame_fallback():\n    frame_root = frame_a = frame_b = None\n\n    def a():\n        nonlocal frame_a\n        frame_a = loguru._get_frame.get_frame_fallback(1)\n        b()\n\n    def b():\n        nonlocal frame_b\n        frame_b = loguru._get_frame.get_frame_fallback(2)\n\n    frame_root = loguru._get_frame.get_frame_fallback(0)\n    a()\n\n    assert frame_a == frame_b == frame_root\n"
  },
  {
    "path": "tests/test_interception.py",
    "content": "import inspect\nimport logging\n\nfrom loguru import logger\n\nfrom .conftest import make_logging_logger\n\n\nclass InterceptHandler(logging.Handler):\n    def emit(self, record):\n        # Get corresponding Loguru level if it exists.\n        try:\n            level = logger.level(record.levelname).name\n        except ValueError:\n            level = record.levelno\n\n        # Find caller from where originated the logged message.\n        frame, depth = inspect.currentframe(), 0\n        while frame:\n            filename = frame.f_code.co_filename\n            is_logging = filename == logging.__file__\n            is_frozen = \"importlib\" in filename and \"_bootstrap\" in filename\n            if depth > 0 and not (is_logging or is_frozen):\n                break\n            frame = frame.f_back\n            depth += 1\n\n        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())\n\n\ndef test_formatting(writer):\n    fmt = (\n        \"{name} - {file.name} - {function} - {level.name} - \"\n        \"{level.no} - {line} - {module} - {message}\"\n    )\n\n    expected = (\n        \"tests.test_interception - test_interception.py - test_formatting - DEBUG - \"\n        \"10 - 44 - test_interception - This is the message\\n\"\n    )\n\n    with make_logging_logger(\"tests\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=fmt)\n        logging_logger.debug(\"This is the %s\", \"message\")\n\n    result = writer.read()\n    assert result == expected\n\n\ndef test_intercept(writer):\n    with make_logging_logger(None, InterceptHandler()) as logging_logger:\n        logging_logger.info(\"Nope\")\n        logger.add(writer, format=\"{message}\")\n        logging_logger.info(\"Test\")\n\n    result = writer.read()\n    assert result == \"Test\\n\"\n\n\ndef test_add_before_intercept(writer):\n    logger.add(writer, format=\"{message}\")\n\n    with make_logging_logger(None, InterceptHandler()) as logging_logger:\n        logging_logger.info(\"Test\")\n\n    result = writer.read()\n    assert result == \"Test\\n\"\n\n\ndef test_remove_interception(writer):\n    h = InterceptHandler()\n\n    with make_logging_logger(\"foobar\", h) as logging_logger:\n        logger.add(writer, format=\"{message}\")\n        logging_logger.debug(\"1\")\n        logging_logger.removeHandler(h)\n        logging_logger.debug(\"2\")\n\n    result = writer.read()\n    assert result == \"1\\n\"\n\n\ndef test_intercept_too_low(writer):\n    with make_logging_logger(\"tests.test_interception\", InterceptHandler()):\n        logger.add(writer, format=\"{message}\")\n        logging.getLogger(\"tests\").error(\"Nope 1\")\n        logging.getLogger(\"foobar\").error(\"Nope 2\")\n\n    result = writer.read()\n    assert result == \"\"\n\n\ndef test_multiple_intercept(writer):\n    with make_logging_logger(\"test_1\", InterceptHandler()) as logging_logger_1:\n        with make_logging_logger(\"test_2\", InterceptHandler()) as logging_logger_2:\n            logger.add(writer, format=\"{message}\")\n            logging_logger_1.info(\"1\")\n            logging_logger_2.info(\"2\")\n\n    result = writer.read()\n    assert result == \"1\\n2\\n\"\n\n\ndef test_exception(writer):\n    with make_logging_logger(\"tests.test_interception\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=\"{message}\")\n\n        try:\n            1 / 0  # noqa: B018\n        except Exception:\n            logging_logger.exception(\"Oops...\")\n\n    lines = writer.read().strip().splitlines()\n    assert lines[0] == \"Oops...\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n    assert sum(line.startswith(\"> \") for line in lines) == 1\n\n\ndef test_level_is_no(writer):\n    with make_logging_logger(\"tests\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=\"<lvl>{level.no} - {level.name} - {message}</lvl>\", colorize=True)\n        logging_logger.log(12, \"Hop\")\n\n    result = writer.read()\n    assert result == \"12 - Level 12 - Hop\\x1b[0m\\n\"\n\n\ndef test_level_does_not_exist(writer):\n    logging.addLevelName(152, \"FANCY_LEVEL\")\n\n    with make_logging_logger(\"tests\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=\"<lvl>{level.no} - {level.name} - {message}</lvl>\", colorize=True)\n        logging_logger.log(152, \"Nop\")\n\n    result = writer.read()\n    assert result == \"152 - Level 152 - Nop\\x1b[0m\\n\"\n\n\ndef test_level_exist_builtin(writer):\n    with make_logging_logger(\"tests\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=\"<lvl>{level.no} - {level.name} - {message}</lvl>\", colorize=True)\n        logging_logger.error(\"Error...\")\n\n    result = writer.read()\n    assert result == \"\\x1b[31m\\x1b[1m40 - ERROR - Error...\\x1b[0m\\n\"\n\n\ndef test_level_exists_custom(writer):\n    logging.addLevelName(99, \"ANOTHER_FANCY_LEVEL\")\n    logger.level(\"ANOTHER_FANCY_LEVEL\", no=99, color=\"<green>\", icon=\"\")\n\n    with make_logging_logger(\"tests\", InterceptHandler()) as logging_logger:\n        logger.add(writer, format=\"<lvl>{level.no} - {level.name} - {message}</lvl>\", colorize=True)\n        logging_logger.log(99, \"Yep!\")\n\n    result = writer.read()\n    assert result == \"\\x1b[32m99 - ANOTHER_FANCY_LEVEL - Yep!\\x1b[0m\\n\"\n\n\ndef test_using_logging_function(writer):\n    with make_logging_logger(None, InterceptHandler()):\n        logger.add(writer, format=\"{function} {line} {module} {file.name} {message}\")\n        logging.warning(\"ABC\")\n\n    result = writer.read()\n    assert result == \"test_using_logging_function 163 test_interception test_interception.py ABC\\n\"\n"
  },
  {
    "path": "tests/test_levels.py",
    "content": "import functools\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import parse\n\n\ndef test_log_int_level(writer):\n    logger.add(writer, format=\"{level.name} -> {level.no} -> {message}\", colorize=False)\n    logger.log(10, \"test\")\n\n    assert writer.read() == \"Level 10 -> 10 -> test\\n\"\n\n\ndef test_log_str_level(writer):\n    logger.add(writer, format=\"{level.name} -> {level.no} -> {message}\", colorize=False)\n    logger.log(\"DEBUG\", \"test\")\n\n    assert writer.read() == \"DEBUG -> 10 -> test\\n\"\n\n\ndef test_add_level(writer):\n    name = \"L3V3L\"\n    icon = \"[o]\"\n    level = 10\n\n    logger.level(name, level, color=\"<red>\", icon=icon)\n    fmt = \"{level.icon} <level>{level.name}</level> -> {level.no} -> {message}\"\n    logger.add(writer, format=fmt, colorize=True)\n\n    logger.log(name, \"test\")\n    expected = parse(\"%s <red>%s</red> -> %d -> test\" % (icon, name, level))\n    assert writer.read() == expected + \"\\n\"\n\n\n@pytest.mark.parametrize(\n    (\"colorize\", \"expected\"), [(False, \"foo | 10 | a\"), (True, parse(\"<red>foo | 10 | a</red>\"))]\n)\ndef test_add_level_after_add(writer, colorize, expected):\n    fmt = \"<level>{level.name} | {level.no} | {message}</level>\"\n    logger.add(writer, level=\"DEBUG\", format=fmt, colorize=colorize)\n    logger.level(\"foo\", 10, color=\"<red>\")\n    logger.log(\"foo\", \"a\")\n    assert writer.read() == expected + \"\\n\"\n\n\ndef test_add_level_then_log_with_int_value(writer):\n    logger.level(\"foo\", 16)\n    logger.add(writer, level=\"foo\", format=\"{level.name} {level.no} {message}\", colorize=False)\n\n    logger.log(16, \"test\")\n\n    assert writer.read() == \"Level 16 16 test\\n\"\n\n\ndef test_add_malicious_level(writer):\n    name = \"Level 15\"\n\n    logger.level(name, 45, color=\"<red>\")\n    fmt = \"{level.name} & {level.no} & <level>{message}</level>\"\n    logger.add(writer, format=fmt, colorize=True)\n\n    logger.log(15, \" A \")\n    logger.log(name, \" B \")\n\n    assert writer.read() == parse(\"Level 15 & 15 &  A \\x1b[0m\\nLevel 15 & 45 & <red> B </red>\\n\")\n\n\ndef test_add_existing_level(writer):\n    logger.level(\"DEBUG\", color=\"<red>\")\n    fmt = \"{level.icon} + <level>{level.name}</level> + {level.no} = {message}\"\n    logger.add(writer, format=fmt, colorize=True)\n\n    logger.debug(\"a\")\n    logger.log(\"DEBUG\", \"b\")\n    logger.log(10, \"c\")\n    logger.log(20, \"d\")\n\n    assert writer.read() == parse(\n        \"🐞 + <red>DEBUG</red> + 10 = a\\n\"\n        \"🐞 + <red>DEBUG</red> + 10 = b\\n\"\n        \"  + Level 10\\x1b[0m + 10 = c\\n\"\n        \"  + Level 20\\x1b[0m + 20 = d\\n\"\n    )\n\n\ndef test_blank_color(writer):\n    logger.level(\"INFO\", color=\" \")\n    logger.add(writer, level=\"DEBUG\", format=\"<level>{message}</level>\", colorize=True)\n    logger.info(\"Test\")\n    assert writer.read() == parse(\"Test\" \"\\x1b[0m\" \"\\n\")\n\n\ndef test_edit_level(writer):\n    logger.level(\"info\", no=11, color=\"<bold>\", icon=\"[?]\")\n    fmt = \"<level>->{level.no}, {level.name}, {level.icon}, {message}<-</level>\"\n    logger.add(writer, format=fmt, colorize=True)\n\n    logger.log(\"info\", \"a\")\n\n    logger.level(\"info\", icon=\"[!]\")\n    logger.log(\"info\", \"b\")\n\n    logger.level(\"info\", color=\"<red>\")\n    logger.log(\"info\", \"c\")\n\n    assert writer.read() == parse(\n        \"<bold>->11, info, [?], a<-</bold>\\n\"\n        \"<bold>->11, info, [!], b<-</bold>\\n\"\n        \"<red>->11, info, [!], c<-</red>\\n\"\n    )\n\n\ndef test_edit_existing_level(writer):\n    logger.level(\"DEBUG\", icon=\"!\")\n    fmt = \"{level.no}, <level>{level.name}</level>, {level.icon}, {message}\"\n    logger.add(writer, format=fmt, colorize=False)\n    logger.debug(\"a\")\n    assert writer.read() == \"10, DEBUG, !, a\\n\"\n\n\ndef test_get_level():\n    level = (\"lvl\", 11, \"<red>\", \"[!]\")\n    logger.level(*level)\n    assert logger.level(\"lvl\") == level\n\n\ndef test_get_existing_level():\n    assert logger.level(\"DEBUG\") == (\"DEBUG\", 10, \"<blue><bold>\", \"🐞\")\n\n\ndef test_add_custom_level(writer):\n    logger.level(\"foo\", 17, color=\"<yellow>\")\n    logger.add(\n        writer,\n        level=\"foo\",\n        format=\"<level>{level.name} + {level.no} + {message}</level>\",\n        colorize=False,\n    )\n\n    logger.debug(\"nope\")\n    logger.info(\"yes\")\n\n    assert writer.read() == \"INFO + 20 + yes\\n\"\n\n\ndef test_updating_min_level(writer):\n    logger.debug(\"Early exit -> no {error}\", nope=None)\n\n    a = logger.add(writer, level=\"DEBUG\")\n\n    with pytest.raises(ValueError, match=\"^The logging message could not be formatted\"):\n        logger.debug(\"An {error} will occur!\", nope=None)\n\n    logger.trace(\"Early exit -> no {error}\", nope=None)\n\n    logger.add(writer, level=\"INFO\")\n    logger.remove(a)\n\n    logger.debug(\"Early exit -> no {error}\", nope=None)\n\n\ndef test_assign_custom_level_method(writer):\n    logger.level(\"foobar\", no=33, icon=\"🤖\", color=\"<blue>\")\n\n    logger.__class__.foobar = functools.partialmethod(logger.__class__.log, \"foobar\")\n    logger.foobar(\"Message not logged\")\n    logger.add(\n        writer,\n        format=\"<lvl>{level.name} {level.no} {level.icon} {message} {extra}</lvl>\",\n        colorize=True,\n    )\n    logger.foobar(\"Logged message\")\n    logger.bind(something=\"otherthing\").foobar(\"Another message\")\n    assert writer.read() == parse(\n        \"<blue>foobar 33 🤖 Logged message {}</blue>\\n\"\n        \"<blue>foobar 33 🤖 Another message {'something': 'otherthing'}</blue>\\n\"\n    )\n\n\ndef test_updating_level_no_not_allowed_default():\n    with pytest.raises(\n        ValueError, match=\"^Level 'DEBUG' already exists, you can't update its severity no$\"\n    ):\n        logger.level(\"DEBUG\", 100)\n\n\ndef test_updating_level_no_not_allowed_custom():\n    logger.level(\"foobar\", no=33)\n    with pytest.raises(\n        ValueError, match=\"^Level 'foobar' already exists, you can't update its severity no$\"\n    ):\n        logger.level(\"foobar\", 100)\n\n\n@pytest.mark.parametrize(\"level\", [3.4, object(), set()])\ndef test_log_invalid_level_type(writer, level):\n    logger.add(writer)\n    with pytest.raises(\n        TypeError, match=\"^Invalid level, it should be an integer or a string, not: '[^']+'$\"\n    ):\n        logger.log(level, \"test\")\n\n\n@pytest.mark.parametrize(\"level\", [-1, -999])\ndef test_log_invalid_level_value(writer, level):\n    logger.add(writer)\n    with pytest.raises(\n        ValueError, match=\"^Invalid level value, it should be a positive integer, not: -?[0-9]+\"\n    ):\n        logger.log(level, \"test\")\n\n\n@pytest.mark.parametrize(\"level\", [\"foo\", \"debug\"])\ndef test_log_unknown_level(writer, level):\n    logger.add(writer)\n    with pytest.raises(ValueError, match=\"^Level '[^']+' does not exist$\"):\n        logger.log(level, \"test\")\n\n\n@pytest.mark.parametrize(\"level_name\", [10, object(), set()])\ndef test_add_invalid_level_name(level_name):\n    with pytest.raises(TypeError):\n        logger.level(level_name, 11)\n\n\n@pytest.mark.parametrize(\"level_value\", [\"1\", object(), 3.4, set()])\ndef test_add_invalid_level_type(level_value):\n    with pytest.raises(TypeError):\n        logger.level(\"test\", level_value)\n\n\n@pytest.mark.parametrize(\"level_value\", [-1, -999])\ndef test_add_invalid_level_value(level_value):\n    with pytest.raises(\n        ValueError, match=\"^Invalid level no, it should be a positive integer, not: -?[0-9]+$\"\n    ):\n        logger.level(\"test\", level_value)\n\n\n@pytest.mark.parametrize(\"level\", [10, object(), set()])\ndef test_get_invalid_level(level):\n    with pytest.raises(TypeError):\n        logger.level(level)\n\n\ndef test_get_unknown_level():\n    with pytest.raises(ValueError, match=\"^Level '[^']+' does not exist$\"):\n        logger.level(\"foo\")\n\n\n@pytest.mark.parametrize(\"level\", [10, object(), set()])\ndef test_edit_invalid_level(level):\n    with pytest.raises(TypeError):\n        logger.level(level, icon=\"?\")\n\n\n@pytest.mark.parametrize(\"level_name\", [\"foo\", \"debug\"])\ndef test_edit_unknown_level(level_name):\n    with pytest.raises(\n        ValueError,\n        match=\"^Level '[^']+' does not exist, you have to create it by specifying a level no$\",\n    ):\n        logger.level(level_name, icon=\"?\")\n\n\n@pytest.mark.parametrize(\"color\", [\"<>\", \"<foo>\"])\ndef test_add_level_unknown_color(color):\n    with pytest.raises(\n        ValueError,\n        match=(\n            'Tag \"<[^>]*>\" does not correspond to any known color directive, '\n            r\"make sure you have not misspelled it \\(or prepend '\\\\' to escape it\\)\"\n        ),\n    ):\n        logger.level(\"foobar\", no=20, icon=\"\", color=color)\n\n\n@pytest.mark.parametrize(\"color\", [\"</>\", \"</red>\", \"</foo>\"])\ndef test_add_level_invalid_markup(color):\n    with pytest.raises(\n        ValueError, match='^Closing tag \"</[^>]*>\" has no corresponding opening tag$'\n    ):\n        logger.level(\"foobar\", no=20, icon=\"\", color=color)\n\n\n@pytest.mark.parametrize(\"color\", [\"<lvl>\", \" <level> \"])\ndef test_add_level_invalid_name(color):\n    with pytest.raises(\n        ValueError,\n        match=(\n            \"^The '<level>' color tag is not allowed in this context, \"\n            r\"it has not yet been associated to any color value\\.$\"\n        ),\n    ):\n        logger.level(\"foobar\", no=20, icon=\"\", color=color)\n"
  },
  {
    "path": "tests/test_locks.py",
    "content": "import gc\nimport pickle\nimport sys\nimport threading\nimport time\n\nimport pytest\n\nfrom loguru import logger\n\n\nclass CyclicReference:\n    \"\"\"A minimal cyclic reference.\n\n    Cyclical references are garbage collected using the generational collector rather than\n    via reference counting. This is important here, because the generational collector runs\n    periodically, meaning that it is hard to predict when the stack will be overtaken by a\n    garbage collection process - but it will almost always be when allocating memory of some\n    kind.\n\n    When this object is garbage-collected, a log will be emitted.\n    \"\"\"\n\n    def __init__(self, _other: \"CyclicReference\" = None):\n        self.other = _other or CyclicReference(_other=self)\n\n    def __del__(self):\n        logger.info(\"tearing down\")\n\n\n@pytest.fixture\ndef _remove_cyclic_references():\n    \"\"\"Prevent cyclic isolate finalizers bleeding into other tests.\"\"\"\n    try:\n        yield\n    finally:\n        gc.collect()\n\n\n@pytest.mark.usefixtures(\"_remove_cyclic_references\")\ndef test_no_deadlock_on_generational_garbage_collection():\n    \"\"\"Regression test for https://github.com/Delgan/loguru/issues/712.\n\n    Assert that deadlocks do not occur when a cyclic isolate containing log output in\n    finalizers is collected by generational GC, during the output of another log message.\n    \"\"\"\n    # GIVEN a sink which assigns some memory\n    output = []\n\n    def sink(message):\n        # The generational GC could be triggered here by any memory assignment, but we\n        # trigger it explicitly to avoid a flaky test.\n        # See https://github.com/Delgan/loguru/issues/712\n        gc.collect()\n\n        # Actually write the message somewhere\n        output.append(message)\n\n    logger.add(sink, colorize=False)\n\n    # WHEN there are cyclic isolates in memory which log on GC\n    # AND logs are produced long enough to trigger generational GC\n    for _ in range(10):\n        CyclicReference()\n        logger.info(\"test\")\n\n    # THEN deadlock should not be reached\n    assert True\n\n\ndef test_no_deadlock_if_logger_used_inside_sink_with_catch(capsys):\n    def sink(message):\n        logger.info(message)\n\n    logger.add(sink, colorize=False, catch=True)\n\n    logger.info(\"Test\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert \"deadlock avoided\" in err\n\n\ndef test_no_deadlock_if_logger_used_inside_sink_without_catch():\n    def sink(message):\n        logger.info(message)\n\n    logger.add(sink, colorize=False, catch=False)\n\n    with pytest.raises(RuntimeError, match=r\".*deadlock avoided.*\"):\n        logger.info(\"Test\")\n\n\ndef test_no_error_if_multithreading(capsys):\n    barrier = threading.Barrier(2)\n\n    def sink(message):\n        barrier.wait()\n        sys.stderr.write(message)\n        time.sleep(0.5)  # Give time to the other thread to try to acquire the lock.\n\n    def worker():\n        logger.info(\"Thread message\")\n        barrier.wait()  # Release main thread.\n\n    logger.add(sink, colorize=False, catch=False, format=\"{message}\")\n    thread = threading.Thread(target=worker)\n    thread.start()\n\n    barrier.wait()\n    logger.info(\"Main message\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"Thread message\\nMain message\\n\"\n\n\ndef _pickle_sink(message):\n    sys.stderr.write(message)\n\n    if message.record[\"extra\"].get(\"clone\", False):\n        new_logger = pickle.loads(pickle.dumps(logger))\n        new_logger.bind(clone=False).info(\"From clone\")\n        new_logger.remove()\n\n\ndef test_pickled_logger_does_not_inherit_acquired_local(capsys):\n    logger.add(_pickle_sink, colorize=False, catch=False, format=\"{message}\")\n\n    logger.bind(clone=True).info(\"From main\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"From main\\nFrom clone\\n\"\n"
  },
  {
    "path": "tests/test_multiprocessing.py",
    "content": "import copy\nimport multiprocessing\nimport os\nimport platform\nimport sys\nimport threading\nimport time\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import new_event_loop_context\n\n\n@pytest.fixture\ndef fork_context():\n    return multiprocessing.get_context(\"fork\")\n\n\n@pytest.fixture\ndef spawn_context():\n    return multiprocessing.get_context(\"spawn\")\n\n\ndef do_something(i):\n    logger.info(\"#{}\", i)\n\n\ndef set_logger(logger_):\n    global logger\n    logger = logger_\n\n\ndef subworker(logger_):\n    logger_.info(\"Child\")\n\n\ndef subworker_inheritance():\n    logger.info(\"Child\")\n\n\ndef subworker_remove(logger_):\n    logger_.info(\"Child\")\n    logger_.remove()\n    logger_.info(\"Nope\")\n\n\ndef subworker_remove_inheritance():\n    logger.info(\"Child\")\n    logger.remove()\n    logger.info(\"Nope\")\n\n\ndef subworker_complete(logger_):\n    async def work():\n        logger_.info(\"Child\")\n        await logger_.complete()\n\n    with new_event_loop_context() as loop:\n        loop.run_until_complete(work())\n\n\ndef subworker_complete_inheritance():\n    async def work():\n        logger.info(\"Child\")\n        await logger.complete()\n\n    with new_event_loop_context() as loop:\n        loop.run_until_complete(work())\n\n\ndef subworker_barrier(logger_, barrier):\n    logger_.info(\"Child\")\n    barrier.wait()\n    time.sleep(0.5)\n    logger_.info(\"Nope\")\n\n\ndef subworker_barrier_inheritance(barrier):\n    logger.info(\"Child\")\n    barrier.wait()\n    time.sleep(0.5)\n    logger.info(\"Nope\")\n\n\nclass Writer:\n    def __init__(self):\n        self._output = \"\"\n\n    def write(self, message):\n        self._output += message\n\n    def read(self):\n        return self._output\n\n\ndef test_apply_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with spawn_context.Pool(1, set_logger, [logger]) as pool:\n        for i in range(3):\n            pool.apply(do_something, (i,))\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_apply_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with fork_context.Pool(1, set_logger, [logger]) as pool:\n        for i in range(3):\n            pool.apply(do_something, (i,))\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_apply_inheritance(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with fork_context.Pool(1) as pool:\n        for i in range(3):\n            pool.apply(do_something, (i,))\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\ndef test_apply_async_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with spawn_context.Pool(1, set_logger, [logger]) as pool:\n        for i in range(3):\n            result = pool.apply_async(do_something, (i,))\n            result.get()\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_apply_async_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with fork_context.Pool(1, set_logger, [logger]) as pool:\n        for i in range(3):\n            result = pool.apply_async(do_something, (i,))\n            result.get()\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_apply_async_inheritance(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with fork_context.Pool(1) as pool:\n        for i in range(3):\n            result = pool.apply_async(do_something, (i,))\n            result.get()\n        pool.close()\n        pool.join()\n\n    logger.info(\"Done!\")\n    logger.remove()\n\n    assert writer.read() == \"#0\\n#1\\n#2\\nDone!\\n\"\n\n\ndef test_process_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = spawn_context.Process(target=subworker, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_process_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_process_inheritance(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker_inheritance)\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\ndef test_remove_in_child_process_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = spawn_context.Process(target=subworker_remove, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_remove_in_child_process_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker_remove, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_remove_in_child_process_inheritance(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker_remove_inheritance)\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\ndef test_remove_in_main_process_spawn(spawn_context):\n    # Actually, this test may fail if sleep time in main process is too small (and no barrier used)\n    # In such situation, it seems the child process has not enough time to initialize itself\n    # It may fail with an \"EOFError\" during unpickling of the (garbage collected / closed) Queue\n    writer = Writer()\n    barrier = spawn_context.Barrier(2)\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = spawn_context.Process(target=subworker_barrier, args=(logger, barrier))\n    process.start()\n    barrier.wait()\n    logger.info(\"Main\")\n    logger.remove()\n    process.join()\n\n    assert process.exitcode == 0\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_remove_in_main_process_fork(fork_context):\n    writer = Writer()\n    barrier = fork_context.Barrier(2)\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker_barrier, args=(logger, barrier))\n    process.start()\n    barrier.wait()\n    logger.info(\"Main\")\n    logger.remove()\n    process.join()\n\n    assert process.exitcode == 0\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_remove_in_main_process_inheritance(fork_context):\n    writer = Writer()\n    barrier = fork_context.Barrier(2)\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker_barrier_inheritance, args=(barrier,))\n    process.start()\n    barrier.wait()\n    logger.info(\"Main\")\n    logger.remove()\n    process.join()\n\n    assert process.exitcode == 0\n\n    assert writer.read() == \"Child\\nMain\\n\"\n\n\ndef test_await_complete_spawn(capsys, spawn_context):\n    async def writer(msg):\n        print(msg, end=\"\")\n\n    with new_event_loop_context() as loop:\n        logger.add(\n            writer, context=spawn_context, format=\"{message}\", loop=loop, enqueue=True, catch=False\n        )\n\n        process = spawn_context.Process(target=subworker_complete, args=(logger,))\n        process.start()\n        process.join()\n\n        assert process.exitcode == 0\n\n        async def local():\n            await logger.complete()\n\n        loop.run_until_complete(local())\n\n    out, err = capsys.readouterr()\n    assert out == \"Child\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_await_complete_fork(capsys, fork_context):\n    async def writer(msg):\n        print(msg, end=\"\")\n\n    with new_event_loop_context() as loop:\n        logger.add(\n            writer, context=fork_context, format=\"{message}\", loop=loop, enqueue=True, catch=False\n        )\n\n        process = fork_context.Process(target=subworker_complete, args=(logger,))\n        process.start()\n        process.join()\n\n        assert process.exitcode == 0\n\n        async def local():\n            await logger.complete()\n\n        loop.run_until_complete(local())\n\n    out, err = capsys.readouterr()\n    assert out == \"Child\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_await_complete_inheritance(capsys, fork_context):\n    async def writer(msg):\n        print(msg, end=\"\")\n\n    with new_event_loop_context() as loop:\n        logger.add(\n            writer, context=fork_context, format=\"{message}\", loop=loop, enqueue=True, catch=False\n        )\n\n        process = fork_context.Process(target=subworker_complete_inheritance)\n        process.start()\n        process.join()\n\n        assert process.exitcode == 0\n\n        async def local():\n            await logger.complete()\n\n        loop.run_until_complete(local())\n\n    out, err = capsys.readouterr()\n    assert out == \"Child\\n\"\n    assert err == \"\"\n\n\ndef test_not_picklable_sinks_spawn(spawn_context, tmp_path, capsys):\n    filepath = tmp_path / \"test.log\"\n    stream = sys.stderr\n    output = []\n\n    logger.add(filepath, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n    logger.add(stream, context=spawn_context, format=\"{message}\", enqueue=True)\n    logger.add(lambda m: output.append(m), context=spawn_context, format=\"{message}\", enqueue=True)\n\n    process = spawn_context.Process(target=subworker, args=[logger])\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n\n    assert filepath.read_text() == \"Child\\nMain\\n\"\n    assert out == \"\"\n    assert err == \"Child\\nMain\\n\"\n    assert output == [\"Child\\n\", \"Main\\n\"]\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_not_picklable_sinks_fork(capsys, tmp_path, fork_context):\n    filepath = tmp_path / \"test.log\"\n    stream = sys.stderr\n    output = []\n\n    logger.add(filepath, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n    logger.add(stream, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n    logger.add(\n        lambda m: output.append(m),\n        context=fork_context,\n        format=\"{message}\",\n        enqueue=True,\n        catch=False,\n    )\n\n    process = fork_context.Process(target=subworker, args=[logger])\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n\n    assert filepath.read_text() == \"Child\\nMain\\n\"\n    assert out == \"\"\n    assert err == \"Child\\nMain\\n\"\n    assert output == [\"Child\\n\", \"Main\\n\"]\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_not_picklable_sinks_inheritance(capsys, tmp_path, fork_context):\n    filepath = tmp_path / \"test.log\"\n    stream = sys.stderr\n    output = []\n\n    logger.add(filepath, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n    logger.add(stream, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n    logger.add(\n        lambda m: output.append(m),\n        context=fork_context,\n        format=\"{message}\",\n        enqueue=True,\n        catch=False,\n    )\n\n    process = fork_context.Process(target=subworker_inheritance)\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n\n    assert filepath.read_text() == \"Child\\nMain\\n\"\n    assert out == \"\"\n    assert err == \"Child\\nMain\\n\"\n    assert output == [\"Child\\n\", \"Main\\n\"]\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\n@pytest.mark.skipif(sys.version_info < (3, 7), reason=\"No 'os.register_at_fork()' function\")\n@pytest.mark.parametrize(\"enqueue\", [True, False])\n@pytest.mark.parametrize(\"deepcopied\", [True, False])\ndef test_no_deadlock_if_internal_lock_in_use(tmp_path, enqueue, deepcopied, fork_context):\n    if deepcopied:\n        logger_ = copy.deepcopy(logger)\n    else:\n        logger_ = logger\n\n    output = tmp_path / \"stdout.txt\"\n\n    with output.open(\"w\") as stdout:\n\n        def slow_sink(msg):\n            time.sleep(0.5)\n            stdout.write(msg)\n            stdout.flush()\n\n        def main():\n            logger_.info(\"Main\")\n\n        def worker():\n            logger_.info(\"Child\")\n\n        logger_.add(\n            slow_sink, context=fork_context, format=\"{message}\", enqueue=enqueue, catch=False\n        )\n\n        thread = threading.Thread(target=main)\n        thread.start()\n\n        process = fork_context.Process(target=worker)\n        process.start()\n\n        thread.join()\n        process.join(2)\n\n        assert process.exitcode == 0\n\n        logger_.remove()\n\n    assert output.read_text() in (\"Main\\nChild\\n\", \"Child\\nMain\\n\")\n\n\n@pytest.mark.skipif(sys.version_info < (3, 7), reason=\"No 'os.register_at_fork()' function\")\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\n@pytest.mark.parametrize(\"enqueue\", [True, False])\ndef test_no_deadlock_if_external_lock_in_use(enqueue, capsys, fork_context):\n    # Can't reproduce the bug on pytest (even if stderr is not wrapped), but let it anyway\n    logger.add(sys.stderr, context=fork_context, enqueue=enqueue, catch=True, format=\"{message}\")\n    num = 100\n\n    for i in range(num):\n        logger.info(\"This is a message: {}\", i)\n        process = fork_context.Process(target=lambda: None)\n        process.start()\n        process.join(1)\n        assert process.exitcode == 0\n\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\".join(\"This is a message: %d\\n\" % i for i in range(num))\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\n@pytest.mark.skipif(platform.python_implementation() == \"PyPy\", reason=\"PyPy is too slow\")\ndef test_complete_from_multiple_child_processes(capsys, fork_context):\n    logger.add(lambda _: None, context=fork_context, enqueue=True, catch=False)\n    num = 100\n\n    barrier = fork_context.Barrier(num)\n\n    def worker(barrier):\n        barrier.wait()\n        logger.complete()\n\n    processes = []\n\n    for _ in range(num):\n        process = fork_context.Process(target=worker, args=(barrier,))\n        process.start()\n        processes.append(process)\n\n    for process in processes:\n        process.join(5)\n        assert process.exitcode == 0\n\n    out, err = capsys.readouterr()\n    assert out == err == \"\"\n"
  },
  {
    "path": "tests/test_opt.py",
    "content": "import sys\nfrom unittest.mock import MagicMock\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import parse\n\n\ndef test_record(writer):\n    logger.add(writer, format=\"{message}\")\n\n    logger.opt(record=True).debug(\"1\")\n    logger.opt(record=True).debug(\"2 {record[level]}\")\n    logger.opt(record=True).log(11, \"3 {0} {a} {record[level].no}\", 4, a=5)\n\n    assert writer.read() == \"1\\n2 DEBUG\\n3 4 5 11\\n\"\n\n\ndef test_record_in_kwargs_too(writer):\n    logger.add(writer, catch=False)\n\n    with pytest.raises(\n        TypeError,\n        match=(\n            \"^The message can't be formatted: 'record' shall not be used as a keyword argument \"\n            r\"while logger has been configured with '\\.opt\\(record=True\\)'$\"\n        ),\n    ):\n        logger.opt(record=True).info(\"Foo {record}\", record=123)\n\n\ndef test_record_not_in_extra():\n    extra = None\n\n    def sink(message):\n        nonlocal extra\n        extra = message.record[\"extra\"]\n\n    logger.add(sink, catch=False)\n\n    logger.opt(record=True).info(\"Test\")\n\n    assert extra == {}\n\n\ndef test_kwargs_in_extra_of_record():\n    message = None\n\n    def sink(message_):\n        nonlocal message\n        message = message_\n\n    logger.add(sink, format=\"{message}\", catch=False)\n\n    logger.opt(record=True).info(\"Test {record[extra][foo]}\", foo=123)\n\n    assert message == \"Test 123\\n\"\n    assert message.record[\"extra\"] == {\"foo\": 123}\n\n\ndef test_exception_boolean(writer):\n    logger.add(writer, format=\"{level.name}: {message}\")\n\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.opt(exception=True).debug(\"Error {0} {record}\", 1, record=\"test\")\n\n    lines = writer.read().strip().splitlines()\n\n    assert lines[0] == \"DEBUG: Error 1 test\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\ndef test_exception_exc_info(writer):\n    logger.add(writer, format=\"{message}\")\n\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        exc_info = sys.exc_info()\n\n    logger.opt(exception=exc_info).debug(\"test\")\n\n    lines = writer.read().strip().splitlines()\n\n    assert lines[0] == \"test\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\ndef test_exception_class(writer):\n    logger.add(writer, format=\"{message}\")\n\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        _, exc_class, _ = sys.exc_info()\n\n    logger.opt(exception=exc_class).debug(\"test\")\n\n    lines = writer.read().strip().splitlines()\n\n    assert lines[0] == \"test\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\ndef test_exception_log_function(writer):\n    logger.add(writer, format=\"{level.no} {message}\")\n\n    try:\n        1 / 0  # noqa: B018\n    except Exception:\n        logger.opt(exception=True).log(50, \"Error\")\n\n    lines = writer.read().strip().splitlines()\n\n    assert lines[0] == \"50 Error\"\n    assert lines[-1] == \"ZeroDivisionError: division by zero\"\n\n\ndef test_lazy(writer):\n    counter = 0\n\n    def laziness():\n        nonlocal counter\n        counter += 1\n        return counter\n\n    logger.add(writer, level=10, format=\"{level.no} => {message}\")\n\n    logger.opt(lazy=True).log(10, \"1: {lazy}\", lazy=laziness)\n    logger.opt(lazy=True).log(5, \"2: {0}\", laziness)\n\n    logger.remove()\n\n    logger.opt(lazy=True).log(20, \"3: {}\", laziness)\n\n    i = logger.add(writer, level=15, format=\"{level.no} => {message}\")\n    logger.add(writer, level=20, format=\"{level.no} => {message}\")\n\n    logger.log(17, \"4: {}\", counter)\n    logger.opt(lazy=True).log(14, \"5: {lazy}\", lazy=lambda: counter)\n\n    logger.remove(i)\n\n    logger.opt(lazy=True).log(16, \"6: {0}\", lambda: counter)\n\n    logger.opt(lazy=True).info(\"7: {}\", laziness)\n    logger.debug(\"7: {}\", counter)\n\n    assert writer.read() == \"10 => 1: 1\\n17 => 4: 1\\n20 => 7: 2\\n\"\n\n\ndef test_lazy_function_executed_only_once(writer):\n    counter = 0\n\n    def laziness():\n        nonlocal counter\n        counter += 1\n        return counter\n\n    logger.add(writer, level=10, format=\"{level.name} => {message}\")\n\n    logger.opt(lazy=True).info(\"1: {lazy} {lazy}\", lazy=laziness)\n    logger.opt(lazy=True).info(\"2: {0} {0}\", laziness)\n\n    assert writer.read() == \"INFO => 1: 1 1\\nINFO => 2: 2 2\\n\"\n\n\ndef test_logging_within_lazy_function(writer):\n    logger.add(writer, level=20, format=\"{message}\")\n\n    def laziness():\n        logger.trace(\"Nope\")\n        logger.warning(\"Yes Warn\")\n\n    logger.opt(lazy=True).trace(\"No\", laziness)\n\n    assert writer.read() == \"\"\n\n    logger.opt(lazy=True).info(\"Yes\", laziness)\n\n    assert writer.read() == \"Yes Warn\\nYes\\n\"\n\n\ndef test_depth(writer):\n    logger.add(writer, format=\"{function} : {message}\")\n\n    def a():\n        logger.opt(depth=1).debug(\"Test 1\")\n        logger.opt(depth=0).debug(\"Test 2\")\n        logger.opt(depth=1).log(10, \"Test 3\")\n\n    a()\n\n    logger.remove()\n\n    assert writer.read() == \"test_depth : Test 1\\na : Test 2\\ntest_depth : Test 3\\n\"\n\n\ndef test_depth_with_unreachable_frame(writer):\n    logger.add(writer, format=\"{name} : {function} : {file} : {line} : {message}\")\n    logger.opt(depth=1000).debug(\"Test\")\n    logger.remove()\n    assert writer.read() == \"None : <unknown> : <unknown> : 0 : Test\\n\"\n\n\ndef test_capture(writer):\n    logger.add(writer, format=\"{message} {extra}\")\n    logger.opt(capture=False).info(\"No {}\", 123, no=False)\n    logger.opt(capture=False).info(\"Formatted: {fmt}\", fmt=456)\n    logger.opt(capture=False).info(\"Formatted bis: {} {fmt}\", 123, fmt=456)\n    assert writer.read() == \"No 123 {}\\nFormatted: 456 {}\\nFormatted bis: 123 456 {}\\n\"\n\n\ndef test_colors(writer):\n    logger.add(writer, format=\"<red>a</red> {message}\", colorize=True)\n    logger.opt(colors=True).debug(\"<blue>b</blue>\")\n    logger.opt(colors=True).log(20, \"<y>c</y>\")\n\n    assert writer.read() == parse(\n        \"<red>a</red> <blue>b</blue>\\n\" \"<red>a</red> <y>c</y>\\n\", strip=False\n    )\n\n\ndef test_colors_not_colorize(writer):\n    logger.add(writer, format=\"<red>a</red> {message}\", colorize=False)\n    logger.opt(colors=True).debug(\"<blue>b</blue>\")\n    assert writer.read() == parse(\"<red>a</red> <blue>b</blue>\\n\", strip=True)\n\n\ndef test_colors_doesnt_color_unrelated(writer):\n    logger.add(writer, format=\"{message} {extra[trap]}\", colorize=True)\n    logger.bind(trap=\"<red>B</red>\").opt(colors=True).debug(\"<red>A</red>\")\n    assert writer.read() == parse(\"<red>A</red>\", strip=False) + \" <red>B</red>\\n\"\n\n\ndef test_colors_doesnt_strip_unrelated(writer):\n    logger.add(writer, format=\"{message} {extra[trap]}\", colorize=False)\n    logger.bind(trap=\"<red>B</red>\").opt(colors=True).debug(\"<red>A</red>\")\n    assert writer.read() == parse(\"<red>A</red>\", strip=True) + \" <red>B</red>\\n\"\n\n\ndef test_colors_doesnt_raise_unrelated_colorize(writer):\n    logger.add(writer, format=\"{message} {extra[trap]}\", colorize=True, catch=False)\n    logger.bind(trap=\"</red>\").opt(colors=True).debug(\"A\")\n    assert writer.read() == \"A </red>\\n\"\n\n\ndef test_colors_doesnt_raise_unrelated_not_colorize(writer):\n    logger.add(writer, format=\"{message} {extra[trap]}\", colorize=False, catch=False)\n    logger.bind(trap=\"</red>\").opt(colors=True).debug(\"A\")\n    assert writer.read() == \"A </red>\\n\"\n\n\ndef test_colors_doesnt_raise_unrelated_colorize_dynamic(writer):\n    logger.add(writer, format=lambda x: \"{message} {extra[trap]}\", colorize=True, catch=False)\n    logger.bind(trap=\"</red>\").opt(colors=True).debug(\"A\")\n    assert writer.read() == \"A </red>\"\n\n\ndef test_colors_doesnt_raise_unrelated_not_colorize_dynamic(writer):\n    logger.add(writer, format=lambda x: \"{message} {extra[trap]}\", colorize=False, catch=False)\n    logger.bind(trap=\"</red>\").opt(colors=True).debug(\"A\")\n    assert writer.read() == \"A </red>\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_within_record(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger_ = logger.bind(start=\"<red>\", end=\"</red>\")\n    logger_.opt(colors=True, record=True).debug(\"{record[extra][start]}B{record[extra][end]}\")\n    assert writer.read() == \"<red>B</red>\\n\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_nested(writer, colorize):\n    logger.add(writer, format=\"(<red>[{message}]</red>)\", colorize=colorize)\n    logger.opt(colors=True).debug(\"A<green>B</green>C<blue>D</blue>E\")\n    assert writer.read() == parse(\n        \"(<red>[A<green>B</green>C<blue>D</blue>E]</red>)\\n\", strip=not colorize\n    )\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_stripped_in_message_record(colorize):\n    message = None\n\n    def sink(msg):\n        nonlocal message\n        message = msg.record[\"message\"]\n\n    logger.add(sink, colorize=colorize)\n    logger.opt(colors=True).debug(\"<red>Test</red>\")\n    assert message == \"Test\"\n\n\n@pytest.mark.parametrize(\"message\", [\"<red>\", \"</red>\", \"X </red> <red> Y\"])\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_invalid_markup_in_message(writer, message, colorize):\n    logger.add(writer, format=\"<red>{message}</red>\", colorize=colorize, catch=False)\n    with pytest.raises(\n        ValueError,\n        match='(Closing|Opening) tag \"[^\"]*\" has no corresponding (opening|closing) tag',\n    ):\n        logger.opt(colors=True).debug(message)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_args(writer, colorize):\n    logger.add(writer, format=\"=> {message} <=\", colorize=colorize)\n    logger.opt(colors=True).debug(\"the {0}test{end}\", \"<red>\", end=\"</red>\")\n    assert writer.read() == \"=> the <red>test</red> <=\\n\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_level(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger.level(\"DEBUG\", color=\"<green>\")\n    logger.opt(colors=True).debug(\"a <level>level</level> b\")\n    assert writer.read() == parse(\"a <green>level</green> b\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_double_message(writer, colorize):\n    logger.add(\n        writer, format=\"<red><b>{message}...</b> - <c>...{message}</c></red>\", colorize=colorize\n    )\n    logger.opt(colors=True).debug(\"<g>foo</g> bar <g>baz</g>\")\n\n    assert writer.read() == parse(\n        \"<red><b><g>foo</g> bar <g>baz</g>...</b> - <c>...<g>foo</g> bar <g>baz</g></c></red>\\n\",\n        strip=not colorize,\n    )\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_multiple_calls(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger.opt(colors=True).debug(\"a <red>foo</red> b\")\n    logger.opt(colors=True).debug(\"a <red>foo</red> b\")\n    assert writer.read() == parse(\"a <red>foo</red> b\\na <red>foo</red> b\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_multiple_calls_level_color_changed(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger.level(\"INFO\", color=\"<blue>\")\n    logger.opt(colors=True).info(\"a <level>foo</level> b\")\n    logger.level(\"INFO\", color=\"<red>\")\n    logger.opt(colors=True).info(\"a <level>foo</level> b\")\n    assert writer.read() == parse(\"a <blue>foo</blue> b\\na <red>foo</red> b\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_dynamic_formatter(writer, colorize):\n    logger.add(writer, format=lambda r: \"<red>{message}</red>\", colorize=colorize)\n    logger.opt(colors=True).debug(\"<b>a</b> <y>b</y>\")\n    assert writer.read() == parse(\"<red><b>a</b> <y>b</y></red>\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_format_specs(writer, colorize):\n    fmt = \"<g>{level.no:03d} {message:} {message!s:} {{nope}} {extra[a][b]!r}</g>\"\n    logger.add(writer, colorize=colorize, format=fmt)\n    logger.bind(a={\"b\": \"c\"}).opt(colors=True).debug(\"<g>{X}</g>\")\n    assert writer.read() == parse(\"<g>010 <g>{X}</g> {X} {nope} 'c'</g>\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_message_specs(writer, colorize):\n    logger.add(writer, colorize=colorize, format=\"<g>{message}</g>\")\n    logger.opt(colors=True).debug(\"{} <b>A</b> {{nope}} {key:03d} {let!r}\", 1, key=10, let=\"c\")\n    logger.opt(colors=True).debug(\"<b>{0:0{1}d}</b>\", 2, 4)\n    assert writer.read() == parse(\n        \"<g>1 <b>A</b> {nope} 010 'c'</g>\\n<g><b>0002</b></g>\\n\", strip=not colorize\n    )\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colored_string_used_as_spec(writer, colorize):\n    logger.add(writer, colorize=colorize, format=\"{level.no:{message}} <red>{message}</red>\")\n    logger.opt(colors=True).log(30, \"03d\")\n    assert writer.read() == parse(\"030 <red>03d</red>\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colored_string_getitem(writer, colorize):\n    logger.add(writer, colorize=colorize, format=\"<red>{message[0]}</red>\")\n    logger.opt(colors=True).info(\"ABC\")\n    assert writer.read() == parse(\"<red>A</red>\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_without_formatting_args(writer, colorize):\n    string = \"{} This { should } not } raise {\"\n    logger.add(writer, colorize=colorize, format=\"{message}\")\n    logger.opt(colors=True).info(string)\n    assert writer.read() == string + \"\\n\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_recursion_depth_exceeded_in_format(writer, colorize):\n    with pytest.raises(\n        ValueError, match=\"^Invalid format, color markups could not be parsed correctly$\"\n    ):\n        logger.add(writer, format=\"{message:{message:{message:}}}\", colorize=colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_recursion_depth_exceeded_in_message(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n\n    with pytest.raises(ValueError, match=\"Max string recursion exceeded\"):\n        logger.opt(colors=True).info(\"{foo:{foo:{foo:}}}\", foo=123)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_auto_indexing(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger.opt(colors=True).info(\"<red>{}</red> <green>{}</green>\", \"foo\", \"bar\")\n    assert writer.read() == parse(\"<red>foo</red> <green>bar</green>\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_colors_with_manual_indexing(writer, colorize):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n    logger.opt(colors=True).info(\"<red>{1}</red> <green>{0}</green>\", \"foo\", \"bar\")\n    assert writer.read() == parse(\"<red>bar</red> <green>foo</green>\\n\", strip=not colorize)\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\n@pytest.mark.parametrize(\"message\", [\"{} {0}\", \"{1} {}\"])\ndef test_colors_with_invalid_indexing(writer, colorize, message):\n    logger.add(writer, format=\"{message}\", colorize=colorize)\n\n    with pytest.raises(\n        ValueError,\n        match=\"cannot switch from manual field specification to automatic field numbering\",\n    ):\n        logger.opt(colors=True).debug(message, 1, 2, 3)\n\n\ndef test_raw(writer):\n    logger.add(writer, format=\"\", colorize=True)\n    logger.opt(raw=True).info(\"Raw {}\", \"message\")\n    logger.opt(raw=True).log(30, \" + The end\")\n    assert writer.read() == \"Raw message + The end\"\n\n\ndef test_raw_with_format_function(writer):\n    logger.add(writer, format=lambda _: \"{time} \\n\")\n    logger.opt(raw=True).debug(\"Raw {message} bis\", message=\"message\")\n    assert writer.read() == \"Raw message bis\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_raw_with_colors(writer, colorize):\n    logger.add(writer, format=\"XYZ\", colorize=colorize)\n    logger.opt(raw=True, colors=True).info(\"Raw <red>colors</red> and <lvl>level</lvl>\")\n    assert writer.read() == parse(\"Raw <red>colors</red> and <b>level</b>\", strip=not colorize)\n\n\ndef test_args_with_colors_not_formatted_twice(capsys):\n    logger.add(sys.stdout, format=\"{message}\", colorize=True)\n    logger.add(sys.stderr, format=\"{message}\", colorize=False)\n    a = MagicMock(__format__=MagicMock(return_value=\"a\"))\n    b = MagicMock(__format__=MagicMock(return_value=\"b\"))\n\n    logger.opt(colors=True).info(\"{} <red>{foo}</red>\", a, foo=b)\n    out, err = capsys.readouterr()\n    assert out == parse(\"a <red>b</red>\\n\")\n    assert err == \"a b\\n\"\n    assert a.__format__.call_count == 1\n    assert b.__format__.call_count == 1\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_level_tag_wrapping_with_colors(writer, colorize):\n    logger.add(writer, format=\"<level>FOO {message} BAR</level>\", colorize=colorize)\n    logger.opt(colors=True).info(\"> foo <red>{}</> bar <lvl>{}</> baz <green>{}</green> <\", 1, 2, 3)\n    logger.opt(colors=True).log(33, \"<lvl> {} <red>{}</red> {} </lvl>\", 1, 2, 3)\n\n    assert writer.read() == parse(\n        \"<b>FOO > foo <red>1</red> bar <b>2</b> baz <green>3</green> < BAR</b>\\n\"\n        \"<level>FOO <level> 1 <red>2</red> 3 </level> BAR</level>\\n\",\n        strip=not colorize,\n    )\n\n\n@pytest.mark.parametrize(\"dynamic_format\", [True, False])\n@pytest.mark.parametrize(\"colorize\", [True, False])\n@pytest.mark.parametrize(\"colors\", [True, False])\n@pytest.mark.parametrize(\"raw\", [True, False])\n@pytest.mark.parametrize(\"use_log\", [True, False])\n@pytest.mark.parametrize(\"use_arg\", [True, False])\ndef test_all_colors_combinations(writer, dynamic_format, colorize, colors, raw, use_log, use_arg):\n    format_ = \"<level>{level.no:03}</level> <red>{message}</red>\"\n    message = \"<green>The</green> <lvl>{}</lvl>\"\n    arg = \"message\"\n\n    def formatter(_):\n        return format_ + \"\\n\"\n\n    logger.add(writer, format=formatter if dynamic_format else format_, colorize=colorize)\n\n    logger_ = logger.opt(colors=colors, raw=raw)\n\n    if use_log:\n        if use_arg:\n            logger_.log(20, message, arg)\n        else:\n            logger_.log(20, message.format(arg))\n    else:\n        if use_arg:\n            logger_.info(message, arg)\n        else:\n            logger_.info(message.format(arg))\n\n    if use_log:\n        if raw:\n            if colors:\n                expected = parse(\"<green>The</green> <level>message</level>\", strip=not colorize)\n            else:\n                expected = \"<green>The</green> <lvl>message</lvl>\"\n        else:\n            if colors:\n                expected = parse(\n                    \"<level>020</level> <red><green>The</green> <level>message</level></red>\\n\",\n                    strip=not colorize,\n                )\n            else:\n                expected = (\n                    parse(\"<level>020</level> <red>%s</red>\\n\", strip=not colorize)\n                    % \"<green>The</green> <lvl>message</lvl>\"\n                )\n\n    else:\n        if raw:\n            if colors:\n                expected = parse(\"<green>The</green> <b>message</b>\", strip=not colorize)\n            else:\n                expected = \"<green>The</green> <lvl>message</lvl>\"\n        else:\n            if colors:\n                expected = parse(\n                    \"<b>020</b> <red><green>The</green> <b>message</b></red>\\n\", strip=not colorize\n                )\n            else:\n                expected = (\n                    parse(\"<b>020</b> <red>%s</red>\\n\", strip=not colorize)\n                    % \"<green>The</green> <lvl>message</lvl>\"\n                )\n\n    assert writer.read() == expected\n\n\ndef test_raw_with_record(writer):\n    logger.add(writer, format=\"Nope\\n\")\n    logger.opt(raw=True, record=True).debug(\"Raw in '{record[function]}'\\n\")\n    assert writer.read() == \"Raw in 'test_raw_with_record'\\n\"\n\n\ndef test_keep_extra(writer):\n    logger.configure(extra=dict(test=123))\n    logger.add(writer, format=\"{extra[test]}\")\n    logger.opt().debug(\"\")\n    logger.opt().log(50, \"\")\n\n    assert writer.read() == \"123\\n123\\n\"\n\n\ndef test_before_bind(writer):\n    logger.add(writer, format=\"{message}\")\n    logger.opt(record=True).bind(key=\"value\").info(\"{record[level]}\")\n    assert writer.read() == \"INFO\\n\"\n\n\ndef test_deprecated_ansi_argument(writer):\n    logger.add(writer, format=\"{message}\", colorize=True)\n    with pytest.warns(DeprecationWarning, match=r\"The 'ansi' parameter is deprecated\"):\n        logger.opt(ansi=True).info(\"Foo <red>bar</red> baz\")\n    assert writer.read() == parse(\"Foo <red>bar</red> baz\\n\")\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_message_update_not_overridden_by_patch(writer, colors):\n    def patcher(record):\n        record[\"message\"] += \" [Patched]\"\n\n    logger.add(writer, format=\"{level} {message}\", colorize=True)\n    logger.patch(patcher).opt(colors=colors).info(\"Message\")\n\n    assert writer.read() == \"INFO Message [Patched]\\n\"\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_message_update_not_overridden_by_format(writer, colors):\n    def formatter(record):\n        record[\"message\"] += \" [Formatted]\"\n        return \"{level} {message}\\n\"\n\n    logger.add(writer, format=formatter, colorize=True)\n    logger.opt(colors=colors).info(\"Message\")\n\n    assert writer.read() == \"INFO Message [Formatted]\\n\"\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_message_update_not_overridden_by_filter(writer, colors):\n    def filter(record):\n        record[\"message\"] += \" [Filtered]\"\n        return True\n\n    logger.add(writer, format=\"{level} {message}\", filter=filter, colorize=True)\n    logger.opt(colors=colors).info(\"Message\")\n\n    assert writer.read() == \"INFO Message [Filtered]\\n\"\n\n\n@pytest.mark.parametrize(\"colors\", [True, False])\ndef test_message_update_not_overridden_by_raw(writer, colors):\n    logger.add(writer, colorize=True)\n    logger.patch(lambda r: r.update(message=\"Updated!\")).opt(raw=True, colors=colors).info(\"Raw!\")\n    assert writer.read() == \"Updated!\"\n\n\ndef test_overridden_message_ignore_colors(writer):\n    def formatter(record):\n        record[\"message\"] += \" <blue>[Ignored]</blue> </xyz>\"\n        return \"{message}\\n\"\n\n    logger.add(writer, format=formatter, colorize=True)\n    logger.opt(colors=True).info(\"<red>Message</red>\")\n\n    assert writer.read() == \"Message <blue>[Ignored]</blue> </xyz>\\n\"\n"
  },
  {
    "path": "tests/test_parse.py",
    "content": "import io\nimport pathlib\nimport re\nfrom datetime import datetime\n\nimport pytest\n\nfrom loguru import logger\n\nTEXT = \"This\\nIs\\nRandom\\nText\\n123456789\\nABC!DEF\\nThis Is The End\\n\"\n\n\n@pytest.fixture\ndef fileobj():\n    with io.StringIO(TEXT) as file:\n        yield file\n\n\ndef test_parse_file(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(TEXT)\n    result, *_ = list(logger.parse(file, r\"(?P<num>\\d+)\"))\n    assert result == dict(num=\"123456789\")\n\n\ndef test_parse_fileobj(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(TEXT)\n    with open(str(file)) as fileobj:\n        result, *_ = list(logger.parse(fileobj, r\"^(?P<t>\\w+)\"))\n    assert result == dict(t=\"This\")\n\n\ndef test_parse_pathlib(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(TEXT)\n    result, *_ = list(logger.parse(pathlib.Path(str(file)), r\"(?P<r>Random)\"))\n    assert result == dict(r=\"Random\")\n\n\ndef test_parse_string_pattern(fileobj):\n    result, *_ = list(logger.parse(fileobj, r\"(?P<num>\\d+)\"))\n    assert result == dict(num=\"123456789\")\n\n\ndef test_parse_regex_pattern(fileobj):\n    regex = re.compile(r\"(?P<maj>[a-z]*![a-z]*)\", flags=re.I)\n    result, *_ = list(logger.parse(fileobj, regex))\n    assert result == dict(maj=\"ABC!DEF\")\n\n\ndef test_parse_multiline_pattern(fileobj):\n    result, *_ = list(logger.parse(fileobj, r\"(?P<text>This[\\s\\S]*Text\\n)\"))\n    assert result == dict(text=\"This\\nIs\\nRandom\\nText\\n\")\n\n\ndef test_parse_without_group(fileobj):\n    result, *_ = list(logger.parse(fileobj, r\"\\d+\"))\n    assert result == {}\n\n\ndef test_parse_bytes():\n    with io.BytesIO(b\"Testing bytes!\") as fileobj:\n        result, *_ = list(logger.parse(fileobj, rb\"(?P<ponct>[?!:])\"))\n    assert result == dict(ponct=b\"!\")\n\n\n@pytest.mark.parametrize(\"chunk\", [-1, 1, 2**16])\ndef test_chunk(fileobj, chunk):\n    result, *_ = list(logger.parse(fileobj, r\"(?P<a>[ABC]+)\", chunk=chunk))\n    assert result == dict(a=\"ABC\")\n\n\ndef test_positive_lookbehind_pattern():\n    text = \"ab\" * 100\n    pattern = r\"(?<=a)(?P<b>b)\"\n    with io.StringIO(text) as file:\n        result = list(logger.parse(file, pattern, chunk=9))\n    assert result == [dict(b=\"b\")] * 100\n\n\ndef test_greedy_pattern():\n    text = (\"\\n\" + \"a\" * 100) * 1000\n    pattern = r\"\\n(?P<a>a+)\"\n    with io.StringIO(text) as file:\n        result = list(logger.parse(file, pattern, chunk=30))\n    assert result == [dict(a=\"a\" * 100)] * 1000\n\n\ndef test_cast_dict(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"[123] [1.1] [2017-03-29 11:11:11]\\n\")\n    regex = r\"\\[(?P<num>.*)\\] \\[(?P<val>.*)\\] \\[(?P<date>.*)\\]\"\n    caster = dict(num=int, val=float, date=lambda d: datetime.strptime(d, \"%Y-%m-%d %H:%M:%S\"))\n    result = next(logger.parse(file, regex, cast=caster))\n    assert result == dict(num=123, val=1.1, date=datetime(2017, 3, 29, 11, 11, 11))\n\n\ndef test_cast_function(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"[123] [1.1] [2017-03-29 11:11:11]\\n\")\n    regex = r\"\\[(?P<num>.*)\\] \\[(?P<val>.*)\\] \\[(?P<date>.*)\\]\"\n\n    def caster(groups):\n        groups[\"num\"] = int(groups[\"num\"])\n        groups[\"val\"] = float(groups[\"val\"])\n        groups[\"date\"] = datetime.strptime(groups[\"date\"], \"%Y-%m-%d %H:%M:%S\")\n\n    result = next(logger.parse(file, regex, cast=caster))\n    assert result == dict(num=123, val=1.1, date=datetime(2017, 3, 29, 11, 11, 11))\n\n\ndef test_cast_with_irrelevant_arg(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"[123] Blabla\")\n    regex = r\"\\[(?P<a>\\d+)\\] .*\"\n    caster = dict(a=int, b=float)\n    result = next(logger.parse(file, regex, cast=caster))\n    assert result == dict(a=123)\n\n\ndef test_cast_with_irrelevant_value(tmp_path):\n    file = tmp_path / \"test.log\"\n    file.write_text(\"[123] Blabla\")\n    regex = r\"\\[(?P<a>\\d+)\\] (?P<b>.*)\"\n    caster = dict(a=int)\n    result = next(logger.parse(file, regex, cast=caster))\n    assert result == dict(a=123, b=\"Blabla\")\n\n\n@pytest.mark.parametrize(\"file\", [object(), 123, dict])\ndef test_invalid_file(file):\n    with pytest.raises(TypeError):\n        next(logger.parse(file, r\"pattern\"))\n\n\n@pytest.mark.parametrize(\"pattern\", [object(), 123, dict])\ndef test_invalid_pattern(fileobj, pattern):\n    with pytest.raises(TypeError):\n        next(logger.parse(fileobj, pattern))\n\n\n@pytest.mark.parametrize(\"cast\", [object(), 123])\ndef test_invalid_cast(fileobj, cast):\n    with pytest.raises(TypeError):\n        next(logger.parse(fileobj, r\"pattern\", cast=cast))\n"
  },
  {
    "path": "tests/test_patch.py",
    "content": "from loguru import logger\n\n\ndef test_patch_after_add(writer):\n    logger.add(writer, format=\"{extra[a]} {message}\")\n    logger_patched = logger.patch(lambda r: r[\"extra\"].update(a=0))\n    logger_patched.debug(\"A\")\n\n    assert writer.read() == \"0 A\\n\"\n\n\ndef test_patch_before_add(writer):\n    logger_patched = logger.patch(lambda r: r[\"extra\"].update(a=0))\n    logger.add(writer, format=\"{extra[a]} {message}\")\n    logger_patched.debug(\"A\")\n\n    assert writer.read() == \"0 A\\n\"\n\n\ndef test_add_using_patched(writer):\n    logger.configure(patcher=lambda r: r[\"extra\"].update(a=-1))\n    logger_patched = logger.patch(lambda r: r[\"extra\"].update(a=0))\n    logger_patched.add(writer, format=\"{extra[a]} {message}\")\n    logger.debug(\"A\")\n    logger_patched.debug(\"B\")\n\n    assert writer.read() == \"-1 A\\n0 B\\n\"\n\n\ndef test_not_override_parent_logger(writer):\n    logger_1 = logger.patch(lambda r: r[\"extra\"].update(a=\"a\"))\n    logger_2 = logger_1.patch(lambda r: r[\"extra\"].update(a=\"A\"))\n    logger.add(writer, format=\"{extra[a]} {message}\")\n\n    logger_1.debug(\"1\")\n    logger_2.debug(\"2\")\n\n    assert writer.read() == \"a 1\\nA 2\\n\"\n\n\ndef test_override_previous_patched(writer):\n    logger.add(writer, format=\"{extra[x]} {message}\")\n    logger2 = logger.patch(lambda r: r[\"extra\"].update(x=3))\n    logger2.patch(lambda r: r[\"extra\"].update(x=2)).debug(\"4\")\n    assert writer.read() == \"2 4\\n\"\n\n\ndef test_no_conflict(writer):\n    logger_ = logger.patch(lambda r: None)\n    logger_2 = logger_.patch(lambda r: r[\"extra\"].update(a=2))\n    logger_3 = logger_.patch(lambda r: r[\"extra\"].update(a=3))\n\n    logger.add(writer, format=\"{extra[a]} {message}\")\n\n    logger_2.debug(\"222\")\n    logger_3.debug(\"333\")\n\n    assert writer.read() == \"2 222\\n3 333\\n\"\n\n\ndef test_override_configured(writer):\n    logger.configure(patcher=lambda r: r[\"extra\"].update(a=123, b=678))\n    logger2 = logger.patch(lambda r: r[\"extra\"].update(a=456))\n\n    logger2.add(writer, format=\"{extra[a]} {extra[b]} {message}\")\n\n    logger2.debug(\"!\")\n\n    assert writer.read() == \"456 678 !\\n\"\n\n\ndef test_multiple_patches(writer):\n    def patch_1(record):\n        record[\"extra\"][\"a\"] = 5\n\n    def patch_2(record):\n        record[\"extra\"][\"a\"] += 1\n\n    def patch_3(record):\n        record[\"extra\"][\"a\"] *= 2\n\n    logger.add(writer, format=\"{extra[a]} {message}\")\n    logger.patch(patch_1).patch(patch_2).patch(patch_3).info(\"Test\")\n\n    assert writer.read() == \"12 Test\\n\"\n"
  },
  {
    "path": "tests/test_pickling.py",
    "content": "import asyncio\nimport contextlib\nimport datetime\nimport logging\nimport pickle\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import parse\n\n\ndef print_(message):\n    print(message, end=\"\")\n\n\nasync def async_print(msg):\n    print_(msg)\n\n\n@contextlib.contextmanager\ndef copied_logger_though_pickle(logger):\n    pickled = pickle.dumps(logger)\n    unpickled = pickle.loads(pickled)\n    try:\n        yield unpickled\n    finally:\n        unpickled.remove()\n\n\nclass StreamHandler:\n    def __init__(self, flushable=False, stoppable=False):\n        if flushable:\n            self.flush = self._flush\n        if stoppable:\n            self.stop = self._stop\n\n        self.wrote = \"\"\n        self.flushed = False\n        self.stopped = False\n\n    def write(self, message):\n        self.wrote += message\n\n    def _flush(self):\n        self.flushed = True\n\n    def _stop(self):\n        self.stopped = True\n\n\nclass MockLock:\n    def __enter__(self):\n        pass\n\n    def __exit__(self, *excinfo):\n        pass\n\n\nclass StandardHandler(logging.Handler):\n    def __init__(self, level):\n        super().__init__(level)\n        self.written = \"\"\n\n    def emit(self, record):\n        self.written += record.getMessage()\n\n    def acquire(self):\n        pass\n\n    def release(self):\n        pass\n\n    def createLock(self):  # noqa: N802\n        self.lock = MockLock()\n\n\ndef format_function(record):\n    return \"-> <red>{message}</red>\"\n\n\ndef filter_function(record):\n    return \"[PASS]\" in record[\"message\"]\n\n\ndef patch_function(record):\n    record[\"extra\"][\"foo\"] = \"bar\"\n\n\ndef rotation_function(message, file):\n    pass\n\n\ndef retention_function(files):\n    pass\n\n\ndef compression_function(path):\n    pass\n\n\ndef test_pickling_function_handler(capsys):\n    logger.add(print_, format=\"{level} - {function} - {message}\")\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n    out, err = capsys.readouterr()\n    assert out == \"DEBUG - test_pickling_function_handler - A message\\n\"\n    assert err == \"\"\n\n\ndef test_pickling_coroutine_function_handler(capsys):\n    logger.add(async_print, format=\"{level} - {function} - {message}\")\n\n    with copied_logger_though_pickle(logger) as dupe_logger:\n\n        async def async_debug():\n            dupe_logger.debug(\"A message\")\n            await dupe_logger.complete()\n\n        asyncio.run(async_debug())\n\n    out, err = capsys.readouterr()\n    assert out == \"DEBUG - async_debug - A message\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"flushable\", [True, False])\n@pytest.mark.parametrize(\"stoppable\", [True, False])\ndef test_pickling_stream_handler(flushable, stoppable):\n    stream = StreamHandler(flushable, stoppable)\n    logger.add(stream, format=\"{level} - {function} - {message}\")\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        stream = next(iter(dupe_logger._core.handlers.values()))._sink._stream\n    assert stream.wrote == \"DEBUG - test_pickling_stream_handler - A message\\n\"\n    assert stream.flushed == flushable\n    assert stream.stopped == stoppable\n\n\ndef test_pickling_standard_handler():\n    handler = StandardHandler(logging.NOTSET)\n    logger.add(handler, format=\"{level} - {function} - {message}\")\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        handler = next(iter(dupe_logger._core.handlers.values()))._sink._handler\n        assert handler.written == \"DEBUG - test_pickling_standard_handler - A message\"\n\n\ndef test_pickling_standard_handler_root_logger_not_picklable(monkeypatch, capsys):\n    def reduce_protocol():\n        raise TypeError(\"Not picklable\")\n\n    with monkeypatch.context() as context:\n        context.setattr(logging.getLogger(), \"__reduce__\", reduce_protocol, raising=False)\n\n        handler = StandardHandler(logging.NOTSET)\n        logger.add(handler, format=\"=> {message}\", catch=False)\n\n        with copied_logger_though_pickle(logger) as dupe_logger:\n            logger.info(\"Ok\")\n            dupe_logger.info(\"Ok\")\n            out, err = capsys.readouterr()\n            assert out == \"\"\n            assert err == \"\"\n            assert handler.written == \"=> Ok\"\n\n\ndef test_pickling_file_handler(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{level} - {function} - {message}\", delay=True)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        assert file.read_text() == \"DEBUG - test_pickling_file_handler - A message\\n\"\n\n\n@pytest.mark.parametrize(\n    \"rotation\",\n    [\n        1000,\n        \"daily\",\n        datetime.timedelta(minutes=60),\n        datetime.time(hour=12, minute=00, second=00),\n        \"200 MB\",\n        \"10:00\",\n        \"5 hours\",\n        rotation_function,\n    ],\n)\ndef test_pickling_file_handler_rotation(tmp_path, rotation):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{level} - {function} - {message}\", delay=True, rotation=rotation)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        assert file.read_text() == \"DEBUG - test_pickling_file_handler_rotation - A message\\n\"\n\n\n@pytest.mark.parametrize(\n    \"retention\", [1000, datetime.timedelta(hours=13), \"10 days\", retention_function]\n)\ndef test_pickling_file_handler_retention(tmp_path, retention):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{level} - {function} - {message}\", delay=True, retention=retention)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        assert file.read_text() == \"DEBUG - test_pickling_file_handler_retention - A message\\n\"\n\n\n@pytest.mark.parametrize(\"compression\", [\"zip\", \"gz\", \"tar\", compression_function])\ndef test_pickling_file_handler_compression(tmp_path, compression):\n    file = tmp_path / \"test.log\"\n    logger.add(file, format=\"{level} - {function} - {message}\", delay=True, compression=compression)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.debug(\"A message\")\n        assert file.read_text() == \"DEBUG - test_pickling_file_handler_compression - A message\\n\"\n\n\ndef test_pickling_no_handler(writer):\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.add(writer, format=\"{level} - {function} - {message}\")\n        dupe_logger.debug(\"A message\")\n        assert writer.read() == \"DEBUG - test_pickling_no_handler - A message\\n\"\n\n\ndef test_pickling_handler_not_serializable():\n    logger.add(lambda m: None)\n    with pytest.raises((pickle.PicklingError, AttributeError), match=\"Can't (pickle|get local)\"):\n        pickle.dumps(logger)\n\n\ndef test_pickling_filter_function(capsys):\n    logger.add(print_, format=\"{message}\", filter=filter_function)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.info(\"Nope\")\n        dupe_logger.info(\"[PASS] Yes\")\n    out, err = capsys.readouterr()\n    assert out == \"[PASS] Yes\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"filter\", [\"\", \"tests\"])\ndef test_pickling_filter_name(capsys, filter):\n    logger.add(print_, format=\"{message}\", filter=filter)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.info(\"A message\")\n    out, err = capsys.readouterr()\n    assert out == \"A message\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_pickling_format_string(capsys, colorize):\n    logger.add(print_, format=\"-> <red>{message}</red>\", colorize=colorize)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.info(\"The message\")\n    out, err = capsys.readouterr()\n    assert out == parse(\"-> <red>The message</red>\\n\", strip=not colorize)\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"colorize\", [True, False])\ndef test_pickling_format_function(capsys, colorize):\n    logger.add(print_, format=format_function, colorize=colorize)\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.info(\"The message\")\n    out, err = capsys.readouterr()\n    assert out == parse(\"-> <red>The message</red>\", strip=not colorize)\n    assert err == \"\"\n\n\ndef test_pickling_filter_function_not_serializable():\n    logger.add(print, filter=lambda r: True)\n    with pytest.raises((pickle.PicklingError, AttributeError), match=\"Can't (pickle|get local)\"):\n        pickle.dumps(logger)\n\n\ndef test_pickling_format_function_not_serializable():\n    logger.add(print, format=lambda r: \"{message}\")\n    with pytest.raises((pickle.PicklingError, AttributeError), match=\"Can't (pickle|get local)\"):\n        pickle.dumps(logger)\n\n\ndef test_pickling_bound_logger(writer):\n    bound_logger = logger.bind(foo=\"bar\")\n    with copied_logger_though_pickle(bound_logger) as dupe_logger:\n        dupe_logger.add(writer, format=\"{extra[foo]}\")\n        dupe_logger.info(\"Test\")\n        assert writer.read() == \"bar\\n\"\n\n\ndef test_pickling_patched_logger(writer):\n    patched_logger = logger.patch(patch_function)\n    with copied_logger_though_pickle(patched_logger) as dupe_logger:\n        dupe_logger.add(writer, format=\"{extra[foo]}\")\n        dupe_logger.info(\"Test\")\n        assert writer.read() == \"bar\\n\"\n\n\ndef test_remove_after_pickling(capsys):\n    i = logger.add(print_, format=\"{message}\")\n    logger.info(\"A\")\n    with copied_logger_though_pickle(logger) as dupe_logger:\n        dupe_logger.remove(i)\n        dupe_logger.info(\"B\")\n    out, err = capsys.readouterr()\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_pickling_logging_method(capsys):\n    logger.add(print_, format=\"{level} - {function} - {message}\")\n    pickled = pickle.dumps(logger.critical)\n    func = pickle.loads(pickled)\n    func(\"A message\")\n    out, err = capsys.readouterr()\n    assert out == \"CRITICAL - test_pickling_logging_method - A message\\n\"\n    assert err == \"\"\n\n\ndef test_pickling_log_method(capsys):\n    logger.add(print_, format=\"{level} - {function} - {message}\")\n    pickled = pickle.dumps(logger.log)\n    func = pickle.loads(pickled)\n    func(19, \"A message\")\n    out, err = capsys.readouterr()\n    assert out == \"Level 19 - test_pickling_log_method - A message\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\n    \"method\",\n    [\n        logger.add,\n        logger.remove,\n        logger.catch,\n        logger.opt,\n        logger.bind,\n        logger.patch,\n        logger.level,\n        logger.disable,\n        logger.enable,\n        logger.configure,\n        logger.parse,\n        logger.exception,\n    ],\n)\ndef test_pickling_no_error(method):\n    pickled = pickle.dumps(method)\n    unpickled = pickle.loads(pickled)\n    assert unpickled\n"
  },
  {
    "path": "tests/test_propagation.py",
    "content": "import logging\nimport sys\nfrom logging import StreamHandler\n\nimport pytest\n\nfrom loguru import logger\n\nfrom .conftest import make_logging_logger\n\n\nclass PropagateHandler(logging.Handler):\n    def emit(self, record):\n        logging.getLogger(record.name).handle(record)\n\n\ndef test_formatting(capsys):\n    fmt = (\n        \"%(name)s - %(filename)s - %(funcName)s - %(levelname)s - \"\n        \"%(levelno)s - %(lineno)d - %(module)s - %(message)s\"\n    )\n\n    expected = (\n        \"tests.test_propagation - test_propagation.py - test_formatting - DEBUG - \"\n        \"10 - 30 - test_propagation - This is my message\\n\"\n    )\n\n    with make_logging_logger(\"tests.test_propagation\", StreamHandler(sys.stderr), fmt):\n        logger.add(PropagateHandler(), format=\"{message}\")\n        logger.debug(\"This {verb} my {}\", \"message\", verb=\"is\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == expected\n\n\ndef test_propagate(capsys):\n    with make_logging_logger(\"tests\", StreamHandler(sys.stderr)) as logging_logger:\n        logging_logger.debug(\"1\")\n        logger.debug(\"2\")\n\n        logger.add(PropagateHandler(), format=\"{message}\")\n\n        logger.debug(\"3\")\n        logger.trace(\"4\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"1\\n3\\n\"\n\n\ndef test_remove_propagation(capsys):\n    with make_logging_logger(\"tests\", StreamHandler(sys.stderr)) as logging_logger:\n        i = logger.add(PropagateHandler(), format=\"{message}\")\n\n        logger.debug(\"1\")\n        logging_logger.debug(\"2\")\n\n        logger.remove(i)\n\n        logger.debug(\"3\")\n        logging_logger.debug(\"4\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"1\\n2\\n4\\n\"\n\n\ndef test_propagate_too_high(capsys):\n    with make_logging_logger(\n        \"tests.test_propagation.deep\", StreamHandler(sys.stderr)\n    ) as logging_logger:\n        logger.add(PropagateHandler(), format=\"{message}\")\n        logger.debug(\"1\")\n        logging_logger.debug(\"2\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"2\\n\"\n\n\n@pytest.mark.parametrize(\"use_opt\", [False, True])\ndef test_exception(capsys, use_opt):\n    with make_logging_logger(\"tests\", StreamHandler(sys.stderr)):\n        logger.add(PropagateHandler(), format=\"{message}\")\n\n        try:\n            1 / 0  # noqa: B018\n        except Exception:\n            if use_opt:\n                logger.opt(exception=True).error(\"Oops...\")\n            else:\n                logger.exception(\"Oops...\")\n\n    out, err = capsys.readouterr()\n    lines = err.strip().splitlines()\n\n    error = \"ZeroDivisionError: division by zero\"\n\n    assert out == \"\"\n    assert lines[0] == \"Oops...\"\n    assert lines[-1] == error\n    assert err.count(error) == 1\n"
  },
  {
    "path": "tests/test_recattr.py",
    "content": "import re\n\nimport loguru._recattrs as recattrs\nfrom loguru import logger\n\n\ndef test_patch_record_file(writer):\n    def patch(record):\n        record[\"file\"].name = \"456\"\n        record[\"file\"].path = \"123/456\"\n\n    logger.add(writer, format=\"{file} {file.name} {file.path}\")\n    logger.patch(patch).info(\"Test\")\n\n    assert writer.read() == \"456 456 123/456\\n\"\n\n\ndef test_patch_record_thread(writer):\n    def patch(record):\n        record[\"thread\"].id = 111\n        record[\"thread\"].name = \"Thread-111\"\n\n    logger.add(writer, format=\"{thread} {thread.name} {thread.id}\")\n    logger.patch(patch).info(\"Test\")\n\n    assert writer.read() == \"111 Thread-111 111\\n\"\n\n\ndef test_patch_record_process(writer):\n    def patch(record):\n        record[\"process\"].id = 123\n        record[\"process\"].name = \"Process-123\"\n\n    logger.add(writer, format=\"{process} {process.name} {process.id}\")\n    logger.patch(patch).info(\"Test\")\n\n    assert writer.read() == \"123 Process-123 123\\n\"\n\n\ndef test_patch_record_exception(writer):\n    def patch(record):\n        type_, value, traceback = record[\"exception\"]\n        record[\"exception\"] = (type_, value, None)\n\n    logger.add(writer, format=\"\")\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.patch(patch).exception(\"Error\")\n\n    assert writer.read() == \"\\nZeroDivisionError: division by zero\\n\"\n\n\ndef test_level_repr():\n    level = recattrs.RecordLevel(\"FOO\", 123, \"!!\")\n    assert repr(level) == \"(name='FOO', no=123, icon='!!')\"\n\n\ndef test_file_repr():\n    file_ = recattrs.RecordFile(\"foo.txt\", \"path/foo.txt\")\n    assert repr(file_) == \"(name='foo.txt', path='path/foo.txt')\"\n\n\ndef test_thread_repr():\n    thread = recattrs.RecordThread(98765, \"thread-1\")\n    assert repr(thread) == \"(id=98765, name='thread-1')\"\n\n\ndef test_process_repr():\n    process = recattrs.RecordProcess(12345, \"process-1\")\n    assert repr(process) == \"(id=12345, name='process-1')\"\n\n\ndef test_exception_repr():\n    exception = recattrs.RecordException(ValueError, ValueError(\"Nope\"), None)\n    regex = r\"\\(type=<class 'ValueError'>, value=ValueError\\('Nope',?\\), traceback=None\\)\"\n    assert re.fullmatch(regex, repr(exception))\n"
  },
  {
    "path": "tests/test_reinstall.py",
    "content": "import multiprocessing\nimport os\n\nimport pytest\n\nfrom loguru import logger\n\n\n@pytest.fixture\ndef fork_context():\n    return multiprocessing.get_context(\"fork\")\n\n\n@pytest.fixture\ndef spawn_context():\n    return multiprocessing.get_context(\"spawn\")\n\n\nclass Writer:\n    def __init__(self):\n        self._output = \"\"\n\n    def write(self, message):\n        self._output += message\n\n    def read(self):\n        return self._output\n\n\ndef subworker(logger):\n    logger.reinstall()\n    logger.info(\"Child\")\n    deeper_subworker()\n\n\ndef poolworker(_):\n    logger.info(\"Child\")\n    deeper_subworker()\n\n\ndef deeper_subworker():\n    logger.info(\"Grandchild\")\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_process_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = fork_context.Process(target=subworker, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nGrandchild\\nMain\\n\"\n\n\ndef test_process_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    process = spawn_context.Process(target=subworker, args=(logger,))\n    process.start()\n    process.join()\n\n    assert process.exitcode == 0\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nGrandchild\\nMain\\n\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"Windows does not support forking\")\ndef test_pool_fork(fork_context):\n    writer = Writer()\n\n    logger.add(writer, context=fork_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with fork_context.Pool(1, initializer=logger.reinstall) as pool:\n        pool.map(poolworker, [None])\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nGrandchild\\nMain\\n\"\n\n\ndef test_pool_spawn(spawn_context):\n    writer = Writer()\n\n    logger.add(writer, context=spawn_context, format=\"{message}\", enqueue=True, catch=False)\n\n    with spawn_context.Pool(1, initializer=logger.reinstall) as pool:\n        pool.map(poolworker, [None])\n\n    logger.info(\"Main\")\n    logger.remove()\n\n    assert writer.read() == \"Child\\nGrandchild\\nMain\\n\"\n"
  },
  {
    "path": "tests/test_remove.py",
    "content": "import sys\nimport time\n\nimport pytest\n\nfrom loguru import logger\n\n\nclass StopSinkError:\n    def write(self, message):\n        print(message, end=\"\")\n\n    def stop(self):\n        raise OSError(\"Stop error\")\n\n\ndef test_remove_all(tmp_path, writer, capsys):\n    file = tmp_path / \"test.log\"\n\n    logger.debug(\"This shouldn't be printed.\")\n\n    logger.add(file, format=\"{message}\")\n    logger.add(sys.stdout, format=\"{message}\")\n    logger.add(sys.stderr, format=\"{message}\")\n    logger.add(writer, format=\"{message}\")\n\n    message = \"some message\"\n    expected = message + \"\\n\"\n\n    logger.debug(message)\n\n    logger.remove()\n\n    logger.debug(\"This shouldn't be printed neither.\")\n\n    out, err = capsys.readouterr()\n\n    assert file.read_text() == expected\n    assert out == expected\n    assert err == expected\n    assert writer.read() == expected\n\n\ndef test_remove_simple(writer):\n    i = logger.add(writer, format=\"{message}\")\n    logger.debug(\"1\")\n    logger.remove(i)\n    logger.debug(\"2\")\n    assert writer.read() == \"1\\n\"\n\n\ndef test_remove_enqueue(writer):\n    i = logger.add(writer, format=\"{message}\", enqueue=True)\n    logger.debug(\"1\")\n    time.sleep(0.1)\n    logger.remove(i)\n    logger.debug(\"2\")\n    assert writer.read() == \"1\\n\"\n\n\ndef test_remove_enqueue_filesink(tmp_path):\n    file = tmp_path / \"test.log\"\n    i = logger.add(file, format=\"{message}\", enqueue=True)\n    logger.debug(\"1\")\n    logger.remove(i)\n    assert file.read_text() == \"1\\n\"\n\n\ndef test_exception_in_stop_during_remove_one(capsys):\n    i = logger.add(StopSinkError(), catch=False, format=\"{message}\")\n    logger.info(\"A\")\n    with pytest.raises(OSError, match=r\"Stop error\"):\n        logger.remove(i)\n    logger.info(\"Nope\")\n\n    out, err = capsys.readouterr()\n\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_exception_in_stop_not_caught_during_remove_all(capsys):\n    logger.add(StopSinkError(), catch=False, format=\"{message}\")\n    logger.add(StopSinkError(), catch=False, format=\"{message}\")\n\n    with pytest.raises(OSError, match=r\"Stop error\"):\n        logger.remove()\n\n    logger.info(\"A\")\n\n    with pytest.raises(OSError, match=r\"Stop error\"):\n        logger.remove()\n\n    logger.info(\"Nope\")\n\n    out, err = capsys.readouterr()\n\n    assert out == \"A\\n\"\n    assert err == \"\"\n\n\ndef test_invalid_handler_id_value(writer):\n    logger.add(writer)\n\n    with pytest.raises(ValueError, match=r\"^There is no existing handler.*\"):\n        logger.remove(42)\n\n\n@pytest.mark.parametrize(\"handler_id\", [sys.stderr, sys, object(), int])\ndef test_invalid_handler_id_type(handler_id):\n    with pytest.raises(TypeError, match=r\"^Invalid handler id.*\"):\n        logger.remove(handler_id)\n"
  },
  {
    "path": "tests/test_repr.py",
    "content": "import logging\nimport pathlib\nimport re\nimport sys\n\nfrom loguru import logger\n\n\ndef test_no_handler():\n    assert repr(logger) == \"<loguru.logger handlers=[]>\"\n\n\ndef test_stderr():\n    logger.add(sys.__stderr__)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<stderr>)]>\"\n\n\ndef test_stdout():\n    logger.add(sys.__stdout__)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<stdout>)]>\"\n\n\ndef test_file_object(tmp_path):\n    path = str(tmp_path / \"test.log\")\n    with open(path, \"w\") as file:\n        logger.add(file)\n        assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=%s)]>\" % path\n\n\ndef test_file_str(tmp_path):\n    path = str(tmp_path / \"test.log\")\n    logger.add(path)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink='%s')]>\" % path\n\n\ndef test_file_pathlib(tmp_path):\n    path = str(tmp_path / \"test.log\")\n    logger.add(pathlib.Path(path))\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink='%s')]>\" % path\n\n\ndef test_stream_object():\n    class MyStream:\n        def __init__(self, name):\n            self.name = name\n\n        def write(self, m):\n            pass\n\n        def __repr__(self):\n            return \"MyStream()\"\n\n    logger.add(MyStream(\"<foobar>\"))\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<foobar>)]>\"\n\n\ndef test_stream_object_without_name_attr():\n    class MyStream:\n        def write(self, m):\n            pass\n\n        def __repr__(self):\n            return \"MyStream()\"\n\n    logger.add(MyStream())\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=MyStream())]>\"\n\n\ndef test_stream_object_with_empty_name():\n    class MyStream2:\n        def __init__(self):\n            self.name = \"\"\n\n        def write(self, message):\n            pass\n\n        def __repr__(self):\n            return \"MyStream2()\"\n\n    logger.add(MyStream2())\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=MyStream2())]>\"\n\n\ndef test_function():\n    def my_function(message):\n        pass\n\n    logger.add(my_function)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=my_function)]>\"\n\n\ndef test_callable_without_name():\n    class Function:\n        def __call__(self):\n            pass\n\n        def __repr__(self):\n            return \"<FunctionWithout>\"\n\n    logger.add(Function())\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<FunctionWithout>)]>\"\n\n\ndef test_callable_with_empty_name():\n    class Function:\n        __name__ = \"\"\n\n        def __call__(self):\n            pass\n\n        def __repr__(self):\n            return \"<FunctionEmpty>\"\n\n    logger.add(Function())\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<FunctionEmpty>)]>\"\n\n\ndef test_coroutine_function():\n    async def my_async_function(message):\n        pass\n\n    logger.add(my_async_function)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=my_async_function)]>\"\n\n\ndef test_coroutine_callable_without_name():\n    class CoroutineFunction:\n        async def __call__(self):\n            pass\n\n        def __repr__(self):\n            return \"<AsyncFunctionWithout>\"\n\n    logger.add(CoroutineFunction())\n    assert (\n        repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<AsyncFunctionWithout>)]>\"\n    )\n\n\ndef test_coroutine_function_with_empty_name():\n    class CoroutineFunction:\n        __name__ = \"\"\n\n        def __call__(self):\n            pass\n\n        def __repr__(self):\n            return \"<AsyncFunctionEmpty>\"\n\n    logger.add(CoroutineFunction())\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=10, sink=<AsyncFunctionEmpty>)]>\"\n\n\ndef test_standard_handler():\n    handler = logging.StreamHandler(sys.__stderr__)\n    logger.add(handler)\n    if sys.version_info >= (3, 6):\n        r = \"<loguru.logger handlers=[(id=0, level=10, sink=<StreamHandler <stderr> (NOTSET)>)]>\"\n        assert repr(logger) == r\n    else:\n        r = r\"<loguru\\.logger handlers=\\[\\(id=0, level=10, sink=<logging\\.StreamHandler .*>\\)\\]>\"\n        assert re.match(r, repr(logger))\n\n\ndef test_multiple_handlers():\n    logger.add(sys.__stdout__)\n    logger.add(sys.__stderr__)\n    r = (\n        \"<loguru.logger handlers=[\"\n        \"(id=0, level=10, sink=<stdout>), \"\n        \"(id=1, level=10, sink=<stderr>)\"\n        \"]>\"\n    )\n    assert repr(logger) == r\n\n\ndef test_handler_removed():\n    i = logger.add(sys.__stdout__)\n    logger.add(sys.__stderr__)\n    logger.remove(i)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=1, level=10, sink=<stderr>)]>\"\n\n\ndef test_handler_level_name():\n    logger.add(sys.__stderr__, level=\"TRACE\")\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=5, sink=<stderr>)]>\"\n\n\ndef test_handler_level_num():\n    logger.add(sys.__stderr__, level=33)\n    assert repr(logger) == \"<loguru.logger handlers=[(id=0, level=33, sink=<stderr>)]>\"\n"
  },
  {
    "path": "tests/test_standard_handler.py",
    "content": "import sys\nfrom logging import FileHandler, Filter, Formatter, Handler, NullHandler, StreamHandler\n\nimport pytest\n\nfrom loguru import logger\n\n\nclass RejectAllFilter(Filter):\n    def filter(self, record):\n        return False\n\n\ndef test_stream_handler(capsys):\n    logger.add(StreamHandler(sys.stderr), format=\"{level} {message}\")\n    logger.info(\"test\")\n    logger.remove()\n    logger.warning(\"nope\")\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"INFO test\\n\"\n\n\ndef test_file_handler(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(FileHandler(str(file)), format=\"{message} {level.name}\")\n    logger.info(\"test\")\n    logger.remove()\n    logger.warning(\"nope\")\n\n    assert file.read_text() == \"test INFO\\n\"\n\n\ndef test_null_handler(capsys):\n    logger.add(NullHandler())\n    logger.error(\"nope\")\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_extra_dict(capsys):\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(extra)s %(message)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=\"<{extra[abc]}> {message}\", catch=False)\n    logger.bind(abc=123).info(\"Extra!\")\n    out, err = capsys.readouterr()\n    assert out == \"{'abc': 123} <123> Extra!\\n\"\n    assert err == \"\"\n\n\ndef test_no_conflict_with_extra_dict(capsys):\n    handler = StreamHandler(sys.stdout)\n    logger.add(handler, format=\"{message}\", catch=False)\n    logger.bind(args=True, name=\"foobar\", message=\"Wut?\").info(\"OK!\")\n    out, err = capsys.readouterr()\n    assert out == \"OK!\\n\"\n    assert err == \"\"\n\n\ndef test_no_exception():\n    result = None\n\n    class NoExceptionHandler(Handler):\n        def emit(self, record):\n            nonlocal result\n            result = bool(not record.exc_info)\n\n    logger.add(NoExceptionHandler())\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error\")\n\n    assert result is False\n\n\ndef test_exception(capsys):\n    result = None\n\n    class ExceptionHandler(Handler):\n        def emit(self, record):\n            nonlocal result\n            result = bool(record.exc_info)\n\n    logger.add(ExceptionHandler())\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error\")\n\n    assert result is True\n\n\ndef test_exception_formatting(tmp_path):\n    file = tmp_path / \"test.log\"\n    logger.add(FileHandler(str(file)), format=\"{message}\")\n\n    try:\n        1 / 0  # noqa: B018\n    except ZeroDivisionError:\n        logger.exception(\"Error\")\n\n    result = file.read_text()\n    lines = result.strip().splitlines()\n\n    error = \"ZeroDivisionError: division by zero\"\n\n    assert lines[1].startswith(\"Traceback\")\n    assert lines[-1] == error\n    assert result.count(error) == 1\n\n\n@pytest.mark.parametrize(\"dynamic_format\", [False, True])\ndef test_standard_formatter(capsys, dynamic_format):\n    def format_(x):\n        return \"{level.no} {message} [Not Chopped]\"\n\n    if not dynamic_format:\n        format_ = format_(None)\n\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(message)s %(levelname)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=format_)\n    logger.info(\"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"20 Test [Not Chopped] INFO\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"dynamic_format\", [False, True])\ndef test_standard_formatter_with_new_line(capsys, dynamic_format):\n    def format_(x):\n        return \"{level.no} {message}\\n\"\n\n    if not dynamic_format:\n        format_ = format_(None)\n\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(message)s %(levelname)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=format_)\n    logger.info(\"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"20 Test\\n INFO\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"dynamic_format\", [False, True])\ndef test_raw_standard_formatter(capsys, dynamic_format):\n    def format_(x):\n        return \"{level.no} {message} [Not Chopped]\"\n\n    if not dynamic_format:\n        format_ = format_(None)\n\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(message)s %(levelname)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=format_)\n    logger.opt(raw=True).info(\"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"Test INFO\\n\"\n    assert err == \"\"\n\n\n@pytest.mark.parametrize(\"dynamic_format\", [False, True])\ndef test_raw_standard_formatter_with_new_line(capsys, dynamic_format):\n    def format_(x):\n        return \"{level.no} {message}\\n\"\n\n    if not dynamic_format:\n        format_ = format_(None)\n\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(message)s %(levelname)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=format_)\n    logger.opt(raw=True).info(\"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"Test INFO\\n\"\n    assert err == \"\"\n\n\ndef test_standard_formatter_with_non_standard_level_name(capsys):\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(levelno)s | %(levelname)s | %(message)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=\"{message}\")\n    logger.success(\"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"25 | SUCCESS | Test\\n\"\n    assert err == \"\"\n\n\ndef test_standard_formatter_with_custom_level_name(capsys):\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(levelno)s | %(levelname)s | %(message)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=\"{message}\")\n    logger.level(\"CUSTOM\", no=35)\n    logger.log(\"CUSTOM\", \"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"35 | CUSTOM | Test\\n\"\n    assert err == \"\"\n\n\ndef test_standard_formatter_with_unregistered_level(capsys):\n    handler = StreamHandler(sys.stdout)\n    formatter = Formatter(\"%(levelno)s | %(levelname)s | %(message)s\")\n    handler.setFormatter(formatter)\n    logger.add(handler, format=\"{message}\")\n    logger.log(45, \"Test\")\n    out, err = capsys.readouterr()\n    assert out == \"45 | Level 45 | Test\\n\"\n    assert err == \"\"\n\n\ndef test_standard_handler_with_configured_filter(capsys):\n    handler = StreamHandler(sys.stdout)\n    filter_ = RejectAllFilter()\n    logger.add(handler, format=\"{message}\")\n    logger.info(\"a\")\n    handler.addFilter(filter_)\n    logger.info(\"b\")\n    handler.removeFilter(filter_)\n    logger.info(\"c\")\n    out, err = capsys.readouterr()\n    assert out == \"a\\nc\\n\"\n    assert err == \"\"\n\n\ndef test_standard_handler_with_configured_level(capsys):\n    handler = StreamHandler(sys.stdout)\n    logger.add(handler, format=\"{message}\")\n    logger.info(\"a\")\n    handler.setLevel(\"WARNING\")\n    logger.info(\"b\")\n    handler.setLevel(\"INFO\")\n    logger.info(\"c\")\n    out, err = capsys.readouterr()\n    assert out == \"a\\nc\\n\"\n    assert err == \"\"\n"
  },
  {
    "path": "tests/test_threading.py",
    "content": "import itertools\nimport time\nfrom threading import Barrier, Thread\n\nfrom loguru import logger\n\n\nclass NonSafeSink:\n    def __init__(self, sleep_time, stop_time=0):\n        self.sleep_time = sleep_time\n        self.stop_time = stop_time\n        self.written = \"\"\n        self.stopped = False\n\n    def write(self, message):\n        if self.stopped:\n            raise RuntimeError(\"Can't write on stopped sink\")\n\n        length = len(message)\n        self.written += message[:length]\n        time.sleep(self.sleep_time)\n        self.written += message[length:]\n\n    def stop(self):\n        time.sleep(self.stop_time)\n        self.stopped = True\n\n\ndef test_safe_logging():\n    barrier = Barrier(2)\n    counter = itertools.count()\n\n    sink = NonSafeSink(1)\n    logger.add(sink, format=\"{message}\", catch=False)\n\n    def threaded():\n        barrier.wait()\n        logger.info(\"___{}___\", next(counter))\n\n    threads = [Thread(target=threaded) for _ in range(2)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    logger.remove()\n\n    assert sink.written in (\"___0___\\n___1___\\n\", \"___1___\\n___0___\\n\")\n\n\ndef test_safe_adding_while_logging(writer):\n    barrier = Barrier(2)\n    counter = itertools.count()\n\n    sink_1 = NonSafeSink(1)\n    sink_2 = NonSafeSink(1)\n    logger.add(sink_1, format=\"{message}\", catch=False)\n\n    def thread_1():\n        barrier.wait()\n        logger.info(\"aaa{}bbb\", next(counter))\n\n    def thread_2():\n        barrier.wait()\n        time.sleep(0.5)\n        logger.add(sink_2, format=\"{message}\", catch=False)\n        logger.info(\"ccc{}ddd\", next(counter))\n\n    threads = [Thread(target=thread_1), Thread(target=thread_2)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    logger.remove()\n\n    assert sink_1.written == \"aaa0bbb\\nccc1ddd\\n\"\n    assert sink_2.written == \"ccc1ddd\\n\"\n\n\ndef test_safe_removing_while_logging(capsys):\n    barrier = Barrier(2)\n    counter = itertools.count()\n\n    sink = NonSafeSink(1)\n    i = logger.add(sink, format=\"{message}\", catch=False)\n\n    def thread_1():\n        barrier.wait()\n        logger.info(\"aaa{}bbb\", next(counter))\n\n    def thread_2():\n        barrier.wait()\n        time.sleep(0.5)\n        logger.remove(i)\n        logger.info(\"ccc{}ddd\", next(counter))\n\n    threads = [Thread(target=thread_1), Thread(target=thread_2)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n    assert sink.written == \"aaa0bbb\\n\"\n\n\ndef test_safe_removing_all_while_logging(capsys):\n    barrier = Barrier(2)\n\n    for _ in range(1000):\n        logger.add(lambda _: None, format=\"{message}\", catch=False)\n\n    def thread_1():\n        barrier.wait()\n        logger.remove()\n\n    def thread_2():\n        barrier.wait()\n        for _ in range(100):\n            logger.info(\"Some message\")\n\n    threads = [Thread(target=thread_1), Thread(target=thread_2)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_safe_slow_removing_all_while_logging(capsys):\n    barrier = Barrier(2)\n\n    for _ in range(10):\n        sink = NonSafeSink(0.1, 0.1)\n        logger.add(sink, format=\"{message}\", catch=False)\n\n    def thread_1():\n        barrier.wait()\n        logger.remove()\n\n    def thread_2():\n        barrier.wait()\n        time.sleep(0.5)\n        logger.info(\"Some message\")\n\n    threads = [Thread(target=thread_1), Thread(target=thread_2)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_safe_writing_after_removing(capsys):\n    barrier = Barrier(2)\n\n    logger.add(NonSafeSink(1), format=\"{message}\", catch=False)\n    i = logger.add(NonSafeSink(1), format=\"{message}\", catch=False)\n\n    def write():\n        barrier.wait()\n        logger.info(\"Writing\")\n\n    def remove():\n        barrier.wait()\n        time.sleep(0.5)\n        logger.remove(i)\n\n    threads = [Thread(target=write), Thread(target=remove)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n\n\ndef test_heavily_threaded_logging(capsys):\n    logger.remove()\n\n    def function():\n        i = logger.add(NonSafeSink(0.1), format=\"{message}\", catch=False)\n        logger.debug(\"AAA\")\n        logger.info(\"BBB\")\n        logger.success(\"CCC\")\n        logger.remove(i)\n\n    threads = [Thread(target=function) for _ in range(10)]\n\n    for thread in threads:\n        thread.start()\n\n    for thread in threads:\n        thread.join()\n\n    logger.remove()\n\n    out, err = capsys.readouterr()\n    assert out == \"\"\n    assert err == \"\"\n"
  },
  {
    "path": "tests/test_type_hinting.py",
    "content": "import sys\n\nimport mypy.api\n\n\ndef test_mypy_import():\n    # Check stub file is valid and can be imported by Mypy.\n    # There exist others tests in \"typesafety\" subfolder but they aren't compatible with Python 3.5.\n    out, _, result = mypy.api.run([\"--strict\", \"-c\", \"from loguru import logger\"])\n    print(\"\".join(out), file=sys.stderr)\n    assert result == 0\n"
  },
  {
    "path": "tests/typesafety/test_logger.yml",
    "content": "# Note that tests defined in this file are slightly modified by \"conftest.py\". In particular, a\n# specific Mypy configuration is applied to each test case to ensure that the output is consistent\n# regardless of the Python version.\n\n- case: basic_logger_usage\n  parametrized:\n  - method: trace\n  - method: debug\n  - method: info\n  - method: success\n  - method: warning\n  - method: error\n  - method: exception\n  - method: critical\n  main: |\n    from loguru import logger\n    res = logger.{{method}}(\"Test\")\n    reveal_type(logger)\n    reveal_type(res)\n  out: |\n    main:3: note: Revealed type is \"loguru.Logger\"\n    main:4: note: Revealed type is \"None\"\n\n- case: using_log_function\n  parametrized:\n  - level: \"'INFO'\"\n  - level: 30\n  main: |\n    from loguru import logger\n    logger.log({{level}}, \"Test\")\n\n- case: using_logging_arguments\n  main: |\n    from loguru import logger\n    logger.info(\"{0} + {1} = {result}\", 1, 2, result=3)\n\n- case: logging_non_string\n  parametrized:\n  - message: 123\n  - message: dict(foo=456)\n  - message: object()\n  main: |\n    from loguru import logger\n    logger.info({{message}})\n\n- case: add_sink\n  parametrized:\n  - sink: sys.stderr\n  - sink: \"'test.txt'\"\n  - sink: Path('file.log')\n  - sink: 'lambda m: None'\n  - sink: StreamHandler()\n  - sink: io.StringIO()\n  - sink: Proxy()\n  main: |\n    import io\n    import sys\n    from loguru import logger\n    from logging import StreamHandler\n    from pathlib import Path\n    class Proxy:\n      def write(self, message: str) -> int:\n        return 0\n    id = logger.add({{sink}})\n    reveal_type(id)\n  out: |\n    main:10: note: Revealed type is \"builtins.int\"\n\n- case: basic_sink_options\n  parametrized:\n  - format: \"'{message}'\"\n    filter: \"'module'\"\n    context: \"'fork'\"\n  - format: \"lambda r: '{message}\\\\n'\"\n    filter: 'lambda r: True'\n    context: get_context('fork')\n  main: |\n    import sys\n    from multiprocessing import get_context\n    from loguru import logger\n    logger.add(\n      sys.stdout,\n      format={{format}},\n      filter={{filter}},\n      colorize=False,\n      serialize=True,\n      backtrace=True,\n      diagnose=False,\n      enqueue=True,\n      context={{context}},\n      catch=False,\n    )\n\n- case: file_sink_options\n  main: |\n    from loguru import logger\n    logger.add(\n      \"test.txt\",\n      delay=True,\n      rotation=\"10:00\",\n      retention=64,\n      compression=\"gz\",\n      mode=\"w\",\n      watch=True,\n      buffering=10,\n      encoding=\"ascii\",\n      opener=None,\n    )\n\n- case: async_sink_options\n  main: |\n    import loguru\n    from loguru import logger\n    import asyncio\n    async def sink(m: loguru.Message) -> None:\n      pass\n    logger.add(\n      sink,\n      context=\"fork\",\n      loop=asyncio.get_event_loop(),\n    )\n\n- case: remove_sink\n  main: |\n    from loguru import logger\n    logger.remove(0)\n\n- case: await_completion\n  main: |\n    from loguru import logger\n    awaitable = logger.complete()\n    async def func() -> None:\n      await awaitable\n    reveal_type(awaitable)\n  out: |\n    main:5: note: Revealed type is \"typing.Awaitable[None]\"\n\n- case: catch_as_decorator_with_parentheses\n  main: |\n    from loguru import logger\n    @logger.catch()\n    def func(a: int, b: int) -> int:\n      return a + b\n    func(1, 2) + 3\n    reveal_type(func)\n  out: |\n    main:6: note: Revealed type is \"def (a: builtins.int, b: builtins.int) -> builtins.int\"\n\n- case: catch_as_decorator_without_parentheses\n  main: |\n    from loguru import logger\n    @logger.catch\n    def func(a: int, b: int) -> int:\n      return a + b\n    func(1, 2) + 3\n    reveal_type(func)\n  out: |\n    main:6: note: Revealed type is \"def (a: builtins.int, b: builtins.int) -> builtins.int\"\n\n- case: catch_as_context_manager\n  main: |\n    from loguru import logger\n    catcher = logger.catch()\n    with catcher:\n      pass\n    reveal_type(catcher)\n  out: |\n    main:5: note: Revealed type is \"loguru.Catcher\"\n\n- case: opt\n  main: |\n    from loguru import logger\n    logger = logger.opt(colors=True)\n    logger.info(\"Message\")\n    reveal_type(logger)\n  out: |\n    main:4: note: Revealed type is \"loguru.Logger\"\n\n- case: bind\n  main: |\n    from loguru import logger\n    logger = logger.bind(arg=1)\n    logger.info(\"Message\")\n    reveal_type(logger)\n  out: |\n    main:4: note: Revealed type is \"loguru.Logger\"\n\n- case: patch\n  main: |\n    from loguru import logger\n    logger = logger.patch(lambda r: None)\n    logger.info(\"Message\")\n    reveal_type(logger)\n  out: |\n    main:4: note: Revealed type is \"loguru.Logger\"\n\n- case: contextualize\n  main: |\n    from loguru import logger\n    contextualizer = logger.contextualize(arg=1)\n    with contextualizer:\n      logger.info(\"Message\")\n    reveal_type(contextualizer)\n  out: |\n    main:5: note: Revealed type is \"loguru.Contextualizer\"\n\n- case: level_get\n  main: |\n    from loguru import logger\n    import loguru\n    level = logger.level(\"INFO\")\n    reveal_type(level)\n  out: |\n    main:4: note: Revealed type is \"tuple[builtins.str, builtins.int, builtins.str, builtins.str, fallback=loguru.Level]\"\n\n- case: level_set\n  main: |\n    from loguru import logger\n    import loguru\n    level = logger.level(\"FOO\", no=11, icon=\"!\", color=\"<blue>\")\n    reveal_type(level)\n  out: |\n    main:4: note: Revealed type is \"tuple[builtins.str, builtins.int, builtins.str, builtins.str, fallback=loguru.Level]\"\n\n- case: level_update\n  main: |\n    from loguru import logger\n    import loguru\n    level = logger.level(\"INFO\", color=\"<blue>\")\n    reveal_type(level)\n  out: |\n    main:4: note: Revealed type is \"tuple[builtins.str, builtins.int, builtins.str, builtins.str, fallback=loguru.Level]\"\n\n- case: enable_and_disable_logger\n  main: |\n    from loguru import logger\n    logger.enable(\"foo\")\n    logger.disable(\"foo\")\n\n- case: configure\n  skip: sys.version_info < (3, 7)  # Old Mypy bug: Union of TypedDict not fully supported.\n  main: |\n    from loguru import logger\n    import sys\n    ids = logger.configure(\n      handlers=[{\"sink\": sys.stderr}],\n      levels=[{\"name\": \"FOO\", \"no\": 11}],\n      extra={\"bar\": \"baz\"},\n      activation=[(\"module\", False)],\n    )\n    reveal_type(ids)\n  out: |\n    main:9: note: Revealed type is \"builtins.list[builtins.int]\"\n\n- case: configure_stream_handler\n  skip: sys.version_info < (3, 7)  # Old Mypy bug: Union of TypedDict not fully supported.\n  main: |\n    from loguru import logger\n    import sys\n    logger.configure(handlers=[{\"sink\": sys.stderr, \"colorize\": False}])\n\n- case: configure_file_handler\n  main: |\n    from loguru import logger\n    logger.configure(handlers=[{\"sink\": \"file.log\", \"mode\": \"w\", \"closefd\": False}])\n\n- case: configure_coroutine_handler\n  main: |\n    import loguru\n    from loguru import logger\n    async def sink(m: loguru.Message) -> None:\n      pass\n    logger.configure(handlers=[{\"sink\": sink, \"context\": \"spawn\", \"loop\": None}])\n\n- case: parse\n  main: |\n    from loguru import logger\n    iterator = logger.parse(\"file.log\", r\"(?P<lvl>[0-9]+): (?P<msg>.*)\")\n    for match in iterator:\n      pass\n    reveal_type(iterator)\n    reveal_type(match)\n  out: |\n    main:5: note: Revealed type is \"typing.Generator[builtins.dict[builtins.str, Any], None, None]\"\n    main:6: note: Revealed type is \"builtins.dict[builtins.str, Any]\"\n\n- case: invalid_add_argument\n  main: |\n    from loguru import logger\n    logger.add(lambda m: None, foobar=123)\n  out: |\n    main:2: error: No overload variant of \"add\" of \"Logger\" matches argument types \"Callable[[Any], None]\", \"int\"\n    main:2: note: Possible overload variants:\n    main:2: note:     def add(self, sink: Union[TextIO, Writable, Callable[[Message], None], Handler], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., context: Union[str, BaseContext, None] = ..., catch: bool = ...) -> int\n    main:2: note:     def add(self, sink: Callable[[Message], Awaitable[None]], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., catch: bool = ..., context: Union[str, BaseContext, None] = ..., loop: Optional[AbstractEventLoop] = ...) -> int\n    main:2: note:     def add(self, sink: Union[str, PathLike[str]], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., context: Union[str, BaseContext, None] = ..., catch: bool = ..., rotation: Union[str, int, time, timedelta, Callable[[Message, TextIO], bool], list[Union[str, int, time, timedelta, Callable[[Message, TextIO], bool]]], None] = ..., retention: Union[str, int, timedelta, Callable[[list[str]], None], None] = ..., compression: Union[str, Callable[[str], None], None] = ..., delay: bool = ..., watch: bool = ..., mode: str = ..., buffering: int = ..., encoding: str = ..., errors: Optional[str] = ..., newline: Optional[str] = ..., closefd: bool = ..., opener: Optional[Callable[[str, int], int]] = ...) -> int\n\n- case: invalid_logged_object_formatting\n  main: |\n    from loguru import logger\n    logger.info(123, foo=123)\n  out: |\n    main:2: error: No overload variant of \"info\" of \"Logger\" matches argument types \"int\", \"int\"\n    main:2: note: Possible overload variants:\n    main:2: note:     def info(__self, str, /, *args: Any, **kwargs: Any) -> None\n    main:2: note:     def info(__self, Any, /) -> None\n\n- case: invalid_configuration\n  main: |\n    from loguru import logger\n    logger.configure(\n      handlers=[{\"x\": \"y\"}],\n      levels=[{\"baz\": 1}],\n      patcher=123,\n      activation=[{\"foo\": \"bar\"}],\n      extra=[1],\n    )\n  out: |\n    main:3: error: List item 0 has incompatible type \"dict[str, str]\"; expected \"Union[BasicHandlerConfig, FileHandlerConfig, AsyncHandlerConfig]\"\n    main:4: error: Extra key \"baz\" for TypedDict \"LevelConfig\"\n    main:5: error: Argument \"patcher\" to \"configure\" of \"Logger\" has incompatible type \"int\"; expected \"Optional[Callable[[Record], None]]\"\n    main:6: error: List item 0 has incompatible type \"dict[str, str]\"; expected \"tuple[Optional[str], bool]\"\n    main:7: error: Argument \"extra\" to \"configure\" of \"Logger\" has incompatible type \"list[int]\"; expected \"Optional[dict[Any, Any]]\"\n\n- case: reinstall\n  main: |\n    from loguru import logger\n    logger.reinstall()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = lint, tests, docs, build\nisolated_build = True\n\n[testenv]\nsetenv = PYTHONPATH = {toxinidir}\nextras = dev\n\n[testenv:lint]\ndescription = Run linting checks on all project files.\ncommands =\n    pre-commit run --show-diff-on-failure --all-files\n\n[testenv:tests]\ndescription = Run the tests and generate code coverage.\ncommands =\n    coverage erase\n    pytest -vv --cov loguru/ --cov-report=\n    coverage report -m\n    coverage xml\n\n[testenv:docs]\ndescription = Build the HTML documentation.\ncommands =\n    sphinx-build -a -b html -W --keep-going docs/ docs/build\n\n[testenv:build]\ndescription = Build the Python package.\ncommands =\n    pyproject-build .\n    twine check --strict dist/*\n"
  }
]