From 25da9d272f70c70be7f203f9d8f4537ce69c3329 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 00:58:35 +0300 Subject: [PATCH 01/27] - add ModelMocker --- pytest_sqlalchemy_mock.py | 58 +++++++++++++++++++++++++++- tests/conftest.py | 14 +++++++ tests/data.py | 22 +++++++++++ tests/db.py | 15 +++++++ tests/test_pytest_sqlalchemy_mock.py | 17 ++++++++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/data.py create mode 100644 tests/db.py diff --git a/pytest_sqlalchemy_mock.py b/pytest_sqlalchemy_mock.py index e1dc3b5..67ec730 100644 --- a/pytest_sqlalchemy_mock.py +++ b/pytest_sqlalchemy_mock.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Tuple import pytest from sqlalchemy import create_engine @@ -8,6 +8,33 @@ from sqlalchemy.orm import Session +class ModelMocker: + # TODO make type annotations + _base = None + _mock_config: List[Tuple[str, List]] = None + + def __init__(self, session, base, mock_config): + self._session: Session = session + self._base = base + self._mock_config = mock_config + + def get_model_class_with_table_name(self, table_name: str): + for mapper in self._base.registry.mappers: + cls = mapper.class_ + if cls.__tablename__ == table_name: + return cls + + def create_all(self): + for model_config in self._mock_config: + table_name, data = model_config + model_class = self.get_model_class_with_table_name(table_name) + if model_class: + for datum in data: + instance = model_class(**datum) + self._session.add(instance) + self._session.commit() + + @pytest.fixture(scope="session") def connection_url(): return "sqlite:///:memory:" @@ -19,7 +46,19 @@ def engine(connection_url): @pytest.fixture(scope="function") -def connection(engine): +def sqlalchemy_declarative_base(): + return + + +@pytest.fixture(scope="function") +def sqlalchemy_mock_config(): + return + + +@pytest.fixture(scope="function") +def connection(engine, sqlalchemy_declarative_base): + if sqlalchemy_declarative_base: + sqlalchemy_declarative_base.metadata.create_all(engine) return engine.connect() @@ -28,3 +67,18 @@ def session(connection): session: Session = sessionmaker()(bind=connection) yield session session.close() + + +@pytest.fixture(scope="function") +def mocked_session( + connection, sqlalchemy_declarative_base, sqlalchemy_mock_config +): + session: Session = sessionmaker()(bind=connection) + + if sqlalchemy_declarative_base and sqlalchemy_mock_config: + ModelMocker( + session, sqlalchemy_declarative_base, sqlalchemy_mock_config + ).create_all() + + yield session + session.close() diff --git a/tests/conftest.py b/tests/conftest.py index 1cd8595..6816aef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1,15 @@ +import pytest + from pytest_sqlalchemy_mock import * +from tests.data import MockData +from tests.db import Base + + +@pytest.fixture(scope="function") +def sqlalchemy_declarative_base(): + return Base + + +@pytest.fixture(scope="function") +def sqlalchemy_mock_config(): + return [("user", MockData.USER_DATA)] diff --git a/tests/data.py b/tests/data.py new file mode 100644 index 0000000..c492fcd --- /dev/null +++ b/tests/data.py @@ -0,0 +1,22 @@ +from datetime import datetime + + +class MockData: + USER_DATA = [ + { + "id": 1, + "name": "Kevin", + "surname": "Malone", + "is_admin": False, + "city": "NY", + "join_date": datetime(2011, 7, 27, 16, 2, 8), + }, + { + "id": 2, + "name": "Dwight", + "surname": "Schrute", + "is_admin": True, + "city": "PA", + "join_date": datetime(2009, 11, 20, 21, 3, 12), + } + ] diff --git a/tests/db.py b/tests/db.py new file mode 100644 index 0000000..666590c --- /dev/null +++ b/tests/db.py @@ -0,0 +1,15 @@ +from sqlalchemy import Boolean, Column, DateTime, Integer, String, func +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + + +class User(Base): + __tablename__ = "user" + + id = Column(Integer, primary_key=True) + name = Column(String) + surname = Column(String) + is_admin = Column(Boolean, default=False) + city = Column(String) + join_date = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/tests/test_pytest_sqlalchemy_mock.py b/tests/test_pytest_sqlalchemy_mock.py index c14bb1f..6a6a781 100644 --- a/tests/test_pytest_sqlalchemy_mock.py +++ b/tests/test_pytest_sqlalchemy_mock.py @@ -1,2 +1,19 @@ +from .data import MockData + + def test_get_session(session): assert session.execute("SELECT 5").scalar() == 5 + + +def test_session_user_table(session): + assert session.execute("SELECT count(*) from user").scalar() == 0 + + +def test_mocked_session_user_table(mocked_session): + user_data = mocked_session.execute("SELECT * from user;").first() + raw_data = MockData.USER_DATA[0] + assert user_data[0] == raw_data["id"] + assert user_data[1] == raw_data["name"] + assert user_data[2] == raw_data["surname"] + assert user_data[3] == raw_data["is_admin"] + assert user_data[4] == raw_data["city"] From 513d43689b30d1a0623d596fbf8f9ebb8dd2da6c Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 13:34:27 +0300 Subject: [PATCH 02/27] - refine README.md - add mocked session tests - bump version to 0.1.1 --- README.md | 69 +++++++++++++++++++++++++++- setup.py | 2 +- tests/test_pytest_sqlalchemy_mock.py | 8 ++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61cdd99..348e408 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,71 @@ # pytest-sqlalchemy-mock [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) -pytest plugin to mock sqlalchemy models +This plugin provides pytest fixtures to create an in-memory DB instance on tests and dump your raw test data. + +## Installation +``` +pip install pytest-sqlalchemy-mock +``` + +## Usage +Let's assume you have a SQLAlchemy declarative base and some models with it. + +**models.py** +```python +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + + +class User(Base): + __tablename__ = "user" + + id = Column(Integer, primary_key=True) + name = Column(String) +``` +Firstly SQLAlchemy base class which is used for declare models should be passed with `sqlalchemy_declarative_base` fixture in `conftest.py` + +**conftest.py** +```python +@pytest.fixture(scope="function") +def sqlalchemy_declarative_base(): + return Base +``` +Then in test functions you can get mocked_session fixture to make query in mocked DB. + +**test_user_table.py** +```python +def test_mocked_session_user_table(mocked_session): + user_data = mocked_session.execute("SELECT * from user;").all() + assert user_data == [] +``` +Also you can dump your mock data to DB before start testing via `sqlalchemy_mock_config` fixture like following. + +**conftest.py** +```python +@pytest.fixture(scope="function") +def sqlalchemy_mock_config(): + return [("user", [ + { + "id": 1, + "name": "Kevin" + }, + { + "id": 2, + "name": "Dwight" + } + ])] +``` +**test_user_table.py** +```python +def test_mocked_session_user_class(mocked_session): + user = mocked_session.query(User).filter_by(id=2).first() + assert user.name == "Dwight" +``` + +## Upcoming Features +* Mock with decoratorsss +* Support to load data from `.json` and `.csv` +* Async SQLAlchemy support diff --git a/setup.py b/setup.py index 340ffd5..50820ea 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def read(fname): license='MIT', description='pytest sqlalchemy plugin for mock', # long_description=read("README.rst"), - version='0.1.0', + version='0.1.1', author='Resul Yurttakalan', author_email='resulyrt93@gmail.com', url='https://github.com/resulyrt93/pytest-sqlalchemy-mock', diff --git a/tests/test_pytest_sqlalchemy_mock.py b/tests/test_pytest_sqlalchemy_mock.py index 6a6a781..c0d2fb0 100644 --- a/tests/test_pytest_sqlalchemy_mock.py +++ b/tests/test_pytest_sqlalchemy_mock.py @@ -1,4 +1,5 @@ from .data import MockData +from .db import User def test_get_session(session): @@ -17,3 +18,10 @@ def test_mocked_session_user_table(mocked_session): assert user_data[2] == raw_data["surname"] assert user_data[3] == raw_data["is_admin"] assert user_data[4] == raw_data["city"] + + +def test_mocked_session_user_model(mocked_session): + user = mocked_session.query(User).filter_by(id=2).first() + raw_data = MockData.USER_DATA[1] + assert user.name == raw_data["name"] + assert user.is_admin == raw_data["is_admin"] From 37e22c08bbf24302207e9ada08d5d8404368290b Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 13:35:59 +0300 Subject: [PATCH 03/27] - change github action condition to only for PRs --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c17d6dd..6f06f25 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,6 +1,6 @@ name: Python package -on: [ push, pull_request ] +on: [ pull_request ] jobs: build: From 19669875ba37175117f6ed1dde1d3c885d9a694c Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 14:01:05 +0300 Subject: [PATCH 04/27] - add pypi deploy action --- .github/workflows/deploy.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..d192c2d --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,24 @@ +name: Publish Python 🐍 distributions 📦 to PyPI + +on: push +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install pypa/build + run: python -m pip install build + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ . + - name: Deploy package + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + verbose: true + skip_existing: true From bb21f32aecc893f43809a1003af844bc8879ee71 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 14:09:35 +0300 Subject: [PATCH 05/27] - add pypi deploy action --- .github/workflows/deploy.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index d192c2d..f155390 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,6 +1,9 @@ name: Publish Python 🐍 distributions 📦 to PyPI -on: push +on: + push: + tags: + - 'v*' jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI From fd687e6f492ba6018cdc107c17d8a1916355a3e8 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 14:16:50 +0300 Subject: [PATCH 06/27] - add long description for pypi --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 50820ea..78c10a5 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,8 @@ def read(fname): name='pytest-sqlalchemy-mock', license='MIT', description='pytest sqlalchemy plugin for mock', - # long_description=read("README.rst"), + long_description=read("README.md"), + long_description_content_type='text/markdown', version='0.1.1', author='Resul Yurttakalan', author_email='resulyrt93@gmail.com', From e9cfc54746aa48912a5216bd2255e4f24d9718c9 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 15:16:54 +0300 Subject: [PATCH 07/27] - create pytest_sqlalchemy_mock module - separate model mocker --- pytest_sqlalchemy_mock/__init__.py | 0 .../base.py | 31 ++-------------- pytest_sqlalchemy_mock/model_mocker.py | 36 +++++++++++++++++++ tests/conftest.py | 5 ++- 4 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 pytest_sqlalchemy_mock/__init__.py rename pytest_sqlalchemy_mock.py => pytest_sqlalchemy_mock/base.py (57%) create mode 100644 pytest_sqlalchemy_mock/model_mocker.py diff --git a/pytest_sqlalchemy_mock/__init__.py b/pytest_sqlalchemy_mock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytest_sqlalchemy_mock.py b/pytest_sqlalchemy_mock/base.py similarity index 57% rename from pytest_sqlalchemy_mock.py rename to pytest_sqlalchemy_mock/base.py index 67ec730..d684e23 100644 --- a/pytest_sqlalchemy_mock.py +++ b/pytest_sqlalchemy_mock/base.py @@ -1,40 +1,15 @@ -from typing import TYPE_CHECKING, List, Tuple +from typing import TYPE_CHECKING import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from .model_mocker import ModelMocker + if TYPE_CHECKING: from sqlalchemy.orm import Session -class ModelMocker: - # TODO make type annotations - _base = None - _mock_config: List[Tuple[str, List]] = None - - def __init__(self, session, base, mock_config): - self._session: Session = session - self._base = base - self._mock_config = mock_config - - def get_model_class_with_table_name(self, table_name: str): - for mapper in self._base.registry.mappers: - cls = mapper.class_ - if cls.__tablename__ == table_name: - return cls - - def create_all(self): - for model_config in self._mock_config: - table_name, data = model_config - model_class = self.get_model_class_with_table_name(table_name) - if model_class: - for datum in data: - instance = model_class(**datum) - self._session.add(instance) - self._session.commit() - - @pytest.fixture(scope="session") def connection_url(): return "sqlite:///:memory:" diff --git a/pytest_sqlalchemy_mock/model_mocker.py b/pytest_sqlalchemy_mock/model_mocker.py new file mode 100644 index 0000000..f414037 --- /dev/null +++ b/pytest_sqlalchemy_mock/model_mocker.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List, Tuple + + from sqlalchemy.orm import Session + + MOCK_CONFIG_TYPE = List[Tuple[str, List]] + + +class ModelMocker: + _base = None + _mock_config: MOCK_CONFIG_TYPE = None + + def __init__(self, session: Session, base, mock_config: MOCK_CONFIG_TYPE): + self._session: Session = session + self._base = base + self._mock_config = mock_config + + def get_model_class_with_table_name(self, table_name: str): + for mapper in self._base.registry.mappers: + cls = mapper.class_ + if cls.__tablename__ == table_name: + return cls + + def create_all(self): + for model_config in self._mock_config: + table_name, data = model_config + model_class = self.get_model_class_with_table_name(table_name) + if model_class: + for datum in data: + instance = model_class(**datum) + self._session.add(instance) + self._session.commit() diff --git a/tests/conftest.py b/tests/conftest.py index 6816aef..fcbc4f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ -import pytest - -from pytest_sqlalchemy_mock import * +# TODO use pytester for testing +from pytest_sqlalchemy_mock.base import * from tests.data import MockData from tests.db import Base From 6c72954daf75096bc4e0eeae0a8591983cc3d12d Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 18:44:39 +0300 Subject: [PATCH 08/27] - add README.md badges --- .github/workflows/deploy.yaml | 3 +-- .github/workflows/tests.yaml | 6 ++---- README.md | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f155390..79dc15d 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,5 +1,4 @@ -name: Publish Python 🐍 distributions 📦 to PyPI - +name: Deploy on: push: tags: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6f06f25..370887f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,15 +1,13 @@ -name: Python package +name: CI on: [ pull_request ] jobs: - build: - + test: runs-on: ubuntu-latest strategy: matrix: python-version: [ "3.7", "3.8", "3.9", "3.10" ] - steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index 348e408..50ffa2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # pytest-sqlalchemy-mock [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) +![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yml/badge.svg?branch=dev) +Code style: black This plugin provides pytest fixtures to create an in-memory DB instance on tests and dump your raw test data. From 4ab5a5efeb19a3725b1d36e6d2c79e032c6f0c8a Mon Sep 17 00:00:00 2001 From: Resul Yurttakalan <45128820+resulyrt93@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:49:29 +0300 Subject: [PATCH 09/27] add workflow badge to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50ffa2b..58e0617 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pytest-sqlalchemy-mock [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) -![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yml/badge.svg?branch=dev) +[![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml/badge.svg?branch=dev)](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml) Code style: black This plugin provides pytest fixtures to create an in-memory DB instance on tests and dump your raw test data. From 7f99466bf1131da7aa4a3d03ac09139a70d1bfaf Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 18:58:38 +0300 Subject: [PATCH 10/27] - refine README.md --- README.md | 8 ++++---- setup.py | 34 +++++++++++++++++----------------- tests/data.py | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 58e0617..f21f5dc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# pytest-sqlalchemy-mock +# pytest-sqlalchemy-mock 👋 [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) [![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml/badge.svg?branch=dev)](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml) Code style: black @@ -35,7 +35,7 @@ Firstly SQLAlchemy base class which is used for declare models should be passed def sqlalchemy_declarative_base(): return Base ``` -Then in test functions you can get mocked_session fixture to make query in mocked DB. +Then in test functions you can use `mocked_session` fixture to make query in mocked DB. **test_user_table.py** ```python @@ -43,7 +43,7 @@ def test_mocked_session_user_table(mocked_session): user_data = mocked_session.execute("SELECT * from user;").all() assert user_data == [] ``` -Also you can dump your mock data to DB before start testing via `sqlalchemy_mock_config` fixture like following. +Also, you can dump your mock data to DB before start testing via `sqlalchemy_mock_config` fixture like following. **conftest.py** ```python @@ -68,6 +68,6 @@ def test_mocked_session_user_class(mocked_session): ``` ## Upcoming Features -* Mock with decoratorsss +* Mock with decorator for specific DB states for specific cases. * Support to load data from `.json` and `.csv` * Async SQLAlchemy support diff --git a/setup.py b/setup.py index 78c10a5..ee8152f 100644 --- a/setup.py +++ b/setup.py @@ -8,23 +8,23 @@ def read(fname): setup( - name='pytest-sqlalchemy-mock', - license='MIT', - description='pytest sqlalchemy plugin for mock', + name="pytest-sqlalchemy-mock", + license="MIT", + description="pytest sqlalchemy plugin for mock", long_description=read("README.md"), - long_description_content_type='text/markdown', - version='0.1.1', - author='Resul Yurttakalan', - author_email='resulyrt93@gmail.com', - url='https://github.com/resulyrt93/pytest-sqlalchemy-mock', - py_modules=['pytest_sqlalchemy_mock'], - entry_points={'pytest11': ['sqlalchemy = pytest_sqlalchemy_mock']}, - install_requires=['pytest>=2.0', 'sqlalchemy'], + long_description_content_type="text/markdown", + version="0.1.2", + author="Resul Yurttakalan", + author_email="resulyrt93@gmail.com", + url="https://github.com/resulyrt93/pytest-sqlalchemy-mock", + py_modules=["pytest_sqlalchemy_mock"], + entry_points={"pytest11": ["sqlalchemy = pytest_sqlalchemy_mock"]}, + install_requires=["pytest>=2.0", "sqlalchemy"], classifiers=[ - 'Framework :: Pytest', - 'Development Status :: 3 - Alpha', - 'Topic :: Software Development :: Testing', - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - ] + "Framework :: Pytest", + "Development Status :: 3 - Alpha", + "Topic :: Software Development :: Testing", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + ], ) diff --git a/tests/data.py b/tests/data.py index c492fcd..e3ff5f8 100644 --- a/tests/data.py +++ b/tests/data.py @@ -18,5 +18,5 @@ class MockData: "is_admin": True, "city": "PA", "join_date": datetime(2009, 11, 20, 21, 3, 12), - } + }, ] From 4072c127e721bd1bbd2ae119009275bdbf66a097 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:06:27 +0300 Subject: [PATCH 11/27] - add codecov --- .github/workflows/tests.yaml | 2 +- requirements-dev.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 370887f..62ba432 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,4 +23,4 @@ jobs: pre-commit run --all-files --show-diff-on-failure - name: Test with pytest run: | - pytest tests + pytest --codecov tests diff --git a/requirements-dev.txt b/requirements-dev.txt index 780ae47..4e65e19 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ sqlalchemy flake8 pytest +pytest-codecov pre-commit sqlalchemy From de70d1aa602d53e5e9c9e360bdee6405d7422969 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:10:02 +0300 Subject: [PATCH 12/27] - add workflow rule --- .github/workflows/tests.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 62ba432..05d4c3e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,6 +1,12 @@ name: CI -on: [ pull_request ] +on: + push: + branches: + - dev + pull_request: + branches: + - dev jobs: test: From 8e786e0fd4280abf2b2166664e11b13ba79b61b3 Mon Sep 17 00:00:00 2001 From: Resul Yurttakalan <45128820+resulyrt93@users.noreply.github.com> Date: Sun, 25 Sep 2022 19:12:05 +0300 Subject: [PATCH 13/27] add codecov badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f21f5dc..6e139cb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # pytest-sqlalchemy-mock 👋 [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) +[![codecov](https://codecov.io/gh/resulyrt93/pytest-sqlalchemy-mock/branch/dev/graph/badge.svg?token=RUQ4DN3CH9)](https://codecov.io/gh/resulyrt93/pytest-sqlalchemy-mock) [![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml/badge.svg?branch=dev)](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml) Code style: black From 7c0b96cfd13a30c798a0ffcd50777c346d6b1e01 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:20:52 +0300 Subject: [PATCH 14/27] - add .coveragerc file --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..06e4886 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[report] +exclude_lines = + pragma: no cover + if TYPE_CHECKING: From 94497071ecd3025f4743f2996cd3e4eb5cbd8b4b Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:22:07 +0300 Subject: [PATCH 15/27] - add .coveragerc file --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 05d4c3e..89ea211 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,4 +29,4 @@ jobs: pre-commit run --all-files --show-diff-on-failure - name: Test with pytest run: | - pytest --codecov tests + pytest --codecov pytest_sqlalchemy_mock --cov-report=term-missing From 99c41cd99eaebb8f96e9148ff75384cfed979884 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:38:26 +0300 Subject: [PATCH 16/27] - fix coverage commands --- .github/workflows/tests.yaml | 4 +++- requirements-dev.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 89ea211..0831fd4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,4 +29,6 @@ jobs: pre-commit run --all-files --show-diff-on-failure - name: Test with pytest run: | - pytest --codecov pytest_sqlalchemy_mock --cov-report=term-missing + pytest --cov pytest_sqlalchemy_mock --cov-report=term-missing + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 diff --git a/requirements-dev.txt b/requirements-dev.txt index 4e65e19..fd6536e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ sqlalchemy flake8 pytest -pytest-codecov +pytest-cov pre-commit sqlalchemy From 4d957bbedddc30e4147d21b55f8d02cdeb232ca1 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 19:46:09 +0300 Subject: [PATCH 17/27] - add supported python versions --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index ee8152f..17be788 100644 --- a/setup.py +++ b/setup.py @@ -26,5 +26,9 @@ def read(fname): "Topic :: Software Development :: Testing", "Intended Audience :: Developers", "Operating System :: OS Independent", + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ], ) From 679f6635a3fc5508744e678376292ad06f706d73 Mon Sep 17 00:00:00 2001 From: Resul Yurttakalan <45128820+resulyrt93@users.noreply.github.com> Date: Sun, 25 Sep 2022 19:52:12 +0300 Subject: [PATCH 18/27] add supported python versions badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e139cb..fc5f2de 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock) [![codecov](https://codecov.io/gh/resulyrt93/pytest-sqlalchemy-mock/branch/dev/graph/badge.svg?token=RUQ4DN3CH9)](https://codecov.io/gh/resulyrt93/pytest-sqlalchemy-mock) [![CI](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml/badge.svg?branch=dev)](https://github.com/resulyrt93/pytest-sqlalchemy-mock/actions/workflows/tests.yaml) +[![Supported Python Version](https://img.shields.io/pypi/pyversions/pytest-sqlalchemy-mock)](https://github.com/resulyrt93/pytest-sqlalchemy-mock) Code style: black This plugin provides pytest fixtures to create an in-memory DB instance on tests and dump your raw test data. From 5b12b3bf337e3d883e1acd795119f5f07b8a8ec3 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 21:01:02 +0300 Subject: [PATCH 19/27] - add package_dir parameter --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 17be788..705c989 100644 --- a/setup.py +++ b/setup.py @@ -13,11 +13,11 @@ def read(fname): description="pytest sqlalchemy plugin for mock", long_description=read("README.md"), long_description_content_type="text/markdown", - version="0.1.2", + version="0.1.3", author="Resul Yurttakalan", author_email="resulyrt93@gmail.com", url="https://github.com/resulyrt93/pytest-sqlalchemy-mock", - py_modules=["pytest_sqlalchemy_mock"], + package_dir={"": "pytest_sqlalchemy_mock"}, entry_points={"pytest11": ["sqlalchemy = pytest_sqlalchemy_mock"]}, install_requires=["pytest>=2.0", "sqlalchemy"], classifiers=[ From 4f383302abb8ff46f8d69a111ea2e6a82f177c5e Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 21:26:08 +0300 Subject: [PATCH 20/27] - fix pytest entrypoint --- setup.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 705c989..ac82f78 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,10 @@ def read(fname): author="Resul Yurttakalan", author_email="resulyrt93@gmail.com", url="https://github.com/resulyrt93/pytest-sqlalchemy-mock", - package_dir={"": "pytest_sqlalchemy_mock"}, - entry_points={"pytest11": ["sqlalchemy = pytest_sqlalchemy_mock"]}, + packages=["pytest_sqlalchemy_mock"], + entry_points={ + "pytest11": ["pytest_sqlalchemy_mock = pytest_sqlalchemy_mock.base"] + }, install_requires=["pytest>=2.0", "sqlalchemy"], classifiers=[ "Framework :: Pytest", @@ -26,9 +28,9 @@ def read(fname): "Topic :: Software Development :: Testing", "Intended Audience :: Developers", "Operating System :: OS Independent", - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], ) From b26ef4eab7bdfcfbbeff9cfb10e838246b7fd01e Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Sun, 25 Sep 2022 21:26:38 +0300 Subject: [PATCH 21/27] - bump version to 0.1.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ac82f78..6314ee1 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(fname): description="pytest sqlalchemy plugin for mock", long_description=read("README.md"), long_description_content_type="text/markdown", - version="0.1.3", + version="0.1.4", author="Resul Yurttakalan", author_email="resulyrt93@gmail.com", url="https://github.com/resulyrt93/pytest-sqlalchemy-mock", From 50e28980217f5e12038e4d8bae0fa054b9838922 Mon Sep 17 00:00:00 2001 From: Resul Yurttakalan <45128820+resulyrt93@users.noreply.github.com> Date: Mon, 26 Sep 2022 09:47:05 +0300 Subject: [PATCH 22/27] remove duplicate dependency --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index fd6536e..ea5954a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,3 @@ flake8 pytest pytest-cov pre-commit -sqlalchemy From fee412315b18d1d835de6fc4bbdeabfac89b1ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serhat=20Yava=C5=9F?= Date: Tue, 27 Sep 2022 19:42:08 +0300 Subject: [PATCH 23/27] add pip caching --- .github/workflows/deploy.yaml | 1 + .github/workflows/tests.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 79dc15d..d4aa7b6 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -13,6 +13,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.10" + cache: 'pip' - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0831fd4..a867041 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,6 +20,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip From 578b840fff3bca09b009c4bc2d976cb7dec09269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serhat=20Yava=C5=9F?= Date: Tue, 27 Sep 2022 20:16:23 +0300 Subject: [PATCH 24/27] add cache-dependency-path --- .github/workflows/deploy.yaml | 1 + .github/workflows/tests.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index d4aa7b6..f6dd493 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -14,6 +14,7 @@ jobs: with: python-version: "3.10" cache: 'pip' + cache-dependency-path: '**/requirements-dev.txt' - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a867041..23aae16 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -21,6 +21,7 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: 'pip' + cache-dependency-path: '**/requirements-dev.txt' - name: Install dependencies run: | python -m pip install --upgrade pip From 9b30e70931736ed6ebdbd86f6b2551e463a5f5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serhat=20Yava=C5=9F?= Date: Tue, 27 Sep 2022 20:25:17 +0300 Subject: [PATCH 25/27] fix: tabs to spaces --- .github/workflows/deploy.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f6dd493..0885295 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -14,7 +14,7 @@ jobs: with: python-version: "3.10" cache: 'pip' - cache-dependency-path: '**/requirements-dev.txt' + cache-dependency-path: '**/requirements-dev.txt' - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 23aae16..2301365 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -21,7 +21,7 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: 'pip' - cache-dependency-path: '**/requirements-dev.txt' + cache-dependency-path: '**/requirements-dev.txt' - name: Install dependencies run: | python -m pip install --upgrade pip From 3f83fa07f8e88c84087eb7c187ad3243d3d0a8a7 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Wed, 15 Mar 2023 19:24:31 +0300 Subject: [PATCH 26/27] - bump version to 0.1.5 - add assocation table support - add new tests for m2m relationship - upgrade isort pre-commit hook version --- .pre-commit-config.yaml | 2 +- pytest_sqlalchemy_mock/model_mocker.py | 18 +++++++-------- requirements-dev.txt | 3 +-- setup.py | 2 +- tests/conftest.py | 6 ++++- tests/data.py | 22 ++++++++++++++++++ tests/db.py | 32 ++++++++++++++++++++++++-- tests/test_pytest_sqlalchemy_mock.py | 26 +++++++++++++++++---- 8 files changed, 90 insertions(+), 21 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eafbbe9..55400ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: exclude: '.*\.pth$' - id: debug-statements - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 diff --git a/pytest_sqlalchemy_mock/model_mocker.py b/pytest_sqlalchemy_mock/model_mocker.py index f414037..097d2f8 100644 --- a/pytest_sqlalchemy_mock/model_mocker.py +++ b/pytest_sqlalchemy_mock/model_mocker.py @@ -5,6 +5,7 @@ if TYPE_CHECKING: from typing import List, Tuple + from sqlalchemy import Table from sqlalchemy.orm import Session MOCK_CONFIG_TYPE = List[Tuple[str, List]] @@ -19,18 +20,15 @@ def __init__(self, session: Session, base, mock_config: MOCK_CONFIG_TYPE): self._base = base self._mock_config = mock_config - def get_model_class_with_table_name(self, table_name: str): - for mapper in self._base.registry.mappers: - cls = mapper.class_ - if cls.__tablename__ == table_name: - return cls + def get_table_by_name(self, table_name: str) -> Table: + return self._base.metadata.tables.get(table_name) def create_all(self): for model_config in self._mock_config: table_name, data = model_config - model_class = self.get_model_class_with_table_name(table_name) - if model_class: + table = self.get_table_by_name(table_name) + + if table is not None: for datum in data: - instance = model_class(**datum) - self._session.add(instance) - self._session.commit() + instance = table.insert().values(**datum) + self._session.execute(instance) diff --git a/requirements-dev.txt b/requirements-dev.txt index fd6536e..51432d5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,5 @@ -sqlalchemy +sqlalchemy==2.0.6 flake8 pytest pytest-cov pre-commit -sqlalchemy diff --git a/setup.py b/setup.py index 6314ee1..a187d20 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(fname): description="pytest sqlalchemy plugin for mock", long_description=read("README.md"), long_description_content_type="text/markdown", - version="0.1.4", + version="0.1.5", author="Resul Yurttakalan", author_email="resulyrt93@gmail.com", url="https://github.com/resulyrt93/pytest-sqlalchemy-mock", diff --git a/tests/conftest.py b/tests/conftest.py index fcbc4f9..fc5c081 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,4 +11,8 @@ def sqlalchemy_declarative_base(): @pytest.fixture(scope="function") def sqlalchemy_mock_config(): - return [("user", MockData.USER_DATA)] + return [ + ("user", MockData.USER_DATA), + ("department", MockData.DEPARTMENT_DATA), + ("user_department", MockData.USER_DEPARTMENT_DATA), + ] diff --git a/tests/data.py b/tests/data.py index e3ff5f8..0408730 100644 --- a/tests/data.py +++ b/tests/data.py @@ -20,3 +20,25 @@ class MockData: "join_date": datetime(2009, 11, 20, 21, 3, 12), }, ] + + DEPARTMENT_DATA = [ + { + "id": 1, + "name": "Accounting", + }, + { + "id": 2, + "name": "Sales", + }, + ] + + USER_DEPARTMENT_DATA = [ + { + "user_id": 1, + "department_id": 1, + }, + { + "user_id": 2, + "department_id": 2, + }, + ] diff --git a/tests/db.py b/tests/db.py index 666590c..f84544e 100644 --- a/tests/db.py +++ b/tests/db.py @@ -1,9 +1,23 @@ -from sqlalchemy import Boolean, Column, DateTime, Integer, String, func -from sqlalchemy.orm import declarative_base +from typing import List + +from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Integer, String, + func) +from sqlalchemy.orm import Mapped, declarative_base, relationship +from sqlalchemy.testing.schema import Table Base = declarative_base() +user_department_association_table = Table( + "user_department", + Base.metadata, + Column("user_id", Integer, ForeignKey("user.id"), primary_key=True), + Column( + "department_id", Integer, ForeignKey("department.id"), primary_key=True + ), +) + + class User(Base): __tablename__ = "user" @@ -13,3 +27,17 @@ class User(Base): is_admin = Column(Boolean, default=False) city = Column(String) join_date = Column(DateTime(timezone=True), server_default=func.now()) + departments: Mapped[List["Department"]] = relationship( + secondary=user_department_association_table, back_populates="users" + ) + + +class Department(Base): + __tablename__ = "department" + + id = Column(Integer, primary_key=True) + name = Column(String) + users: Mapped[List[User]] = relationship( + secondary=user_department_association_table, + back_populates="departments", + ) diff --git a/tests/test_pytest_sqlalchemy_mock.py b/tests/test_pytest_sqlalchemy_mock.py index c0d2fb0..8992fe8 100644 --- a/tests/test_pytest_sqlalchemy_mock.py +++ b/tests/test_pytest_sqlalchemy_mock.py @@ -1,17 +1,26 @@ +from sqlalchemy import text + from .data import MockData -from .db import User +from .db import Department, User def test_get_session(session): - assert session.execute("SELECT 5").scalar() == 5 + assert session.execute(text("SELECT 5")).scalar() == 5 def test_session_user_table(session): - assert session.execute("SELECT count(*) from user").scalar() == 0 + assert session.execute(text("SELECT count(*) from user")).scalar() == 0 + + +def test_session_query_for_assocation_table(session): + assert ( + session.execute(text("SELECT count(*) from user_department")).scalar() + == 0 + ) def test_mocked_session_user_table(mocked_session): - user_data = mocked_session.execute("SELECT * from user;").first() + user_data = mocked_session.execute(text("SELECT * from user;")).first() raw_data = MockData.USER_DATA[0] assert user_data[0] == raw_data["id"] assert user_data[1] == raw_data["name"] @@ -25,3 +34,12 @@ def test_mocked_session_user_model(mocked_session): raw_data = MockData.USER_DATA[1] assert user.name == raw_data["name"] assert user.is_admin == raw_data["is_admin"] + assert user.departments[0].name == "Sales" + + +def test_mocked_session_department_model(mocked_session): + department = mocked_session.query(Department).filter_by(id=1).first() + raw_data = MockData.DEPARTMENT_DATA[0] + assert department.name == raw_data["name"] + assert len(department.users) == 1 + assert department.users[0].name == "Kevin" From 974789fd3730fb42a67aa528447cdcf533629836 Mon Sep 17 00:00:00 2001 From: resulyurttakalan Date: Wed, 15 Mar 2023 19:37:13 +0300 Subject: [PATCH 27/27] - remove python 3.7 support --- .github/workflows/tests.yaml | 2 +- setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0831fd4..867a9b8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10" ] + python-version: [ "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/setup.py b/setup.py index a187d20..86118d9 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,6 @@ def read(fname): "Topic :: Software Development :: Testing", "Intended Audience :: Developers", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10",