From b26d2e5c89dbf0f782513ba0153ec092ebe1049e Mon Sep 17 00:00:00 2001 From: msftcangoblowme Date: Mon, 26 Feb 2024 09:19:15 +0000 Subject: [PATCH] clean up doc warnings and new logo and ci - feat: util.pep518_read for reading pyproject.toml sections - docs(fix): util.pep518_read missing dependency of docs/conf.py - fix: remove, as much as possible, mentions of package asz beside pyproject.toml sections - chore(tox.ini): add test target - chore(tox.ini): suppress noisy package build output - chore(igor.py): add function do_quietly. suppress a commands noisy output - docs: add scriv as a dependency - docs: add logo logging-strict-logo.svg Cleaning svg breaks it. Remove systray-udisk2 logo - docs: create sphinx object inventories to minimize sphinx build warnings - docs: query sphinx object inventory using sphobjinv rather than sphinx.ext.intersphinx - chore(ci): add .readthedocs.yml config file - chore: add tox/ and cheats.txt to .gitignore - chore(ci): add many .github/workflows - chore(ci): add Makefile ci targets. add clean and sterile targets --- .github/workflows/codeql-analysis.yml | 77 +++ .github/workflows/dependency-review.yml | 35 ++ .github/workflows/kit.yml | 290 +++++++++++ .github/workflows/python-nightly.yml | 89 ++++ .github/workflows/quality.yml | 106 ++++ .github/workflows/release.yml | 45 ++ .github/workflows/testsuite.yml | 126 +++++ .gitignore | 4 +- .readthedocs.yaml | 26 + CHANGES.rst | 23 +- Makefile | 59 ++- README.rst | 16 +- docs/Makefile | 43 +- ...tective-woman-folder-with-cat-bookmark.svg | 1 - docs/_static/frog-sitting.svg | 77 +++ docs/_static/log.svg | 30 ++ docs/_static/logging-strict-logo.svg | 201 ++++++++ docs/api/locals/scope_b_locals.rst | 2 +- docs/api/logging/api_logging_asynchronous.rst | 2 +- docs/api/logging/api_logging_handlers.rst | 58 --- docs/api/logging/api_logging_synchronous.rst | 20 +- docs/api/logging/api_logging_warnings.rst | 21 +- docs/api/logging/index.rst | 1 - docs/code/tech_niques/coverage_misbehaves.rst | 8 + docs/code/tech_niques/index.rst | 7 + docs/code/util/index.rst | 1 + docs/code/util/pep518_read.rst | 9 + docs/code/yaml/logging_api.rst | 3 +- docs/code/yaml/logging_yaml_validate.rst | 18 +- docs/conf.py | 46 +- docs/getting_started/exporting.rst | 3 +- docs/getting_started/usage.rst | 12 +- docs/getting_started/validation.rst | 2 +- docs/index.rst | 8 +- docs/objects-black.inv | Bin 0 -> 294 bytes docs/objects-black.txt | 7 + docs/objects-coverage.inv | Bin 0 -> 327 bytes docs/objects-coverage.txt | 9 + docs/objects-logging-strict.inv | Bin 0 -> 5821 bytes docs/objects-logging-strict.txt | 482 ++++++++++++++++++ docs/objects-python.inv | Bin 0 -> 136075 bytes docs/objects-strictyaml.inv | Bin 0 -> 612 bytes docs/objects-strictyaml.txt | 35 ++ docs/objects-textual.inv | Bin 0 -> 231 bytes docs/objects-textual.txt | 7 + docs/requirements.in | 4 +- docs/requirements.pip | 39 +- igor.py | 18 +- pyproject.toml | 2 + requirements/kit.in | 20 + requirements/kit.pip | 52 ++ src/logging_strict/__init__.py | 1 + src/logging_strict/constants.py | 7 +- src/logging_strict/exceptions.py | 32 +- src/logging_strict/logging_api.py | 88 ++-- src/logging_strict/logging_yaml_abc.py | 90 ++-- src/logging_strict/logging_yaml_abc.pyi | 19 +- src/logging_strict/logging_yaml_validate.py | 4 +- src/logging_strict/tech_niques/__init__.py | 27 +- src/logging_strict/tech_niques/__init__.pyi | 13 + .../tech_niques/context_locals.py | 36 +- .../tech_niques/logger_redirect.py | 2 +- .../tech_niques/logging_capture.py | 133 ++--- .../tech_niques/logging_capture.pyi | 19 +- .../tech_niques/stream_capture.py | 16 +- src/logging_strict/util/check_logging.py | 7 +- src/logging_strict/util/check_type.py | 20 +- src/logging_strict/util/check_type.pyi | 21 +- src/logging_strict/util/package_resource.py | 206 ++++---- src/logging_strict/util/package_resource.pyi | 3 +- src/logging_strict/util/pep518_read.py | 218 ++++++++ src/logging_strict/util/pep518_read.pyi | 28 + src/logging_strict/util/util_root.py | 2 +- .../tech_niques/test_docs_sync_log_capture.py | 110 ++++ tests/tech_niques/test_logging_capture.py | 23 +- tests/test_logging_api.py | 5 +- tests/test_pep518_read.py | 238 +++++++++ tox.ini | 18 +- 78 files changed, 3056 insertions(+), 474 deletions(-) create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/kit.yml create mode 100644 .github/workflows/python-nightly.yml create mode 100644 .github/workflows/quality.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/testsuite.yml create mode 100644 .readthedocs.yaml delete mode 100644 docs/_static/detective-woman-folder-with-cat-bookmark.svg create mode 100644 docs/_static/frog-sitting.svg create mode 100644 docs/_static/log.svg create mode 100644 docs/_static/logging-strict-logo.svg delete mode 100644 docs/api/logging/api_logging_handlers.rst create mode 100644 docs/code/tech_niques/coverage_misbehaves.rst create mode 100644 docs/code/util/pep518_read.rst create mode 100644 docs/objects-black.inv create mode 100644 docs/objects-black.txt create mode 100644 docs/objects-coverage.inv create mode 100644 docs/objects-coverage.txt create mode 100644 docs/objects-logging-strict.inv create mode 100644 docs/objects-logging-strict.txt create mode 100644 docs/objects-python.inv create mode 100644 docs/objects-strictyaml.inv create mode 100644 docs/objects-strictyaml.txt create mode 100644 docs/objects-textual.inv create mode 100644 docs/objects-textual.txt create mode 100644 requirements/kit.in create mode 100644 requirements/kit.pip create mode 100644 src/logging_strict/util/pep518_read.py create mode 100644 src/logging_strict/util/pep518_read.pyi create mode 100644 tests/tech_niques/test_docs_sync_log_capture.py create mode 100644 tests/test_pep518_read.py diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..4553266 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,77 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: + - master + pull_request: + # The branches below must be a subset of the branches above + branches: + - master + schedule: + - cron: '30 20 * * 6' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: + - python + - javascript + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..e4adf81 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,35 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part +# of a Pull Reqest, surfacing known-vulnerable versions of the packages +# declared or updated in the PR. Once installed, if the workflow run is +# marked as required, PRs introducing known-vulnerable packages will be +# blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement + +name: 'Dependency Review' +on: + push: + branches: + - master + - msftcangoblowm/* + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + dependency-review: + if: github.repository_owner == 'msftcangoblowm' + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + with: + base-ref: ${{ github.event.pull_request.base.sha || 'master' }} + head-ref: ${{ github.event.pull_request.head.sha || github.ref }} diff --git a/.github/workflows/kit.yml b/.github/workflows/kit.yml new file mode 100644 index 0000000..7f7c150 --- /dev/null +++ b/.github/workflows/kit.yml @@ -0,0 +1,290 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +# This file is meant to be processed with cog. +# Running "make prebuild" will bring it up to date. + +# Based on: +# https://github.com/joerick/cibuildwheel/blob/master/examples/github-deploy.yml + +# To test installing wheels without uploading them to PyPI: +# +# $ mkdir /tmp/pypi +# $ cp dist/* /tmp/pypi +# $ python -m pip install piprepo +# $ piprepo build /tmp/pypi +# $ python -m pip install -v coverage --index-url=file:///tmp/pypi/simple +# +# Note that cibuildwheel recommends not shipping wheels for pre-release versions +# of Python: https://cibuildwheel.readthedocs.io/en/stable/options/#prerelease-pythons +# So we don't. + +name: "Kits" + +on: + push: + branches: + # Don't build kits all the time, but do if the branch is about kits. + - "**/*kit*" + workflow_dispatch: + repository_dispatch: + types: + - build-kits + +defaults: + run: + shell: bash + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + wheels: + name: "${{ matrix.py }} ${{ matrix.os }} ${{ matrix.arch }} wheels" + runs-on: ${{ matrix.os }}-latest + env: + MATRIX_ID: "${{ matrix.py }}-${{ matrix.os }}-${{ matrix.arch }}" + strategy: + matrix: + include: + # To change the matrix, edit the choices, then process this file with cog: + # + # $ make workflows + # + # which runs: + # + # $ python -m pip install cogapp + # $ python -m cogapp -crP .github/workflows/kit.yml + # + # Choices come from the table on https://pypi.org/project/cibuildwheel/ + # + # [[[cog + # #----- vvv Choices for the matrix vvv ----- + # + # # Operating systems: + # oss = ["ubuntu", "macos", "windows"] + # + # # For each OS, what arch to use with cibuildwheel: + # os_archs = { + # "ubuntu": ["x86_64", "i686", "aarch64"], + # "macos": ["arm64", "x86_64"], + # "windows": ["x86", "AMD64"], + # } + # # PYVERSIONS. Available versions: + # # https://github.com/actions/python-versions/blob/main/versions-manifest.json + # # PyPy versions are handled further below in the "pypy" step. + # pys = ["cp38", "cp39", "cp310", "cp311", "cp312"] + # + # # Some OS/arch combinations need overrides for the Python versions: + # os_arch_pys = { + # ("macos", "arm64"): ["cp38", "cp39", "cp310", "cp311", "cp312"], + # } + # + # #----- ^^^ ---------------------- ^^^ ----- + # + # import json + # for the_os in oss: + # for the_arch in os_archs[the_os]: + # for the_py in os_arch_pys.get((the_os, the_arch), pys): + # them = { + # "os": the_os, + # "py": the_py, + # "arch": the_arch, + # } + # print(f"- {json.dumps(them)}") + # ]]] + - {"os": "ubuntu", "py": "cp38", "arch": "x86_64"} + - {"os": "ubuntu", "py": "cp39", "arch": "x86_64"} + - {"os": "ubuntu", "py": "cp310", "arch": "x86_64"} + - {"os": "ubuntu", "py": "cp311", "arch": "x86_64"} + - {"os": "ubuntu", "py": "cp312", "arch": "x86_64"} + - {"os": "ubuntu", "py": "cp38", "arch": "i686"} + - {"os": "ubuntu", "py": "cp39", "arch": "i686"} + - {"os": "ubuntu", "py": "cp310", "arch": "i686"} + - {"os": "ubuntu", "py": "cp311", "arch": "i686"} + - {"os": "ubuntu", "py": "cp312", "arch": "i686"} + - {"os": "ubuntu", "py": "cp38", "arch": "aarch64"} + - {"os": "ubuntu", "py": "cp39", "arch": "aarch64"} + - {"os": "ubuntu", "py": "cp310", "arch": "aarch64"} + - {"os": "ubuntu", "py": "cp311", "arch": "aarch64"} + - {"os": "ubuntu", "py": "cp312", "arch": "aarch64"} + - {"os": "macos", "py": "cp38", "arch": "arm64"} + - {"os": "macos", "py": "cp39", "arch": "arm64"} + - {"os": "macos", "py": "cp310", "arch": "arm64"} + - {"os": "macos", "py": "cp311", "arch": "arm64"} + - {"os": "macos", "py": "cp312", "arch": "arm64"} + - {"os": "macos", "py": "cp38", "arch": "x86_64"} + - {"os": "macos", "py": "cp39", "arch": "x86_64"} + - {"os": "macos", "py": "cp310", "arch": "x86_64"} + - {"os": "macos", "py": "cp311", "arch": "x86_64"} + - {"os": "macos", "py": "cp312", "arch": "x86_64"} + - {"os": "windows", "py": "cp38", "arch": "x86"} + - {"os": "windows", "py": "cp39", "arch": "x86"} + - {"os": "windows", "py": "cp310", "arch": "x86"} + - {"os": "windows", "py": "cp311", "arch": "x86"} + - {"os": "windows", "py": "cp312", "arch": "x86"} + - {"os": "windows", "py": "cp38", "arch": "AMD64"} + - {"os": "windows", "py": "cp39", "arch": "AMD64"} + - {"os": "windows", "py": "cp310", "arch": "AMD64"} + - {"os": "windows", "py": "cp311", "arch": "AMD64"} + - {"os": "windows", "py": "cp312", "arch": "AMD64"} + # [[[end]]] (checksum: a6ca53e9c620c9e5ca85e7322122056c) + fail-fast: false + + steps: + - name: "Setup QEMU" + if: matrix.os == 'ubuntu' + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 + with: + platforms: arm64 + + - name: "Check out the repo" + uses: actions/checkout@v4 + + - name: "Install Python 3.9" + uses: actions/setup-python@v5 + with: + # PYVERSIONS + python-version: "3.9" + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Install tools" + run: | + python -m pip install -r requirements/kit.pip + + - name: "Build wheels" + env: + CIBW_BUILD: ${{ matrix.py }}-* + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_ENVIRONMENT: PIP_DISABLE_PIP_VERSION_CHECK=1 + CIBW_PRERELEASE_PYTHONS: True + run: | + python -m cibuildwheel --output-dir wheelhouse + + - name: "List wheels" + run: | + ls -al wheelhouse/ + + - name: "Upload wheels" + uses: actions/upload-artifact@v4 + with: + name: dist-${{ env.MATRIX_ID }} + path: wheelhouse/*.whl + retention-days: 7 + + sdist: + name: "Source distribution" + runs-on: ubuntu-latest + steps: + - name: "Check out the repo" + uses: actions/checkout@v4 + + - name: "Install Python 3.9" + uses: actions/setup-python@v5 + with: + # PYVERSIONS + python-version: "3.9" + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Install tools" + run: | + python -m pip install -r requirements/kit.pip + + - name: "Build sdist" + run: | + python igor.py build_next "" + + - name: "List tarballs" + run: | + ls -al dist/ + + - name: "Upload sdist" + uses: actions/upload-artifact@v4 + with: + name: dist-sdist + path: dist/*.tar.gz + retention-days: 7 + + pypy: + name: "PyPy wheel" + runs-on: ubuntu-latest + steps: + - name: "Check out the repo" + uses: actions/checkout@v4 + + - name: "Install PyPy" + uses: actions/setup-python@v5 + with: + python-version: "pypy-3.9" # Minimum of PyPy PYVERSIONS + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Install requirements" + run: | + pypy3 -m pip install -r requirements/kit.pip + + - name: "Build wheel" + env: + DIST_EXTRA_CONFIG: extra.cfg + run: | + # One wheel works for all PyPy versions. PYVERSIONS + # yes, this is weird syntax: https://github.com/pypa/build/issues/202 + echo -e "[bdist_wheel]\npython_tag=pp38.pp39.pp310" > $DIST_EXTRA_CONFIG + pypy3 -m build -w + + - name: "List wheels" + run: | + ls -al dist/ + + - name: "Upload wheels" + uses: actions/upload-artifact@v4 + with: + name: dist-pypy + path: dist/*.whl + retention-days: 7 + + sign: + # This signs our artifacts, but we don't use the signatures for anything + # yet. Someday maybe PyPI will have a way to upload and verify them. + name: "Sign artifacts" + needs: + - wheels + - sdist + - pypy + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: "Download artifacts" + uses: actions/download-artifact@v4 + with: + pattern: dist-* + merge-multiple: true + + - name: "Sign artifacts" + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: coverage-*.* + + - name: "List files" + run: | + ls -alR + + - name: "Upload signatures" + uses: actions/upload-artifact@v4 + with: + name: signatures + path: | + *.crt + *.sig + *.sigstore + retention-days: 7 diff --git a/.github/workflows/python-nightly.yml b/.github/workflows/python-nightly.yml new file mode 100644 index 0000000..9e8448c --- /dev/null +++ b/.github/workflows/python-nightly.yml @@ -0,0 +1,89 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +name: "Python Nightly Tests" + +on: + push: + branches: + - "**/*nightly*" + schedule: + # Run at 2:22am early every morning Eastern time (6/7:22 UTC) + # so that we get tips of CPython development tested. + # https://crontab.guru/#22_7_%2a_%2a_%2a + - cron: "22 7 * * *" + workflow_dispatch: + +defaults: + run: + shell: bash + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + tests: + name: "${{ matrix.python-version }}" + # Choose a recent Ubuntu that deadsnakes still builds all the versions for. + # For example, deadsnakes doesn't provide 3.10 nightly for 22.04 (jammy) + # because jammy ships 3.10, and deadsnakes doesn't want to clobber it. + # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages + # https://github.com/deadsnakes/issues/issues/234 + # bionic: 18, focal: 20, jammy: 22 + runs-on: ubuntu-20.04 + # If it doesn't finish in an hour, it's not going to. Don't spin for six + # hours needlessly. + timeout-minutes: 60 + + strategy: + matrix: + python-version: + # When changing this list, be sure to check the [gh] list in + # tox.ini so that tox will run properly. PYVERSIONS + # Available versions: + # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages + - "3.11-dev" + - "3.12-dev" + - "3.13-dev" + # https://github.com/actions/setup-python#available-versions-of-pypy + - "pypy-3.9-nightly" + - "pypy-3.10-nightly" + fail-fast: false + + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install ${{ matrix.python-version }} with deadsnakes" + uses: deadsnakes/action@6c8b9b82fe0b4344f4b98f2775fcc395df45e494 + if: "!startsWith(matrix.python-version, 'pypy-')" + with: + python-version: "${{ matrix.python-version }}" + + - name: "Install ${{ matrix.python-version }} with setup-python" + uses: "actions/setup-python@v5" + if: "startsWith(matrix.python-version, 'pypy-')" + with: + python-version: "${{ matrix.python-version }}" + + - name: "Show diagnostic info" + run: | + set -xe + python -VV + python -m site + env + + - name: "Install dependencies" + run: | + python -m pip install -r requirements/tox.pip + + - name: "Run tox" + run: | + python -m tox -- -rfsEX diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..7d645f5 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,106 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +name: "Quality" + +on: + push: + branches: + - master + - msftcangoblowm/* + pull_request: + workflow_dispatch: + +defaults: + run: + shell: bash + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + lint: + name: "isort black flake8 etc" + runs-on: ubuntu-latest + + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.9" # Minimum of PYVERSIONS + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Install dependencies" + run: | + python -m pip install -r requirements/tox.pip + + - name: "Tox lint" + run: | + python -m tox -e lint + + mypy: + name: "Check types" + runs-on: ubuntu-latest + + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.9" # Minimum of PYVERSIONS, but at least 3.9 + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Install dependencies" + run: | + # Runs on 3.9, but hashes supported by pip-tools is + # not supported by setuptools. So don't expect hashes + python -m pip install -r requirements/tox.pip + + - name: "Tox mypy" + run: | + python -m tox -e mypy + + docs: + name: "Build docs" + runs-on: ubuntu-latest + + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.9" # Doc version from PYVERSIONS + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: "Show environment" + run: | + set -xe + python -VV + python -m site + env + + - name: "Install dependencies" + run: | + set -xe + python -m pip install -r requirements/tox.pip + + - name: "Tox docs" + run: | + python -m tox -e docs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e7fce6d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: release + +on: + push: + branches: + - master + tags: + - '*.*.*' + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + + - name: Install py39 + uses: actions/setup-python@v5 + with: + python-version: 3.9 + cache: pip + cache-dependency-path: 'requirements/*.pip' + + - name: build + shell: bash + run: | + python -m pip install --upgrade -r requirements/pip-tools.pip + python -m build . + - name: Release PyPI + shell: bash + env: + TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: | + pip install --upgrade twine + twine upload dist/* + - name: Release GitHub + uses: softprops/action-gh-release@v1 + with: + files: "dist/*" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml new file mode 100644 index 0000000..8bd8027 --- /dev/null +++ b/.github/workflows/testsuite.yml @@ -0,0 +1,126 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +name: "Tests" + +on: + push: + branches: + - master + - msftcangoblowm/* + pull_request: + workflow_dispatch: + +defaults: + run: + shell: bash + +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + COVERAGE_IGOR_VERBOSE: 1 + FORCE_COLOR: 1 # Get colored test output + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + tests: + name: "${{ matrix.python-version }} on ${{ matrix.os }}" + runs-on: "${{ matrix.os }}-latest" + # Don't run tests if the branch name includes "-notests" + if: "!contains(github.ref, '-notests')" + strategy: + matrix: + os: + - ubuntu + - macos + - windows + python-version: + # When changing this list, be sure to check the [gh] list in + # tox.ini so that tox will run properly. PYVERSIONS + # Available versions: + # https://github.com/actions/python-versions/blob/main/versions-manifest.json + # https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md#available-versions-of-python-and-pypy + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "pypy-3.8" + - "pypy-3.9" + - "pypy-3.10" + exclude: + # Windows pypy 3.9 and 3.10 get stuck with PyPy 7.3.15. I hope to + # unstick them, but I don't want that to block all other progress, so + # skip them for now. + - os: windows + python-version: "pypy-3.9" + - os: windows + python-version: "pypy-3.10" + # Skip 3.13.0a4 and pin to 3.13.0a3 for Windows due to build error. + # Undo when 3.13.0a5 is released. + - os: windows + python-version: "3.13" + include: + - os: windows + python-version: "3.13.0-alpha.3" + fail-fast: false + + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "${{ matrix.python-version }}" + allow-prereleases: true + # At a certain point, installing dependencies failed on pypy 3.9 and + # 3.10 on Windows. Commenting out the cache here fixed it. Someday + # try using the cache again. + #cache: pip + #cache-dependency-path: 'requirements/*.pip' + + - name: "Show environment" + run: | + set -xe + python -VV + python -m site + # For extreme debugging: + # python -c "import urllib.request as r; exec(r.urlopen('https://bit.ly/pydoctor').read())" + env + + - name: "Install dependencies" + run: | + set -xe + python -m pip install -r requirements/tox.pip + + - name: "Run tox for ${{ matrix.python-version }}" + run: | + python -m tox -- -rfsEX + + - name: "Retry tox for ${{ matrix.python-version }}" + if: failure() + run: | + # `exit 1` makes sure that the job remains red with flaky runs + python -m tox -- -rfsEX --lf -vvvvv && exit 1 + + # This job aggregates test results. It's the required check for branch protection. + # https://github.com/marketplace/actions/alls-green#why + # https://github.com/orgs/community/discussions/33579 + success: + name: Tests successful + # The tests didn't run if the branch name includes "-notests" + if: "!contains(github.ref, '-notests')" + needs: + - tests + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index 224994e..ab49ea8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist/ *.egg-info/ sdist/ wheels/ +.tox/ ##@ mypy .mypy_cache/ @@ -25,9 +26,10 @@ tests/tech_niques/__pycache__/ # Placed in parent of package base folder. So not in repository # .python-version -##@ this app +##@ stuff in the root junk/ retired/ +cheats.txt ## autogenerated by setuptools_scm src/logging_strict/_version.py diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..212c1bd --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt +# +# ReadTheDocs configuration. +# See https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: ubuntu-22.04 + tools: + # PYVERSIONS: the version we use for building docs. Check tox.ini[doc] also. + python: "3.9" + +sphinx: + builder: html + configuration: docs/conf.py + +# Don't build anything except HTML. +formats: [] + +python: + install: + - requirements: docs/requirements.pip + - method: pip + path: . diff --git a/CHANGES.rst b/CHANGES.rst index 8010d44..b98b2b7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,8 +8,6 @@ Changelog Feature request ................. - - integrate with asz - Known regressions .................. @@ -20,9 +18,28 @@ Changelog Commit items for NEXT VERSION ................................. - .. scriv-start-here +.. _changes_1-2-0: + +Version 1.2.0 — 2024-02-26 +-------------------------- + +- feat: util.pep518_read for reading pyproject.toml sections +- docs(fix): util.pep518_read missing dependency of docs/conf.py +- fix: remove, as much as possible, mentions of package asz beside pyproject.toml sections +- chore(tox.ini): add test target +- chore(tox.ini): suppress noisy package build output +- chore(igor.py): add function do_quietly. suppress a commands noisy output +- docs: add scriv as a dependency +- docs: add logo logging-strict-logo.svg Cleaning svg breaks it. Remove systray-udisk2 logo +- docs: create sphinx object inventories to minimize sphinx build warnings +- docs: query sphinx object inventory using sphobjinv rather than sphinx.ext.intersphinx +- chore(ci): add .readthedocs.yml config file +- chore: add tox/ and cheats.txt to .gitignore +- chore(ci): add many .github/workflows +- chore(ci): add Makefile ci targets. add clean and sterile targets + .. _changes_1-1-0: Version 1.1.0 — 2024-02-23 diff --git a/Makefile b/Makefile index 159dcb9..409666b 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,33 @@ endif help: ## (Default) Display this help -- Always up to date @awk -F ':.*##' '/^[^: ]+:.*##/{printf " \033[1m%-20s\033[m %s\n",$$1,$$2} /^##@/{printf "\n%s\n",substr($$0,5)}' $(MAKEFILE_LIST) +.PHONY: clean_platform clean sterile + +_clean_platform: + @rm -f *.so */*.so + rm -f *.pyd */*.pyd + rm -rf __pycache__ */__pycache__ */*/__pycache__ */*/*/__pycache__ */*/*/*/__pycache__ */*/*/*/*/__pycache__ + rm -f *.pyc */*.pyc */*/*.pyc */*/*/*.pyc */*/*/*/*.pyc */*/*/*/*/*.pyc + rm -f *.pyo */*.pyo */*/*.pyo */*/*/*.pyo */*/*/*/*.pyo */*/*/*/*/*.pyo + rm -f *$$py.class */*$$py.class */*/*$$py.class */*/*/*$$py.class */*/*/*/*$$py.class */*/*/*/*/*$$py.class + +clean: _clean_platform ## Remove artifacts of test execution, installation, etc. + @echo "Cleaning..." + -pip uninstall -yq logging_strict + mkdir -p build # so the chmod won't fail if build doesn't exist + chmod -R 777 build ||: + rm -rf logging_strict.egg-info dist + rm -f MANIFEST + rm -f .coverage .coverage-combine-* .coverage-recipe-* + rm -f coverage.xml coverage.json + rm -f */.coverage */*/.coverage */*/*/.coverage */*/*/*/.coverage */*/*/*/*/.coverage */*/*/*/*/*/.coverage + rm -rf doc/_build + rm -rf tmp + rm -rf .*cache */.*cache */*/.*cache */*/*/.*cache .hypothesis + rm -rf .mypy_cache/ + +sterile: clean ## Remove all non-controlled content, even if expensive. + rm -rf .tox ##@ Build dependencies @@ -74,6 +101,7 @@ ifeq ($(is_venv),1) @pip install --quiet --disable-pip-version-check --requirement requirements/pip-tools.pip $(PIP_COMPILE) -o requirements/pip-tools.pip requirements/pip-tools.in $(PIP_COMPILE) -o requirements/pip.pip requirements/pip.in + $(PIP_COMPILE) -o requirements/kit.pip requirements/kit.in $(PIP_COMPILE) -o requirements/prod.pip requirements/prod.in $(PIP_COMPILE) --no-strip-extras -o requirements/tox.pip requirements/tox.in $(PIP_COMPILE) --no-strip-extras -o requirements/manage.pip requirements/manage.in @@ -152,21 +180,42 @@ endif REPO_OWNER := msftcangoblowm/logging_strict REPO := $(REPO_OWNER)/logging_strict -.PHONY: edit_for_release cheats relbranch check_kits +.PHONY: edit_for_release cheats relbranch kit_check kit_build kit_upload +.PHONY: test_upload kits_build kits_download github_releases -edit_for_release: ## Edit sources to insert release facts +edit_for_release: ## Edit sources to insert release facts (see howto.txt) python igor.py edit_for_release -cheats: ## Create some useful snippets for releasing +cheats: ## Create some useful snippets for releasing (see howto.txt) python igor.py cheats | tee cheats.txt -relbranch: ## Create the branch for releasing +relbranch: ## Create the branch for releasing (see howto.txt) git switch -c $(REPO_OWNER)/release-$$(date +%Y%m%d) -check_kits: ## Check that dist/* are well-formed +# Do not create a target(s) kit: or kits: +kit_check: ## Check that dist/* are well-formed python -m twine check dist/* @echo $$(ls -1 dist | wc -l) distribution kits +kit_build: ## Make the source distribution + python igor.py build_next "" + +kit_upload: ## Upload the built distributions to PyPI + twine upload --verbose dist/* + +test_upload: ## Upload the distributions to PyPI's testing server + twine upload --verbose --repository testpypi --password $$TWINE_TEST_PASSWORD dist/* + +kits_build: ## Trigger GitHub to build kits + python ci/trigger_build_kits.py $(REPO_OWNER) + +kits_download: ## Download the built kits from GitHub + python ci/download_gha_artifacts.py $(REPO_OWNER) 'dist-*' dist + +github_releases: $(DOCBIN) ## Update GitHub releases. + $(DOCBIN)/python -m scriv github-release --all + + .PHONY: install install: override usage := make [force=1] install: override check_web := Install failed. Possible cause no web connection diff --git a/README.rst b/README.rst index 511fac4..e00ac53 100644 --- a/README.rst +++ b/README.rst @@ -31,10 +31,10 @@ For logging.config yaml files, logging-strict does the following: - validates against a strictyaml schema - The schema is specifically tailored for the logging.config.handlers + The schema is specifically tailored for the logging.handlers As long as the yaml is valid, will have the data types - logging.config.handlers expect + logging.handlers expect - exports package data @@ -46,18 +46,16 @@ For logging.config yaml files, logging-strict does the following: * Python 3.9 through 3.12, and 3.13.0a3 and up. +**New in 1.2.x:** + +sphinx object inventories minimizes sphinx warnings; package logo; .readthedocs.yml; +tox.ini; many .github/workflows; + **New in 1.1.x:** Sphinx documentation; Public API changed. retired functions setup_worker and setup_ui. Split each into 2 or 3; -**New in 0.1.x:** - -techniques relavent to logging; -validate logging.config yaml; -two logging.config yaml files. One for app. One for worker; -pre-commit hook validate-logging-strict-yaml - Why? ------ diff --git a/docs/Makefile b/docs/Makefile index 480da5c..f31ff2e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -55,12 +55,49 @@ ifeq ($(is_venv),1) @$(SPHINXBUILD) -b linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" || echo "$(err_check_web_conn). exit code $$?" endif -.PHONY: objects -objects: ## Query Sphinx known objects. Limited by docs/_build/html/objects.inv +.PHONY: _objects +_objects: ifeq ($(is_venv),1) - @$(VENV_BIN_PYTHON) -m sphinx.ext.intersphinx $(SOURCEDIR)/$(BUILDDIR)/html/objects.inv + @if [[ -n "$(project_name)" ]] && [[ -z "$(find)" ]]; then + + echo "$(usage)" 1>&2 + + else + + if [[ ! -x $(VENV_BIN)/sphobjinv ]]; then + echo "package sphobjinv not installed or available" + else + objinv=objects-$(project_name).inv + /bin/yes | $(VENV_BIN)/sphobjinv suggest $(SOURCEDIR)/$$objinv "$(find)" -st 58 + fi + + fi endif +# make objects_py find=logging.Logger +# $(VENV_BIN_PYTHON) -m sphinx.ext.intersphinx $(SOURCEDIR)/objects-python.inv +.PHONY: objects_py +objects_py: override project_name := python +objects_py: override usage := "make objects_py find=[dotted path]" +objects_py: _objects ## Query python Sphinx objects. make objects_py find="[dotted path]" +objects_py: + +# $(VENV_BIN_PYTHON) -m sphinx.ext.intersphinx $(SOURCEDIR)/objects-logging-strict.inv +# make objects_strict find=VERSION_FALLBACK +.PHONY: objects_strict +objects_strict: override project_name := logging-strict +objects_strict: override usage := "make objects_strict find=[dotted path]" +objects_strict: _objects ## Query logging-strict Sphinx objects. make objects_strict find="[dotted path]" +objects_strict: + +# make obj_strictyaml find=YAML +# make obj_strictyaml find=strictyaml.exceptions +.PHONY: obj_strictyaml +obj_strictyaml: override project_name := strictyaml +obj_strictyaml: override usage := "make obj_strictyaml find=[dotted path]" +obj_strictyaml: _objects ## Query strictyaml Sphinx objects. make obj_strictyaml find="[dotted path]" +obj_strictyaml: + # Stubbornly insists on repeating on outdated files. # -T show tracebacks on Exception # -q quiet diff --git a/docs/_static/detective-woman-folder-with-cat-bookmark.svg b/docs/_static/detective-woman-folder-with-cat-bookmark.svg deleted file mode 100644 index 8aec52a..0000000 --- a/docs/_static/detective-woman-folder-with-cat-bookmark.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_static/frog-sitting.svg b/docs/_static/frog-sitting.svg new file mode 100644 index 0000000..d67df00 --- /dev/null +++ b/docs/_static/frog-sitting.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/log.svg b/docs/_static/log.svg new file mode 100644 index 0000000..79edac3 --- /dev/null +++ b/docs/_static/log.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/logging-strict-logo.svg b/docs/_static/logging-strict-logo.svg new file mode 100644 index 0000000..a903ebe --- /dev/null +++ b/docs/_static/logging-strict-logo.svg @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Logging-Strict + YAML + + + + included + diff --git a/docs/api/locals/scope_b_locals.rst b/docs/api/locals/scope_b_locals.rst index d47d9cb..f8e6880 100644 --- a/docs/api/locals/scope_b_locals.rst +++ b/docs/api/locals/scope_b_locals.rst @@ -29,7 +29,7 @@ Would like to know the locals key/value pairs and return value .. code-block:: python - from asz.tech_niques import get_locals + from logging_strict.tech_niques import get_locals func_path = f'{__name__}.piggy_back' args = ("A",) diff --git a/docs/api/logging/api_logging_asynchronous.rst b/docs/api/logging/api_logging_asynchronous.rst index 22fa315..ee8983d 100644 --- a/docs/api/logging/api_logging_asynchronous.rst +++ b/docs/api/logging/api_logging_asynchronous.rst @@ -19,7 +19,7 @@ Package :pypi_org:`aiologger` docs are authoritative, but sparse/terse. >>> from aiologger.levels import LogLevel >>> from aiologger.logger import Logger >>> ->>> from asz.constants import LOG_FORMAT +>>> from logging_strict.constants import LOG_FORMAT >>> >>> g_app_name = "asz" # your package >>> diff --git a/docs/api/logging/api_logging_handlers.rst b/docs/api/logging/api_logging_handlers.rst deleted file mode 100644 index 3ece308..0000000 --- a/docs/api/logging/api_logging_handlers.rst +++ /dev/null @@ -1,58 +0,0 @@ -.. _api_logging_handlers: - -====================== -logging (third party) -====================== - -Without :ref:`Capture logging `, to capture python -modules or third party packages log messages, would configure -logging handlers . - -Obviously this is more involved and messy. - -Basic logging config captures one package level and below. - ->>> import logging ->>> import sys ->>> from asz.constants import LOG_FORMAT ->>> g_module = "asz.tests_one.test_folder_contains_one" ->>> g_logger = logging.getLogger(g_module) ->>> logging.basicConfig( -... format=LOG_FORMAT, -... level=logging.INFO, -... stream=sys.stdout, -... ) - -More advanced logging that captures root logger - ->>> import logging ->>> import logging.config ->>> from asz.constants import CONFIG_THREAD_WORKER ->>> g_module = "asz.tests_one.test_folder_contains_one" ->>> g_logger = logging.getLogger(g_module) ->>> logging.config.dictConfig(CONFIG_THREAD_WORKER) - -dict config configuration (CONFIG_THREAD_WORKER) - -.. code-block:: text - - LOG_FMT_DETAILED = ( - "%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s" - ) - LOG_FMT_SIMPLE = "%(name)-15s %(levelname)-8s %(processName)-10s %(message)s" - LOG_LEVEL_WORKER = "INFO" - CONFIG_THREAD_WORKER = { # type: ignore - "version": 1, - "formatters": { - "detailed": {"class": "logging.Formatter", "format": LOG_FMT_DETAILED}, - "simple": {"class": "logging.Formatter", "format": LOG_FMT_SIMPLE}, - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "formatter": "simple", - "level": LOG_LEVEL_WORKER, - } - }, - "root": {"handlers": ["console"], "level": LOG_LEVEL_WORKER}, - } diff --git a/docs/api/logging/api_logging_synchronous.rst b/docs/api/logging/api_logging_synchronous.rst index b025bbf..a06054d 100644 --- a/docs/api/logging/api_logging_synchronous.rst +++ b/docs/api/logging/api_logging_synchronous.rst @@ -16,23 +16,27 @@ Instead, to see root messages, initialize logging and include handlers import logging import sys import unittest - from asz.constants import LOG_FORMAT - from asz.util.unittest_inspect import LoggerRedirector + from logging_strict.constants import LOG_FORMAT, g_app_name + from logging_strict.tech_niques import LoggerRedirector class SomeUnittestClass(unittest.TestCase): def setUp(self): - package_name = "asz" # replace with your package name + package_name = g_app_name # replace with your package name g_module_name = "test_docs_sync_log_capture" g_module = f"{package_name}.tests.tech_niques.{g_module_name}" self._LOGGER = logging.getLogger(g_module) # So see root logging messages, replace, needs logging with handlers + """ logging.basicConfig( format=LOG_FORMAT, level=logging.INFO, stream=sys.stdout, ) + """ + # ^^ uncomment ^^ + pass LoggerRedirector.redirect_loggers( fake_stdout=sys.stdout, @@ -46,14 +50,16 @@ Instead, to see root messages, initialize logging and include handlers ) def test_logging_redirecting(self): - self._LOGGER.info("Is this shown?") + # self._LOGGER.info("Is this shown?") + # ^^ uncomment ^^ + pass if __name__ == "__main__": # pragma: no cover unittest.main(tb_locals=True) -This is an actual unittest module in the package, asz. Uncomment the -``self._LOGGER.info`` line and then run the unittest +Before running this unittest, uncomment out the lines preceding the +comment, ``# ^^ uncomment ^^`` .. code-block:: shell @@ -64,7 +70,7 @@ Expected output .. code-block:: text $> python -m tests.test_docs_sync_log_capture - INFO test_docs_sync_log_capture test_logging_redirecting: 55: Is this shown? + INFO test_docs_sync_log_capture test_logging_redirecting: *: Is this shown? . ---------------------------------------------------------------------- Ran 1 test in 0.000s diff --git a/docs/api/logging/api_logging_warnings.rst b/docs/api/logging/api_logging_warnings.rst index 757116f..1119801 100644 --- a/docs/api/logging/api_logging_warnings.rst +++ b/docs/api/logging/api_logging_warnings.rst @@ -15,8 +15,8 @@ to simply turn off these warnings. It's freak'n annoying! -The :py:mod:`asyncio` warnings are :py:obj:`logging.warning`, not -warning.warn +The :py:mod:`asyncio` warnings are :py:func:`logging.warning`, not +external:python+ref:`warning.warn ` How to handle :ref:`warning.warn messages ` @@ -43,10 +43,9 @@ The downside of this technique is has to be applied to all asyncio code. .. seealso:: - :py:obj:`logging_strict.tech_niques.captureLogs` - - :py:obj:`logging_strict.tests.util.test_logging_capture.TestsLoggingCapture` + external:logging-strict+ref:`logging_strict.tech_niques.captureLogs` + tests.util.test_logging_capture.TestsLoggingCapture .. _asyncio_logging_warnings_suppress: @@ -69,12 +68,13 @@ see these warnings. At least on screens and widgets where this technique has been applied After applying this technique, in affected unittests, can remove the -mitigation technique, captureLogs +mitigation technique, +external:logging-strict+ref:`~logging_strict.tech_niques.captureLogs` .. _api_logging_warning_warn: -warning.warn -------------- +warning.warn almost forgot +--------------------------- :py:mod:`warnings` are typically used for depreciation warnings. @@ -89,11 +89,12 @@ warning.warn The above should suppress :py:func:`warnings.warn` messages, not :py:func:`logging.warning` messages. -Less common to see :py:obj:`warning.warn` messages. +Less common to see external:python+ref:`warning.warn ` messages. Indicates the package dependency is from a mature project that has gone thru (major version) API breaking changes or usage depreciation. Once a coder sees these warning messages, more likely than not would quickly update code to use the newer usage syntax. Afterwards no warning -messages and therefore no need to suppress :py:obj:`warnings.warn` messages +messages and therefore no need to suppress +external:python+ref:`warning.warn ` messages diff --git a/docs/api/logging/index.rst b/docs/api/logging/index.rst index e7f8a19..f891b70 100644 --- a/docs/api/logging/index.rst +++ b/docs/api/logging/index.rst @@ -9,4 +9,3 @@ Logging api_logging_synchronous api_logging_warnings api_logging_asynchronous - api_logging_handlers diff --git a/docs/code/tech_niques/coverage_misbehaves.rst b/docs/code/tech_niques/coverage_misbehaves.rst new file mode 100644 index 0000000..830016d --- /dev/null +++ b/docs/code/tech_niques/coverage_misbehaves.rst @@ -0,0 +1,8 @@ +Coverage misbehaves +==================== + +.. automodule:: logging_strict.tech_niques.coverage_misbehaves + :members: + :private-members: + :undoc-members: + :noindex: diff --git a/docs/code/tech_niques/index.rst b/docs/code/tech_niques/index.rst index 73e78a9..07604e6 100644 --- a/docs/code/tech_niques/index.rst +++ b/docs/code/tech_niques/index.rst @@ -3,6 +3,12 @@ Techniques tech_niques which are logging related as well as code inspection +.. automodule:: logging_strict.tech_niques + :members: + :private-members: + :undoc-members: + :noindex: + .. toctree:: :maxdepth: 2 :name: toctree_tech_niques @@ -11,3 +17,4 @@ tech_niques which are logging related as well as code inspection logger_redirect context_locals stream_capture + coverage_misbehaves diff --git a/docs/code/util/index.rst b/docs/code/util/index.rst index 675d23d..731b2c6 100644 --- a/docs/code/util/index.rst +++ b/docs/code/util/index.rst @@ -10,3 +10,4 @@ Util package_resource util_root xdg_folder + pep518_read diff --git a/docs/code/util/pep518_read.rst b/docs/code/util/pep518_read.rst new file mode 100644 index 0000000..47c5d4c --- /dev/null +++ b/docs/code/util/pep518_read.rst @@ -0,0 +1,9 @@ +pep518 read pyproject.toml +=================================================== + +.. automodule:: logging_strict.util.pep518_read + :members: + :private-members: + :undoc-members: + :noindex: + :show-inheritance: diff --git a/docs/code/yaml/logging_api.rst b/docs/code/yaml/logging_api.rst index c4be533..70fbfc2 100644 --- a/docs/code/yaml/logging_api.rst +++ b/docs/code/yaml/logging_api.rst @@ -1,7 +1,8 @@ YAML API ========= -An implementation of LoggingYamlType. +An implementation of +external:logging-strict+ref:`logging_strict.logging_yaml_abc.LoggingYamlType`. Not on the Public API, but underlies Public API functions dealing with extract and setup of :py:mod:`logging.config` yaml files diff --git a/docs/code/yaml/logging_yaml_validate.rst b/docs/code/yaml/logging_yaml_validate.rst index e9b8ba8..609548e 100644 --- a/docs/code/yaml/logging_yaml_validate.rst +++ b/docs/code/yaml/logging_yaml_validate.rst @@ -38,7 +38,8 @@ Limitations 2. :py:class:`logging.handlers.TimedRotatingFileHandler` arg ``asTime`` - :py:mod:`strictyaml` has no support for :py:class:`datetime.time` + `strictyaml ` has no support for + :py:class:`datetime.time` Module private variables ------------------------- @@ -50,7 +51,7 @@ Module private variables Module exports .. py:data:: schema_logging_config - :type: strictyaml.Validator + :type: external:strictyaml+ref:`strictyaml.validators.Validator` strictyaml schema to compare the yaml against @@ -76,8 +77,8 @@ Module private variables - item1 - Eventhough it's easy to fix the yaml, logging.config.dictConfig will - accept the non-fixed yaml + Eventhough it's easy to fix the yaml, + external:python+ref:`logging.config.dictConfig` will accept the non-fixed yaml Reluctantly ... allow flow style @@ -91,10 +92,13 @@ Module private variables :param yaml_snippet: :py:mod:`logging.config` YAML str :type yaml_snippet: str - :param schema: :py:mod:`strictyaml` strict typing schema - :type schema: :py:class:`strictyaml.Validator` or :py:data:`.schema_logging_config` + :param schema: `strictyaml ` strict typing schema + :type schema: + + strictyaml.validators.Validator | :py:data:`.schema_logging_config` + :returns: YAML object. Pass this to each worker - :rtype: :py:class:`strictyaml.YAML` | None + :rtype: external:strictyaml+ref:`~strictyaml.representation.YAML` | None .. seealso:: diff --git a/docs/conf.py b/docs/conf.py index ddb0944..79ec381 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,11 +13,12 @@ import sys from pathlib import Path -from asz.constants import __version__ as proj_version -from asz.util.pep518_read import find_project_root from packaging.version import parse from sphinx_pyproject import SphinxConfig +from logging_strict.constants import __version__ as proj_version +from logging_strict.util.pep518_read import find_project_root + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -59,11 +60,11 @@ # @@@ editable copyright = "2023–2024, Dave Faulkmore" # The short X.Y.Z version. -version = "1.1.0" +version = "1.2.0" # The full version, including alpha/beta/rc tags. -release = "1.1.0" +release = "1.2.0" # The date of release, in "monthname day, year" format. -release_date = "February 23, 2024" +release_date = "February 26, 2024" # @@@ end # release = config.version @@ -77,10 +78,10 @@ # Dynamic ############### htmlhelp_basename = f"{slug}doc" +# .. |doc-url| replace:: https://logging_strict.readthedocs.io/en/{release} rst_epilog = """ .. |project_name| replace:: {slug} .. |package-equals-release| replace:: logging_strict=={release} -.. |doc-url| replace:: https://logging_strict.readthedocs.io/en/{release} """.format( release=release, slug=slug ) @@ -88,8 +89,8 @@ html_theme_options = { "description": proj_description, "show_relbars": True, - "logo_name": True, - "logo": "detective-woman-folder-with-cat-bookmark.svg", + "logo_name": False, + "logo": "logging-strict-logo.svg", "show_powered_by": False, } @@ -146,15 +147,40 @@ } # Creating ``objects.inv`` files for third party packages that lack them +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html # `sphobjinv pypi `_ # `sphobjinv docs `_ +# https://sphobjinv.readthedocs.io/en/v2.3.1/api_usage.html#exporting-an-inventory intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), + "python": ( + "https://docs.python.org/3", + ("objects-python.inv", None), + ), + "logging-strict": ( + "", + ("objects-logging-strict.inv", "objects-logging-strict.txt"), + ), + "strictyaml": ( + "", + ("objects-strictyaml.inv", "objects-strictyaml.txt"), + ), + "textual": ( + "", + ("objects-textual.inv", "objects-textual.txt"), + ), + "black": ( + "", + ("objects-black.inv", "objects-black.txt"), + ), + "coverage": ( + "", + ("objects-coverage.inv", "objects-coverage.txt"), + ), } intersphinx_disabled_reftypes = ["std:doc"] # https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html -extlinks_detect_hardcoded_links = True +# extlinks_detect_hardcoded_links = True def strip_anchor(widget_name: str) -> str: diff --git a/docs/getting_started/exporting.rst b/docs/getting_started/exporting.rst index 050ee5e..97b6ab4 100644 --- a/docs/getting_started/exporting.rst +++ b/docs/getting_started/exporting.rst @@ -9,7 +9,8 @@ are two process types: worker and app When an app is run, it exports the app logging configuration. -Right before a ProcessPool runs, it exports the worker logging configuration. +Right before a :py:class:`multiprocessing.pool.Pool` runs, it exports +the worker logging configuration. Right before a thread or ThreadPool runs, G'd and Darwin sit down to decide which calamity will befall you. Best to avoid that cuz Python logging module is diff --git a/docs/getting_started/usage.rst b/docs/getting_started/usage.rst index 1a4b0a5..7e00624 100644 --- a/docs/getting_started/usage.rst +++ b/docs/getting_started/usage.rst @@ -189,9 +189,9 @@ This is a 2 step process. within entrypoint ~~~~~~~~~~~~~~~~~~ -The ProcessPool (not ThreadPool) worker is isolated within it's own -process. So the dirty nature of logging configuration has no effect -on other processes. +The :py:class:`multiprocessing.pool.Pool` (not ThreadPool) worker is +isolated within it's own process. So the dirty nature of logging +configuration has no effect on other processes. logging.config yaml file within package, logging_strict @@ -222,8 +222,8 @@ logging.config yaml file within another package within worker ~~~~~~~~~~~~~~ -entrypoint passes str_yaml to the (ProcessPool) worker. A worker calls -`setup_logging_yaml` with the yaml str +entrypoint passes str_yaml to the (:py:class:`multiprocessing.pool.Pool`) +worker. A worker calls `setup_logging_yaml` with the yaml str .. code:: text @@ -232,7 +232,7 @@ entrypoint passes str_yaml to the (ProcessPool) worker. A worker calls setup_logging_yaml(str_yaml) -To learn more about building UI apps that have `multiprocessing.pool.ProcessPool` +To learn more about building UI apps that have :py:class:`multiprocessing.pool.Pool` workers, check out the `asz` source code Whats next diff --git a/docs/getting_started/validation.rst b/docs/getting_started/validation.rst index c1bd732..28f474f 100644 --- a/docs/getting_started/validation.rst +++ b/docs/getting_started/validation.rst @@ -138,6 +138,6 @@ There is no command, instead here's a list: - mp_1_asz.worker.logging.config.yaml - For use with :py:class:`multiprocessing.pool.ProcessPool` workers + For use with :py:class:`multiprocessing.pool.Pool` workers Handler: logging.StreamHandler diff --git a/docs/index.rst b/docs/index.rst index 6697e35..3d334ca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 .. For details: https://github.com/msftcangoblowm/logging_strict/blob/master/NOTICE.txt -.. module:: asz +.. module:: logging_strict logging_strict =============== @@ -37,14 +37,14 @@ For logging.config yaml files, logging-strict does the following: - validates against a strictyaml schema - The schema is specifically tailored for the logging.config.handlers + The schema is specifically tailored for the external:python+ref:`logging.handlers` As long as the yaml is valid, will have the data types - logging.config.handlers expect + external:python+ref:`logging.handlers` expect - exports package data - Alternative to pkgutil.get_data + Alternative to :py:func:`pkgutil.get_data` Export data files using a pattern rather than one file at a time diff --git a/docs/objects-black.inv b/docs/objects-black.inv new file mode 100644 index 0000000000000000000000000000000000000000..2e5c2320d92c7639bb0f77a48a15ec9d9c4e5260 GIT binary patch literal 294 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~JCNNleaGFf!IN zG}1E!N`yfaSs@99WTYw-rRFAP=B1<-DdeXqlw@QUE2L%Sq$&XE^5sg{qv)Bt5Q*TU+~Vg$^kQv{w5c?Abcuh{K#Y2dz^QB6*}D**qrRo-HAro|7B* z`JbI}X2pgbU;H15KKD^wUUlFsfKAg9# hqw^g{s)tLea$#}9Bh3xJ_Ujy9{U&Ul_?@cU0|3IUc^&`& literal 0 HcmV?d00001 diff --git a/docs/objects-black.txt b/docs/objects-black.txt new file mode 100644 index 0000000..f9c2015 --- /dev/null +++ b/docs/objects-black.txt @@ -0,0 +1,7 @@ +# Sphinx inventory version 2 +# Project: black 23.12.1 +# Version: 23.12.1 +# The remainder of this file is compressed using zlib. +black py:module 0 https://black.readthedocs.io/en/stable - +black.files py:module 0 https://github.com/psf/black/blob/ea66d40dd7f1eaa20256e6fccaf6d7b853ccc541/src/black/files.py - +black.files.find_project_root py:function 1 https://github.com/psf/black/blob/ea66d40dd7f1eaa20256e6fccaf6d7b853ccc541/src/black/files.py#L57 - diff --git a/docs/objects-coverage.inv b/docs/objects-coverage.inv new file mode 100644 index 0000000000000000000000000000000000000000..c7d67cf086e03f35a48b37757cf1263dd1067570 GIT binary patch literal 327 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~JCR2MQ;qrz)81 zndlh+Wx^n;tl$D68L0|Iskw=nc`2zy3i)XYB^jB;3Tc@+sR}?kIX}0cD7CmaHASJc zI5RI@p(-acNsp`I*4_yhvkn{ZxZeLQ+Gcw_@6xi&U%U7vC+WOu5SEOVeDdDh&2Z_I zAdNSHm#*4|EB$=NBW{*y>;Gu_iL~^lEUonCFRZrAJeZxJAM!?{FJ0Vm=Zc$otJ0zt z^=We3ZrRl%CALLn^6Z<+8K(ZL4{N;GaP%@m`$9*XcU5w`gk`_!n%b5e-*?>h&$q-E zw|2eIy14AC8RyA0k&pI2-SJ6o`G)!C^2<1u&Up5f-P+@Ecqr@KN5(C>T#^6ZrSU$x TuUvcO)w%lJKNfcH`|XX literal 0 HcmV?d00001 diff --git a/docs/objects-coverage.txt b/docs/objects-coverage.txt new file mode 100644 index 0000000..8e1ca54 --- /dev/null +++ b/docs/objects-coverage.txt @@ -0,0 +1,9 @@ +# Sphinx inventory version 2 +# Project: coverage 7.4.0 +# Version: 7.4.0 +# The remainder of this file is compressed using zlib. +coverage py:module 0 https://coverage.readthedocs.io/en/latest - +coverage.multiproc py:module 0 https://github.com/nedbat/coveragepy/blob/b2b89a491bb607e958c432c17a5677b3abb6a952/coverage/multiproc.py - +coverage.control py:module 0 https://github.com/nedbat/coveragepy/blob/b2b89a491bb607e958c432c17a5677b3abb6a952/coverage/control.py - +coverage.control.Coverage py:class 1 https://coverage.readthedocs.io/en/latest/api_coverage.html - +coverage.control.Coverage.start py:method 1 https://coverage.readthedocs.io/en/latest/api_coverage.html#coverage.Coverage.start - diff --git a/docs/objects-logging-strict.inv b/docs/objects-logging-strict.inv new file mode 100644 index 0000000000000000000000000000000000000000..47b67a534011655e18f9f0de4c85503fb583f0ad GIT binary patch literal 5821 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 literal 0 HcmV?d00001 diff --git a/docs/objects-logging-strict.txt b/docs/objects-logging-strict.txt new file mode 100644 index 0000000..02a0740 --- /dev/null +++ b/docs/objects-logging-strict.txt @@ -0,0 +1,482 @@ +# Sphinx inventory version 2 +# Project: logging-strict 1.1.0 +# Version: 1.1.0 +# The remainder of this file is compressed using zlib. +__all__ py:data 1 code/yaml/logging_yaml_validate.html#all__ - +__version__ py:data 1 code/constants/version.html#version__ - +__version_tuple__ py:data 1 code/constants/version.html#version_tuple__ - +logging_strict py:module 0 index.html#module-$ - +logging_strict.constants py:module 0 code/constants/constants_general.html#module-$ - +logging_strict.constants.FALLBACK_LEVEL py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.LOG_FMT_DETAILED py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.LOG_FMT_SIMPLE py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.LOG_FORMAT py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.LOG_LEVEL_WORKER py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.PREFIX_DEFAULT py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.__all__ py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.__version__ py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants._dev py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants._map_release py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.g_app_name py:data 1 code/constants/constants_general.html#$ - +logging_strict.constants.version_info py:data 1 code/constants/constants_general.html#$ - +logging_strict.ep_validate_yaml py:module 0 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions py:module 0 code/constants/exceptions.html#module-$ - +logging_strict.exceptions.__all__ py:data 1 code/constants/exceptions.html#$ - +logging_strict.exceptions.LoggingStrictError py:exception 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictError.params.msg py:parameter 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictPackageNameRequired py:exception 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictPackageNameRequired.params.msg py:parameter 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictPackageStartFolderNameRequired py:exception 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictPackageStartFolderNameRequired.params.msg py:parameter 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictProcessCategoryRequired py:exception 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictProcessCategoryRequired.params.msg py:parameter 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictGenreRequired py:exception 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.exceptions.LoggingStrictGenreRequired.params.msg py:parameter 1 code/yaml/ep_validate_yaml.html#module-$ - +logging_strict.logging_api py:module 0 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.__all__ py:data 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.cb_true py:function 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml py:class 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.suffixes py:attribute 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.package_name py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.package_data_folder_start py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.category py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.genre py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.flavor py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.params.version_no py:parameter 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.file_stem py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.category py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.genre py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.flavor py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.version py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.file_suffix py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.file_name py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.package py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.dest_folder py:property 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.extract py:method 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.LoggingConfigYaml.extract.params.path_relative_package_dir py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other py:function 1 code/yaml/logging_api.html#module-$ - +logging_strict.logging_api.setup_ui_other.params.package_name py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other.params.package_data_folder_start py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other.params.genre py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other.params.flavor py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other.params.version_no py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_ui_other.params.package_start_relative_folder py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.ui_yaml_curated py:function 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.ui_yaml_curated.params.flavor py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.ui_yaml_curated.params.genre py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.ui_yaml_curated.params.version_no py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.ui_yaml_curated.params.package_start_relative_folder py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.worker_yaml_curated py:function 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.worker_yaml_curated.params.flavor py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.worker_yaml_curated.params.genre py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.worker_yaml_curated.params.package_start_relative_folder py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.worker_yaml_curated.params.version_no py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other py:function 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.package_name py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.package_data_folder_start py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.genre py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.flavor py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.version_no py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_api.setup_worker_other.params.package_start_relative_folder py:parameter 1 code/yaml/logging_api.html#$ - +logging_strict.logging_yaml_abc py:module 0 code/yaml/logging_yaml_abc.html#module-$ - +logging_strict.logging_yaml_abc.__all__ py:data 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.YAML_LOGGING_CONFIG_SUFFIX py:data 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.VERSION_FALLBACK py:data 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.setup_logging_yaml py:function 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.setup_logging_yaml.params.path_yaml py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.as_str py:function 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.as_str.params.package_name py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.as_str.params.file_name py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType py:class 1 code/yaml/logging_yaml_abc.html#module-$ - +logging_strict.logging_yaml_abc.LoggingYamlType.get_version py:staticmethod 1 code/yaml/logging_yaml_abc.html#module-$ - +logging_strict.logging_yaml_abc.LoggingYamlType.get_version.params.val 1 py:parameter 1 code/yaml/logging_yaml_abc.html#module-$ - +logging_strict.logging_yaml_abc.LoggingYamlType.pattern py:classmethod 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.category py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.flavor py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.genre py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.version py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.iter_yamls py:method 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.iter_yamls.params.path_dir py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.__subclasshook__ py:classmethod 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.__subclasshook__.params.C py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.file_stem py:property 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.file_name py:property 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.package py:property 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.dest_folder py:property 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.extract py:method 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.extract.params.path_relative_package_dir py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.as_str py:method 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.setup py:method 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.logging_yaml_abc.LoggingYamlType.setup.params.str_yaml py:parameter 1 code/yaml/logging_yaml_abc.html#$ - +logging_strict.tech_niques.captureLogs py:function 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.CaptureOutput py:class 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.get_locals py:function 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.detect_coverage py:function 1 code/tech_niques/coverage_misbehaves.html#module-$ - +logging_strict.tech_niques.LoggerRedirector py:class 1 code/tech_niques/logger_redirect.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes py:class 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes.CLASSMETHOD py:attribute 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes.STATICMETHOD py:attribute 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes.PROPERTY py:attribute 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes.METHOD py:attribute 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.ClassAttribTypes.DATA py:attribute 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.is_class_attrib_kind py:function 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.is_class_attrib_kind.params.cls py:parameter 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.is_class_attrib_kind.params.str_m py:parameter 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.is_class_attrib_kind.params.kind py:parameter 1 code/tech_niques/index.html#module-$ - +logging_strict.tech_niques.context_locals py:module 0 code/tech_niques/context_locals.html#module-$ - +logging_strict.tech_niques.context_locals.P py:data 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.T py:data 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.__all__ py:data 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.get_locals py:function 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.get_locals.params.args py:parameter 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.get_locals.params.func py:parameter 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.get_locals.params.func_path py:parameter 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.context_locals.get_locals.params.kwargs py:parameter 1 code/tech_niques/context_locals.html#$ - +logging_strict.tech_niques.logger_redirect py:module 0 code/tech_niques/logger_redirect.html#module-$ - +logging_strict.tech_niques.logger_redirect.LoggerRedirector py:class 1 code/tech_niques/logger_redirect.html#module-$ - +logging_strict.tech_niques.logger_redirect.LoggerRedirector.redirect_loggers.params.fake_stderr py:parameter 1 code/tech_niques/logger_redirect.html#$ - +logging_strict.tech_niques.logger_redirect.LoggerRedirector.redirect_loggers.params.fake_stdout py:parameter 1 code/tech_niques/logger_redirect.html#$ - +logging_strict.tech_niques.logger_redirect.LoggerRedirector.reset_loggers.params.fake_stderr py:parameter 1 code/tech_niques/logger_redirect.html#$ - +logging_strict.tech_niques.logger_redirect.LoggerRedirector.reset_loggers.params.fake_stdout py:parameter 1 code/tech_niques/logger_redirect.html#$ - +logging_strict.tech_niques.logging_capture py:module 0 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_formatter py:function 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_formatter.params.format_ py:parameter 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_logger py:function 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_logger.params.logger py:parameter 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_level_name py:function 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_level_name.params.logger_name py:parameter 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_level py:function 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._normalize_level.params.level py:paramater 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher py:class 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.records py:attribute 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.output py:attribute 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.getHandlerByName py:method 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.getHandlerByName.params.name py:parameter 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.getHandlerNames py:method 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.getLevelNo py:method 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._LoggingWatcher.getLevelNo.params.level_name 1 code/tech_niques/logging_capture.html#module-$ - +logging_strict.tech_niques.logging_capture._CapturingHandler py:class 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._CapturingHandler.watcher py:attribute 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._CapturingHandler.flush py:method 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._CapturingHandler.emit py:method 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._CapturingHandler.emit.params.record py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._LoggerStoredState py:class 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._LoggerStoredState.level_name py:attribute 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._LoggerStoredState.propagate py:attribute 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture._LoggerStoredState.handlers py:attribute 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.__all__ py:data 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogs py:function 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogs.params.format_ py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogs.params.level py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogs.params.logger py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogsMany py:function 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogsMany.params.format_ py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogsMany.params.levels py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.logging_capture.captureLogsMany.params.loggers py:parameter 1 code/tech_niques/logging_capture.html#$ - +logging_strict.tech_niques.stream_capture py:module 0 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.CaptureOutput py:class 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.__enter__ py:method 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.__exit__ py:method 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.__exit__.params.exc_type py:parameter 1 code/tech_niques/stream_capture.html#$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.__exit__.params.exc_value py:parameter 1 code/tech_niques/stream_capture.html#$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.__exit__.params.exc_tb py:parameter 1 code/tech_niques/stream_capture.html#$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.stderr py:property 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.CaptureOutput.stdout py:property 1 code/tech_niques/stream_capture.html#module-$ - +logging_strict.tech_niques.stream_capture.__all__ py:data 1 code/tech_niques/stream_capture.html#$ - +logging_strict.util.check_logging py:module 0 code/util/check_logging.html#module-$ - +logging_strict.util.check_logging.__all__ py:data 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.check_formatter.params.format_ py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.check_level.params.level py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.check_level_name.params.logger_name py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.check_logger.params.logger py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.is_assume_root.params.logger_name py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_logging.str2int.params.level py:parameter 1 code/util/check_logging.html#$ - +logging_strict.util.check_type py:module 0 code/util/check_type.html#module-$ - +logging_strict.util.check_type.__all__ py:attribute 1 code/util/check_type.html#$ - +logging_strict.util.check_type.check_int_verbosity.params.test py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.check_type.check_start_folder_importable.params.folder_start py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.check_type.check_type_path.params.module_path py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.check_type.check_type_path.params.msg_additional py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.check_type.is_not_ok.params.test py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.check_type.is_ok.params.test py:parameter 1 code/util/check_type.html#$ - +logging_strict.util.package_resource py:module 0 code/util/package_resource.html#module-$ - +logging_strict.util.package_resource.PackageResource.cache_extract py:method 1 code/util/package_resource.html#module-$ - +logging_strict.util.package_resource.PackageResource.cache_extract.params.base_folder_generator py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.cache_extract.params.cb_file_stem py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.cache_extract.params.cb_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.get_parent_paths py:method 1 code/util/package_resource.html#module-$ - +logging_strict.util.package_resource.PackageResource.get_parent_paths.params.cb_file_stem py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.get_parent_paths.params.cb_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.get_parent_paths.params.parent_count py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.get_parent_paths.params.path_relative_package_dir py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.package_data_folders py:method 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.package_data_folders.params.cb_file_stem py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.package_data_folders.params.cb_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.package_data_folders.params.package_name py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.package_data_folders.params.path_relative_package_dir py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.path_relative py:method 1 code/util/package_resource.html#module-$ - +logging_strict.util.package_resource.PackageResource.path_relative.params.parent_count py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.path_relative.params.path_relative_package_dir py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.path_relative.params.y py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract py:method 1 code/util/package_resource.html#module-$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.as_user py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.base_folder_generator py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.cb_file_stem py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.cb_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.is_overwrite py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PackageResource.resource_extract.params.path_dest py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PartStem.params.file_expected py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PartStem.params.test_file_stem py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PartSuffix.params.expected_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.PartSuffix.params.test_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource._LOGGER py:data 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.__all__ py:data 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_file_stem py:function 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_file_stem.params.expected_file_name py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_file_stem.params.test_file_name py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_suffix py:function 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_suffix.params.expected_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.filter_by_suffix.params.test_suffix py:parameter 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.g_module py:data 1 code/util/package_resource.html#$ - +logging_strict.util.package_resource.is_module_debug py:data 1 code/util/package_resource.html#$ - +logging_strict.util.pep518_read py:module 0 code/util/pep518_read.html#module-$ - +logging_strict.util.pep518_read.__all__ py:attribute 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_project_root py:function 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_project_root.params.srcs py:parameter 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_project_root.params.stdin_filename py:parameter 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_pyproject_toml py:function 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_pyproject_toml.params.path_search_start py:parameter 1 code/util/pep518_read.html#$ - +logging_strict.util.pep518_read.find_pyproject_toml.params.stdin_filename py:parameter 1 code/util/pep518_read.html#$ - +logging_strict.util.util_root py:module 0 code/util/util_root.html#module-$ - +logging_strict.util.util_root.__all__ py:data 1 code/util/util_root.html#$ - +logging_strict.util.util_root.g_module py:data 1 code/util/util_root.html#$ - +logging_strict.util.util_root._LOGGER py:data 1 code/util/util_root.html#$ - +logging_strict.util.util_root.g_is_root py:data 1 code/util/util_root.html#$ - +logging_strict.util.util_root.is_python_old py:data 1 code/util/util_root.html#$ - +logging_strict.util.util_root.get_logname py:function 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.ungraceful_app_exit py:function 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.is_root py:staticmethod 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.__slots__ py:attribute 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.path_home_root py:classmethod 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_root py:classmethod 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_root.params.callback py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_root.params.is_app_exit py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_root.params.is_raise_exc py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_not_root py:classmethod 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_not_root.params.callback py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_not_root.params.is_app_exit py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.check_not_root.params.is_raise_exc py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.set_owner_as_user py:classmethod 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.set_owner_as_user.params.is_as_user py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.IsRoot.set_owner_as_user.params.path_file py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.check_python_not_old.params.callback py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.check_python_not_old.params.is_app_exit py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.util_root.check_python_not_old.params.is_raise_exc py:parameter 1 code/util/util_root.html#module-$ - +logging_strict.util.xdg_folder py:module 0 code/util/xdg_folder.html#module-$ - +logging_strict.util.xdg_folder.__all__ py:data 1 code/util/xdg_folder.html#$ - +schema_logging_config py:data 1 code/yaml/logging_yaml_validate.html#$ - +validate_yaml_dirty py:function 1 code/yaml/logging_yaml_validate.html#$ - +api/index std:doc -1 api/index.html API +api/index:api std:label -1 api/index.html#api API +api/locals/index std:doc -1 api/locals/index.html Locals +api/locals/index:locals std:label -1 api/locals/index.html#locals Locals +api/locals/scope_b_locals std:doc -1 api/locals/scope_b_locals.html Scope B locals +api/locals/scope_b_locals:scope b locals std:label -1 api/locals/scope_b_locals.html#scope-b-locals Scope B locals +api/locals/unittest_setup_to_include_locals std:doc -1 api/locals/unittest_setup_to_include_locals.html unittest tb_locals +api/locals/unittest_setup_to_include_locals:unittest tb_locals std:label -1 api/locals/unittest_setup_to_include_locals.html#unittest-tb-locals unittest tb_locals +api/logging/api_capture_logging std:doc -1 api/logging/api_capture_logging.html Capture logging +api/logging/api_capture_logging:capture logging std:label -1 api/logging/api_capture_logging.html#capture-logging Capture logging +api/logging/api_logging_asynchronous std:doc -1 api/logging/api_logging_asynchronous.html Asynchronous logging +api/logging/api_logging_asynchronous:asynchronous logging std:label -1 api/logging/api_logging_asynchronous.html#asynchronous-logging Asynchronous logging +api/logging/api_logging_handlers std:doc -1 api/logging/api_logging_handlers.html Hardcoded dict +api/logging/api_logging_handlers:hardcoded dict std:label -1 api/logging/api_logging_handlers.html#hardcoded-dict Hardcoded dict +api/logging/api_logging_synchronous std:doc -1 api/logging/api_logging_synchronous.html Synchronous logging +api/logging/api_logging_synchronous:synchronous logging std:label -1 api/logging/api_logging_synchronous.html#synchronous-logging Synchronous logging +api/logging/api_logging_warnings std:doc -1 api/logging/api_logging_warnings.html logging warnings +api/logging/api_logging_warnings:capture asyncio std:label -1 api/logging/api_logging_warnings.html#capture-asyncio Capture asyncio +api/logging/api_logging_warnings:logging warnings std:label -1 api/logging/api_logging_warnings.html#logging-warnings logging warnings +api/logging/api_logging_warnings:suppress logging warnings std:label -1 api/logging/api_logging_warnings.html#suppress-logging-warnings Suppress logging warnings +api/logging/api_logging_warnings:warning.warn std:label -1 api/logging/api_logging_warnings.html#warning-warn warning.warn +api/logging/index std:doc -1 api/logging/index.html Logging +api/logging/index:logging std:label -1 api/logging/index.html#logging Logging +api_asynchronous_logging std:label -1 api/logging/api_logging_asynchronous.html#api-asynchronous-logging Asynchronous logging +api_capture_logging std:label -1 api/logging/api_capture_logging.html#api-capture-logging Capture logging +api_locals_scope_b_locals_capture std:label -1 api/locals/scope_b_locals.html#api-locals-scope-b-locals-capture Scope B locals +api_locals_unittest_tb std:label -1 api/locals/unittest_setup_to_include_locals.html#api-locals-unittest-tb unittest tb_locals +api_logging_handlers std:label -1 api/logging/api_logging_handlers.html#api-logging-handlers Hardcoded dict +api_logging_warning_warn std:label -1 api/logging/api_logging_warnings.html#api-logging-warning-warn warning.warn +api_logging_warnings std:label -1 api/logging/api_logging_warnings.html#api-logging-warnings logging warnings +api_logging_warnings_capture_asyncio std:label -1 api/logging/api_logging_warnings.html#api-logging-warnings-capture-asyncio Capture asyncio +api_synchronous_logging std:label -1 api/logging/api_logging_synchronous.html#api-synchronous-logging Synchronous logging +asyncio_logging_warnings_suppress std:label -1 api/logging/api_logging_warnings.html#asyncio-logging-warnings-suppress Suppress logging warnings +code/constants/constants_general std:doc -1 code/constants/constants_general.html Constants – general +code/constants/constants_general:constants -- general std:label -1 code/constants/constants_general.html#module-logging_strict.constants Constants – general +code/constants/constants_general:module objects std:label -1 code/constants/constants_general.html#module-objects Module objects +code/constants/constants_general:module private variables std:label -1 code/constants/constants_general.html#module-private-variables Module private variables +code/constants/constants_general:release phases std:label -1 code/constants/constants_general.html#release-phases Release phases +code/constants/exceptions std:doc -1 code/constants/exceptions.html Exceptions +code/constants/exceptions:exceptions std:label -1 code/constants/exceptions.html#module-logging_strict.exceptions Exceptions +code/constants/exceptions:module objects std:label -1 code/constants/exceptions.html#module-objects Module objects +code/constants/exceptions:module private variables std:label -1 code/constants/exceptions.html#module-private-variables Module private variables +code/constants/exceptions:public api std:label -1 code/constants/exceptions.html#public-api Public API +code/constants/index std:doc -1 code/constants/index.html Constants +code/constants/index:constants std:label -1 code/constants/index.html#constants Constants +code/constants/version std:doc -1 code/constants/version.html Version +code/constants/version:build / kitting (dev) std:label -1 code/constants/version.html#build-kitting-dev Build / kitting (dev) +code/constants/version:build / kitting (tagged) std:label -1 code/constants/version.html#build-kitting-tagged Build / kitting (tagged) +code/constants/version:development versions std:label -1 code/constants/version.html#development-versions Development versions +code/constants/version:install std:label -1 code/constants/version.html#install Install +code/constants/version:module private variables std:label -1 code/constants/version.html#module-private-variables Module private variables +code/constants/version:tagged versions std:label -1 code/constants/version.html#tagged-versions Tagged versions +code/constants/version:update the ``_version`` file std:label -1 code/constants/version.html#update-the-version-file Update the _version file +code/constants/version:version std:label -1 code/constants/version.html#version Version +code/index std:doc -1 code/index.html Code Manual +code/index:code manual std:label -1 code/index.html#code-manual Code Manual +code/public_api/index std:doc -1 code/public_api/index.html Public API +code/public_api/index:constants std:label -1 code/public_api/index.html#constants Constants +code/public_api/index:exceptions std:label -1 code/public_api/index.html#exceptions Exceptions +code/public_api/index:public api std:label -1 code/public_api/index.html#module-logging_strict Public API +code/public_api/index:step 1 std:label -1 code/public_api/index.html#step-1 Step 1 +code/public_api/index:step 2 std:label -1 code/public_api/index.html#step-2 Step 2 +code/public_api/index:types std:label -1 code/public_api/index.html#types Types +code/public_api/index:ui std:label -1 code/public_api/index.html#ui UI +code/public_api/index:worker process std:label -1 code/public_api/index.html#worker-process worker process +code/tech_niques/context_locals std:doc -1 code/tech_niques/context_locals.html Context locals +code/tech_niques/context_locals:and wait! there's more std:label -1 code/tech_niques/context_locals.html#and-wait-there-s-more And wait! There’s more +code/tech_niques/context_locals:context locals std:label -1 code/tech_niques/context_locals.html#module-logging_strict.tech_niques.context_locals Context locals +code/tech_niques/context_locals:module objects std:label -1 code/tech_niques/context_locals.html#module-objects Module objects +code/tech_niques/context_locals:module private variables std:label -1 code/tech_niques/context_locals.html#module-private-variables Module private variables +code/tech_niques/context_locals:one return value example std:label -1 code/tech_niques/context_locals.html#one-return-value-example One return value example +code/tech_niques/context_locals:want desired context's locals std:label -1 code/tech_niques/context_locals.html#want-desired-context-s-locals Want desired context’s locals +code/tech_niques/index std:doc -1 code/tech_niques/index.html Techniques +code/tech_niques/index:techniques std:label -1 code/tech_niques/index.html#techniques Techniques +code/tech_niques/logger_redirect std:doc -1 code/tech_niques/logger_redirect.html Logger redirect +code/tech_niques/logger_redirect:logger redirect std:label -1 code/tech_niques/logger_redirect.html#module-logging_strict.tech_niques.logger_redirect Logger redirect +code/tech_niques/logger_redirect:module objects std:label -1 code/tech_niques/logger_redirect.html#module-objects Module objects +code/tech_niques/logger_redirect:module private variables std:label -1 code/tech_niques/logger_redirect.html#module-private-variables Module private variables +code/tech_niques/logging_capture std:doc -1 code/tech_niques/logging_capture.html Logging capture +code/tech_niques/logging_capture:code snippet std:label -1 code/tech_niques/logging_capture.html#code-snippet Code snippet +code/tech_niques/logging_capture:into the rabbit hole std:label -1 code/tech_niques/logging_capture.html#into-the-rabbit-hole Into the rabbit hole +code/tech_niques/logging_capture:logging capture std:label -1 code/tech_niques/logging_capture.html#module-logging_strict.tech_niques.logging_capture Logging capture +code/tech_niques/logging_capture:module objects std:label -1 code/tech_niques/logging_capture.html#module-objects Module objects +code/tech_niques/logging_capture:module private variables std:label -1 code/tech_niques/logging_capture.html#module-private-variables Module private variables +code/tech_niques/logging_capture:sync and async logging std:label -1 code/tech_niques/logging_capture.html#sync-and-async-logging sync and async logging +code/tech_niques/logging_capture:the details std:label -1 code/tech_niques/logging_capture.html#the-details The details +code/tech_niques/logging_capture:tl;dr; std:label -1 code/tech_niques/logging_capture.html#tl-dr tl;dr; +code/tech_niques/logging_capture:unittest assertlogs/assertnologs std:label -1 code/tech_niques/logging_capture.html#unittest-assertlogs-assertnologs unittest assertLogs/assertNoLogs +code/tech_niques/stream_capture std:doc -1 code/tech_niques/stream_capture.html Stream capture +code/tech_niques/stream_capture:module objects std:label -1 code/tech_niques/stream_capture.html#module-objects Module objects +code/tech_niques/stream_capture:module private variables std:label -1 code/tech_niques/stream_capture.html#module-private-variables Module private variables +code/tech_niques/stream_capture:stream capture std:label -1 code/tech_niques/stream_capture.html#module-logging_strict.tech_niques.stream_capture Stream capture +code/util/check_logging std:doc -1 code/util/check_logging.html Check logging +code/util/check_logging:check logging std:label -1 code/util/check_logging.html#module-logging_strict.util.check_logging Check logging +code/util/check_logging:module objects std:label -1 code/util/check_logging.html#module-objects Module objects +code/util/check_logging:module private variables std:label -1 code/util/check_logging.html#module-private-variables Module private variables +code/util/check_type std:doc -1 code/util/check_type.html Check type +code/util/check_type:check type std:label -1 code/util/check_type.html#module-logging_strict.util.check_type Check type +code/util/check_type:module objects std:label -1 code/util/check_type.html#module-objects Module objects +code/util/check_type:module private variables std:label -1 code/util/check_type.html#module-private-variables Module private variables +code/util/index std:doc -1 code/util/index.html Util +code/util/index:util std:label -1 code/util/index.html#util Util +code/util/package_resource std:doc -1 code/util/package_resource.html Package resource +code/util/package_resource:example std:label -1 code/util/package_resource.html#example Example +code/util/package_resource:module objects std:label -1 code/util/package_resource.html#module-objects Module objects +code/util/package_resource:module private variables std:label -1 code/util/package_resource.html#module-private-variables Module private variables +code/util/package_resource:package resource std:label -1 code/util/package_resource.html#module-logging_strict.util.package_resource Package resource +code/util/package_resource:the problem std:label -1 code/util/package_resource.html#the-problem The Problem +code/util/pep518_read std:doc -1 code/util/pep518_read.html pep518 read pyproject.toml +code/util/pep518_read:module objects std:label -1 code/util/pep518_read.html#module-objects Module objects +code/util/pep518_read:module private variables std:label -1 code/util/pep518_read.html#module-private-variables Module private variables +code/util/pep518_read:pep518 read pyproject.toml std:label -1 code/util/pep518_read.html#module-logging_strict.util.pep518_read pep518 read pyproject.toml +code/util/util_root std:doc -1 code/util/util_root.html Is root +code/util/util_root:is root std:label -1 code/util/util_root.html#module-logging_strict.util.util_root Is root +code/util/util_root:module objects std:label -1 code/util/util_root.html#module-objects Module objects +code/util/util_root:module private variables std:label -1 code/util/util_root.html#module-private-variables Module private variables +code/util/xdg_folder std:doc -1 code/util/xdg_folder.html XDG folders +code/util/xdg_folder:module objects std:label -1 code/util/xdg_folder.html#module-objects Module objects +code/util/xdg_folder:module private variables std:label -1 code/util/xdg_folder.html#module-private-variables Module private variables +code/util/xdg_folder:xdg folders std:label -1 code/util/xdg_folder.html#module-logging_strict.util.xdg_folder XDG folders +code/yaml/ep_validate_yaml std:doc -1 code/yaml/ep_validate_yaml.html Validate YAML +code/yaml/ep_validate_yaml:module objects std:label -1 code/yaml/ep_validate_yaml.html#module-objects Module objects +code/yaml/ep_validate_yaml:validate yaml std:label -1 code/yaml/ep_validate_yaml.html#validate-yaml Validate YAML +code/yaml/index std:doc -1 code/yaml/index.html YAML +code/yaml/index:yaml std:label -1 code/yaml/index.html#yaml YAML +code/yaml/logging_api std:doc -1 code/yaml/logging_api.html YAML API +code/yaml/logging_api:module objects std:label -1 code/yaml/logging_api.html#module-objects Module objects +code/yaml/logging_api:module private variables std:label -1 code/yaml/logging_api.html#module-private-variables Module private variables +code/yaml/logging_api:yaml api std:label -1 code/yaml/logging_api.html#yaml-api YAML API +code/yaml/logging_yaml_abc std:doc -1 code/yaml/logging_yaml_abc.html YAML ABC +code/yaml/logging_yaml_abc:module objects std:label -1 code/yaml/logging_yaml_abc.html#module-objects Module objects +code/yaml/logging_yaml_abc:module private variables std:label -1 code/yaml/logging_yaml_abc.html#module-private-variables Module private variables +code/yaml/logging_yaml_abc:yaml abc std:label -1 code/yaml/logging_yaml_abc.html#yaml-abc YAML ABC +code/yaml/logging_yaml_validate std:doc -1 code/yaml/logging_yaml_validate.html YAML Validate +code/yaml/logging_yaml_validate:limitations std:label -1 code/yaml/logging_yaml_validate.html#limitations Limitations +code/yaml/logging_yaml_validate:module private variables std:label -1 code/yaml/logging_yaml_validate.html#module-private-variables Module private variables +code/yaml/logging_yaml_validate:yaml validate std:label -1 code/yaml/logging_yaml_validate.html#yaml-validate YAML Validate +configure_when_not_required std:label -1 getting_started/usage.html#configure-when-not-required - +error_purposefully_cause std:label -1 api/locals/unittest_setup_to_include_locals.html#error-purposefully-cause - +genindex std:label -1 genindex.html Index +getting_started std:label -1 getting_started/install.html#getting-started Getting Started +getting_started/exporting std:doc -1 getting_started/exporting.html Exporting +getting_started/exporting:exporting std:label -1 getting_started/exporting.html#exporting Exporting +getting_started/exporting:upgrade path std:label -1 getting_started/exporting.html#upgrade-path Upgrade path +getting_started/exporting:when std:label -1 getting_started/exporting.html#when When +getting_started/exporting:where / what std:label -1 getting_started/exporting.html#where-what Where / what +getting_started/index std:doc -1 getting_started/index.html Getting started +getting_started/index:getting started std:label -1 getting_started/index.html#getting-started Getting started +getting_started/install std:doc -1 getting_started/install.html Overview +getting_started/install:activate venv std:label -1 getting_started/install.html#activate-venv Activate venv +getting_started/install:getting started std:label -1 getting_started/install.html#getting-started Getting Started +getting_started/install:helpers std:label -1 getting_started/install.html#helpers helpers +getting_started/install:install -- optional (pypi.org) std:label -1 getting_started/install.html#install-optional-pypi-org Install – optional (pypi.org) +getting_started/install:install -- optional (source code) std:label -1 getting_started/install.html#install-optional-source-code Install – optional (source code) +getting_started/install:install -- production (pypi.org) std:label -1 getting_started/install.html#install-production-pypi-org Install – production (pypi.org) +getting_started/install:install -- production (source code) std:label -1 getting_started/install.html#install-production-source-code Install – production (source code) +getting_started/install:overview std:label -1 getting_started/install.html#overview Overview +getting_started/install:tech_niques std:label -1 getting_started/install.html#tech-niques tech_niques +getting_started/install:what's in |project_name| std:label -1 getting_started/install.html#what-s-in-project-name What’s in logging-strict +getting_started/install:whats next std:label -1 getting_started/install.html#whats-next Whats next +getting_started/usage std:doc -1 getting_started/usage.html app integration +getting_started/usage:app std:label -1 getting_started/usage.html#app app +getting_started/usage:app integration std:label -1 getting_started/usage.html#app-integration app integration +getting_started/usage:logging.config yaml -- within another package std:label -1 getting_started/usage.html#logging-config-yaml-within-another-package logging.config yaml – within another package +getting_started/usage:logging.config yaml -- within logging_strict std:label -1 getting_started/usage.html#logging-config-yaml-within-logging-strict logging.config yaml – within logging_strict +getting_started/usage:loggingstate std:label -1 getting_started/usage.html#loggingstate LoggingState +getting_started/usage:whats next std:label -1 getting_started/usage.html#whats-next Whats next +getting_started/usage:within entrypoint std:label -1 getting_started/usage.html#within-entrypoint within entrypoint +getting_started/usage:within worker std:label -1 getting_started/usage.html#within-worker within worker +getting_started/usage:worker std:label -1 getting_started/usage.html#worker worker +getting_started/validation std:doc -1 getting_started/validation.html Validation +getting_started/validation:curated files std:label -1 getting_started/validation.html#curated-files Curated files +getting_started/validation:pre-commit std:label -1 getting_started/validation.html#pre-commit pre-commit +getting_started/validation:validate a logging.config.yaml file std:label -1 getting_started/validation.html#validate-a-logging-config-yaml-file Validate a logging.config.yaml file +getting_started/validation:validation std:label -1 getting_started/validation.html#validation Validation +index std:doc -1 index.html logging_strict +index-overview std:label -1 index.html#$ Overview +index-toc std:label -1 index.html#$ Table of Contents +index:logging_strict std:label -1 index.html#logging-strict logging_strict +index:overview std:label -1 index.html#overview Overview +index:quick start std:label -1 index.html#quick-start Quick start +index:table of contents std:label -1 index.html#table-of-contents Table of Contents +index:why? std:label -1 index.html#why Why? +install_activate_venv std:label -1 getting_started/install.html#install-activate-venv Activate venv +install_dependencies_from_pypi_org std:label -1 getting_started/install.html#install-dependencies-from-pypi-org Install – production (pypi.org) +install_dependencies_from_source std:label -1 getting_started/install.html#install-dependencies-from-source Install – production (source code) +install_optional_dependencies_from_source std:label -1 getting_started/install.html#install-optional-dependencies-from-source Install – optional (source code) +install_optional_dependencies_pypi_org std:label -1 getting_started/install.html#install-optional-dependencies-pypi-org Install – optional (pypi.org) +install_overview std:label -1 getting_started/install.html#install-overview Overview +install_whats_next std:label -1 getting_started/install.html#install-whats-next Whats next +modindex std:label -1 py-modindex.html Module Index +py-modindex std:label -1 py-modindex.html Python Module Index +search std:label -1 search.html Search Page +toctree_api std:label -1 api/index.html#toctree-api - +toctree_api_locals std:label -1 api/locals/index.html#toctree-api-locals - +toctree_api_logging std:label -1 api/logging/index.html#toctree-api-logging - +toctree_constants std:label -1 code/constants/index.html#toctree-constants - +toctree_getting_started std:label -1 getting_started/index.html#toctree-getting-started - +toctree_logging_strict std:label -1 index.html#toctree-logging-strict - +toctree_project_base_folder std:label -1 code/index.html#toctree-project-base-folder - +toctree_tech_niques std:label -1 code/tech_niques/index.html#toctree-tech-niques - +toctree_util std:label -1 code/util/index.html#toctree-util - +toctree_yaml std:label -1 code/yaml/index.html#toctree-yaml - +whats_in_package std:label -1 getting_started/install.html#whats-in-package What’s in logging-strict diff --git a/docs/objects-python.inv b/docs/objects-python.inv new file mode 100644 index 0000000000000000000000000000000000000000..67d70471c78e134de3016f1ededac13e114cb144 GIT binary patch literal 136075 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 ?cLsbpdNwZK786R$ADTH< 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 literal 0 HcmV?d00001 diff --git a/docs/objects-strictyaml.inv b/docs/objects-strictyaml.inv new file mode 100644 index 0000000000000000000000000000000000000000..7076e158b861bf571ac3a7e03567098867882b5e GIT binary patch literal 612 zcmV-q0-OCKAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGk&baH89 zba`QIY#=c%H!d>@BOq2~a&u{KZaN?_E;lYS3L_v?Xk{RBWo=<;Ze(S0Aa78b#rNMXCQiPX<{x4c-qz2OHbo45CGu&{)$R_-X`TC zC^vQkQbiF55YnEii9KlzerY^m$^QCv(v}n|_OOSpbF>|IzD%u&?ZBi2H(>P0Hy6gq zTH`1mt(bkd$g-v4Rb7sSGnpXigtOLB7WzrwzVwYTz6$iLoDKi-Rm9ifR6x{?}%+9`3p(=mIOKb+WlF zQ~<5rFO(1N8EvY}2UvE#o|~%lI_hr0du5mDV=t{(IT}c2i`r`?fbj;KkUn>2W5^K8 zYOJ<1E4Rmy^x*_5;DQ^E3)~jksn#=xW|AIP#Vu{mwHlhfX96`=Y&mt-U-PVIs6oI94NXmrZI`a=H=cOu`0~uE zkiBPWSY!2ORQT0ZRi(vQeXRN_%x&~FahJiFi`Z#_8BoYm+oYcvy1iy0AIUV4FCWD literal 0 HcmV?d00001 diff --git a/docs/objects-textual.txt b/docs/objects-textual.txt new file mode 100644 index 0000000..e776d6f --- /dev/null +++ b/docs/objects-textual.txt @@ -0,0 +1,7 @@ +# Sphinx inventory version 2 +# Project: textual 0.46.0 +# Version: 0.46.0 +# The remainder of this file is compressed using zlib. +textual py:module 0 https://textual.textualize.io - +textual.logging py:module 0 https://textual.textualize.io/api/logging - +textual.logging.TextualHandler py:exception 1 https://textual.textualize.io/api/logging/#textual.logging.TextualHandler - diff --git a/docs/requirements.in b/docs/requirements.in index 9aeaef7..3343073 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -1,9 +1,11 @@ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/msftcangoblowm/asz/blob/master/NOTICE.txt +# For details: https://github.com/msftcangoblowm/logging_strict/blob/master/NOTICE.txt -c ../requirements/pins.pip -c ../requirements/prod.pip # currently same venv +scriv # for writing GitHub releases +sphobjinv # cross-references inventories, objects.inv sphinx sphinx-pyproject sphinx-autobuild diff --git a/docs/requirements.pip b/docs/requirements.pip index b3b9fbb..69d498b 100644 --- a/docs/requirements.pip +++ b/docs/requirements.pip @@ -6,6 +6,13 @@ apeye==1.4.1 # via sphinx-toolbox apeye-core==1.1.4 # via apeye +attrs==23.2.0 + # via + # -c docs/../requirements/prod.pip + # jsonschema + # referencing + # scriv + # sphobjinv autodocsumm==0.2.11 # via sphinx-toolbox babel==2.13.1 @@ -17,9 +24,17 @@ cachecontrol[filecache]==0.13.1 # cachecontrol # sphinx-toolbox certifi==2023.11.17 - # via requests + # via + # requests + # sphobjinv charset-normalizer==3.3.2 # via requests +click==8.1.7 + # via + # click-log + # scriv +click-log==0.4.0 + # via scriv colorama==0.4.6 # via sphinx-autobuild cssutils==2.9.0 @@ -66,15 +81,24 @@ importlib-metadata==7.0.0 # via sphinx jinja2==3.1.2 # via + # scriv # sphinx # sphinx-jinja2-compat # sphinx-licenseinfo +jsonschema==4.21.1 + # via sphobjinv +jsonschema-specifications==2023.12.1 + # via jsonschema livereload==2.6.3 # via sphinx-autobuild +markdown-it-py==3.0.0 + # via scriv markupsafe==2.1.3 # via # jinja2 # sphinx-jinja2-compat +mdurl==0.1.2 + # via markdown-it-py msgpack==1.0.7 # via cachecontrol natsort==8.4.0 @@ -98,15 +122,26 @@ pyyaml==6.0.1 # via # pychoosealicense # python-frontmatter +referencing==0.33.0 + # via + # jsonschema + # jsonschema-specifications requests==2.31.0 # via # apeye # cachecontrol + # scriv # sphinx +rpds-py==0.18.0 + # via + # jsonschema + # referencing ruamel-yaml==0.18.5 # via sphinx-toolbox ruamel-yaml-clib==0.2.8 # via ruamel-yaml +scriv==1.5.1 + # via -r docs/requirements.in six==1.16.0 # via # -c docs/../requirements/prod.pip @@ -169,6 +204,8 @@ sphinxcontrib-qthelp==1.0.6 # via sphinx sphinxcontrib-serializinghtml==1.1.9 # via sphinx +sphobjinv==2.3.1 + # via -r docs/requirements.in tabulate==0.9.0 # via sphinx-toolbox toml==0.10.2 diff --git a/igor.py b/igor.py index 59e33ac..d1bab54 100644 --- a/igor.py +++ b/igor.py @@ -45,8 +45,7 @@ def sanitize_kind(kind: Optional[str] = None) -> str: - """Allow kind to be a version str, 'current', 'tag' - """ + """Allow kind to be a version str, 'current', 'tag'""" if kind is None: # Fallback kind_ = "tag" @@ -114,6 +113,18 @@ def sanitize_tag(ver: str) -> str: return str_v +def do_quietly(command): + """Run a noisy command in a shell to suppress the output""" + proc = subprocess.run( + command, + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + return proc.returncode + + def do_show_env() -> None: """Show the environment variables.""" print("Environment:") @@ -424,8 +435,7 @@ def _arbritary_version(next_version: str) -> Optional[str]: def _tag_version(next_version="") -> Optional[str]: - """Get version potentially overriding it - """ + """Get version potentially overriding it""" # empty str means take current tag version ret = _arbritary_version(next_version) diff --git a/pyproject.toml b/pyproject.toml index 6a4a569..387f555 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,6 +146,7 @@ linkcheck_ignore = [ "https://github.com/psf/black/blob/e11eaf2f44d3db5713fb99bdec966ba974b60c8c/src/black/files.py#L46", # block.files.find_project_root "https://github.com/python/cpython/blob/db6f297d448ce46e58a5b90239a4779553333198/Lib/unittest/case.py#L193", # unittest.case._BaseTestCaseContext "https://github.com/python/cpython/blob/db6f297d448ce46e58a5b90239a4779553333198/Lib/unittest/case.py#L816", # unittest.case.assertLogs + "https://stackoverflow.com/a/69994813", # redirect ] exclude_patterns = [ "_build", @@ -210,6 +211,7 @@ sign = true "test_util_package_resource.py" = 4 "test_check_type.py" = 11 "tech_niques/test_logging_capture.py" = 12 +"test_pep518_read" = 14 # Required by docs/conf.py "tech_niques/test_docs_logging_capture.py" = 30 "test_versioning.py" = 31 "test_check_logging.py" = 32 diff --git a/requirements/kit.in b/requirements/kit.in new file mode 100644 index 0000000..ac1829a --- /dev/null +++ b/requirements/kit.in @@ -0,0 +1,20 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +-c pins.pip + +# Things needed to make distribution kits. +# "make upgrade" turns this into requirements/kit.pip. + +auditwheel +build +cibuildwheel +setuptools +setuptools-scm # versioning +wheel + +# Build has a windows-only dependency on colorama: +# https://github.com/pypa/build/blob/main/setup.cfg#L32 +# colorama;os_name == "nt" +# We copy it here so it can get pinned. +colorama diff --git a/requirements/kit.pip b/requirements/kit.pip new file mode 100644 index 0000000..c1a0f8d --- /dev/null +++ b/requirements/kit.pip @@ -0,0 +1,52 @@ +auditwheel==6.0.0 + # via -r requirements/kit.in +bashlex==0.18 + # via cibuildwheel +bracex==2.4 + # via cibuildwheel +build==1.0.3 + # via -r requirements/kit.in +certifi==2024.2.2 + # via cibuildwheel +cibuildwheel==2.16.5 + # via -r requirements/kit.in +colorama==0.4.6 + # via -r requirements/kit.in +filelock==3.13.1 + # via cibuildwheel +importlib-metadata==7.0.1 + # via build +packaging==23.2 + # via + # auditwheel + # build + # cibuildwheel + # setuptools-scm +platformdirs==4.2.0 + # via cibuildwheel +pyelftools==0.30 + # via auditwheel +pyproject-hooks==1.0.0 + # via build +setuptools-scm==8.0.4 + # via -r requirements/kit.in +tomli==2.0.1 + # via + # build + # cibuildwheel + # pyproject-hooks + # setuptools-scm +typing-extensions==4.9.0 + # via + # cibuildwheel + # setuptools-scm +wheel==0.42.0 + # via -r requirements/kit.in +zipp==3.17.0 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +setuptools==69.1.1 + # via + # -r requirements/kit.in + # setuptools-scm diff --git a/src/logging_strict/__init__.py b/src/logging_strict/__init__.py index 40e04ce..c1914b5 100644 --- a/src/logging_strict/__init__.py +++ b/src/logging_strict/__init__.py @@ -2,6 +2,7 @@ .. py:module:: logging_strict :platform: Unix :synopsis: Public interface + :noindex: .. moduleauthor:: Dave Faulkmore diff --git a/src/logging_strict/constants.py b/src/logging_strict/constants.py index 4af826e..28a021c 100644 --- a/src/logging_strict/constants.py +++ b/src/logging_strict/constants.py @@ -125,8 +125,9 @@ .. seealso:: - - :py:meth:`coverage.control.Coverage.start` - - :py:mod:`coverage.multiproc` + - external:coverage+ref:`coverage.control.Coverage.start` + + - external:coverage+ref:`coverage.multiproc` .. py:data:: version_info @@ -144,7 +145,7 @@ Semantic versioning. - setuptools-scm semantic versioning in: asz._version.__version__ + setuptools-scm semantic versioning in: logging_strict._version.__version__ .. code-block:: shell diff --git a/src/logging_strict/exceptions.py b/src/logging_strict/exceptions.py index 903958e..5c0c1a8 100644 --- a/src/logging_strict/exceptions.py +++ b/src/logging_strict/exceptions.py @@ -28,7 +28,7 @@ ------------------------- .. py:data:: __all__ - :type: tuple[str, str] + :type: tuple[str, str, str, str, str] :value: ("LoggingStrictError", "LoggingStrictPackageNameRequired", \ "LoggingStrictPackageStartFolderNameRequired", \ "LoggingStrictProcessCategoryRequired", "LoggingStrictGenreRequired") @@ -49,25 +49,55 @@ class LoggingStrictError(ValueError): + """Catchall back exception + + :ivar msg: The error message + :vartype msg: str + """ + def __init__(self, msg: str) -> None: super().__init__(msg) class LoggingStrictPackageNameRequired(LoggingStrictError): + """In entrypoint, package name is required + + :ivar msg: The error message + :vartype msg: str + """ + def __init__(self, msg: str) -> None: super().__init__(msg) class LoggingStrictPackageStartFolderNameRequired(LoggingStrictError): + """In entrypoint, package start data folder name is required + + :ivar msg: The error message + :vartype msg: str + """ + def __init__(self, msg: str) -> None: super().__init__(msg) class LoggingStrictProcessCategoryRequired(LoggingStrictError): + """Category is required + + :ivar msg: The error message + :vartype msg: str + """ + def __init__(self, msg: str) -> None: super().__init__(msg) class LoggingStrictGenreRequired(LoggingStrictError): + """Genre is required + + :ivar msg: The error message + :vartype msg: str + """ + def __init__(self, msg: str) -> None: super().__init__(msg) diff --git a/src/logging_strict/logging_api.py b/src/logging_strict/logging_api.py index 72010f4..b6403df 100644 --- a/src/logging_strict/logging_api.py +++ b/src/logging_strict/logging_api.py @@ -175,7 +175,7 @@ class LoggingConfigYaml(LoggingYamlType): :vartype category: - :py:class:`LoggingConfigCategory` | str | :py:class:`~typing.Any` | None + external:logging-strict+ref:`~logging_strict.constants.LoggingConfigCategory` | str | :py:class:`~typing.Any` | None :ivar genre: @@ -183,19 +183,20 @@ class LoggingConfigYaml(LoggingYamlType): a library of yaml files that can be used with a particular UI framework or worker type - :vartype genre: str or None + :vartype genre: str | None :ivar flavor: Unique identifier name given to a particular :py:mod:`logging.config` yaml. This name is slugified. Meaning period and underscores converted to hyphens - Flavor is a very terse description, for a :paramref:`genre`, how - this yaml differs from others. If completely generic, call it + Flavor is a very terse description, for a + external:logging-strict+ref:`~logging_strict.logging_api.LoggingConfigYaml.genre`, + how this yaml differs from others. If completely generic, call it ``generic``. If different handlers or formatters or filters are used, what is the yaml's purpose? - :vartype flavor: str or None + :vartype flavor: str | None :ivar version_no: .. line-block:: @@ -205,14 +206,14 @@ class LoggingConfigYaml(LoggingYamlType): **Not** the version of the yaml spec. Don't confuse the two. - :vartype version_no: :py:class:`~typing.Any` or None + :vartype version_no: :py:class:`~typing.Any` | None :raises: - - :py:exc:`LoggingStrictPackageNameRequired` -- Package name required for - determining destination folder + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictPackageNameRequired` + -- Package name required for determining destination folder - - :py:exc:`LoggingStrictPackageStartFolderNameRequired` -- Package base - data folder name is required + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictPackageStartFolderNameRequired` + -- Package base data folder name is required """ @@ -285,8 +286,8 @@ def file_stem(self): :rtype: str :raises: - - :py:exc:`LoggingStrictGenreRequired` --- Genre is required. e.g. - textual pyside mp rabbitmq + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictGenreRequired` + -- Genre is required. e.g. textual pyside mp rabbitmq .. todo:: slugify @@ -322,13 +323,13 @@ def genre(self): So can use: - - :py:meth:`iter_yaml` + - external:logging-strict+ref:`LoggingYamlType.iter_yamls ` Can't use - - :py:meth:`extract` + - external:logging-strict+ref:`LoggingConfigYaml.extract ` - - :py:meth:`setup` + - - external:logging-strict+ref:`LoggingYamlType.setup ` Genre is the UI framwork or worker characteristic @@ -346,10 +347,12 @@ def genre(self): def flavor(self): """Specific implementation of a genre. - E.g. multiple :py:mod:`logging.config` yaml files for :py:mod:`textual` + E.g. multiple :py:mod:`logging.config` yaml files for + external:textual+ref:`textual` - Uses the handler, TextualHandler, but each has some variation. - Like custom formaters or filters + Uses the handler, + external:textual+ref:`textual.logging.TextualHandler`, but each + has some variation. Like custom formaters or filters So the flavor may be @@ -394,7 +397,8 @@ def file_suffix(self): :rtype: str :raises: - - :py:exc:`LoggingStrictProcessCategoryRequired` -- Requires category + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictProcessCategoryRequired` + -- Requires category """ if is_not_ok(self.category): @@ -420,9 +424,11 @@ def file_name(self): :rtype: str :raises: - - :py:exc:`LoggingStrictProcessCategoryRequired` -- Category required + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictProcessCategoryRequired` + -- Category required - - :py:exc:`LoggingStrictGenreRequired` -- Genre required + - external:logging-strict+ref:`logging_strict.exceptions.LoggingStrictGenreRequired` + -- Genre required """ try: @@ -693,7 +699,7 @@ def worker_yaml_curated( changes are not overwritten Process 2nd step is calling: - :py:func:`logging_strict.logging_yaml_abc.setup_logging_yaml` + external:logging-strict+ref:`setup_logging_yaml ` :param genre: @@ -716,8 +722,8 @@ def worker_yaml_curated( :param version_no: Default 1. Version of this particular - :paramref:`category`. **Not** the version of the - yaml spec. Don't confuse the two. + :paramref:`~logging_strict.logging_api.worker_yaml_curated.params.genre`. + **Not** the version of the yaml spec. Don't confuse the two. :type version_no: :py:class:`~typing.Any` or None :param package_start_relative_folder: @@ -733,8 +739,8 @@ def worker_yaml_curated( - :py:exc:`FileNotFoundError` -- yaml file not found within package - - :py:exc:`strictyaml.exceptions.YAMLValidationError` -- yaml file - validation failed + - external:strictyaml+ref:`strictyaml.exceptions.YAMLValidationError` + -- yaml file validation failed - :py:exc:`AssertionError` -- Expecting one yaml file, many found @@ -773,7 +779,7 @@ def setup_worker_other( Use this if located in another package Process 2nd step is calling: - :py:func:`logging_strict.logging_yaml_abc.setup_logging_yaml` + external:logging-strict+ref:`setup_logging_yaml ` :param package_name: @@ -783,8 +789,9 @@ def setup_worker_other( :type package_name: str :param package_data_folder_start: - Within :paramref:`package_name`, base data folder name. Not a - relative path. Does not assume ``data`` + Within + :paramref:`~logging_strict.logging_api.setup_worker_other.params.package_name`, + base data folder name. Not a relative path. Does not assume ``data`` :type package_data_folder_start: str :param genre: @@ -799,8 +806,9 @@ def setup_worker_other( Default "asz". Unique identifier name given to a particular :py:mod:`logging.config` yaml. Should be one word w/o special characters - Flavor is a very terse description, for a :paramref:`genre`, how - this yaml differs from others. If completely generic, call it + Flavor is a very terse description, for a + :paramref:`~logging_strict.logging_api.setup_worker_other.params.genre`, + how this yaml differs from others. If completely generic, call it ``generic``. If different handlers or formatters or filters are used, what is the yaml's purpose? @@ -808,8 +816,8 @@ def setup_worker_other( :param version_no: Default 1. Version of this particular - :paramref:`category`. **Not** the version of the - yaml spec. Don't confuse the two. + :paramref:`~logging_strict.logging_api.setup_worker_other.params.genre`. + **Not** the version of the yaml spec. Don't confuse the two. :type version_no: :py:class:`~typing.Any` or None :param package_start_relative_folder: @@ -825,16 +833,16 @@ def setup_worker_other( - :py:exc:`FileNotFoundError` -- yaml file not found within package - - :py:exc:`strictyaml.exceptions.YAMLValidationError` -- yaml file - validation failed + - external:strictyaml+ref:`strictyaml.exceptions.YAMLValidationError` + -- yaml file validation failed - :py:exc:`AssertionError` -- Expecting one yaml file, many found - - :py:exc:`LoggingStrictPackageNameRequired` -- Which package - are the logging.config yaml in? + - external:logging-strict+ref:`logging_strict.LoggingStrictPackageNameRequired` + -- Which package are the logging.config yaml in? - - :py:exc:`LoggingStrictPackageStartFolderNameRequired` -- Within the - provided package, the package base data folder name + - external:logging-strict+ref:`logging_strict.LoggingStrictPackageStartFolderNameRequired` + -- Within the provided package, the package base data folder name """ try: @@ -867,7 +875,7 @@ class LoggingState: If run from app:: - logging is redirected to ``textual.logging.TextualHandler`` + logging is redirected to external:textual+ref:`textual.logging.TextualHandler` See |textual_api|`logging` diff --git a/src/logging_strict/logging_yaml_abc.py b/src/logging_strict/logging_yaml_abc.py index f22b027..e9f10dd 100644 --- a/src/logging_strict/logging_yaml_abc.py +++ b/src/logging_strict/logging_yaml_abc.py @@ -1,18 +1,28 @@ """ .. py:module:: logging_strict.logging_yaml_abc :platform: Unix - :synopsis: Refresh or reload logging state of worker + :synopsis: base class of logging_yaml implementations .. moduleauthor:: Dave Faulkmore .. +Base class of logging_yaml implementations + +:py:mod:`logging.config` yaml config files are exported to +:code:`$HOME/.locals/share/[app name]` + +One for the app and another for worker(s). + +``QA Tester`` can edit the yaml config files, **before using**, +ensure validation passes! + Module private variables ------------------------- .. py:data:: __all__ - :type: tuple[str, str] - :value: ("LoggingYamlType", "YAML_LOGGING_CONFIG_SUFFIX") + :type: tuple[str, str, str] + :value: ("LoggingYamlType", "YAML_LOGGING_CONFIG_SUFFIX", "setup_logging_yaml") Module exports @@ -23,6 +33,11 @@ For logging.config YAML files, define file extension (Suffixes) Differentiates from other .yaml files +.. py:data:: VERSION_FALLBACK + :type: str + :value: "1" + + Initial version of :py:mod:`logging.config` YAML files Module objects --------------- @@ -69,21 +84,16 @@ def setup_logging_yaml(path_yaml): """Loads :py:mod:`logging.config` configuration. - :py:mod:`logging.config` yaml config files are exported to - :code:`$HOME/.locals/share/[app name]` - One for the app and another for worker(s). - - ``QA Tester`` can edit the yaml config files, **before using**, - ensure validation passes! + Can pass in a path or a the YAML str :param path_yaml: :py:mod:`logging.config` YAML file path :type path_yaml: :py:class:`~typing.Any` :raises: - - :py:exc:`strictyaml.exceptions.YAMLValidationError` -- Invalid. - Validation against logging.config schema failed + - external:python+ref:`strictyaml.YAMLValidationError` -- Invalid. + Validation against logging.config schema failed """ if TYPE_CHECKING: @@ -135,7 +145,7 @@ def as_str(package_name: str, file_name: str) -> str: :raises: - - :py:exc:`strictyaml.exceptions.YAMLValidationError` -- Invalid. + - external:python+ref:`strictyaml.YAMLValidationError` -- Invalid. Validation against logging.config schema failed - :py:exc:`FileNotFoundError` -- Could not find logging config YAML file @@ -153,7 +163,7 @@ def as_str(package_name: str, file_name: str) -> str: if is_exists: # test load the yaml file str_yaml = path_yaml.read_text() - """raises :py:exc:`strictyaml.exceptions.YAMLValidationError` + """raises external:python+ref:`strictyaml.YAMLValidationError` If another yaml implementation, the exception raised will be that implementation specific """ @@ -214,33 +224,35 @@ def pattern( - genre and flavor - :type category: :py:class:`~logging_strict.constants.LoggingConfigCategory` + :param category: + :type category: str | None :param genre: If UI: "textual" or "rich". If worker: "stream". Then can have a library of yaml files that can be used with a particular UI framework or worker type - :type genre: str or None + :type genre: str | None :param flavor: Unique identifier name given to a particular :py:mod:`logging.config` yaml. This name is slugified. Meaning period and underscores converted to hyphens - Flavor is a very terse description, for a :paramref:`genre`, how - this yaml differs from others. If completely generic, call it + Flavor is a very terse description, for a + :paramref:`logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.genre`, + how this yaml differs from others. If completely generic, call it ``generic``. If different handlers or formatters or filters are used, what is the yaml's purpose? - :type flavor: str or None + :type flavor: str | None :param version: Default "1". Version of this particular - :paramref:`category`. **Not** the version of the - yaml spec. Don't confuse the two. + :paramref:`logging_strict.logging_yaml_abc.LoggingYamlType.pattern.params.category`. + **Not** the version of the yaml spec. Don't confuse the two. - :type version: :py:class:`~typing.Any` or None + :type version: :py:class:`~typing.Any` | None :returns: Pattern used with :py:func:`glob.glob` to find files :rtype: str """ @@ -251,7 +263,8 @@ def pattern( file_suffixes = f".{category}{cls.suffixes}" else: """empty str, unsupported type, or None, or str with only - whitespace would be stopped by type[LoggingYamlType] + whitespace would be stopped by + type[ external:logging-strict+ref:`~logging_strict.logging_yaml_abc.LoggingYamlType` ] constructor producing a ValueError """ file_suffixes = f".*{cls.suffixes}" @@ -274,7 +287,8 @@ def pattern( def iter_yamls(self, path_dir): """Conducts a recursive search thru the folder tree starting from package base data folder, further narrow search by relative - (to package base data folder) path, :paramref:`path_dir` + (to package base data folder) path, + :paramref:`logging_strict.logging_yaml_abc.LoggingYamlType.iter_yamls.params.path_dir` Iterator of absolute path of search results @@ -288,7 +302,7 @@ def iter_yamls(self, path_dir): ``True`` if at least one yaml file exists in folder otherwise ``False`` - :rtype: Iterator[Path] + :rtype: Iterator[ :py:class:`~pathlib.Path` ] """ cls = type(self) # print(f"{self.category} {self.genre} {self.flavor} {self.version}") @@ -319,7 +333,9 @@ def iter_yamls(self, path_dir): @classmethod def __subclasshook__(cls, C): - """A class wanting to be :py:class:`.LoggingYamlType`, minimally requires: + """A class wanting to be + external:logging-strict+ref:`~logging_strict.logging_yaml_abc.LoggingYamlType`, + minimally requires: Properties: @@ -341,7 +357,7 @@ def __subclasshook__(cls, C): Then register itself :code:`LoggingYamlType.register(AnotherDatumClass)` or subclass - :py:class:`.LoggingYamlType` + external:logging-strict+ref:`~logging_strict.logging_yaml_abc.LoggingYamlType` :param C: @@ -350,8 +366,10 @@ def __subclasshook__(cls, C): :type C: :py:class:`~typing.Any` :returns: - ``True`` implements :py:class:`.LoggingYamlType` interface or is a subclass. - ``False`` not a :py:class:`.LoggingYamlType` + ``True`` implements + external:logging-strict+ref:`~logging_strict.logging_yaml_abc.LoggingYamlType` + interface or is a subclass. ``False`` not a + external:logging-strict+ref:`~logging_strict.logging_yaml_abc.LoggingYamlType` :rtype: bool """ @@ -448,12 +466,12 @@ def as_str(self): :rtype: str :raises: - - :py:exc:`strictyaml.exceptions.YAMLValidationError` -- Invalid. + - external:python+ref:`strictyaml.exceptions.YAMLValidationError` -- Invalid. Validation against logging.config schema failed - :py:exc:`FileNotFoundError` -- Could not find logging config YAML file - - :py:exc:`~logging_strict.exceptions.LoggingStrictGenreRequired` -- + - external:python+ref:`~logging_strict.exceptions.LoggingStrictGenreRequired` -- Genre required to get file name """ @@ -468,14 +486,14 @@ def as_str(self): return ret def setup(self, str_yaml): # pragma: no cover dangerous - """A :py:class:`multiprocessing.pool.ProcessPool` worker, needs - to be feed the :py:mod:`logging.config` YAML file + """Only called by app, not worker. For worker, is a 2 step + process, not 1. - xdg user data folder: :code:`$HOME/.local/share/[app name]` - - During testing call :py:meth:`.LoggingYamlType.extract` + A :py:class:`multiprocessing.pool.Pool` worker, needs + to be feed the contents of the :py:mod:`logging.config` + YAML file - Only called by app, not worker. For worker, is a 2 step process, not 1. + xdg user data folder: :code:`$HOME/.local/share/[app name]` :param str_yaml: :py:mod:`logging.config` yaml str :type str_yaml: str diff --git a/src/logging_strict/logging_yaml_abc.pyi b/src/logging_strict/logging_yaml_abc.pyi index b7cbd7b..f226e4a 100644 --- a/src/logging_strict/logging_yaml_abc.pyi +++ b/src/logging_strict/logging_yaml_abc.pyi @@ -22,30 +22,29 @@ __all__: Final[tuple[str, str, str]] YAML_LOGGING_CONFIG_SUFFIX: Final[str] PATTERN_DEFAULT: Final[str] +VERSION_FALLBACK: str def setup_logging_yaml(path_yaml: Any) -> None: ... def as_str(package_name: str, file_name: str) -> str: ... -VERSION_FALLBACK: str - class LoggingYamlType(abc.ABC): @staticmethod def get_version(val: Any) -> str: ... @classmethod def pattern( cls, - category: Optional[str] = None, - genre: Optional[str] = None, - flavor: Optional[str] = None, - version: Optional[str] = VERSION_FALLBACK, + category: str | None = None, + genre: str | None = None, + flavor: str | None = None, + version: str | None = VERSION_FALLBACK, ) -> str: ... def iter_yamls( self, path_dir: Path, - category: Optional[str] = None, - genre: Optional[str] = None, - flavor: Optional[str] = None, - version: Optional[str] = VERSION_FALLBACK, + category: str | None = None, + genre: str | None = None, + flavor: str | None = None, + version: str | None = VERSION_FALLBACK, ) -> Iterator[Path]: ... @classmethod def __subclasshook__(cls, C: Any) -> bool: ... diff --git a/src/logging_strict/logging_yaml_validate.py b/src/logging_strict/logging_yaml_validate.py index 8f63bc4..f658722 100644 --- a/src/logging_strict/logging_yaml_validate.py +++ b/src/logging_strict/logging_yaml_validate.py @@ -46,8 +46,6 @@ :py:mod:`strictyaml` has no support for :py:class:`datetime.time` - - Module private variables ------------------------- @@ -58,7 +56,7 @@ Module exports .. py:data:: schema_logging_config - :type: strictyaml.Validator + :type: external:strictyaml+ref:`strictyaml.validators.Validator` strictyaml schema to compare the yaml against diff --git a/src/logging_strict/tech_niques/__init__.py b/src/logging_strict/tech_niques/__init__.py index e292a9f..72f94f9 100644 --- a/src/logging_strict/tech_niques/__init__.py +++ b/src/logging_strict/tech_niques/__init__.py @@ -9,6 +9,9 @@ Conveniently exports all technique helpers +Module private variables +---------------------------- + .. py:data:: __all__ :type: tuple[str, str, str] :value: ("get_locals", "is_class_attrib_kind", "ClassAttribTypes", \ @@ -16,10 +19,12 @@ This modules exports +Module objects +---------------------------- + """ import enum import inspect -from typing import Any from .context_locals import get_locals from .coverage_misbehaves import detect_coverage @@ -39,7 +44,7 @@ class ClassAttribTypes(enum.Enum): - """As understood by :py:func:`inspect.classify_class_attrs` + """As understood by external:python+ref:`inspect.classify_class_attrs` .. py:attribute:: CLASSMETHOD :type: str @@ -80,14 +85,24 @@ class ClassAttribTypes(enum.Enum): DATA = "data" -def is_class_attrib_kind(cls: type[Any], str_m: Any, kind: ClassAttribTypes) -> bool: +def is_class_attrib_kind(cls, str_m, kind): """For testing an ABC implementation :param cls: A class - :type cls: type[Any] + :type cls: type[ :py:class:`~typing.Any` ] :param str_m: A class member's name. Check the class interface is exists - :type str_m: str - :returns: ``True`` if is expected :paramref:`kind` otherwise ``False`` + :type str_m: :py:class:`~typing.Any` + :param kind: class attribute type + :type kind: + + external:logging-strict+ref:`~logging_strict.tech_niques.ClassAttribTypes` + + :returns: + + ``True`` if is expected + :paramref:`logging_strict.tech_niques.is_class_attrib_kind.params.kind` + otherwise ``False`` + :rtype: bool :raises: diff --git a/src/logging_strict/tech_niques/__init__.pyi b/src/logging_strict/tech_niques/__init__.pyi index 2f885ff..c85b67c 100644 --- a/src/logging_strict/tech_niques/__init__.pyi +++ b/src/logging_strict/tech_niques/__init__.pyi @@ -1,4 +1,6 @@ +import enum import sys +from typing import Any if sys.version_info >= (3, 8): from typing import Final @@ -6,3 +8,14 @@ else: from typing_extensions import Final __all__: Final[tuple[str, str, str, str, str, str, str]] + +class ClassAttribTypes(enum.Enum): + CLASSMETHOD = "class method" + STATICMETHOD = "static method" + PROPERTY = "property" + METHOD = "method" + DATA = "data" + +def is_class_attrib_kind( + cls: type[Any], str_m: Any, kind: ClassAttribTypes +) -> bool: ... diff --git a/src/logging_strict/tech_niques/context_locals.py b/src/logging_strict/tech_niques/context_locals.py index 4c8b002..df63d00 100644 --- a/src/logging_strict/tech_niques/context_locals.py +++ b/src/logging_strict/tech_niques/context_locals.py @@ -140,16 +140,16 @@ def main(): This modules exports .. py:data:: T - :type: :py:class:`typing.TypeVar` + :type: typing.TypeVar :value: typing.TypeVar("T") - Equivalent to :py:class:`typing.Any` + Equivalent to :py:class:`~typing.Any` .. py:data:: P - :type: :py:class:`typing_extensions.ParamSpec` + :type: typing.ParamSpec :value: typing_extensions.ParamSpec('P') - Equivalent to :py:class:`typing.Any` + Equivalent to :py:class:`~typing.Any` Module objects ---------------------------- @@ -185,7 +185,15 @@ def main(): def _func(param_a: str, param_b: int | None = 10) -> str: - """Sample function to inspect the locals""" + """Sample function to inspect the locals + + :param param_a: To whom am i speaking to? Name please + :type param_a: str + :param param_b: Default 10. A int to be modified + :type param_b: int | None + :returns: Greetings from this function to our adoring fans + :rtype: str + """ if is_not_ok(param_a): param_a = "" else: # pragma: no cover @@ -279,12 +287,12 @@ def __call__( # type: ignore[misc] # missing self non-static method def get_locals( - func_path: str, - func: Callable[..., Any], + func_path, + func, /, - *args: P.args, - **kwargs: P.kwargs, -) -> tuple[T, dict[str, Any]]: + *args, + **kwargs, +): """Uses :py:func:`patch ` to retrieve the tested functions locals and return value! @@ -296,13 +304,13 @@ def get_locals( :param func_path: dotted path to func :type func_path: str :param func: The func - :type func: Callable[..., Any] + :type func: Callable[..., :py:class:`~typing.Any`] :param args: Positional arguments - :type args: P.args + :type args: external:python+ref:`typing.ParamSpecArgs` :param kwargs: Optional (keyword) arguments - :type kwargs: P.kwargs + :type kwargs: external:python+ref:`typing.ParamSpecKwargs` :returns: Tuple containing return value and the locals - :rtype: tuple[T, dict[str, Any]] + :rtype: tuple[T, dict[str, :py:class:`~typing.Any`]] """ with patch( func_path, diff --git a/src/logging_strict/tech_niques/logger_redirect.py b/src/logging_strict/tech_niques/logger_redirect.py index 2c7e33c..e376404 100644 --- a/src/logging_strict/tech_niques/logger_redirect.py +++ b/src/logging_strict/tech_niques/logger_redirect.py @@ -59,7 +59,7 @@ class LoggerRedirector: # pragma: no cover import sys import logging - from asz.util.unittest_inspect import LoggerRedirector + from logging_strict.tech_niques import LoggerRedirector def setUp(self): # unittest has reassigned sys.stdout and sys.stderr by this point diff --git a/src/logging_strict/tech_niques/logging_capture.py b/src/logging_strict/tech_niques/logging_capture.py index 2f4af20..10bf967 100644 --- a/src/logging_strict/tech_niques/logging_capture.py +++ b/src/logging_strict/tech_niques/logging_capture.py @@ -44,8 +44,7 @@ import logging import sys - from logging_strict.stream_capture import CaptureOutput - from logging_strict.logging_capture import captureLogs + from logging_strict.tech_niques import CaptureOutput, captureLogs msg0 = 'first msg' msg1 = 'second msg' @@ -78,8 +77,8 @@ When when chaining context managers together, it's a one liner In addition to the two context managers above, in unittests, -:py:obj:`unittest.mock.patch` can alter modules behavior and results without -changes to the modules source code. +exteral:python+ref:`unittest.mock.patch` can alter modules behavior +and results without changes to the modules source code. sync and async logging --------------------------- @@ -88,7 +87,7 @@ :ref:`synchronous logging `, a context manager to fix the issue of redirecting stdout/stderr. - :py:obj:`LoggerRedirector ` + external:logging-strict+ref:`LoggerRedirector ` :code:`from logging_strict.tech_niques import LoggerRedirector` @@ -101,13 +100,13 @@ tl;dr; ^^^^^^^ -:py:obj:`unittest.TestCase.assertLogs` assertion makes it ill-suited for -general use, besides within unittests +external:python+ref:`unittest.TestCase.assertLogs` assertion makes it +ill-suited for general use, besides within unittests The details ^^^^^^^^^^^^ -:py:obj:`unittest.TestCase.assertLogs` does log capturing +external:python+ref:`unittest.TestCase.assertLogs` does log capturing **Tests** that **at least one message is logged** on the logger or one of its children, with at least the given level. @@ -142,9 +141,10 @@ def test_assert_logging_output(self): .. seealso:: - assertLogs :py:meth:`[docs] ` `[source] `_ + assertLogs external:python+ref:`[docs] ` + `[source] `_ - assertNoLogs (py310+) :py:meth:`[docs] ` + assertNoLogs (py310+) external:python+ref:`[docs] ` Into the rabbit hole --------------------------- @@ -185,10 +185,6 @@ def test_assert_logging_output(self): import contextlib import logging import sys -from typing import ( - Any, - Optional, -) import attrs @@ -287,14 +283,14 @@ def _hierlevel(logger): pass -def _normalize_level( - level: Optional[Any], -) -> str: - """For captureLogs, normalize level +def _normalize_level(level): + """For + external:logging-strict+ref:`logging_strict.tech_niques.logging_capture.captureLogs`, + normalize level :param level: str or int or logging.INFO (, etc) or :py:class:`~typing.Any` - :type level: :py:class:`~typing.Any` or ``None`` + :type level: :py:class:`~typing.Any` | None :returns: Normalized logger level name :rtype: str :raise: @@ -375,16 +371,16 @@ def _normalize_level( return level_name -def _normalize_level_name( - logger_name: Optional[Any], -) -> str: - """For captureLogs, normalize level names +def _normalize_level_name(logger_name): + """For + external:logging-strict+ref:`logging_strict.tech_niques.logging_capture.captureLogs`, + normalize level names :param logger_name: - Logger name can be a :py:class:`logging.Logger`, str + Logger name can be a external:python+ref:`logging.Logger`, str - :type logger_name: :py:class:`~typing.Any` or ``None`` + :type logger_name: :py:class:`~typing.Any` | None :returns: Normalized logger level name :rtype: str :raises: @@ -411,16 +407,16 @@ def _normalize_level_name( return level_name -def _normalize_logger(logger: logging.Logger | str | None) -> logging.Logger: +def _normalize_logger(logger): """Ensure working with a :py:class:`logger.Logger` :param logger: - Logger name can be a :py:class:`logging.Logger` or str + Logger name can be a external:python+ref:`logging.Logger` or str - :type logger: :py:class:`~typing.Any` or ``None`` + :type logger: external:python+ref:`~logging.Logger` | str | ``None`` :returns: Normalized logger - :rtype: :py:class:`logging.Logger` + :rtype: external:python+ref:`logging.Logger` :raises: @@ -428,8 +424,8 @@ def _normalize_logger(logger: logging.Logger | str | None) -> logging.Logger: .. note:: - logging.Manager.getLogger requires logger name to be a str or - :py:exc:`TypeError` occurs + external:python+ref:`logging.Manager.getLogger` requires logger + name to be a str or :py:exc:`TypeError` occurs """ _logger_name = logger @@ -448,16 +444,18 @@ def _normalize_logger(logger: logging.Logger | str | None) -> logging.Logger: return _logger -def _normalize_formatter(format_: Optional[Any] = LOG_FORMAT) -> logging.Formatter: +def _normalize_formatter(format_=LOG_FORMAT): """Retrieve logging.Formatter from user input - :param format_: Default ``LOG_FORMAT`` + :param format_: + + Default external:logging-strict+ref:`logging_strict.constants.LOG_FORMAT` Can pass in anything. Intended to be a logging format str - :type format_: :py:class:`~typing.Any` or ``None`` + :type format_: :py:class:`~typing.Any` | None :returns: Valid logging formatter to be added to a logging.Handler - :rtype: :py:class:`logging.Formatter` + :rtype: external:python+ref:`logging.Formatter` """ if format_ is None or is_not_ok(format_): format_str = LOG_FORMAT @@ -496,20 +494,33 @@ class _LoggingWatcher: ), ) - def getHandlerByName(self, name: str) -> type[logging.Handler]: - """ - Get a handler with the specified *name*, or None if there isn't one with - that name. + def getHandlerByName(self, name): + """Get a handler with the specified *name*, or None if there + isn't one with that name. + + :param name: handler function name + :type name: str + :returns: A logging handler func + :rtype: type[logging.Handler] """ return logging.getHandlerByName(name) - def getHandlerNames(self) -> frozenset[str]: - """ - Return all known handler names as an immutable set. + def getHandlerNames(self): + """Return all known handler names as an immutable set + + :returns: Handler function names + :rtype: frozenset[str] """ return logging.getHandlerNames() - def getLevelNo(self, level_name: str) -> Optional[int]: + def getLevelNo(self, level_name): + """Get Logging level number, given a logging level name + + :param level_name: logging level name + :type level_name: str + :returns: Logging level integer + :rtype: int | None + """ mapping = logging.getLevelNamesMapping() if level_name in mapping.keys(): ret = mapping[level_name] @@ -527,6 +538,7 @@ def __init__(self): self.watcher = _LoggingWatcher([], []) def flush(self): # pragma: no cover No way to test this. No side effect(s) + """Flush records""" self.watcher.records.clear() self.watcher.output.clear() @@ -534,7 +546,7 @@ def emit(self, record): """Save record. Format/Save message :param record: logging record. Save as record and as str message - :type record: :py:class:`logging.LogRecord` + :type record: external:python+ref:`logging.LogRecord` """ self.watcher.records.append(record) msg = self.format(record) @@ -573,7 +585,9 @@ def captureLogs( logging.getLogger('foo.bar').error('second message') print(cm.output) - The watcher (:py:class:`._LoggingWatcher`) has attributes: + The watcher ( + external:logging-strict+ref:`logging_strict.tech_niques.logging_capture._LoggingWatcher` + ) has attributes: - output @@ -583,17 +597,18 @@ def captureLogs( :param logger: Default ``None``. logger or logger name - :type logger: str or :py:class:`logging.Logger` or None + :type logger: str | external:python+ref:`logging.Logger` | None :param level: Default ``None``. Logging level - :type level: str or int or ``None`` + :type level: str | int | ``None`` :param format_: Default ``None``. Can override logging format spec - :type format_: str or ``None`` + :type format_: str | ``None`` :returns: - Context manager yields one :py:class:`_LoggingWatcher`. Which - stores the log records/messages + Context manager yields one + external:logging-strict+ref:`logging_strict.tech_niques.logging_capture._LoggingWatcher`. + Which stores the log records/messages - :rtype: Iterator[_LoggingWatcher] + :rtype: Iterator[ external:logging-strict+ref:`logging_strict.tech_niques.logging_capture._LoggingWatcher`. ] .. seealso:: @@ -658,21 +673,23 @@ def captureLogsMany( levels=(), format_=LOG_FORMAT, ): - """Behave exactly like :py:func:`.captureLogs` except intended - for multiple loggers rather than one + """Behave exactly like + external:logging-strict+ref:`~logging_strict.tech_niques.logging_capture.captureLogs` + except intended for multiple loggers rather than one :param loggers: Sequence of loggers :type loggers: Sequence[str | logging.Logger] :param levels: Sequence of levels corresponding to each loggers in order - :type levels: Sequence[str or int or ``None``] + :type levels: Sequence[str | int | ``None``] :param format_: Default ``None``. Can override logging format spec - :type format_: str or ``None`` + :type format_: str | ``None`` :returns: - Context manager yields all :py:class:`_LoggingWatcher` in a tuple. - Order maintained + Context manager yields all + external:logging-strict+ref:`logging_strict.tech_niques.logging_capture._LoggingWatcher`. + in a tuple. Order maintained - :rtype: Iterator[tuple[_LoggingWatcher]] + :rtype: Iterator[tuple[ external:logging-strict+ref:`logging_strict.tech_niques.logging_capture._LoggingWatcher`. ]] :raises: - :py:exc:`AssertionError` -- Loggers and levels count mismatch diff --git a/src/logging_strict/tech_niques/logging_capture.pyi b/src/logging_strict/tech_niques/logging_capture.pyi index 80e1b1a..3727af8 100644 --- a/src/logging_strict/tech_niques/logging_capture.pyi +++ b/src/logging_strict/tech_niques/logging_capture.pyi @@ -3,10 +3,7 @@ from __future__ import annotations import contextlib import logging import sys -from typing import ( - Any, - Optional, -) +from typing import Any import attrs @@ -37,19 +34,19 @@ from ..constants import ( __all__: Final[tuple[str, str]] def is_assume_root( - logger_name: Optional[Any], + logger_name: Any | None, ) -> bool: ... def _normalize_level( - level: Optional[Any], + level: Any | None, ) -> str: ... def _normalize_level_name( - logger_name: Optional[Any], + logger_name: Any | None, ) -> str: ... def _normalize_logger( logger: logging.Logger | str | None, ) -> logging.Logger: ... def _normalize_formatter( - format_: Optional[Any] = LOG_FORMAT, + format_: Any | None = LOG_FORMAT, ) -> logging.Formatter: ... @attrs.define class _LoggingWatcher: @@ -74,7 +71,7 @@ class _LoggingWatcher: def getHandlerByName(self, name: str) -> type[logging.Handler]: ... def getHandlerNames(self) -> frozenset[str]: ... - def getLevelNo(self, level_name: str) -> Optional[int]: ... + def getLevelNo(self, level_name: str) -> int | None: ... class _CapturingHandler(logging.Handler): def __init__(self) -> None: ... @@ -98,11 +95,11 @@ class _LoggerStoredState: def captureLogs( logger: str | logging.Logger | None = None, level: str | int | None = None, - format_: Optional[str] = LOG_FORMAT, + format_: str | None = LOG_FORMAT, ) -> Iterator[_LoggingWatcher]: ... @contextlib.contextmanager def captureLogsMany( loggers: Sequence[str | logging.Logger] = (), levels: Sequence[str | int | None] = (), - format_: Optional[str] = LOG_FORMAT, + format_: str | None = LOG_FORMAT, ) -> Iterator[Sequence[_LoggingWatcher]]: ... diff --git a/src/logging_strict/tech_niques/stream_capture.py b/src/logging_strict/tech_niques/stream_capture.py index 1dd1eec..e79d419 100644 --- a/src/logging_strict/tech_niques/stream_capture.py +++ b/src/logging_strict/tech_niques/stream_capture.py @@ -11,7 +11,7 @@ Intuitive interface. Use within :code:`with` block -:py:class:`multiprocessing.pool.ProcessPool` workers have to capture +:py:class:`multiprocessing.pool.Pool` workers have to capture both streams and logging output Module private variables @@ -26,6 +26,8 @@ --------------- """ +from __future__ import annotations + import io import sys @@ -46,7 +48,9 @@ def __enter__(self): class instance stores :py:data:`sys.stdout` and :py:data:`sys.stderr` initial state - :rtype: :py:class:`CaptureOutput` + :rtype: + + external:logging-strict+ref:`~logging_strict.tech_niques.stream_capture.CaptureOutput` .. seealso:: @@ -68,11 +72,11 @@ def __exit__(self, *args): """Context Manager teardown. Restores sys.stdout and sys.stderr previous state :param exc_type: Exception type - :type exc_type: Optional[type[Exception]] + :type exc_type: type[Exception] | None :param exc_value: Exception value - :type exc_value: Optional[Any] - :param exc_value: Exception traceback if an Exception occurred - :type exc_value: Optional[ :py:obj:`types.TracebackType` ] + :type exc_value: :py:class:`~typing.Any` | None + :param exc_tb: Exception traceback if an Exception occurred + :type exc_tb: :py:obj:`types.TracebackType` | None """ self._stdout_output = sys.stdout.getvalue() sys.stdout = self._stdout diff --git a/src/logging_strict/util/check_logging.py b/src/logging_strict/util/check_logging.py index 99444e2..c867bb2 100644 --- a/src/logging_strict/util/check_logging.py +++ b/src/logging_strict/util/check_logging.py @@ -107,7 +107,7 @@ def is_assume_root(logger_name): def check_logger(logger): - """Check working with a :py:class:`logger.Logger` + """Check working with a :py:class:`logging.Logger` :param logger: @@ -160,8 +160,9 @@ def check_level_name(logger_name): def check_level(level): - """Check whether or not :paramref:`level` can be normalized into - a logging level name + """Check whether or not + :paramref:`~logging_strict.util.check_logging.check_level.params.level` + can be normalized into a logging level name :param level: diff --git a/src/logging_strict/util/check_type.py b/src/logging_strict/util/check_type.py index b4d0145..2c55d43 100644 --- a/src/logging_strict/util/check_type.py +++ b/src/logging_strict/util/check_type.py @@ -14,7 +14,7 @@ ------------------------- .. py:attribute:: __all__ - :type: tuple[str] + :type: tuple[str, str, str, str, str] :value: ("check_type_path", "is_not_ok", "is_ok", "check_int_verbosity", \ "check_start_folder_importable") @@ -24,6 +24,8 @@ ------------------------- """ +from __future__ import annotations + from pathlib import ( Path, PurePath, @@ -43,19 +45,21 @@ def check_type_path(module_path, *, msg_context=None): - """Check :paramref:`module_path` is a :py:class:`~pathlib.Path` + """Check + external:logging-strict+ref:`logging_strict.util.check_type.check_type_path.params.module_path` + is a :py:class:`~pathlib.Path` :param module_path: Parameter to check, if possible and necessary, coerse into :py:class:`~pathlib.Path` - :type module_path: :py:class:`Any` or None + :type module_path: :py:class:`~typing.Any` | None :param msg_additional: Context specific message, **not** concerning type - :type msg_additional: str or None + :type msg_additional: str | None :returns: Parameter coersed to :py:class:`~pathlib.Path` :rtype: :py:class:`~pathlib.Path` :raises: @@ -110,7 +114,7 @@ def is_not_ok(test): """Check not ``None``, not a str, or an empty str :param test: variable to test - :type test: :py:class:`Any` or None + :type test: :py:class:`~typing.Any` | None :returns: ``True`` if either: ``None``, not a str, or an empty str :rtype: bool """ @@ -142,7 +146,7 @@ def is_ok(test): Edge case: contains only whitespace --> ``False`` :param test: variable to test - :type test: :py:class:`Any` or None + :type test: :py:class:`~typing.Any` | None :returns: ``True`` if non-empty str otherwise ``False`` :rtype: bool """ @@ -162,7 +166,7 @@ def check_int_verbosity(test): """Check verbosity is an integer with value either 1 or 2 :param test: variable to test - :type test: :py:class:`Any` or None + :type test: :py:class:`~typing.Any` | None :returns: ``True`` if integer 1 or 2 otherwise ``False`` :rtype: bool """ @@ -215,7 +219,7 @@ def check_start_folder_importable(folder_start): "Make a __init__.py" in that folder, touch [folder path]/__init__.py" :param folder_start: folder absolute path - :type folder_start: :py:class:`Any` or None + :type folder_start: :py:class:`~typing.Any` | None :returns: ``True`` if folder contains ``__init__.py`` file otherwise ``False`` diff --git a/src/logging_strict/util/check_type.pyi b/src/logging_strict/util/check_type.pyi index de6ed8c..20e5440 100644 --- a/src/logging_strict/util/check_type.pyi +++ b/src/logging_strict/util/check_type.pyi @@ -1,21 +1,22 @@ +from __future__ import annotations + import sys from pathlib import Path -from typing import ( - Any, - Optional, -) +from typing import Any if sys.version_info >= (3, 8): from typing import Final else: from typing_extensions import Final -__all__: Final[tuple[str, str, str, str]] +__all__: Final[tuple[str, str, str, str, str]] def check_type_path( - module_path: Optional[Any], *, msg_context: Optional[str] = None + module_path: Any | None, + *, + msg_context: str | None = None, ) -> Path: ... -def is_not_ok(test: Optional[Any]) -> bool: ... -def is_ok(test: Optional[Any]) -> bool: ... -def check_int_verbosity(test: Optional[Any]) -> bool: ... -def check_start_folder_importable(folder_start: Optional[Any]) -> bool: ... +def is_not_ok(test: Any | None) -> bool: ... +def is_ok(test: Any | None) -> bool: ... +def check_int_verbosity(test: Any | None) -> bool: ... +def check_start_folder_importable(folder_start: Any | None) -> bool: ... diff --git a/src/logging_strict/util/package_resource.py b/src/logging_strict/util/package_resource.py index 1b9d321..1e00f34 100644 --- a/src/logging_strict/util/package_resource.py +++ b/src/logging_strict/util/package_resource.py @@ -28,22 +28,11 @@ to be easy. Enough that they'll go back and remove all those ugly hacks and bash scripts. -.. note:: :func:`.package_data_folders` yields data folders +.. note:: external:logging-strict+ref:`PackageResource.package_data_folders ` yields data folders First step to extracting package data is to narrow down the (package) folders. The Second step is extracting the data files. - -.. note:: Complements module :py:mod:`.package_paths` - - :py:mod:`.package_paths` contains one class :py:class:`Locations`. - This contains many property returning paths needed by a package. - - In one and only one place! - - :py:mod:`.util_package_resource` and :py:mod:`.package_paths` go - well together - Example --------- @@ -111,20 +100,22 @@ For more fine control, options are: - move it within the cache_extract for loop -- :py:func:`.resource_extract` + +- external:logging-strict+ref:`PackageResource.resource_extract ` .. note:: DIY - Especially :func:`.filter_by_file_stem`, but this might apply to - :func:`.filter_by_suffix` as well, these are for the - simplest scenerio. They are both just a normal function. - If/when necessary, roll your own + Especially + external:logging-strict+ref:`~logging_strict.util.package_resource.filter_by_file_stem`, + but this might apply to + external:logging-strict+ref:`~logging_strict.util.package_resource.filter_by_suffix` + as well, these are for the simplest scenerio. They are both just a + normal function. If/when necessary, roll your own -.. note:: :func:`.generator_folders` kwarg :paramref:`package_name` +.. note:: package_data_folders param :py:obj:`~logging_strict.util.package_resource.PackageResource.package_data_folders.params.package_name` - Change :paramref:`package_name` to whichever package - contains the data files you are interested in, probably - not ``decimals`` + Change to whichever package contains the data files you are interested + in. Not the package in this example Module private variables @@ -254,13 +245,13 @@ def msg_stem(file_name): :param file_name: A file name or file path - :type file_name: str or Path + :type file_name: str | :py:class:`~pathlib.Path` :returns: file name without any file extensions :rtype: str .. warning:: No Optional[str] - Use with either :py:class:`pathlib.Path` or :py:class:`str`, + Use with either :py:class:`~pathlib.Path` or :py:class:`str`, not ``None`` @@ -314,8 +305,8 @@ class PartSuffix(Protocol): from typing import TYPE_CHECKING from functools import partial - from decimals.util_package_resource import filter_by_suffix - from decimals.util_package_resource import PartSuffix + from logging_strict.package_resource import filter_by_suffix + from logging_strict.package_resource import PartSuffix if TYPE_CHECKING: cb_suffix: PartSuffix @@ -349,8 +340,8 @@ class PartStem(Protocol): from typing import TYPE_CHECKING from functools import partial - from decimals.util_package_resource import filter_by_file_stem - from decimals.util_package_resource import PartStem + from logging_strict.util.package_resource import filter_by_file_stem + from logging_strict.util.package_resource import PartStem if TYPE_CHECKING: cb_file_stem: PartStem @@ -384,16 +375,16 @@ def match_file(y, /, *, cb_suffix=None, cb_file_stem=None): Terminology is :menuselection:`x, y --> folder, file` :param y: A traversable file - :type y: Traversible + :type y: external:python+ref:`~importlib.resources.abc.Traversable` :param cb_suffix: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by suffix :type cb_suffix: Callable[[str],bool] or None :param cb_file_stem: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by file name stem :type cb_file_stem: Callable[[str],bool] or None @@ -432,21 +423,25 @@ def check_folder(x, cb_suffix=None, cb_file_stem=None): Terminology is :menuselection:`x, y --> folder, file` :param x: A traversable folder - :type x: Traversible + :type x: external:python+ref:`~importlib.resources.abc.Traversable` :param cb_suffix: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by suffix :type cb_suffix: Callable[[str],bool] or None :param cb_file_stem: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by file name stem :type cb_file_stem: Callable[[str],bool] or None - :returns: if a match, yield :paramref:`x` - :rtype: Iterator[Traversable] + :returns: + + if a match, yield + :paramref:`logging_strict.util.package_resource.check_folder.params.x` + + :rtype: Iterator[ external:python+ref:`importlib.resources.abc.Traversable` ] """ if TYPE_CHECKING: is_found_target_file: bool @@ -482,12 +477,13 @@ def filter_by_suffix(expected_suffix, test_suffix): .. code-block:: python from functools import partial - from decimals.util_package_resource import filter_by_suffix, PartSuffix + from logging_strict.package_resource import filter_by_suffix, PartSuffix cb_suffix: PartSuffix = partial(filter_by_suffix, expected_suffix) ... - Then use ``cb_suffix`` as kwarg to :py:meth:`.cache_extract` + Then use ``cb_suffix`` as kwarg to + external:logging-strict+ref:`PackageResource.cache_extract ` :param expected_suffix: Suffix (e.g. ".ppn") searching for :type expected_suffix: str or tuple[str, ...] @@ -542,7 +538,7 @@ def filter_by_file_stem(expected_file_name, test_file_name): .. code-block:: python from functools import partial - from decimals.util_package_resource import filter_by_file_stem + from logging_strict.util.package_resource import filter_by_file_stem cb_file_stem = partial(filter_by_file_stem, expected_file_name) ... @@ -579,7 +575,9 @@ def filter_by_file_stem(expected_file_name, test_file_name): def _extract_folder(package): """Mockable to change the destination folder - Use only by :py:func:`cache_extract` so can override destination folder + Use only by + :py:meth:`logging_strict.util.package_resource.PackageResource.cache_extract` + so can override destination folder :param package: package name :type package: str @@ -589,35 +587,23 @@ def _extract_folder(package): return DestFolderUser(package).cache_dir -def walk_tree_folders( - traversable_root: Traversable, -) -> Iterator[Traversable]: +def walk_tree_folders(traversable_root): """:py:meth:`importlib.resources.files` returns a single - Traversable which is the Python3 package root folder. A - Traversable supports :py:meth:`pathlib.Path.iterdir`, but - not :py:meth:`pathlib.Path.glob`. - - walking algo - - Generator yield only folders - - .. seealso:: - - Traversable_ - + external:python+ref:`~importlib.resources.abc.Traversable` which is + the Python3 package root folder. A + external:python+ref:`~importlib.resources.abc.Traversable` supports + :py:meth:`pathlib.Path.iterdir`, but not :py:meth:`pathlib.Path.glob`. + :param process_name: package data folder or a subfolder + :type process_name: + external:python+ref:`~importlib.resources.abc.Traversable` - :param process_name: package data folder or a subfolder - :type process_name: :py:class:`~importlib.abc.Traversable` :returns: The entire sub-tree. Includes self - :rtype: :py:class:`~collections.abc.Iterator` - - .. note:: empty folder + :rtype: - Does not check whether a folder is empty + :py:class:`~collections.abc.Iterator`[ external:python+ref:`~importlib.resources.abc.Traversable` ] - .. _Traversable: https://github.com/python/cpython/blob/3.12/Lib/importlib/resources/abc.py """ if TYPE_CHECKING: traversable_x: Traversable @@ -668,7 +654,7 @@ def _get_package_data_folder(dotted_path): The traversable path. Either a package root or a subfolder - :rtype: Optional[Traversable] + :rtype: external:python+ref:`~importlib.resources.abc.Traversable` | None """ try: trav_ret = importlib_resources.files(dotted_path) @@ -685,9 +671,6 @@ class PackageResource: base folder in which to start the search for data files. As in a fallback folder - Previously :paramref:`package_data_folder_start` was mockable - module level variable, :py:data:`FALLBACK_FOLDER`. - Do not assume the default start data folder is ``data``. Impose rule that data files must not be stored in the package base folder; must be placed into a folder @@ -723,7 +706,7 @@ def package_data_folder_start(self): def path_relative( self, - y: Path, + y, /, *, path_relative_package_dir=None, @@ -737,9 +720,11 @@ def path_relative( during testing, to move the extracted data file to another folder - An Example :paramref:`y` which is an absolute path package data - extracted by :py:meth:`importlib_resources.as_file`. Which - should be zip safe + + An Example + :paramref:`~logging_strict.util.package_resource.PackageResource.path_relative.params.y` + which is an absolute path package data extracted by + external:python+ref:`importlib.resources.as_file`. Which should be zip safe .. code-block:: text @@ -774,13 +759,13 @@ def path_relative( :param y: Extracted data file's path - :type y: :py:class:`Traversible` + :type y: :py:class:`~pathlib.Path` :param path_relative_package_dir: Default "data" (folder). Relative package path. Treat a base folder - :type path_relative_package_dir: Path or str or None + :type path_relative_package_dir: :py:class:`~pathlib.Path` | str | None :param parent_count: Ignoring file name. @@ -791,9 +776,9 @@ def path_relative( :returns: Relative path excluding from - :paramref:`path_relative_package_dir` + :paramref:`~logging_strict.util.package_resource.PackageResource.path_relative.params.path_relative_package_dir` - :rtype: Path + :rtype: :py:class:`~pathlib.Path` :raises: @@ -934,27 +919,28 @@ def get_parent_paths( Function creating using :py:func:`functools.partial` which filters by suffix - :type cb_suffix: Callable[[str],bool] or None + :type cb_suffix: Callable[[str],bool] | None :param cb_file_stem: Function creating using :py:func:`functools.partial` which filters by file name stem - :type cb_file_stem: Callable[[str],bool] or None + :type cb_file_stem: Callable[[str],bool] | None :param path_relative_package_dir: package base folder to start the search. None becomes the - :py:data:`FALLBACK_FOLDER`, not the package base folder. Assumes - package authors are smart and would never be that gullible. + external:logging-strict+ref:`~logging_strict.util.package_resource.PackageResource.get_parent_paths.param.package_data_folder_start`, + not the package base folder. Assumes package authors are smart + and would never be that gullible. - :type path_relative_package_dir: Path or str or None + :type path_relative_package_dir: :py:class:`~pathlib.Path` | str | None :param parent_count: Default 1. Retrieve x number of parent folder names - :type parent_count: int or None + :type parent_count: int | None :returns: file name and respective parents as an Sequence[str] - :rtype: Optional[dict[str, Sequence[str]]] + :rtype: dict[str, Sequence[str]] | None """ if TYPE_CHECKING: path_relative_to: Path @@ -1084,18 +1070,20 @@ def package_data_folders( that to occur. This function is used as input to functions: - :py:func:`resource_extract` or :py:func:`cache_extract`. + external:logging-strict+ref:`PackageResource.resource_extract ` + or + external:logging-strict+ref:`PackageResource.cache_extract `. So any Exception or logging would be delayed until those calls :param cb_suffix: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by suffix :type cb_suffix: Callable[[str],bool] :param cb_file_stem: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by file name stem :type cb_file_stem: Callable[[str],bool] @@ -1115,9 +1103,10 @@ def package_data_folders( :returns: - All Traversable paths. Possibly filtered by theme + All external:python+ref:`importlib.resources.abc.Traversable` + paths. Possibly filtered by theme - :rtype: Iterator[Traversable] + :rtype: Iterator[ external:python+ref:`importlib.resources.abc.Traversable` ] :raises: - :py:exc:`ImportError` -- package not installed. Before @@ -1249,21 +1238,24 @@ def resource_extract( :param base_folder_generator: - Package data Generator(s) can be found in - :py:mod:`.package_paths` + Package data folder Generator. Narrows down the search to + folders known to contain target package data files + + :type base_folder_generator: + + Iterator[ external:python+ref:`importlib.resources.abc.Traversable` ] - :type base_folder_generator: Iterator[Traversable] :param path_dest: destination folder - :type path_dest: Path or str + :type path_dest: :py:class:`pathlib.Path` | str :param cb_suffix: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by suffix :type cb_suffix: Callable[[str],bool] :param cb_file_stem: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by file name stem :type cb_file_stem: Callable[[str],bool] @@ -1271,23 +1263,23 @@ def resource_extract( Default ``False``. Force overwriting of destination file - :type is_overwrite: bool or None + :type is_overwrite: bool | None :param as_user: Default ``False``. ``False`` dest file owner set to root. Otherwise dest file owner set to user - :type as_user: bool or None + :type as_user: bool | None :returns: local cached file path - :rtype: Iterator[Path] + :rtype: Iterator[ :py:class:`~pathlib.Path` ] .. seealso:: :menuselection:`Generator --> Resource folders` - :py:func:`.package_data_folders` + external:logging-strict+ref:`PackageResource.package_data_folders ` cb_suffix - :py:func:`.filter_by_suffix` + external:logging-strict+ref:`~logging_strict.util.package_resource.filter_by_suffix` .. caution:: Refresh generator @@ -1346,8 +1338,9 @@ def resource_extract( # Check acl writable permissions. Is dest folder tree writable? pass - """ if package not installed, :paramref:`base_folder_generator` - will raise :py:exc:`ImportError` + """ if package not installed, + :paramref:`~logging_strict.util.package_resource.PackageResource.resource_extract.params.base_folder_generator` + will raise :py:exc:`ImportError` """ try: for traversable_dir in base_folder_generator: @@ -1604,28 +1597,31 @@ def cache_extract( :param base_folder_generator: - Package data Generator(s) can be found in - :py:mod:`..package_paths` + Package data folder Generator. Narrows down folders to + search to only folders containing the target data files + + :type base_folder_generator: + + Iterator[ external:python+ref:`importlib.resources.abc.Traversable` ] - :type base_folder_generator: Iterator[Traversable] :param cb_suffix: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by suffix :type cb_suffix: Callable[[str],bool] :param cb_file_stem: - Function creating using :py:meth:`functools.partial` which + Function creating using :py:func:`functools.partial` which filters by file name stem :type cb_file_stem: Callable[[str],bool] :returns: local cached file path - :rtype: Iterator[Path] + :rtype: Iterator[ :py:class:`~pathlib.Path` ] .. seealso:: - :py:func:`.filter_by_suffix` + external:logging-strict+ref:`~logging_strict.util.package_resource.filter_by_suffix` .. caution:: Refresh generator diff --git a/src/logging_strict/util/package_resource.pyi b/src/logging_strict/util/package_resource.pyi index 4b09516..cc06803 100644 --- a/src/logging_strict/util/package_resource.pyi +++ b/src/logging_strict/util/package_resource.pyi @@ -28,12 +28,11 @@ else: runtime_checkable, ) -__all__: tuple[str, str, str, str, str, str, str, str] +__all__: tuple[str, str, str, str, str] is_module_debug: bool g_module: str _LOGGER: logging.Logger -FALLBACK_FOLDER: str def msg_stem(file_name: str) -> str: ... @runtime_checkable diff --git a/src/logging_strict/util/pep518_read.py b/src/logging_strict/util/pep518_read.py new file mode 100644 index 0000000..8092182 --- /dev/null +++ b/src/logging_strict/util/pep518_read.py @@ -0,0 +1,218 @@ +""" +.. module:: logging_strict.util.pep518_read + :platform: Unix + :synopsis: pyproject.toml generic read functions + +.. moduleauthor:: Dave Faulkmore + +.. + +These :pep:`518` (aka pyproject.toml) functions are **not** app specific + +These functions are lifted from the black project. With minor changes: + +- Removed black specific version handling +- Added sphinx style code documentation +- Removed py38 styles typing like: Tuple or Dict +- Import Sequence correctly + +.. seealso:: + + ``pyproject.toml`` handling + + - external:black+ref:`black` + +.. note:: Flake8 config files handling + + Flake8 + `load_config `_ + function + + Monkeypatch of configparser to support pyproject.toml, + `ConfigParserTomlMixin `_ + + Apply monkeypatch + `FixFilenames.apply `_ + +Module private variables +------------------------- + +.. py:attribute:: __all__ + :type: tuple[str] + :value: ("find_project_root", "find_pyproject_toml") + + Exported objects from this module + +Module objects +--------------- + +""" +from __future__ import annotations + +import sys +from functools import lru_cache +from pathlib import Path +from typing import Any + +from logging_strict.util.check_type import is_ok + +if sys.version_info >= (3, 9): # pragma: no cover + from collections.abc import Sequence # noqa: F401 Used by sphinx +else: # pragma: no cover + from typing import Sequence # noqa: F401 Used by sphinx + +__all__ = ( + "find_project_root", + "find_pyproject_toml", +) + + +@lru_cache +def find_project_root(srcs, stdin_filename=None): + """Return folder containing .git, .hg, or ``pyproject.toml``. + + If no directory in the tree contains a marker that would specify it's the + project root, the root of the file system is returned. + + Returns a two-tuple with the first element as the project root path and + the second element as a string describing the method by which the + project root was discovered. + + :param srcs: + + Files or folders, for files will take the parent folder. + Potential folders that may contain ``pyproject.toml`` + + :type srcs: Sequence[Any] | None + + :param stdin_filename: + + Default ``None``. stdin file name, considers files parent as the + project top folder + + :type stdin_filename: str | ``None`` + :returns: + + Folder containing .git, .hg, or ``pyproject.toml``, will be a common + parent of all files and directories passed in + :paramref:`logging_strict.util.pep518_read.find_project_root.params.srcs` + + :rtype: tuple[:py:class:`~pathlib.Path`, str] + :raises: + + - :py:exc:`PermissionError` -- Unreadable folder. Ungracefully handled + + .. note:: + + Passing ``pyproject.toml``, by stdin, could be useful for testing recipes + + .. seealso:: + + external:black+ref:`black.files.find_project_root` source and credit Black + + """ + + def is_sequence_empty(some_sequence: Sequence[Any] | None) -> bool: + ret = ( + some_sequence is not None + and isinstance(some_sequence, Sequence) + and len(some_sequence) == 0 + ) + return ret + + def is_none(arg: Any) -> bool: + ret = arg is None + return ret + + def is_sequence_none(some_sequence: Sequence[Any] | None) -> bool: + ret = ( + some_sequence is not None + and isinstance(some_sequence, Sequence) + and len(some_sequence) == 1 + and some_sequence[0] is None + ) + return ret + + is_use_cwd = is_none(srcs) or is_sequence_none(srcs) + if is_use_cwd: + # Intention is: use cwd + # None or Sequence[None] + cwd_path = str(Path.cwd().resolve()) + srcs = [] + srcs.append(cwd_path) + else: + """Signature is intended to be Sequence[str], but assume + Sequence[Any]. Filter out non-str, including None and str + containing only whitespace""" + srcs = [src for src in srcs if is_ok(src)] + + if stdin_filename is not None and len(srcs) != 0: + srcs = list(stdin_filename if s == "-" else s for s in srcs) + elif stdin_filename is None and len(srcs) != 0: # pragma: no cover + # Added "-", but didn't supply :paramref:`stdin_filename` + srcs = [s for s in srcs if s != "-"] + else: # pragma: no cover + pass + + if is_none(srcs) or is_sequence_empty(srcs): # pragma: no cover fallback + srcs = [str(Path.cwd().resolve())] + else: # pragma: no cover + pass + + path_srcs = [Path(Path.cwd(), src).resolve() for src in srcs] + + # A list of lists of parents for each 'src'. 'src' is included as a + # "parent" of itself if it is a directory + src_parents = [ + list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs + ] + + common_base = max( + set.intersection(*(set(parents) for parents in src_parents)), + key=lambda path: path.parts, + ) + + for directory in (common_base, *common_base.parents): + if (directory / ".git").exists(): + return directory, ".git directory" + else: # pragma: no cover + pass + + if (directory / ".hg").is_dir(): + return directory, ".hg directory" + else: # pragma: no cover + pass + + if (directory / "pyproject.toml").is_file(): + return directory, "pyproject.toml" + else: # pragma: no cover + pass + + return directory, "file system root" + + +def find_pyproject_toml(path_search_start, stdin_filename): + """Find the absolute filepath to a ``pyproject.toml`` if it exists + + :param path_search_start: + + absolute paths of files or folders to start search for project base folder + + :type path_search_start: tuple[str, ...] + :param stdin_filename: ``pyproject.toml`` passed into stdin. May be a file + :type stdin_filename: str | ``None`` + :returns: Absolute path to project ``pyproject.toml`` otherwise ``None`` + :rtype: str | ``None`` + """ + """2nd item, reason, is string describing the method by which the project + root was discovered""" + try: + path_project_root, _ = find_project_root(path_search_start, stdin_filename) + except PermissionError: + return None + else: + path_pyproject_toml = path_project_root / "pyproject.toml" + if path_pyproject_toml.is_file(): + return str(path_pyproject_toml) + else: + return None diff --git a/src/logging_strict/util/pep518_read.pyi b/src/logging_strict/util/pep518_read.pyi new file mode 100644 index 0000000..a23f4ee --- /dev/null +++ b/src/logging_strict/util/pep518_read.pyi @@ -0,0 +1,28 @@ +from __future__ import annotations + +import sys +from functools import lru_cache +from pathlib import Path +from typing import Any + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +if sys.version_info >= (3, 9): + from collections.abc import Sequence +else: + from typing import Sequence + +__all__: Final[tuple[str, str]] + +@lru_cache +def find_project_root( + srcs: Sequence[Any] | None, + stdin_filename: str | None = None, +) -> tuple[Path, str]: ... +def find_pyproject_toml( + path_search_start: tuple[str, ...], + stdin_filename: str | None = None, +) -> str | None: ... diff --git a/src/logging_strict/util/util_root.py b/src/logging_strict/util/util_root.py index 6666ace..5845dee 100644 --- a/src/logging_strict/util/util_root.py +++ b/src/logging_strict/util/util_root.py @@ -27,7 +27,7 @@ This module's dotted path .. py:data:: _LOGGER - :type: :py:class:`logging.Logger` + :type: logging.Logger Module level logger diff --git a/tests/tech_niques/test_docs_sync_log_capture.py b/tests/tech_niques/test_docs_sync_log_capture.py new file mode 100644 index 0000000..c1c5a7c --- /dev/null +++ b/tests/tech_niques/test_docs_sync_log_capture.py @@ -0,0 +1,110 @@ +""" +.. module:: tests.test_docs_sync_log_capture + :platform: Unix + :synopsis: Demonstrate synchronous logging capture + +.. moduleauthor:: Dave Faulkmore + +.. + +Demonstrate synchronous logging capture + +This test appears in the docs, :ref:`api/logging/api_logging_synchronous:synchronous logging` + +Uncomment lines starting with ``# ^^ uncomment ^^`` then run this test + +Reasons these are commented out: + +- SPAM. The logging message will appear when during :command:`run coverage` + +- ``logging.basicConfig`` call is destructive. OK only when this one unittest + is run by itself. Not ok when running within testing app or by coverage + +""" +import logging +import sys +import unittest + +from logging_strict.constants import LOG_FORMAT # noqa: F401 +from logging_strict.constants import g_app_name +from logging_strict.tech_niques import LoggerRedirector + + +class SomeUnittestClass(unittest.TestCase): + def setUp(self): + g_module_name = "test_docs_sync_log_capture" + g_module = f"{g_app_name}.tests.tech_niques.{g_module_name}" + + self._LOGGER = logging.getLogger(g_module) + + # So see root logging messages, replace, needs logging with handlers + """ + logging.basicConfig( + format=LOG_FORMAT, + level=logging.INFO, + stream=sys.stdout, + ) + """ + # ^^ uncomment ^^ + pass + + LoggerRedirector.redirect_loggers( + fake_stdout=sys.stdout, + fake_stderr=sys.stderr, + ) + + def tearDown(self): + LoggerRedirector.reset_loggers( + fake_stdout=sys.stdout, + fake_stderr=sys.stderr, + ) + + def test_logging_redirect(self): + """Are log messages shown? Uncomment self._LOGGER.info line then run + + Confirm log message printed + + .. code-block:: text + + INFO test_docs_sync_log_capture test_logging_redirecting: *: Is this shown? + + """ + # self._LOGGER.info("Is this shown?") + # ^^ uncomment ^^ + pass + + # self.assertTrue(False) + # In separate test try, ^^ uncomment ^^ + pass + + +if __name__ == "__main__": # pragma: no cover + """In synchronous unittest (class), capture logging + + The **purpose** of this unittest is to + **demonstrate this redirect logging technique**, not to test a source module. + + Before running this unittest, comments ``# ^^ uncomment ^^`` and + uncomment preceding commented out lines + + Then can run the unittest, from package base folder + + .. code-block:: shell + + python -m tests.tech_niques.test_docs_sync_log_capture + + + Output + + .. code-block:: text + + $> python -m tests.tech_niques.test_docs_sync_log_capture + INFO test_docs_sync_log_capture test_logging_redirecting: *: Is this shown? + . + ---------------------------------------------------------------------- + Ran 1 test in 0.000s + + OK + + """ + unittest.main(tb_locals=True) diff --git a/tests/tech_niques/test_logging_capture.py b/tests/tech_niques/test_logging_capture.py index 6004cf2..600a0e3 100644 --- a/tests/tech_niques/test_logging_capture.py +++ b/tests/tech_niques/test_logging_capture.py @@ -64,9 +64,9 @@ class AppLoggingStateSafe(unittest.TestCase): When run by the UI, on unittest screens, CaptureLogs is being used! So if CaptureLogs has side effects, it's really confusing. - Whereas RecipeScreen is being run within a ProcessPool. So - adverse changes to logging config will not propagate outside - of a worker process + Whereas RecipeScreen is being run within a + :py:class:`multiprocessing.pool.Pool`. So adverse changes to + :py:mod:`logging.config` will not propagate outside of a worker process Not so with a ThreadPool. @@ -89,7 +89,8 @@ def setUp(self): If being run from within a worker process, do nothing. The worker is responsible for setting up logging. - If being run within a threadpool, that's insane, refactor as a processpool + If being run within a threadpool, that's insane, refactor as a + :py:class:`multiprocessing.pool.Pool` Choose the logging config yaml. Which can be located within a: @@ -164,14 +165,6 @@ def test_normalize_level_name(self): _normalize_level_name(30) # logging.Logger(app) as logging.Logger and as str - # - # In asz.ui.textual.logging_state_app, func logging_config_set_ui - # sets logging config for: - # - asz (INFO) - # - asyncio (ERROR) - # - everything else (ERROR) - # - # Called in asz.ui.textual.asz logger_app = logging.getLogger(g_app_name) """ sames = ( @@ -686,9 +679,7 @@ def test_py312_backport(self): handler_names = logging2.getHandlerNames() self.assertIsInstance(handler_names, frozenset) - # run within asz count == 1. handler_names frozenset({"console"}) - # self.assertEqual(len(handler_names), 0) - # self.assertFalse(logger_both.hasHandlers()) + self.assertIsInstance(logger_both.hasHandlers(), bool) self.assertIsNone(logging2.getHandlerByName(WORKER_BOTH)) @@ -706,8 +697,6 @@ def test_py312_backport(self): handler_names = cm.getHandlerNames() self.assertIsInstance(handler_names, frozenset) - # run within asz count == 1. handler_names frozenset({"console"}) - # self.assertEqual(len(handler_names), 0) self.assertIsNone(cm.getHandlerByName(WORKER_BOTH)) diff --git a/tests/test_logging_api.py b/tests/test_logging_api.py index 872be62..2b32388 100644 --- a/tests/test_logging_api.py +++ b/tests/test_logging_api.py @@ -470,7 +470,8 @@ class SharedResourceLogger(unittest.TestCase): """The unittest features are implemented as a ThreadPool, so the logging state is shared. This is not ideal. - The best solution is to refactor and implement as a ProcessPool. + The best solution is to refactor and implement as a + :py:class:`multiprocessing.pool.Pool`. In the meantime, stuck with the less than ideal situation (ThreadPool implementation), not the situation would like to have. @@ -478,7 +479,7 @@ class SharedResourceLogger(unittest.TestCase): - cli - ui (unittest module, class, or function screens). ThreadPool - - ui (recipe screen). ProcessPool + - ui (recipe screen). :py:class:`multiprocessing.pool.Pool` Messing with logging is a bad idea and dirty, each :py:class:`logger.Logger` is a Singleton so hangs around forever diff --git a/tests/test_pep518_read.py b/tests/test_pep518_read.py new file mode 100644 index 0000000..3e714f7 --- /dev/null +++ b/tests/test_pep518_read.py @@ -0,0 +1,238 @@ +""" +.. module:: tests.util.test_pep518_read + :platform: Unix + :synopsis: pyproject.toml read table sections + +.. moduleauthor:: Dave Faulkmore + +.. + +.. seealso:: + + coverage/inorout.py:523: CoverageWarning: Module logging_strict was previously + imported, but not measured (module-not-measured) + https://stackoverflow.com/a/18104544 + +""" +# import sys +# del sys.modules["logging_strict"] +# import coverage + +import sys +import tempfile +import unittest +from pathlib import ( + Path, + PurePath, +) +from unittest.mock import patch + +from logging_strict.util.pep518_read import ( + find_project_root, + find_pyproject_toml, +) + +if sys.version_info >= (3, 9): # pragma: no cover + from collections.abc import Sequence # noqa: F401 Used by sphinx +else: # pragma: no cover + from typing import Sequence # noqa: F401 Used by sphinx + + +class pep518_sections(unittest.TestCase): + def setUp(self): + self.path_tests = Path(__file__).parent.parent + self.cwd = self.path_tests.parent + + def test_find_project_root(self): + """Check possibilities: .git, .hg, pyproject.toml, or file system root""" + # "pyproject.toml" + with ( + tempfile.TemporaryDirectory() as f_d, + patch("pathlib.Path.cwd", return_value=Path(f_d)), + ): + tests = ((f_d, "pyproject.toml"),) + for t_valid_dirs in tests: + self.assertIsInstance(t_valid_dirs, tuple) + valid_dir, reason_expected = t_valid_dirs + path_dir = Path(valid_dir) + # create an empty pyproject.toml + # Do not mkdir .git or .hg + path_f = path_dir.joinpath("pyproject.toml") + path_f.touch(mode=0o666, exist_ok=False) + + srcs = (valid_dir,) + path_project_folder, reason = find_project_root(srcs) + self.assertTrue(issubclass(type(path_project_folder), PurePath)) + self.assertIsInstance(reason, str) + self.assertEqual(reason, reason_expected) + + is_found = False + for child in path_project_folder.iterdir(): + if child.name == "pyproject.toml": + is_found = True + self.assertTrue(is_found) + + # cwd contains a .git folder, making it ill-suited to test as an empty folder + # Do not make .git or .hg folder. Do not make pyproject.toml file + with ( + tempfile.TemporaryDirectory() as f_d, + patch("pathlib.Path.cwd", return_value=Path(f_d)), + ): + tests = ( + ((f_d,), "file system root"), + ((None,), "file system root"), # means should use cwd + (None, "file system root"), # forgot; should be a Sequence + ( + ( + 0.1234, + None, + 0.4321, + Path("pyproject.toml"), + ), + "file system root", + ), # filter out non-str + ) + for t_valid_dirs in tests: + self.assertIsInstance(t_valid_dirs, Sequence) + srcs, reason_expected = t_valid_dirs + if srcs is not None: + self.assertIsInstance(srcs, Sequence) + else: + self.assertIsNone(srcs) + self.assertIsInstance(reason_expected, str) + + path_project_folder, reason = find_project_root(srcs) + self.assertTrue(issubclass(type(path_project_folder), PurePath)) + self.assertIsInstance(reason, str) + self.assertEqual(reason, reason_expected) + + is_found = False + for child in path_project_folder.iterdir(): + if child.name == "pyproject.toml": + is_found = True + self.assertFalse(is_found) + + # stdin_filename + # .git > pyproject.toml + with ( + tempfile.TemporaryDirectory() as f_d, + patch("pathlib.Path.cwd", return_value=Path(f_d)), + ): + path_dir = Path(f_d) + path_dot_git = path_dir.joinpath(".git") + path_dot_git.mkdir( + mode=0o777, + parents=False, + exist_ok=False, + ) + + # pyproject.toml does not yet exist + srcs = ("-",) + stdin_filename = None + self.assertIsNone(find_pyproject_toml(srcs, stdin_filename)) + + path_f = path_dir.joinpath("pyproject.toml") + path_f.touch(mode=0o666, exist_ok=False) + str_toml = ( + "[tool.asz.unittest]\n" + "util/test_pep518_read.py = 14\n\n" + "[tool.asz.recipe]\n" + "util/pep518_read = [14]\n\n" + ) + path_f.write_text(str_toml) + + """pyproject.toml exists, although stdin_filename not supplied + + make coverage says it's None, unittest and running coverage + on the module says it's a str. + + It shouldn't be None + """ + # self.assertIsInstance(find_pyproject_toml(srcs, stdin_filename), str) + pass + + # pyproject.toml exists, stdin_filename supplied + stdin_filename = str(path_f) + self.assertIsInstance(find_pyproject_toml(srcs, stdin_filename), str) + + path_project_folder, reason = find_project_root(srcs, stdin_filename) + self.assertTrue(issubclass(type(path_project_folder), PurePath)) + self.assertIsInstance(reason, str) + self.assertEqual(reason, ".git directory") + + # Has .git, .hg folders + tests = ( + (".git", ".git directory"), + (".hg", ".hg directory"), + ) + for t_valid_dirs in tests: + self.assertIsInstance(t_valid_dirs, tuple) + valid_dir, reason_expected = t_valid_dirs + with ( + tempfile.TemporaryDirectory() as f_d, + patch("pathlib.Path.cwd", return_value=Path(f_d)), + ): + path_dir = Path(f_d) + path_dot_vcs = path_dir.joinpath(valid_dir) + path_dot_vcs.mkdir( + mode=0o777, + parents=False, + exist_ok=False, + ) + srcs = (f_d,) + path_project_folder, reason = find_project_root(srcs) + self.assertTrue(issubclass(type(path_project_folder), PurePath)) + self.assertIsInstance(reason, str) + self.assertEqual(reason, reason_expected) + + # search fails + with ( + tempfile.TemporaryDirectory() as f_d, + patch("pathlib.Path.cwd", return_value=Path(f_d)), + ): + tests = ((f_d, "file system root"),) + for t_valid_dirs in tests: + self.assertIsInstance(t_valid_dirs, tuple) + valid_dir, reason_expected = t_valid_dirs + srcs = (valid_dir,) + + path_project_folder, reason = find_project_root(srcs) + self.assertTrue(issubclass(type(path_project_folder), PurePath)) + self.assertIsInstance(reason, str) + self.assertEqual(reason, reason_expected) + + # must be a tuple[str], not tuple[Path] + stdin_filename = None + self.assertIsNone(find_pyproject_toml(srcs, stdin_filename)) + + # PermissionError, **not** testing a filesystem base folder + srcs = ("/root",) + stdin_filename = None + with self.assertRaises(PermissionError): + find_project_root(srcs) + self.assertIsNone(find_pyproject_toml(srcs, stdin_filename)) + + +if __name__ == "__main__": # pragma: no cover + """ + .. code-block:: shell + + python -m tests.util.test_pep518_read + + coverage run --data-file=".coverage-combine-14" \ + -m unittest discover -t. -s tests/util -p "test_pep518_read*.py" --buffer + + coverage report --include="*pep518_read*" --no-skip-covered \ + --data-file=".coverage-combine-14" + + Ran 1 test in 0.004s + Branch Coverage: 100% + + Avoid combine since only one RecipePortion + + .. code-block:: shell + + coverage combine --keep .coverage-combine-14 + + """ + unittest.main(tb_locals=True) diff --git a/tox.ini b/tox.ini index 2466e25..5e4dac0 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,7 @@ setenv = PYTHON_COLORS=0 [testenv:docs] +description = sphinx docs # One of the PYVERSIONS, that's currently supported by Sphinx. Make sure it # matches the `python:version:` in the .readthedocs.yml file, and the # python-version in the `doc` job in the .github/workflows/quality.yml workflow. @@ -42,6 +43,7 @@ commands = - sphinx-build -b html -b linkcheck -aEnQW docs docs/_build/html [testenv:lint] +description = pre-commit and build # Minimum of PYVERSIONS basepython = python3.9 deps = @@ -59,10 +61,12 @@ commands = python -m black --quiet --include='\.pyi?$' src/ python -m black --quiet --include='\.pyi?$' tests/ - python igor.py build_next "tag" + # suppress noisy output + python igor.py quietly "python igor.py build_next tag" twine check dist/* [testenv:mypy] +description = static type checking basepython = python3.9 deps = @@ -74,6 +78,18 @@ setenv = commands = mypy --python-version=3.9 -p logging_strict +[testenv:test] +description = Run coverage +deps = + -r requirements/dev.pip + +setenv = + {[testenv]setenv} + +commands = + coverage run --omit="*.txt" -m unittest discover -t. -s tests -p "test_*.py" --verbose --locals + coverage report + [gh] # https://pypi.org/project/tox-gh/ # PYVERSIONS