commit 25f99025ea68b81ed761222f7a286a6535a9f889 Author: old-tom <892955278@msn.cn> Date: Fri Jun 23 00:56:22 2023 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..287a2f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..f58c0e4 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +futool-db-lite \ No newline at end of file diff --git a/.idea/futool-db-lite.iml b/.idea/futool-db-lite.iml new file mode 100644 index 0000000..dfbb3cf --- /dev/null +++ b/.idea/futool-db-lite.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f7476a6 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..663682d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5f2c747 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..7ff7007 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.8" +python_full_version = "3.8.10" diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..b2970a6 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:58 +# @Author : old tom +# @File : __init__.py.py +# @Project : futool-db-lite +# @Desc : diff --git a/config/db_config.py b/config/db_config.py new file mode 100644 index 0000000..7e6f71d --- /dev/null +++ b/config/db_config.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:58 +# @Author : old tom +# @File : db_config.py +# @Project : futool-db-lite +# @Desc : +import os +from pydantic import BaseModel + +import toml + +# 默认配置文件名 +DEFAULT_CONF_NAME = 'db-conf.toml' +path = os.path.dirname(__file__) +path = os.path.dirname(path) +# 默认配置文件位置 +DEFAULT_CONF_PATH = os.path.join(path, DEFAULT_CONF_NAME) +# 配置项名称 +DB_CONF_ITEM_NAME = ['dialect', 'host', 'port', 'user', 'passwd', 'database'] + + +class DbConfigNotFoundError(Exception): + """ + 配置不存在异常 + """ + + def __init__(self, msg): + Exception.__init__(self, msg) + + +class DbConf(BaseModel): + dialect: str + host: str + port: int + user: str + passwd: str + database: str + pool_size: int + pool_recycle: int + show_sql: bool = False + + +class DbConfigLoader(object): + """ + 数据库连接加载 + """ + + def __init__(self, db_name, conf_path): + if not os.path.isfile(conf_path): + raise DbConfigNotFoundError(f'数据配置文件{DEFAULT_CONF_NAME}不存在') + self.db_conf = self.load(db_name, conf_path) + + @staticmethod + def load(dbname, conf_path) -> DbConf: + """ + 校验并加载配置 + :return: + """ + conf = toml.load(conf_path) + return DbConf(**conf[dbname]) diff --git a/db-conf.toml b/db-conf.toml new file mode 100644 index 0000000..776a9d3 --- /dev/null +++ b/db-conf.toml @@ -0,0 +1,19 @@ +[testdb] +# 方言 +dialect = 'postgresql' +# ip +host = '127.0.0.1' +# 端口 +port = 5432 +# 用户名 +user = 'postgres' +# 密码 +passwd = 'root@123' +# 数据库 +database = 'postgres' +# 连接池大小 +pool_size = 15 +# 连接超时回收(秒) +pool_recycle = 3600 +# 打印SQL +show_sql = true \ No newline at end of file diff --git a/executor/__init__.py b/executor/__init__.py new file mode 100644 index 0000000..c30d6d1 --- /dev/null +++ b/executor/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 21:55 +# @Author : old tom +# @File : __init__.py.py +# @Project : futool-db-lite +# @Desc : diff --git a/executor/sql_executor.py b/executor/sql_executor.py new file mode 100644 index 0000000..838b5e0 --- /dev/null +++ b/executor/sql_executor.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 21:55 +# @Author : old tom +# @File : sql_executor.py +# @Project : futool-db-lite +# @Desc : 获取游标,执行SQL +from sqlalchemy import text, CursorResult + +from transaction.connect_transaction import Transaction + + +class SQLExecutorError(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) + + +class SQLExecutor(object): + def __init__(self, tx: Transaction): + self._tx = tx + self._conn = tx.get_connection() + + @staticmethod + def _format_sql(sql): + return text(sql) + + def query(self, sql) -> CursorResult: + try: + return self._conn.execute(self._format_sql(sql)) + except Exception as e: + raise SQLExecutorError(msg=f'{e}') + + def execute_update(self, sql) -> int: + try: + self._tx.begin_transaction() + rt = self._conn.execute(self._format_sql(sql)) + self._tx.commit() + return rt.rowcount + except Exception as e: + self._tx.rollback() + raise SQLExecutorError(msg=f'{e}') + + def get_connection(self): + return self._conn + + def get_transaction(self): + return self._tx diff --git a/main.py b/main.py new file mode 100644 index 0000000..d720931 --- /dev/null +++ b/main.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 21:55 +# @Author : old tom +# @File : main.py +# @Project : futool-db-lite +# @Desc : 数据库测试 +from session.engine.dbengine import DatabaseEngine + +if __name__ == '__main__': + db_engine = DatabaseEngine('testdb') + session_factory = db_engine.create_session_factory() + sql_session = session_factory.open_session() + # print(sql_session.select_all('select * from metadata_object')) + sql_session.select_one('select * from metadata_object') + sql_session.select_many('select * from metadata_object', 2) + # print(sql_session.delete('delete from metadata_object where meta_id=1657707190871527425')) + sql_session.close() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ebb51d6 Binary files /dev/null and b/requirements.txt differ diff --git a/session/__init__.py b/session/__init__.py new file mode 100644 index 0000000..5e3576c --- /dev/null +++ b/session/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:52 +# @Author : old tom +# @File : __init__.py.py +# @Project : futool-db-lite +# @Desc : diff --git a/session/engine/__init__.py b/session/engine/__init__.py new file mode 100644 index 0000000..9a4d012 --- /dev/null +++ b/session/engine/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:53 +# @Author : old tom +# @File : __init__.py.py +# @Project : futool-db-lite +# @Desc : diff --git a/session/engine/dbengine.py b/session/engine/dbengine.py new file mode 100644 index 0000000..ed942b0 --- /dev/null +++ b/session/engine/dbengine.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:54 +# @Author : old tom +# @File : dbengine.py +# @Project : futool-db-lite +# @Desc : +from urllib.parse import quote_plus as urlquote +from sqlalchemy import create_engine +from config.db_config import DbConfigLoader, DEFAULT_CONF_PATH +from session.session import SqlsessionFactory + + +class DatabaseEngineError(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) + + +class DatabaseEngine(object): + """ + 数据库连接 + dialect+driver://username:password@host:port/database + mysql: mysql+pymysql://scott:tiger@localhost/foo + sqlserver: mssql+pymssql://scott:tiger@hostname:port/dbname + """ + DB_URL = { + 'postgresql': 'postgresql+psycopg2://{0}:{1}@{2}:{3}/{4}', + 'oracle': 'oracle+cx_oracle://{0}:{1}@{2}:{3}/?service_name={4}' + } + + def __init__(self, db_name, conf_path=DEFAULT_CONF_PATH): + """ + pool_size:连接池大小 + pool_recycle:连接回收(秒) + pool_pre_ping:测试连接,执行select 1 + 参考 https://docs.sqlalchemy.org/en/20/core/pooling.html#connection-pool-configuration + """ + loader = DbConfigLoader(db_name, conf_path) + # 数据库配置 + conf = loader.db_conf + if conf.dialect not in self.DB_URL: + raise DatabaseEngineError(msg='不支持的数据库类型') + # urlquote 处理密码中的特殊字符 + url = self.DB_URL[conf.dialect].format(conf.user, urlquote(conf.passwd), conf.host, conf.port, conf.database) + self.engine = create_engine(url, pool_size=conf.pool_size, pool_recycle=conf.pool_recycle, pool_pre_ping=True, + echo=conf.show_sql) + + def create_session_factory(self): + """ + 创建session工厂 + :return: + """ + return SqlsessionFactory(self.engine) diff --git a/session/session.py b/session/session.py new file mode 100644 index 0000000..c5fb72b --- /dev/null +++ b/session/session.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 19:53 +# @Author : old tom +# @File : session.py +# @Project : futool-db-lite +# @Desc : +from sqlalchemy import Engine +from executor.sql_executor import SQLExecutor +from transaction.connect_transaction import TransactionFactory + + +class CreateSessionError(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) + + +class SqlsessionFactory(object): + """ + 工厂模式创建sqlSession + """ + + def __init__(self, engine: Engine): + self._engine = engine + + def open_session(self): + try: + conn = self._engine.connect() + tx_factory = TransactionFactory(conn) + tx = tx_factory.create_transaction() + executor = SQLExecutor(tx) + return Sqlsession(executor) + except Exception as e: + raise CreateSessionError(msg=f'创建sqlSession异常,e={e}') + + +class Sqlsession(object): + def __init__(self, executor: SQLExecutor): + self.executor = executor + + def select_one(self, sql): + return self.executor.query(sql).fetchone() + + def select_many(self, sql, size): + return self.executor.query(sql).fetchmany(size) + + def select_all(self, sql): + return self.executor.query(sql).fetchall() + + def insert(self, sql): + return self.executor.execute_update(sql) + + def update(self, sql): + return self.executor.execute_update(sql) + + def delete(self, sql): + return self.executor.execute_update(sql) + + def begin_transaction(self): + """ + 开启事务 + :return: + """ + self.executor.get_transaction().begin_transaction() + + def commit(self): + """ + 提交 + :return: + """ + self.executor.get_transaction().commit() + + def rollback(self): + """ + 回滚 + :return: + """ + self.executor.get_transaction().rollback() + + def close(self): + """ + 关闭连接 + :return: + """ + self.executor.get_connection().close() diff --git a/transaction/__init__.py b/transaction/__init__.py new file mode 100644 index 0000000..4d306ae --- /dev/null +++ b/transaction/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 21:35 +# @Author : old tom +# @File : __init__.py.py +# @Project : futool-db-lite +# @Desc : diff --git a/transaction/connect_transaction.py b/transaction/connect_transaction.py new file mode 100644 index 0000000..3b52560 --- /dev/null +++ b/transaction/connect_transaction.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2023/6/22 21:37 +# @Author : old tom +# @File : connect_transaction.py +# @Project : futool-db-lite +# @Desc : +from sqlalchemy import Connection + + +class TransactionFactory(object): + + def __init__(self, conn: Connection): + self.conn = conn + + def create_transaction(self): + return Transaction(self.conn) + + +class Transaction(object): + def __init__(self, conn: Connection): + self.conn = conn + + def begin_transaction(self): + self.conn.begin() + + def commit(self): + self.conn.commit() + + def rollback(self): + self.conn.rollback() + + def get_connection(self): + return self.conn