Skip to content

Commit 05ab861

Browse files
authored
AssertionLib 2.3.0 (#19)
AssertionLib 2.3.0 ------------------ * Added the ``AssertionManager.xor()``, ``AssertionManager.isdisjoint()`` and ``AssertionManager.length_hint()`` methods. * Annotate most ``AssertionManager`` methods using Protocols. * Moved Protocols to their own separate stub module. * Cleaned up the ``_MetaAM`` metaclass. * Reworked some of the internals of ``AssertionManager``. * Added tests using [pydocstyle ](https://github.com/henry0312/pytest-pydocstyle).
1 parent 66d7e71 commit 05ab861

29 files changed

+1349
-986
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
python-version: ${{ matrix.python-version }}
3030

3131
- name: Install dependencies
32-
run: pip install -e .[test]
32+
run: pip install -e .[test]; pip install git+https://github.com/numpy/numpy-stubs@master
3333

3434
- name: Test with pytest
35-
run: pytest assertionlib tests docs
35+
run: pytest assertionlib tests

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ install:
3838

3939
# Install the tests
4040
- pip install .[test] --upgrade
41+
- pip install git+https://github.com/numpy/numpy-stubs@master
4142

4243
script:
4344
# Run the unitary tests excluding the expensive computations
44-
- pytest assertionlib tests docs
45+
- pytest assertionlib tests
4546
- coverage xml && coverage report -m
4647

4748
branches:

CHANGELOG.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file.
66
This project adheres to `Semantic Versioning <http://semver.org/>`_.
77

88

9+
2.3.0
10+
*****
11+
* Added the ``AssertionManager.xor()``, ``AssertionManager.isdisjoint()`` and ``AssertionManager.length_hint()`` methods.
12+
* Annotate most ``AssertionManager`` methods using Protocols.
13+
* Moved Protocols to their own separate stub module.
14+
* Cleaned up the ``_MetaAM`` metaclass.
15+
* Reworked some of the internals of ``AssertionManager``.
16+
* Added tests using `pydocstyle <https://github.com/henry0312/pytest-pydocstyle>`_.
17+
18+
919
2.2.3
1020
*****
1121
* Windows bug fix: Check for the presence of the ``AssertionManager._isdir()``

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
##################
21-
AssertionLib 2.2.3
21+
AssertionLib 2.3.0
2222
##################
2323

2424
A package for performing assertions and providing informative exception messages.

assertionlib/README.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ A module for holding the ``assertionlib.NDRepr()`` class,
2525
a subclass of the builtin ``reprlib.Repr()`` class.
2626

2727

28-
``assertionlib.signature_utils``
29-
--------------------------------
30-
Various functions for manipulating the signatures of callables.
31-
32-
3328
.. _`Python 3.7`: https://www.python.org/dev/peps/pep-0557/
3429

3530
.. |dataclasses| replace:: :mod:`dataclasses`
@@ -38,7 +33,6 @@ Various functions for manipulating the signatures of callables.
3833
.. |assertionlib.functions| replace:: :mod:`assertionlib.functions`
3934
.. |assertionlib.manager| replace:: :mod:`assertionlib.manager`
4035
.. |assertionlib.ndrepr| replace:: :mod:`assertionlib.ndrepr`
41-
.. |assertionlib.signature| replace:: :mod:`assertionlib.signature`
4236

4337
.. |assertionlib.AssertionManager| replace:: :class:`assertionlib.AssertionManager<assertionlib.manager.AssertionManager>`
4438
.. |assertionlib.NDRepr| replace:: :class:`assertionlib.NDRepr<assertionlib.ndrepr.NDRepr>`

assertionlib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""AssertionLib."""
2+
13
from .__version__ import __version__
24

35
from .ndrepr import NDRepr, aNDRepr

assertionlib/__version__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
__version__ = '2.2.3'
1+
"""The AssertionLib version."""
2+
3+
__version__ = '2.3.0'
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""A module with various new assertion functions.
2+
3+
Index
4+
-----
5+
.. currentmodule:: assertionlib.assertion_functions
6+
.. autosummary::
7+
len_eq
8+
str_eq
9+
shape_eq
10+
isdisjoint
11+
function_eq
12+
13+
API
14+
---
15+
.. autofunction:: len_eq
16+
.. autofunction:: str_eq
17+
.. autofunction:: shape_eq
18+
.. autofunction:: isdisjoint
19+
.. autofunction:: function_eq
20+
21+
"""
22+
23+
import dis
24+
from types import FunctionType
25+
from itertools import zip_longest
26+
from typing import (
27+
Sized,
28+
Callable,
29+
TypeVar,
30+
Iterable,
31+
Union,
32+
Tuple,
33+
Hashable,
34+
TYPE_CHECKING
35+
)
36+
37+
from .functions import to_positional
38+
39+
if TYPE_CHECKING:
40+
from numpy import ndarray # type: ignore
41+
else:
42+
ndarray = 'numpy.ndarray'
43+
44+
__all__ = ['len_eq', 'str_eq', 'shape_eq', 'isdisjoint', 'function_eq']
45+
46+
T = TypeVar('T')
47+
IT = TypeVar('IT', bound=Union[None, dis.Instruction])
48+
49+
50+
@to_positional
51+
def len_eq(a: Sized, b: int) -> bool:
52+
"""Check if the length of **a** is equivalent to **b**: :code:`len(a) == b`.
53+
54+
Parameters
55+
----------
56+
a : :class:`~collections.abc.Sized`
57+
The object whose size will be evaluated.
58+
59+
b : :class:`int`
60+
The integer that will be matched against the size of **a**.
61+
62+
"""
63+
return len(a) == b
64+
65+
66+
@to_positional
67+
def str_eq(a: T, b: str, *, str_converter: Callable[[T], str] = repr) -> bool:
68+
"""Check if the string-representation of **a** is equivalent to **b**: :code:`repr(a) == b`.
69+
70+
Parameters
71+
----------
72+
a : :class:`T<typing.TypeVar>`
73+
An object whose string represention will be evaluated.
74+
75+
b : :class:`str`
76+
The string that will be matched against the string-output of **a**.
77+
78+
Keyword Arguments
79+
-----------------
80+
str_converter : :data:`Callable[[T], str]<typing.Callable>`
81+
The callable for constructing **a**'s string representation.
82+
Uses :func:`repr` by default.
83+
84+
"""
85+
return str_converter(a) == b
86+
87+
88+
@to_positional
89+
def shape_eq(a: ndarray, b: Union[ndarray, Tuple[int, ...]]) -> bool:
90+
"""Check if the shapes of **a** and **b** are equivalent: :code:`a.shape == getattr(b, 'shape', b)`.
91+
92+
**b** should be either an object with the ``shape`` attribute (*e.g.* a NumPy array)
93+
or a :class:`tuple` representing a valid array shape.
94+
95+
Parameters
96+
----------
97+
a : :class:`numpy.ndarray`
98+
A NumPy array.
99+
100+
b : :class:`numpy.ndarray` or :class:`tuple` [:class:`int`, ...]
101+
A NumPy array or a tuple of integers representing the shape of **a**.
102+
103+
""" # noqa
104+
return a.shape == getattr(b, 'shape', b)
105+
106+
107+
@to_positional
108+
def isdisjoint(a: Iterable[Hashable], b: Iterable[Hashable]) -> bool:
109+
"""Check if **a** has no elements in **b**.
110+
111+
Parameters
112+
----------
113+
a/b : :class:`~collections.abc.Iterable` [:class:`~collections.abc.Hashable`]
114+
Two to-be compared iterables.
115+
Note that both iterables must consist of hashable objects.
116+
117+
See Also
118+
--------
119+
:meth:`set.isdisjoint()<frozenset.isdisjoint>`
120+
Return ``True`` if two sets have a null intersection.
121+
122+
"""
123+
try:
124+
return a.isdisjoint(b) # type: ignore
125+
126+
# **a** does not have the isdisjoint method
127+
except AttributeError:
128+
return set(a).isdisjoint(b)
129+
130+
# **a.isdisjoint** is not a callable or
131+
# **a** and/or **b** do not consist of hashable elements
132+
except TypeError as ex:
133+
if callable(a.isdisjoint): # type: ignore
134+
raise ex
135+
return set(a).isdisjoint(b)
136+
137+
138+
@to_positional
139+
def function_eq(func1: FunctionType, func2: FunctionType) -> bool:
140+
"""Check if two functions are equivalent by checking if their :attr:`__code__` is identical.
141+
142+
**func1** and **func2** should be instances of :data:`~types.FunctionType`
143+
or any other object with access to the :attr:`__code__` attribute.
144+
145+
Parameters
146+
----------
147+
func1/func2 : :data:`~types.FunctionType`
148+
Two functions.
149+
150+
Examples
151+
--------
152+
.. code:: python
153+
154+
>>> from assertionlib.assertion_functions import function_eq
155+
156+
>>> func1 = lambda x: x + 5
157+
>>> func2 = lambda x: x + 5
158+
>>> func3 = lambda x: 5 + x
159+
160+
>>> print(function_eq(func1, func2))
161+
True
162+
163+
>>> print(function_eq(func1, func3))
164+
False
165+
166+
"""
167+
code1 = None
168+
try:
169+
code1 = func1.__code__
170+
code2 = func2.__code__
171+
except AttributeError as ex:
172+
name, obj = ('func1', func1) if code1 is None else ('func2', func2)
173+
raise TypeError(f"{name!r} expected a function or object with the '__code__' attribute; "
174+
f"observed type: {obj.__class__.__name__!r}") from ex
175+
176+
iterator = zip_longest(dis.get_instructions(code1), dis.get_instructions(code2))
177+
tup_iter = ((_sanitize_instruction(i), _sanitize_instruction(j)) for i, j in iterator)
178+
return all([i == j for i, j in tup_iter])
179+
180+
181+
def _sanitize_instruction(instruction: IT) -> IT:
182+
"""Sanitize the supplied instruction by setting :attr:`~dis.Instruction.starts_line` to :data:`None`.""" # noqa
183+
if instruction is None:
184+
return None
185+
return instruction._replace(starts_line=None) # type: ignore

0 commit comments

Comments
 (0)