mock_alchemy.mocking

A module for basic mocking of SQLAlchemy sessions and calls.

class mock_alchemy.mocking.AlchemyMagicMock(*args, **kwargs)[source]

Bases: unittest.mock.MagicMock

Compares SQLAlchemy expressions for simple asserts.

MagicMock for SQLAlchemy which can compare alchemys expressions in assertions.

For example:

>>> from sqlalchemy import or_
>>> from sqlalchemy.sql.expression import column
>>> c = column('column')
>>> s = AlchemyMagicMock()

>>> _ = s.filter(or_(c == 5, c == 10))

>>> _ = s.filter.assert_called_once_with(or_(c == 5, c == 10))
>>> _ = s.filter.assert_any_call(or_(c == 5, c == 10))
>>> _ = s.filter.assert_has_calls([mock.call(or_(c == 5, c == 10))])

>>> s.reset_mock()
>>> _ = s.filter(c == 5)
>>> _ = s.filter.assert_called_once_with(c == 10)
Traceback (most recent call last):
...
AssertionError: expected call not found.
Expected: filter(BinaryExpression(sql='"column" = :column_1',         params={'column_1': 10}))
Actual: filter(BinaryExpression(sql='"column" = :column_1',         params={'column_1': 5}))
__init__(spec: Optional[Any] = ..., side_effect: Optional[Any] = ..., return_value: Any = ..., wraps: Optional[Any] = ..., name: Optional[Any] = ..., spec_set: Optional[Any] = ..., parent: Optional[Any] = ..., _spec_state: Optional[Any] = ..., _new_name: Any = ..., _new_parent: Optional[Any] = ..., **kwargs: Any) None[source]

Creates AlchemyMagicMock that can be used as limited SQLAlchemy session.

_format_mock_call_signature(args, kwargs)[source]

Formats the mock call into a string.

Return type

str

assert_any_call(*args, **kwargs)[source]

Assert for a specific call to have happened.

Return type

None

assert_called_with(*args, **kwargs)[source]

Assert for a specific call to have happened.

Return type

None

assert_has_calls(calls, any_order=False)[source]

Assert for a list of calls to have happened.

Return type

None

class mock_alchemy.mocking.UnifiedAlchemyMagicMock(*args, **kwargs)[source]

Bases: mock_alchemy.mocking.AlchemyMagicMock

A MagicMock that combines SQLALchemy to mock a session.

MagicMock which unifies common SQLALchemy session functions for easier assertions.

boundary

A dict of SQLAlchemy functions or statements that get or retreive data from calls. This dictionary has values that are the callable functions to process the function calls.

Type

Dict[str, Callable]

unify

A dict of SQLAlchemy functions or statements that are to unifying expressions together. This dictionary has values that are the callable functions to process the function calls. Note that across query calls data and, as such, these calls are not unified. Check out the examples for this class for more detail about this limitation.

Type

Dict[str, Optional[mock_alchemy.mocking.UnorderedCall]]

mutate

A set of operations that mutate data. The currently supported operations include .delete(), .add(), and .add_all(). More operations are planned and this is a future area of work.

Type

Set[str]

For example:

>>> from sqlalchemy.sql.expression import column
>>> c = column('column')

>>> s = UnifiedAlchemyMagicMock()
>>> s.query(None).filter(c == 'one').filter(c == 'two').all()
[]
>>> s.query(None).filter(c == 'three').filter(c == 'four').all()
[]
>>> s.filter.call_count
2
>>> s.filter.assert_any_call(c == 'one', c == 'two')
>>> s.filter.assert_any_call(c == 'three', c == 'four')

In addition, mock data be specified to stub real DB interactions. Result-sets are specified per filtering criteria so that unique data can be returned depending on query/filter/options criteria. Data is given as a list of (criteria, result) tuples where criteria is a list of calls. Reason for passing data as a list vs a dict is that calls and SQLAlchemy expressions are not hashable hence cannot be dict keys.

For example:

>>> from sqlalchemy import Column, Integer, String
>>> from sqlalchemy.ext.declarative import declarative_base

>>> Base = declarative_base()

>>> class SomeClass(Base):
...     __tablename__ = 'some_table'
...     pk1 = Column(Integer, primary_key=True)
...     pk2 = Column(Integer, primary_key=True)
...     name =  Column(String(50))
...     def __repr__(self):
...         return str(self.pk1)

>>> s = UnifiedAlchemyMagicMock(data=[
...     (
...         [mock.call.query('foo'),
...          mock.call.filter(c == 'one', c == 'two')],
...         [SomeClass(pk1=1, pk2=1), SomeClass(pk1=2, pk2=2)]
...     ),
...     (
...         [mock.call.query('foo'),
...          mock.call.filter(c == 'one', c == 'two'),
...          mock.call.order_by(c)],
...         [SomeClass(pk1=2, pk2=2), SomeClass(pk1=1, pk2=1)]
...     ),
...     (
...         [mock.call.filter(c == 'three')],
...         [SomeClass(pk1=3, pk2=3)]
...     ),
... ])

# .all()
>>> s.query('foo').filter(c == 'one').filter(c == 'two').all()
[1, 2]
>>> s.query('bar').filter(c == 'one').filter(c == 'two').all()
[]
>>> s.query('foo').filter(c == 'one').filter(c == 'two').order_by(c).all()
[2, 1]
>>> s.query('foo').filter(c == 'one').filter(c == 'three').order_by(c).all()
[]
>>> s.query('foo').filter(c == 'three').all()
[3]
>>> s.query(None).filter(c == 'four').all()
[]

# .iter()
>>> list(s.query('foo').filter(c == 'two').filter(c == 'one'))
[1, 2]

# .count()
>>> s.query('foo').filter(c == 'two').filter(c == 'one').count()
2

# .first()
>>> s.query('foo').filter(c == 'one').filter(c == 'two').first()
1
>>> s.query('bar').filter(c == 'one').filter(c == 'two').first()

# .one()
>>> s.query('foo').filter(c == 'three').one()
3
>>> s.query('bar').filter(c == 'one').filter(c == 'two').one_or_none()

# .get()
>>> s.query('foo').get((1, 1))
1
>>> s.query('foo').get((4, 4))
>>> s.query('foo').filter(c == 'two').filter(c == 'one').get((1, 1))
1
>>> s.query('foo').filter(c == 'three').get((1, 1))
1
>>> s.query('foo').filter(c == 'three').get((4, 4))

# dynamic session
>>> class Model(Base):
...     __tablename__ = 'model_table'
...     pk1 = Column(Integer, primary_key=True)
...     name = Column(String)
...     def __repr__(self):
...         return str(self.pk1)
>>> s = UnifiedAlchemyMagicMock()
>>> s.add(SomeClass(pk1=1, pk2=1))
>>> s.add_all([SomeClass(pk1=2, pk2=2)])
>>> s.add_all([SomeClass(pk1=4, pk2=3)])
>>> s.add_all([Model(pk1=4, name='some_name')])
>>> s.query(SomeClass).all()
[1, 2, 4]
>>> s.query(SomeClass).get((1, 1))
1
>>> s.query(SomeClass).get((2, 2))
2
>>> s.query(SomeClass).get((3, 3))
>>> s.query(SomeClass).filter(c == 'one').all()
[1, 2, 4]
>>> s.query(SomeClass).get((4, 3))
4
>>> s.query(SomeClass).get({"pk2": 3, "pk1": 4})
4
>>> s.query(Model).get(4)
4

# .delete()
>>> s = UnifiedAlchemyMagicMock(data=[
...     (
...         [mock.call.query('foo'),
...          mock.call.filter(c == 'one', c == 'two')],
...         [SomeClass(pk1=1, pk2=1), SomeClass(pk1=2, pk2=2)]
...     ),
...     (
...         [mock.call.query('foo'),
...          mock.call.filter(c == 'one', c == 'two'),
...          mock.call.order_by(c)],
...         [SomeClass(pk1=2, pk2=2), SomeClass(pk1=1, pk2=1)]
...     ),
...     (
...         [mock.call.filter(c == 'three')],
...         [SomeClass(pk1=3, pk2=3)]
...     ),
...     (
...         [mock.call.query('foo'),
...          mock.call.filter(
...             c == 'one',
...             c == 'two',
...             c == 'three',
...         )],
...         [
...             SomeClass(pk1=1, pk2=1),
...             SomeClass(pk1=2, pk2=2),
...             SomeClass(pk1=3, pk2=3),
...         ]
...     ),
... ])

>>> s.query('foo').filter(c == 'three').all()
[3]
>>> s.query('foo').all()
[]
>>> s.query('foo').filter(c == 'three').delete()
1
>>> s.query('foo').filter(c == 'three').all()
[]
>>> s.query('foo').filter(c == 'one').filter(c == 'two').all()
[1, 2]
>>> a = s.query('foo').filter(c == 'one').filter(c == 'two')
>>> a.filter(c == 'three').all()
[1, 2, 3]
>>> s = UnifiedAlchemyMagicMock()
>>> s.add(SomeClass(pk1=1, pk2=1))
>>> s.add_all([SomeClass(pk1=2, pk2=2)])
>>> s.query(SomeClass).all()
[1, 2]
>>> s.query(SomeClass).delete()
2
>>> s.query(SomeClass).all()
[]
>>> s = UnifiedAlchemyMagicMock()
>>> s.add_all([SomeClass(pk1=2, pk2=2)])
>>> s.query(SomeClass).delete()
1
>>> s.query(SomeClass).delete()
0

Also note that only within same query functions are unified. After .all() is called or query is iterated over, future queries are not unified.

__init__(spec: Optional[Any] = ..., side_effect: Optional[Any] = ..., return_value: Any = ..., wraps: Optional[Any] = ..., name: Optional[Any] = ..., spec_set: Optional[Any] = ..., parent: Optional[Any] = ..., _spec_state: Optional[Any] = ..., _new_name: Any = ..., _new_parent: Optional[Any] = ..., **kwargs: Any) None[source]

Creates an UnifiedAlchemyMagicMock to mock a SQLAlchemy session.

_get_data(*args, **kwargs)[source]

Get the data for the SQLAlchemy expression.

Return type

Any

_get_previous_call(name, calls)[source]

Gets the previous call right before the current call.

Return type

Optional[_Call]

_get_previous_calls(calls)[source]

Gets the previous calls on the same line.

Return type

Iterator

_mutate_data(*args, **kwargs)[source]

Alter the data for the SQLAlchemy expression.

Return type

Optional[int]

_unify(value: Any = ..., name: Optional[Any] = ..., parent: Optional[Any] = ..., two: bool = ..., from_kall: bool = ...) None[source]

Unify the SQLAlchemy expressions.

Return type

Any

class mock_alchemy.mocking.UnorderedCall(value=(), name='', parent=None, two=False, from_kall=True)[source]

Bases: unittest.mock._Call

Same as Call except in comparison order of parameters does not matter.

A mock.Call subclass that ensures that eqaulity does not depend on order. This isued to check if SQLAlchemy calls match up regardless of order. For example, this is useful in the case of filtering when .filter(y == 4).filter(y == 2) is the same as .filter(y == 2).filter(y == 4).

For example:

>>> a = ((1, 2, 3), {'hello': 'world'})
>>> b = ((3, 2, 1), {'hello': 'world'})
>>> UnorderedCall(a) == Call(b)
True
__eq__(other)[source]

Compares another call for equality.

Return type

bool

__hash__ = None
class mock_alchemy.mocking.UnorderedTuple(iterable=(), /)[source]

Bases: tuple

Same as tuple except in comparison order does not matter.

A tuple in which order does not matter for equality. It compares by remove elements from the other tuple.

For example:

>>> UnorderedTuple((1, 2, 3)) == (3, 2, 1)
True
__eq__(other)[source]

Compares another tuple for equality.

Return type

bool

__hash__ = None
mock_alchemy.mocking.sqlalchemy_call(call, with_name=False, base_call=<class 'unittest.mock._Call'>)[source]

Convert mock.call() into call.

Convert mock.call() into call with all parameters wrapped with ExpressionMatcher. This is useful for comparing SQLAlchemy statements for equality.

Parameters
  • call (_Call) – The call to convert.

  • with_name (bool) – Whether to convert the name of the call.

  • base_call (Any) – The type of call to convert into.

Return type

Any

Returns

Returns the converted call of the type base_call.

For example:

>>> args, kwargs = sqlalchemy_call(mock.call(5, foo='bar'))
>>> isinstance(args[0], ExpressionMatcher)
True
>>> isinstance(kwargs['foo'], ExpressionMatcher)
True