diff --git a/.github/workflows/bad-name-notifier-cd.yml b/.github/workflows/bad-name-notifier-cd.yml index 396ba3fc3..d3271039b 100644 --- a/.github/workflows/bad-name-notifier-cd.yml +++ b/.github/workflows/bad-name-notifier-cd.yml @@ -25,6 +25,10 @@ on: - "false" - "true" +permissions: + id-token: write + contents: write + jobs: namex-bad-name-notifier-cd: uses: bcgov/bcregistry-sre/.github/workflows/backend-job-cd.yaml@main diff --git a/.github/workflows/bad-name-notifier-ci.yml b/.github/workflows/bad-name-notifier-ci.yml index 29cfececa..a5f5c7b67 100644 --- a/.github/workflows/bad-name-notifier-ci.yml +++ b/.github/workflows/bad-name-notifier-ci.yml @@ -10,12 +10,14 @@ defaults: shell: bash working-directory: ./jobs/bad-name-notifier +permissions: + id-token: write + contents: write + jobs: namex-bad-name-notifier-ci: uses: bcgov/bcregistry-sre/.github/workflows/backend-ci.yaml@main with: app_name: "namex-bad-name-notifier" working_directory: "./jobs/bad-name-notifier" - codecov_flag: "namexbadnamenotifier" - skip_isort: "true" - skip_black: "true" \ No newline at end of file + codecov_flag: "namex-bad-name-notifier" \ No newline at end of file diff --git a/jobs/bad-name-notifier/.flake8 b/jobs/bad-name-notifier/.flake8 new file mode 100644 index 000000000..7bd56a393 --- /dev/null +++ b/jobs/bad-name-notifier/.flake8 @@ -0,0 +1,8 @@ +[flake8] +max-line-length = 80 +extend-ignore = E501 +exclude = + .venv, + .git, + migrations, + tests diff --git a/jobs/bad-name-notifier/README.md b/jobs/bad-name-notifier/README.md index 2d1614937..62826c395 100644 --- a/jobs/bad-name-notifier/README.md +++ b/jobs/bad-name-notifier/README.md @@ -18,5 +18,33 @@ Bad-Name-Notifier is a Python application designed to identify names with specia 1. Clone the repository: ```bash - git clone https://github.com/your-repo/bad-name-notifier.git - cd bad-name-notifier + git clone https://github.com/your-repo/namex.git + cd jobs/bad-name-notifier + +2 ### Install the dependencies +```bash +poetry install +``` + +3 ### Configure the .env +(see .env.sample) + +```bash +eval $(poetry env activate) +``` + +4 ### Run the job +```bash +python src/bad_name_notifier/app.py +OR: ./run.sh +``` + +5 ### Run Linting +```bash +poetry run ruff check --fix +``` + +6 ### Run unit tests +```bash +poetry run pytest +``` \ No newline at end of file diff --git a/jobs/bad-name-notifier/__init__.py b/jobs/bad-name-notifier/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/jobs/bad-name-notifier/coverage.xml b/jobs/bad-name-notifier/coverage.xml new file mode 100644 index 000000000..cb816fde9 --- /dev/null +++ b/jobs/bad-name-notifier/coverage.xml @@ -0,0 +1,56 @@ + + + + + + /mnt/c/dsk01/lab/temp/namex/jobs/bad-name-notifier/src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jobs/bad-name-notifier/poetry.lock b/jobs/bad-name-notifier/poetry.lock index c6b8fb1cb..9340c4003 100644 --- a/jobs/bad-name-notifier/poetry.lock +++ b/jobs/bad-name-notifier/poetry.lock @@ -229,6 +229,18 @@ files = [ {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, ] +[[package]] +name = "astroid" +version = "3.3.11" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec"}, + {file = "astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce"}, +] + [[package]] name = "attrs" version = "22.2.0" @@ -248,6 +260,21 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope.interface"] tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990) ; platform_python_implementation == \"CPython\"", "mypy (>=0.971,<0.990) ; platform_python_implementation == \"CPython\"", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" + [[package]] name = "blinker" version = "1.9.0" @@ -494,12 +521,131 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "platform_system == \"Windows\"" +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\"", test = "sys_platform == \"win32\""} + +[[package]] +name = "coverage" +version = "7.13.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415"}, + {file = "coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9"}, + {file = "coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf"}, + {file = "coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9"}, + {file = "coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9"}, + {file = "coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f"}, + {file = "coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0"}, + {file = "coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246"}, + {file = "coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126"}, + {file = "coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a"}, + {file = "coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d"}, + {file = "coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd"}, + {file = "coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b"}, + {file = "coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9"}, + {file = "coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd"}, + {file = "coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0"}, + {file = "coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb"}, + {file = "coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505"}, + {file = "coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0"}, + {file = "coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea"}, + {file = "coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932"}, + {file = "coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b"}, + {file = "coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0"}, + {file = "coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -602,6 +748,22 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "dill" +version = "0.4.1" +description = "serialize all of Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, + {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "dnspython" version = "2.8.0" @@ -657,6 +819,23 @@ files = [ [package.extras] tests = ["coverage", "coveralls", "dill", "mock", "nose"] +[[package]] +name = "flake8" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" + [[package]] name = "flask" version = "3.1.0" @@ -1403,6 +1582,33 @@ typing-extensions = "*" docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-ruff"] +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +groups = ["dev"] +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -1826,6 +2032,18 @@ docs = ["alabaster (==0.7.12)", "sphinx (==4.4.0)", "sphinx-issues (==3.0.1)"] lint = ["flake8 (==4.0.1)", "flake8-bugbear (==22.7.1)", "pre-commit (>=2.0,<3.0)"] tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "multidict" version = "6.7.0" @@ -2212,7 +2430,7 @@ version = "21.3" description = "Core utilities for Python packages" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -2249,6 +2467,34 @@ files = [ {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, ] +[[package]] +name = "platformdirs" +version = "4.9.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, + {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + [[package]] name = "pronouncing" version = "0.2.0" @@ -2472,6 +2718,18 @@ files = [ [package.dependencies] pyasn1 = ">=0.4.6,<0.7.0" +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + [[package]] name = "pycountry" version = "22.3.5" @@ -2566,6 +2824,18 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pyflakes" +version = "3.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, +] + [[package]] name = "pyjwt" version = "2.10.1" @@ -2584,13 +2854,38 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pylint" +version = "3.3.9" +description = "python code static checker" +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7"}, + {file = "pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a"}, +] + +[package.dependencies] +astroid = ">=3.3.8,<=3.4.0.dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} +isort = ">=4.2.5,<5.13 || >5.13,<7" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pyparsing" version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2660,6 +2955,64 @@ setuptools = "*" [package.extras] solrcloud = ["kazoo (>=2.5.0)"] +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3317,6 +3670,18 @@ all = ["tornado (>=4.0)", "twisted"] tornado = ["tornado (>=4.0)"] twisted = ["twisted"] +[[package]] +name = "tomlkit" +version = "0.14.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, + {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, +] + [[package]] name = "toolz" version = "0.12.1" @@ -3600,4 +3965,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.13" -content-hash = "4f85181739af5db44985729cfbb939525851cef2ad30afc784bf16129c248869" +content-hash = "1894ee6c94a2dc0684ec8f9e148a76fdd7b64a7ab28faa463481756cadee5903" diff --git a/jobs/bad-name-notifier/pyproject.toml b/jobs/bad-name-notifier/pyproject.toml index 6cce0cfb5..da4b6ad4b 100644 --- a/jobs/bad-name-notifier/pyproject.toml +++ b/jobs/bad-name-notifier/pyproject.toml @@ -1,15 +1,34 @@ [tool.poetry] name = "bad-name-notifier" -version = "0.2.0" +version = "0.3.0" description = "An app to detect and notify about bad names." authors = ["Your Name "] license = "Apache-2.0" readme = "README.md" -packages = [ - { include = "services", from = "src" }, - { include = "src" } -] +packages = [{ include = "*", from = "src" }] [tool.poetry.dependencies] python = ">=3.12,<3.13" -namex = { git = "https://github.com/bcgov/namex.git", subdirectory = "api", branch = "main" } \ No newline at end of file +namex = { git = "https://github.com/bcgov/namex.git", subdirectory = "api", branch = "main" } +[tool.poetry.group.test.dependencies] +pytest = "^7.4.4" +pytest-cov = "^5.0.0" +pytest-mock = "^3.14.0" + +[tool.poetry.group.dev.dependencies] +pylint = "^3.3.1" +flake8 = "^7.1.1" +isort = "^5.13.2" +autopep8 = "^2.3.2" + +[tool.coverage.run] +branch = true +source = [ + "namex_solr_importer", +] +omit = [ + "*/.venv/*", + "*/__init__.py", + "*/app.py", + "*/services/*", +] \ No newline at end of file diff --git a/jobs/bad-name-notifier/src/app.py b/jobs/bad-name-notifier/src/app.py index 154ba3061..8a1ddc025 100644 --- a/jobs/bad-name-notifier/src/app.py +++ b/jobs/bad-name-notifier/src/app.py @@ -1,21 +1,26 @@ -from config import APP_CONFIG +"""Application factory and configuration setup for the Bad Name Notifier service.""" + from flask import Flask, current_app +from structured_logging import StructuredLogging + +from config import get_named_config from services.database_service import get_bad_names from services.email_service import send_email_notification -from structured_logging import StructuredLogging + def create_app(config_name="default"): """Creates and configures the Flask app.""" - app = Flask(__name__) # NOSONAR - app.config.from_object(APP_CONFIG[config_name]) + flask_app = Flask(__name__) # NOSONAR + flask_app.config.from_object(get_named_config(config_name)) # Configure Structured Logging structured_logger = StructuredLogging() - structured_logger.init_app(app) - app.logger = structured_logger.get_logger() + structured_logger.init_app(flask_app) + flask_app.logger = structured_logger.get_logger() + + return flask_app - return app def run_task(): """Executes the task to query bad names and send an email.""" @@ -26,9 +31,10 @@ def run_task(): # Step 2: Send email send_email_notification(bad_names) current_app.logger.info("Notification sent successfully.") - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught current_app.logger.error(f"An error occurred: {e}") + if __name__ == "__main__": app = create_app() with app.app_context(): # Ensures Flask app context is available diff --git a/jobs/bad-name-notifier/src/config.py b/jobs/bad-name-notifier/src/config.py index 95ffc42aa..b142376bc 100644 --- a/jobs/bad-name-notifier/src/config.py +++ b/jobs/bad-name-notifier/src/config.py @@ -1,36 +1,47 @@ +"""Application configuration module for the Bad Name Notifier service.""" + +# pylint: disable=too-few-public-methods import os -from dotenv import find_dotenv, load_dotenv +from dotenv import load_dotenv # Get the project root directory BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Load the .env file from the project root -load_dotenv(os.path.join(BASE_DIR, '.env')) +load_dotenv(os.path.join(BASE_DIR, ".env")) class Config: """Base configuration class.""" # Database Configuration - DB_USER = os.getenv('DATABASE_USERNAME', 'postgres') - DB_PASSWORD = os.getenv('DATABASE_PASSWORD', 'postgres') - DB_NAME = os.getenv('DATABASE_NAME', 'unittesting') - DB_HOST = os.getenv('DATABASE_HOST', 'localhost') - DB_PORT = os.getenv('DATABASE_PORT', '5432') - - DB_SCHEMA = os.getenv('DATABASE_SCHEMA', 'public') - DB_IP_TYPE = os.getenv('DATABASE_IP_TYPE', 'private') - DB_OWNER = os.getenv('DATABASE_OWNER', 'postgres') - - if DB_INSTANCE_CONNECTION_NAME := os.getenv('DATABASE_INSTANCE_CONNECTION_NAME', None): - SQLALCHEMY_DATABASE_URI = 'postgresql+pg8000://' + DB_USER = os.getenv("DATABASE_USERNAME", "") + DB_PASSWORD = os.getenv("DATABASE_PASSWORD", "") + DB_NAME = os.getenv("DATABASE_NAME", "") + DB_HOST = os.getenv("DATABASE_HOST", "") + DB_PORT = os.getenv("DATABASE_PORT", "5432") + + DB_SCHEMA = os.getenv("DATABASE_SCHEMA", "") + DB_IP_TYPE = os.getenv("DATABASE_IP_TYPE", "") + DB_OWNER = os.getenv("DATABASE_OWNER", "") + + if DB_INSTANCE_CONNECTION_NAME := os.getenv( + "DATABASE_INSTANCE_CONNECTION_NAME", None + ): + SQLALCHEMY_DATABASE_URI = "postgresql+pg8000://" else: - SQLALCHEMY_DATABASE_URI = f'postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}' + SQLALCHEMY_DATABASE_URI = ( + f"postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" + ) # Email Configuration EMAIL_RECIPIENTS = os.getenv("EMAIL_RECIPIENTS", "").split(",") - NOTIFY_API_URL = f"{os.getenv("NOTIFY_API_URL", "") + os.getenv("NOTIFY_API_VERSION", "")}/notify" + NOTIFY_API_URL = ( + f"{os.getenv('NOTIFY_API_URL', '')}" + f"{os.getenv('NOTIFY_API_VERSION', '')}" + "/notify" + ) ACCOUNT_SVC_AUTH_URL = os.getenv("KEYCLOAK_AUTH_TOKEN_URL", "") ACCOUNT_SVC_CLIENT_ID = os.getenv("KEYCLOAK_CLIENT_ID", "") ACCOUNT_SVC_CLIENT_SECRET = os.getenv("KEYCLOAK_CLIENT_SECRET", "") @@ -42,18 +53,21 @@ class Config: class DevConfig(Config): """Development-specific configuration.""" + DEBUG = True TESTING = False class TestConfig(Config): """Testing-specific configuration.""" + DEBUG = True TESTING = True class ProdConfig(Config): """Production-specific configuration.""" + DEBUG = False TESTING = False diff --git a/jobs/bad-name-notifier/src/services/__init__.py b/jobs/bad-name-notifier/src/services/__init__.py index 674a7149f..f708e7623 100644 --- a/jobs/bad-name-notifier/src/services/__init__.py +++ b/jobs/bad-name-notifier/src/services/__init__.py @@ -1,4 +1,4 @@ -# services/__init__.py +"""Service layer package for the Bad Name Notifier application.""" from .database_service import get_bad_names from .email_service import send_email_notification diff --git a/jobs/bad-name-notifier/src/services/database_service.py b/jobs/bad-name-notifier/src/services/database_service.py index ff11437e6..5c8b9b204 100644 --- a/jobs/bad-name-notifier/src/services/database_service.py +++ b/jobs/bad-name-notifier/src/services/database_service.py @@ -1,9 +1,28 @@ -from cloud_sql_connector import DBConfig, getconn +"""Database service for retrieving bad names from the data source.""" + from flask import current_app +from google.cloud.sql.connector import Connector from .utils import get_yesterday_utc_range +def getconn(): + """Create and return a database connection using Cloud SQL Connector. + Returns: + tuple: A tuple containing (connection, connector instance). + """ + connector = Connector() + conn = connector.connect( + current_app.config.get("DB_INSTANCE_CONNECTION_NAME"), + "pg8000", # driver + user=current_app.config.get("DB_USER"), + db=current_app.config.get("DB_NAME"), + ip_type=current_app.config.get("DB_IP_TYPE", "private"), + enable_iam_auth=True, # 🔥 REQUIRED + ) + return conn, connector + + def get_bad_names() -> list[dict]: """ Fetch bad names from the database and return as a list of dictionaries. @@ -14,22 +33,7 @@ def get_bad_names() -> list[dict]: - Contains non-standard ASCII characters - Event occurred after the start of yesterday UTC """ - schema = current_app.config.get("DB_SCHEMA", "public") - - db_config = DBConfig( - instance_name=current_app.config.get("DB_INSTANCE_CONNECTION_NAME"), - database=current_app.config.get("DB_NAME"), - user=current_app.config.get("DB_USER"), - ip_type=current_app.config.get("DB_IP_TYPE", "private"), - schema=schema, - pool_recycle=300, - ) - - # Ensure required fields are set - if not all([db_config.instance_name, db_config.database, db_config.user]): - raise ValueError("DBConfig fields instance_name, database, and user must be set") - - conn = getconn(db_config) + conn, connector = getconn() cursor = conn.cursor() start_of_yesterday_utc, start_of_today_utc = get_yesterday_utc_range() @@ -73,3 +77,4 @@ def get_bad_names() -> list[dict]: finally: cursor.close() conn.close() + connector.close() diff --git a/jobs/bad-name-notifier/src/services/email_service.py b/jobs/bad-name-notifier/src/services/email_service.py index f5cb1768e..07e23f05e 100644 --- a/jobs/bad-name-notifier/src/services/email_service.py +++ b/jobs/bad-name-notifier/src/services/email_service.py @@ -1,35 +1,48 @@ -import requests +""" +Email service module for sending bad name notifications +via the Notify API. +""" + from http import HTTPStatus + +import requests from flask import current_app -from .utils import get_yesterday_str, get_bearer_token + +from .utils import get_bearer_token, get_yesterday_str + def load_recipients(): """Load recipients dynamically from an environment variable.""" recipients = current_app.config["EMAIL_RECIPIENTS"] - return [r.strip('[]') for r in recipients] if isinstance(recipients, list) else [] + return [r.strip("[]") for r in recipients] if isinstance(recipients, list) else [] + def send_email(email: dict, token: str): """Send the email""" current_app.logger.info(f"Send Email: {email}") return requests.post( - f'{current_app.config.get("NOTIFY_API_URL", "")}', + f"{current_app.config.get('NOTIFY_API_URL', '')}", json=email, headers={ "Content-Type": "application/json", "Authorization": f"Bearer {token}", }, + timeout=10, # ✅ prevents hanging forever ) + def send_email_notification(bad_names): """Sends an email notification with the bad names.""" # Dynamically load recipients recipients = load_recipients() - current_app.logger.info(f'recipients:{recipients}') + current_app.logger.info(f"recipients:{recipients}") # Check if recipients list is empty if not recipients: current_app.logger.error("No recipients found in the configuration.") - raise ValueError("Email recipients are not defined. Please check the configuration.") + raise ValueError( + "Email recipients are not defined. Please check the configuration." + ) # Create email content body = generate_report_title() + "\n\n" + generate_report_body(bad_names) @@ -52,28 +65,29 @@ def send_email_notification(bad_names): f"Failed to send email. Status Code: {resp.status_code}, Response: {resp.text}" ) + def generate_report_title(): """Generates an email title with yesterday's date.""" # Format the date as yyyy-mm-dd yesterday = get_yesterday_str() - + # Construct the email title email_title = f"BAD CHARACTERS FOR {yesterday}" - + return email_title + def generate_report_body(bad_names): """Formats the result into a table with headers and adds a total count at the end.""" # Table headers title = f"{'NR Number':<15}{'Choice':<10}Name" separator = "-" * len(title) - + # Format each row lines = [ - f"{row['nr_num']:<15}{row['choice']:<10}{row['name']}" - for row in bad_names + f"{row['nr_num']:<15}{row['choice']:<10}{row['name']}" for row in bad_names ] - + # Add total count at the end with an extra newline before the footer. total_count = len(bad_names) footer_separator = "-" * len(title) diff --git a/jobs/bad-name-notifier/src/services/utils.py b/jobs/bad-name-notifier/src/services/utils.py index f8d6cd663..d95a00ce1 100644 --- a/jobs/bad-name-notifier/src/services/utils.py +++ b/jobs/bad-name-notifier/src/services/utils.py @@ -1,30 +1,44 @@ +""" +Utility functions for date calculations and authentication +used by the Bad Name Notifier service. +""" + from datetime import datetime, timedelta + import pytz import requests from cachetools import TTLCache, cached from flask import current_app + def get_yesterday_str(): - # Calculate yesterday's date + """Calculate yesterday's date""" yesterday = datetime.now() - timedelta(days=1) - + # Format the date as yyyy-mm-dd - return yesterday.strftime('%Y-%m-%d') + return yesterday.strftime("%Y-%m-%d") + def get_yesterday_utc_range(): - pacific = pytz.timezone('America/Los_Angeles') + """Calculate the start of today and yesterday in Pacific Time""" + pacific = pytz.timezone("America/Los_Angeles") - # Calculate the start of today and yesterday in Pacific Time - start_of_today_pacific = pacific.localize(datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)) + start_of_today_pacific = pacific.localize( + datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + ) start_of_yesterday_pacific = start_of_today_pacific - timedelta(days=1) # Convert to UTC - start_of_today_utc = start_of_today_pacific.astimezone(pytz.utc).strftime('%Y-%m-%d %H:%M:%S') - start_of_yesterday_utc = start_of_yesterday_pacific.astimezone(pytz.utc).strftime('%Y-%m-%d %H:%M:%S') + start_of_today_utc = start_of_today_pacific.astimezone(pytz.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + start_of_yesterday_utc = start_of_yesterday_pacific.astimezone(pytz.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) return start_of_yesterday_utc, start_of_today_utc -@staticmethod + @cached(cache=TTLCache(maxsize=1, ttl=180)) def get_bearer_token(): """Get a valid Bearer token for the service to use.""" @@ -38,10 +52,11 @@ def get_bearer_token(): data="grant_type=client_credentials", headers={"content-type": "application/x-www-form-urlencoded"}, auth=(client_id, client_secret), + timeout=10, # ✅ prevents hanging ) try: return res.json().get("access_token") - except Exception: - current_app.logger.error(f"Error getting Bearer Token.") - return None \ No newline at end of file + except Exception: # pylint: disable=broad-exception-caught + current_app.logger.error("Error getting Bearer Token.") + return None diff --git a/jobs/bad-name-notifier/tests/__init__.py b/jobs/bad-name-notifier/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/jobs/bad-name-notifier/tests/test_bad_name_notifier.py b/jobs/bad-name-notifier/tests/test_bad_name_notifier.py new file mode 100644 index 000000000..df0589a52 --- /dev/null +++ b/jobs/bad-name-notifier/tests/test_bad_name_notifier.py @@ -0,0 +1,21 @@ +import sys +import types + +# Mock 'config' so Python won't complain +sys.modules["config"] = types.ModuleType("config") + +# Optional: add functions or attributes your code expects +sys.modules["config"].get_named_config = lambda name: {} + +from app import create_app # noqa: E402 + + +def test_create_app_returns_flask_app(): + app = create_app() + assert app is not None + assert hasattr(app, "run") + + +def test_app_config_default(): + app = create_app() + assert app.config is not None diff --git a/jobs/bad-name-notifier/tests/test_config.py b/jobs/bad-name-notifier/tests/test_config.py new file mode 100644 index 000000000..038210b1e --- /dev/null +++ b/jobs/bad-name-notifier/tests/test_config.py @@ -0,0 +1,45 @@ +import sys + +import pytest + + +def reload_config_module(): + if "config" in sys.modules: + del sys.modules["config"] + import config as config + + return config + + +def test_sqlalchemy_uri_with_instance(monkeypatch): + monkeypatch.setenv("DATABASE_INSTANCE_CONNECTION_NAME", "test-instance") + + config = reload_config_module() + + assert config.Config.SQLALCHEMY_DATABASE_URI == "postgresql+pg8000://" + + +def test_sqlalchemy_uri_without_instance(monkeypatch): + monkeypatch.delenv("DATABASE_INSTANCE_CONNECTION_NAME", raising=False) + + config = reload_config_module() + + assert "postgresql+pg8000://" in config.Config.SQLALCHEMY_DATABASE_URI + + +def test_get_named_config_valid(): + import config as config + + assert config.get_named_config("development") == config.DevConfig + assert config.get_named_config("testing") == config.TestConfig + assert config.get_named_config("production") == config.ProdConfig + assert config.get_named_config("default") == config.ProdConfig + + +def test_get_named_config_invalid(): + import config as config + + with pytest.raises(KeyError) as exc_info: + config.get_named_config("invalid-config") + + assert "Unknown configuration" in str(exc_info.value)