Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
.vscode

__pycache__

build
dist
fastapi_efficient_sql.egg-info

Pipfile.lock
Pipfile.lock
poetry.lock
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.7.9
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 BryanLee
Copyright (c) 2022-2023 BryanLee

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
22 changes: 0 additions & 22 deletions Pipfile

This file was deleted.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Installed as package by `pip install fastapi-efficient-sql`

Install developing requirements by `pipenv install --skip-lock --dev` or `pip install -r requirements-dev.txt`
Install developing requirements by `pyenv local 3.7.9`, `poetry env use 3.7.9`, `poetry shell` and `pip install -r requirements-dev.txt`

Run demo service by `python -m examples.service`

Run unittest by `pytest -sv`

Expand Down
7 changes: 3 additions & 4 deletions examples/service/models/demo/account.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from fastapi_esql import BaseModel
from tortoise import fields
from tortoise.contrib.pydantic import pydantic_model_creator

from examples.service.constants.enums import GenderEnum, LocaleEnum

Expand All @@ -13,6 +12,9 @@ class Account(BaseModel):
`gender` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:unknown, 1:male, 2:female',
`name` varchar(32) NOT NULL DEFAULT '',
`locale` varchar(5) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`extend` json NOT NULL,
PRIMARY KEY (`id`),
KEY `created_at-idx` (`created_at`),
KEY `updated_at-idx` (`updated_at`)
Expand All @@ -27,6 +29,3 @@ class Account(BaseModel):
class Meta:
app = "demo"
table = "account"


AccountIn = pydantic_model_creator(Account, name="AccountIn", exclude=("active"), exclude_readonly=True)
28 changes: 16 additions & 12 deletions fastapi_esql/utils/sqlizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def resolve_wheres(
model: Optional[Model] = None,
) -> str:
if not model and not isinstance(wheres, str):
raise WrongParamsError("Parameter `wheres` only support str type if no model passed")
raise WrongParamsError("Parameter `wheres` only supports str if no model exists")

if isinstance(wheres, str):
return wheres
Expand All @@ -61,10 +61,10 @@ def resolve_wheres(
elif isinstance(wheres, list):
qs = [q for q in wheres if isinstance(q, Q)]
else:
raise WrongParamsError("Parameter `wheres` only supports str, dict and list type")
raise WrongParamsError("Parameter `wheres` only support str, Q, Dict[str, Any] and List[Q]")

if not qs:
raise QsParsingError("Parsing `wheres` for qs failed!")
raise QsParsingError("Parsing `wheres` for QuerySet failed")

modifier = QueryModifier()
for q in qs:
Expand Down Expand Up @@ -122,9 +122,9 @@ def select_custom_fields(
model: Optional[Model] = None,
) -> Optional[str]:
if not all([table, fields, wheres]):
raise WrongParamsError("Please check your params")
raise WrongParamsError("Parameters `table`, `fields`, `wheres` are required")
if having and not groups:
raise WrongParamsError("Parameter `groups` shoud be no empty when `having` isn't")
raise WrongParamsError("Parameter `groups` shoud not be empty if `having` exists")

group_by = f" GROUP BY {', '.join(groups)}" if groups else ""
having_ = f" HAVING {having}" if having else ""
Expand Down Expand Up @@ -160,9 +160,11 @@ def update_json_field(
model: Optional[Model] = None,
) -> Optional[str]:
if not all([table, json_field, wheres]):
raise WrongParamsError("Please check your params")
raise WrongParamsError("Parameters `table`, `json_field`, `wheres` are required")
if not any([merge_dict, path_value_dict, remove_paths]):
raise WrongParamsError("Please check your params")
raise WrongParamsError(
"At least one no empty parameter is required between `merge_dict`, `path_value_dict` and `remove_paths`"
)

json_obj = f"COALESCE({json_field}, '{json_type()}')"
if remove_paths:
Expand Down Expand Up @@ -195,7 +197,7 @@ def upsert_on_duplicate(
using_values: bool = False,
) -> Optional[str]:
if not all([table, dicts, insert_fields, upsert_fields]):
raise WrongParamsError("Please check your params")
raise WrongParamsError("Parameters `table`, `dicts`, `insert_fields`, `upsert_fields` are required")

values = [
f" ({', '.join(cls.sqlize_value(d.get(f)) for f in insert_fields)})"
Expand Down Expand Up @@ -236,8 +238,10 @@ def insert_into_select(
to_table: Optional[str] = None,
model: Optional[Model] = None,
) -> Optional[str]:
if not all([table, wheres] or not any([remain_fields, assign_field_dict])):
raise WrongParamsError("Please check your params")
if not all([table, wheres]):
raise WrongParamsError("Parameters `table`, `wheres` are required")
if not any([remain_fields, assign_field_dict]):
raise WrongParamsError("At least one no empty parameter is required between `remain_fields` and `assign_field_dict`")

fields = [*remain_fields]
assign_fields = []
Expand All @@ -263,7 +267,7 @@ def build_fly_table(
using_values: bool = True,
) -> Optional[str]:
if not all([dicts, fields]):
raise WrongParamsError("Please check your params")
raise WrongParamsError("Parameters `dicts`, `fields` are required")

if using_values:
rows = [
Expand Down Expand Up @@ -297,7 +301,7 @@ def bulk_update_from_dicts(
using_values: bool = True,
) -> Optional[str]:
if not all([table, dicts, join_fields, update_fields]):
raise WrongParamsError("Please check your params")
raise WrongParamsError("Parameters `table`, `dicts`, `join_fields`, `update_fields` are required")

joins = [f"{table}.{jf}=tmp.{jf}" for jf in join_fields]
updates = [f"{table}.{uf}=tmp.{uf}" for uf in update_fields]
Expand Down
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool.poetry]
name = "fastapi-efficient-sql"
version = "0.0.8"
description = "Generate bulk DML SQL and execute them based on Tortoise ORM and mysql8.0+, and integrated with FastAPI."
authors = ["BryanLee <bryanlee@126.com>"]
license = "MIT"
readme = "README.md"
packages = [{include = "fastapi_esql"}]

[tool.poetry.dependencies]
python = "^3.7"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
3 changes: 1 addition & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
aiomysql
asynctest
autopep8
cryptography
faker
fastapi
pytest
pytest-asyncio
tortoise-orm
tortoise-orm>=0.16.17
uvicorn
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
tortoise-orm
tortoise-orm>=0.16.17
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def read_requirements(filename):
version=fastapi_esql.__version__,
author="BryanLee",
author_email="bryanlee@126.com",
description="Generate bulk DML SQL and execute them based on tortoise-orm and mysql8.0+, and integrated with fastapi.",
description="Generate bulk DML SQL and execute them based on Tortoise ORM and mysql8.0+, and integrated with FastAPI.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/NightMarcher/fastapi-efficient-sql",
Expand Down
9 changes: 2 additions & 7 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import asyncio

from tortoise import Tortoise

TEST_CONN = "test"


def init_test_orm():
async def init_tortoise():
"""
CREATE USER 'demo_test'@'localhost' IDENTIFIED BY 'demo_TEST#0';
GRANT ALL ON demo.* TO 'demo_test'@'localhost';
Expand Down Expand Up @@ -33,11 +31,8 @@ def init_test_orm():
}
}
}
asyncio.run(Tortoise.init(config=config))
await Tortoise.init(config=config)


def get_test_conn():
return Tortoise.get_connection(TEST_CONN)


init_test_orm()
26 changes: 15 additions & 11 deletions tests/test_cursor_handler.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
from logging import getLogger

from asynctest import CoroutineMock, TestCase, patch
from asynctest import TestCase, patch

from . import get_test_conn
from . import get_test_conn, init_tortoise
from fastapi_esql import CursorHandler

logger = getLogger(__name__)


class TestCursorHandler(TestCase):

conn = get_test_conn()
use_default_loop = True

async def setUp(self):
await init_tortoise()
self.conn = get_test_conn()

async def test_fetch_dicts(self):
with patch(
"tortoise.backends.mysql.client_class.execute_query_dict",
# new=CoroutineMock()
) as mock_exec:
mock_exec.side_effect = Exception("Error")
assert await CursorHandler.fetch_dicts("SELECT true", self.conn, logger) is None
result = await CursorHandler.fetch_dicts("SELECT 1 idx", self.conn, logger)
print(f"result => {result}")
assert result

@patch("tortoise.backends.mysql.client_class.execute_query")
async def test_sum_row_cnt(self, mock_exec):
mock_exec.return_value = 10, object()
assert await CursorHandler.sum_row_cnt("SELECT true", self.conn, logger) == 10

async def test_exec_if_ok(self):
# True means test connection is reachable
assert await CursorHandler.exec_if_ok("SELECT true", self.conn, logger) == True
with patch(
"tortoise.backends.mysql.client_class.execute_script",
) as mock_exec:
mock_exec.side_effect = Exception("Error")
assert await CursorHandler.exec_if_ok("SELECT true", self.conn, logger) == False
2 changes: 2 additions & 0 deletions tests/test_metaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ class TestSingleton(TestCase):

def test_inheritance(self):
c0, c1 = Child(), Child()
print(c0, c1)
assert c0 is c1

def test_parent_and_child(self):
p0, p1 = Parent(), Parent()
c = Child()
print(p0, p1, c)
assert p0 is p1
assert p0 is not c
7 changes: 6 additions & 1 deletion tests/test_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ class MyModel(BaseModel):
class AccountMgr(BaseManager, metaclass=DemoMetaclass):
model = MyModel

def test_no_conn(self):
def test_ro_conn(self):
class AccountMgr(BaseManager, metaclass=DemoMetaclass):
model = Account
AccountMgr.ro_conn

def test_no_rw_conn(self):
with self.assertRaises(NotImplementedError):
class AccountMgr(BaseManager, metaclass=DemoMetaclass):
model = Account
Expand Down
7 changes: 6 additions & 1 deletion tests/test_sqlizer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from asynctest import TestCase
from datetime import datetime
from unittest import TestCase

from tortoise import run_async

from examples.service.models.demo import Account
from examples.service.constants.enums import GenderEnum, LocaleEnum
from fastapi_esql import (
Cases, RawSQL, SQLizer,
Q, QsParsingError, WrongParamsError,
)
from . import init_tortoise

run_async(init_tortoise())


class TestRawSQL(TestCase):
Expand Down