mock-alchemy
A module for basic mocking of SQLAlchemy sessions and calls.
mock_alchemy.mocking.
AlchemyMagicMock
Bases: unittest.mock.MagicMock
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__
Creates AlchemyMagicMock that can be used as limited SQLAlchemy session.
_format_mock_call_signature
Formats the mock call into a string.
str
assert_any_call
Assert for a specific call to have happened.
None
assert_called_with
assert_has_calls
Assert for a list of calls to have happened.
UnifiedAlchemyMagicMock
Bases: mock_alchemy.mocking.AlchemyMagicMock
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.
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.
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.
.delete()
.add()
.add_all()
Set[str]
>>> 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.
(criteria, result)
criteria
>>> 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() Traceback (most recent call last): ... sqlalchemy.orm.exc.NoResultFound: No row was found for one() >>> s.query('foo').filter(c == 'one').filter(c == 'two').one() Traceback (most recent call last): ... sqlalchemy.orm.exc.MultipleResultsFound: Multiple rows were found for one() >>> 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.
.all()
Creates an UnifiedAlchemyMagicMock to mock a SQLAlchemy session.
_get_data
Get the data for the SQLAlchemy expression.
Any
_get_previous_call
Gets the previous call right before the current call.
Optional[_Call]
Optional
_Call
_get_previous_calls
Gets the previous calls on the same line.
Iterator
_mutate_data
Alter the data for the SQLAlchemy expression.
Optional[int]
int
_unify
Unify the SQLAlchemy expressions.
UnorderedCall
Bases: unittest.mock._Call
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).
mock.Call
.filter(y == 4).filter(y == 2)
.filter(y == 2).filter(y == 4)
>>> a = ((1, 2, 3), {'hello': 'world'}) >>> b = ((3, 2, 1), {'hello': 'world'}) >>> UnorderedCall(a) == Call(b) True
__eq__
Compares another call for equality.
bool
UnorderedTuple
Bases: tuple
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.
>>> UnorderedTuple((1, 2, 3)) == (3, 2, 1) True
Compares another tuple for equality.
sqlalchemy_call
Convert mock.call() into call.
mock.call()
Convert mock.call() into call with all parameters wrapped with ExpressionMatcher. This is useful for comparing SQLAlchemy statements for equality.
ExpressionMatcher
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.
Returns the converted call of the type base_call.
base_call
>>> args, kwargs = sqlalchemy_call(mock.call(5, foo='bar')) >>> isinstance(args[0], ExpressionMatcher) True >>> isinstance(kwargs['foo'], ExpressionMatcher) True