{"id":18098269,"url":"https://github.com/miki725/alchemy-mock","last_synced_at":"2025-04-09T16:09:23.343Z","repository":{"id":55149105,"uuid":"117190235","full_name":"miki725/alchemy-mock","owner":"miki725","description":"SQLAlchemy mock helpers.","archived":false,"fork":false,"pushed_at":"2021-01-07T23:56:50.000Z","size":64,"stargazers_count":81,"open_issues_count":17,"forks_count":15,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-09T16:09:14.128Z","etag":null,"topics":["mock","sqlalchemy","testing"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/miki725.png","metadata":{"files":{"readme":"README.rst","changelog":"HISTORY.rst","contributing":null,"funding":null,"license":"LICENSE.rst","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-12T04:00:09.000Z","updated_at":"2024-11-28T16:33:43.000Z","dependencies_parsed_at":"2022-08-14T13:31:13.266Z","dependency_job_id":null,"html_url":"https://github.com/miki725/alchemy-mock","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miki725%2Falchemy-mock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miki725%2Falchemy-mock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miki725%2Falchemy-mock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miki725%2Falchemy-mock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/miki725","download_url":"https://codeload.github.com/miki725/alchemy-mock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065283,"owners_count":21041871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mock","sqlalchemy","testing"],"created_at":"2024-10-31T20:09:15.753Z","updated_at":"2025-04-09T16:09:23.321Z","avatar_url":"https://github.com/miki725.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"===============\nSQLAlchemy Mock\n===============\n\n.. image:: https://badge.fury.io/py/alchemy-mock.png\n    :target: http://badge.fury.io/py/alchemy-mock\n\n.. image:: https://travis-ci.org/miki725/alchemy-mock.png?branch=master\n    :target: https://travis-ci.org/miki725/alchemy-mock\n\n.. image:: https://coveralls.io/repos/miki725/alchemy-mock/badge.png?branch=master\n    :target: https://coveralls.io/r/miki725/alchemy-mock?branch=master\n\nSQLAlchemy mock helpers.\n\n* Free software: MIT license\n* GitHub: https://github.com/miki725/alchemy-mock\n\nInstalling\n----------\n\nYou can install ``alchemy-mock`` using pip::\n\n    $ pip install alchemy-mock\n\nWhy?\n----\n\nSQLAlchemy is awesome. Unittests are great.\nAccessing DB during tests - not so much.\nThis library provides easy way to mock SQLAlchemy's session\nin unittests while preserving ability to do sane asserts.\nNormally SQLAlchemy's expressions cannot be easily compared\nas comparison on binary expression produces yet another binary expression::\n\n    \u003e\u003e\u003e type((Model.foo == 5) == (Model.bar == 5))\n    \u003cclass 'sqlalchemy.sql.elements.BinaryExpression'\u003e\n\nBut they can be compared with this library::\n\n    \u003e\u003e\u003e ExpressionMatcher(Model.foo == 5) == (Model.bar == 5)\n    False\n\nUsing\n-----\n\n``ExpressionMatcher`` can be directly used::\n\n    \u003e\u003e\u003e from alchemy_mock.comparison import ExpressionMatcher\n    \u003e\u003e\u003e ExpressionMatcher(Model.foo == 5) == (Model.foo == 5)\n    True\n\nAlternatively ``AlchemyMagicMock`` can be used to mock out SQLAlchemy session::\n\n    \u003e\u003e\u003e from alchemy_mock.mocking import AlchemyMagicMock\n    \u003e\u003e\u003e session = AlchemyMagicMock()\n    \u003e\u003e\u003e session.query(Model).filter(Model.foo == 5).all()\n\n    \u003e\u003e\u003e session.query.return_value.filter.assert_called_once_with(Model.foo == 5)\n\nIn real world though session can be interacted with multiple times to query some data.\nIn those cases ``UnifiedAlchemyMagicMock`` can be used which combines various calls for easier assertions::\n\n    \u003e\u003e\u003e from alchemy_mock.mocking import UnifiedAlchemyMagicMock\n    \u003e\u003e\u003e session = UnifiedAlchemyMagicMock()\n\n    \u003e\u003e\u003e m = session.query(Model)\n    \u003e\u003e\u003e q = m.filter(Model.foo == 5)\n    \u003e\u003e\u003e if condition:\n    ...     q = q.filter(Model.bar \u003e 10).all()\n    \u003e\u003e\u003e data1 = q.all()\n    \u003e\u003e\u003e data2 = m.filter(Model.note == 'hello world').all()\n\n    \u003e\u003e\u003e session.filter.assert_has_calls([\n    ...     mock.call(Model.foo == 5, Model.bar \u003e 10),\n    ...     mock.call(Model.note == 'hello world'),\n    ... ])\n\nAlso real-data can be stubbed by criteria::\n\n    \u003e\u003e\u003e from alchemy_mock.mocking import UnifiedAlchemyMagicMock\n    \u003e\u003e\u003e session = UnifiedAlchemyMagicMock(data=[\n    ...     (\n    ...         [mock.call.query(Model),\n    ...          mock.call.filter(Model.foo == 5, Model.bar \u003e 10)],\n    ...         [Model(foo=5, bar=11)]\n    ...     ),\n    ...     (\n    ...         [mock.call.query(Model),\n    ...          mock.call.filter(Model.note == 'hello world')],\n    ...         [Model(note='hello world')]\n    ...     ),\n    ...     (\n    ...         [mock.call.query(AnotherModel),\n    ...          mock.call.filter(Model.foo == 5, Model.bar \u003e 10)],\n    ...         [AnotherModel(foo=5, bar=17)]\n    ...     ),\n    ... ])\n    \u003e\u003e\u003e session.query(Model).filter(Model.foo == 5).filter(Model.bar \u003e 10).all()\n    [Model(foo=5, bar=11)]\n    \u003e\u003e\u003e session.query(Model).filter(Model.note == 'hello world').all()\n    [Model(note='hello world')]\n    \u003e\u003e\u003e session.query(AnotherModel).filter(Model.foo == 5).filter(Model.bar \u003e 10).all()\n    [AnotherModel(foo=5, bar=17)]\n    \u003e\u003e\u003e session.query(AnotherModel).filter(Model.note == 'hello world').all()\n    []\n\nFinally ``UnifiedAlchemyMagicMock`` can partially fake session mutations\nsuch as ``session.add(instance)``. For example::\n\n    \u003e\u003e\u003e session = UnifiedAlchemyMagicMock()\n    \u003e\u003e\u003e session.add(Model(pk=1, foo='bar'))\n    \u003e\u003e\u003e session.add(Model(pk=2, foo='baz'))\n    \u003e\u003e\u003e session.query(Model).all()\n    [Model(foo='bar'), Model(foo='baz')]\n    \u003e\u003e\u003e session.query(Model).get(1)\n    Model(foo='bar')\n    \u003e\u003e\u003e session.query(Model).get(2)\n    Model(foo='baz')\n\nNote that its partially correct since if added models are filtered on,\nsession is unable to actually apply any filters so it returns everything::\n\n   \u003e\u003e\u003e session.query(Model).filter(Model.foo == 'bar').all()\n   [Model(foo='bar'), Model(foo='baz')]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiki725%2Falchemy-mock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmiki725%2Falchemy-mock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiki725%2Falchemy-mock/lists"}