diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 9c3d52e..f09abb5 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -6,6 +6,9 @@ on: push: branches: - '**' + pull_request: + branches: + - '**' permissions: contents: write diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4d08562..b2c805e 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -16,11 +16,11 @@ jobs: matrix: os: [macos-latest, ubuntu-latest, windows-latest] python-version: [ - '3.8', '3.9', '3.10', '3.11', - # 'pypy-3.10', + '3.12', + 'pypy-3.10', ] exclude: diff --git a/.gitignore b/.gitignore index 9feb06d..c8a594a 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,6 @@ tests/reports # Ruff .ruff_cache + +# Logs +logs/ diff --git a/README.md b/README.md index 6dd67ce..f309d77 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ Remember to: 1. Replace all instances of `project-name` with the real name of the project 2. Replace all instances of `project_name` with the "package name" of the project -3. Rename the source code folder from `project_name` to something more fitting -4. Generate a lock file +3. Rename the source code folder from `project_name` to your package name +4. Generate a lock file (`poetry install --with dev,tests,linters`) 5. Rewrite the `README.md` +6. If the project is not an executable, delete the files: + * `src/project_name/main.py` + * `src/project_name/logger.py` + * `src/project_name/logger_config.toml` + * `src/project_name/config.py` + * `python-dotenv` and `tomli` as dependencies in `pyproject.toml` (unless otherwise needed) diff --git a/poetry.lock b/poetry.lock index 441e77c..1e87834 100644 --- a/poetry.lock +++ b/poetry.lock @@ -35,63 +35,73 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.6.4" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, ] [package.dependencies] @@ -102,24 +112,24 @@ toml = ["tomli"] [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -317,6 +327,20 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "ruff" version = "0.7.0" @@ -346,13 +370,13 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -412,13 +436,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.27.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, + {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, ] [package.dependencies] @@ -432,5 +456,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" -python-versions = "^3.8.1" -content-hash = "5e2b06ebd033ce7f3408f8f9740f90a379100f4bd214b586d0b1bbfdea1c53ab" +python-versions = "^3.9.0" +content-hash = "fce256c92b760fba3af4a2b81a7024df13c7d9d33d781f54a9c04b6bffaf635e" diff --git a/pyproject.toml b/pyproject.toml index a76aae4..172b30a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,13 @@ requires = ["poetry-core>=1.2.0", "wheel",] build-backend = "poetry.core.masonry.api" +[tool.coverage.report] +exclude_lines = [ + "pragma: not covered", + "@overload", +] + + [tool.coverage.run] branch = true relative_files = true @@ -39,7 +46,6 @@ classifiers = [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -51,7 +57,9 @@ classifiers = [ [tool.poetry.dependencies] -python = "^3.8.1" +python = "^3.9.0" +python-dotenv = "^1.0.1" +tomli = { version = "^2.0.1", python = "<3.11" } [tool.poetry.group.dev.dependencies] @@ -98,113 +106,127 @@ testpaths = [ [tool.ruff] -select = [ - "A", # Builtins - "ANN", # Annotations - "ARG", # Unused arguments - "B", # Bugbear - "BLE", # Blind except - "C4", # Comprehensions - "C90", # mccabe - "COM", # Commas - "D1", # Undocumented public elements - "D2", # Docstring conventions - "D3", # Triple double quotes - "D4", # Docstring text format - "DTZ", # Datetimes - "EM", # Error messages - "ERA", # Commented-out code - "EXE", # Executable - "F", # Pyflakes - "FA", # __future__ annotations - "FLY", # F-strings - # "FURB", # Refurb - "G", # Logging format - "I", # Isort - "ICN", # Import conventions - "INP", # Disallow PEP-420 (Implicit namespace packages) - "INT", # gettext - "ISC", # Implicit str concat - # "LOG", # Logging - "N", # PEP-8 Naming - "NPY", # Numpy - "PERF", # Unnecessary performance costs - "PGH", # Pygrep hooks - "PIE", # Unnecessary code - "PL", # Pylint - "PT", # Pytest - "PTH", # Use Pathlib - "PYI", # Stub files - "Q", # Quotes - "RET", # Return - "RUF", # Ruff - "RSE", # Raise - "S", # Bandit - "SIM", # Code simplification - "SLF", # Private member access - "SLOT", # __slots__ - "T10", # Debugger - "T20", # Print - "TCH", # Type checking - "TID", # Tidy imports - "TRY", # Exception handling - "UP", # Pyupgrade - "W", # Warnings - "YTT", # sys.version +lint.select = [ + "A", # Builtins + # "AIR", # Airflow + "ANN", # Annotations + "ARG", # Unused arguments + "ASYNC", # Asynchronous code + "B", # Bugbear + "BLE", # Blind except + "C4", # Comprehensions + "C90", # mccabe + "COM", # Commas + # "CPY", # Copyright + "D1", # Undocumented public elements + "D2", # Docstring conventions + "D3", # Triple double quotes + "D4", # Docstring text format + # "DJ", # Django + "DTZ", # Datetimes + "E", # Errors + "EM", # Error messages + "ERA", # Commented-out code + "EXE", # Executable + "F", # Pyflakes + "FA", # __future__ annotations + # "FAST", # FastAPI + "FBT", # "Boolean trap" + "FIX", # "FIXME"-comments + "FLY", # F-strings + "FURB", # Refurb + "G", # Logging format + "I", # Isort + "ICN", # Import conventions + "INP", # Disallow PEP-420 (Implicit namespace packages) + "INT", # gettext + "ISC", # Implicit str concat + "LOG", # Logging + "N", # PEP-8 Naming + # "NPY", # Numpy + # "PD", # Pandas + "PERF", # Unnecessary performance costs + "PGH", # Pygrep hooks + "PIE", # Unnecessary code + "PL", # Pylint + "PT", # Pytest + "PTH", # Use Pathlib + "PYI", # Stub files + "Q", # Quotes + "RET", # Return + "RUF", # Ruff + "RSE", # Raise + "S", # Bandit + "SIM", # Code simplification + "SLF", # Private member access + "SLOT", # __slots__ + "T10", # Debugger + "T20", # Print + "TCH", # Type checking + "TD", # "TODO"-comments + "TID", # Tidy imports + "TRY", # Exception handling + "UP", # Pyupgrade + "W", # Warnings + "YTT", # sys.version ] -ignore = [ +lint.ignore = [ + "ANN101", # Type annotation for `self` "D203", # One blank line before class docstring "D212", # Multi-line summary first line "PLR0913", # Too many arguments "Q000", # Single quotes found but double quotes preferred ] -ignore-init-module-imports = true line-length = 120 -# preview = true show-fixes = true src = ["src",] -target-version = "py38" +target-version = "py39" + + +[tool.ruff.lint.flake8-copyright] +author = "Lari Liuhamo" -[tool.ruff.flake8-quotes] +[tool.ruff.lint.flake8-quotes] docstring-quotes = "double" multiline-quotes = "double" -[tool.ruff.mccabe] -# Unlike Flake8, default to a complexity level of 10. +[tool.ruff.lint.mccabe] max-complexity = 10 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # https://beta.ruff.rs/docs/rules/ -"__init__.py" = ["F401","F403","F405",] +"__init__.py" = ["F401", "F403", "F405",] "tests/*" = ["ANN", "ARG", "INP001", "S101",] +"logger.py" = ["N815",] -[tool.ruff.pylint] +[tool.ruff.lint.pylint] max-args = 15 max-branches = 20 max-returns = 10 max-statements = 80 -[tool.ruff.flake8-tidy-imports] +[tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" [tool.tox] legacy_tox_ini = """ [tox] -envlist = py38, py39, py310, py311, pypy3 +envlist = py39, py310, py311, py312, pypy3 skip_missing_interpreters = true [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 + 3.12: py312 pypy-3.10: pypy3 [testenv] diff --git a/src/project_name/config.py b/src/project_name/config.py new file mode 100644 index 0000000..2042d27 --- /dev/null +++ b/src/project_name/config.py @@ -0,0 +1,18 @@ +"""Global config options of the package.""" + +import os +from importlib import resources as pkg_resources +from pathlib import Path + +from dotenv import load_dotenv + +import project_name + +load_dotenv() + +PACKAGE_NAME = "project_name" + +with pkg_resources.as_file(pkg_resources.files(project_name)) as package_dir: + DEFAULT_CONFIG_FILE_PATH = package_dir / 'logger_config.toml' + +LOGGER_CONFIG_FILE = Path(os.environ.get("PROJECT_NAME_LOGGER_CONFIG_FILE", DEFAULT_CONFIG_FILE_PATH)) diff --git a/src/project_name/lib.py b/src/project_name/lib.py index bef97f7..5a4da15 100644 --- a/src/project_name/lib.py +++ b/src/project_name/lib.py @@ -1,5 +1,6 @@ """Code.""" + def adder(first: int, second: int) -> int: """Lorem Ipsum dolor sit amet.""" return first + second diff --git a/src/project_name/logger.py b/src/project_name/logger.py new file mode 100644 index 0000000..1133e79 --- /dev/null +++ b/src/project_name/logger.py @@ -0,0 +1,172 @@ +"""Configure the root logger.""" + +from __future__ import annotations + +import atexit +import datetime as dt +import json +import logging +import logging.config +import logging.handlers +from enum import Enum, auto +from pathlib import Path +from typing import Any, NotRequired, TypedDict, override + +try: + import tomllib +except ImportError: + import tomli as tomllib # type: ignore[import-not-found, no-redef] + +from project_name.config import LOGGER_CONFIG_FILE, PACKAGE_NAME + +ROOT_LOGGER_NAME = PACKAGE_NAME + +__all__ = ('ROOT_LOGGER_NAME', 'setup_logging') + + +class RecordAttrs(str, Enum): + """Log record attributes.""" + + @override + @staticmethod + def _generate_next_value_(name: str, start: int, count: int, last_values: list) -> str: + """Create enum values from the names automatically.""" + return name + + args = auto() + asctime = auto() + created = auto() + exc_info = auto() + exc_text = auto() + filename = auto() + funcName = auto() + levelname = auto() + levelno = auto() + lineno = auto() + module = auto() + msecs = auto() + message = auto() + msg = auto() + name: str = "name" + pathname = auto() + process = auto() + processName = auto() + relativeCreated = auto() + stack_info = auto() + thread = auto() + threadName = auto() + taskName = auto() + + +class ColouredFormatter(logging.Formatter): + """Coloured log formatter.""" + + @override + def format(self, record: logging.LogRecord) -> str: + """Format the log record.""" + log_level_colours = { + logging.CRITICAL: '\033[31;1;40m', # Red, bold + logging.ERROR: '\033[31;40m', # Red + logging.WARNING: '\033[33;40m', # Yellow + logging.INFO: '\033[32;40m', # Green + logging.DEBUG: '\033[36;40m', # Cyan + } + reset = '\033[0m' + + record.msg = f"{log_level_colours.get(record.levelno, reset)}{record.msg}{reset}" + record.levelname = ( + f"{log_level_colours.get(record.levelno, reset)}{record.levelname:^8}{reset}" + ) + + return super().format(record) + + +class FormatKeys(TypedDict): + """Log format keys.""" + + message: NotRequired[str] + timestamp: NotRequired[dt.datetime] + level: NotRequired[str] + logger: NotRequired[str] + module: NotRequired[str] + function: NotRequired[str] + line: NotRequired[int] + thread_name: NotRequired[str] + + +class LogDict(FormatKeys): + """Specify keys log entries can contain.""" + + exc_info: NotRequired[str] + stack_info: NotRequired[str] + + +class CustomQueueHandler(logging.handlers.QueueHandler): + """Custom queue handler.""" + + @override + def __init__(self, *args: Any, **kwargs: Any) -> None: + queue_handler = logging.getHandlerByName("queue_handler") + if queue_handler is None: + super().__init__(*args, **kwargs) # type: ignore[arg-type] + + +class JSONLogFormatter(logging.Formatter): + """Custom JSON log formatter.""" + + @override + def __init__(self, *, fmt_keys: FormatKeys | None = None) -> None: + super().__init__() + self.fmt_keys = ( + fmt_keys + if fmt_keys is not None + else FormatKeys() + ) + + @override + def format(self, record: logging.LogRecord) -> str: + message = self._prepare_log_dict(record) + return json.dumps(message, default=str) + + def _prepare_log_dict(self, record: logging.LogRecord) -> LogDict: + required_fields: LogDict = { + 'message': record.getMessage(), + 'timestamp': dt.datetime.fromtimestamp( + record.created, + tz=dt.timezone.utc, + ), + } + + if record.exc_info is not None: + required_fields['exc_info'] = self.formatException(record.exc_info) + + if record.stack_info is not None: + required_fields['stack_info'] = self.formatStack(record.stack_info) + + message: LogDict = { # type: ignore[assignment] + key: msg_val + if (msg_val := required_fields.pop(val, None)) is not None # type: ignore[misc, typeddict-item] + else getattr(record, val) # type: ignore[call-overload] + for key, val in self.fmt_keys.items() + } + + message.update(required_fields) + + for key, val in record.__dict__.items(): + if key not in RecordAttrs: + message[key] = val # type: ignore[literal-required] + + return message + + +def setup_logging() -> None: + """Set up logging.""" + (Path.cwd() / 'logs').mkdir(exist_ok=True) + logger_data = LOGGER_CONFIG_FILE.read_text(encoding='utf-8') + logging_config = tomllib.loads(logger_data) + logging.config.dictConfig(logging_config) + + queue_handler = logging.getHandlerByName("queue_handler") + if queue_handler is not None: + queue_handler.listener.start() # type: ignore[attr-defined] + atexit.register(queue_handler.listener.stop) # type: ignore[attr-defined] diff --git a/src/project_name/logger_config.toml b/src/project_name/logger_config.toml new file mode 100644 index 0000000..ef6bac5 --- /dev/null +++ b/src/project_name/logger_config.toml @@ -0,0 +1,59 @@ +version = 1 +disable_existing_loggers = false + +[formatters.colour] +"()" = "project_name.logger.ColouredFormatter" +format = "%(asctime)s [%(levelname)s] %(module)s:L%(lineno)04d | %(funcName)s: %(message)s" +datefmt = "%d-%m-%Y %I:%M:%S" + +[formatters.simple] +format = "[%(levelname)s|%(module)s|L%(lineno)d] %(asctime)s: %(message)s" +datefmt = "%Y-%m-%dT%H:%M:%S%z" + +[formatters.json] +"()" = "project_name.logger.JSONLogFormatter" + +[formatter.json.fmt_keys] +level = "levelname" +message = "message" +timestamp = "timestamp" +logger = "name" +module = "module" +function = "funcName" +line = "lineno" +thread_name = "threadName" + +[handlers.stdout] +class = "logging.StreamHandler" +level = "INFO" +formatter = "colour" +stream = "ext://sys.stdout" + +[handlers.stderr] +class = "logging.StreamHandler" +level = "WARNING" +formatter = "simple" +stream = "ext://sys.stderr" + +[handlers.file_json] +class = "logging.handlers.RotatingFileHandler" +level = "DEBUG" +formatter = "json" +filename = "logs/project_name.log.jsonl" +maxBytes = 10_000_000 +backupCount = 3 + +[handlers.queue_handler] +class = "project_name.logger.CustomQueueHandler" +handlers = [ + "file_json", + # "stderr", + "stdout", +] +respect_handler_level = true + +[loggers.root] +level = "DEBUG" +handlers = [ + "queue_handler", +] diff --git a/src/project_name/main.py b/src/project_name/main.py new file mode 100644 index 0000000..0daf920 --- /dev/null +++ b/src/project_name/main.py @@ -0,0 +1,27 @@ +"""Main entry point, remove if project is not an executable.""" + +import logging + +from project_name.logger import ROOT_LOGGER_NAME, setup_logging + +logger = logging.getLogger(ROOT_LOGGER_NAME) + + +def main() -> None: + """Lorem Ipsum.""" + setup_logging() + + logger.debug("debug message", extra={'foo': 'hello'}) + logger.info("info message") + logger.warning("warning message") + logger.error("error message") + logger.critical("critical message") + + try: + _ = 1337 / 0 + except ZeroDivisionError: + logger.exception("exception message") + + +if __name__ == '__main__': + main()