Skip to content
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

Miscellaneous improvements to approx() #3741

Merged
merged 16 commits into from Aug 2, 2018

Conversation

kalekundert
Copy link
Contributor

@kalekundert kalekundert commented Jul 31, 2018

This PR fixes a couple small issues with approx():

@nicoddemus
Copy link
Member

Hi @kalekundert, thanks a lot for the PR.

I did not take a good look at it yet, but a cursory glance reminds me that we should add changelog notes to the PR. Feel free to add more than one if you feel like it. 👍

@coveralls
Copy link

coveralls commented Jul 31, 2018

Coverage Status

Coverage increased (+0.1%) to 92.605% when pulling a5c0fb7 on kalekundert:approx_misc_tweaks into 2534193 on pytest-dev:master.

CHANGELOG.rst Outdated
@@ -54,6 +54,9 @@ Bug Fixes
- `#3695 <https://github.com/pytest-dev/pytest/issues/3695>`_: Fix ``ApproxNumpy`` initialisation argument mixup, ``abs`` and ``rel`` tolerances were flipped causing strange comparsion results.
Add tests to check ``abs`` and ``rel`` tolerances for ``np.array`` and test for expecting ``nan`` with ``np.array()``

- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of an numpy array when calling ``repr()`` on ``approx()``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have been more clear: you should add changelog files to the changelog/ folder. See the README on https://github.com/pytest-dev/pytest/tree/master/changelog for more instructions. 👍

Copy link
Member

@nicoddemus nicoddemus Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I did some a small tweak in your branch and fixed the changelog notes myself. 👍 😁

* Hide the internal traceback
* Use !r representation instead of !s (the default for {} formatting)
Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work, thank you a lot!

for x in self.expected.values():
if isinstance(x, type(self.expected)):
raise TypeError(
"pytest.approx() does not support nested dictionaries, e.g. {!r}".format(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for extra support this should also include the key first and it might help to use "pretty formatting" to split larger dicts to something readable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done:

____________________________________ test1 ____________________________________

    def test1():
>       pytest.approx({'a': 1.2, 'b': {1:2}})
E       TypeError: pytest.approx() does not support nested dictionaries: key='b' value={1: 2}
E         full mapping={'a': 1.2, 'b': {1: 2}}

.tmp\foo.py:5: TypeError
____________________________________ test2 ____________________________________

    def test2():
>       pytest.approx([1,2,3, [1,2,3]])
E       TypeError: pytest.approx() does not support nested data structures: [1, 2, 3] at index 3
E         full sequence: [1, 2, 3, [1, 2, 3]]

.tmp\foo.py:8: TypeError
____________________________________ test3 ____________________________________

    def test3():
>       pytest.approx('foo')
E       TypeError: cannot make approximate comparisons to non-numeric values: 'foo'

.tmp\foo.py:11: TypeError
____________________________________ test4 ____________________________________

    def test4():
>       pytest.approx([1,2,3, ['foo']])
E       TypeError: pytest.approx() does not support nested data structures: ['foo'] at index 3
E         full sequence: [1, 2, 3, ['foo']]

.tmp\foo.py:14: TypeError
____________________________________ test5 ____________________________________

    def test5():
>       pytest.approx({'a': 1.2, 'b': {1:'foo'}})
E       TypeError: pytest.approx() does not support nested dictionaries: key='b' value={1: 'foo'}
E         full mapping={'a': 1.2, 'b': {1: 'foo'}}

.tmp\foo.py:17: TypeError
========================== 5 failed in 0.12 seconds ===========================

np_array = np.array([5.])
assert approx(np_array) == 5.0
assert repr(approx(np_array)) == string_expected
examples = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we put the parameter to np.array into a parametrize - the importorskip and wrapping can stay in the test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as extra note i realized that is_numpy_array can be massively simplified by sys.modules.get('numpy') followed by a getattr(...., 'ndarray', None)

also a potential follow-up is support for pypys micronumpy`

# It might be nice to rewrite this function to account for the
# shape of the array...
import numpy as np
def recursive_map(f, x):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this helper is independent, lets move it out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

for key, value in self.expected.items():
if isinstance(value, type(self.expected)):
msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}"
raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lovely 👍

@@ -439,6 +443,13 @@ def test_foo():
["*At index 0 diff: 3 != 4 * {}".format(expected), "=* 1 failed in *="]
)

@pytest.mark.parametrize(
"x", [None, "string", ["string"], [[1]], {"key": "string"}, {"key": {"key": 1}}]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe test ids would help here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@nicoddemus
Copy link
Member

as extra note i realized that is_numpy_array can be massively simplified by sys.modules.get('numpy') followed by a getattr(...., 'ndarray', None)

Done as well, let me know if that's what you had in mind. 👍

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fabulous 👍

pass


def recursive_map(f, x):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little hestiant about this being a module-level function, because it only works for nested lists (e.g. not tuples or any other kind of iterable). That's fine for ApproxNumpy.__repr__(), because numpy.tolist() is guaranteed to return nested lists, but this isn't really a generally useful function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You got a point, but I think it doesn't hurt leaving it there; it is not part of the public API after all. @RonnyPfannschmidt any thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name it recursive_list_map

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, renamed it _recursive_list_map as well to make it clear it is an internal API.

@kalekundert
Copy link
Contributor Author

Thanks for all the revisions!

@nicoddemus nicoddemus merged commit 804fc40 into pytest-dev:master Aug 2, 2018
@kalekundert kalekundert deleted the approx_misc_tweaks branch August 2, 2018 04:55
@blueyed blueyed mentioned this pull request Aug 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants