Skip to content

[Practice Exercises]: Add Better Error Handling Instructions & Tests for Error Raising Messages (# 5 of 8) #2716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions exercises/practice/nth-prime/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Instructions append

## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the `prime()` function receives malformed input. Since this exercise deals only with _positive_ numbers, any number < 1 is malformed. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# when the prime function receives malformed input
raise ValueError('there is no zeroth prime')
```
2 changes: 1 addition & 1 deletion exercises/practice/nth-prime/.meta/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

def prime(number):
if number < 1:
raise ValueError('The parameter `number` has to be a positive number.')
raise ValueError('there is no zeroth prime')

known = []
candidates = prime_candidates()
Expand Down
7 changes: 3 additions & 4 deletions exercises/practice/nth-prime/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
{%- set input = case["input"] -%}
def test_{{ case["description"] | to_snake }}(self):
{% if case is error_case -%}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{ case["property"] | to_snake }}({{ input["number"] }})
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{% else -%}
self.assertEqual(
{{ case["property"] | to_snake }}({{ input["number"] }}),
Expand All @@ -30,6 +32,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{{ test_case(case) }}
{% endfor %}
{%- endif %}


{{ macros.footer(has_error_case) }}
12 changes: 3 additions & 9 deletions exercises/practice/nth-prime/nth_prime_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ def test_big_prime(self):
self.assertEqual(prime(10001), 104743)

def test_there_is_no_zeroth_prime(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
prime(0)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "there is no zeroth prime")

# Additional tests for this track

Expand Down Expand Up @@ -57,11 +59,3 @@ def test_first_twenty_primes(self):
71,
],
)

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()
18 changes: 16 additions & 2 deletions exercises/practice/ocr-numbers/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Hints
# Instructions append

Your converter should validate its input and raise a ValueError with a meaningful message if it receives a string that isn't a valid OCR number.
## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the `convert()` function receives a string that isn't a valid OCR number. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# when the rows aren't multiples of 4
raise ValueError("Number of input lines is not a multiple of four")

# when the columns aren't multiples of 3
raise ValueError("Number of input columns is not a multiple of three")
```
9 changes: 6 additions & 3 deletions exercises/practice/ocr-numbers/.meta/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ def convert(input_grid):


def convert_one_line(input_grid):
if (len(input_grid) != NUM_ROWS or len(input_grid[0]) % NUM_COLS or
any(len(r) != len(input_grid[0]) for r in input_grid)):
raise ValueError('Wrong grid size.')
if len(input_grid) != NUM_ROWS:
raise ValueError("Number of input lines is not a multiple of four")

if len(input_grid[0]) % NUM_COLS:
raise ValueError("Number of input columns is not a multiple of three")

numbers = split_ocr(input_grid)
digits = ''
for n in numbers:
Expand Down
8 changes: 4 additions & 4 deletions exercises/practice/ocr-numbers/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
def test_{{ case["description"] | to_snake }}(self):
{% set candidate = case["input"]["rows"] -%}
{% set output = case["expected"] -%}
{% if output is mapping -%}
with self.assertRaisesWithMessage(ValueError):
{% if case is error_case -%}
with self.assertRaises(ValueError) as err:
{{ case["property"] | to_snake }}({{ candidate }})
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{% else -%}
{% set expected = case["expected"] -%}
self.assertEqual({{ case["property"] | to_snake }}({{ candidate }}), "{{ expected }}")
{% endif %}

{% endfor %}

{{ macros.footer(True) }}
1 change: 1 addition & 0 deletions exercises/practice/ocr-numbers/ocr_numbers.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
def convert(input_grid):
pass

20 changes: 10 additions & 10 deletions exercises/practice/ocr-numbers/ocr_numbers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,22 @@ def test_unreadable_but_correctly_sized_inputs_return(self):
def test_input_with_a_number_of_lines_that_is_not_a_multiple_of_four_raises_an_error(
self,
):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
convert([" _ ", "| |", " "])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(
err.exception.args[0], "Number of input lines is not a multiple of four"
)

def test_input_with_a_number_of_columns_that_is_not_a_multiple_of_three_raises_an_error(
self,
):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
convert([" ", " |", " |", " "])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(
err.exception.args[0], "Number of input columns is not a multiple of three"
)

def test_recognizes_110101100(self):
self.assertEqual(
Expand Down Expand Up @@ -114,11 +122,3 @@ def test_numbers_separated_by_empty_lines_are_recognized_lines_are_joined_by_com
),
"123,456,789",
)

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
# Notes regarding the implementation of `smallest` and `largest`:
# Instructions append

## Notes regarding the implementation of `smallest` and `largest`:

Both functions must take two keyword arguments:
- `max_factor`: int
- `min_factor`: int, default 0

Their return value must be a tuple (value, factors) where value is the
palindrome itself, and factors is an iterable containing both factors of the
palindrome in arbitrary order.
Their return value must be a `tuple -- (value, factors)` where `value` is the
palindrome itself, and `factors` is an `iterable` containing both factors of the
palindrome in arbitrary order.


## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the `largest()` or `smallest()` function receives a pair of factors that are not in the correct range. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# if the max_factor is less than the min_factor
raise ValueError("min must be <= max")
```
12 changes: 3 additions & 9 deletions exercises/practice/palindrome-products/.meta/example.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
from __future__ import division
from itertools import chain
from math import log10, floor, ceil


def largest(min_factor, max_factor):
return get_extreme_palindrome_with_factors(max_factor, min_factor,
"largest")
return get_extreme_palindrome_with_factors(max_factor, min_factor, "largest")


def smallest(max_factor, min_factor):
return get_extreme_palindrome_with_factors(max_factor, min_factor,
"smallest")
return get_extreme_palindrome_with_factors(max_factor, min_factor, "smallest")


def get_extreme_palindrome_with_factors(max_factor, min_factor, extreme):
Expand Down Expand Up @@ -53,10 +50,7 @@ def palindromes(max_factor, min_factor, reverse=False):
most of the palindromes just to find the one it needs.
"""
if max_factor < min_factor:
raise ValueError("invalid input: min is {min_factor} "
"and max is {max_factor}"
.format(min_factor=min_factor,
max_factor=max_factor))
raise ValueError("min must be <= max")

minimum = min_factor ** 2
maximum = max_factor ** 2
Expand Down
6 changes: 3 additions & 3 deletions exercises/practice/palindrome-products/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
{%- set expected = case["expected"] -%}
def test_{{ case["description"] | to_snake }}(self):
{%- if case is error_case %}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{ value_factor_unpacking(case) }}
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{%- else %}
{{ value_factor_unpacking(case) }}
{%- if expected["value"] is none %}
Expand All @@ -31,5 +33,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):

def assertFactorsEqual(self, actual, expected):
self.assertEqual(set(map(frozenset, actual)), set(map(frozenset, expected)))

{{ macros.footer() }}
18 changes: 18 additions & 0 deletions exercises/practice/palindrome-products/palindrome_products.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
def largest(min_factor, max_factor):
"""Given a range of numbers, find the largest palindromes which
are products of two numbers within that range.

:param min_factor: int with a default value of 0
:param max_factor: int
:return: tuple of (palindrome, iterable).
Iterable should contain both factors of the palindrome in an arbitrary order.
"""

pass


def smallest(min_factor, max_factor):
"""Given a range of numbers, find the smallest palindromes which
are products of two numbers within that range.

:param min_factor: int with a default value of 0
:param max_factor: int
:return: tuple of (palindrome, iterable).
Iterable should contain both factors of the palindrome in an arbitrary order.
"""

pass
16 changes: 6 additions & 10 deletions exercises/practice/palindrome-products/palindrome_products_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,16 @@ def test_empty_result_for_largest_if_no_palindrome_in_the_range(self):
self.assertFactorsEqual(factors, [])

def test_error_result_for_smallest_if_min_is_more_than_max(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
value, factors = smallest(min_factor=10000, max_factor=1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "min must be <= max")

def test_error_result_for_largest_if_min_is_more_than_max(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
value, factors = largest(min_factor=2, max_factor=1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "min must be <= max")

def assertFactorsEqual(self, actual, expected):
self.assertEqual(set(map(frozenset, actual)), set(map(frozenset, expected)))

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()