Skip to content

Commit

Permalink
Merge pull request #9 from resulyrt93/dev
Browse files Browse the repository at this point in the history
dev -> master
  • Loading branch information
resulyrt93 authored Mar 15, 2023
2 parents a374b55 + b1231f2 commit 2374aec
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 61 deletions.
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
28 changes: 28 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Deploy
on:
push:
tags:
- 'v*'
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"
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
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
22 changes: 15 additions & 7 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
name: Python package
name: CI

on: [ push, pull_request ]
on:
push:
branches:
- dev
pull_request:
branches:
- dev

jobs:
build:

test:
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 }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: '**/requirements-dev.txt'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -25,4 +31,6 @@ jobs:
pre-commit run --all-files --show-diff-on-failure
- name: Test with pytest
run: |
pytest tests
pytest --cov pytest_sqlalchemy_mock --cov-report=term-missing
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 73 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,75 @@
# 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)
[![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)
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>

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 use `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 decorator for specific DB states for specific cases.
* Support to load data from `.json` and `.csv`
* Async SQLAlchemy support
30 changes: 0 additions & 30 deletions pytest_sqlalchemy_mock.py

This file was deleted.

Empty file.
59 changes: 59 additions & 0 deletions pytest_sqlalchemy_mock/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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


@pytest.fixture(scope="session")
def connection_url():
return "sqlite:///:memory:"


@pytest.fixture(scope="function")
def engine(connection_url):
return create_engine(connection_url)


@pytest.fixture(scope="function")
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()


@pytest.fixture(scope="function")
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()
34 changes: 34 additions & 0 deletions pytest_sqlalchemy_mock/model_mocker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import List, Tuple

from sqlalchemy import Table
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_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
table = self.get_table_by_name(table_name)

if table is not None:
for datum in data:
instance = table.insert().values(**datum)
self._session.execute(instance)
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sqlalchemy
sqlalchemy==2.0.6
flake8
pytest
pytest-cov
pre-commit
sqlalchemy
40 changes: 23 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@ def read(fname):


setup(
name='pytest-sqlalchemy-mock',
license='MIT',
description='pytest sqlalchemy plugin for mock',
# long_description=read("README.rst"),
version='0.1.0',
author='Resul Yurttakalan',
author_email='[email protected]',
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'],
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.5",
author="Resul Yurttakalan",
author_email="[email protected]",
url="https://github.com/resulyrt93/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',
'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",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
19 changes: 18 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
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


@pytest.fixture(scope="function")
def sqlalchemy_declarative_base():
return Base


@pytest.fixture(scope="function")
def sqlalchemy_mock_config():
return [
("user", MockData.USER_DATA),
("department", MockData.DEPARTMENT_DATA),
("user_department", MockData.USER_DEPARTMENT_DATA),
]
Loading

0 comments on commit 2374aec

Please sign in to comment.