MySQL Mimic
mysql-mimic is a pure Python implementation of the MySQL server protocol. It enables developers to create fake MySQL servers to intercept connections, log queries, serve custom data, or test database clients without requiring a real MySQL instance. The current version, 3.0.2, primarily features an asynchronous API and aims for robust protocol emulation. Releases generally follow major breaking changes or significant feature additions, with major versions appearing every 2-3 years.
Common errors
-
TypeError: object MySQLServer.on_query() is not async
cause Attempting to define `on_query` (or other server lifecycle methods like `on_connect`, `authenticate`) as a synchronous function after upgrading to `mysql-mimic` v3.0.0+.fixEnsure all custom server methods are defined using `async def`, e.g., `async def on_query(...)`. -
AttributeError: module 'mysql_mimic' has no attribute 'Authenticator'
cause Using the old import path for `Authenticator` (or `SSLContextProvider`) after upgrading to `mysql-mimic` v3.0.0+.fixUpdate import statements to `from mysql_mimic.auth import Authenticator`. -
TypeError: 'str' object is not iterable
cause The `on_query` method is returning a single string instead of a list of tuples as expected by `mysql-mimic` v3.0.0+.fixEnsure `on_query` always returns a `list` where each element is a `tuple` representing a row. E.g., `return [(f"Result: {query}",)]`. -
OSError: [Errno 98] Address already in use
cause Another process (e.g., a real MySQL server or a previous run of mysql-mimic) is already listening on the configured port.fixChange the `port` argument in the `MyServer` constructor to an unused port (e.g., `3307`), or ensure no other applications are using the desired port.
Warnings
- breaking Version 3.0.0 introduced a significant breaking change by moving to an entirely asynchronous API. All custom server methods like `on_query`, `on_connect`, `authenticate` (in `Authenticator`), and `serve()` itself are now `async def` and must be `await`ed or run within an `asyncio` event loop.
- breaking In v3.0.0, `Authenticator` and `SSLContextProvider` classes were moved from the top-level `mysql_mimic` module to `mysql_mimic.auth` and `mysql_mimic.ssl` respectively.
- gotcha The `on_query` method (since v3.0.0) must return a `list` of `tuple`s, representing rows and columns. Returning a single string, tuple, or non-list will lead to a `TypeError` at runtime.
- gotcha Running `mysql-mimic` on the default MySQL port (3306) will fail if another MySQL server or process is already using that port. This is a common issue during development.
Install
-
pip install mysql-mimic
Imports
- MySQLServer
from mysql_mimic import MySQLServer
- Authenticator
from mysql_mimic import Authenticator
from mysql_mimic.auth import Authenticator
- SSLContextProvider
from mysql_mimic import SSLContextProvider
from mysql_mimic.ssl import SSLContextProvider
Quickstart
import asyncio
import logging
import os
from mysql_mimic import MySQLServer
from mysql_mimic.auth import Authenticator
logging.basicConfig(level=logging.INFO)
# Define a test password via an environment variable for security best practices
# In a real scenario, this would be a secure, complex password.
MIMIC_PASSWORD = os.environ.get('MIMIC_TEST_PASSWORD', 'super-secret-password')
MIMIC_HOST = os.environ.get('MIMIC_HOST', '127.0.0.1')
MIMIC_PORT = int(os.environ.get('MIMIC_PORT', 3307)) # Use a non-standard port
class MyAuthenticator(Authenticator):
async def authenticate(self, username, password, database):
logging.info(f"Auth attempt: user='{username}', db='{database}'")
if password == MIMIC_PASSWORD:
logging.info(f"Authentication successful for '{username}'")
return True
logging.warning(f"Authentication failed for '{username}' with provided password.")
return False
class MyServer(MySQLServer):
async def on_query(self, query: str, database: str):
logging.info(f"Query received on '{database}': '{query}'")
# Return a list of tuples, mimicking a result set
if query.lower().startswith("select version()"):
return [("3.0.2",)] # Mimic current version of mysql-mimic
elif query.lower().startswith("select 'hello world'"):
return [("hello world from mysql-mimic!",)]
else:
return [(f"You queried: '{query}'",)]
# This is the simplest way to run for a quickstart in an async context
# Make sure to set MIMIC_TEST_PASSWORD in your environment before running, e.g.:
# export MIMIC_TEST_PASSWORD="super-secret-password"
# python your_script.py
server = MyServer(authenticator=MyAuthenticator(), host=MIMIC_HOST, port=MIMIC_PORT)
logging.info(f"Starting MySQL Mimic server on {MIMIC_HOST}:{MIMIC_PORT}")
server.serve_forever_async()
# To test from your terminal:
# mysql -h 127.0.0.1 -P 3307 -u anyuser -p'super-secret-password' -e "SELECT 'Hello World';"