{"id":22568318,"url":"https://github.com/ralgond/mybatis-py","last_synced_at":"2025-04-09T14:10:34.969Z","repository":{"id":266193055,"uuid":"896847637","full_name":"ralgond/mybatis-py","owner":"ralgond","description":"A python ORM like mybatis. It supports MySQL, PostgreSQL and SQLite","archived":false,"fork":false,"pushed_at":"2024-12-24T12:56:40.000Z","size":87,"stargazers_count":39,"open_issues_count":3,"forks_count":10,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-02T10:13:19.560Z","etag":null,"topics":["mybatis","mysql","orm","postgresql","python","sqlite3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ralgond.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-01T13:02:10.000Z","updated_at":"2025-03-17T08:31:49.000Z","dependencies_parsed_at":"2024-12-08T16:47:48.680Z","dependency_job_id":null,"html_url":"https://github.com/ralgond/mybatis-py","commit_stats":null,"previous_names":["ralgond/mybatis-py"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralgond%2Fmybatis-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralgond%2Fmybatis-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralgond%2Fmybatis-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralgond%2Fmybatis-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ralgond","download_url":"https://codeload.github.com/ralgond/mybatis-py/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248054195,"owners_count":21039952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mybatis","mysql","orm","postgresql","python","sqlite3"],"created_at":"2024-12-08T00:12:35.958Z","updated_at":"2025-04-09T14:10:34.941Z","avatar_url":"https://github.com/ralgond.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mybatis-py\n[![Latest Version](https://img.shields.io/pypi/v/mybatis.svg)](https://pypi.org/project/mybatis/)\n[![codecov](https://codecov.io/gh/ralgond/mybatis-py/branch/main/graph/badge.svg)](https://codecov.io/gh/ralgond/mybatis-py)\n\n\nA python ORM like mybatis.\n\n## How to Use\n\n### Install \npip install -U mybatis\n\n### Create Database\n#### mysql\n```sql\nCREATE DATABASE mybatis;\n\nUSE mybatis;\n\nCREATE TABLE IF NOT EXISTS fruits (\n        id INT AUTO_INCREMENT PRIMARY KEY, \n        name VARCHAR(100),\n        category VARCHAR(100),\n        price int);\n\nINSERT INTO fruits (name, category, price) VALUES ('Alice', 'A', 100);\nINSERT INTO fruits (name, category, price) VALUES ('Bob', 'B', 200);\n```\n#### postgresql\n```sql\nCREATE TABLE if not exists fruits (\n        id SERIAL PRIMARY KEY, \n        name VARCHAR(100),\n        category VARCHAR(100),\n        price int);\n        \nINSERT INTO fruits (name, category, price) VALUES ('Alice', 'A', 100);\n\nINSERT INTO fruits (name, category, price) VALUES ('Bob', 'B', 200);\n```\n\n## Decorator\nThe example is as follows:\n```python\nfrom mybatis import Mybatis, ConnectionFactory\n\nconn = ConnectionFactory.get_connection(\n    dbms_name='mysql', # change to 'postgresql' if you are using PostgreSQL\n    host=\"localhost\",\n    user=\"mybatis\",\n    password=\"mybatis\",\n    database=\"mybatis\")\n\nmb = Mybatis(conn, \"mapper\", cache_memory_limit=50*1024*1024)\n\n@mb.SelectOne(\"SELECT * FROM fruits WHERE id=#{id}\")\ndef get_one(id:int):\n    pass\n\n@mb.SelectMany(\"SELECT * FROM fruits\")\ndef get_many():\n    pass\n\n@mb.Insert(\"INSERT INTO fruits (name, category, price) VALUES (#{name}, #{category}, #{price})\", primary_key=\"id\")\ndef insert(name:str, category:str, price:int):\n    pass\n\n@mb.Delete(\"DELETE FROM fruits WHERE id=#{id}\")\ndef delete(id:int):\n    pass\n\n@mb.Update(\"UPDATE fruits SET name=#{name} WHERE id=#{id}\")\ndef update(name:str, id:int):\n    pass\n\nprint(get_one(id=1))\n\nprint(delete(id=4))\n\nprint(get_many())\n\nprint(insert(name=\"Dating\", category=\"D\", price=20))\n\nprint(get_many())\n\nprint(update(name='Amazon', id=1))\n\nprint(get_many())\n```\n\n## Dynamic SQL\n\n### Write Code\n\nrefer to [test_mybatis.py](https://github.com/ralgond/mybatis-py/blob/main/test/test_mybatis.py)、[test2.xml](https://github.com/ralgond/mybatis-py/blob/main/mapper/test.xml)\n\nCreate a mapper directory, and create a file named mapper/test.xml, as follows:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\"\u003e\n\u003cmapper\u003e\n    \u003cinsert id=\"testInsert1\"\u003e\n        INSERT INTO fruits (name, category, price) VALUES (#{name}, #{category}, #{price})\n        \u003cif test=\"'__need_returning_id__' in params\"\u003e\n            RETURNING ${__need_returning_id__}\n        \u003c/if\u003e\n    \u003c/insert\u003e\n    \u003cselect id=\"testBasic1\"\u003e\n        SELECT * from fruits where id=#{id}\n    \u003c/select\u003e\n\u003c/mapper\u003e\n```\nCreate a Python file named \"test.py\" as follows:\n```python\nfrom mybatis import *\n\ndef main():\n    conn = ConnectionFactory.get_connection(\n            dbms_name='mysql', # change to 'postgresql' if you are using PostgreSQL\n            host=\"localhost\",\n            user=\"mybatis\",\n            password=\"mybatis\",\n            database=\"mybatis\")\n\n    mb = Mybatis(conn, \"mapper\", cache_memory_limit=50*1024*1024)\n\n    ret = mb.insert('testInsert1', {'name':'Alice', 'category':'C', 'price':500})\n    ret = mb.select_one(\"testBasic1\", {'id':1})\n\n    print(ret)\n\nif __name__ == \"__main__\":\n    main()\n```\n\n### Namespace\nCreate xml mapper as follows:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\"\u003e\n\u003cmapper namespace=\"test_namespace\"\u003e\n    \u003cinsert id=\"testInsert\"\u003e\n        INSERT INTO fruits (name, category, price) VALUES (#{name},#{category},#{price})\n        \u003cif test=\"'__need_returning_id__' in params\"\u003e\n            RETURNING id\n        \u003c/if\u003e\n    \u003c/insert\u003e\n    \u003cdelete id=\"testDelete\"\u003e\n        DELETE FROM fruits WHERE id=#{id}\n    \u003c/delete\u003e\n    \u003cupdate id=\"testUpdate\"\u003e\n        UPDATE fruits SET name=#{name} WHERE id=#{id}\n    \u003c/update\u003e\n\u003c/mapper\u003e\n```\nWrite python code as follows:\n```python\nfrom mybatis import MapperManager\n\nmm = MapperManager()\nmm.read_mapper_xml_file(\"mapper/test_namespace.xml\")\n\nsql, param_list = mm.insert(\"test_namespace.testInsert\", {'name': 'Alice', 'category': 'A', 'price': 100})\nassert sql == \"INSERT INTO fruits (name, category, price) VALUES (?,?,?)\"\n```\n\n### The difference between ${} and #{} \n#{} is a placeholder that exists for prepared statement and will become the character '?' after processing by MapperManager.\n${} represents simple string replacement. The following example illustrates the difference:\n```python\nfrom mybatis import *\n\nmm = MapperManager()\n\n'''\nThe contents of test.xml are as follows:\n\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\"\u003e\n\u003cmapper\u003e\n    \u003cselect id=\"testStringReplace\"\u003e\n        SELECT * from fruits_${date} where id=#{id}\n    \u003c/select\u003e\n\u003c/mapper\u003e\n'''\nmm.read_mapper_xml_file(\"mapper/test.xml\")\n\nsql, param_list = mm.select(\"testStringReplace\", {'id':1, 'date':\"20241204\"})\nprint(sql, param_list)\n```\nThe result is as follows:\n```bash\nSELECT * from fruits_20241204 where id=? [1]\n```\nYou can see that ${date} is replaced with \"20241204\", and #{id} is replaced with '?', and only one parameter value in param_list is 1.\n\nBased on security considerations, in order to prevent SQL injection, it is recommended not to use ${} as long as #{} can be used, unless you are confident enough.\n\n## Cache\nmybatis-py maintains a cache pool for each connection. The elimination strategy is LRU. You can define the maximum byte capacity of the pool. If you do not want to use cache, you can set the parameter configuration. The code is as follows:\n```python\nfrom mybatis import *\n\ndef main():\n    conn1 = ConnectionFactory.get_connection(\n            dbms_name='mysql', # change to 'postgresql' if you are using PostgreSQL\n            host=\"localhost\",\n            user=\"mybatis\",\n            password=\"mybatis\",\n            database=\"mybatis\")\n\n    conn2 = ConnectionFactory.get_connection(\n            dbms_name='mysql', # change to 'postgresql' if you are using PostgreSQL\n            host=\"localhost\",\n            user=\"mybatis\",\n            password=\"mybatis\",\n            database=\"mybatis\")\n\n    mb1 = Mybatis(conn1, \"mapper\", cache_memory_limit=50*1024*1024) # Capacity limit is 50MB\n    mb2 = Mybatis(conn2, \"mapper\", cache_memory_limit=None) # Disable caching\n```\n### Timeout mechanism\nIn order to prevent users from always getting old data, the cache will determine whether the key-value has expired when fetching a key-value. The maximum life milliseconds of the key-value can be specified through the ```cache_max_live_ms``` parameter in the constructor of the Mybatis class.\n\n## Use in Flask\n### Auto Reconnecting\n```python\nfrom flask import Flask\n\nimport mybatis.errors\nfrom mybatis import Mybatis, ConnectionFactory\nimport orjson as json\nimport functools\n\napp = Flask(__name__)\n\n\n# 连接到 MySQL 数据库\nconn = None\nmb = Mybatis(conn, \"mapper\", cache_memory_limit=50*1024*1024)\nconnection_error = False\nerror_string = \"\"\n\ndef make_connection_and_mybatis():\n    global conn\n    global mb\n    global connection_error\n    global error_string\n\n    if conn is None:\n        try:\n            conn = ConnectionFactory.get_connection(\n                            dbms_name=\"postgresql\",\n                            host=\"localhost\",\n                            user=\"mybatis\",  \n                            password=\"mybatis\", \n                            database=\"mybatis\"\n            )\n            mb.conn = conn\n            mb.conn.set_autocommit(False)\n                \n            return True\n            \n        except Exception as e:\n            connection_error = True\n            error_string = str(e)\n            return False\n    else:\n        try:\n            if connection_error:\n                conn.reconnect(3, 3)\n                connection_error = False\n\n            if mb.conn is None:\n                mb.conn = conn\n            return True\n        except Exception as e:\n            connection_error = True\n            error_string = str(e)\n            return False\n\n\n@mb.SelectOne(\"SELECT * FROM fruits WHERE id=#{id}\")\ndef select_one(id:int):\n    pass\n\n@mb.SelectMany(\"SELECT * FROM fruits\")\ndef select_many():\n    pass\n\n@mb.Insert(\"INSERT INTO fruits (name,category,price) VALUES ('Candy', 'C', 500)\")\ndef insert():\n    pass\n\ndef sql_auto_reconnect(func):\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        global connection_error\n        try:\n            ret = make_connection_and_mybatis()\n            if ret is False:\n                return error_string, 500\n\n            ret = func(*args, **kwargs)\n            return ret, 200\n\n        except mybatis.errors.DatabaseError as e:\n            connection_error = True\n            return str(e), 500\n        except Exception as e:\n            return str(e), 500\n\n    return wrapper\n\n\n@app.route('/')\n@sql_auto_reconnect\ndef hello():\n    ret = select_many()\n    return json.dumps(ret)\n\n\n@app.route('/insert')\n@sql_auto_reconnect\ndef do_insert():\n    ret = insert()\n    return json.dumps(ret)\n\nif __name__ == \"__main__\":\n    app.run(debug=True)\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fralgond%2Fmybatis-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fralgond%2Fmybatis-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fralgond%2Fmybatis-py/lists"}