From 42b56a9fa4f9d7d8cb2cd9d05600dc1bd8082d05 Mon Sep 17 00:00:00 2001 From: Teng Huang Date: Fri, 6 Dec 2024 11:31:26 +0800 Subject: [PATCH] Release 0.0.9: add decorator. --- example_decorator.py | 45 +++++++ mybatis/mybatis.py | 139 ++++++++++++++++++++ setup.py | 2 +- test/test_mybatis.py | 2 +- test/test_mybatis_decorator.py | 225 +++++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 example_decorator.py create mode 100644 test/test_mybatis_decorator.py diff --git a/example_decorator.py b/example_decorator.py new file mode 100644 index 0000000..ff0293e --- /dev/null +++ b/example_decorator.py @@ -0,0 +1,45 @@ +import mysql.connector +from mybatis import Mybatis + +conn = mysql.connector.connect( + host="localhost", # MySQL 主机地址 + user="mybatis", # MySQL 用户名 + password="mybatis", # MySQL 密码 + database="mybatis", # 需要连接的数据库, +) + +mb = Mybatis(conn, "mapper", cache_memory_limit=50*1024*1024) + +@mb.SelectOne("SELECT * FROM fruits WHERE id=#{id}") +def get_one(id:int): + pass + +@mb.SelectMany("SELECT * FROM fruits") +def get_many(): + pass + +@mb.Insert("INSERT INTO fruits (name, category, price) VALUES (#{name}, #{category}, #{price})") +def insert(): + pass + +@mb.Delete("DELETE FROM fruits WHERE id=#{id}") +def delete(id:int): + pass + +@mb.Update("UPDATE fruits SET name='Amazon' WHERE id=#{id}") +def update(id:int): + pass + +print(get_one(id=1)) + +print(delete(id=4)) + +print(get_many()) + +print(insert(name="Dating", category="D", price=20)) + +print(get_many()) + +print(update(id=1)) + +print(get_many()) \ No newline at end of file diff --git a/mybatis/mybatis.py b/mybatis/mybatis.py index e8ca2b8..7a4214d 100644 --- a/mybatis/mybatis.py +++ b/mybatis/mybatis.py @@ -61,8 +61,12 @@ def select_many(self, id:str, params:dict) -> Optional[List[Dict]]: d[item] = row[idx] res_list.append(d) + if len(res_list) == 0: + res_list = None + if self.cache is not None: self.cache.put(CacheKey(sql, param_list), res_list) + return res_list def update(self, id:str, params:dict) -> int: @@ -112,3 +116,138 @@ def insert(self, id:str, params:dict) -> int: self.conn.commit() last_id = cursor.lastrowid return last_id + + + def SelectOne(self, unparsed_sql:str) -> Optional[Dict]: + def decorator(func): + def wrapper(*args, **kwargs): + params = {} + for key, value in kwargs.items(): + params[key] = value + + sql, param_list = self.mapper_manager._to_prepared_statement(unparsed_sql, params) + sql = self.mapper_manager._to_replace(sql, params) + + if self.cache is not None: + res = self.cache.get(CacheKey(sql, param_list)) + if res is not None: + return res + + with self.conn.cursor(prepared=True) as cursor: + cursor.execute(sql, param_list) + ret = cursor.fetchone() + if ret is None: + return None + + column_name = [item[0] for item in cursor.description] + res = {} + for idx, item in enumerate(column_name): + res[item] = ret[idx] + + if self.cache is not None: + self.cache.put(CacheKey(sql, param_list), res) + + return res + + return wrapper + return decorator + + def SelectMany(self, unparsed_sql:str) -> Optional[List[Dict]]: + def decorator(func): + def wrapper(*args, **kwargs): + params = {} + for key, value in kwargs.items(): + params[key] = value + + sql, param_list = self.mapper_manager._to_prepared_statement(unparsed_sql, params) + sql = self.mapper_manager._to_replace(sql, params) + + if self.cache is not None: + res = self.cache.get(CacheKey(sql, param_list)) + if res is not None: + return res + + with self.conn.cursor(prepared=True) as cursor: + cursor.execute(sql, param_list) + + res_list = [] + column_name = [item[0] for item in cursor.description] + + ret = cursor.fetchall() + for row in ret: + d = {} + for idx, item in enumerate(column_name): + d[item] = row[idx] + res_list.append(d) + + if len(res_list) == 0: + res_list = None + + if self.cache is not None: + self.cache.put(CacheKey(sql, param_list), res_list) + + return res_list + return wrapper + return decorator + + def Insert(self, unparsed_sql:str) -> int: + def decorator(func): + def wrapper(*args, **kwargs): + params = {} + for key, value in kwargs.items(): + params[key] = value + + sql, param_list = self.mapper_manager._to_prepared_statement(unparsed_sql, params) + sql = self.mapper_manager._to_replace(sql, params) + + res = self.cache.clear() + + with self.conn.cursor(prepared=True) as cursor: + cursor.execute(sql, param_list) + self.conn.commit() + last_id = cursor.lastrowid + return last_id + return wrapper + return decorator + + def Delete(self, unparsed_sql:str) -> int: + def decorator(func): + def wrapper(*args, **kwargs): + params = {} + for key, value in kwargs.items(): + params[key] = value + + sql, param_list = self.mapper_manager._to_prepared_statement(unparsed_sql, params) + sql = self.mapper_manager._to_replace(sql, params) + + res = self.cache.clear() + + with self.conn.cursor(prepared=True) as cursor: + cursor.execute(sql, param_list) + affected_rows = cursor.rowcount + self.conn.commit() + return affected_rows + return wrapper + return decorator + + def Update(self, unparsed_sql:str) -> int: + def decorator(func): + def wrapper(*args, **kwargs): + params = {} + for key, value in kwargs.items(): + params[key] = value + + sql, param_list = self.mapper_manager._to_prepared_statement(unparsed_sql, params) + sql = self.mapper_manager._to_replace(sql, params) + + res = self.cache.clear() + + with self.conn.cursor(prepared=True) as cursor: + cursor.execute(sql, param_list) + affected_rows = cursor.rowcount + self.conn.commit() + return affected_rows + + return wrapper + + return decorator \ No newline at end of file diff --git a/setup.py b/setup.py index 500dcd5..44f2b19 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='mybatis', - version='0.0.8', + version='0.0.9', description='A python ORM like mybatis.', long_description=open('README.md').read(), long_description_content_type='text/markdown', # 如果你使用的是Markdown格式的README diff --git a/test/test_mybatis.py b/test/test_mybatis.py index c1079b1..d7ee4e6 100644 --- a/test/test_mybatis.py +++ b/test/test_mybatis.py @@ -89,7 +89,7 @@ def test_select_many(db_connection): def test_select_many_none(db_connection): mb = Mybatis(db_connection, "mapper") ret = mb.select_many('testBasicNone', {}) - assert len(ret) == 0 + assert ret is None def test_update(db_connection): mb = Mybatis(db_connection, "mapper") diff --git a/test/test_mybatis_decorator.py b/test/test_mybatis_decorator.py new file mode 100644 index 0000000..891796d --- /dev/null +++ b/test/test_mybatis_decorator.py @@ -0,0 +1,225 @@ +import pytest +import mysql.connector + +from mybatis import Mybatis + + +@pytest.fixture(scope="function") +def db_connection(): + # 配置数据库连接 + connection = mysql.connector.connect( + host="localhost", + user="mybatis", + password="mybatis", + database="mybatis" + ) + cursor = connection.cursor() + cursor.execute("DROP TABLE IF EXISTS fruits") + create_table_sql = '''CREATE TABLE IF NOT EXISTS fruits ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + category VARCHAR(100), + price int) + ''' + # 在测试开始前准备数据 + cursor.execute(create_table_sql) + cursor.execute("INSERT INTO fruits (name, category, price) VALUES ('Alice', 'A', 100)") + cursor.execute("INSERT INTO fruits (name, category, price) VALUES ('Bob', 'B', 200)") + connection.commit() + + # 提供数据库连接给测试用例 + yield connection + + # 清理数据和关闭连接 + connection.close() + +def test_select_one(db_connection): + mb = Mybatis(db_connection, "mapper", cache_memory_limit=50*1024*1024) + + @mb.SelectOne("SELECT * FROM fruits WHERE id=#{id}") + def select_one(id:int): + pass + + ret = select_one(id=1) + + assert ret is not None + assert len(ret) == 4 + assert ret['id'] == 1 + assert ret['name'] == 'Alice' + assert ret['category'] == 'A' + assert ret['price'] == 100 + + ret = mb.select_one('testBasic', {}) + assert ret is not None + assert len(ret) == 4 + assert ret['id'] == 1 + assert ret['name'] == 'Alice' + assert ret['category'] == 'A' + assert ret['price'] == 100 + +def test_select_one_none(db_connection): + mb = Mybatis(db_connection, "mapper") + + @mb.SelectOne("SELECT * FROM fruits WHERE id=#{id}") + def select_one(id: int): + pass + + ret = select_one(id=3) + assert ret is None + +def test_select_many(db_connection): + mb = Mybatis(db_connection, "mapper", cache_memory_limit=50*1024*1024) + @mb.SelectMany("SELECT * FROM fruits") + def select_many(): + pass + + ret = select_many() + assert ret is not None + assert isinstance(ret, list) + assert len(ret) == 2 + assert ret[0]['id'] == 1 + assert ret[0]['name'] == 'Alice' + assert ret[0]['category'] == 'A' + assert ret[0]['price'] == 100 + assert ret[1]['id'] == 2 + assert ret[1]['name'] == 'Bob' + assert ret[1]['category'] == 'B' + assert ret[1]['price'] == 200 + + +def test_select_many_none(db_connection): + mb = Mybatis(db_connection, "mapper") + @mb.SelectMany("SELECT * FROM fruits WHERE id=5") + def select_many(): + pass + + ret = select_many() + assert ret is None + + +def test_update(db_connection): + mb = Mybatis(db_connection, "mapper") + + @mb.SelectMany("SELECT * FROM fruits") + def select_many(): + pass + + @mb.Update("UPDATE fruits SET name=#{name} WHERE id=#{id}") + def update(name:str, id:int): + pass + + assert mb.cache.empty() is True + + ret = update(name="Candy", id=2) + + assert mb.cache.empty() is True + + assert ret == 1 + ret = select_many() + assert ret is not None + assert isinstance(ret, list) + assert len(ret) == 2 + assert ret[0]['id'] == 1 + assert ret[0]['name'] == 'Alice' + assert ret[0]['category'] == 'A' + assert ret[0]['price'] == 100 + assert ret[1]['id'] == 2 + assert ret[1]['name'] == 'Candy' + assert ret[1]['category'] == 'B' + assert ret[1]['price'] == 200 + + assert mb.cache.empty() is True + +def test_update_with_cache(db_connection): + mb = Mybatis(db_connection, "mapper", cache_memory_limit=50*1024*1024) + + @mb.SelectMany("SELECT * FROM fruits") + def select_many(): + pass + + @mb.SelectOne("SELECT * FROM fruits WHERE id=1") + def select_one(): + pass + + @mb.Update("UPDATE fruits SET name=#{name} WHERE id=#{id}") + def update(name: str, id: int): + pass + + select_one() + + assert mb.cache.empty() is False + + ret = update(name="Candy", id=2) + + assert mb.cache.empty() is True + + assert ret == 1 + ret = select_many() + assert ret is not None + assert isinstance(ret, list) + assert len(ret) == 2 + assert ret[0]['id'] == 1 + assert ret[0]['name'] == 'Alice' + assert ret[0]['category'] == 'A' + assert ret[0]['price'] == 100 + assert ret[1]['id'] == 2 + assert ret[1]['name'] == 'Candy' + assert ret[1]['category'] == 'B' + assert ret[1]['price'] == 200 + + assert mb.cache.empty() is False + +def test_delete(db_connection): + mb = Mybatis(db_connection, "mapper") + + @mb.SelectMany("SELECT * FROM fruits") + def select_many(): + pass + + @mb.Delete("DELETE FROM fruits WHERE id=#{id}") + def delete(id:int): + pass + + ret = delete(id=2) + + assert ret == 1 + ret = select_many() + assert ret is not None + assert isinstance(ret, list) + assert len(ret) == 1 + assert ret[0]['id'] == 1 + assert ret[0]['name'] == 'Alice' + assert ret[0]['category'] == 'A' + assert ret[0]['price'] == 100 + +def test_insert(db_connection): + mb = Mybatis(db_connection, "mapper") + + @mb.SelectMany("SELECT * FROM fruits") + def select_many(): + pass + + @mb.Insert("INSERT INTO fruits (name, category, price) VALUES (#{name}, #{category}, #{price})") + def insert(name:str, category:str, price:int): + pass + + ret = insert(name="Candy", category="B", price=200) + + assert ret == 3 + + ret = select_many() + assert ret is not None + assert isinstance(ret, list) + assert len(ret) == 3 + assert ret[0]['id'] == 1 + assert ret[0]['name'] == 'Alice' + assert ret[0]['category'] == 'A' + assert ret[0]['price'] == 100 + assert ret[1]['id'] == 2 + assert ret[1]['name'] == 'Bob' + assert ret[1]['category'] == 'B' + assert ret[1]['price'] == 200 + assert ret[2]['id'] == 3 + assert ret[2]['name'] == 'Candy' + assert ret[2]['category'] == 'B' + assert ret[2]['price'] == 200 \ No newline at end of file