diff --git a/.github/citools/python/python-lint-refurb b/.github/citools/python/python-lint-refurb
index 08aa20d9..8672574c 100755
--- a/.github/citools/python/python-lint-refurb
+++ b/.github/citools/python/python-lint-refurb
@@ -20,10 +20,19 @@ main() {
print_ruler
# shellcheck disable=SC2048,SC2068,SC2086
- run_command refurb ${@:-./src}
+ run_command refurb --quiet --ignore 183 ${@:-./src}
print_ruler
+ local -a codes
+ local code
+ # shellcheck disable=SC2048,SC2068,SC2086
+ mapfile -t codes < <(refurb --quiet --ignore 183 ${@:-./src} |& sed -r -e 's/^.*\[(FURB[0-9]+)\].*$/\1/g')
+ for code in "${codes[@]}"; do
+ run_command refurb --explain "${code}"
+ print_ruler
+ done
+
echo Exit code: "${retval}"
return "${retval}"
}
diff --git a/python/README.md b/python/README.md
index 01486a22..a5c1548c 100644
--- a/python/README.md
+++ b/python/README.md
@@ -39,3 +39,4 @@
- [leap](./leap/README.md)
- [mecha-munch-management](./mecha-munch-management/README.md)
- [plane-tickets](./plane-tickets/README.md)
+- [raindrops](./raindrops/README.md)
diff --git a/python/raindrops/.coverage b/python/raindrops/.coverage
new file mode 100644
index 00000000..bcf70444
--- /dev/null
+++ b/python/raindrops/.coverage
@@ -0,0 +1 @@
+!coverage.py: This is a private format, don't read it directly!{"arcs":{"/home/vpayno/git_vpayno/exercism-workspace/python/raindrops/test/__init__.py":[[0,0],[0,-1]],"/home/vpayno/git_vpayno/exercism-workspace/python/raindrops/raindrops.py":[[0,1],[1,4],[4,-1],[4,11],[11,13],[13,16],[16,19],[19,22],[22,23],[23,25],[25,-4],[13,14],[14,16],[16,17],[17,19],[19,20],[20,22],[22,25]]}}
\ No newline at end of file
diff --git a/python/raindrops/.coverage.xml b/python/raindrops/.coverage.xml
new file mode 100644
index 00000000..d49cdd1b
--- /dev/null
+++ b/python/raindrops/.coverage.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ /home/vpayno/git_vpayno/exercism-workspace/python/raindrops
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/raindrops/.coveragerc b/python/raindrops/.coveragerc
new file mode 100644
index 00000000..883531db
--- /dev/null
+++ b/python/raindrops/.coveragerc
@@ -0,0 +1,2 @@
+[run]
+omit = __init__.py, *_test.py
diff --git a/python/raindrops/.exercism/config.json b/python/raindrops/.exercism/config.json
new file mode 100644
index 00000000..70763b83
--- /dev/null
+++ b/python/raindrops/.exercism/config.json
@@ -0,0 +1,32 @@
+{
+ "authors": [],
+ "contributors": [
+ "behrtam",
+ "BethanyG",
+ "bsoyka",
+ "cmccandless",
+ "Dog",
+ "ikhadykin",
+ "kytrinyx",
+ "lowks",
+ "N-Parsons",
+ "pheanex",
+ "sjakobi",
+ "tqa236",
+ "yawpitch"
+ ],
+ "files": {
+ "solution": [
+ "raindrops.py"
+ ],
+ "test": [
+ "raindrops_test.py"
+ ],
+ "example": [
+ ".meta/example.py"
+ ]
+ },
+ "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.",
+ "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.",
+ "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz"
+}
diff --git a/python/raindrops/.exercism/metadata.json b/python/raindrops/.exercism/metadata.json
new file mode 100644
index 00000000..3532e583
--- /dev/null
+++ b/python/raindrops/.exercism/metadata.json
@@ -0,0 +1 @@
+{"track":"python","exercise":"raindrops","id":"954a4e30d0eb49f9bb1ff7a14982fb08","url":"https://exercism.org/tracks/python/exercises/raindrops","handle":"vpayno","is_requester":true,"auto_approve":false}
\ No newline at end of file
diff --git a/python/raindrops/.pylintrc b/python/raindrops/.pylintrc
new file mode 120000
index 00000000..30b33b52
--- /dev/null
+++ b/python/raindrops/.pylintrc
@@ -0,0 +1 @@
+../.pylintrc
\ No newline at end of file
diff --git a/python/raindrops/HELP.md b/python/raindrops/HELP.md
new file mode 100644
index 00000000..87397529
--- /dev/null
+++ b/python/raindrops/HELP.md
@@ -0,0 +1,130 @@
+# Help
+
+## Running the tests
+
+We use [pytest][pytest: Getting Started Guide] as our website test runner.
+You will need to install `pytest` on your development machine if you want to run tests for the Python track locally.
+You should also install the following `pytest` plugins:
+
+- [pytest-cache][pytest-cache]
+- [pytest-subtests][pytest-subtests]
+
+Extended information can be found in our website [Python testing guide][Python track tests page].
+
+
+### Running Tests
+
+To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_).
+Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded.
+
+Linux/MacOS
+```bash
+$ cd {path/to/exercise-folder-location}
+```
+
+Windows
+```powershell
+PS C:\Users\foobar> cd {path\to\exercise-folder-location}
+```
+
+
+
+Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file:
+
+Linux/MacOS
+```bash
+$ python3 -m pytest -o markers=task {exercise_test.py}
+==================== 7 passed in 0.08s ====================
+```
+
+Windows
+```powershell
+PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py}
+==================== 7 passed in 0.08s ====================
+```
+
+
+### Common options
+- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_)
+- `-v` : enable verbose output.
+- `-x` : stop running tests on first failure.
+- `--ff` : run failures from previous test before running other test cases.
+
+For additional options, use `python3 -m pytest -h` or `py -m pytest -h`.
+
+
+### Fixing warnings
+
+If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax:
+
+```bash
+PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
+```
+
+To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file.
+We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini].
+
+You can also create your own `pytest.ini` file with the following content:
+
+```ini
+[pytest]
+markers =
+ task: A concept exercise task.
+```
+
+Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings.
+More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers].
+
+Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats].
+
+
+### Extending your IDE or Code Editor
+
+Many IDEs and code editors have built-in support for using `pytest` and other code quality tools.
+Some community-sourced options can be found on our [Python track tools page][Python track tools page].
+
+[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html
+[Python track tools page]: https://exercism.org/docs/tracks/python/tools
+[Python track tests page]: https://exercism.org/docs/tracks/python/tests
+[pytest-cache]:http://pythonhosted.org/pytest-cache/
+[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests
+[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini
+[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats
+[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks
+[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers
+
+## Submitting your solution
+
+You can submit your solution using the `exercism submit raindrops.py` command.
+This command will upload your solution to the Exercism website and print the solution page's URL.
+
+It's possible to submit an incomplete solution which allows you to:
+
+- See how others have completed the exercise
+- Request help from a mentor
+
+## Need to get help?
+
+If you'd like help solving the exercise, check the following pages:
+
+- The [Python track's documentation](https://exercism.org/docs/tracks/python)
+- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python)
+- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
+- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
+
+Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
+
+Below are some resources for getting help if you run into trouble:
+
+- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources.
+- [The Exercism Community on Discord](https://exercism.org/r/discord)
+- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community.
+- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners.
+- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done.
+- [Python Community Forums](https://discuss.python.org/)
+- [Free Code Camp Community Forums](https://forum.freecodecamp.org/)
+- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help)
+- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually.
+
+Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already.
+ If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question.
\ No newline at end of file
diff --git a/python/raindrops/README.md b/python/raindrops/README.md
new file mode 100644
index 00000000..97f1bea7
--- /dev/null
+++ b/python/raindrops/README.md
@@ -0,0 +1,80 @@
+# Raindrops
+
+Welcome to Raindrops on Exercism's Python Track.
+If you need help running the tests or submitting your code, check out `HELP.md`.
+
+## Introduction
+
+Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question.
+
+## Instructions
+
+Your task is to convert a number into its corresponding raindrop sounds.
+
+If a given number:
+
+- is divisible by 3, add "Pling" to the result.
+- is divisible by 5, add "Plang" to the result.
+- is divisible by 7, add "Plong" to the result.
+- **is not** divisible by 3, 5, or 7, the result should be the number as a string.
+
+## Examples
+
+- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`.
+- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`.
+- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`.
+
+~~~~exercism/note
+A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero.
+Most languages provide operators or functions for one (or both) of these.
+
+[remainder]: https://exercism.org/docs/programming/operators/remainder
+[modulo]: https://en.wikipedia.org/wiki/Modulo_operation
+~~~~
+
+## How this Exercise is Structured in Python
+
+This exercise is best solved with Python's `%` ([modulo][modulo]) operator, which returns the remainder of positive integer division.
+It has a method equivalent, `operator.mod()` in the [operator module][operator-mod].
+
+
+Python also offers additional 'remainder' methods in the [math module][math-module].
+[`math.fmod()`][fmod] behaves like `%`, but operates on floats.
+[`math.remainder()`][remainder] implements a "step closest to zero" algorithm for the remainder of division.
+While we encourage you to get familiar with these methods, neither of these will exactly match the result of `%`, and are not recommended for use with this exercise.
+
+The built-in function [`divmod()`][divmod] will also give a remainder than matches `%` if used with two positive integers, but returns a `tuple` that needs to be unpacked.
+
+[divmod]: https://docs.python.org/3/library/functions.html#divmod
+[fmod]: https://docs.python.org/3/library/math.html#math.fmod
+[math-module]: https://docs.python.org/3/library/math.html
+[modulo]: https://www.programiz.com/python-programming/operators#arithmetic
+[operator-mod]: https://docs.python.org/3/library/operator.html#operator.mod
+[remainder]: https://docs.python.org/3/library/math.html#math.remainder
+
+## Source
+
+### Contributed to by
+
+- @behrtam
+- @BethanyG
+- @bsoyka
+- @cmccandless
+- @Dog
+- @ikhadykin
+- @kytrinyx
+- @lowks
+- @N-Parsons
+- @pheanex
+- @sjakobi
+- @tqa236
+- @yawpitch
+
+### Based on
+
+A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division. - https://en.wikipedia.org/wiki/Fizz_buzz
+
+### My Solution
+
+- [my solution](./raindrops.py)
+- [run-tests](./run-tests-python.txt)
diff --git a/python/raindrops/pytest.ini b/python/raindrops/pytest.ini
new file mode 100644
index 00000000..8dc12a49
--- /dev/null
+++ b/python/raindrops/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+pythonpath = .
+addopts = --doctest-modules
+markers =
+ task: exercise task/step
diff --git a/python/raindrops/raindrops.py b/python/raindrops/raindrops.py
new file mode 100644
index 00000000..62218fee
--- /dev/null
+++ b/python/raindrops/raindrops.py
@@ -0,0 +1,25 @@
+"""Raindrops exercism: https://exercism.org/tracks/python/exercises/raindrops"""
+
+
+def convert(number: int) -> str:
+ """Converts a number to the sound of rain drops.
+
+ :param number: positive integer
+ :return: string with rain sounds or the number if it can't be converted
+ """
+
+ sounds: list[str] = []
+
+ if number % 3 == 0:
+ sounds.append("Pling")
+
+ if number % 5 == 0:
+ sounds.append("Plang")
+
+ if number % 7 == 0:
+ sounds.append("Plong")
+
+ if not sounds:
+ sounds.append(f"{number}")
+
+ return "".join(sounds)
diff --git a/python/raindrops/raindrops.py,cover b/python/raindrops/raindrops.py,cover
new file mode 100644
index 00000000..03cb477e
--- /dev/null
+++ b/python/raindrops/raindrops.py,cover
@@ -0,0 +1,25 @@
+> """Raindrops exercism: https://exercism.org/tracks/python/exercises/raindrops"""
+
+
+> def convert(number: int) -> str:
+> """Converts a number to the sound of rain drops.
+
+> :param number: positive integer
+> :return: string with rain sounds or the number if it can't be converted
+> """
+
+> sounds: list[str] = []
+
+> if number % 3 == 0:
+> sounds.append("Pling")
+
+> if number % 5 == 0:
+> sounds.append("Plang")
+
+> if number % 7 == 0:
+> sounds.append("Plong")
+
+> if not sounds:
+> sounds.append(f"{number}")
+
+> return "".join(sounds)
diff --git a/python/raindrops/raindrops_test.py b/python/raindrops/raindrops_test.py
new file mode 100644
index 00000000..b07e70dc
--- /dev/null
+++ b/python/raindrops/raindrops_test.py
@@ -0,0 +1,67 @@
+# These tests are auto-generated with test data from:
+# https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json
+# File last updated on 2023-07-19
+
+import unittest
+
+from raindrops import (
+ convert,
+)
+
+
+class RaindropsTest(unittest.TestCase):
+ def test_the_sound_for_1_is_1(self):
+ self.assertEqual(convert(1), "1")
+
+ def test_the_sound_for_3_is_pling(self):
+ self.assertEqual(convert(3), "Pling")
+
+ def test_the_sound_for_5_is_plang(self):
+ self.assertEqual(convert(5), "Plang")
+
+ def test_the_sound_for_7_is_plong(self):
+ self.assertEqual(convert(7), "Plong")
+
+ def test_the_sound_for_6_is_pling_as_it_has_a_factor_3(self):
+ self.assertEqual(convert(6), "Pling")
+
+ def test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not_the_base(
+ self,
+ ):
+ self.assertEqual(convert(8), "8")
+
+ def test_the_sound_for_9_is_pling_as_it_has_a_factor_3(self):
+ self.assertEqual(convert(9), "Pling")
+
+ def test_the_sound_for_10_is_plang_as_it_has_a_factor_5(self):
+ self.assertEqual(convert(10), "Plang")
+
+ def test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7(self):
+ self.assertEqual(convert(14), "Plong")
+
+ def test_the_sound_for_15_is_pling_plang_as_it_has_factors_3_and_5(self):
+ self.assertEqual(convert(15), "PlingPlang")
+
+ def test_the_sound_for_21_is_pling_plong_as_it_has_factors_3_and_7(self):
+ self.assertEqual(convert(21), "PlingPlong")
+
+ def test_the_sound_for_25_is_plang_as_it_has_a_factor_5(self):
+ self.assertEqual(convert(25), "Plang")
+
+ def test_the_sound_for_27_is_pling_as_it_has_a_factor_3(self):
+ self.assertEqual(convert(27), "Pling")
+
+ def test_the_sound_for_35_is_plang_plong_as_it_has_factors_5_and_7(self):
+ self.assertEqual(convert(35), "PlangPlong")
+
+ def test_the_sound_for_49_is_plong_as_it_has_a_factor_7(self):
+ self.assertEqual(convert(49), "Plong")
+
+ def test_the_sound_for_52_is_52(self):
+ self.assertEqual(convert(52), "52")
+
+ def test_the_sound_for_105_is_pling_plang_plong_as_it_has_factors_3_5_and_7(self):
+ self.assertEqual(convert(105), "PlingPlangPlong")
+
+ def test_the_sound_for_3125_is_plang_as_it_has_a_factor_5(self):
+ self.assertEqual(convert(3125), "Plang")
diff --git a/python/raindrops/run-tests-python.txt b/python/raindrops/run-tests-python.txt
new file mode 100644
index 00000000..f510e891
--- /dev/null
+++ b/python/raindrops/run-tests-python.txt
@@ -0,0 +1,503 @@
+Running automated test file(s):
+
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-lint-pylint
+
+Running Python Lint - PyLint
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: pylint --version
+
+pylint 3.0.3
+astroid 3.0.2
+Python 3.12.1 (main, Dec 28 2023, 08:22:05) [GCC 10.2.1 20210110]
+
+real 0m0.242s
+user 0m0.184s
+sys 0m0.061s
+
+
+ ==============================================================================
+
+Running: pylint ./src
+
+
+--------------------------------------------------------------------
+Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
+
+
+real 0m0.425s
+user 0m0.344s
+sys 0m0.083s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m1.568s
+user 0m1.171s
+sys 0m0.417s
+
+real 0m1.572s
+user 0m1.173s
+sys 0m0.418s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-lint-ruff
+
+Running Python Lint - Ruff
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: ruff --version
+
+ruff 0.3.5
+
+real 0m0.087s
+user 0m0.027s
+sys 0m0.063s
+
+
+ ==============================================================================
+
+Running: ruff check --ignore E501 ./src
+
+All checks passed!
+
+real 0m0.118s
+user 0m0.050s
+sys 0m0.071s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m1.160s
+user 0m0.741s
+sys 0m0.442s
+
+real 0m1.163s
+user 0m0.742s
+sys 0m0.444s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-lint-pyright
+
+Running Python Lint - PyRight
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: pyright --version
+
+pyright 1.1.357
+
+real 0m0.718s
+user 0m0.522s
+sys 0m0.113s
+
+
+ ==============================================================================
+
+Running: pyright --stats ./src
+
+Found 2 source files
+pyright 1.1.357
+0 errors, 0 warnings, 0 informations
+Completed in 0.573sec
+
+Analysis stats
+Total files parsed and bound: 20
+Total files checked: 2
+
+Timing stats
+Find Source Files: 0sec
+Read Source Files: 0.01sec
+Tokenize: 0.05sec
+Parse: 0.05sec
+Resolve Imports: 0.04sec
+Bind: 0.06sec
+Check: 0.05sec
+Detect Cycles: 0sec
+
+real 0m1.200s
+user 0m1.230s
+sys 0m0.159s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m2.967s
+user 0m2.510s
+sys 0m0.577s
+
+real 0m2.971s
+user 0m2.512s
+sys 0m0.579s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-lint-bandit
+
+Running Python Lint - Bandit
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: bandit --version
+
+bandit 1.7.7
+ python version = 3.12.1 (main, Dec 28 2023, 08:22:05) [GCC 10.2.1 20210110]
+
+real 0m0.285s
+user 0m0.223s
+sys 0m0.064s
+
+
+ ==============================================================================
+
+Running: bandit --verbose --recursive ./src
+
+[main] INFO profile include tests: None
+[main] INFO profile exclude tests: None
+[main] INFO cli include tests: None
+[main] INFO cli exclude tests: None
+[main] INFO running on Python 3.12.1
+Run started:2024-04-09 01:39:01.006850
+Files in scope (2):
+ ./src/raindrops/__init__.py (score: {SEVERITY: 0, CONFIDENCE: 0})
+ ./src/raindrops/raindrops.py (score: {SEVERITY: 0, CONFIDENCE: 0})
+Files excluded (2):
+ ./src/raindrops/__pycache__/__init__.cpython-312.pyc
+ ./src/raindrops/__pycache__/raindrops.cpython-312.pyc
+
+Test results:
+ No issues identified.
+
+Code scanned:
+ Total lines of code: 16
+ Total lines skipped (#nosec): 0
+ Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
+
+Run metrics:
+ Total issues (by severity):
+ Undefined: 0
+ Low: 0
+ Medium: 0
+ High: 0
+ Total issues (by confidence):
+ Undefined: 0
+ Low: 0
+ Medium: 0
+ High: 0
+Files skipped (0):
+
+real 0m0.236s
+user 0m0.170s
+sys 0m0.067s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m1.496s
+user 0m1.100s
+sys 0m0.416s
+
+real 0m1.498s
+user 0m1.102s
+sys 0m0.417s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-lint-refurb
+
+Running Python Lint - Refurb
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: refurb --version
+
+Refurb: v1.26.0
+Mypy: v1.9.0
+
+real 0m0.201s
+user 0m0.134s
+sys 0m0.069s
+
+
+ ==============================================================================
+
+Running: refurb --quiet --ignore 183 ./src
+
+
+real 0m1.132s
+user 0m1.046s
+sys 0m0.088s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m3.391s
+user 0m2.872s
+sys 0m0.543s
+
+real 0m3.394s
+user 0m2.873s
+sys 0m0.543s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-test-with-doctests
+
+Running Python DocTests
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+PYTHONPATH="./src"
+
+ ==============================================================================
+
+Running: python -m doctest -v ./src/raindrops/__init__.py ./src/raindrops/raindrops.py
+
+1 items had no tests:
+ __init__
+0 tests in 1 items.
+0 passed and 0 failed.
+Test passed.
+2 items had no tests:
+ raindrops
+ raindrops.convert
+0 tests in 2 items.
+0 passed and 0 failed.
+Test passed.
+
+real 0m0.142s
+user 0m0.085s
+sys 0m0.059s
+
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m1.015s
+user 0m0.726s
+sys 0m0.306s
+
+real 0m1.016s
+user 0m0.728s
+sys 0m0.306s
+
+===============================================================================
+
+Running: ../../.github/citools/python/python-test-with-coverage
+
+Running Python Tests With Coverage
+
+Python versions:
+
+ Python 3.12.1
+ pyenv 2.3.36-21-g7e550e31
+ pip 24.0 from /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pip (python 3.12)
+ PDM, version 2.13.2
+
+
+ ==============================================================================
+
+Running: rm -rf ./coverage
+
+
+real 0m0.001s
+user 0m0.001s
+sys 0m0.000s
+
+
+ ==============================================================================
+
+Running: pytest --version
+
+pytest 7.4.3
+
+real 0m0.798s
+user 0m0.885s
+sys 0m0.804s
+
+
+ ==============================================================================
+
+PYTHONPATH="./src"
+
+ ==============================================================================
+
+Running: pytest --verbose --cov=. --cov-branch --cov-report=term-missing --cov-report=xml:.coverage.xml -p no:randomly ./test
+
+============================= test session starts ==============================
+platform linux -- Python 3.12.1, pytest-7.4.3, pluggy-1.3.0 -- /home/vpayno/.pyenv/versions/3.12.1/bin/python
+cachedir: .pytest_cache
+hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/vpayno/git_vpayno/exercism-workspace/python/raindrops/.hypothesis/examples'))
+rootdir: /home/vpayno/git_vpayno/exercism-workspace/python/raindrops
+configfile: pytest.ini
+plugins: anyio-4.2.0, libtmux-0.25.0, pylama-8.4.1, cov-4.1.0, datafiles-3.0.0, docker-2.0.1, subprocess-1.5.0, typeguard-4.1.5, hypothesis-6.98.17, snapshot-0.9.0
+collecting ... collected 18 items
+
+test/raindrops_test.py::RaindropsTest::test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not_the_base PASSED [ 5%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_105_is_pling_plang_plong_as_it_has_factors_3_5_and_7 PASSED [ 11%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_10_is_plang_as_it_has_a_factor_5 PASSED [ 16%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7 PASSED [ 22%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_15_is_pling_plang_as_it_has_factors_3_and_5 PASSED [ 27%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_1_is_1 PASSED [ 33%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_21_is_pling_plong_as_it_has_factors_3_and_7 PASSED [ 38%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_25_is_plang_as_it_has_a_factor_5 PASSED [ 44%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_27_is_pling_as_it_has_a_factor_3 PASSED [ 50%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_3125_is_plang_as_it_has_a_factor_5 PASSED [ 55%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_35_is_plang_plong_as_it_has_factors_5_and_7 PASSED [ 61%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_3_is_pling PASSED [ 66%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_49_is_plong_as_it_has_a_factor_7 PASSED [ 72%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_52_is_52 PASSED [ 77%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_5_is_plang PASSED [ 83%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_6_is_pling_as_it_has_a_factor_3 PASSED [ 88%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_7_is_plong PASSED [ 94%]
+test/raindrops_test.py::RaindropsTest::test_the_sound_for_9_is_pling_as_it_has_a_factor_3 PASSED [100%]
+
+=============================== warnings summary ===============================
+../../../../.pyenv/versions/3.12.1/lib/python3.12/site-packages/coverage/pytracer.py:164
+ /home/vpayno/.pyenv/versions/3.12.1/lib/python3.12/site-packages/coverage/pytracer.py:164: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
+ self.thread = self.threading.currentThread()
+
+-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
+
+---------- coverage: platform linux, python 3.12.1-final-0 -----------
+Name Stmts Miss Branch BrPart Cover Missing
+--------------------------------------------------------------
+raindrops.py 12 1 8 0 95%
+test/__init__.py 0 0 0 0 100%
+--------------------------------------------------------------
+TOTAL 12 1 8 0 95%
+Coverage XML written to file .coverage.xml
+
+======================== 18 passed, 1 warning in 0.91s =========================
+
+real 0m1.801s
+user 0m1.662s
+sys 0m0.140s
+
+
+ ==============================================================================
+
+Running: coverage report --show-missing
+
+Name Stmts Miss Branch BrPart Cover Missing
+--------------------------------------------------------------
+raindrops.py 12 1 8 0 95%
+test/__init__.py 0 0 0 0 100%
+--------------------------------------------------------------
+TOTAL 12 1 8 0 95%
+
+real 0m0.158s
+user 0m0.093s
+sys 0m0.068s
+
+
+ ==============================================================================
+
+Running: coverage annotate
+
+
+real 0m0.149s
+user 0m0.095s
+sys 0m0.056s
+
+
+ ==============================================================================
+
+Line Coverage: 91.7%
+Branch Coverage: 100.0%
+
+ ==============================================================================
+
+Exit code: 0
+
+real 0m3.846s
+user 0m3.405s
+sys 0m1.355s
+
+real 0m3.847s
+user 0m3.405s
+sys 0m1.357s
+
+===============================================================================
+
+tail -n 10000 ./*,cover | grep -E -C 3 '^> def |^! '
+
+> def convert(number: int) -> str:
+> """Converts a number to the sound of rain drops.
+
+===============================================================================
+
+Running: misspell ./src/raindrops/__init__.py ./src/raindrops/raindrops.py
+
+real 0m0.018s
+user 0m0.020s
+sys 0m0.008s
+
+===============================================================================
+
diff --git a/python/raindrops/src/raindrops/__init__.py b/python/raindrops/src/raindrops/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/raindrops/src/raindrops/raindrops.py b/python/raindrops/src/raindrops/raindrops.py
new file mode 120000
index 00000000..0b8d2792
--- /dev/null
+++ b/python/raindrops/src/raindrops/raindrops.py
@@ -0,0 +1 @@
+../../raindrops.py
\ No newline at end of file
diff --git a/python/raindrops/test/__init__.py b/python/raindrops/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/raindrops/test/__init__.py,cover b/python/raindrops/test/__init__.py,cover
new file mode 100644
index 00000000..e69de29b
diff --git a/python/raindrops/test/raindrops_test.py b/python/raindrops/test/raindrops_test.py
new file mode 120000
index 00000000..f3e8de9b
--- /dev/null
+++ b/python/raindrops/test/raindrops_test.py
@@ -0,0 +1 @@
+../raindrops_test.py
\ No newline at end of file