Module pedantic.tests.tests_pedantic_class

Expand source code
import sys
import unittest
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import IntEnum
from typing import Any, Optional, Callable, Union, Dict, List

from pedantic.env_var_logic import disable_pedantic, enable_pedantic
from pedantic import overrides
from pedantic.decorators.class_decorators import pedantic_class
from pedantic.exceptions import PedanticOverrideException, PedanticTypeCheckException, \
    PedanticCallWithArgsException


class TestPedanticClass(unittest.TestCase):
    def tearDown(self) -> None:
        enable_pedantic()

    def test_constructor(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

        MyClass(a=42)

    def test_constructor_with_list(self):
        class Foo(IntEnum):
            A = 1
            B = 2

        @pedantic_class
        class MyClass:
            def __init__(self, b: int, a: List[Foo]) -> None:
                self.a = a
                self.b = b

        MyClass(b=42, a=[Foo.A, Foo.B])

    def test_constructor_param_without_type_hint(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a) -> None:
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_without_return_type(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int):
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_wrong_return_type(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> int:
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_must_be_called_with_kwargs(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

    def test_multiple_methods(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

            def calc(self, b: int) -> int:
                return self.a - b

            def print(self, s: str) -> None:
                print(f'{self.a} and {s}')

        m = MyClass(a=5)
        m.calc(b=42)
        m.print(s='Hi')
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc(45)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(b=45.0)
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.print('Hi')

    def test_multiple_methods_with_missing_and_wrong_type_hints(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

            def calc(self, b: int) -> float:
                return self.a - b

            def dream(self, b) -> int:
                return self.a * b

            def print(self, s: str):
                print(f'{self.a} and {s}')

        m = MyClass(a=5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(b=42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.print(s='Hi')
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.dream(b=2)

    def test_type_annotation_string(self):
        @pedantic_class
        class MyClass:
            def __init__(self, s: str) -> None:
                self.s = s

            @staticmethod
            def generator() -> 'MyClass':
                return MyClass(s='generated')

        MyClass.generator()

    def test_typo_in_type_annotation_string(self):
        @pedantic_class
        class MyClass:
            def __init__(self, s: str) -> None:
                self.s = s

            @staticmethod
            def generator() -> 'MyClas':
                return MyClass(s='generated')

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.generator()

    def test_overriding_contains(self):
        @pedantic_class
        class MyClass(list):
            def __contains__(self, item: int) -> bool:
                return True

        m = MyClass()
        print(42 in m)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print('something' in m)

    def test_type_annotation_string_typo(self):
        @pedantic_class
        class MyClass:
            def compare(self, other: 'MyClas') -> bool:
                return self == other

            def fixed_compare(self, other: 'MyClass') -> bool:
                return self == other

        m = MyClass()
        m.fixed_compare(other=m)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.compare(other=m)

    def test_docstring_not_required(self):
        @pedantic_class
        class Foo:
            def __init__(self, a: int) -> None:
                self.a = a

            def bunk(self) -> int:
                '''
                Function with correct docstring. Yes, single-quoted docstrings are allowed too.
                Returns:
                    int: 42
                '''
                return self.a

        foo = Foo(a=10)
        foo.bunk()

    def test_overrides(self):
        @pedantic_class
        class Abstract:
            def func(self, b: str) -> str:
                pass

            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return 42

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()

    def test_overrides_abc(self):
        @pedantic_class
        class Abstract(ABC):
            @abstractmethod
            def func(self, b: str) -> str:
                pass

            @abstractmethod
            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return 42

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()

    def test_overrides_with_type_errors_and_call_by_args3(self):
        @pedantic_class
        class Abstract:
            def func(self, b: str) -> str:
                pass

            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return self.a

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            f.func('Hi')
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo(a=3.1415)
        f.a = 3.145
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            f.bunk()

    def test_overrides_goes_wrong(self):
        @pedantic_class
        class Parent:
            def func(self, b: str) -> str:
                return b + b + b

            def bunk(self) -> int:
                return 42

        with self.assertRaises(expected_exception=PedanticOverrideException):
            @pedantic_class
            class Foo(Parent):
                def __init__(self, a: int) -> None:
                    self.a = a

                @overrides(Parent)
                def funcy(self, b: str) -> str:
                    return b

                @overrides(Parent)
                def bunk(self) -> int:
                    return self.a

            f = Foo(a=40002)
            f.func(b='Hi')
            f.bunk()

        p = Parent()
        p.func(b='Hi')
        p.bunk()

    def test_static_method_with_sloppy_type_annotation(self):
        @pedantic_class
        class MyStaticClass:
            @staticmethod
            def double_func(a: int) -> int:
                x, y = MyStaticClass.static_bar()
                return x

            @staticmethod
            def static_bar() -> (int, int):  # this is wrong. Correct would be Tuple[int, int]
                return 0, 1

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(MyStaticClass.double_func(a=0))

    def test_property(self):
        @pedantic_class
        class MyClass(object):
            def __init__(self, some_arg: Any) -> None:
                self._some_attribute = some_arg

            @property
            def some_attribute(self) -> int:
                return self._some_attribute

            @some_attribute.setter
            def some_attribute(self, value: str) -> None:
                self._some_attribute = value

            def calc(self, value: float) -> float:
                return 2 * value

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

        m = MyClass(some_arg=42)
        self.assertEqual(m.some_attribute, 42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.some_attribute = 100
        self.assertEqual(m.some_attribute, 42)
        m.some_attribute = '100'
        self.assertEqual(m._some_attribute, '100')
        m.calc(value=42.0)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(m.some_attribute)

    def test_property_getter_and_setter_misses_type_hints(self):
        @pedantic_class
        class MyClass(object):
            def __init__(self, some_arg: int) -> None:
                self._some_attribute = some_arg

            @property
            def some_attribute(self):
                return self._some_attribute

            @some_attribute.setter
            def some_attribute(self, value: int):
                self._some_attribute = value

            def calc(self, value: float) -> float:
                return 2 * value

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

        m = MyClass(some_arg=42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.some_attribute = 100

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(m.some_attribute)
        m.calc(value=42.0)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(value=42)

    def test_default_constructor(self):
        @pedantic_class
        class MyClass:
            def fun(self) -> int:
                return 42
        m = MyClass()
        m.fun()

    def test_optional_callable(self):
        @pedantic_class
        class SemanticSimilarity:
            def __init__(self, post_processing: bool = True, val: Optional[Callable[[float], float]] = None) -> None:
                if post_processing is None:
                    self.post_processing = val
                else:
                    self.post_processing = lambda x: x

        SemanticSimilarity()

    def test_optional_lambda(self):
        @pedantic_class
        class SemanticSimilarity:
            def __init__(self, val: Callable[[float], float] = lambda x: x) -> None:
                self.post_processing = val

        SemanticSimilarity()

    def test_class_method_type_annotation_missing(self):
        @pedantic_class
        class MyClass:
            @classmethod
            def do(cls):
                print('i did something')

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.do()

    def test_class_method_type_annotation(self):
        @pedantic_class
        class MyClass:
            @classmethod
            def do(cls) -> None:
                print('i did something')

            @classmethod
            def calc(cls, x: Union[int, float]) -> int:
                return x * x

        MyClass.do()
        MyClass.calc(x=5)
        m = MyClass()
        m.do()

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc(5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc(x=5.1)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc('hi')

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc(5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(x=5.1)
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc('hi')

    def test_dataclass_inside(self):
        """Pedantic cannot be used on dataclasses."""

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            @pedantic_class
            @dataclass
            class MyClass:
                name: str
                unit_price: float
                quantity_on_hand: int = 0

    def test_dataclass_outside(self):
        """Pedantic cannot check the constructor of dataclasses"""

        @dataclass
        @pedantic_class
        class MyClass:
            name: str
            unit_price: float
            quantity_on_hand: int = 0

            def total_cost(self) -> int:
                return self.unit_price * self.quantity_on_hand

        MyClass(name='name', unit_price=5.1)
        a = MyClass(name='name', unit_price=5.0, quantity_on_hand=42)

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            a.total_cost()

    def test_class_decorator_static_class_method(self):
        @pedantic_class
        class Foo:
            @staticmethod
            def staticmethod() -> int:
                return 'foo'

            @classmethod
            def classmethod(cls) -> int:
                return 'foo'

            def method(self) -> int:
                return 'foo'

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo.staticmethod()
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo.classmethod()
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo().method()

    def test_pedantic_class_disable_pedantic(self):
        disable_pedantic()

        @pedantic_class
        class MyClass:
            def __init__(self, pw, **kwargs):
                self._validate_str_len(new_values=kwargs)

            @staticmethod
            def _validate_str_len(new_values: Dict[str, Any]) -> None:
                return 42

            def method(pw, **kwargs):
                MyClass._validate_str_len(new_values=kwargs)

        MyClass._validate_str_len(None)
        MyClass._validate_str_len(new_values={1: 1, 2: 2})
        MyClass(name='hi', age=12, pw='123')

    def test_disable_pedantic_2(self):
        """ https://github.com/LostInDarkMath/pedantic-python-decorators/issues/37 """

        disable_pedantic()

        @pedantic_class
        class Foo:
            def __init__(self) -> None:
                self._value = 42

            def do(self) -> None:
                print(self.bar(value=self._value))

            @staticmethod
            def bar(value: int) -> int:
                return value + 75

        f = Foo()
        f.do()

Classes

class TestPedanticClass (methodName='runTest')

A class whose instances are single test cases.

By default, the test code itself should be placed in a method named 'runTest'.

If the fixture may be used for many test cases, create as many test methods as are needed. When instantiating such a TestCase subclass, specify in the constructor arguments the name of the test method that the instance is to execute.

Test authors should subclass TestCase for their own tests. Construction and deconstruction of the test's environment ('fixture') can be implemented by overriding the 'setUp' and 'tearDown' methods respectively.

If it is necessary to override the init method, the base class init method must always be called. It is important that subclasses should not change the signature of their init method, since instances of the classes are instantiated automatically by parts of the framework in order to be run.

When subclassing TestCase, you can set these attributes: * failureException: determines which exception will be raised when the instance's assertion methods fail; test methods raising this exception will be deemed to have 'failed' rather than 'errored'. * longMessage: determines whether long messages (including repr of objects used in assert methods) will be printed on failure in addition to any explicit message passed. * maxDiff: sets the maximum length of a diff in failure messages by assert methods using difflib. It is looked up as an instance attribute so can be configured by individual tests if required.

Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name.

Expand source code
class TestPedanticClass(unittest.TestCase):
    def tearDown(self) -> None:
        enable_pedantic()

    def test_constructor(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

        MyClass(a=42)

    def test_constructor_with_list(self):
        class Foo(IntEnum):
            A = 1
            B = 2

        @pedantic_class
        class MyClass:
            def __init__(self, b: int, a: List[Foo]) -> None:
                self.a = a
                self.b = b

        MyClass(b=42, a=[Foo.A, Foo.B])

    def test_constructor_param_without_type_hint(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a) -> None:
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_without_return_type(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int):
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_wrong_return_type(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> int:
                self.a = a

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass(a=42)

    def test_constructor_must_be_called_with_kwargs(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

    def test_multiple_methods(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

            def calc(self, b: int) -> int:
                return self.a - b

            def print(self, s: str) -> None:
                print(f'{self.a} and {s}')

        m = MyClass(a=5)
        m.calc(b=42)
        m.print(s='Hi')
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc(45)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(b=45.0)
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.print('Hi')

    def test_multiple_methods_with_missing_and_wrong_type_hints(self):
        @pedantic_class
        class MyClass:
            def __init__(self, a: int) -> None:
                self.a = a

            def calc(self, b: int) -> float:
                return self.a - b

            def dream(self, b) -> int:
                return self.a * b

            def print(self, s: str):
                print(f'{self.a} and {s}')

        m = MyClass(a=5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(b=42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.print(s='Hi')
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.dream(b=2)

    def test_type_annotation_string(self):
        @pedantic_class
        class MyClass:
            def __init__(self, s: str) -> None:
                self.s = s

            @staticmethod
            def generator() -> 'MyClass':
                return MyClass(s='generated')

        MyClass.generator()

    def test_typo_in_type_annotation_string(self):
        @pedantic_class
        class MyClass:
            def __init__(self, s: str) -> None:
                self.s = s

            @staticmethod
            def generator() -> 'MyClas':
                return MyClass(s='generated')

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.generator()

    def test_overriding_contains(self):
        @pedantic_class
        class MyClass(list):
            def __contains__(self, item: int) -> bool:
                return True

        m = MyClass()
        print(42 in m)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print('something' in m)

    def test_type_annotation_string_typo(self):
        @pedantic_class
        class MyClass:
            def compare(self, other: 'MyClas') -> bool:
                return self == other

            def fixed_compare(self, other: 'MyClass') -> bool:
                return self == other

        m = MyClass()
        m.fixed_compare(other=m)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.compare(other=m)

    def test_docstring_not_required(self):
        @pedantic_class
        class Foo:
            def __init__(self, a: int) -> None:
                self.a = a

            def bunk(self) -> int:
                '''
                Function with correct docstring. Yes, single-quoted docstrings are allowed too.
                Returns:
                    int: 42
                '''
                return self.a

        foo = Foo(a=10)
        foo.bunk()

    def test_overrides(self):
        @pedantic_class
        class Abstract:
            def func(self, b: str) -> str:
                pass

            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return 42

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()

    def test_overrides_abc(self):
        @pedantic_class
        class Abstract(ABC):
            @abstractmethod
            def func(self, b: str) -> str:
                pass

            @abstractmethod
            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return 42

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()

    def test_overrides_with_type_errors_and_call_by_args3(self):
        @pedantic_class
        class Abstract:
            def func(self, b: str) -> str:
                pass

            def bunk(self) -> int:
                pass

        @pedantic_class
        class Foo(Abstract):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Abstract)
            def func(self, b: str) -> str:
                return b

            @overrides(Abstract)
            def bunk(self) -> int:
                return self.a

        f = Foo(a=42)
        f.func(b='Hi')
        f.bunk()
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            f.func('Hi')
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo(a=3.1415)
        f.a = 3.145
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            f.bunk()

    def test_overrides_goes_wrong(self):
        @pedantic_class
        class Parent:
            def func(self, b: str) -> str:
                return b + b + b

            def bunk(self) -> int:
                return 42

        with self.assertRaises(expected_exception=PedanticOverrideException):
            @pedantic_class
            class Foo(Parent):
                def __init__(self, a: int) -> None:
                    self.a = a

                @overrides(Parent)
                def funcy(self, b: str) -> str:
                    return b

                @overrides(Parent)
                def bunk(self) -> int:
                    return self.a

            f = Foo(a=40002)
            f.func(b='Hi')
            f.bunk()

        p = Parent()
        p.func(b='Hi')
        p.bunk()

    def test_static_method_with_sloppy_type_annotation(self):
        @pedantic_class
        class MyStaticClass:
            @staticmethod
            def double_func(a: int) -> int:
                x, y = MyStaticClass.static_bar()
                return x

            @staticmethod
            def static_bar() -> (int, int):  # this is wrong. Correct would be Tuple[int, int]
                return 0, 1

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(MyStaticClass.double_func(a=0))

    def test_property(self):
        @pedantic_class
        class MyClass(object):
            def __init__(self, some_arg: Any) -> None:
                self._some_attribute = some_arg

            @property
            def some_attribute(self) -> int:
                return self._some_attribute

            @some_attribute.setter
            def some_attribute(self, value: str) -> None:
                self._some_attribute = value

            def calc(self, value: float) -> float:
                return 2 * value

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

        m = MyClass(some_arg=42)
        self.assertEqual(m.some_attribute, 42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.some_attribute = 100
        self.assertEqual(m.some_attribute, 42)
        m.some_attribute = '100'
        self.assertEqual(m._some_attribute, '100')
        m.calc(value=42.0)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(m.some_attribute)

    def test_property_getter_and_setter_misses_type_hints(self):
        @pedantic_class
        class MyClass(object):
            def __init__(self, some_arg: int) -> None:
                self._some_attribute = some_arg

            @property
            def some_attribute(self):
                return self._some_attribute

            @some_attribute.setter
            def some_attribute(self, value: int):
                self._some_attribute = value

            def calc(self, value: float) -> float:
                return 2 * value

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            MyClass(42)

        m = MyClass(some_arg=42)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.some_attribute = 100

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            print(m.some_attribute)
        m.calc(value=42.0)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(value=42)

    def test_default_constructor(self):
        @pedantic_class
        class MyClass:
            def fun(self) -> int:
                return 42
        m = MyClass()
        m.fun()

    def test_optional_callable(self):
        @pedantic_class
        class SemanticSimilarity:
            def __init__(self, post_processing: bool = True, val: Optional[Callable[[float], float]] = None) -> None:
                if post_processing is None:
                    self.post_processing = val
                else:
                    self.post_processing = lambda x: x

        SemanticSimilarity()

    def test_optional_lambda(self):
        @pedantic_class
        class SemanticSimilarity:
            def __init__(self, val: Callable[[float], float] = lambda x: x) -> None:
                self.post_processing = val

        SemanticSimilarity()

    def test_class_method_type_annotation_missing(self):
        @pedantic_class
        class MyClass:
            @classmethod
            def do(cls):
                print('i did something')

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.do()

    def test_class_method_type_annotation(self):
        @pedantic_class
        class MyClass:
            @classmethod
            def do(cls) -> None:
                print('i did something')

            @classmethod
            def calc(cls, x: Union[int, float]) -> int:
                return x * x

        MyClass.do()
        MyClass.calc(x=5)
        m = MyClass()
        m.do()

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc(5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc(x=5.1)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            MyClass.calc('hi')

        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc(5)
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            m.calc(x=5.1)
        with self.assertRaises(expected_exception=PedanticCallWithArgsException):
            m.calc('hi')

    def test_dataclass_inside(self):
        """Pedantic cannot be used on dataclasses."""

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            @pedantic_class
            @dataclass
            class MyClass:
                name: str
                unit_price: float
                quantity_on_hand: int = 0

    def test_dataclass_outside(self):
        """Pedantic cannot check the constructor of dataclasses"""

        @dataclass
        @pedantic_class
        class MyClass:
            name: str
            unit_price: float
            quantity_on_hand: int = 0

            def total_cost(self) -> int:
                return self.unit_price * self.quantity_on_hand

        MyClass(name='name', unit_price=5.1)
        a = MyClass(name='name', unit_price=5.0, quantity_on_hand=42)

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            a.total_cost()

    def test_class_decorator_static_class_method(self):
        @pedantic_class
        class Foo:
            @staticmethod
            def staticmethod() -> int:
                return 'foo'

            @classmethod
            def classmethod(cls) -> int:
                return 'foo'

            def method(self) -> int:
                return 'foo'

        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo.staticmethod()
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo.classmethod()
        with self.assertRaises(expected_exception=PedanticTypeCheckException):
            Foo().method()

    def test_pedantic_class_disable_pedantic(self):
        disable_pedantic()

        @pedantic_class
        class MyClass:
            def __init__(self, pw, **kwargs):
                self._validate_str_len(new_values=kwargs)

            @staticmethod
            def _validate_str_len(new_values: Dict[str, Any]) -> None:
                return 42

            def method(pw, **kwargs):
                MyClass._validate_str_len(new_values=kwargs)

        MyClass._validate_str_len(None)
        MyClass._validate_str_len(new_values={1: 1, 2: 2})
        MyClass(name='hi', age=12, pw='123')

    def test_disable_pedantic_2(self):
        """ https://github.com/LostInDarkMath/pedantic-python-decorators/issues/37 """

        disable_pedantic()

        @pedantic_class
        class Foo:
            def __init__(self) -> None:
                self._value = 42

            def do(self) -> None:
                print(self.bar(value=self._value))

            @staticmethod
            def bar(value: int) -> int:
                return value + 75

        f = Foo()
        f.do()

Ancestors

  • unittest.case.TestCase

Methods

def tearDown(self) ‑> None

Hook method for deconstructing the test fixture after testing it.

Expand source code
def tearDown(self) -> None:
    enable_pedantic()
def test_class_decorator_static_class_method(self)
Expand source code
def test_class_decorator_static_class_method(self):
    @pedantic_class
    class Foo:
        @staticmethod
        def staticmethod() -> int:
            return 'foo'

        @classmethod
        def classmethod(cls) -> int:
            return 'foo'

        def method(self) -> int:
            return 'foo'

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        Foo.staticmethod()
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        Foo.classmethod()
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        Foo().method()
def test_class_method_type_annotation(self)
Expand source code
def test_class_method_type_annotation(self):
    @pedantic_class
    class MyClass:
        @classmethod
        def do(cls) -> None:
            print('i did something')

        @classmethod
        def calc(cls, x: Union[int, float]) -> int:
            return x * x

    MyClass.do()
    MyClass.calc(x=5)
    m = MyClass()
    m.do()

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass.calc(5)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass.calc(x=5.1)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass.calc('hi')

    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        m.calc(5)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.calc(x=5.1)
    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        m.calc('hi')
def test_class_method_type_annotation_missing(self)
Expand source code
def test_class_method_type_annotation_missing(self):
    @pedantic_class
    class MyClass:
        @classmethod
        def do(cls):
            print('i did something')

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass.do()
def test_constructor(self)
Expand source code
def test_constructor(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int) -> None:
            self.a = a

    MyClass(a=42)
def test_constructor_must_be_called_with_kwargs(self)
Expand source code
def test_constructor_must_be_called_with_kwargs(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int) -> None:
            self.a = a

    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        MyClass(42)
def test_constructor_param_without_type_hint(self)
Expand source code
def test_constructor_param_without_type_hint(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a) -> None:
            self.a = a

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass(a=42)
def test_constructor_with_list(self)
Expand source code
def test_constructor_with_list(self):
    class Foo(IntEnum):
        A = 1
        B = 2

    @pedantic_class
    class MyClass:
        def __init__(self, b: int, a: List[Foo]) -> None:
            self.a = a
            self.b = b

    MyClass(b=42, a=[Foo.A, Foo.B])
def test_constructor_without_return_type(self)
Expand source code
def test_constructor_without_return_type(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int):
            self.a = a

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass(a=42)
def test_constructor_wrong_return_type(self)
Expand source code
def test_constructor_wrong_return_type(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int) -> int:
            self.a = a

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass(a=42)
def test_dataclass_inside(self)

Pedantic cannot be used on dataclasses.

Expand source code
def test_dataclass_inside(self):
    """Pedantic cannot be used on dataclasses."""

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        @pedantic_class
        @dataclass
        class MyClass:
            name: str
            unit_price: float
            quantity_on_hand: int = 0
def test_dataclass_outside(self)

Pedantic cannot check the constructor of dataclasses

Expand source code
def test_dataclass_outside(self):
    """Pedantic cannot check the constructor of dataclasses"""

    @dataclass
    @pedantic_class
    class MyClass:
        name: str
        unit_price: float
        quantity_on_hand: int = 0

        def total_cost(self) -> int:
            return self.unit_price * self.quantity_on_hand

    MyClass(name='name', unit_price=5.1)
    a = MyClass(name='name', unit_price=5.0, quantity_on_hand=42)

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        a.total_cost()
def test_default_constructor(self)
Expand source code
def test_default_constructor(self):
    @pedantic_class
    class MyClass:
        def fun(self) -> int:
            return 42
    m = MyClass()
    m.fun()
def test_disable_pedantic_2(self)
Expand source code
def test_disable_pedantic_2(self):
    """ https://github.com/LostInDarkMath/pedantic-python-decorators/issues/37 """

    disable_pedantic()

    @pedantic_class
    class Foo:
        def __init__(self) -> None:
            self._value = 42

        def do(self) -> None:
            print(self.bar(value=self._value))

        @staticmethod
        def bar(value: int) -> int:
            return value + 75

    f = Foo()
    f.do()
def test_docstring_not_required(self)
Expand source code
def test_docstring_not_required(self):
    @pedantic_class
    class Foo:
        def __init__(self, a: int) -> None:
            self.a = a

        def bunk(self) -> int:
            '''
            Function with correct docstring. Yes, single-quoted docstrings are allowed too.
            Returns:
                int: 42
            '''
            return self.a

    foo = Foo(a=10)
    foo.bunk()
def test_multiple_methods(self)
Expand source code
def test_multiple_methods(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int) -> None:
            self.a = a

        def calc(self, b: int) -> int:
            return self.a - b

        def print(self, s: str) -> None:
            print(f'{self.a} and {s}')

    m = MyClass(a=5)
    m.calc(b=42)
    m.print(s='Hi')
    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        m.calc(45)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.calc(b=45.0)
    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        m.print('Hi')
def test_multiple_methods_with_missing_and_wrong_type_hints(self)
Expand source code
def test_multiple_methods_with_missing_and_wrong_type_hints(self):
    @pedantic_class
    class MyClass:
        def __init__(self, a: int) -> None:
            self.a = a

        def calc(self, b: int) -> float:
            return self.a - b

        def dream(self, b) -> int:
            return self.a * b

        def print(self, s: str):
            print(f'{self.a} and {s}')

    m = MyClass(a=5)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.calc(b=42)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.print(s='Hi')
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.dream(b=2)
def test_optional_callable(self)
Expand source code
def test_optional_callable(self):
    @pedantic_class
    class SemanticSimilarity:
        def __init__(self, post_processing: bool = True, val: Optional[Callable[[float], float]] = None) -> None:
            if post_processing is None:
                self.post_processing = val
            else:
                self.post_processing = lambda x: x

    SemanticSimilarity()
def test_optional_lambda(self)
Expand source code
def test_optional_lambda(self):
    @pedantic_class
    class SemanticSimilarity:
        def __init__(self, val: Callable[[float], float] = lambda x: x) -> None:
            self.post_processing = val

    SemanticSimilarity()
def test_overrides(self)
Expand source code
def test_overrides(self):
    @pedantic_class
    class Abstract:
        def func(self, b: str) -> str:
            pass

        def bunk(self) -> int:
            pass

    @pedantic_class
    class Foo(Abstract):
        def __init__(self, a: int) -> None:
            self.a = a

        @overrides(Abstract)
        def func(self, b: str) -> str:
            return b

        @overrides(Abstract)
        def bunk(self) -> int:
            return 42

    f = Foo(a=42)
    f.func(b='Hi')
    f.bunk()
def test_overrides_abc(self)
Expand source code
def test_overrides_abc(self):
    @pedantic_class
    class Abstract(ABC):
        @abstractmethod
        def func(self, b: str) -> str:
            pass

        @abstractmethod
        def bunk(self) -> int:
            pass

    @pedantic_class
    class Foo(Abstract):
        def __init__(self, a: int) -> None:
            self.a = a

        @overrides(Abstract)
        def func(self, b: str) -> str:
            return b

        @overrides(Abstract)
        def bunk(self) -> int:
            return 42

    f = Foo(a=42)
    f.func(b='Hi')
    f.bunk()
def test_overrides_goes_wrong(self)
Expand source code
def test_overrides_goes_wrong(self):
    @pedantic_class
    class Parent:
        def func(self, b: str) -> str:
            return b + b + b

        def bunk(self) -> int:
            return 42

    with self.assertRaises(expected_exception=PedanticOverrideException):
        @pedantic_class
        class Foo(Parent):
            def __init__(self, a: int) -> None:
                self.a = a

            @overrides(Parent)
            def funcy(self, b: str) -> str:
                return b

            @overrides(Parent)
            def bunk(self) -> int:
                return self.a

        f = Foo(a=40002)
        f.func(b='Hi')
        f.bunk()

    p = Parent()
    p.func(b='Hi')
    p.bunk()
def test_overrides_with_type_errors_and_call_by_args3(self)
Expand source code
def test_overrides_with_type_errors_and_call_by_args3(self):
    @pedantic_class
    class Abstract:
        def func(self, b: str) -> str:
            pass

        def bunk(self) -> int:
            pass

    @pedantic_class
    class Foo(Abstract):
        def __init__(self, a: int) -> None:
            self.a = a

        @overrides(Abstract)
        def func(self, b: str) -> str:
            return b

        @overrides(Abstract)
        def bunk(self) -> int:
            return self.a

    f = Foo(a=42)
    f.func(b='Hi')
    f.bunk()
    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        f.func('Hi')
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        Foo(a=3.1415)
    f.a = 3.145
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        f.bunk()
def test_overriding_contains(self)
Expand source code
def test_overriding_contains(self):
    @pedantic_class
    class MyClass(list):
        def __contains__(self, item: int) -> bool:
            return True

    m = MyClass()
    print(42 in m)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        print('something' in m)
def test_pedantic_class_disable_pedantic(self)
Expand source code
def test_pedantic_class_disable_pedantic(self):
    disable_pedantic()

    @pedantic_class
    class MyClass:
        def __init__(self, pw, **kwargs):
            self._validate_str_len(new_values=kwargs)

        @staticmethod
        def _validate_str_len(new_values: Dict[str, Any]) -> None:
            return 42

        def method(pw, **kwargs):
            MyClass._validate_str_len(new_values=kwargs)

    MyClass._validate_str_len(None)
    MyClass._validate_str_len(new_values={1: 1, 2: 2})
    MyClass(name='hi', age=12, pw='123')
def test_property(self)
Expand source code
def test_property(self):
    @pedantic_class
    class MyClass(object):
        def __init__(self, some_arg: Any) -> None:
            self._some_attribute = some_arg

        @property
        def some_attribute(self) -> int:
            return self._some_attribute

        @some_attribute.setter
        def some_attribute(self, value: str) -> None:
            self._some_attribute = value

        def calc(self, value: float) -> float:
            return 2 * value

    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        MyClass(42)

    m = MyClass(some_arg=42)
    self.assertEqual(m.some_attribute, 42)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.some_attribute = 100
    self.assertEqual(m.some_attribute, 42)
    m.some_attribute = '100'
    self.assertEqual(m._some_attribute, '100')
    m.calc(value=42.0)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        print(m.some_attribute)
def test_property_getter_and_setter_misses_type_hints(self)
Expand source code
def test_property_getter_and_setter_misses_type_hints(self):
    @pedantic_class
    class MyClass(object):
        def __init__(self, some_arg: int) -> None:
            self._some_attribute = some_arg

        @property
        def some_attribute(self):
            return self._some_attribute

        @some_attribute.setter
        def some_attribute(self, value: int):
            self._some_attribute = value

        def calc(self, value: float) -> float:
            return 2 * value

    with self.assertRaises(expected_exception=PedanticCallWithArgsException):
        MyClass(42)

    m = MyClass(some_arg=42)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.some_attribute = 100

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        print(m.some_attribute)
    m.calc(value=42.0)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.calc(value=42)
def test_static_method_with_sloppy_type_annotation(self)
Expand source code
def test_static_method_with_sloppy_type_annotation(self):
    @pedantic_class
    class MyStaticClass:
        @staticmethod
        def double_func(a: int) -> int:
            x, y = MyStaticClass.static_bar()
            return x

        @staticmethod
        def static_bar() -> (int, int):  # this is wrong. Correct would be Tuple[int, int]
            return 0, 1

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        print(MyStaticClass.double_func(a=0))
def test_type_annotation_string(self)
Expand source code
def test_type_annotation_string(self):
    @pedantic_class
    class MyClass:
        def __init__(self, s: str) -> None:
            self.s = s

        @staticmethod
        def generator() -> 'MyClass':
            return MyClass(s='generated')

    MyClass.generator()
def test_type_annotation_string_typo(self)
Expand source code
def test_type_annotation_string_typo(self):
    @pedantic_class
    class MyClass:
        def compare(self, other: 'MyClas') -> bool:
            return self == other

        def fixed_compare(self, other: 'MyClass') -> bool:
            return self == other

    m = MyClass()
    m.fixed_compare(other=m)
    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        m.compare(other=m)
def test_typo_in_type_annotation_string(self)
Expand source code
def test_typo_in_type_annotation_string(self):
    @pedantic_class
    class MyClass:
        def __init__(self, s: str) -> None:
            self.s = s

        @staticmethod
        def generator() -> 'MyClas':
            return MyClass(s='generated')

    with self.assertRaises(expected_exception=PedanticTypeCheckException):
        MyClass.generator()