Testing failure of an assignment with unittest - python

One of my attributes is a property where the setter calls a validation function that raises an exception if the new value is invalid:
pos.offset = 0
# #offset.setter calls validate(offset=0)
# PositionError: Offset may not be 0.
I'm trying to add a test to ensure that this fails. However, I can't figure out how to get assertRaises to work with an assignment.
The normal syntax of assertRaises requires a method, not an attribute/property:
self.assertRaises(PositionError, pos.offset, 0)
# TypeError: 'int' object is not callable
The other forms I've tried are invalid Python:
self.assertRaises(PositionError, pos.offset = 0)
# SyntaxError: Keyword can't be an expression
self.assertRaises(PositionError, lambda: pos.offset = 0)
# SyntaxError: lambda cannot contain assignment
How do I test failure of assignment to a property?
Note: Python 2.6, I know unittest has some new features in 2.7

When you want to use unittest to test that an exception occurs in a block of code rather than just a function call, you can use assertRaises as a context manager:
with self.assertRaises(PositionError):
pos.offset = 0
This use can be found in the unittest docs.
While this is not available in Python 2.6, and won't work for you as such (I just saw your note), I think it's worth including among the answers, as it's probably the clearer way to do this for python 2.7+.

Before Python 2.7, you want to use setattr(object, name, value) (doc). This allows you to set a value to an attribute.
self.assertRaises(PositionError, setattr, pos, "offset", 0)
# ... ok
This should always work, because if the attribute is a property and thus "secretly" calls a function, it must be within a class. I don't think a standard assignment can fail (although an expression on the right side can fail, i.e. x = 1/0).

Related

Inspect (duck) type of python function arguments

Given the following display function,
def display(some_object):
print(some_object.value)
is there a way to programmatically determine that the attributes of some_object must include value?
Modern IDEs (like PyCharm) yield a syntax error if I try to pass an int to the display function, so they are obviously doing this kind of analysis behind the scenes. I am aware how to get the function signature, this question is only about how to get the (duck) type information, i.e. which attributes are expected for each function argument.
EDIT: In my specific use case, I have access to the source code (non obfuscated), but I am not in control of adding the type hints as the functions are user defined.
Toy example
For the simple display function, the following inspection code would do,
class DuckTypeInspector:
def __init__(self):
self.attrs = []
def __getattr__(self, attr):
return self.attrs.append(attr)
dti = DuckTypeInspector()
display(dti)
print(dti.attrs)
which outputs
None # from the print in display
['value'] # from the last print statement, this is what i am after
However, as the DuckTypeInspector always returns None, this approach won't work in general. A simple add function for example,
def add(a, b):
return a + b
dti1 = DuckTypeInspector()
dti2 = DuckTypeInspector()
add(dti1, dti2)
would yield the following error,
TypeError: unsupported operand type(s) for +: 'DuckTypeInspector' and 'DuckTypeInspector'
The way to do this with static analysis is to declare the parameters as adhering to a protocol and then use mypy to validate that the actual parameters implement that protocol:
from typing import Protocol
class ValueProtocol(Protocol):
value: str
class ValueThing:
def __init__(self):
self.value = "foo"
def display(some_object: ValueProtocol):
print(some_object.value)
display(ValueThing()) # no errors, because ValueThing implements ValueProtocol
display("foo") # mypy error: Argument 1 to "display" has incompatible type "str"; expected "ValueProtocol"
Doing this at runtime with mock objects is impossible to do in a generic way, because you can't be certain that the function will go through every possible code path; you would need to write a unit test with carefully constructed mock objects for each function and make sure that you maintain 100% code coverage.
Using type annotations and static analysis is much easier, because mypy (or similar tools) can check each branch of the function to make sure that the code is compatible with the declared type of the parameter, without having to generate fake values and actually execute the function against them.
If you want to programmatically inspect the annotations from someone else's module, you can use the magic __annotations__ attribute:
>>> display.__annotations__
{'some_object': <class '__main__.ValueProtocol'>, 'return': None}

What is the difference between using magic method of a class with following syntaxes in Python: method(member) and member.__method__()?

I created a class and specified the attributes of the member with the following code:
Mexico_66 = Product('Mexico 66 VIN', 99.90, 4)
In the class, I have defined the following magic method:
def __len__(self):
print(self.quantity)
When I try to use this magic method with the following syntax: len(Mexico_66), the code executes but gives off an error at the very end: TypeError: 'NoneType' object cannot be interpreted as an integer
However, when executing the code with the following syntax: Mexico_66.len(), no error appears.
I don't quite understand why the error is caused in the first case and what is the difference between the 1st and 2nd options of executing magic method. I would be grateful if someone could explain it.
The __len__ magic method is supposed to return something, in this case, probably return self.quantity. You are getting the type error because your method implicitly returns None.
The idea of using these magic methods is to define behavior for commonly used functions like len(). If you call it using instance.__len__(), you are not utilizing the magic method, you are simply calling it like a regular instance method, which is why you don't see any error in that use case

Mypy rejects type objects created with `type(name, (bases,), {})`

I'm having some trouble getting mypy to accept type objects. I am
convinced I'm just doing it wrong but my google searches have not led me to any answers so far.
class Good(object):
a = 1
def works(thing: Good):
print(thing.a)
o = Good()
works(o)
Bad = type('Bad', (object, ), dict(a=1))
def fails_mypy(thing: Bad):
print(thing.a)
s = Bad()
fails_mypy(s)
Things constructed like 'Good' are ok, while things constructed like 'Bad' fail mypy checks with:
error: Invalid type "test.Bad"
error: Bad? has no attribute "a"
Based on the Unsupported Python Features section of mypys wiki, runtime creation of classes like this isn't currently supported. It cannot understand what Bad is in your function definition. Using reveal_type(Good) and reveal_type(Bad) when executing mypy should make this clear.
An approach to silence these is by using Any. Either using Python 3.6 variable annotation syntax:
Bad: Any = type('Bad', (), {'a':1})
or, with Python < 3.6:
Bad = type('Bad', (), {'a':1}) # type: Any
(in both cases Any should first be imported from typing)
of course, this basically means that your function now accepts anything. A price to pay, but that's what you get with dynamic languages; since Bar is defined at runtime, it can theoretically be anything :-)

Use isinstance with an undefined class

Assume that class MyClass is sometimes, but not always, defined. I have a function foo(a=None) in which argument a can be None, a string, or an object of MyClass.
My question is: If MyClass is not defined in my Python session, how can I check the type of argument a in a fashion similar to isinstance without getting a NameError?
Note on duck-typing: I am deliberately limiting the function.
I'm using Python 2.6.x and Updating is not an option. A forward-compatible solution (especially for 2.7.x) is highly appreciated.
I would suggest a different approach: polyfill the class so all code that wants to refer to it can simply do so:
try:
from foo import Bar # load the native class
except ImportError:
class Bar:
pass # implement necessary parts here
You can put this into your own module and then from mymodule import Bar everywhere it's needed. That allows all your code to use Bar regardless of whether it's defined natively or not.
Even if redefining the class isn't your preferred way to handle this, handling the ImportError is still the way to handle this situation, since you will have to import the class either way and that's where the error will occur. Instead of defining the class, you may instead want to set a class_exists = False flag or something.
If MyClass isn't defined then you have no way to reference its type.
Therefore you can have no way to verify that type(a) has the correct value.
I workarounded the problem by overriding a method in MyClass and doing nothing in it (pass). After that I no longer needed to check its type.
Different workarounds may exist for different cases. Catching the NameError could be another one.
t = 'asdfas'
print(isinstance(t, str))
try:
print(isinstance(t, MyClass))
except NameError:
print(False)
Seems to me, that such a construct may appear in future python. Like typed python, which is quite new. And in typed python we have a possibility to use future types, in apos.

What misspellings / typos are supported in Python?

What misspellings / typos are supported in Python?
Not alternate spellings such as is_dir vs isdir, nor color vs colour but actual wrongly spelt aliases, such as proprety for property (which isn't supported).
As of Python 3.5 beta 3 the unittest.mock object now supports assret standing in for assert -- note that this is not the keyword assert, but any attribute of a mock object that matches the regular expression assert.* or assret.*.
Some explanation:
When a mock object is created the default for any attribute access is to return a new Mock, except in one case: if the attribute is one of assert_called_with, assert_called_once_with, assert_any_call, assert_has_calls, and assert_not_called, in which case some code is actually run.
The problem is if one forgets the exact name and uses, for example, assert_called, then instead of code running to check that the mock was called, a new mock is returned instead and the test one wrote passes instead of actually doing the test and possibly failing.
To combat this problem Mock now raises an AttributeError if any access is made to an attribute that starts with assert.
Besides assert, Mock will also raise an AttributeError if any access is made to an attribute that starts with assret.
If one does not want the extra protection (for assert and assret) one can use unsafe=True when creating the Mock.

Categories