From 4bf38a17f6f59e6f4c2cca08ffe72a952821e6e2 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 14:16:30 +0800 Subject: [PATCH 1/9] [chore] easter egg! --- MercurySQL/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MercurySQL/__init__.py b/MercurySQL/__init__.py index ecf7ca0..bdfbf62 100644 --- a/MercurySQL/__init__.py +++ b/MercurySQL/__init__.py @@ -17,3 +17,10 @@ from .gensql import DataBase, Table, set_driver from . import drivers + +class SQL: + """Wrap everything together""" + DataBase = DataBase + Table = Table + set_driver = set_driver + drivers = drivers From b7d0e5a8ec92a4174e91570cc314b78ef704f46e Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:37:01 +0800 Subject: [PATCH 2/9] [fix] gitignore bug --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cfc5d5e..7701a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode/* */__pycache__/* +__pycache__ *.pyc __local/* *.db @@ -11,3 +12,4 @@ MercurySQL.egg-info/* runtime runtime/* *.test.py + From 8fc8cd37c18ce77f96c1cda696492365cc0f8d45 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:47:46 +0800 Subject: [PATCH 3/9] [refactor] rename mysql to mysql_driver to avoid bugs --- MercurySQL/drivers/mysql.py | 227 ------------------------------------ 1 file changed, 227 deletions(-) delete mode 100644 MercurySQL/drivers/mysql.py diff --git a/MercurySQL/drivers/mysql.py b/MercurySQL/drivers/mysql.py deleted file mode 100644 index 6dd9b6b..0000000 --- a/MercurySQL/drivers/mysql.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -Requirements: - - mysql-connector-python -""" -from .base import BaseDriver - -import mysql.connector -from typing import Any, List - - -class Driver_MySQL(BaseDriver): - pass - - -class Driver_MySQL(BaseDriver): - """ - .. note:: - Supported MySQL Version: **8.2** - """ - dependencies = ['mysql-connector-python'] - version = '0.1.0' - payload = '%s' - - Conn = mysql.connector.MySQLConnection - Cursor = mysql.connector.cursor_cext.CMySQLCursor - - class APIs: - class gensql: - @staticmethod - def drop_table(table_name: str) -> str: - return f"DROP TABLE {table_name};" - - @staticmethod - def get_all_tables() -> str: - return "SHOW TABLES;" - - @staticmethod - def get_all_columns(table_name: str) -> str: - return f"DESCRIBE `{table_name}`;" - - @staticmethod - def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, primaryKey=False, autoIncrement=False, engine='', charset='') -> str: - return f""" - CREATE TABLE IF NOT EXISTS `{table_name}` ( - `{column_name}` {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTO_INCREMENT' if autoIncrement else ''} - ) {engine} {f'DEFAULT CHARSET={charset}' if charset else ''}; - """ - - @staticmethod - def add_column(table_name: str, column_name: str, column_type: str) -> str: - return f"ALTER TABLE `{table_name}` ADD COLUMN `{column_name}` {column_type};" - - @staticmethod - def drop_column(table_name: str, column_name: str) -> str: - return f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;" - - @staticmethod - def set_primary_key(table, keyname: str, keytype: str) -> list: - return [ - f"ALTER TABLE `{table.table_name}` DROP PRIMARY KEY;", - f"ALTER TABLE `{table.table_name}` ADD PRIMARY KEY (`{keyname}`);" - ] - - @staticmethod - def insert(table_name: str, columns: str, values: str) -> str: - return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values});" - - @staticmethod - def insert_or_update(table_name: str, columns: str, values: str) -> str: - update_columns = ', '.join(f'{col}=VALUES({col})' for col in columns.split(', ')) - return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values}) ON DUPLICATE KEY UPDATE {update_columns};" - - @staticmethod - def update(table_name: str, columns: str, condition: str) -> str: - return f"UPDATE `{table_name}` SET {columns} WHERE {condition};" - - @staticmethod - def query(table_name: str, selection: str, condition: str) -> str: - return f"SELECT {selection} FROM `{table_name}` WHERE {condition};" - - @staticmethod - def delete(table_name: str, condition: str) -> str: - return f"DELETE FROM `{table_name}` WHERE {condition};" - - @classmethod - def get_all_tables(cls, conn) -> List[str]: - cursor = conn.cursor() - cursor.execute(cls.gensql.get_all_tables()) - return list(map(lambda x: x[0], cursor.fetchall())) - - @classmethod - def get_all_columns(cls, conn, table_name: str) -> List[str]: - cursor = conn.cursor() - cursor.execute(cls.gensql.get_all_columns(table_name)) - # [name, type, null, key, default, extra] - return list(map(lambda x: [x[0], x[1], x[2], x[3], x[4], x[5]], cursor.fetchall())) - - class TypeParser: - """ - Parse the type from `Python Type` -> `MySQL Type`. - """ - - @staticmethod - def parse(type_: Any) -> str: - """ - Compile the type to MySQL type. - - :param type_: The type to parse. - :type type_: Any - - :return: The MySQL type. - :rtype: str - - +-------------------+-------------+ - | Supported Types | SQLite Type | - +===================+=============+ - | bool | BOOLEAN | - +-------------------+-------------+ - | int | INTEGER | - +-------------------+-------------+ - | float | FLOAT | - +-------------------+-------------+ - | str | VARCHAR(225)| - +-------------------+-------------+ - | bytes | BLOB | - +-------------------+-------------+ - | datetime.datetime | DATETIME | - +-------------------+-------------+ - | datetime.date | DATE | - +-------------------+-------------+ - | datetime.time | TIME | - +-------------------+-------------+ - - - Example Usage: - - .. code-block:: python - - TypeParser.parse(int(10)) # INT DEFAULT 10 - TypeParser.parse(str) # VARCHAR - TypeParser.parse(float(1.3)) # FLOAT DEFAULT 1.3 - TypeParser.parse('\t DOUBLE DEFAULT 1.23') # DOUBLE DEFAULT 1.23 - ... - - """ - import datetime - - supported_types = { - bool: 'BOOLEAN', - int: 'INT', - float: 'FLOAT', - str: 'VARCHAR(225)', - bytes: 'BLOB', - - datetime.datetime: 'DATETIME', - datetime.date: 'DATE', - datetime.time: 'TIME', - } - - if not isinstance(type_, tuple): - type_ = (type_,) - - res = "" - - # round 1: Built-in Types - if type_[0] in supported_types: - res = supported_types[type_[0]] - - # round 2: Custom Types - if res == "" and isinstance(type_[0], str) and type_[0].startswith('\t'): # custom type - return type_[0].strip() - - # round 3: Built-in Types With Default Value - if type(type_[0]) != type: - if isinstance(type_[0], bytes): - type_[0] = type_[0].decode() - res = f"{supported_types[type(type_[0])]} DEFAULT {type_[0]}" - - # round 4: Not Null - for i in range(1, len(type_)): - if type_[i] == 'not null': - res += ' NOT NULL' - else: - res += f" DFAULT {type_[i]}" - - # Not Supported - if res == "": - raise TypeError(f"Type `{str(type_)}` is not supported.") - - return res - - @staticmethod - def connect(db_name: str, host: str, user: str, passwd: str = '', force=False) -> Conn: - """ - Connect to a MySQL database. - """ - if force: - return mysql.connector.connect( - host=host, - user=user, - passwd=passwd, - database=db_name - ) - else: - conn = mysql.connector.connect( - host=host, - user=user, - passwd=passwd - ) - conn.backup_cursor = conn.cursor - - def cursor(): - """ Create Database if not exists. """ - conn.cursor = conn.backup_cursor - - c = conn.cursor() - - try: - c.execute(f'USE {db_name};') - except mysql.connector.errors.ProgrammingError: - c.execute(f'CREATE DATABASE {db_name};') - c.execute(f'USE {db_name};') - - return c - - conn.cursor = cursor - return conn From b66f1b31cb4f6b8cc89d9144be43fa8f8cc6cd91 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:48:25 +0800 Subject: [PATCH 4/9] [style] change param "passwd" to "password" in connect funtion --- MercurySQL/drivers/mysql_driver.py | 228 +++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 MercurySQL/drivers/mysql_driver.py diff --git a/MercurySQL/drivers/mysql_driver.py b/MercurySQL/drivers/mysql_driver.py new file mode 100644 index 0000000..2e8ac38 --- /dev/null +++ b/MercurySQL/drivers/mysql_driver.py @@ -0,0 +1,228 @@ +""" +Requirements: + - mysql-connector-python +""" +from .base import BaseDriver + +import mysql.connector +from typing import Any, List + + +class Driver_MySQL(BaseDriver): + pass + + +class Driver_MySQL(BaseDriver): + """ + .. note:: + Supported MySQL Version: **8.2** + """ + dependencies = ['mysql-connector-python'] + version = '0.1.0' + payload = '%s' + + Conn = mysql.connector.MySQLConnection + # Cursor = mysql.connector.cursor_cext.CMySQLCursor + Cursor = mysql.connector.cursor.MySQLCursor + + class APIs: + class gensql: + @staticmethod + def drop_table(table_name: str) -> str: + return f"DROP TABLE {table_name};" + + @staticmethod + def get_all_tables() -> str: + return "SHOW TABLES;" + + @staticmethod + def get_all_columns(table_name: str) -> str: + return f"DESCRIBE `{table_name}`;" + + @staticmethod + def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, primaryKey=False, autoIncrement=False, engine='', charset='') -> str: + return f""" + CREATE TABLE IF NOT EXISTS `{table_name}` ( + `{column_name}` {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTO_INCREMENT' if autoIncrement else ''} + ) {engine} {f'DEFAULT CHARSET={charset}' if charset else ''}; + """ + + @staticmethod + def add_column(table_name: str, column_name: str, column_type: str) -> str: + return f"ALTER TABLE `{table_name}` ADD COLUMN `{column_name}` {column_type};" + + @staticmethod + def drop_column(table_name: str, column_name: str) -> str: + return f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;" + + @staticmethod + def set_primary_key(table, keyname: str, keytype: str) -> list: + return [ + f"ALTER TABLE `{table.table_name}` DROP PRIMARY KEY;", + f"ALTER TABLE `{table.table_name}` ADD PRIMARY KEY (`{keyname}`);" + ] + + @staticmethod + def insert(table_name: str, columns: str, values: str) -> str: + return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values});" + + @staticmethod + def insert_or_update(table_name: str, columns: str, values: str) -> str: + update_columns = ', '.join(f'{col}=VALUES({col})' for col in columns.split(', ')) + return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values}) ON DUPLICATE KEY UPDATE {update_columns};" + + @staticmethod + def update(table_name: str, columns: str, condition: str) -> str: + return f"UPDATE `{table_name}` SET {columns} WHERE {condition};" + + @staticmethod + def query(table_name: str, selection: str, condition: str) -> str: + return f"SELECT {selection} FROM `{table_name}` WHERE {condition};" + + @staticmethod + def delete(table_name: str, condition: str) -> str: + return f"DELETE FROM `{table_name}` WHERE {condition};" + + @classmethod + def get_all_tables(cls, conn) -> List[str]: + cursor = conn.cursor() + cursor.execute(cls.gensql.get_all_tables()) + return list(map(lambda x: x[0], cursor.fetchall())) + + @classmethod + def get_all_columns(cls, conn, table_name: str) -> List[str]: + cursor = conn.cursor() + cursor.execute(cls.gensql.get_all_columns(table_name)) + # [name, type, null, key, default, extra] + return list(map(lambda x: [x[0], x[1], x[2], x[3], x[4], x[5]], cursor.fetchall())) + + class TypeParser: + """ + Parse the type from `Python Type` -> `MySQL Type`. + """ + + @staticmethod + def parse(type_: Any) -> str: + """ + Compile the type to MySQL type. + + :param type_: The type to parse. + :type type_: Any + + :return: The MySQL type. + :rtype: str + + +-------------------+-------------+ + | Supported Types | SQLite Type | + +===================+=============+ + | bool | BOOLEAN | + +-------------------+-------------+ + | int | INTEGER | + +-------------------+-------------+ + | float | FLOAT | + +-------------------+-------------+ + | str | VARCHAR(225)| + +-------------------+-------------+ + | bytes | BLOB | + +-------------------+-------------+ + | datetime.datetime | DATETIME | + +-------------------+-------------+ + | datetime.date | DATE | + +-------------------+-------------+ + | datetime.time | TIME | + +-------------------+-------------+ + + + Example Usage: + + .. code-block:: python + + TypeParser.parse(int(10)) # INT DEFAULT 10 + TypeParser.parse(str) # VARCHAR + TypeParser.parse(float(1.3)) # FLOAT DEFAULT 1.3 + TypeParser.parse('\t DOUBLE DEFAULT 1.23') # DOUBLE DEFAULT 1.23 + ... + + """ + import datetime + + supported_types = { + bool: 'BOOLEAN', + int: 'INT', + float: 'FLOAT', + str: 'VARCHAR(225)', + bytes: 'BLOB', + + datetime.datetime: 'DATETIME', + datetime.date: 'DATE', + datetime.time: 'TIME', + } + + if not isinstance(type_, tuple): + type_ = (type_,) + + res = "" + + # round 1: Built-in Types + if type_[0] in supported_types: + res = supported_types[type_[0]] + + # round 2: Custom Types + if res == "" and isinstance(type_[0], str) and type_[0].startswith('\t'): # custom type + return type_[0].strip() + + # round 3: Built-in Types With Default Value + if type(type_[0]) != type: + if isinstance(type_[0], bytes): + type_[0] = type_[0].decode() + res = f"{supported_types[type(type_[0])]} DEFAULT {type_[0]}" + + # round 4: Not Null + for i in range(1, len(type_)): + if type_[i] == 'not null': + res += ' NOT NULL' + else: + res += f" DFAULT {type_[i]}" + + # Not Supported + if res == "": + raise TypeError(f"Type `{str(type_)}` is not supported.") + + return res + + @staticmethod + def connect(db_name: str, host: str, user: str, password: str = '', force=False) -> Conn: + """ + Connect to a MySQL database. + """ + if force: + return mysql.connector.connect( + host=host, + user=user, + passwd=password, + database=db_name + ) + else: + conn = mysql.connector.connect( + host=host, + user=user, + passwd=password + ) + conn.backup_cursor = conn.cursor + + def cursor(): + """ Create Database if not exists. """ + conn.cursor = conn.backup_cursor + + c = conn.cursor() + + try: + c.execute(f'USE {db_name};') + except mysql.connector.errors.ProgrammingError: + c.execute(f'CREATE DATABASE {db_name};') + c.execute(f'USE {db_name};') + + return c + + conn.cursor = cursor + return conn From 891b89cf37af4ae53123a97aeb62d5ce6f5e0676 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:48:58 +0800 Subject: [PATCH 5/9] [fix] to avoid some special bugs, we set default driver to sqlite --- MercurySQL/gensql/database.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MercurySQL/gensql/database.py b/MercurySQL/gensql/database.py index c969e61..d318f22 100644 --- a/MercurySQL/gensql/database.py +++ b/MercurySQL/gensql/database.py @@ -22,6 +22,9 @@ from .table import Table +from ..drivers import sqlite + + # ========== Class Decorations ========== class DataBase: pass @@ -37,7 +40,8 @@ class DataBase: # ========= Tool Functions ========= -default_driver = None +default_driver = sqlite +# Our default driver is sqlite def set_driver(driver): From 7164d6a8b079151c0f9a4fce5f0090389d9fd8cd Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:08:40 +0800 Subject: [PATCH 6/9] [fix] edit bug caused by default driver --- MercurySQL/gensql/database.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MercurySQL/gensql/database.py b/MercurySQL/gensql/database.py index d318f22..0292917 100644 --- a/MercurySQL/gensql/database.py +++ b/MercurySQL/gensql/database.py @@ -40,8 +40,7 @@ class DataBase: # ========= Tool Functions ========= -default_driver = sqlite -# Our default driver is sqlite +default_driver = None def set_driver(driver): From 93b65f1def384d5878efc010e60500f0857d056a Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:09:53 +0800 Subject: [PATCH 7/9] [chore] a small update --- MercurySQL/drivers/mysql_driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MercurySQL/drivers/mysql_driver.py b/MercurySQL/drivers/mysql_driver.py index 2e8ac38..cc84eba 100644 --- a/MercurySQL/drivers/mysql_driver.py +++ b/MercurySQL/drivers/mysql_driver.py @@ -22,8 +22,7 @@ class Driver_MySQL(BaseDriver): payload = '%s' Conn = mysql.connector.MySQLConnection - # Cursor = mysql.connector.cursor_cext.CMySQLCursor - Cursor = mysql.connector.cursor.MySQLCursor + Cursor = mysql.connector.cursor_cext.CMySQLCursor class APIs: class gensql: From 29fc09c32e97d4c108f96760b1003cc3943954f8 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:10:37 +0800 Subject: [PATCH 8/9] [chore] update requirements --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index ef634fa..023d895 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ sphinx_rtd_theme # for release wheel + +# MySQL +mysql.connector From 9191c10e6455b51efb278f16a6e6601655da9829 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 15:10:52 -0400 Subject: [PATCH 9/9] [feat] new insert syntax support --- MercurySQL/gensql/table.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MercurySQL/gensql/table.py b/MercurySQL/gensql/table.py index 3b5d1e8..c8d2525 100644 --- a/MercurySQL/gensql/table.py +++ b/MercurySQL/gensql/table.py @@ -365,8 +365,15 @@ def insert(self, __auto=False, **kwargs) -> None: table.insert(id=1, name='Bernie', age=15, __auto=True) """ - # get keys and clean them - keys = list(kwargs.keys()) + + # if __auto is a dict + if isinstance(__auto, dict): + keys = list(__auto.keys()) + kwargs = __auto + else: + # get keys and clean them + keys = list(kwargs.keys()) + if "__auto" in keys: __auto = kwargs["__auto"] keys.remove("__auto")